其他分享
首页 > 其他分享> > P4基础

P4基础

作者:互联网

basic实验

步骤 1:运行(不完整的)初学者代码

h1 ping h2 失败

图片1.png

pingall 失败

图片2.png

步骤 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 成功

图片3.png

pingall 成功

图片4.png

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目录,能否继续运行?如果不能运行,怎样才能运行。

5.png

不能。因为 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 代码运行所需的相关包文件全部拷贝。

图片1.png

图片2.png

新建一个 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实验项目的相关流规则设置是如何应用的?

pod-topo.png

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 输入处理模块所自定义的流表,匹配域和动作。

图片1.png

标签:hdr,P4,基础,packet,parse,ipv4,ethernet,metadata
来源: https://www.cnblogs.com/dump16/p/16300803.html