1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
|
#define REGISTER_SIZE 1024
#define REGISTER_WIDTH 32
#define PKT_INSTANCE_TYPE_NORMAL 0
#define PKT_INSTANCE_TYPE_INGRESS_CLONE 1
#define PKT_INSTANCE_TYPE_EGRESS_CLONE 2
#define PKT_INSTANCE_TYPE_COALESCED 3
#define PKT_INSTANCE_TYPE_INGRESS_RECIRC 4
#define PKT_INSTANCE_TYPE_REPLICATION 5
#define PKT_INSTANCE_TYPE_RESUBMIT 6
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
// 为各流存储ECMP时的哈希种子,在链路拥塞时修改种子值,即可改变哈希结果
register <bit<REGISTER_WIDTH>>(REGISTER_SIZE) loadbalance_seed;
action drop() {
mark_to_drop(standard_metadata);
}
// 设置下一跳类型是主机还是交换机
action set_egress_type (bit<4> egress_type){
// 将下一跳类型存入自定义元数据中
meta.egress_type = egress_type;
}
// 匹配表存储各出端口对应的下一跳节点类型
table egress_type {
key = {
standard_metadata.egress_spec: exact;
}
actions = {
set_egress_type;
NoAction;
}
size=64;
default_action = NoAction;
}
// 在链路拥塞时触发调整种子值,改变ECMP哈希结果
action update_flow_seed(){
// 生成值介于0~1234567的随机数值作为新的种子
bit<32> seed;
random(seed, (bit<32>)0, (bit<32>)1234567);
// 计算该流在寄存器数组中的位置
bit<12> register_index;
hash(register_index,
HashAlgorithm.crc16,
(bit<1>)0,
{ hdr.ipv4.dstAddr,
hdr.ipv4.srcAddr,
hdr.tcp.srcPort,
hdr.tcp.dstPort,
hdr.ipv4.protocol},
(bit<12>)REGISTER_SIZE);
// 在寄存器的正确位置写入新的种子值
loadbalance_seed.write((bit<32>)register_index, seed);
}
action ecmp_group(bit<14> ecmp_group_id, bit<16> num_nhops) {
// 计算该流在寄存器数组中的位置
bit<12> register_index;
hash(register_index,
HashAlgorithm.crc16,
(bit<1>)0,
{ hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr,
hdr.tcp.srcPort,
hdr.tcp.dstPort,
hdr.ipv4.protocol},
(bit<12>)REGISTER_SIZE);
// 读取新的种子值
bit<32> seed;
loadbalance_seed.read(seed, (bit<32>)register_index);
// 通过五元组+种子值计算哈希值,存储到自定义元数据中
hash(meta.ecmp_hash,
HashAlgorithm.crc16,
(bit<1>)0,
{ hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr,
hdr.tcp.srcPort,
hdr.tcp.dstPort,
hdr.ipv4.protocol,
seed},
num_nhops);
// 存储ECMP组合到自定义元数据中
meta.ecmp_group_id = ecmp_group_id;
}
action set_nhop(macAddr_t dstAddr, egressSpec_t port) {
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = dstAddr;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
standard_metadata.egress_spec = port;
}
table ecmp_group_to_nhop {
key = {
meta.ecmp_group_id: exact;
meta.ecmp_hash: exact;
}
actions = {
drop;
set_nhop;
}
size = 1024;
}
table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
set_nhop;
ecmp_group;
drop;
}
size = 1024;
default_action = drop;
}
apply {
// 从标准元数据中查看数据包类型,如果是recirculate过来的包,则为需要发送的反馈包
if (standard_metadata.instance_type == PKT_INSTANCE_TYPE_INGRESS_RECIRC){
// 反馈包要发送回源节点,因此交换源IP地址与目的IP地址
bit<32> src_ip = hdr.ipv4.srcAddr;
hdr.ipv4.srcAddr = hdr.ipv4.dstAddr;
hdr.ipv4.dstAddr = src_ip;
// 标记该数据包为反馈包
hdr.ethernet.etherType = TYPE_FEEDBACK;
}
// ECMP相关的路由转发
if (hdr.ipv4.isValid() && hdr.ipv4.ttl > 1){
switch (ipv4_lpm.apply().action_run){
ecmp_group: {
ecmp_group_to_nhop.apply();
}
}
}
// 在匹配表中查询下一跳节点的类型
egress_type.apply();
// 反馈包沿着路径反向传播
// 反馈包最后要作用到路径上的第一台交换机,因为ECMP选路是第一台交换机进行的
// 第一个条件:反馈包是正常数据包,不是egress克隆回来的数据包
// 第二个条件:该数据包是一个反馈包
// 第三个条件:下一跳节点是主机,表示是到达了路径上的第一台交换机
if (standard_metadata.instance_type == PKT_INSTANCE_TYPE_NORMAL && hdr.ethernet.etherType == TYPE_FEEDBACK && meta.egress_type == TYPE_EGRESS_HOST){
// 更新其ECMP哈希函数种子值
update_flow_seed();
// 完成作用,将该反馈包丢包
drop();
}
// 克隆出来的反馈包类型为PKT_INSTANCE_TYPE_EGRESS_CLONE
// 在ingress阶段不做处理,将自动到达随后的egress阶段
// 其实克隆出来的数据包直接在ingress阶段即可直接处理、发送
// 猜测原作者是想展示clone和recircle的用法
}
}
|