例程目录文件

p4app.json
或network.py
:描述需要创建的拓扑(拓扑由mininet创建)
reflector.p4
:P4程序
send_receive.py
:用于发送和接收数据包的Python脚本
拓扑描述文件
https://nsg-ethz.github.io/p4-utils/usage.html#json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
{
"p4_src": "reflector.p4",
"cli": true,
"pcap_dump": true,
"enable_log": true,
"topology": {
"assignment_strategy": "l2",
"links": [["h1", "s1"],
"hosts": {
"h1": {
}
},
"switches": {
"s1": {
}
}
}
}
|
pr_src
指出需要编译运行的P4源程序是reflector.p4
pcap_dump
指示是否激活交换机端口上的数据包嗅探,嗅探的数据包保存在.pcap文件中
assignment_strategy
指示网络运行模式:
l2
表示所有的交换机都在L2链路层工作,所有的hosts都会被放在同一个子网10.0.0.0/16中(IP形如10.0.x.y/16),同时每个host的ARP Table也会被自动填充完成
l3
表示所有交换机位于L3网络层工作,每个接口都属于独立的子网:主机IP为10.x.y.2/24,其相连的交换机端口IP地址为10.x.y.1/24(x为交换机ID,y为主机ID);交换机sw1与交换机sw2连接时,sw1端口的IP地址为20.sw1.sw2.1/24,sw2端口的IP地址为20.sw1.sw2.2/24,sw1为sw1交换机编号,sw2为sw2交换机编号
mixed
表示每个host只能和一个交换机连接,连接在同一个交换机上的host属于1 个/24子网中,该交换机作为该host的L3层网络层网关
端侧收发脚本
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
|
#!/usr/bin/env python3
import sys
import socket
import random
import time
from threading import Thread, Event
# Scapy可实现对网络数据包发送、监听、解析等操作
from scapy.all import *
class Sniffer(Thread):
# Sniffer类抓包
def __init__(self, interface="eth0"):
super(Sniffer, self).__init__()
self.interface = interface
self.my_mac = get_if_hwaddr(interface)
self.daemon = True
self.socket = None
self.stop_sniffer = Event()
def isNotOutgoing(self, pkt):
return pkt[Ether].src != self.my_mac
def run(self):
self.socket = conf.L2listen(
type=ETH_P_ALL,
iface=self.interface,
filter="ip"
)
# opened_socket对指定的对象使用.recv进去读取
# prn指定回调函数 每当符合filter的报文被探测到时 就会执行回调函数
# lfilter使得每接收到数据包后 调用函数确定是否可以执行进一步的操作
# stop_filter在处理完每个符合filter的报文后 调用函数判断是否停止监听
# lfilter相当于Python中的continue
# stop_filter相当于Python中的break
sniff(opened_socket=self.socket, prn=self.print_packet, lfilter=self.isNotOutgoing, stop_filter=self.should_stop_sniffer)
def join(self, timeout=None):
self.stop_sniffer.set()
super(Sniffer, self).join(timeout)
def should_stop_sniffer(self, packet):
# 如果停止抓包的 Event 被设置了,就不再抓包
return self.stop_sniffer.isSet()
def print_packet(self, packet):
print("[!] A packet was reflected from the switch: ")
#packet.show()
ether_layer = packet.getlayer(Ether)
print(("[!] Info: {src} -> {dst}\n".format(src=ether_layer.src, dst=ether_layer.dst)))
def get_if():
# 获取interface列表
ifs=get_if_list()
# 在interface列表中查找目标接口 x-eth0
iface=None
for i in get_if_list():
if "eth0" in i:
iface=i
break
if not iface:
print("Cannot find eth0 interface")
exit(1)
return iface
def send_packet(iface, addr):
input("Press the return key to send a packet:")
print("Sending on interface %s to %s\n" % (iface, str(addr)))
# 构造数据包的L2层
pkt = Ether(src=get_if_hwaddr(iface), dst='00:01:02:03:04:05')
# 构造数据包的L3层
pkt = pkt /IP(dst=addr)
# 发送数据包
sendp(pkt, iface=iface, verbose=False)
def main():
# 目标IP地址为10.0.0.2
addr = "10.0.0.2"
# gethostbyname函数尝试将主机名解析为IP地址
# 例如:socket.gethostbyname('www.163.com')
# 将返回:'60.191.81.49'
addr = socket.gethostbyname(addr)
# 获取当前的interface
iface = get_if()
# 从该interface创建线程开始抓包
listener = Sniffer(iface)
listener.start()
time.sleep(0.1)
try:
while True:
# 每0.5秒发送一个数据包
send_packet(iface, addr)
time.sleep(0.5)
except KeyboardInterrupt:
# 键盘键入退出事件 结束监听
print("[*] Stop sniffing")
listener.join(2.0)
if listener.isAlive():
listener.socket.close()
if __name__ == '__main__':
main()
|
交换机反射器程序
反射器把将数据包从入端口发送回去,即数据包从哪来,就发至哪处,因此需要调换源地址和目标地址
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
|
/* -*- P4_16 -*- */
#include <core.p4>
#include <v1model.p4>
/*************************************************************************
*********************** H E A D E R S ***********************************
*************************************************************************/
typedef bit<48> macAddr_t;
// 定义PacketHeader 此处定义的为链路层头部
header ethernet_t {
macAddr_t dstAddr;
macAddr_t srcAddr;
bit<16> etherType;
}
// 定义元数据
struct metadata {
/* empty */
}
// 实例化数据包头部
struct headers {
ethernet_t ethernet;
}
/*************************************************************************
*********************** P A R S E R ***********************************
*************************************************************************/
// 定义解析器
// in 解析器的输入是将要处理的数据包
// out 解析器的输出是完成解析的数据包头部
// inout 解析器的双方向参数是元数据
parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
// 解析器采用状态机的方式组织 以start开始
state start{
// 解析提取数据包packet头部 填充进struct header定义的ethernet中
packet.extract(hdr.ethernet);
// 解析器以accept结束 accept表示解析成功
transition accept;
}
}
/*************************************************************************
************ C H E C K S U M V E R I F I C A T I O N *************
*************************************************************************/
// 定义在入端口的控制流 用于检查数据包校验和 此时省略跳过检查过程
control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
apply { }
}
/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/
// 定义在入端口侧的控制流
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
// 定义action动作
action swap_mac(){
// 定义临时变量tmp
macAddr_t tmp;
// 交换L2链路层头部的源MAC地址与目的MAC地址
tmp = hdr.ethernet.srcAddr;
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = tmp;
}
// 控制逻辑
apply {
// 执行定义的swap_mac动作 以实现源目的MAC地址的对调
swap_mac();
// 在固有元数据中设置出端口号为入端口号
standard_metadata.egress_spec = standard_metadata.ingress_port;
}
}
/*************************************************************************
**************** E G R E S S P R O C E S S I N G *******************
*************************************************************************/
// 定义在出端口侧的控制流 不执行任何动作
control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
apply { }
}
/*************************************************************************
************* C H E C K S U M C O M P U T A T I O N **************
*************************************************************************/
// 定义在出端口的控制流 用于重新计算数据包校验和 此时省略跳过计算过程
control MyComputeChecksum(inout headers hdr, inout metadata meta) {
apply { }
}
/*************************************************************************
*********************** D E P A R S E R *******************************
*************************************************************************/
// 封装器
control MyDeparser(packet_out packet, in headers hdr) {
apply {
// 使用emit函数重新封装struct header定义的ethernet头部
packet.emit(hdr.ethernet);
}
}
/*************************************************************************
*********************** S W I T C H *******************************
*************************************************************************/
// main函数依次调用解析器、流水线、封装器
V1Switch(
// 调用解析器进行数据包解析
MyParser(),
// 检查接收数据包的校验和
MyVerifyChecksum(),
// 入队列侧的Match-Action处理
MyIngress(),
// 入队列侧流水线与出队列侧流水线间存在交换逻辑
// 出队列侧的Match-Action处理
MyEgress(),
// 重新计算新数据包的校验和
MyComputeChecksum(),
// 调用封装器封装数据包
MyDeparser()
) main;
|
执行P4仿真环境
在拓扑描述文件p4app.json
所在的目录执行以下命令,启动网络拓扑:
该命令会自动调用Python脚本,解析拓扑描述文件p4app.json
,创建基于mininet的虚拟网络环境,随后自动编译P4代码,并配置进软件交换机bmv2中

使用xterm h1
命令登陆host1的shell界面,运行命令执行端侧收发Python脚本

需要说明的是:当mininet运行过程中,若更改了P4程序,可以直接在mininet中使用以下命令,重新编译加载新的P4程序,而无需关闭正在运行的mininet
1
|
mininet> p4switch_reboot s1;
|
