例程拓扑

该例程实现了简单的双端口交换机,使得两个主机能够进行通信,为了实现带宽限速,需要使用Meter表。Meter本质就是一个流量计量器,它支持实时统计通过的流量,并根据配置的速率限制流量。底层实现一般是用令牌桶或者类似算法,令牌桶维护两个计数器:桶容量代表允许突发的最大流量、令牌生成速率用于控制流量的平均速率。在交换机中的逻辑实现可采用如下配置,当有数据包到达时:
- 桶里有足够令牌时:包被标记为绿色(正常,合规流量),消耗对应令牌数;
- 桶里令牌不足但尚未完全溢出时:包被标记为黄色(轻微超出限速,可以容忍);
- 令牌完全用完时:包被标记为红色(超出限速,丢弃或降级处理)。
静态配置方案
拓扑描述文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
{
"p4_src": "repeater.p4",
"cli": true,
"pcap_dump": true,
"enable_log": true,
"topology": {
"assignment_strategy": "l2",
"links": [["h1", "s1"], ["h2", "s1"],
"hosts": {
"h1": {},
"h2": {}
},
"switches": {
"s1": {
}
}
}
}
|
交换机程序
main函数
1
2
3
4
5
6
7
8
9
10
11
|
#include <core.p4>
#include <v1model.p4>
V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;
|
定义头部
1
2
3
4
5
6
7
8
9
10
|
const bit<2> MeterColorGreen = 0;
const bit<2> MeterColorYellow = 1;
const bit<2> MeterColorRed = 2;
struct metadata {
bit<2> meter_color;
}
struct headers {
}
|
数据包解析
1
2
3
4
5
6
7
8
|
parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
state start{
transition accept;
}
}
|
检查校验和
1
2
3
|
control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
apply { }
}
|
入队列侧处理
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
|
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
// direct_meter指该meter是直接绑定的,匹配表的每个条目自动关联一个Meter实例
// 在底层实施时,匹配表的每个条目都有一个索引
// 控制平面在添加表项时,条目索引会自动关联对应的Meter实例
direct_meter<bit<2>>(MeterType.bytes) port_meter;
action drop() {
mark_to_drop(standard_metadata);
}
// 读取Meter实例,将令牌桶的状态写入自定义的元数据中
action do_meter() {
port_meter.read(meta.meter_color);
}
// Meter必须在Table中使用,使得每一个表项都可以绑定到一个具体的Meter实例
// 例如table_add meter_table do_meter 1 =>命令是为入端口1创建了Meter实例
// 而table_add meter_table do_meter 2 =>命令是为入端口2创建了Meter实例
table meter_table {
// 根据入端口号匹配、使用Meter实例
key = {
standard_metadata.ingress_port: exact;
}
actions = {
do_meter;
NoAction;
}
default_action = NoAction();
// 本表支持使用port_meter这个Meter
meters = port_meter;
size = 2;
}
apply {
// 为简化代码,转发逻辑静态固定
if (standard_metadata.ingress_port == 1){
standard_metadata.egress_spec = 2;
}
else if (standard_metadata.ingress_port == 2){
standard_metadata.egress_spec = 1;
}
// 初始化元数据
meta.meter_color = MeterColorGreen;
// 根据入端口读取令牌桶状态
meter_table.apply();
// 如果令牌桶中的令牌不足,则将当前数据包丢弃
if (meta.meter_color != MeterColorGreen) {
drop();
}
}
}
|
出队列侧处理
1
2
3
4
5
|
control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
apply { }
}
|
计算校验值
1
2
3
|
control MyComputeChecksum(inout headers hdr, inout metadata meta) {
apply { }
}
|
封装数据包
1
2
3
|
control MyDeparser(packet_out packet, in headers hdr) {
apply { }
}
|
运行测试
Thrift命令行
在启动P4网络后,使用Thrift客户端连接至P4交换机,进而进行配置:
1
2
|
sudo p4run
simple_switch_CLI --thrift-port 9090
|
为端口1添加匹配表规则,即为其关联一个Meter表实例,需留意添加条目后返回的handle值,该值即为该条目的索引编号:
1
|
table_add meter_table do_meter 1 =>
|

通过help命令可以查看设置Meter表的命令,根据提示即可进行Meter实例中令牌的设置:
1
2
|
help meter_set_rates
meter_set_rates port_meter 0 6.25:700000 6.25:700000
|

通过测速可观察到交换机端口1上的限速已生效:
1
2
|
mininet > iperf h1 h2
mininet > iperf h2 h1
|

若需要删除Meter限速,直接删除在meter_table匹配表中的相应条目即可:
1
2
|
help table_delete
table_delete meter_table 0
|

Thrift脚本
使用Python脚本同样可以完成上述在命令行中的操作,以下只提供核心函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# 清空交换机中的匹配表、寄存器等
controllers[sw_name].reset_state()
# 添加匹配表条目,并获取条目的索引编号
entry_handle = controllers[sw_name].table_add('meter_table', 'do_meter', ['1'], [])
# 设置Meter实例
rates = self.get_meter_rates_from_bw(bw)
self.controllers[sw_name].meter_set_rates('port_meter', entry_handle, rates)
# 若需要删除Meter限速,直接删除在meter_table匹配表中的相应条目即可
controllers[sw_name].table_delete('meter_table', entry_handle, True)
# 根据带宽计算出令牌桶CIR和POR所需的参数
def get_meter_rates_from_bw(self, bw, burst_size=700000):
# bw (float): desired bandwdith in mbps
# burst_size (int, optional): Max capacity of the meter buckets. Defaults to 50000.
rates = []
rates.append((0.125 * bw, burst_size))
rates.append((0.125 * bw, burst_size))
return rates
|