利用p4实现ipv6转发实验
写在前面
只是作为一个入门p4的实验尝试,借用了一些即成的运行代码。
p4代码
/**p4_16,v1_model**/
1 #include<core.p4> 2 #include<v1model.p4> 3 4 const bit<16> TYPE_IPV6 = 0x08DD;//ipv6在以太网中的id 5 6 /*HEADERS*/ 7 8 typedef bit<9> egressSpec_t; 9 typedef bit<48> macAddr_t; 10 11 header ethernet_t{ 12 macAddr_t dstAddr; 13 macAddr_t srcAddr; 14 bit<16> etherType; 15 } 16 17 header ipv6_t{ 18 bit<4> version; 19 bit<8> trafficClass; 20 bit<20> flowLabel; 21 bit<16> payLoadLen; 22 bit<8> nextHdr; 23 bit<8> hopLimit; 24 bit<128> srcAddr; 25 bit<128> dstAddr; 26 } 27 28 struct metadata{ 29 } 30 31 struct headers{ 32 ethernet_t ethernet; 33 ipv6_t ipv6; 34 } 35 36 /*PARSER*/ 37 38 parser MyParser(packet_in packet,out headers hdr,inout metadata meta,inout standard_metadata_t standard_metadata){ 39 state start{ 40 transition parse_ethernet;//start开始先以底层eth解析 41 } 42 43 state parse_ethernet{ 44 packet.extract(hdr.ethernet); 45 transition select(hdr.ethernet.etherType){ 46 TYPE_IPV6:parse_ipv6;//转至ipv6解析 47 default:accept; 48 } 49 } 50 51 state parse_ipv6{ 52 packet.extract(hdr.ipv6); 53 transition accept; 54 } 55 } 56 57 /*CHECKSUM VERIFICATION*/ 58 59 control MyVerifyChecksum(inout headers hdr,inout metadata meta){ 60 apply{} 61 } 62 63 /*INGRESS PROCESSING*/ 64 65 control MyIngress(inout headers hdr,inout metadata meta,inout standard_metadata_t standard_metadata){ 66 action drop(){ 67 mark_to_drop();//将要丢弃的包标记为丢弃 68 } 69 70 action ipv6_forward(macAddr_t dstAddr,egressSpec_t port){ 71 standard_metadata.egress_spec = port; 72 hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; 73 hdr.ethernet.dstAddr = dstAddr; 74 hdr.ipv6.hopLimit = hdr.ipv6.hopLimit - 1;//这个类似ipv4中ttl,为0时就超时 75 } 76 77 table ipv6_lpm{ 78 key = { 79 hdr.ipv6.dstAddr: lpm;//lpm是最长前缀匹配,exact完全匹配,ternary三元匹配 80 } 81 82 actions = { 83 ipv6_forward;//转发 84 drop;//丢弃 85 NoAction;//空动作 86 } 87 88 size = 1024;//流表项容量 89 90 default_action = drop();//table miss则丢弃 91 } 92 93 apply{ 94 if(hdr.ipv6.isValid()){ 95 ipv6_lpm.apply(); 96 } 97 } 98 } 99 100 /*EGRESS PROCESSING*/ 101 102 control MyEgress(inout headers hdr,inout metadata meta,inout standard_metadata_t standard_metadata){ 103 apply{} 104 } 105 106 /*CHECKSUM COMPUTATION*/ 107 108 control MyComputeChecksum(inout headers hdr,inout metadata meta){ 109 apply{} 110 } 111 112 /*DEPARSER*/ 113 114 control MyDeparser(packet_out packet,in headers hdr){ 115 apply{ 116 packet.emit(hdr.ethernet); 117 packet.emit(hdr.ipv6); 118 } 119 } 120 121 /*SWITCH*/ 122 123 V1Switch( 124 MyParser(), 125 MyVerifyChecksum(), 126 MyIngress(), 127 MyEgress(), 128 MyComputeChecksum(), 129 MyDeparser() 130 )main;
实验拓扑
这里实验的拓扑用来最简单的一个交换机下挂两个主机,交换机是bmv2。其json文件书写如下: 交换机s1的流表项通过s1-commands.txt文件定义。
1 { 2 "program": "ipv6_forward.p4", 3 "language": "p4-16", 4 "targets": { 5 "multiswitch": { 6 "auto-control-plane": true, 7 "cli": true, 8 "pcap_dump": true, 9 "bmv2_log": true, 10 "links": [["h1", "s1"], ["h2", "s1"]], 11 "hosts": { 12 "h1": { 13 }, 14 "h2": { 15 } 16 }, 17 "switches": { 18 "s1": { 19 "entries": "s1-commands.txt" 20 } 21 } 22 } 23 } 24 }
在s1-commans.txt文件中做如下定义:
1 table_set_default ipv6_lpm drop 2 table_add ipv6_lpm ipv6_forward fe80::5678/128 => 00:04:00:00:00:02 2 3 table_add ipv6_lpm ipv6_forward fe80::1234/128 => 00:04:00:00:00:01 1
将其流表项画出来如下:
收发包脚本代码
鉴于对python socket发包代码不是那么了解,所以套用了一部分模板做了修改。
send.py
1 import argparse 2 import sys 3 import socket 4 import random 5 import struct 6 7 from scapy.all import sendp, send, get_if_list, get_if_hwaddr 8 from scapy.all import Packet 9 from scapy.all import Ether, IPv6, UDP 10 11 def get_if(): 12 ifs=get_if_list() 13 iface=None 14 for i in get_if_list(): 15 if "eth0" in i: 16 iface=i 17 break; 18 if not iface: 19 print "Cannot find eth0 interface" 20 exit(1) 21 return iface 22 23 def main(): 24 25 if len(sys.argv)<3: 26 print 'pass 3 arguments:<source> <destination> "<message>"' 27 exit(1) 28 saddr = sys.argv[1] 29 addr = sys.argv[2] 30 iface = get_if() 31 32 print "sending on interface %s to %s" % (iface, str(addr)) 33 pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') / IPv6(src=saddr,dst=addr) / UDP(dport=4321, sport=1234) / sys.argv[3] 34 pkt.show2() 35 sendp(pkt, iface=iface, verbose=False) 36 37 38 if __name__ == '__main__': 39 main()
receive.py
1 import sys 2 import struct 3 import os 4 5 from scapy.all import sniff, sendp, hexdump, get_if_list, get_if_hwaddr 6 from scapy.all import Packet, IPOption 7 from scapy.all import ShortField, IntField, LongField, BitField, FieldListField, FieldLenField 8 from scapy.all import IPv6, TCP, UDP, Raw 9 from scapy.layers.inet import _IPOption_HDR 10 11 def get_if(): 12 ifs=get_if_list() 13 iface=None 14 for i in get_if_list(): 15 if "eth0" in i: 16 iface=i 17 break; 18 if not iface: 19 print "Cannot find eth0 interface" 20 exit(1) 21 return iface 22 23 class IPOption_MRI(IPOption): 24 name = "MRI" 25 option = 31 26 fields_desc = [ _IPOption_HDR, 27 FieldLenField("length", None, fmt="B", 28 length_of="swids", 29 adjust=lambda pkt,l:l+4), 30 ShortField("count", 0), 31 FieldListField("swids", 32 [], 33 IntField("", 0), 34 length_from=lambda pkt:pkt.count*4) ] 35 def handle_pkt(pkt): 36 print "got a packet" 37 pkt.show2() 38 #hexdump(pkt) 39 sys.stdout.flush() 40 41 42 def main(): 43 ifaces = filter(lambda i: 'eth' in i, os.listdir('/sys/class/net/')) 44 iface = ifaces[0] 45 print "sniffing on %s" % iface 46 sys.stdout.flush() 47 sniff(filter="udp and port 4321",iface = iface, 48 prn = lambda x: handle_pkt(x)) 49 50 if __name__ == '__main__': 51 main()
测试
1 ./run.sh 2 //在mininet cli中 3 xterm h1 h2 4 //在h2中 5 ./receive.py 6 //在h1中 7 ./send.py fe80::1234 fe80::5678 "Hello p4!"
结果如下:
牢骚
第一次尝试构建p4相关的实验,了解了一部分p4的语法,以及一些的工作原理,也发掘出一部分并未了解的知识内容待后续去琢磨学习。过程有很多不完美,结果也有很多不完美,虽然现在也不满足,但是寒假也快结束了。