P4基础
作者:互联网
basic实验
步骤 1:运行(不完整的)初学者代码
h1 ping h2 失败
pingall 失败
步骤 2:实现 L3 转发
Parser解析数据包
从start状态开始,每一个状态便解析一种协议,然后根据低层协议的类型字段,选择解析高一层协议的状态,然后transition到该状态解析上层协议,最后transition到accept。
parser MyParser(packet_in packet,
out headers hdr, //out相当于输出的数据,type是headers
inout metadata meta, //inout同时作为输入和输出值,类似c++里面的引用
inout standard_metadata_t standard_metadata) {
state start {//①
transition parse_ethernet; //转移到parse_ethernet状态(解析以太网包头)
}
state parse_ethernet {
packet.extract(hdr.ethernet); //提取以太网包头
transition select(hdr.ethernet.etherType) { //根据etherType的值(协议类型)选择下一个状态
//类似switch...case
TYPE_IPV4: parse_ipv4; //转换到parse_ipv4状态(解析ip包头)
default: accept; //默认是接受,进入下一步处理
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4); //提取ip包头
transition accept; //接受,进入下一步处理
}
}
Ingress输入处理
定义一个用于转发的流表,定义匹配域和动作。
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop(standard_metadata); //内置函数,将当前数据包标记为即将丢弃的数据包
}
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {//②
//转发需要以下几个步骤
standard_metadata.egress_spec = port; //设置下一跃点的出口端口
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; //使用交换机的地址更新以太网源地址
hdr.ethernet.dstAddr = dstAddr; //使用下一跃点的地址更新以太网目标地址
hdr.ipv4.ttl = hdr.ipv4.ttl - 1; //递减生存时间TTL
}
table ipv4_lpm {
key = { //匹配域
hdr.ipv4.dstAddr: lpm; //匹配字段是数据包头部字段中的ipv4头部的目标地址
//lpm是最长前缀匹配
}
actions = { //动作类型集合
ipv4_forward; //自定义的转发动作
drop; //丢弃动作
NoAction; //空动作
}
size = 1024; //流表可以容纳多少流表项
default_action = drop(); //默认是丢弃
}
apply {//③
if (hdr.ipv4.isValid()) {
ipv4_lpm.apply(); //仅当解析成功时,应用ipv4_lpm
}
}
}
Deparser逆解析器
control MyDeparser(packet_out packet, in headers hdr) {
apply {//④
packet.emit(hdr.ethernet); //按顺序,发射
packet.emit(hdr.ipv4);
}
}
步骤 3:运行解决方案
h1 ping h2 成功
pingall 成功
basic_tunnel实验
步骤 1:实现基本隧道
Parser解析数据包
parser MyParser(packet_in packet,
out headers hdr, //out相当于输出的数据,type是headers
inout metadata meta, //inout同时作为输入和输出值,类似c++里面的引用
inout standard_metadata_t standard_metadata) {
state start {
transition parse_ethernet; //转移到parse_ethernet状态(解析以太网包头)
}
state parse_ethernet {
packet.extract(hdr.ethernet); //提取以太网包头
transition select(hdr.ethernet.etherType) { //根据etherType的值(协议类型)选择下一个状态
//类似switch
TYPE_MYTUNNEL: parse_myTunnel; //转移到parse_myTunnel状态(隧道)
TYPE_IPV4: parse_ipv4; //转换到parse_ipv4状态(解析ip包头)
default: accept; //默认是接受,进入下一步处理
}
}
state parse_myTunnel {//①
packet.extract(hdr.myTunnel);
transition select(hdr.myTunnel.proto_id) {
TYPE_IPV4: parse_ipv4;
default: accept;
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4); //提取ip包头
transition accept; //接受,进入下一步处理
}
}
Ingress输入处理
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop(standard_metadata); //内置函数,将当前数据包标记为即将丢弃的数据包
}
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {//②
//转发需要以下几个步骤
standard_metadata.egress_spec = port; //设置下一跃点的出口端口
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; //使用交换机的地址更新以太网源地址
hdr.ethernet.dstAddr = dstAddr; //使用下一跃点的地址更新以太网目标地址
hdr.ipv4.ttl = hdr.ipv4.ttl - 1; //递减生存时间TTL
}
table ipv4_lpm {
key = { //匹配域
hdr.ipv4.dstAddr: lpm; //匹配字段是数据包头部字段中的ipv4头部的目标地址
//lpm是最长前缀匹配
}
actions = { //动作类型集合
ipv4_forward; //自定义的转发动作
drop; //丢弃动作
NoAction; //空动作
}
size = 1024; //流表可以容纳多少流表项
default_action = drop(); //默认是丢弃
}
//②将出口端口号设置为控制平面提供的端口号
action myTunnel_forward(egressSpec_t port) {
standard_metadata.egress_spec = port;
}
//③如果表中myTunnel_forward存在匹配项,则该表应调用该drop动作,否则应调用该动作。
table myTunnel_exact {
key = {
hdr.myTunnel.dst_id: exact;//要匹配的动作和模式,exact是精准匹配
}
actions = { //可选动作
myTunnel_forward;
drop;
}
size = 1024;
default_action = drop(); //默认是丢弃
}
apply {//④
if (hdr.myTunnel.isValid()) { //隧道包
myTunnel_exact.apply();
}
else if (hdr.ipv4.isValid()) { //ipv4包
ipv4_lpm.apply();
}
}
}
Deparser逆解析器
control MyDeparser(packet_out packet, in headers hdr) {//⑤
apply {
packet.emit(hdr.ethernet); //按顺序,发射
packet.emit(hdr.myTunnel); //加上隧道包
packet.emit(hdr.ipv4);
}
}
步骤 2:运行解决方案
(1)h1运行 ./send.py 10.0.2.2 "P4 is cool"
,只设置IPv4转发,h2成功接收,并且含有ip头;
(2)h1运行 ./send.py 10.0.2.2 "P4 is cool" --dst_id 2
,同时设置IPv4转发和隧道转发,目标都是h2,h2成功接收,并且包头解析到隧道信息;
(3)h1运行 ./send.py 10.0.3.3 "P4 is cool"
,同时设置IPv4转发给h3和隧道转发给h2,验证转发优先级,结果h2成功接收,并且包头解析到隧道信息,而h3不会接收;
思考
1.如果将basic和basic_tunnel项目文件移出tutorials/exercise目录,能否继续运行?如果不能运行,怎样才能运行。
不能。因为 basic 和 basic_tunnel 项目文件的 Makefile 中使用了include ../../utils/Makefile
,include 关键字的含义是把别的 Makefile 文件包含进来,而将 basic 和 basic_tunnel 项目文件移出 tutorials/exercise目录,此处引用的 Makefile 的路径出现错误。根据路径进一步查看 tutorials/utils/Makefile 文件,由于 basic 项目中的 Makefile 文件已有 TOPO 的另行定义,因此会覆盖此处 TOPO=topology.json 的定义。而 RUN_SCRIPT 涉及 run_exercise.py ,需要将该 python 代码运行所需的相关包文件全部拷贝。
新建一个 p4examples 目录,把 tutorials/exercise/basic/ 整体拷入,将原有拓扑文件整合到新建的 topo 目录,修改 topology.json 文件中每台交换机配置文件的目录,以s1交换机为例,改成 “topo/s1-runtime.json”,新建 script 目录,整合所需要构建的包含P4交换机的网络拓扑的 Python 代码,并修改 Makefile 文件。如此,在 p4examples/basic 目录下即可运行成功。
2.阅读basic实验提供的send.py和receive.py脚本,分析程序导入的python包scapy,说明各自的实现逻辑是什么样的?
send.py:
①首先使用 gethostbyname() 函数,用域名或主机名获取IP地址;
②使用自定义的 get_if() 函数获取源主机的发送端口,作为发送数据的端口;
③使用 Ether() 函数设置发送方和接收方的 MAC 地址;
④使用 IP() 和 TCP() 构造一个 HTTP 数据包;
⑤最后使用 sendp() 函数发送 Ether 数据包。
receive.py:
①使用 os.listdir() 返回 /sys/class/net/ 列表中包含 “eth” 的名称,即物理网卡、子网卡、虚拟VLAN网卡,赋值给ifaces;iface为ifaces中第0个元素;
②使用sys.stdout.flush()显示地让缓冲区的内容输出;
③sniff(iface = iface,prn = lambda x: handle_pkt(x))对于指定的网络接口进行抓包。同时prn定义回调函数handle_pkt(),当符合filter的流量被捕获时,就会执行回调函数,把收到的包里面的东西立刻打印出来。
3.同样的拓扑在传统网络中是如何实现IPv4转发的,网关在这当中起到了什么作用,basic实验项目的相关流规则设置是如何应用的?
IPv4转发的实现:
主机 ( h1, h2, h3, h4) 发送数据报,交换机 ( s1, s2, s3, s4) 对数据报进行转发。具体过程如下:
①主机发送 ARP 广播获取网关 MAC 地址;
②交换机形成该主机的 MAC 表项,并用网关 MAC 地址回应该主机的 ARP 请求;
③该主机把网关 MAC 地址当作目的主机的 MAC 地址,来访问目的主机;
④交换机接收到一个数据报,将这个数据报头部的目的 MAC 信息提取出来,与自身的 MAC 地址表比较:如果找到对应项,则按 MAC 表进行转发;如果没找到对应项,则在除了接收到数据包以外的所有端口进行转发(广播)。
网关的作用:
连接相同或不同类型网络,并且能找到网络中数据传输最合适的路径,即路由选择。
相关流规则设置的应用:
载入静态流表项时采用 runtime 方法:在 sx-runtime.json 文件中,定义一个个具体的流表项,标明了流表项所处的位置,匹配域,匹配模式,动作名,以及动作参数。而这些字段依赖于 P4 代码中Ingress 输入处理模块所自定义的流表,匹配域和动作。
标签:hdr,P4,基础,packet,parse,ipv4,ethernet,metadata 来源: https://www.cnblogs.com/dump16/p/16300803.html