├── Assignment ├── .DS_Store ├── P4_tutorial.pdf ├── README.md ├── exercises │ ├── .DS_Store │ └── acl │ │ ├── Makefile │ │ ├── acl.p4 │ │ ├── receive.py │ │ ├── s1-acl.json │ │ ├── send.py │ │ └── topology.json ├── p4-cheat-sheet.pdf └── utils │ ├── Makefile │ ├── mininet │ └── p4_mininet.py │ ├── netstat.py │ ├── p4_mininet.py │ ├── p4apprunner.py │ ├── p4runtime_lib │ ├── __init__.py │ ├── bmv2.py │ ├── convert.py │ ├── error_utils.py │ ├── helper.py │ ├── simple_controller.py │ └── switch.py │ ├── p4runtime_switch.py │ └── run_exercise.py ├── Course_Material ├── .DS_Store ├── 1-Introduction.pdf ├── 1-Introduction.r.pdf ├── 10-Data-Plane-Programming-part1.pdf ├── 11-Data-Plane-Programming-part2.pdf ├── 12-Virtualization-part1.pdf ├── 13-Virtualization-part2.pdf ├── 14-NFV.pdf ├── 2-SDN-Research-History.pdf ├── 3-SDN.pdf ├── 4-OpenFlow-Part1.pdf ├── 5-Tools.pdf ├── 6-Mininet_OVS.pdf ├── 7-RYU-Controller.pdf ├── 8-OpenFlow-Part2.pdf ├── 9-OpenFlow-Part3.pdf ├── Assignment-1.pdf ├── lec01-overview.pdf └── lec2-overview.pdf ├── Exercises ├── .DS_Store ├── ARP_Proxy_Switch.py ├── HOW TO USE.txt ├── Makefile ├── Makefile 2 ├── acl │ ├── Makefile │ ├── acl.p4 │ ├── receive.py │ ├── s1-acl.json │ ├── send.py │ └── topology.json ├── acl_tunnel.p4 ├── basic_tunnel.p4 ├── controller.py ├── l2_without_loop.py ├── myTunnel_header.py ├── mytopo.py ├── mytopology.py ├── netstat.py ├── p4_mininet.py ├── p4apprunner.py ├── p4runtime_lib │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── bmv2.cpython-38.pyc │ │ ├── convert.cpython-38.pyc │ │ ├── helper.cpython-38.pyc │ │ ├── simple_controller.cpython-38.pyc │ │ └── switch.cpython-38.pyc │ ├── bmv2.py │ ├── convert.py │ ├── error_utils.py │ ├── helper.py │ ├── simple_controller.py │ └── switch.py ├── p4runtime_switch.py ├── receive.py ├── run_exercise.py ├── runtime.py ├── s1-runtime.json ├── s2-runtime.json ├── s3-runtime.json ├── send.py ├── topo-2.png ├── topo-sdn-disabled.py ├── topo-stp-enabled.py ├── topo.pdf ├── topo.png ├── topology.json └── utils │ ├── Makefile │ ├── mininet │ └── p4_mininet.py │ ├── netstat.py │ ├── p4_mininet.py │ ├── p4apprunner.py │ ├── p4runtime_lib │ ├── __init__.py │ ├── bmv2.py │ ├── convert.py │ ├── error_utils.py │ ├── helper.py │ ├── simple_controller.py │ └── switch.py │ ├── p4runtime_switch.py │ └── run_exercise.py ├── Final_Project ├── .DS_Store ├── .ipynb_checkpoints │ ├── main-checkpoint.ipynb │ └── nmt_with_attention-checkpoint.ipynb ├── BPFabric Data Plane Programmability for Software Defined Network.pdf ├── __pycache__ │ ├── environment.cpython-38.pyc │ └── service_batch_generator.cpython-38.pyc ├── comparison.png ├── environment.py ├── main.ipynb ├── main_article_results │ ├── .DS_Store │ ├── agent_loss_arr.npy │ ├── estimated_value_arr.npy │ ├── lagrangian_arr.npy │ ├── non_zero_arr.npy │ ├── penalty_arr.npy │ ├── reward_arr.npy │ └── ve_loss_arr.npy └── service_batch_generator.py ├── LICENSE ├── P4_tutorial.pdf ├── README.md ├── cheat_sheet_src ├── .DS_Store ├── main.tex └── src │ ├── actions.txt │ ├── adv_parsing.txt │ ├── architecture.txt │ ├── control_flow.txt │ ├── counters.txt │ ├── data_types.txt │ ├── deparsing.txt │ ├── expressions.txt │ ├── header_stack.txt │ ├── parsers.txt │ ├── tables.txt │ └── v1model_std_metadata.txt ├── mininet ├── appcontroller.py ├── apptopo.py ├── multi_switch_mininet.py ├── p4_mininet.py ├── shortest_path.py └── single_switch_mininet.py └── p4-cheat-sheet.pdf /Assignment/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Assignment/.DS_Store -------------------------------------------------------------------------------- /Assignment/P4_tutorial.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Assignment/P4_tutorial.pdf -------------------------------------------------------------------------------- /Assignment/README.md: -------------------------------------------------------------------------------- 1 | # Implementing Tunneling with P4 2 | 3 | 4 | ## Introduction 5 | 6 | In this exercise, we will add support for a tunneling to routers. Your job is to change the P4 code to have the switches add the `myTunnel` header to an IP packet upon ingress to the network and then remove the myTunnel header as the packet leaves to the network to an end host. You have to define a new header type to encapsulate the IP packet and modify the switch code, so that it instead decides the destination port using a new tunnel header. 7 | The new header type will contain a protocol ID, which indicates the type of packet being encapsulated, along with a destination ID to be used for routing. 8 | 9 | 10 | - **Start early and feel free to ask questions on Github and [CW](https://cw.sharif.edu)**. 11 | - **You can also stop by [Performance and Dependability Lab (PDL)](http://pdl.ce.sharif.edu/) (Room #815, CE Dep) with prior appointment.** 12 | 13 | 14 | ## Obtaining required software 15 | 16 | 1. Follow the instructions in [P4 lang tutorials](https://github.com/p4lang/tutorials) and install the required VM. 17 | 2. install `iperf3` on the VM. 18 | 19 | ```bash 20 | sudo apt-get install iperf3 21 | ``` 22 | 3. On the VM, cloen this repository 23 | 24 | ```bash 25 | git clone -b spring-2022 https://github.com/arshiarezaei/course-net.git 26 | ``` 27 | 28 | ## Step 1: Implement Tunneling 29 | 30 | A complete implementation of the `basic_tunnel.p4` switch will be able to forward based on the contents of a custom encapsulation header as well as perform normal IP forwarding if the encapsulation header does not exist in the packet. 31 | 32 | 33 | 1. **NOTE:** A new header type has been added called `myTunnel_t` that contains 34 | two 16-bit fields: `proto_id` and `dst_id`. 35 | 2. **NOTE:** The `myTunnel_t` header has been added to the `headers` struct. 36 | 37 | ## Step 2: Implement ACL for the Switch `s3` 38 | 39 | In the switch `s3` you must also add a simple access control list (ACL) that simply drops every packet with `UDP destination port == 80` and `ipv4 dstAddress==10.0.3.3`. 40 | 41 | **Important: if you hard code ACL roles in the data plane you lose some points.** 42 | 43 | ### Step 3: Add Forwarding Table Enteries 44 | 45 | A P4 program defines a packet-processing pipeline, but the rules within each table are inserted by the control plane. When a rule matches a packet, its action is invoked with parameters supplied by the control plane as part of the rule. 46 | 47 | For this exercise, you have to added the necessary rules to `sX-runtime.json`. 48 | 49 | 50 | **Important: You will be asked to modify the forwarding behavior of the control plane.** 51 | 52 | 53 | ## Step 3: Run your solution 54 | 55 | 1. In your shell, run: 56 | ```bash 57 | make run 58 | ``` 59 | This will: 60 | * compile `tunnel.p4` and `acl_tunnel.p4` 61 | * start a Mininet instance with three switches (`s1`, `s2`, `s3`) configured 62 | in a triangle, each connected to one host (`h1`, `h2`, and `h3`). 63 | * The hosts are assigned IPs of `10.0.1.1`, `10.0.2.2`, and `10.0.3.3`. 64 | 65 | 2. You should now see a Mininet command prompt. Open two terminals for `h1` and 66 | `h2`, respectively: 67 | 68 | ```bash 69 | mininet> xterm h1 h2 70 | ``` 71 | 3. Each host includes a small Python-based messaging client and server. In 72 | `h2`'s xterm, start the server: 73 | 74 | ```bash 75 | ./receive.py 76 | ``` 77 | 4. First we will test without tunneling. In `h1`'s xterm, send a message to`h2`: 78 | 79 | ```bash 80 | ./send.py 10.0.2.2 "P4 is cool" 81 | ``` 82 | The packet should be received at `h2`. If you examine the received packet 83 | you should see that is consists of an Ethernet header, an IP header, a TCP 84 | header, and the message. If you change the destination IP address (e.g. try 85 | to send to `10.0.3.3`) then the message should not be received by `h2`, and 86 | will instead be received by `h3`. 87 | 88 | 5. Type `exit` or `Ctrl-D` to leave each xterm and the Mininet command line. 89 | 90 | 91 | #### Cleaning up Mininet 92 | 93 | In the latter two cases above, `make` may leave a Mininet instance running in 94 | the background. Use the following command to clean up these instances: 95 | 96 | ```bash 97 | make stop 98 | ``` 99 | 100 | ### Policy 101 | 102 | If you violate any of the following rules, you will get `0` for this assignment. 103 | 104 | 1.You must complete this assignment individually. 105 | 2.You are not allowed to share your code (or a peace of it) with other students. 106 | 3.If you use code on the internet you should cite the source(s). 107 | 108 | 109 | ## Submission 110 | You must submit: 111 | 112 | * Your source code for the exercises, in a folder called `P4-assignment` and submit an `assignment2.zip` file. Make sure to submit both the p4 code and the corresponding json file that configures the table entries. 113 | -------------------------------------------------------------------------------- /Assignment/exercises/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Assignment/exercises/.DS_Store -------------------------------------------------------------------------------- /Assignment/exercises/acl/Makefile: -------------------------------------------------------------------------------- 1 | BMV2_SWITCH_EXE = simple_switch_grpc 2 | NO_P4 = true 3 | P4C_ARGS = --p4runtime-file $(basename $@).p4info --p4runtime-format text 4 | 5 | include ../../utils/Makefile 6 | -------------------------------------------------------------------------------- /Assignment/exercises/acl/acl.p4: -------------------------------------------------------------------------------- 1 | /* -*- P4_16 -*- */ 2 | #include 3 | #include 4 | 5 | const bit<16> TYPE_IPV4 = 0x800; 6 | 7 | /************************************************************************* 8 | *********************** H E A D E R S *********************************** 9 | *************************************************************************/ 10 | 11 | typedef bit<9> egressSpec_t; 12 | typedef bit<48> macAddr_t; 13 | typedef bit<32> ip4Addr_t; 14 | 15 | header ethernet_t { 16 | macAddr_t dstAddr; 17 | macAddr_t srcAddr; 18 | bit<16> etherType; 19 | } 20 | 21 | header ipv4_t { 22 | bit<4> version; 23 | bit<4> ihl; 24 | bit<8> diffserv; 25 | bit<16> totalLen; 26 | bit<16> identification; 27 | bit<3> flags; 28 | bit<13> fragOffset; 29 | bit<8> ttl; 30 | bit<8> protocol; 31 | bit<16> hdrChecksum; 32 | ip4Addr_t srcAddr; 33 | ip4Addr_t dstAddr; 34 | } 35 | 36 | 37 | struct metadata { 38 | /* empty */ 39 | } 40 | 41 | struct headers { 42 | ethernet_t ethernet; 43 | ipv4_t ipv4; 44 | } 45 | 46 | /************************************************************************* 47 | *********************** P A R S E R *********************************** 48 | *************************************************************************/ 49 | 50 | parser MyParser(packet_in packet, 51 | out headers hdr, 52 | inout metadata meta, 53 | inout standard_metadata_t standard_metadata) { 54 | 55 | state start { 56 | transition parse_ethernet; 57 | } 58 | 59 | state parse_ethernet { 60 | packet.extract(hdr.ethernet); 61 | transition select(hdr.ethernet.etherType) { 62 | TYPE_IPV4: parse_ipv4; 63 | default: accept; 64 | } 65 | } 66 | 67 | state parse_ipv4 { 68 | packet.extract(hdr.ipv4); 69 | transition select(hdr.ipv4.protocol) { 70 | default: accept; 71 | } 72 | } 73 | 74 | 75 | } 76 | 77 | /************************************************************************* 78 | ************ C H E C K S U M V E R I F I C A T I O N ************* 79 | *************************************************************************/ 80 | 81 | control MyVerifyChecksum(inout headers hdr, inout metadata meta) { 82 | apply { } 83 | } 84 | 85 | 86 | /************************************************************************* 87 | ************** I N G R E S S P R O C E S S I N G ******************* 88 | *************************************************************************/ 89 | 90 | control MyIngress(inout headers hdr, 91 | inout metadata meta, 92 | inout standard_metadata_t standard_metadata) { 93 | action drop() { 94 | mark_to_drop(); 95 | } 96 | 97 | action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) { 98 | standard_metadata.egress_spec = port; 99 | hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; 100 | hdr.ethernet.dstAddr = dstAddr; 101 | hdr.ipv4.ttl = hdr.ipv4.ttl - 1; 102 | } 103 | 104 | table ipv4_lpm { 105 | key = { 106 | hdr.ipv4.dstAddr: lpm; 107 | } 108 | actions = { 109 | ipv4_forward; 110 | drop; 111 | NoAction; 112 | } 113 | size = 1024; 114 | default_action = drop(); 115 | } 116 | 117 | 118 | apply { 119 | if (hdr.ipv4.isValid()) { 120 | ipv4_lpm.apply(); 121 | /* TODO: add your table to the control flow */ 122 | 123 | } 124 | 125 | } 126 | } 127 | 128 | /************************************************************************* 129 | **************** E G R E S S P R O C E S S I N G ******************* 130 | *************************************************************************/ 131 | 132 | control MyEgress(inout headers hdr, 133 | inout metadata meta, 134 | inout standard_metadata_t standard_metadata) { 135 | apply { } 136 | } 137 | 138 | /************************************************************************* 139 | ************* C H E C K S U M C O M P U T A T I O N ************** 140 | *************************************************************************/ 141 | 142 | control MyComputeChecksum(inout headers hdr, inout metadata meta) { 143 | apply { 144 | update_checksum( 145 | hdr.ipv4.isValid(), 146 | { hdr.ipv4.version, 147 | hdr.ipv4.ihl, 148 | hdr.ipv4.diffserv, 149 | hdr.ipv4.totalLen, 150 | hdr.ipv4.identification, 151 | hdr.ipv4.flags, 152 | hdr.ipv4.fragOffset, 153 | hdr.ipv4.ttl, 154 | hdr.ipv4.protocol, 155 | hdr.ipv4.srcAddr, 156 | hdr.ipv4.dstAddr }, 157 | hdr.ipv4.hdrChecksum, 158 | HashAlgorithm.csum16); 159 | } 160 | } 161 | 162 | /************************************************************************* 163 | *********************** D E P A R S E R ******************************* 164 | *************************************************************************/ 165 | 166 | control MyDeparser(packet_out packet, in headers hdr) { 167 | apply { 168 | } 169 | } 170 | 171 | /************************************************************************* 172 | *********************** S W I T C H ******************************* 173 | *************************************************************************/ 174 | 175 | V1Switch( 176 | MyParser(), 177 | MyVerifyChecksum(), 178 | MyIngress(), 179 | MyEgress(), 180 | MyComputeChecksum(), 181 | MyDeparser() 182 | ) main; 183 | -------------------------------------------------------------------------------- /Assignment/exercises/acl/receive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import struct 4 | import os 5 | 6 | from scapy.all import sniff, sendp, hexdump, get_if_list, get_if_hwaddr 7 | from scapy.all import Packet, IPOption 8 | from scapy.all import ShortField, IntField, LongField, BitField, FieldListField, FieldLenField 9 | from scapy.all import IP, TCP, UDP, Raw 10 | from scapy.layers.inet import _IPOption_HDR 11 | 12 | def get_if(): 13 | ifs=get_if_list() 14 | iface=None 15 | for i in get_if_list(): 16 | if "eth0" in i: 17 | iface=i 18 | break; 19 | if not iface: 20 | print "Cannot find eth0 interface" 21 | exit(1) 22 | return iface 23 | 24 | class IPOption_MRI(IPOption): 25 | name = "MRI" 26 | option = 31 27 | fields_desc = [ _IPOption_HDR, 28 | FieldLenField("length", None, fmt="B", 29 | length_of="swids", 30 | adjust=lambda pkt,l:l+4), 31 | ShortField("count", 0), 32 | FieldListField("swids", 33 | [], 34 | IntField("", 0), 35 | length_from=lambda pkt:pkt.count*4) ] 36 | def handle_pkt(pkt, input_dport): 37 | if TCP in pkt and pkt[TCP].dport == input_dport: 38 | # print "got a packet" 39 | # pkt.show2() 40 | print(pkt.load) 41 | # hexdump(pkt) 42 | sys.stdout.flush() 43 | elif UDP in pkt and pkt[UDP].dport == input_dport: 44 | # print "got a packet" 45 | # pkt.show2() 46 | print(pkt.load) 47 | # hexdump(pkt) 48 | sys.stdout.flush() 49 | 50 | 51 | def main(): 52 | ifaces = filter(lambda i: 'eth' in i, os.listdir('/sys/class/net/')) 53 | iface = ifaces[0] 54 | # print "sniffing on %s" % iface 55 | sys.stdout.flush() 56 | sniff(iface = iface, 57 | prn = lambda x: handle_pkt(x, int(sys.argv[1]))) 58 | 59 | if __name__ == '__main__': 60 | main() 61 | -------------------------------------------------------------------------------- /Assignment/exercises/acl/s1-acl.json: -------------------------------------------------------------------------------- 1 | { 2 | "target": "bmv2", 3 | "p4info": "build/acl.p4info", 4 | "bmv2_json": "build/acl.json", 5 | "table_entries": [ 6 | { 7 | "table": "MyIngress.ipv4_lpm", 8 | "default_action": true, 9 | "action_name": "MyIngress.drop", 10 | "action_params": { } 11 | }, 12 | { 13 | "table": "MyIngress.ipv4_lpm", 14 | "match": { 15 | "hdr.ipv4.dstAddr": ["10.0.1.1", 32] 16 | }, 17 | "action_name": "MyIngress.ipv4_forward", 18 | "action_params": { 19 | "dstAddr": "00:00:00:00:01:01", 20 | "port": 1 21 | } 22 | }, 23 | { 24 | "table": "MyIngress.ipv4_lpm", 25 | "match": { 26 | "hdr.ipv4.dstAddr": ["10.0.1.2", 32] 27 | }, 28 | "action_name": "MyIngress.ipv4_forward", 29 | "action_params": { 30 | "dstAddr": "00:00:00:00:01:02", 31 | "port": 2 32 | } 33 | }, 34 | { 35 | "table": "MyIngress.ipv4_lpm", 36 | "match": { 37 | "hdr.ipv4.dstAddr": ["10.0.1.3", 32] 38 | }, 39 | "action_name": "MyIngress.ipv4_forward", 40 | "action_params": { 41 | "dstAddr": "00:00:00:00:01:03", 42 | "port": 3 43 | } 44 | }, 45 | { 46 | "table": "MyIngress.ipv4_lpm", 47 | "match": { 48 | "hdr.ipv4.dstAddr": ["10.0.1.4", 32] 49 | }, 50 | "action_name": "MyIngress.ipv4_forward", 51 | "action_params": { 52 | "dstAddr": "00:00:00:00:01:04", 53 | "port": 4 54 | } 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /Assignment/exercises/acl/send.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import sys 4 | import socket 5 | import random 6 | import struct 7 | 8 | from scapy.all import sendp, send, get_if_list, get_if_hwaddr 9 | from scapy.all import Packet 10 | from scapy.all import Ether, IP, UDP, TCP 11 | 12 | def get_if(): 13 | ifs=get_if_list() 14 | iface=None # "h1-eth0" 15 | for i in get_if_list(): 16 | if "eth0" in i: 17 | iface=i 18 | break; 19 | if not iface: 20 | print "Cannot find eth0 interface" 21 | exit(1) 22 | return iface 23 | 24 | def main(): 25 | 26 | if len(sys.argv)<3: 27 | print 'pass 2 arguments: ""' 28 | exit(1) 29 | 30 | addr = socket.gethostbyname(sys.argv[1]) 31 | iface = get_if() 32 | 33 | print "sending on interface %s to %s" % (iface, str(addr)) 34 | pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') 35 | if sys.argv[2] == "TCP": 36 | pkt = pkt /IP(dst=addr) / TCP(dport=int(sys.argv[3]), sport=random.randint(49152,65535)) / sys.argv[4] 37 | else: 38 | pkt = pkt /IP(dst=addr) / UDP(dport=int(sys.argv[3]), sport=random.randint(49152,65535)) / sys.argv[4] 39 | pkt.show2() 40 | sendp(pkt, iface=iface, verbose=False) 41 | 42 | 43 | if __name__ == '__main__': 44 | main() 45 | -------------------------------------------------------------------------------- /Assignment/exercises/acl/topology.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosts": [ 3 | "h1", 4 | "h2", 5 | "h3", 6 | "h4" 7 | ], 8 | "switches": { 9 | "s1": { "runtime_json" : "s1-acl.json" } 10 | }, 11 | "links": [ 12 | ["h1", "s1"], ["s1", "h2"], 13 | ["s1", "h3"], ["s1", "h4"] 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Assignment/p4-cheat-sheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Assignment/p4-cheat-sheet.pdf -------------------------------------------------------------------------------- /Assignment/utils/Makefile: -------------------------------------------------------------------------------- 1 | BUILD_DIR = build 2 | PCAP_DIR = pcaps 3 | LOG_DIR = logs 4 | 5 | TOPO = topology.json 6 | P4C = p4c-bm2-ss 7 | RUN_SCRIPT = ../../utils/run_exercise.py 8 | 9 | source := $(wildcard *.p4) 10 | outfile := $(source:.p4=.json) 11 | 12 | compiled_json := $(BUILD_DIR)/$(outfile) 13 | 14 | # Define NO_P4 to start BMv2 without a program 15 | ifndef NO_P4 16 | run_args += -j $(compiled_json) 17 | endif 18 | 19 | # Set BMV2_SWITCH_EXE to override the BMv2 target 20 | ifdef BMV2_SWITCH_EXE 21 | run_args += -b $(BMV2_SWITCH_EXE) 22 | endif 23 | 24 | all: run 25 | 26 | run: build 27 | sudo python $(RUN_SCRIPT) -t $(TOPO) $(run_args) 28 | 29 | acl: build 30 | sudo python $(RUN_SCRIPT) -t $(TOPO) -e "acl" $(run_args) 31 | 32 | lb: build 33 | sudo python $(RUN_SCRIPT) -t $(TOPO) -e "lb" $(run_args) 34 | 35 | stop: 36 | sudo mn -c 37 | 38 | build: dirs $(compiled_json) 39 | 40 | $(BUILD_DIR)/%.json: %.p4 41 | $(P4C) --p4v 16 $(P4C_ARGS) -o $@ $< 42 | 43 | dirs: 44 | mkdir -p $(BUILD_DIR) $(PCAP_DIR) $(LOG_DIR) 45 | 46 | clean: stop 47 | rm -f *.pcap 48 | rm -rf $(BUILD_DIR) $(PCAP_DIR) $(LOG_DIR) 49 | -------------------------------------------------------------------------------- /Assignment/utils/mininet/p4_mininet.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013-present Barefoot Networks, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | from mininet.net import Mininet 17 | from mininet.node import Switch, Host 18 | from mininet.log import setLogLevel, info, error, debug 19 | from mininet.moduledeps import pathCheck 20 | from sys import exit 21 | from time import sleep 22 | import os 23 | import tempfile 24 | import socket 25 | 26 | class P4Host(Host): 27 | def config(self, **params): 28 | r = super(P4Host, self).config(**params) 29 | 30 | for off in ["rx", "tx", "sg"]: 31 | cmd = "/sbin/ethtool --offload %s %s off" % (self.defaultIntf().name, off) 32 | self.cmd(cmd) 33 | 34 | # disable IPv6 35 | self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1") 36 | self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1") 37 | self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") 38 | 39 | return r 40 | 41 | def describe(self, sw_addr=None, sw_mac=None): 42 | print "**********" 43 | print "Network configuration for: %s" % self.name 44 | print "Default interface: %s\t%s\t%s" %( 45 | self.defaultIntf().name, 46 | self.defaultIntf().IP(), 47 | self.defaultIntf().MAC() 48 | ) 49 | if sw_addr is not None or sw_mac is not None: 50 | print "Default route to switch: %s (%s)" % (sw_addr, sw_mac) 51 | print "**********" 52 | 53 | class P4Switch(Switch): 54 | """P4 virtual switch""" 55 | device_id = 0 56 | 57 | def __init__(self, name, sw_path = None, json_path = None, 58 | log_file = None, 59 | thrift_port = None, 60 | pcap_dump = False, 61 | log_console = False, 62 | verbose = False, 63 | device_id = None, 64 | enable_debugger = False, 65 | **kwargs): 66 | Switch.__init__(self, name, **kwargs) 67 | assert(sw_path) 68 | assert(json_path) 69 | # make sure that the provided sw_path is valid 70 | pathCheck(sw_path) 71 | # make sure that the provided JSON file exists 72 | if not os.path.isfile(json_path): 73 | error("Invalid JSON file.\n") 74 | exit(1) 75 | self.sw_path = sw_path 76 | self.json_path = json_path 77 | self.verbose = verbose 78 | self.log_file = log_file 79 | if self.log_file is None: 80 | self.log_file = "/tmp/p4s.{}.log".format(self.name) 81 | self.output = open(self.log_file, 'w') 82 | self.thrift_port = thrift_port 83 | self.pcap_dump = pcap_dump 84 | self.enable_debugger = enable_debugger 85 | self.log_console = log_console 86 | if device_id is not None: 87 | self.device_id = device_id 88 | P4Switch.device_id = max(P4Switch.device_id, device_id) 89 | else: 90 | self.device_id = P4Switch.device_id 91 | P4Switch.device_id += 1 92 | self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) 93 | 94 | @classmethod 95 | def setup(cls): 96 | pass 97 | 98 | def check_switch_started(self, pid): 99 | """While the process is running (pid exists), we check if the Thrift 100 | server has been started. If the Thrift server is ready, we assume that 101 | the switch was started successfully. This is only reliable if the Thrift 102 | server is started at the end of the init process""" 103 | while True: 104 | if not os.path.exists(os.path.join("/proc", str(pid))): 105 | return False 106 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 107 | sock.settimeout(0.5) 108 | result = sock.connect_ex(("localhost", self.thrift_port)) 109 | if result == 0: 110 | return True 111 | 112 | def start(self, controllers): 113 | "Start up a new P4 switch" 114 | info("Starting P4 switch {}.\n".format(self.name)) 115 | args = [self.sw_path] 116 | for port, intf in self.intfs.items(): 117 | if not intf.IP(): 118 | args.extend(['-i', str(port) + "@" + intf.name]) 119 | if self.pcap_dump: 120 | args.append("--pcap") 121 | # args.append("--useFiles") 122 | if self.thrift_port: 123 | args.extend(['--thrift-port', str(self.thrift_port)]) 124 | if self.nanomsg: 125 | args.extend(['--nanolog', self.nanomsg]) 126 | args.extend(['--device-id', str(self.device_id)]) 127 | P4Switch.device_id += 1 128 | args.append(self.json_path) 129 | if self.enable_debugger: 130 | args.append("--debugger") 131 | if self.log_console: 132 | args.append("--log-console") 133 | info(' '.join(args) + "\n") 134 | 135 | pid = None 136 | with tempfile.NamedTemporaryFile() as f: 137 | # self.cmd(' '.join(args) + ' > /dev/null 2>&1 &') 138 | self.cmd(' '.join(args) + ' >' + self.log_file + ' 2>&1 & echo $! >> ' + f.name) 139 | pid = int(f.read()) 140 | debug("P4 switch {} PID is {}.\n".format(self.name, pid)) 141 | sleep(1) 142 | if not self.check_switch_started(pid): 143 | error("P4 switch {} did not start correctly." 144 | "Check the switch log file.\n".format(self.name)) 145 | exit(1) 146 | info("P4 switch {} has been started.\n".format(self.name)) 147 | 148 | def stop(self): 149 | "Terminate P4 switch." 150 | self.output.flush() 151 | self.cmd('kill %' + self.sw_path) 152 | self.cmd('wait') 153 | self.deleteIntfs() 154 | 155 | def attach(self, intf): 156 | "Connect a data port" 157 | assert(0) 158 | 159 | def detach(self, intf): 160 | "Disconnect a data port" 161 | assert(0) 162 | -------------------------------------------------------------------------------- /Assignment/utils/netstat.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-present Open Networking Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import psutil 17 | def check_listening_on_port(port): 18 | for c in psutil.net_connections(kind='inet'): 19 | if c.status == 'LISTEN' and c.laddr[1] == port: 20 | return True 21 | return False 22 | -------------------------------------------------------------------------------- /Assignment/utils/p4_mininet.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013-present Barefoot Networks, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | from mininet.net import Mininet 17 | from mininet.node import Switch, Host 18 | from mininet.log import setLogLevel, info, error, debug 19 | from mininet.moduledeps import pathCheck 20 | from sys import exit 21 | import os 22 | import tempfile 23 | import socket 24 | from time import sleep 25 | 26 | from netstat import check_listening_on_port 27 | 28 | SWITCH_START_TIMEOUT = 10 # seconds 29 | 30 | class P4Host(Host): 31 | def config(self, **params): 32 | r = super(Host, self).config(**params) 33 | 34 | self.defaultIntf().rename("eth0") 35 | 36 | for off in ["rx", "tx", "sg"]: 37 | cmd = "/sbin/ethtool --offload eth0 %s off" % off 38 | self.cmd(cmd) 39 | 40 | # disable IPv6 41 | self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1") 42 | self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1") 43 | self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") 44 | 45 | return r 46 | 47 | def describe(self): 48 | print "**********" 49 | print self.name 50 | print "default interface: %s\t%s\t%s" %( 51 | self.defaultIntf().name, 52 | self.defaultIntf().IP(), 53 | self.defaultIntf().MAC() 54 | ) 55 | print "**********" 56 | 57 | class P4Switch(Switch): 58 | """P4 virtual switch""" 59 | device_id = 0 60 | 61 | def __init__(self, name, sw_path = None, json_path = None, 62 | thrift_port = None, 63 | pcap_dump = False, 64 | log_console = False, 65 | log_file = None, 66 | verbose = False, 67 | device_id = None, 68 | enable_debugger = False, 69 | **kwargs): 70 | Switch.__init__(self, name, **kwargs) 71 | assert(sw_path) 72 | assert(json_path) 73 | # make sure that the provided sw_path is valid 74 | pathCheck(sw_path) 75 | # make sure that the provided JSON file exists 76 | if not os.path.isfile(json_path): 77 | error("Invalid JSON file.\n") 78 | exit(1) 79 | self.sw_path = sw_path 80 | self.json_path = json_path 81 | self.verbose = verbose 82 | logfile = "/tmp/p4s.{}.log".format(self.name) 83 | self.output = open(logfile, 'w') 84 | self.thrift_port = thrift_port 85 | if check_listening_on_port(self.thrift_port): 86 | error('%s cannot bind port %d because it is bound by another process\n' % (self.name, self.grpc_port)) 87 | exit(1) 88 | self.pcap_dump = pcap_dump 89 | self.enable_debugger = enable_debugger 90 | self.log_console = log_console 91 | if log_file is not None: 92 | self.log_file = log_file 93 | else: 94 | self.log_file = "/tmp/p4s.{}.log".format(self.name) 95 | if device_id is not None: 96 | self.device_id = device_id 97 | P4Switch.device_id = max(P4Switch.device_id, device_id) 98 | else: 99 | self.device_id = P4Switch.device_id 100 | P4Switch.device_id += 1 101 | self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) 102 | 103 | @classmethod 104 | def setup(cls): 105 | pass 106 | 107 | def check_switch_started(self, pid): 108 | """While the process is running (pid exists), we check if the Thrift 109 | server has been started. If the Thrift server is ready, we assume that 110 | the switch was started successfully. This is only reliable if the Thrift 111 | server is started at the end of the init process""" 112 | while True: 113 | if not os.path.exists(os.path.join("/proc", str(pid))): 114 | return False 115 | if check_listening_on_port(self.thrift_port): 116 | return True 117 | sleep(0.5) 118 | 119 | def start(self, controllers): 120 | "Start up a new P4 switch" 121 | info("Starting P4 switch {}.\n".format(self.name)) 122 | args = [self.sw_path] 123 | for port, intf in self.intfs.items(): 124 | if not intf.IP(): 125 | args.extend(['-i', str(port) + "@" + intf.name]) 126 | if self.pcap_dump: 127 | args.append("--pcap %s" % self.pcap_dump) 128 | if self.thrift_port: 129 | args.extend(['--thrift-port', str(self.thrift_port)]) 130 | if self.nanomsg: 131 | args.extend(['--nanolog', self.nanomsg]) 132 | args.extend(['--device-id', str(self.device_id)]) 133 | P4Switch.device_id += 1 134 | args.append(self.json_path) 135 | if self.enable_debugger: 136 | args.append("--debugger") 137 | if self.log_console: 138 | args.append("--log-console") 139 | info(' '.join(args) + "\n") 140 | 141 | pid = None 142 | with tempfile.NamedTemporaryFile() as f: 143 | # self.cmd(' '.join(args) + ' > /dev/null 2>&1 &') 144 | self.cmd(' '.join(args) + ' >' + self.log_file + ' 2>&1 & echo $! >> ' + f.name) 145 | pid = int(f.read()) 146 | debug("P4 switch {} PID is {}.\n".format(self.name, pid)) 147 | if not self.check_switch_started(pid): 148 | error("P4 switch {} did not start correctly.\n".format(self.name)) 149 | exit(1) 150 | info("P4 switch {} has been started.\n".format(self.name)) 151 | 152 | def stop(self): 153 | "Terminate P4 switch." 154 | self.output.flush() 155 | self.cmd('kill %' + self.sw_path) 156 | self.cmd('wait') 157 | self.deleteIntfs() 158 | 159 | def attach(self, intf): 160 | "Connect a data port" 161 | assert(0) 162 | 163 | def detach(self, intf): 164 | "Disconnect a data port" 165 | assert(0) 166 | -------------------------------------------------------------------------------- /Assignment/utils/p4runtime_lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Assignment/utils/p4runtime_lib/__init__.py -------------------------------------------------------------------------------- /Assignment/utils/p4runtime_lib/bmv2.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-present Open Networking Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from switch import SwitchConnection 16 | from p4.tmp import p4config_pb2 17 | 18 | 19 | def buildDeviceConfig(bmv2_json_file_path=None): 20 | "Builds the device config for BMv2" 21 | device_config = p4config_pb2.P4DeviceConfig() 22 | device_config.reassign = True 23 | with open(bmv2_json_file_path) as f: 24 | device_config.device_data = f.read() 25 | return device_config 26 | 27 | 28 | class Bmv2SwitchConnection(SwitchConnection): 29 | def buildDeviceConfig(self, **kwargs): 30 | return buildDeviceConfig(**kwargs) 31 | -------------------------------------------------------------------------------- /Assignment/utils/p4runtime_lib/convert.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-present Open Networking Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | import re 16 | import socket 17 | 18 | import math 19 | 20 | ''' 21 | This package contains several helper functions for encoding to and decoding from byte strings: 22 | - integers 23 | - IPv4 address strings 24 | - Ethernet address strings 25 | ''' 26 | 27 | mac_pattern = re.compile('^([\da-fA-F]{2}:){5}([\da-fA-F]{2})$') 28 | def matchesMac(mac_addr_string): 29 | return mac_pattern.match(mac_addr_string) is not None 30 | 31 | def encodeMac(mac_addr_string): 32 | return mac_addr_string.replace(':', '').decode('hex') 33 | 34 | def decodeMac(encoded_mac_addr): 35 | return ':'.join(s.encode('hex') for s in encoded_mac_addr) 36 | 37 | ip_pattern = re.compile('^(\d{1,3}\.){3}(\d{1,3})$') 38 | def matchesIPv4(ip_addr_string): 39 | return ip_pattern.match(ip_addr_string) is not None 40 | 41 | def encodeIPv4(ip_addr_string): 42 | return socket.inet_aton(ip_addr_string) 43 | 44 | def decodeIPv4(encoded_ip_addr): 45 | return socket.inet_ntoa(encoded_ip_addr) 46 | 47 | def bitwidthToBytes(bitwidth): 48 | return int(math.ceil(bitwidth / 8.0)) 49 | 50 | def encodeNum(number, bitwidth): 51 | byte_len = bitwidthToBytes(bitwidth) 52 | num_str = '%x' % number 53 | if number >= 2 ** bitwidth: 54 | raise Exception("Number, %d, does not fit in %d bits" % (number, bitwidth)) 55 | return ('0' * (byte_len * 2 - len(num_str)) + num_str).decode('hex') 56 | 57 | def decodeNum(encoded_number): 58 | return int(encoded_number.encode('hex'), 16) 59 | 60 | def encode(x, bitwidth): 61 | 'Tries to infer the type of `x` and encode it' 62 | byte_len = bitwidthToBytes(bitwidth) 63 | if (type(x) == list or type(x) == tuple) and len(x) == 1: 64 | x = x[0] 65 | encoded_bytes = None 66 | if type(x) == str: 67 | if matchesMac(x): 68 | encoded_bytes = encodeMac(x) 69 | elif matchesIPv4(x): 70 | encoded_bytes = encodeIPv4(x) 71 | else: 72 | # Assume that the string is already encoded 73 | encoded_bytes = x 74 | elif type(x) == int: 75 | encoded_bytes = encodeNum(x, bitwidth) 76 | else: 77 | raise Exception("Encoding objects of %r is not supported" % type(x)) 78 | assert(len(encoded_bytes) == byte_len) 79 | return encoded_bytes 80 | 81 | if __name__ == '__main__': 82 | # TODO These tests should be moved out of main eventually 83 | mac = "aa:bb:cc:dd:ee:ff" 84 | enc_mac = encodeMac(mac) 85 | assert(enc_mac == '\xaa\xbb\xcc\xdd\xee\xff') 86 | dec_mac = decodeMac(enc_mac) 87 | assert(mac == dec_mac) 88 | 89 | ip = "10.0.0.1" 90 | enc_ip = encodeIPv4(ip) 91 | assert(enc_ip == '\x0a\x00\x00\x01') 92 | dec_ip = decodeIPv4(enc_ip) 93 | assert(ip == dec_ip) 94 | 95 | num = 1337 96 | byte_len = 5 97 | enc_num = encodeNum(num, byte_len * 8) 98 | assert(enc_num == '\x00\x00\x00\x05\x39') 99 | dec_num = decodeNum(enc_num) 100 | assert(num == dec_num) 101 | 102 | assert(matchesIPv4('10.0.0.1')) 103 | assert(not matchesIPv4('10.0.0.1.5')) 104 | assert(not matchesIPv4('1000.0.0.1')) 105 | assert(not matchesIPv4('10001')) 106 | 107 | assert(encode(mac, 6 * 8) == enc_mac) 108 | assert(encode(ip, 4 * 8) == enc_ip) 109 | assert(encode(num, 5 * 8) == enc_num) 110 | assert(encode((num,), 5 * 8) == enc_num) 111 | assert(encode([num], 5 * 8) == enc_num) 112 | 113 | num = 256 114 | byte_len = 2 115 | try: 116 | enc_num = encodeNum(num, 8) 117 | raise Exception("expected exception") 118 | except Exception as e: 119 | print e 120 | -------------------------------------------------------------------------------- /Assignment/utils/p4runtime_lib/error_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013-present Barefoot Networks, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import sys 17 | 18 | from google.rpc import status_pb2, code_pb2 19 | import grpc 20 | from p4 import p4runtime_pb2 21 | 22 | # Used to indicate that the gRPC error Status object returned by the server has 23 | # an incorrect format. 24 | class P4RuntimeErrorFormatException(Exception): 25 | def __init__(self, message): 26 | super(P4RuntimeErrorFormatException, self).__init__(message) 27 | 28 | 29 | # Parse the binary details of the gRPC error. This is required to print some 30 | # helpful debugging information in tha case of batched Write / Read 31 | # requests. Returns None if there are no useful binary details and throws 32 | # P4RuntimeErrorFormatException if the error is not formatted 33 | # properly. Otherwise, returns a list of tuples with the first element being the 34 | # index of the operation in the batch that failed and the second element being 35 | # the p4.Error Protobuf message. 36 | def parseGrpcErrorBinaryDetails(grpc_error): 37 | if grpc_error.code() != grpc.StatusCode.UNKNOWN: 38 | return None 39 | 40 | error = None 41 | # The gRPC Python package does not have a convenient way to access the 42 | # binary details for the error: they are treated as trailing metadata. 43 | for meta in grpc_error.trailing_metadata(): 44 | if meta[0] == "grpc-status-details-bin": 45 | error = status_pb2.Status() 46 | error.ParseFromString(meta[1]) 47 | break 48 | if error is None: # no binary details field 49 | return None 50 | if len(error.details) == 0: 51 | # binary details field has empty Any details repeated field 52 | return None 53 | 54 | indexed_p4_errors = [] 55 | for idx, one_error_any in enumerate(error.details): 56 | p4_error = p4runtime_pb2.Error() 57 | if not one_error_any.Unpack(p4_error): 58 | raise P4RuntimeErrorFormatException( 59 | "Cannot convert Any message to p4.Error") 60 | if p4_error.canonical_code == code_pb2.OK: 61 | continue 62 | indexed_p4_errors += [(idx, p4_error)] 63 | 64 | return indexed_p4_errors 65 | 66 | 67 | # P4Runtime uses a 3-level message in case of an error during the processing of 68 | # a write batch. This means that some care is required when printing the 69 | # exception if we do not want to end-up with a non-helpful message in case of 70 | # failure as only the first level will be printed. In this function, we extract 71 | # the nested error message when present (one for each operation included in the 72 | # batch) in order to print error code + user-facing message. See P4Runtime 73 | # documentation for more details on error-reporting. 74 | def printGrpcError(grpc_error): 75 | print "gRPC Error", grpc_error.details(), 76 | status_code = grpc_error.code() 77 | print "({})".format(status_code.name), 78 | traceback = sys.exc_info()[2] 79 | print "[{}:{}]".format( 80 | traceback.tb_frame.f_code.co_filename, traceback.tb_lineno) 81 | if status_code != grpc.StatusCode.UNKNOWN: 82 | return 83 | p4_errors = parseGrpcErrorBinaryDetails(grpc_error) 84 | if p4_errors is None: 85 | return 86 | print "Errors in batch:" 87 | for idx, p4_error in p4_errors: 88 | code_name = code_pb2._CODE.values_by_number[ 89 | p4_error.canonical_code].name 90 | print "\t* At index {}: {}, '{}'\n".format( 91 | idx, code_name, p4_error.message) 92 | -------------------------------------------------------------------------------- /Assignment/utils/p4runtime_lib/switch.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-present Open Networking Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from Queue import Queue 16 | from abc import abstractmethod 17 | from datetime import datetime 18 | 19 | import grpc 20 | from p4 import p4runtime_pb2 21 | from p4.tmp import p4config_pb2 22 | 23 | MSG_LOG_MAX_LEN = 1024 24 | 25 | # List of all active connections 26 | connections = [] 27 | 28 | def ShutdownAllSwitchConnections(): 29 | for c in connections: 30 | c.shutdown() 31 | 32 | class SwitchConnection(object): 33 | 34 | def __init__(self, name=None, address='127.0.0.1:50051', device_id=0, 35 | proto_dump_file=None): 36 | self.name = name 37 | self.address = address 38 | self.device_id = device_id 39 | self.p4info = None 40 | self.channel = grpc.insecure_channel(self.address) 41 | if proto_dump_file is not None: 42 | interceptor = GrpcRequestLogger(proto_dump_file) 43 | self.channel = grpc.intercept_channel(self.channel, interceptor) 44 | self.client_stub = p4runtime_pb2.P4RuntimeStub(self.channel) 45 | self.requests_stream = IterableQueue() 46 | self.stream_msg_resp = self.client_stub.StreamChannel(iter(self.requests_stream)) 47 | self.proto_dump_file = proto_dump_file 48 | connections.append(self) 49 | 50 | @abstractmethod 51 | def buildDeviceConfig(self, **kwargs): 52 | return p4config_pb2.P4DeviceConfig() 53 | 54 | def shutdown(self): 55 | self.requests_stream.close() 56 | self.stream_msg_resp.cancel() 57 | 58 | def MasterArbitrationUpdate(self, dry_run=False, **kwargs): 59 | request = p4runtime_pb2.StreamMessageRequest() 60 | request.arbitration.device_id = self.device_id 61 | request.arbitration.election_id.high = 0 62 | request.arbitration.election_id.low = 1 63 | 64 | if dry_run: 65 | print "P4Runtime MasterArbitrationUpdate: ", request 66 | else: 67 | self.requests_stream.put(request) 68 | for item in self.stream_msg_resp: 69 | return item # just one 70 | 71 | def SetForwardingPipelineConfig(self, p4info, dry_run=False, **kwargs): 72 | device_config = self.buildDeviceConfig(**kwargs) 73 | request = p4runtime_pb2.SetForwardingPipelineConfigRequest() 74 | request.election_id.low = 1 75 | request.device_id = self.device_id 76 | config = request.config 77 | 78 | config.p4info.CopyFrom(p4info) 79 | config.p4_device_config = device_config.SerializeToString() 80 | 81 | request.action = p4runtime_pb2.SetForwardingPipelineConfigRequest.VERIFY_AND_COMMIT 82 | if dry_run: 83 | print "P4Runtime SetForwardingPipelineConfig:", request 84 | else: 85 | self.client_stub.SetForwardingPipelineConfig(request) 86 | 87 | def WriteTableEntry(self, table_entry, dry_run=False): 88 | request = p4runtime_pb2.WriteRequest() 89 | request.device_id = self.device_id 90 | request.election_id.low = 1 91 | update = request.updates.add() 92 | update.type = p4runtime_pb2.Update.INSERT 93 | update.entity.table_entry.CopyFrom(table_entry) 94 | if dry_run: 95 | print "P4Runtime Write:", request 96 | else: 97 | self.client_stub.Write(request) 98 | 99 | def ReadTableEntries(self, table_id=None, dry_run=False): 100 | request = p4runtime_pb2.ReadRequest() 101 | request.device_id = self.device_id 102 | entity = request.entities.add() 103 | table_entry = entity.table_entry 104 | if table_id is not None: 105 | table_entry.table_id = table_id 106 | else: 107 | table_entry.table_id = 0 108 | if dry_run: 109 | print "P4Runtime Read:", request 110 | else: 111 | for response in self.client_stub.Read(request): 112 | yield response 113 | 114 | def ReadCounters(self, counter_id=None, index=None, dry_run=False): 115 | request = p4runtime_pb2.ReadRequest() 116 | request.device_id = self.device_id 117 | entity = request.entities.add() 118 | counter_entry = entity.counter_entry 119 | if counter_id is not None: 120 | counter_entry.counter_id = counter_id 121 | else: 122 | counter_entry.counter_id = 0 123 | if index is not None: 124 | counter_entry.index.index = index 125 | if dry_run: 126 | print "P4Runtime Read:", request 127 | else: 128 | for response in self.client_stub.Read(request): 129 | yield response 130 | 131 | 132 | class GrpcRequestLogger(grpc.UnaryUnaryClientInterceptor, 133 | grpc.UnaryStreamClientInterceptor): 134 | """Implementation of a gRPC interceptor that logs request to a file""" 135 | 136 | def __init__(self, log_file): 137 | self.log_file = log_file 138 | with open(self.log_file, 'w') as f: 139 | # Clear content if it exists. 140 | f.write("") 141 | 142 | def log_message(self, method_name, body): 143 | with open(self.log_file, 'a') as f: 144 | ts = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] 145 | msg = str(body) 146 | f.write("\n[%s] %s\n---\n" % (ts, method_name)) 147 | if len(msg) < MSG_LOG_MAX_LEN: 148 | f.write(str(body)) 149 | else: 150 | f.write("Message too long (%d bytes)! Skipping log...\n" % len(msg)) 151 | f.write('---\n') 152 | 153 | def intercept_unary_unary(self, continuation, client_call_details, request): 154 | self.log_message(client_call_details.method, request) 155 | return continuation(client_call_details, request) 156 | 157 | def intercept_unary_stream(self, continuation, client_call_details, request): 158 | self.log_message(client_call_details.method, request) 159 | return continuation(client_call_details, request) 160 | 161 | class IterableQueue(Queue): 162 | _sentinel = object() 163 | 164 | def __iter__(self): 165 | return iter(self.get, self._sentinel) 166 | 167 | def close(self): 168 | self.put(self._sentinel) 169 | -------------------------------------------------------------------------------- /Assignment/utils/p4runtime_switch.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-present Barefoot Networks, Inc. 2 | # Copyright 2017-present Open Networking Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | import sys, os, tempfile, socket 18 | from time import sleep 19 | 20 | from mininet.node import Switch 21 | from mininet.moduledeps import pathCheck 22 | from mininet.log import info, error, debug 23 | 24 | from p4_mininet import P4Switch, SWITCH_START_TIMEOUT 25 | from netstat import check_listening_on_port 26 | 27 | class P4RuntimeSwitch(P4Switch): 28 | "BMv2 switch with gRPC support" 29 | next_grpc_port = 50051 30 | next_thrift_port = 9090 31 | 32 | def __init__(self, name, sw_path = None, json_path = None, 33 | grpc_port = None, 34 | thrift_port = None, 35 | pcap_dump = False, 36 | log_console = False, 37 | verbose = False, 38 | device_id = None, 39 | enable_debugger = False, 40 | log_file = None, 41 | **kwargs): 42 | Switch.__init__(self, name, **kwargs) 43 | assert (sw_path) 44 | self.sw_path = sw_path 45 | # make sure that the provided sw_path is valid 46 | pathCheck(sw_path) 47 | 48 | if json_path is not None: 49 | # make sure that the provided JSON file exists 50 | if not os.path.isfile(json_path): 51 | error("Invalid JSON file.\n") 52 | exit(1) 53 | self.json_path = json_path 54 | else: 55 | self.json_path = None 56 | 57 | if grpc_port is not None: 58 | self.grpc_port = grpc_port 59 | else: 60 | self.grpc_port = P4RuntimeSwitch.next_grpc_port 61 | P4RuntimeSwitch.next_grpc_port += 1 62 | 63 | if thrift_port is not None: 64 | self.thrift_port = thrift_port 65 | else: 66 | self.thrift_port = P4RuntimeSwitch.next_thrift_port 67 | P4RuntimeSwitch.next_thrift_port += 1 68 | 69 | if check_listening_on_port(self.grpc_port): 70 | error('%s cannot bind port %d because it is bound by another process\n' % (self.name, self.grpc_port)) 71 | exit(1) 72 | 73 | self.verbose = verbose 74 | logfile = "/tmp/p4s.{}.log".format(self.name) 75 | self.output = open(logfile, 'w') 76 | self.pcap_dump = pcap_dump 77 | self.enable_debugger = enable_debugger 78 | self.log_console = log_console 79 | if log_file is not None: 80 | self.log_file = log_file 81 | else: 82 | self.log_file = "/tmp/p4s.{}.log".format(self.name) 83 | if device_id is not None: 84 | self.device_id = device_id 85 | P4Switch.device_id = max(P4Switch.device_id, device_id) 86 | else: 87 | self.device_id = P4Switch.device_id 88 | P4Switch.device_id += 1 89 | self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) 90 | 91 | 92 | def check_switch_started(self, pid): 93 | for _ in range(SWITCH_START_TIMEOUT * 2): 94 | if not os.path.exists(os.path.join("/proc", str(pid))): 95 | return False 96 | if check_listening_on_port(self.grpc_port): 97 | return True 98 | sleep(0.5) 99 | 100 | def start(self, controllers): 101 | info("Starting P4 switch {}.\n".format(self.name)) 102 | args = [self.sw_path] 103 | for port, intf in self.intfs.items(): 104 | if not intf.IP(): 105 | args.extend(['-i', str(port) + "@" + intf.name]) 106 | if self.pcap_dump: 107 | args.append("--pcap %s" % self.pcap_dump) 108 | if self.nanomsg: 109 | args.extend(['--nanolog', self.nanomsg]) 110 | args.extend(['--device-id', str(self.device_id)]) 111 | P4Switch.device_id += 1 112 | if self.json_path: 113 | args.append(self.json_path) 114 | else: 115 | args.append("--no-p4") 116 | if self.enable_debugger: 117 | args.append("--debugger") 118 | if self.log_console: 119 | args.append("--log-console") 120 | if self.thrift_port: 121 | args.append('--thrift-port ' + str(self.thrift_port)) 122 | if self.grpc_port: 123 | args.append("-- --grpc-server-addr 0.0.0.0:" + str(self.grpc_port)) 124 | cmd = ' '.join(args) 125 | info(cmd + "\n") 126 | 127 | 128 | pid = None 129 | with tempfile.NamedTemporaryFile() as f: 130 | self.cmd(cmd + ' >' + self.log_file + ' 2>&1 & echo $! >> ' + f.name) 131 | pid = int(f.read()) 132 | debug("P4 switch {} PID is {}.\n".format(self.name, pid)) 133 | if not self.check_switch_started(pid): 134 | error("P4 switch {} did not start correctly.\n".format(self.name)) 135 | exit(1) 136 | info("P4 switch {} has been started.\n".format(self.name)) 137 | 138 | -------------------------------------------------------------------------------- /Course_Material/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/.DS_Store -------------------------------------------------------------------------------- /Course_Material/1-Introduction.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/1-Introduction.pdf -------------------------------------------------------------------------------- /Course_Material/1-Introduction.r.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/1-Introduction.r.pdf -------------------------------------------------------------------------------- /Course_Material/10-Data-Plane-Programming-part1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/10-Data-Plane-Programming-part1.pdf -------------------------------------------------------------------------------- /Course_Material/11-Data-Plane-Programming-part2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/11-Data-Plane-Programming-part2.pdf -------------------------------------------------------------------------------- /Course_Material/12-Virtualization-part1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/12-Virtualization-part1.pdf -------------------------------------------------------------------------------- /Course_Material/13-Virtualization-part2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/13-Virtualization-part2.pdf -------------------------------------------------------------------------------- /Course_Material/14-NFV.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/14-NFV.pdf -------------------------------------------------------------------------------- /Course_Material/2-SDN-Research-History.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/2-SDN-Research-History.pdf -------------------------------------------------------------------------------- /Course_Material/3-SDN.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/3-SDN.pdf -------------------------------------------------------------------------------- /Course_Material/4-OpenFlow-Part1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/4-OpenFlow-Part1.pdf -------------------------------------------------------------------------------- /Course_Material/5-Tools.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/5-Tools.pdf -------------------------------------------------------------------------------- /Course_Material/6-Mininet_OVS.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/6-Mininet_OVS.pdf -------------------------------------------------------------------------------- /Course_Material/7-RYU-Controller.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/7-RYU-Controller.pdf -------------------------------------------------------------------------------- /Course_Material/8-OpenFlow-Part2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/8-OpenFlow-Part2.pdf -------------------------------------------------------------------------------- /Course_Material/9-OpenFlow-Part3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/9-OpenFlow-Part3.pdf -------------------------------------------------------------------------------- /Course_Material/Assignment-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/Assignment-1.pdf -------------------------------------------------------------------------------- /Course_Material/lec01-overview.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/lec01-overview.pdf -------------------------------------------------------------------------------- /Course_Material/lec2-overview.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Course_Material/lec2-overview.pdf -------------------------------------------------------------------------------- /Exercises/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Exercises/.DS_Store -------------------------------------------------------------------------------- /Exercises/ARP_Proxy_Switch.py: -------------------------------------------------------------------------------- 1 | from ryu.base import app_manager 2 | from ryu.controller import ofp_event 3 | from ryu.controller.handler import MAIN_DISPATCHER 4 | from ryu.controller.handler import set_ev_cls 5 | from ryu.ofproto import ofproto_v1_0 6 | from ryu.lib.packet import arp 7 | 8 | 9 | 10 | 11 | 12 | 13 | class L2Switch(app_manager.RyuApp): 14 | OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION] 15 | arp_table = {"10.0.0.1": "00:00:00:00:00:01", 16 | "10.0.0.2": "00:00:00:00:00:02", 17 | "10.0.0.3": "00:00:00:00:00:03", 18 | "10.0.0.4": "00:00:00:00:00:04" 19 | } 20 | 21 | 22 | def __init__(self, *args, **kwargs): 23 | super(L2Switch, self).__init__(*args, **kwargs) 24 | 25 | @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) 26 | def packet_in_handler(self, ev): 27 | msg = ev.msg 28 | dp = msg.datapath 29 | ofp = dp.ofproto 30 | ofp_parser = dp.ofproto_parser 31 | 32 | actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)] 33 | 34 | data = None 35 | if msg.buffer_id == ofp.OFP_NO_BUFFER: 36 | data = msg.data 37 | if eth.ethertype == ether_types.ETH_TYPE_ARP: 38 | a = pkt.get_protocol(arp.arp) 39 | self.arp_process(datapath, eth, a, in_port) 40 | return 41 | 42 | out = ofp_parser.OFPPacketOut( 43 | datapath=dp, buffer_id=msg.buffer_id, in_port=msg.in_port, 44 | actions=actions, data = data) 45 | dp.send_msg(out) 46 | def arp_process(self, datapath, eth, a, in_port): 47 | row = arp_table.get(a.dst_ip) 48 | if row: 49 | arp_response = packet.Packet() 50 | arp_response.add_protocol(ethernet.ethernet(ethertype=eth.ethertype, 51 | dst=eth.src, src=row)) 52 | arp_response.add_protocol(arp.arp(opcode=arp.ARP_REPLY, 53 | src_mac=row, src_ip=a.dst_ip, 54 | dst_mac=a.src_mac, 55 | dst_ip=a.src_ip)) 56 | 57 | arp_response.serialize() 58 | actions = [] 59 | actions.append(datapath.ofproto_parser.OFPActionOutput(in_port)) 60 | parser = datapath.ofproto_parser 61 | ofproto = datapath.ofproto 62 | out = parser.OFPPacketOut(datapath=datapath, buffer_id=ofproto.OFP_NO_BUFFER, 63 | in_port=ofproto.OFPP_CONTROLLER, actions=actions, data=arp_response) 64 | datapath.send_msg(out) 65 | self.logger.info("ARP Response Sended!!!") 66 | -------------------------------------------------------------------------------- /Exercises/HOW TO USE.txt: -------------------------------------------------------------------------------- 1 | * To tun (inside "tunneling_and_acl" directory): 2 | sudo make clean 3 | sudo make run 4 | 5 | * To test basic forwarding: 6 | xterm h1 h2 h3 7 | ./recieve.py (on one of the hosts) 8 | ./send.py 10.0.x.x 'message' (on another host) 9 | 10 | * To test basic tunneling: 11 | xterm h1 h2 h3 12 | ./recieve.py (on one of the hosts) 13 | ./send.py 10.0.x.x 'message' --dst_id x (on another host) 14 | 15 | * To test access control: 16 | xterm h1 h2 h3 17 | ./recieve.py (on one of the hosts) 18 | ./send.py 10.0.x.x 'message' --transport_protocol UDP --port 80 19 | message should be shown SUCCESSFULLY on host X 20 | run command: "sudo python3 runtime.py --dst_id 10.0.3.3 --udp_port 80" in another terminal (inside "tunneling_and_acl" directory) 21 | send again (on xterm): "./send.py 10.0.x.x 'message' --transport_protocol UDP --port 80" 22 | message sholud NOT be shown since flow table has been updated with new access control entry -------------------------------------------------------------------------------- /Exercises/Makefile: -------------------------------------------------------------------------------- 1 | BMV2_SWITCH_EXE = simple_switch_grpc 2 | DEFAULT_PROG = basic_tunnel.p4 3 | 4 | include ../../utils/Makefile 5 | -------------------------------------------------------------------------------- /Exercises/Makefile 2: -------------------------------------------------------------------------------- 1 | BUILD_DIR = build 2 | PCAP_DIR = pcaps 3 | LOG_DIR = logs 4 | 5 | P4C = p4c-bm2-ss 6 | P4C_ARGS += --p4runtime-files $(BUILD_DIR)/$(basename $@).p4.p4info.txt 7 | 8 | RUN_SCRIPT = ../../utils/run_exercise.py 9 | 10 | ifndef TOPO 11 | TOPO = topology.json 12 | endif 13 | 14 | source = $(wildcard *.p4) 15 | compiled_json := $(source:.p4=.json) 16 | 17 | ifndef DEFAULT_PROG 18 | DEFAULT_PROG = $(wildcard *.p4) 19 | endif 20 | DEFAULT_JSON = $(BUILD_DIR)/$(DEFAULT_PROG:.p4=.json) 21 | 22 | # Define NO_P4 to start BMv2 without a program 23 | ifndef NO_P4 24 | run_args += -j $(DEFAULT_JSON) 25 | endif 26 | 27 | # Set BMV2_SWITCH_EXE to override the BMv2 target 28 | ifdef BMV2_SWITCH_EXE 29 | run_args += -b $(BMV2_SWITCH_EXE) 30 | endif 31 | 32 | all: run 33 | 34 | run: build 35 | sudo python3 $(RUN_SCRIPT) -t $(TOPO) $(run_args) 36 | 37 | stop: 38 | sudo mn -c 39 | 40 | build: dirs $(compiled_json) 41 | 42 | %.json: %.p4 43 | $(P4C) --p4v 16 $(P4C_ARGS) -o $(BUILD_DIR)/$@ $< 44 | 45 | dirs: 46 | mkdir -p $(BUILD_DIR) $(PCAP_DIR) $(LOG_DIR) 47 | 48 | clean: stop 49 | rm -f *.pcap 50 | rm -rf $(BUILD_DIR) $(PCAP_DIR) $(LOG_DIR) 51 | -------------------------------------------------------------------------------- /Exercises/acl/Makefile: -------------------------------------------------------------------------------- 1 | BMV2_SWITCH_EXE = simple_switch_grpc 2 | NO_P4 = true 3 | P4C_ARGS = --p4runtime-file $(basename $@).p4info --p4runtime-format text 4 | 5 | include ../../utils/Makefile 6 | -------------------------------------------------------------------------------- /Exercises/acl/acl.p4: -------------------------------------------------------------------------------- 1 | /* -*- P4_16 -*- */ 2 | #include 3 | #include 4 | 5 | const bit<16> TYPE_IPV4 = 0x800; 6 | 7 | /************************************************************************* 8 | *********************** H E A D E R S *********************************** 9 | *************************************************************************/ 10 | 11 | typedef bit<9> egressSpec_t; 12 | typedef bit<48> macAddr_t; 13 | typedef bit<32> ip4Addr_t; 14 | 15 | header ethernet_t { 16 | macAddr_t dstAddr; 17 | macAddr_t srcAddr; 18 | bit<16> etherType; 19 | } 20 | 21 | header ipv4_t { 22 | bit<4> version; 23 | bit<4> ihl; 24 | bit<8> diffserv; 25 | bit<16> totalLen; 26 | bit<16> identification; 27 | bit<3> flags; 28 | bit<13> fragOffset; 29 | bit<8> ttl; 30 | bit<8> protocol; 31 | bit<16> hdrChecksum; 32 | ip4Addr_t srcAddr; 33 | ip4Addr_t dstAddr; 34 | } 35 | 36 | 37 | struct metadata { 38 | /* empty */ 39 | } 40 | 41 | struct headers { 42 | ethernet_t ethernet; 43 | ipv4_t ipv4; 44 | } 45 | 46 | /************************************************************************* 47 | *********************** P A R S E R *********************************** 48 | *************************************************************************/ 49 | 50 | parser MyParser(packet_in packet, 51 | out headers hdr, 52 | inout metadata meta, 53 | inout standard_metadata_t standard_metadata) { 54 | 55 | state start { 56 | transition parse_ethernet; 57 | } 58 | 59 | state parse_ethernet { 60 | packet.extract(hdr.ethernet); 61 | transition select(hdr.ethernet.etherType) { 62 | TYPE_IPV4: parse_ipv4; 63 | default: accept; 64 | } 65 | } 66 | 67 | state parse_ipv4 { 68 | packet.extract(hdr.ipv4); 69 | transition select(hdr.ipv4.protocol) { 70 | default: accept; 71 | } 72 | } 73 | 74 | 75 | } 76 | 77 | /************************************************************************* 78 | ************ C H E C K S U M V E R I F I C A T I O N ************* 79 | *************************************************************************/ 80 | 81 | control MyVerifyChecksum(inout headers hdr, inout metadata meta) { 82 | apply { } 83 | } 84 | 85 | 86 | /************************************************************************* 87 | ************** I N G R E S S P R O C E S S I N G ******************* 88 | *************************************************************************/ 89 | 90 | control MyIngress(inout headers hdr, 91 | inout metadata meta, 92 | inout standard_metadata_t standard_metadata) { 93 | action drop() { 94 | mark_to_drop(); 95 | } 96 | 97 | action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) { 98 | standard_metadata.egress_spec = port; 99 | hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; 100 | hdr.ethernet.dstAddr = dstAddr; 101 | hdr.ipv4.ttl = hdr.ipv4.ttl - 1; 102 | } 103 | 104 | table ipv4_lpm { 105 | key = { 106 | hdr.ipv4.dstAddr: lpm; 107 | } 108 | actions = { 109 | ipv4_forward; 110 | drop; 111 | NoAction; 112 | } 113 | size = 1024; 114 | default_action = drop(); 115 | } 116 | 117 | 118 | apply { 119 | if (hdr.ipv4.isValid()) { 120 | ipv4_lpm.apply(); 121 | /* TODO: add your table to the control flow */ 122 | 123 | } 124 | 125 | } 126 | } 127 | 128 | /************************************************************************* 129 | **************** E G R E S S P R O C E S S I N G ******************* 130 | *************************************************************************/ 131 | 132 | control MyEgress(inout headers hdr, 133 | inout metadata meta, 134 | inout standard_metadata_t standard_metadata) { 135 | apply { } 136 | } 137 | 138 | /************************************************************************* 139 | ************* C H E C K S U M C O M P U T A T I O N ************** 140 | *************************************************************************/ 141 | 142 | control MyComputeChecksum(inout headers hdr, inout metadata meta) { 143 | apply { 144 | update_checksum( 145 | hdr.ipv4.isValid(), 146 | { hdr.ipv4.version, 147 | hdr.ipv4.ihl, 148 | hdr.ipv4.diffserv, 149 | hdr.ipv4.totalLen, 150 | hdr.ipv4.identification, 151 | hdr.ipv4.flags, 152 | hdr.ipv4.fragOffset, 153 | hdr.ipv4.ttl, 154 | hdr.ipv4.protocol, 155 | hdr.ipv4.srcAddr, 156 | hdr.ipv4.dstAddr }, 157 | hdr.ipv4.hdrChecksum, 158 | HashAlgorithm.csum16); 159 | } 160 | } 161 | 162 | /************************************************************************* 163 | *********************** D E P A R S E R ******************************* 164 | *************************************************************************/ 165 | 166 | control MyDeparser(packet_out packet, in headers hdr) { 167 | apply { 168 | } 169 | } 170 | 171 | /************************************************************************* 172 | *********************** S W I T C H ******************************* 173 | *************************************************************************/ 174 | 175 | V1Switch( 176 | MyParser(), 177 | MyVerifyChecksum(), 178 | MyIngress(), 179 | MyEgress(), 180 | MyComputeChecksum(), 181 | MyDeparser() 182 | ) main; 183 | -------------------------------------------------------------------------------- /Exercises/acl/receive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import struct 4 | import os 5 | 6 | from scapy.all import sniff, sendp, hexdump, get_if_list, get_if_hwaddr 7 | from scapy.all import Packet, IPOption 8 | from scapy.all import ShortField, IntField, LongField, BitField, FieldListField, FieldLenField 9 | from scapy.all import IP, TCP, UDP, Raw 10 | from scapy.layers.inet import _IPOption_HDR 11 | 12 | def get_if(): 13 | ifs=get_if_list() 14 | iface=None 15 | for i in get_if_list(): 16 | if "eth0" in i: 17 | iface=i 18 | break; 19 | if not iface: 20 | print "Cannot find eth0 interface" 21 | exit(1) 22 | return iface 23 | 24 | class IPOption_MRI(IPOption): 25 | name = "MRI" 26 | option = 31 27 | fields_desc = [ _IPOption_HDR, 28 | FieldLenField("length", None, fmt="B", 29 | length_of="swids", 30 | adjust=lambda pkt,l:l+4), 31 | ShortField("count", 0), 32 | FieldListField("swids", 33 | [], 34 | IntField("", 0), 35 | length_from=lambda pkt:pkt.count*4) ] 36 | def handle_pkt(pkt, input_dport): 37 | if TCP in pkt and pkt[TCP].dport == input_dport: 38 | # print "got a packet" 39 | # pkt.show2() 40 | print(pkt.load) 41 | # hexdump(pkt) 42 | sys.stdout.flush() 43 | elif UDP in pkt and pkt[UDP].dport == input_dport: 44 | # print "got a packet" 45 | # pkt.show2() 46 | print(pkt.load) 47 | # hexdump(pkt) 48 | sys.stdout.flush() 49 | 50 | 51 | def main(): 52 | ifaces = filter(lambda i: 'eth' in i, os.listdir('/sys/class/net/')) 53 | iface = ifaces[0] 54 | # print "sniffing on %s" % iface 55 | sys.stdout.flush() 56 | sniff(iface = iface, 57 | prn = lambda x: handle_pkt(x, int(sys.argv[1]))) 58 | 59 | if __name__ == '__main__': 60 | main() 61 | -------------------------------------------------------------------------------- /Exercises/acl/s1-acl.json: -------------------------------------------------------------------------------- 1 | { 2 | "target": "bmv2", 3 | "p4info": "build/acl.p4info", 4 | "bmv2_json": "build/acl.json", 5 | "table_entries": [ 6 | { 7 | "table": "MyIngress.ipv4_lpm", 8 | "default_action": true, 9 | "action_name": "MyIngress.drop", 10 | "action_params": { } 11 | }, 12 | { 13 | "table": "MyIngress.ipv4_lpm", 14 | "match": { 15 | "hdr.ipv4.dstAddr": ["10.0.1.1", 32] 16 | }, 17 | "action_name": "MyIngress.ipv4_forward", 18 | "action_params": { 19 | "dstAddr": "00:00:00:00:01:01", 20 | "port": 1 21 | } 22 | }, 23 | { 24 | "table": "MyIngress.ipv4_lpm", 25 | "match": { 26 | "hdr.ipv4.dstAddr": ["10.0.1.2", 32] 27 | }, 28 | "action_name": "MyIngress.ipv4_forward", 29 | "action_params": { 30 | "dstAddr": "00:00:00:00:01:02", 31 | "port": 2 32 | } 33 | }, 34 | { 35 | "table": "MyIngress.ipv4_lpm", 36 | "match": { 37 | "hdr.ipv4.dstAddr": ["10.0.1.3", 32] 38 | }, 39 | "action_name": "MyIngress.ipv4_forward", 40 | "action_params": { 41 | "dstAddr": "00:00:00:00:01:03", 42 | "port": 3 43 | } 44 | }, 45 | { 46 | "table": "MyIngress.ipv4_lpm", 47 | "match": { 48 | "hdr.ipv4.dstAddr": ["10.0.1.4", 32] 49 | }, 50 | "action_name": "MyIngress.ipv4_forward", 51 | "action_params": { 52 | "dstAddr": "00:00:00:00:01:04", 53 | "port": 4 54 | } 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /Exercises/acl/send.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import sys 4 | import socket 5 | import random 6 | import struct 7 | 8 | from scapy.all import sendp, send, get_if_list, get_if_hwaddr 9 | from scapy.all import Packet 10 | from scapy.all import Ether, IP, UDP, TCP 11 | 12 | def get_if(): 13 | ifs=get_if_list() 14 | iface=None # "h1-eth0" 15 | for i in get_if_list(): 16 | if "eth0" in i: 17 | iface=i 18 | break; 19 | if not iface: 20 | print "Cannot find eth0 interface" 21 | exit(1) 22 | return iface 23 | 24 | def main(): 25 | 26 | if len(sys.argv)<3: 27 | print 'pass 2 arguments: ""' 28 | exit(1) 29 | 30 | addr = socket.gethostbyname(sys.argv[1]) 31 | iface = get_if() 32 | 33 | print "sending on interface %s to %s" % (iface, str(addr)) 34 | pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') 35 | if sys.argv[2] == "TCP": 36 | pkt = pkt /IP(dst=addr) / TCP(dport=int(sys.argv[3]), sport=random.randint(49152,65535)) / sys.argv[4] 37 | else: 38 | pkt = pkt /IP(dst=addr) / UDP(dport=int(sys.argv[3]), sport=random.randint(49152,65535)) / sys.argv[4] 39 | pkt.show2() 40 | sendp(pkt, iface=iface, verbose=False) 41 | 42 | 43 | if __name__ == '__main__': 44 | main() 45 | -------------------------------------------------------------------------------- /Exercises/acl/topology.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosts": [ 3 | "h1", 4 | "h2", 5 | "h3", 6 | "h4" 7 | ], 8 | "switches": { 9 | "s1": { "runtime_json" : "s1-acl.json" } 10 | }, 11 | "links": [ 12 | ["h1", "s1"], ["s1", "h2"], 13 | ["s1", "h3"], ["s1", "h4"] 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Exercises/basic_tunnel.p4: -------------------------------------------------------------------------------- 1 | /* -*- P4_16 -*- */ 2 | #include 3 | #include 4 | 5 | const bit<16> TYPE_MYTUNNEL = 0x1212; 6 | const bit<16> TYPE_IPV4 = 0x800; 7 | const bit<32> MAX_TUNNEL_ID = 1 << 16; 8 | 9 | /************************************************************************* 10 | *********************** H E A D E R S *********************************** 11 | *************************************************************************/ 12 | 13 | typedef bit<9> egressSpec_t; 14 | typedef bit<48> macAddr_t; 15 | typedef bit<32> ip4Addr_t; 16 | 17 | header ethernet_t { 18 | macAddr_t dstAddr; 19 | macAddr_t srcAddr; 20 | bit<16> etherType; 21 | } 22 | 23 | header myTunnel_t { 24 | bit<16> proto_id; 25 | bit<16> dst_id; 26 | } 27 | 28 | header ipv4_t { 29 | bit<4> version; 30 | bit<4> ihl; 31 | bit<8> diffserv; 32 | bit<16> totalLen; 33 | bit<16> identification; 34 | bit<3> flags; 35 | bit<13> fragOffset; 36 | bit<8> ttl; 37 | bit<8> protocol; 38 | bit<16> hdrChecksum; 39 | ip4Addr_t srcAddr; 40 | ip4Addr_t dstAddr; 41 | } 42 | 43 | struct metadata { 44 | /* empty */ 45 | } 46 | 47 | struct headers { 48 | ethernet_t ethernet; 49 | myTunnel_t myTunnel; 50 | ipv4_t ipv4; 51 | } 52 | 53 | /************************************************************************* 54 | *********************** P A R S E R *********************************** 55 | *************************************************************************/ 56 | 57 | parser MyParser(packet_in packet, 58 | out headers hdr, 59 | inout metadata meta, 60 | inout standard_metadata_t standard_metadata) { 61 | 62 | state start { 63 | packet.extract(hdr.ethernet); 64 | transition select(hdr.ethernet.etherType) { 65 | TYPE_MYTUNNEL: parse_myTunnel; 66 | TYPE_IPV4: parse_ipv4; 67 | default: accept; 68 | } 69 | } 70 | 71 | state parse_myTunnel { 72 | packet.extract(hdr.myTunnel); 73 | transition select(hdr.myTunnel.proto_id) { 74 | TYPE_IPV4: parse_ipv4; 75 | default: accept; 76 | } 77 | } 78 | 79 | state parse_ipv4 { 80 | packet.extract(hdr.ipv4); 81 | transition accept; 82 | } 83 | } 84 | 85 | /************************************************************************* 86 | ************ C H E C K S U M V E R I F I C A T I O N ************* 87 | *************************************************************************/ 88 | 89 | control MyVerifyChecksum(inout headers hdr, inout metadata meta) { 90 | //TODO: verify IP header checksum and drop the packet if it fails 91 | apply { } 92 | } 93 | 94 | 95 | /************************************************************************* 96 | ************** I N G R E S S P R O C E S S I N G ******************* 97 | *************************************************************************/ 98 | 99 | control MyIngress(inout headers hdr, 100 | inout metadata meta, 101 | inout standard_metadata_t standard_metadata) { 102 | 103 | action drop() { 104 | mark_to_drop(standard_metadata); 105 | } 106 | 107 | action myTunnel_forward(egressSpec_t port) { 108 | standard_metadata.egress_spec = port; 109 | } 110 | 111 | table myTunnel_exact { 112 | key = { 113 | hdr.myTunnel.dst_id: exact; 114 | } 115 | actions = { 116 | myTunnel_forward; 117 | drop; 118 | } 119 | size = 1024; 120 | default_action = drop(); 121 | } 122 | 123 | action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) { 124 | standard_metadata.egress_spec = port; 125 | hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; 126 | hdr.ethernet.dstAddr = dstAddr; 127 | hdr.ipv4.ttl = hdr.ipv4.ttl - 1; 128 | } 129 | 130 | table ipv4_lpm { 131 | key = { 132 | hdr.ipv4.dstAddr: lpm; 133 | } 134 | actions = { 135 | ipv4_forward; 136 | drop; 137 | NoAction; 138 | } 139 | size = 1024; 140 | default_action = drop(); 141 | } 142 | 143 | apply { 144 | if (hdr.myTunnel.isValid()) { 145 | myTunnel_exact.apply(); 146 | } 147 | else if (hdr.ipv4.isValid()) { 148 | ipv4_lpm.apply(); 149 | } 150 | } 151 | } 152 | 153 | /************************************************************************* 154 | **************** E G R E S S P R O C E S S I N G ******************* 155 | *************************************************************************/ 156 | 157 | control MyEgress(inout headers hdr, 158 | inout metadata meta, 159 | inout standard_metadata_t standard_metadata) { 160 | apply { 161 | } 162 | } 163 | 164 | /************************************************************************* 165 | ************* C H E C K S U M C O M P U T A T I O N ************** 166 | *************************************************************************/ 167 | 168 | control MyComputeChecksum(inout headers hdr, inout metadata meta) { 169 | apply { 170 | update_checksum( 171 | hdr.ipv4.isValid(), 172 | { hdr.ipv4.version, 173 | hdr.ipv4.ihl, 174 | hdr.ipv4.diffserv, 175 | hdr.ipv4.totalLen, 176 | hdr.ipv4.identification, 177 | hdr.ipv4.flags, 178 | hdr.ipv4.fragOffset, 179 | hdr.ipv4.ttl, 180 | hdr.ipv4.protocol, 181 | hdr.ipv4.srcAddr, 182 | hdr.ipv4.dstAddr }, 183 | hdr.ipv4.hdrChecksum, 184 | HashAlgorithm.csum16); 185 | } 186 | } 187 | 188 | /************************************************************************* 189 | *********************** D E P A R S E R ******************************* 190 | *************************************************************************/ 191 | 192 | control MyDeparser(packet_out packet, in headers hdr) { 193 | apply { 194 | packet.emit(hdr.ethernet); 195 | packet.emit(hdr.myTunnel); 196 | packet.emit(hdr.ipv4); 197 | } 198 | } 199 | 200 | /************************************************************************* 201 | *********************** S W I T C H ******************************* 202 | *************************************************************************/ 203 | 204 | V1Switch( 205 | MyParser(), 206 | MyVerifyChecksum(), 207 | MyIngress(), 208 | MyEgress(), 209 | MyComputeChecksum(), 210 | MyDeparser() 211 | ) main; 212 | -------------------------------------------------------------------------------- /Exercises/myTunnel_header.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from scapy.all import * 4 | 5 | TYPE_MYTUNNEL = 0x1212 6 | TYPE_IPV4 = 0x0800 7 | 8 | class MyTunnel(Packet): 9 | name = "MyTunnel" 10 | fields_desc = [ 11 | ShortField("pid", 0), 12 | ShortField("dst_id", 0) 13 | ] 14 | def mysummary(self): 15 | return self.sprintf("pid=%pid%, dst_id=%dst_id%") 16 | 17 | 18 | bind_layers(Ether, MyTunnel, type=TYPE_MYTUNNEL) 19 | bind_layers(MyTunnel, IP, pid=TYPE_IPV4) 20 | 21 | -------------------------------------------------------------------------------- /Exercises/mytopo.py: -------------------------------------------------------------------------------- 1 | 2 | from mininet.topo import Topo 3 | 4 | 5 | class Simple3PktSwitch(Topo): 6 | """Simple topology example.""" 7 | 8 | def __init__(self, **opts): 9 | """Create custom topo.""" 10 | 11 | # Initialize topology 12 | super(Simple3PktSwitch, self).__init__(**opts) 13 | #Topo.__init__(self) 14 | 15 | # Add hosts and switches 16 | h1 = self.addHost('h1') 17 | h2 = self.addHost('h2') 18 | h3 = self.addHost('h3') 19 | h4 = self.addHost('h4') 20 | 21 | opts = dict(protocols='OpenFlow13') 22 | 23 | # Adding switches 24 | s1 = self.addSwitch('s1', dpid="0000000000000001") 25 | s2 = self.addSwitch('s2', dpid="0000000000000002") 26 | s3 = self.addSwitch('s3', dpid="0000000000000003") 27 | s4 = self.addSwitch('s4', dpid="0000000000000004") 28 | s5 = self.addSwitch('s5', dpid="0000000000000005") 29 | s6 = self.addSwitch('s6', dpid="0000000000000006") 30 | 31 | # Add links 32 | self.addLink(h1, s1) 33 | self.addLink(h2, s1) 34 | 35 | self.addLink(h3, s6) 36 | self.addLink(h4, s6) 37 | 38 | self.addLink(s1, s2) 39 | self.addLink(s1, s3) 40 | self.addLink(s2, s4) 41 | self.addLink(s3, s5) 42 | self.addLink(s4, s6) 43 | self.addLink(s5, s6) 44 | 45 | topos = { 'mytopo': ( lambda:Simple3PktSwitch() ) } 46 | 47 | -------------------------------------------------------------------------------- /Exercises/mytopology.py: -------------------------------------------------------------------------------- 1 | from mininet.topo import Topo 2 | 3 | class MyTopo(Topo): 4 | def build(self): 5 | h1=self.addHost('h1', mac='00:00:00:00:00:01', ip="10.0.0.1") 6 | h2=self.addHost('h2', mac='00:00:00:00:00:02', ip="10.0.0.2") 7 | h3=self.addHost('h3', mac='00:00:00:00:00:03', ip="10.0.0.3") 8 | h4=self.addHost('h4', mac='00:00:00:00:00:04', ip="10.0.0.4") 9 | s1=self.addSwitch('s1') 10 | s2=self.addSwitch('s2') 11 | s3=self.addSwitch('s3') 12 | s4=self.addSwitch('s4') 13 | s5=self.addSwitch('s5') 14 | s6=self.addSwitch('s6') 15 | s7=self.addSwitch('s7') 16 | 17 | self.addLink(h1,s1) 18 | self.addLink(s1,s2) 19 | self.addLink(s1,s5) 20 | self.addLink(s1,s3) 21 | self.addLink(h3,s3) 22 | self.addLink(s3,s6) 23 | self.addLink(s3,s5) 24 | self.addLink(s2,s4) 25 | self.addLink(s2,s5) 26 | self.addLink(h4,s4) 27 | self.addLink(s5,s4) 28 | self.addLink(s5,s6) 29 | self.addLink(s5,s7) 30 | self.addLink(s4,s7) 31 | self.addLink(s6,s7) 32 | self.addLink(s7,h2) 33 | topos={ 'mytopo':( lambda:MyTopo())} 34 | -------------------------------------------------------------------------------- /Exercises/netstat.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-present Open Networking Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import psutil 17 | 18 | 19 | def check_listening_on_port(port): 20 | for c in psutil.net_connections(kind='inet'): 21 | if c.status == 'LISTEN' and c.laddr[1] == port: 22 | return True 23 | return False 24 | -------------------------------------------------------------------------------- /Exercises/p4_mininet.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013-present Barefoot Networks, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import os 17 | import tempfile 18 | from sys import exit 19 | from time import sleep 20 | 21 | from mininet.log import debug, error, info 22 | from mininet.moduledeps import pathCheck 23 | from mininet.node import Host, Switch 24 | from netstat import check_listening_on_port 25 | 26 | SWITCH_START_TIMEOUT = 10 # seconds 27 | 28 | class P4Host(Host): 29 | def config(self, **params): 30 | r = super(Host, self).config(**params) 31 | 32 | self.defaultIntf().rename("eth0") 33 | 34 | for off in ["rx", "tx", "sg"]: 35 | cmd = "/sbin/ethtool --offload eth0 %s off" % off 36 | self.cmd(cmd) 37 | 38 | # disable IPv6 39 | self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1") 40 | self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1") 41 | self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") 42 | 43 | return r 44 | 45 | def describe(self): 46 | print("**********") 47 | print(self.name) 48 | print("default interface: %s\t%s\t%s" %( 49 | self.defaultIntf().name, 50 | self.defaultIntf().IP(), 51 | self.defaultIntf().MAC() 52 | )) 53 | print("**********") 54 | 55 | class P4Switch(Switch): 56 | """P4 virtual switch""" 57 | device_id = 0 58 | 59 | def __init__(self, name, sw_path = None, json_path = None, 60 | thrift_port = None, 61 | pcap_dump = False, 62 | log_console = False, 63 | log_file = None, 64 | verbose = False, 65 | device_id = None, 66 | enable_debugger = False, 67 | **kwargs): 68 | Switch.__init__(self, name, **kwargs) 69 | assert(sw_path) 70 | assert(json_path) 71 | # make sure that the provided sw_path is valid 72 | pathCheck(sw_path) 73 | # make sure that the provided JSON file exists 74 | if not os.path.isfile(json_path): 75 | error("Invalid JSON file.\n") 76 | exit(1) 77 | self.sw_path = sw_path 78 | self.json_path = json_path 79 | self.verbose = verbose 80 | logfile = "/tmp/p4s.{}.log".format(self.name) 81 | self.output = open(logfile, 'w') 82 | self.thrift_port = thrift_port 83 | if check_listening_on_port(self.thrift_port): 84 | error('%s cannot bind port %d because it is bound by another process\n' % (self.name, self.grpc_port)) 85 | exit(1) 86 | self.pcap_dump = pcap_dump 87 | self.enable_debugger = enable_debugger 88 | self.log_console = log_console 89 | if log_file is not None: 90 | self.log_file = log_file 91 | else: 92 | self.log_file = "/tmp/p4s.{}.log".format(self.name) 93 | if device_id is not None: 94 | self.device_id = device_id 95 | P4Switch.device_id = max(P4Switch.device_id, device_id) 96 | else: 97 | self.device_id = P4Switch.device_id 98 | P4Switch.device_id += 1 99 | self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) 100 | 101 | @classmethod 102 | def setup(cls): 103 | pass 104 | 105 | def check_switch_started(self, pid): 106 | """While the process is running (pid exists), we check if the Thrift 107 | server has been started. If the Thrift server is ready, we assume that 108 | the switch was started successfully. This is only reliable if the Thrift 109 | server is started at the end of the init process""" 110 | while True: 111 | if not os.path.exists(os.path.join("/proc", str(pid))): 112 | return False 113 | if check_listening_on_port(self.thrift_port): 114 | return True 115 | sleep(0.5) 116 | 117 | def start(self, controllers): 118 | "Start up a new P4 switch" 119 | info("Starting P4 switch {}.\n".format(self.name)) 120 | args = [self.sw_path] 121 | for port, intf in list(self.intfs.items()): 122 | if not intf.IP(): 123 | args.extend(['-i', str(port) + "@" + intf.name]) 124 | if self.pcap_dump: 125 | args.append("--pcap %s" % self.pcap_dump) 126 | if self.thrift_port: 127 | args.extend(['--thrift-port', str(self.thrift_port)]) 128 | if self.nanomsg: 129 | args.extend(['--nanolog', self.nanomsg]) 130 | args.extend(['--device-id', str(self.device_id)]) 131 | P4Switch.device_id += 1 132 | args.append(self.json_path) 133 | if self.enable_debugger: 134 | args.append("--debugger") 135 | if self.log_console: 136 | args.append("--log-console") 137 | info(' '.join(args) + "\n") 138 | 139 | pid = None 140 | with tempfile.NamedTemporaryFile() as f: 141 | # self.cmd(' '.join(args) + ' > /dev/null 2>&1 &') 142 | self.cmd(' '.join(args) + ' >' + self.log_file + ' 2>&1 & echo $! >> ' + f.name) 143 | pid = int(f.read()) 144 | debug("P4 switch {} PID is {}.\n".format(self.name, pid)) 145 | if not self.check_switch_started(pid): 146 | error("P4 switch {} did not start correctly.\n".format(self.name)) 147 | exit(1) 148 | info("P4 switch {} has been started.\n".format(self.name)) 149 | 150 | def stop(self): 151 | "Terminate P4 switch." 152 | self.output.flush() 153 | self.cmd('kill %' + self.sw_path) 154 | self.cmd('wait') 155 | self.deleteIntfs() 156 | 157 | def attach(self, intf): 158 | "Connect a data port" 159 | assert(0) 160 | 161 | def detach(self, intf): 162 | "Disconnect a data port" 163 | assert(0) 164 | -------------------------------------------------------------------------------- /Exercises/p4runtime_lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Exercises/p4runtime_lib/__init__.py -------------------------------------------------------------------------------- /Exercises/p4runtime_lib/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Exercises/p4runtime_lib/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /Exercises/p4runtime_lib/__pycache__/bmv2.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Exercises/p4runtime_lib/__pycache__/bmv2.cpython-38.pyc -------------------------------------------------------------------------------- /Exercises/p4runtime_lib/__pycache__/convert.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Exercises/p4runtime_lib/__pycache__/convert.cpython-38.pyc -------------------------------------------------------------------------------- /Exercises/p4runtime_lib/__pycache__/helper.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Exercises/p4runtime_lib/__pycache__/helper.cpython-38.pyc -------------------------------------------------------------------------------- /Exercises/p4runtime_lib/__pycache__/simple_controller.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Exercises/p4runtime_lib/__pycache__/simple_controller.cpython-38.pyc -------------------------------------------------------------------------------- /Exercises/p4runtime_lib/__pycache__/switch.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Exercises/p4runtime_lib/__pycache__/switch.cpython-38.pyc -------------------------------------------------------------------------------- /Exercises/p4runtime_lib/bmv2.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-present Open Networking Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from p4.tmp import p4config_pb2 16 | 17 | from .switch import SwitchConnection 18 | 19 | 20 | def buildDeviceConfig(bmv2_json_file_path=None): 21 | "Builds the device config for BMv2" 22 | device_config = p4config_pb2.P4DeviceConfig() 23 | device_config.reassign = True 24 | with open(bmv2_json_file_path) as f: 25 | device_config.device_data = f.read().encode('utf-8') 26 | return device_config 27 | 28 | 29 | class Bmv2SwitchConnection(SwitchConnection): 30 | def buildDeviceConfig(self, **kwargs): 31 | return buildDeviceConfig(**kwargs) 32 | -------------------------------------------------------------------------------- /Exercises/p4runtime_lib/convert.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-present Open Networking Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | import math 16 | import re 17 | import socket 18 | 19 | ''' 20 | This package contains several helper functions for encoding to and decoding from byte strings: 21 | - integers 22 | - IPv4 address strings 23 | - Ethernet address strings 24 | ''' 25 | 26 | mac_pattern = re.compile('^([\da-fA-F]{2}:){5}([\da-fA-F]{2})$') 27 | def matchesMac(mac_addr_string): 28 | return mac_pattern.match(mac_addr_string) is not None 29 | 30 | def encodeMac(mac_addr_string): 31 | return bytes.fromhex(mac_addr_string.replace(':', '')) 32 | 33 | def decodeMac(encoded_mac_addr): 34 | return ':'.join(s.hex() for s in encoded_mac_addr) 35 | 36 | ip_pattern = re.compile('^(\d{1,3}\.){3}(\d{1,3})$') 37 | def matchesIPv4(ip_addr_string): 38 | return ip_pattern.match(ip_addr_string) is not None 39 | 40 | def encodeIPv4(ip_addr_string): 41 | return socket.inet_aton(ip_addr_string) 42 | 43 | def decodeIPv4(encoded_ip_addr): 44 | return socket.inet_ntoa(encoded_ip_addr) 45 | 46 | def bitwidthToBytes(bitwidth): 47 | return int(math.ceil(bitwidth / 8.0)) 48 | 49 | def encodeNum(number, bitwidth): 50 | byte_len = bitwidthToBytes(bitwidth) 51 | num_str = '%x' % number 52 | if number >= 2 ** bitwidth: 53 | raise Exception("Number, %d, does not fit in %d bits" % (number, bitwidth)) 54 | return bytes.fromhex('0' * (byte_len * 2 - len(num_str)) + num_str) 55 | 56 | def decodeNum(encoded_number): 57 | return int(encoded_number.hex(), 16) 58 | 59 | def encode(x, bitwidth): 60 | 'Tries to infer the type of `x` and encode it' 61 | byte_len = bitwidthToBytes(bitwidth) 62 | if (type(x) == list or type(x) == tuple) and len(x) == 1: 63 | x = x[0] 64 | encoded_bytes = None 65 | if type(x) == str: 66 | if matchesMac(x): 67 | encoded_bytes = encodeMac(x) 68 | elif matchesIPv4(x): 69 | encoded_bytes = encodeIPv4(x) 70 | else: 71 | # Assume that the string is already encoded 72 | encoded_bytes = x 73 | elif type(x) == int: 74 | encoded_bytes = encodeNum(x, bitwidth) 75 | else: 76 | raise Exception("Encoding objects of %r is not supported" % type(x)) 77 | assert(len(encoded_bytes) == byte_len) 78 | return encoded_bytes 79 | 80 | if __name__ == '__main__': 81 | # TODO These tests should be moved out of main eventually 82 | mac = "aa:bb:cc:dd:ee:ff" 83 | enc_mac = encodeMac(mac) 84 | assert(enc_mac == '\xaa\xbb\xcc\xdd\xee\xff') 85 | dec_mac = decodeMac(enc_mac) 86 | assert(mac == dec_mac) 87 | 88 | ip = "10.0.0.1" 89 | enc_ip = encodeIPv4(ip) 90 | assert(enc_ip == '\x0a\x00\x00\x01') 91 | dec_ip = decodeIPv4(enc_ip) 92 | assert(ip == dec_ip) 93 | 94 | num = 1337 95 | byte_len = 5 96 | enc_num = encodeNum(num, byte_len * 8) 97 | assert(enc_num == '\x00\x00\x00\x05\x39') 98 | dec_num = decodeNum(enc_num) 99 | assert(num == dec_num) 100 | 101 | assert(matchesIPv4('10.0.0.1')) 102 | assert(not matchesIPv4('10.0.0.1.5')) 103 | assert(not matchesIPv4('1000.0.0.1')) 104 | assert(not matchesIPv4('10001')) 105 | 106 | assert(encode(mac, 6 * 8) == enc_mac) 107 | assert(encode(ip, 4 * 8) == enc_ip) 108 | assert(encode(num, 5 * 8) == enc_num) 109 | assert(encode((num,), 5 * 8) == enc_num) 110 | assert(encode([num], 5 * 8) == enc_num) 111 | 112 | num = 256 113 | byte_len = 2 114 | try: 115 | enc_num = encodeNum(num, 8) 116 | raise Exception("expected exception") 117 | except Exception as e: 118 | print(e) 119 | -------------------------------------------------------------------------------- /Exercises/p4runtime_lib/error_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013-present Barefoot Networks, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import sys 17 | 18 | import grpc 19 | from google.rpc import code_pb2, status_pb2 20 | from p4.v1 import p4runtime_pb2 21 | 22 | 23 | # Used to indicate that the gRPC error Status object returned by the server has 24 | # an incorrect format. 25 | class P4RuntimeErrorFormatException(Exception): 26 | def __init__(self, message): 27 | super(P4RuntimeErrorFormatException, self).__init__(message) 28 | 29 | 30 | # Parse the binary details of the gRPC error. This is required to print some 31 | # helpful debugging information in tha case of batched Write / Read 32 | # requests. Returns None if there are no useful binary details and throws 33 | # P4RuntimeErrorFormatException if the error is not formatted 34 | # properly. Otherwise, returns a list of tuples with the first element being the 35 | # index of the operation in the batch that failed and the second element being 36 | # the p4.Error Protobuf message. 37 | def parseGrpcErrorBinaryDetails(grpc_error): 38 | if grpc_error.code() != grpc.StatusCode.UNKNOWN: 39 | return None 40 | 41 | error = None 42 | # The gRPC Python package does not have a convenient way to access the 43 | # binary details for the error: they are treated as trailing metadata. 44 | for meta in grpc_error.trailing_metadata(): 45 | if meta[0] == "grpc-status-details-bin": 46 | error = status_pb2.Status() 47 | error.ParseFromString(meta[1]) 48 | break 49 | if error is None: # no binary details field 50 | return None 51 | if len(error.details) == 0: 52 | # binary details field has empty Any details repeated field 53 | return None 54 | 55 | indexed_p4_errors = [] 56 | for idx, one_error_any in enumerate(error.details): 57 | p4_error = p4runtime_pb2.Error() 58 | if not one_error_any.Unpack(p4_error): 59 | raise P4RuntimeErrorFormatException( 60 | "Cannot convert Any message to p4.Error") 61 | if p4_error.canonical_code == code_pb2.OK: 62 | continue 63 | indexed_p4_errors += [(idx, p4_error)] 64 | 65 | return indexed_p4_errors 66 | 67 | 68 | # P4Runtime uses a 3-level message in case of an error during the processing of 69 | # a write batch. This means that some care is required when printing the 70 | # exception if we do not want to end-up with a non-helpful message in case of 71 | # failure as only the first level will be printed. In this function, we extract 72 | # the nested error message when present (one for each operation included in the 73 | # batch) in order to print error code + user-facing message. See P4Runtime 74 | # documentation for more details on error-reporting. 75 | def printGrpcError(grpc_error): 76 | print("gRPC Error", grpc_error.details(), end=' ') 77 | status_code = grpc_error.code() 78 | print("({})".format(status_code.name), end=' ') 79 | traceback = sys.exc_info()[2] 80 | print("[{}:{}]".format( 81 | traceback.tb_frame.f_code.co_filename, traceback.tb_lineno)) 82 | if status_code != grpc.StatusCode.UNKNOWN: 83 | return 84 | p4_errors = parseGrpcErrorBinaryDetails(grpc_error) 85 | if p4_errors is None: 86 | return 87 | print("Errors in batch:") 88 | for idx, p4_error in p4_errors: 89 | code_name = code_pb2._CODE.values_by_number[ 90 | p4_error.canonical_code].name 91 | print("\t* At index {}: {}, '{}'\n".format( 92 | idx, code_name, p4_error.message)) 93 | -------------------------------------------------------------------------------- /Exercises/p4runtime_lib/switch.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-present Open Networking Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from abc import abstractmethod 16 | from datetime import datetime 17 | from queue import Queue 18 | 19 | import grpc 20 | from p4.tmp import p4config_pb2 21 | from p4.v1 import p4runtime_pb2, p4runtime_pb2_grpc 22 | 23 | MSG_LOG_MAX_LEN = 1024 24 | 25 | # List of all active connections 26 | connections = [] 27 | 28 | def ShutdownAllSwitchConnections(): 29 | for c in connections: 30 | c.shutdown() 31 | 32 | class SwitchConnection(object): 33 | 34 | def __init__(self, name=None, address='127.0.0.1:50051', device_id=0, 35 | proto_dump_file=None): 36 | self.name = name 37 | self.address = address 38 | self.device_id = device_id 39 | self.p4info = None 40 | self.channel = grpc.insecure_channel(self.address) 41 | if proto_dump_file is not None: 42 | interceptor = GrpcRequestLogger(proto_dump_file) 43 | self.channel = grpc.intercept_channel(self.channel, interceptor) 44 | self.client_stub = p4runtime_pb2_grpc.P4RuntimeStub(self.channel) 45 | self.requests_stream = IterableQueue() 46 | self.stream_msg_resp = self.client_stub.StreamChannel(iter(self.requests_stream)) 47 | self.proto_dump_file = proto_dump_file 48 | connections.append(self) 49 | 50 | @abstractmethod 51 | def buildDeviceConfig(self, **kwargs): 52 | return p4config_pb2.P4DeviceConfig() 53 | 54 | def shutdown(self): 55 | self.requests_stream.close() 56 | self.stream_msg_resp.cancel() 57 | 58 | def MasterArbitrationUpdate(self, dry_run=False, **kwargs): 59 | request = p4runtime_pb2.StreamMessageRequest() 60 | request.arbitration.device_id = self.device_id 61 | request.arbitration.election_id.high = 0 62 | request.arbitration.election_id.low = 1 63 | 64 | if dry_run: 65 | print("P4Runtime MasterArbitrationUpdate: ", request) 66 | else: 67 | self.requests_stream.put(request) 68 | for item in self.stream_msg_resp: 69 | return item # just one 70 | 71 | def SetForwardingPipelineConfig(self, p4info, dry_run=False, **kwargs): 72 | device_config = self.buildDeviceConfig(**kwargs) 73 | request = p4runtime_pb2.SetForwardingPipelineConfigRequest() 74 | request.election_id.low = 1 75 | request.device_id = self.device_id 76 | config = request.config 77 | 78 | config.p4info.CopyFrom(p4info) 79 | config.p4_device_config = device_config.SerializeToString() 80 | 81 | request.action = p4runtime_pb2.SetForwardingPipelineConfigRequest.VERIFY_AND_COMMIT 82 | if dry_run: 83 | print("P4Runtime SetForwardingPipelineConfig:", request) 84 | else: 85 | self.client_stub.SetForwardingPipelineConfig(request) 86 | 87 | def WriteTableEntry(self, table_entry, dry_run=False): 88 | request = p4runtime_pb2.WriteRequest() 89 | request.device_id = self.device_id 90 | request.election_id.low = 1 91 | update = request.updates.add() 92 | if table_entry.is_default_action: 93 | update.type = p4runtime_pb2.Update.MODIFY 94 | else: 95 | update.type = p4runtime_pb2.Update.INSERT 96 | update.entity.table_entry.CopyFrom(table_entry) 97 | if dry_run: 98 | print("P4Runtime Write:", request) 99 | else: 100 | self.client_stub.Write(request) 101 | 102 | def ReadTableEntries(self, table_id=None, dry_run=False): 103 | request = p4runtime_pb2.ReadRequest() 104 | request.device_id = self.device_id 105 | entity = request.entities.add() 106 | table_entry = entity.table_entry 107 | if table_id is not None: 108 | table_entry.table_id = table_id 109 | else: 110 | table_entry.table_id = 0 111 | if dry_run: 112 | print("P4Runtime Read:", request) 113 | else: 114 | for response in self.client_stub.Read(request): 115 | yield response 116 | 117 | def ReadCounters(self, counter_id=None, index=None, dry_run=False): 118 | request = p4runtime_pb2.ReadRequest() 119 | request.device_id = self.device_id 120 | entity = request.entities.add() 121 | counter_entry = entity.counter_entry 122 | if counter_id is not None: 123 | counter_entry.counter_id = counter_id 124 | else: 125 | counter_entry.counter_id = 0 126 | if index is not None: 127 | counter_entry.index.index = index 128 | if dry_run: 129 | print("P4Runtime Read:", request) 130 | else: 131 | for response in self.client_stub.Read(request): 132 | yield response 133 | 134 | 135 | def WritePREEntry(self, pre_entry, dry_run=False): 136 | request = p4runtime_pb2.WriteRequest() 137 | request.device_id = self.device_id 138 | request.election_id.low = 1 139 | update = request.updates.add() 140 | update.type = p4runtime_pb2.Update.INSERT 141 | update.entity.packet_replication_engine_entry.CopyFrom(pre_entry) 142 | if dry_run: 143 | print("P4Runtime Write:", request) 144 | else: 145 | self.client_stub.Write(request) 146 | 147 | class GrpcRequestLogger(grpc.UnaryUnaryClientInterceptor, 148 | grpc.UnaryStreamClientInterceptor): 149 | """Implementation of a gRPC interceptor that logs request to a file""" 150 | 151 | def __init__(self, log_file): 152 | self.log_file = log_file 153 | with open(self.log_file, 'w') as f: 154 | # Clear content if it exists. 155 | f.write("") 156 | 157 | def log_message(self, method_name, body): 158 | with open(self.log_file, 'a') as f: 159 | ts = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] 160 | msg = str(body) 161 | f.write("\n[%s] %s\n---\n" % (ts, method_name)) 162 | if len(msg) < MSG_LOG_MAX_LEN: 163 | f.write(str(body)) 164 | else: 165 | f.write("Message too long (%d bytes)! Skipping log...\n" % len(msg)) 166 | f.write('---\n') 167 | 168 | def intercept_unary_unary(self, continuation, client_call_details, request): 169 | self.log_message(client_call_details.method, request) 170 | return continuation(client_call_details, request) 171 | 172 | def intercept_unary_stream(self, continuation, client_call_details, request): 173 | self.log_message(client_call_details.method, request) 174 | return continuation(client_call_details, request) 175 | 176 | class IterableQueue(Queue): 177 | _sentinel = object() 178 | 179 | def __iter__(self): 180 | return iter(self.get, self._sentinel) 181 | 182 | def close(self): 183 | self.put(self._sentinel) 184 | -------------------------------------------------------------------------------- /Exercises/p4runtime_switch.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-present Barefoot Networks, Inc. 2 | # Copyright 2017-present Open Networking Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | import os 18 | import tempfile 19 | from time import sleep 20 | 21 | from mininet.log import debug, error, info 22 | from mininet.moduledeps import pathCheck 23 | from mininet.node import Switch 24 | from netstat import check_listening_on_port 25 | from p4_mininet import SWITCH_START_TIMEOUT, P4Switch 26 | 27 | 28 | class P4RuntimeSwitch(P4Switch): 29 | "BMv2 switch with gRPC support" 30 | next_grpc_port = 50051 31 | next_thrift_port = 9090 32 | 33 | def __init__(self, name, sw_path = None, json_path = None, 34 | grpc_port = None, 35 | thrift_port = None, 36 | pcap_dump = False, 37 | log_console = False, 38 | verbose = False, 39 | device_id = None, 40 | enable_debugger = False, 41 | log_file = None, 42 | **kwargs): 43 | Switch.__init__(self, name, **kwargs) 44 | assert (sw_path) 45 | self.sw_path = sw_path 46 | # make sure that the provided sw_path is valid 47 | pathCheck(sw_path) 48 | 49 | if json_path is not None: 50 | # make sure that the provided JSON file exists 51 | if not os.path.isfile(json_path): 52 | error("Invalid JSON file: {}\n".format(json_path)) 53 | exit(1) 54 | self.json_path = json_path 55 | else: 56 | self.json_path = None 57 | 58 | if grpc_port is not None: 59 | self.grpc_port = grpc_port 60 | else: 61 | self.grpc_port = P4RuntimeSwitch.next_grpc_port 62 | P4RuntimeSwitch.next_grpc_port += 1 63 | 64 | if thrift_port is not None: 65 | self.thrift_port = thrift_port 66 | else: 67 | self.thrift_port = P4RuntimeSwitch.next_thrift_port 68 | P4RuntimeSwitch.next_thrift_port += 1 69 | 70 | if check_listening_on_port(self.grpc_port): 71 | error('%s cannot bind port %d because it is bound by another process\n' % (self.name, self.grpc_port)) 72 | exit(1) 73 | 74 | self.verbose = verbose 75 | logfile = "/tmp/p4s.{}.log".format(self.name) 76 | self.output = open(logfile, 'w') 77 | self.pcap_dump = pcap_dump 78 | self.enable_debugger = enable_debugger 79 | self.log_console = log_console 80 | if log_file is not None: 81 | self.log_file = log_file 82 | else: 83 | self.log_file = "/tmp/p4s.{}.log".format(self.name) 84 | if device_id is not None: 85 | self.device_id = device_id 86 | P4Switch.device_id = max(P4Switch.device_id, device_id) 87 | else: 88 | self.device_id = P4Switch.device_id 89 | P4Switch.device_id += 1 90 | self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) 91 | 92 | 93 | def check_switch_started(self, pid): 94 | for _ in range(SWITCH_START_TIMEOUT * 2): 95 | if not os.path.exists(os.path.join("/proc", str(pid))): 96 | return False 97 | if check_listening_on_port(self.grpc_port): 98 | return True 99 | sleep(0.5) 100 | 101 | def start(self, controllers): 102 | info("Starting P4 switch {}.\n".format(self.name)) 103 | args = [self.sw_path] 104 | for port, intf in list(self.intfs.items()): 105 | if not intf.IP(): 106 | args.extend(['-i', str(port) + "@" + intf.name]) 107 | if self.pcap_dump: 108 | args.append("--pcap %s" % self.pcap_dump) 109 | if self.nanomsg: 110 | args.extend(['--nanolog', self.nanomsg]) 111 | args.extend(['--device-id', str(self.device_id)]) 112 | P4Switch.device_id += 1 113 | if self.json_path: 114 | args.append(self.json_path) 115 | else: 116 | args.append("--no-p4") 117 | if self.enable_debugger: 118 | args.append("--debugger") 119 | if self.log_console: 120 | args.append("--log-console") 121 | if self.thrift_port: 122 | args.append('--thrift-port ' + str(self.thrift_port)) 123 | if self.grpc_port: 124 | args.append("-- --grpc-server-addr 0.0.0.0:" + str(self.grpc_port)) 125 | cmd = ' '.join(args) 126 | info(cmd + "\n") 127 | 128 | 129 | pid = None 130 | with tempfile.NamedTemporaryFile() as f: 131 | self.cmd(cmd + ' >' + self.log_file + ' 2>&1 & echo $! >> ' + f.name) 132 | pid = int(f.read()) 133 | debug("P4 switch {} PID is {}.\n".format(self.name, pid)) 134 | if not self.check_switch_started(pid): 135 | error("P4 switch {} did not start correctly.\n".format(self.name)) 136 | exit(1) 137 | info("P4 switch {} has been started.\n".format(self.name)) 138 | 139 | -------------------------------------------------------------------------------- /Exercises/receive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | 5 | from myTunnel_header import MyTunnel 6 | from scapy.all import TCP, get_if_list, sniff 7 | 8 | 9 | def get_if(): 10 | ifs=get_if_list() 11 | iface=None 12 | for i in get_if_list(): 13 | if "eth0" in i: 14 | iface=i 15 | break; 16 | if not iface: 17 | print("Cannot find eth0 interface") 18 | exit(1) 19 | return iface 20 | 21 | def handle_pkt(pkt): 22 | if MyTunnel in pkt or (TCP in pkt and pkt[TCP].dport == 1234): 23 | print("got a packet") 24 | pkt.show2() 25 | # hexdump(pkt) 26 | # print "len(pkt) = ", len(pkt) 27 | sys.stdout.flush() 28 | 29 | 30 | def main(): 31 | ifaces = [i for i in os.listdir('/sys/class/net/') if 'eth' in i] 32 | iface = ifaces[0] 33 | print("sniffing on %s" % iface) 34 | sys.stdout.flush() 35 | sniff(iface = iface, 36 | prn = lambda x: handle_pkt(x)) 37 | 38 | if __name__ == '__main__': 39 | main() 40 | -------------------------------------------------------------------------------- /Exercises/runtime.py: -------------------------------------------------------------------------------- 1 | import argparse, os, sys, grpc 2 | 3 | sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../utils/')) 4 | 5 | import p4runtime_lib.bmv2, p4runtime_lib.helper 6 | from p4runtime_lib.switch import ShutdownAllSwitchConnections 7 | 8 | p4info = './build/acl_tunnel.p4.p4info.txt' 9 | bmv2_json = './build/acl_tunnel.json' 10 | 11 | 12 | p4info_helper = p4runtime_lib.helper.P4InfoHelper(p4info) 13 | sw = p4runtime_lib.bmv2.Bmv2SwitchConnection( 14 | name='s3', address='127.0.0.1:50053', device_id=2, proto_dump_file='logs/s3-p4runtime-requests.txt') 15 | sw.MasterArbitrationUpdate() 16 | #sw.SetForwardingPipelineConfig(p4info=p4info_helper.p4info, bmv2_json_file_path=bmv2_json) 17 | 18 | parser = argparse.ArgumentParser(description='P4Runtime Controller') 19 | parser.add_argument('--udp_port', type=int, required=False) 20 | parser.add_argument('--dst_ip', type=str, required=False) 21 | args = parser.parse_args() 22 | 23 | udp_port = args.udp_port 24 | dst_ip = args.dst_ip 25 | 26 | match_fields = {} 27 | if udp_port != None: 28 | match_fields["hdr.udp.dstPort"] = [udp_port, 65535] 29 | if dst_ip != None: 30 | match_fields["hdr.ipv4.dstAddr"] = [dst_ip, 4294967295] 31 | assert len(match_fields)>0 32 | 33 | table_entry = p4info_helper.buildTableEntry( 34 | table_name="MyIngress.access_control", 35 | match_fields=match_fields, 36 | action_name="MyIngress.drop", 37 | action_params={}, 38 | priority=1 39 | ) 40 | sw.WriteTableEntry(table_entry) 41 | print('entry added to flow table of switch s3') 42 | ShutdownAllSwitchConnections() 43 | -------------------------------------------------------------------------------- /Exercises/s1-runtime.json: -------------------------------------------------------------------------------- 1 | { 2 | "target": "bmv2", 3 | "p4info": "build/basic_tunnel.p4.p4info.txt", 4 | "bmv2_json": "build/basic_tunnel.json", 5 | "table_entries": [ 6 | { 7 | "table": "MyIngress.ipv4_lpm", 8 | "match": { 9 | "hdr.ipv4.dstAddr": ["10.0.1.1", 32] 10 | }, 11 | "action_name": "MyIngress.ipv4_forward", 12 | "action_params": { 13 | "dstAddr": "08:00:00:00:01:11", 14 | "port": 1 15 | } 16 | }, 17 | { 18 | "table": "MyIngress.ipv4_lpm", 19 | "match": { 20 | "hdr.ipv4.dstAddr": ["10.0.2.2", 32] 21 | }, 22 | "action_name": "MyIngress.ipv4_forward", 23 | "action_params": { 24 | "dstAddr": "08:00:00:00:02:00", 25 | "port": 2 26 | } 27 | }, 28 | { 29 | "table": "MyIngress.ipv4_lpm", 30 | "match": { 31 | "hdr.ipv4.dstAddr": ["10.0.3.3", 32] 32 | }, 33 | "action_name": "MyIngress.ipv4_forward", 34 | "action_params": { 35 | "dstAddr": "08:00:00:00:03:00", 36 | "port": 3 37 | } 38 | }, 39 | 40 | { 41 | "table": "MyIngress.myTunnel_exact", 42 | "match": { 43 | "hdr.myTunnel.dst_id": [1] 44 | }, 45 | "action_name": "MyIngress.myTunnel_forward", 46 | "action_params": { 47 | "port": 1 48 | } 49 | }, 50 | { 51 | "table": "MyIngress.myTunnel_exact", 52 | "match": { 53 | "hdr.myTunnel.dst_id": [2] 54 | }, 55 | "action_name": "MyIngress.myTunnel_forward", 56 | "action_params": { 57 | "port": 2 58 | } 59 | }, 60 | { 61 | "table": "MyIngress.myTunnel_exact", 62 | "match": { 63 | "hdr.myTunnel.dst_id": [3] 64 | }, 65 | "action_name": "MyIngress.myTunnel_forward", 66 | "action_params": { 67 | "port": 3 68 | } 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /Exercises/s2-runtime.json: -------------------------------------------------------------------------------- 1 | { 2 | "target": "bmv2", 3 | "p4info": "build/basic_tunnel.p4.p4info.txt", 4 | "bmv2_json": "build/basic_tunnel.json", 5 | "table_entries": [ 6 | { 7 | "table": "MyIngress.ipv4_lpm", 8 | "match": { 9 | "hdr.ipv4.dstAddr": ["10.0.1.1", 32] 10 | }, 11 | "action_name": "MyIngress.ipv4_forward", 12 | "action_params": { 13 | "dstAddr": "08:00:00:00:01:00", 14 | "port": 2 15 | } 16 | }, 17 | { 18 | "table": "MyIngress.ipv4_lpm", 19 | "match": { 20 | "hdr.ipv4.dstAddr": ["10.0.2.2", 32] 21 | }, 22 | "action_name": "MyIngress.ipv4_forward", 23 | "action_params": { 24 | "dstAddr": "08:00:00:00:02:22", 25 | "port": 1 26 | } 27 | }, 28 | { 29 | "table": "MyIngress.ipv4_lpm", 30 | "match": { 31 | "hdr.ipv4.dstAddr": ["10.0.3.3", 32] 32 | }, 33 | "action_name": "MyIngress.ipv4_forward", 34 | "action_params": { 35 | "dstAddr": "08:00:00:00:03:00", 36 | "port": 3 37 | } 38 | }, 39 | 40 | { 41 | "table": "MyIngress.myTunnel_exact", 42 | "match": { 43 | "hdr.myTunnel.dst_id": [1] 44 | }, 45 | "action_name": "MyIngress.myTunnel_forward", 46 | "action_params": { 47 | "port": 2 48 | } 49 | }, 50 | { 51 | "table": "MyIngress.myTunnel_exact", 52 | "match": { 53 | "hdr.myTunnel.dst_id": [2] 54 | }, 55 | "action_name": "MyIngress.myTunnel_forward", 56 | "action_params": { 57 | "port": 1 58 | } 59 | }, 60 | { 61 | "table": "MyIngress.myTunnel_exact", 62 | "match": { 63 | "hdr.myTunnel.dst_id": [3] 64 | }, 65 | "action_name": "MyIngress.myTunnel_forward", 66 | "action_params": { 67 | "port": 3 68 | } 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /Exercises/s3-runtime.json: -------------------------------------------------------------------------------- 1 | { 2 | "target": "bmv2", 3 | "p4info": "build/acl_tunnel.p4.p4info.txt", 4 | "bmv2_json": "build/acl_tunnel.json", 5 | "table_entries": [ 6 | { 7 | "table": "MyIngress.ipv4_lpm", 8 | "match": { 9 | "hdr.ipv4.dstAddr": ["10.0.1.1", 32] 10 | }, 11 | "action_name": "MyIngress.ipv4_forward", 12 | "action_params": { 13 | "dstAddr": "08:00:00:00:01:00", 14 | "port": 2 15 | } 16 | }, 17 | { 18 | "table": "MyIngress.ipv4_lpm", 19 | "match": { 20 | "hdr.ipv4.dstAddr": ["10.0.2.2", 32] 21 | }, 22 | "action_name": "MyIngress.ipv4_forward", 23 | "action_params": { 24 | "dstAddr": "08:00:00:00:02:00", 25 | "port": 3 26 | } 27 | }, 28 | { 29 | "table": "MyIngress.ipv4_lpm", 30 | "match": { 31 | "hdr.ipv4.dstAddr": ["10.0.3.3", 32] 32 | }, 33 | "action_name": "MyIngress.ipv4_forward", 34 | "action_params": { 35 | "dstAddr": "08:00:00:00:03:33", 36 | "port": 1 37 | } 38 | }, 39 | { 40 | "table": "MyIngress.myTunnel_exact", 41 | "match": { 42 | "hdr.myTunnel.dst_id": [1] 43 | }, 44 | "action_name": "MyIngress.myTunnel_forward", 45 | "action_params": { 46 | "port": 2 47 | } 48 | }, 49 | { 50 | "table": "MyIngress.myTunnel_exact", 51 | "match": { 52 | "hdr.myTunnel.dst_id": [2] 53 | }, 54 | "action_name": "MyIngress.myTunnel_forward", 55 | "action_params": { 56 | "port": 3 57 | } 58 | }, 59 | { 60 | "table": "MyIngress.myTunnel_exact", 61 | "match": { 62 | "hdr.myTunnel.dst_id": [3] 63 | }, 64 | "action_name": "MyIngress.myTunnel_forward", 65 | "action_params": { 66 | "port": 1 67 | } 68 | }, 69 | { 70 | "table": "MyIngress.acl_exact", 71 | "match": { 72 | "hdr.udp.dstPort": [80], 73 | "hdr.ipv4.dstAddr": ["10.0.3.3"] 74 | }, 75 | "action_name": "MyIngress.drop", 76 | "action_params": {} 77 | } 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /Exercises/send.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import random 4 | import socket 5 | 6 | from myTunnel_header import MyTunnel 7 | from scapy.all import IP, TCP, Ether, get_if_hwaddr, get_if_list, sendp 8 | 9 | 10 | def get_if(): 11 | ifs=get_if_list() 12 | iface=None # "h1-eth0" 13 | for i in get_if_list(): 14 | if "eth0" in i: 15 | iface=i 16 | break; 17 | if not iface: 18 | print("Cannot find eth0 interface") 19 | exit(1) 20 | return iface 21 | 22 | def main(): 23 | parser = argparse.ArgumentParser() 24 | parser.add_argument('ip_addr', type=str, help="The destination IP address to use") 25 | parser.add_argument('message', type=str, help="The message to include in packet") 26 | parser.add_argument('--dst_id', type=int, default=None, help='The myTunnel dst_id to use, if unspecified then myTunnel header will not be included in packet') 27 | args = parser.parse_args() 28 | 29 | addr = socket.gethostbyname(args.ip_addr) 30 | dst_id = args.dst_id 31 | iface = get_if() 32 | 33 | if (dst_id is not None): 34 | print("sending on interface {} to dst_id {}".format(iface, str(dst_id))) 35 | pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') 36 | pkt = pkt / MyTunnel(dst_id=dst_id) / IP(dst=addr) / args.message 37 | else: 38 | print("sending on interface {} to IP addr {}".format(iface, str(addr))) 39 | pkt = Ether(src=get_if_hwaddr(iface), dst='ff:ff:ff:ff:ff:ff') 40 | pkt = pkt / IP(dst=addr) / TCP(dport=1234, sport=random.randint(49152,65535)) / args.message 41 | 42 | pkt.show2() 43 | # hexdump(pkt) 44 | # print "len(pkt) = ", len(pkt) 45 | sendp(pkt, iface=iface, verbose=False) 46 | 47 | 48 | if __name__ == '__main__': 49 | main() 50 | -------------------------------------------------------------------------------- /Exercises/topo-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Exercises/topo-2.png -------------------------------------------------------------------------------- /Exercises/topo-sdn-disabled.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from mininet.net import Mininet 4 | from mininet.node import RemoteController 5 | from mininet.node import Host 6 | from mininet.node import OVSKernelSwitch 7 | from mininet.cli import CLI 8 | from mininet.log import setLogLevel, info 9 | from mininet.link import TCLink 10 | 11 | def myNetwork(): 12 | 13 | net = Mininet( topo=None, 14 | build=False, 15 | ipBase='10.0.0.0/8') 16 | 17 | info( '*** Adding controller\n' ) 18 | c0 = net.addController('c0', RemoteController) 19 | info( '*** Add switches\n') 20 | s1 = net.addSwitch('s1', cls=OVSKernelSwitch) 21 | s2 = net.addSwitch('s2', cls=OVSKernelSwitch) 22 | s3 = net.addSwitch('s3', cls=OVSKernelSwitch) 23 | s4 = net.addSwitch('s4', cls=OVSKernelSwitch) 24 | s5 = net.addSwitch('s5', cls=OVSKernelSwitch) 25 | s6 = net.addSwitch('s6', cls=OVSKernelSwitch) 26 | 27 | info( '*** Add hosts\n') 28 | h1 = net.addHost('h1', cls=Host, ip='10.0.0.1', defaultRoute=None) 29 | h2 = net.addHost('h2', cls=Host, ip='10.0.0.2', defaultRoute=None) 30 | h3 = net.addHost('h3', cls=Host, ip='10.0.0.3', defaultRoute=None) 31 | h4 = net.addHost('h4', cls=Host, ip='10.0.0.4', defaultRoute=None) 32 | 33 | info( '*** Add links\n') 34 | net.addLink(s1, s2, cls=TCLink, bw=100) 35 | net.addLink(s2, s4, cls=TCLink, bw=100) 36 | net.addLink(s4, s6, cls=TCLink, bw=100) 37 | net.addLink(s6, s5, cls=TCLink, bw=100) 38 | net.addLink(s5, s3, cls=TCLink, bw=100) 39 | net.addLink(s3, s1, cls=TCLink, bw=100) 40 | net.addLink(h1, s1, cls=TCLink, bw=100) 41 | net.addLink(h2, s1, cls=TCLink, bw=100) 42 | net.addLink(h3, s6, cls=TCLink, bw=100) 43 | net.addLink(s6, h4, cls=TCLink, bw=100) 44 | 45 | info( '*** Starting network\n') 46 | net.build() 47 | info( '*** Starting controllers\n') 48 | c0.start() 49 | 50 | info( '*** Starting switches\n') 51 | net.get('s1').start([c0]) 52 | net.get('s2').start([c0]) 53 | net.get('s5').start([c0]) 54 | net.get('s6').start([c0]) 55 | net.get('s3').start([c0]) 56 | net.get('s4').start([c0]) 57 | 58 | info( '*** Post configure switches and hosts\n') 59 | 60 | CLI(net) 61 | net.stop() 62 | 63 | if __name__ == '__main__': 64 | setLogLevel( 'info' ) 65 | myNetwork() 66 | 67 | -------------------------------------------------------------------------------- /Exercises/topo-stp-enabled.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from mininet.net import Mininet 4 | from mininet.node import RemoteController 5 | from mininet.node import Host 6 | from mininet.node import OVSKernelSwitch 7 | from mininet.cli import CLI 8 | from mininet.log import setLogLevel, info 9 | from mininet.link import TCLink 10 | 11 | def myNetwork(): 12 | 13 | net = Mininet( topo=None, 14 | build=False, 15 | ipBase='10.0.0.0/8') 16 | 17 | info( '*** Adding controller\n' ) 18 | c0 = net.addController('c0', RemoteController) 19 | info( '*** Add switches\n') 20 | s1 = net.addSwitch('s1', cls=OVSKernelSwitch, stp=True, failMode='standalone') 21 | s2 = net.addSwitch('s2', cls=OVSKernelSwitch, stp=True, failMode='standalone') 22 | s3 = net.addSwitch('s3', cls=OVSKernelSwitch, stp=True, failMode='standalone') 23 | s4 = net.addSwitch('s4', cls=OVSKernelSwitch, stp=True, failMode='standalone') 24 | s5 = net.addSwitch('s5', cls=OVSKernelSwitch, stp=True, failMode='standalone') 25 | s6 = net.addSwitch('s6', cls=OVSKernelSwitch, stp=True, failMode='standalone') 26 | 27 | info( '*** Add hosts\n') 28 | h1 = net.addHost('h1', cls=Host, ip='10.0.0.1', defaultRoute=None) 29 | h2 = net.addHost('h2', cls=Host, ip='10.0.0.2', defaultRoute=None) 30 | h3 = net.addHost('h3', cls=Host, ip='10.0.0.3', defaultRoute=None) 31 | h4 = net.addHost('h4', cls=Host, ip='10.0.0.4', defaultRoute=None) 32 | 33 | info( '*** Add links\n') 34 | net.addLink(s1, s2, cls=TCLink, bw=100) 35 | net.addLink(s2, s4, cls=TCLink, bw=100) 36 | net.addLink(s4, s6, cls=TCLink, bw=100) 37 | net.addLink(s6, s5, cls=TCLink, bw=100) 38 | net.addLink(s5, s3, cls=TCLink, bw=100) 39 | net.addLink(s3, s1, cls=TCLink, bw=100) 40 | net.addLink(h1, s1, cls=TCLink, bw=100) 41 | net.addLink(h2, s1, cls=TCLink, bw=100) 42 | net.addLink(h3, s6, cls=TCLink, bw=100) 43 | net.addLink(s6, h4, cls=TCLink, bw=100) 44 | 45 | info( '*** Starting network\n') 46 | net.build() 47 | info( '*** Starting controllers\n') 48 | c0.start() 49 | 50 | info( '*** Starting switches\n') 51 | net.get('s1').start([c0]) 52 | net.get('s2').start([c0]) 53 | net.get('s5').start([c0]) 54 | net.get('s6').start([c0]) 55 | net.get('s3').start([c0]) 56 | net.get('s4').start([c0]) 57 | 58 | info( '*** Post configure switches and hosts\n') 59 | 60 | CLI(net) 61 | net.stop() 62 | 63 | if __name__ == '__main__': 64 | setLogLevel( 'info' ) 65 | myNetwork() 66 | 67 | -------------------------------------------------------------------------------- /Exercises/topo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Exercises/topo.pdf -------------------------------------------------------------------------------- /Exercises/topo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Exercises/topo.png -------------------------------------------------------------------------------- /Exercises/topology.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosts": { 3 | "h1": {"ip": "10.0.1.1/24", "mac": "08:00:00:00:01:11", 4 | "commands":["route add default gw 10.0.1.10 dev eth0", 5 | "arp -i eth0 -s 10.0.1.10 08:00:00:00:01:00"]}, 6 | "h2": {"ip": "10.0.2.2/24", "mac": "08:00:00:00:02:22", 7 | "commands":["route add default gw 10.0.2.20 dev eth0", 8 | "arp -i eth0 -s 10.0.2.20 08:00:00:00:02:00"]}, 9 | "h3": {"ip": "10.0.3.3/24", "mac": "08:00:00:00:03:33", 10 | "commands":["route add default gw 10.0.3.30 dev eth0", 11 | "arp -i eth0 -s 10.0.3.30 08:00:00:00:03:00"]} 12 | }, 13 | "switches": { 14 | "s1": { "runtime_json" : "s1-runtime.json","program":"build/basic_tunnel.json"}, 15 | "s2": { "runtime_json" : "s2-runtime.json","program":"build/basic_tunnel.json"}, 16 | "s3": { "runtime_json" : "s3-runtime.json","program":"build/acl_tunnel.json"} 17 | }, 18 | "links": [ 19 | ["h1", "s1-p1"], ["s1-p2", "s2-p2"], ["s1-p3", "s3-p2"], 20 | ["s3-p3", "s2-p3"], ["h2", "s2-p1"], ["h3", "s3-p1"] 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /Exercises/utils/Makefile: -------------------------------------------------------------------------------- 1 | BUILD_DIR = build 2 | PCAP_DIR = pcaps 3 | LOG_DIR = logs 4 | 5 | TOPO = topology.json 6 | P4C = p4c-bm2-ss 7 | RUN_SCRIPT = ../../utils/run_exercise.py 8 | 9 | source := $(wildcard *.p4) 10 | outfile := $(source:.p4=.json) 11 | 12 | compiled_json := $(BUILD_DIR)/$(outfile) 13 | 14 | # Define NO_P4 to start BMv2 without a program 15 | ifndef NO_P4 16 | run_args += -j $(compiled_json) 17 | endif 18 | 19 | # Set BMV2_SWITCH_EXE to override the BMv2 target 20 | ifdef BMV2_SWITCH_EXE 21 | run_args += -b $(BMV2_SWITCH_EXE) 22 | endif 23 | 24 | all: run 25 | 26 | run: build 27 | sudo python $(RUN_SCRIPT) -t $(TOPO) $(run_args) 28 | 29 | acl: build 30 | sudo python $(RUN_SCRIPT) -t $(TOPO) -e "acl" $(run_args) 31 | 32 | lb: build 33 | sudo python $(RUN_SCRIPT) -t $(TOPO) -e "lb" $(run_args) 34 | 35 | stop: 36 | sudo mn -c 37 | 38 | build: dirs $(compiled_json) 39 | 40 | $(BUILD_DIR)/%.json: %.p4 41 | $(P4C) --p4v 16 $(P4C_ARGS) -o $@ $< 42 | 43 | dirs: 44 | mkdir -p $(BUILD_DIR) $(PCAP_DIR) $(LOG_DIR) 45 | 46 | clean: stop 47 | rm -f *.pcap 48 | rm -rf $(BUILD_DIR) $(PCAP_DIR) $(LOG_DIR) 49 | -------------------------------------------------------------------------------- /Exercises/utils/mininet/p4_mininet.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013-present Barefoot Networks, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | from mininet.net import Mininet 17 | from mininet.node import Switch, Host 18 | from mininet.log import setLogLevel, info, error, debug 19 | from mininet.moduledeps import pathCheck 20 | from sys import exit 21 | from time import sleep 22 | import os 23 | import tempfile 24 | import socket 25 | 26 | class P4Host(Host): 27 | def config(self, **params): 28 | r = super(P4Host, self).config(**params) 29 | 30 | for off in ["rx", "tx", "sg"]: 31 | cmd = "/sbin/ethtool --offload %s %s off" % (self.defaultIntf().name, off) 32 | self.cmd(cmd) 33 | 34 | # disable IPv6 35 | self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1") 36 | self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1") 37 | self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") 38 | 39 | return r 40 | 41 | def describe(self, sw_addr=None, sw_mac=None): 42 | print "**********" 43 | print "Network configuration for: %s" % self.name 44 | print "Default interface: %s\t%s\t%s" %( 45 | self.defaultIntf().name, 46 | self.defaultIntf().IP(), 47 | self.defaultIntf().MAC() 48 | ) 49 | if sw_addr is not None or sw_mac is not None: 50 | print "Default route to switch: %s (%s)" % (sw_addr, sw_mac) 51 | print "**********" 52 | 53 | class P4Switch(Switch): 54 | """P4 virtual switch""" 55 | device_id = 0 56 | 57 | def __init__(self, name, sw_path = None, json_path = None, 58 | log_file = None, 59 | thrift_port = None, 60 | pcap_dump = False, 61 | log_console = False, 62 | verbose = False, 63 | device_id = None, 64 | enable_debugger = False, 65 | **kwargs): 66 | Switch.__init__(self, name, **kwargs) 67 | assert(sw_path) 68 | assert(json_path) 69 | # make sure that the provided sw_path is valid 70 | pathCheck(sw_path) 71 | # make sure that the provided JSON file exists 72 | if not os.path.isfile(json_path): 73 | error("Invalid JSON file.\n") 74 | exit(1) 75 | self.sw_path = sw_path 76 | self.json_path = json_path 77 | self.verbose = verbose 78 | self.log_file = log_file 79 | if self.log_file is None: 80 | self.log_file = "/tmp/p4s.{}.log".format(self.name) 81 | self.output = open(self.log_file, 'w') 82 | self.thrift_port = thrift_port 83 | self.pcap_dump = pcap_dump 84 | self.enable_debugger = enable_debugger 85 | self.log_console = log_console 86 | if device_id is not None: 87 | self.device_id = device_id 88 | P4Switch.device_id = max(P4Switch.device_id, device_id) 89 | else: 90 | self.device_id = P4Switch.device_id 91 | P4Switch.device_id += 1 92 | self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) 93 | 94 | @classmethod 95 | def setup(cls): 96 | pass 97 | 98 | def check_switch_started(self, pid): 99 | """While the process is running (pid exists), we check if the Thrift 100 | server has been started. If the Thrift server is ready, we assume that 101 | the switch was started successfully. This is only reliable if the Thrift 102 | server is started at the end of the init process""" 103 | while True: 104 | if not os.path.exists(os.path.join("/proc", str(pid))): 105 | return False 106 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 107 | sock.settimeout(0.5) 108 | result = sock.connect_ex(("localhost", self.thrift_port)) 109 | if result == 0: 110 | return True 111 | 112 | def start(self, controllers): 113 | "Start up a new P4 switch" 114 | info("Starting P4 switch {}.\n".format(self.name)) 115 | args = [self.sw_path] 116 | for port, intf in self.intfs.items(): 117 | if not intf.IP(): 118 | args.extend(['-i', str(port) + "@" + intf.name]) 119 | if self.pcap_dump: 120 | args.append("--pcap") 121 | # args.append("--useFiles") 122 | if self.thrift_port: 123 | args.extend(['--thrift-port', str(self.thrift_port)]) 124 | if self.nanomsg: 125 | args.extend(['--nanolog', self.nanomsg]) 126 | args.extend(['--device-id', str(self.device_id)]) 127 | P4Switch.device_id += 1 128 | args.append(self.json_path) 129 | if self.enable_debugger: 130 | args.append("--debugger") 131 | if self.log_console: 132 | args.append("--log-console") 133 | info(' '.join(args) + "\n") 134 | 135 | pid = None 136 | with tempfile.NamedTemporaryFile() as f: 137 | # self.cmd(' '.join(args) + ' > /dev/null 2>&1 &') 138 | self.cmd(' '.join(args) + ' >' + self.log_file + ' 2>&1 & echo $! >> ' + f.name) 139 | pid = int(f.read()) 140 | debug("P4 switch {} PID is {}.\n".format(self.name, pid)) 141 | sleep(1) 142 | if not self.check_switch_started(pid): 143 | error("P4 switch {} did not start correctly." 144 | "Check the switch log file.\n".format(self.name)) 145 | exit(1) 146 | info("P4 switch {} has been started.\n".format(self.name)) 147 | 148 | def stop(self): 149 | "Terminate P4 switch." 150 | self.output.flush() 151 | self.cmd('kill %' + self.sw_path) 152 | self.cmd('wait') 153 | self.deleteIntfs() 154 | 155 | def attach(self, intf): 156 | "Connect a data port" 157 | assert(0) 158 | 159 | def detach(self, intf): 160 | "Disconnect a data port" 161 | assert(0) 162 | -------------------------------------------------------------------------------- /Exercises/utils/netstat.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-present Open Networking Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import psutil 17 | def check_listening_on_port(port): 18 | for c in psutil.net_connections(kind='inet'): 19 | if c.status == 'LISTEN' and c.laddr[1] == port: 20 | return True 21 | return False 22 | -------------------------------------------------------------------------------- /Exercises/utils/p4_mininet.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013-present Barefoot Networks, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | from mininet.net import Mininet 17 | from mininet.node import Switch, Host 18 | from mininet.log import setLogLevel, info, error, debug 19 | from mininet.moduledeps import pathCheck 20 | from sys import exit 21 | import os 22 | import tempfile 23 | import socket 24 | from time import sleep 25 | 26 | from netstat import check_listening_on_port 27 | 28 | SWITCH_START_TIMEOUT = 10 # seconds 29 | 30 | class P4Host(Host): 31 | def config(self, **params): 32 | r = super(Host, self).config(**params) 33 | 34 | self.defaultIntf().rename("eth0") 35 | 36 | for off in ["rx", "tx", "sg"]: 37 | cmd = "/sbin/ethtool --offload eth0 %s off" % off 38 | self.cmd(cmd) 39 | 40 | # disable IPv6 41 | self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1") 42 | self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1") 43 | self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") 44 | 45 | return r 46 | 47 | def describe(self): 48 | print "**********" 49 | print self.name 50 | print "default interface: %s\t%s\t%s" %( 51 | self.defaultIntf().name, 52 | self.defaultIntf().IP(), 53 | self.defaultIntf().MAC() 54 | ) 55 | print "**********" 56 | 57 | class P4Switch(Switch): 58 | """P4 virtual switch""" 59 | device_id = 0 60 | 61 | def __init__(self, name, sw_path = None, json_path = None, 62 | thrift_port = None, 63 | pcap_dump = False, 64 | log_console = False, 65 | log_file = None, 66 | verbose = False, 67 | device_id = None, 68 | enable_debugger = False, 69 | **kwargs): 70 | Switch.__init__(self, name, **kwargs) 71 | assert(sw_path) 72 | assert(json_path) 73 | # make sure that the provided sw_path is valid 74 | pathCheck(sw_path) 75 | # make sure that the provided JSON file exists 76 | if not os.path.isfile(json_path): 77 | error("Invalid JSON file.\n") 78 | exit(1) 79 | self.sw_path = sw_path 80 | self.json_path = json_path 81 | self.verbose = verbose 82 | logfile = "/tmp/p4s.{}.log".format(self.name) 83 | self.output = open(logfile, 'w') 84 | self.thrift_port = thrift_port 85 | if check_listening_on_port(self.thrift_port): 86 | error('%s cannot bind port %d because it is bound by another process\n' % (self.name, self.grpc_port)) 87 | exit(1) 88 | self.pcap_dump = pcap_dump 89 | self.enable_debugger = enable_debugger 90 | self.log_console = log_console 91 | if log_file is not None: 92 | self.log_file = log_file 93 | else: 94 | self.log_file = "/tmp/p4s.{}.log".format(self.name) 95 | if device_id is not None: 96 | self.device_id = device_id 97 | P4Switch.device_id = max(P4Switch.device_id, device_id) 98 | else: 99 | self.device_id = P4Switch.device_id 100 | P4Switch.device_id += 1 101 | self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) 102 | 103 | @classmethod 104 | def setup(cls): 105 | pass 106 | 107 | def check_switch_started(self, pid): 108 | """While the process is running (pid exists), we check if the Thrift 109 | server has been started. If the Thrift server is ready, we assume that 110 | the switch was started successfully. This is only reliable if the Thrift 111 | server is started at the end of the init process""" 112 | while True: 113 | if not os.path.exists(os.path.join("/proc", str(pid))): 114 | return False 115 | if check_listening_on_port(self.thrift_port): 116 | return True 117 | sleep(0.5) 118 | 119 | def start(self, controllers): 120 | "Start up a new P4 switch" 121 | info("Starting P4 switch {}.\n".format(self.name)) 122 | args = [self.sw_path] 123 | for port, intf in self.intfs.items(): 124 | if not intf.IP(): 125 | args.extend(['-i', str(port) + "@" + intf.name]) 126 | if self.pcap_dump: 127 | args.append("--pcap %s" % self.pcap_dump) 128 | if self.thrift_port: 129 | args.extend(['--thrift-port', str(self.thrift_port)]) 130 | if self.nanomsg: 131 | args.extend(['--nanolog', self.nanomsg]) 132 | args.extend(['--device-id', str(self.device_id)]) 133 | P4Switch.device_id += 1 134 | args.append(self.json_path) 135 | if self.enable_debugger: 136 | args.append("--debugger") 137 | if self.log_console: 138 | args.append("--log-console") 139 | info(' '.join(args) + "\n") 140 | 141 | pid = None 142 | with tempfile.NamedTemporaryFile() as f: 143 | # self.cmd(' '.join(args) + ' > /dev/null 2>&1 &') 144 | self.cmd(' '.join(args) + ' >' + self.log_file + ' 2>&1 & echo $! >> ' + f.name) 145 | pid = int(f.read()) 146 | debug("P4 switch {} PID is {}.\n".format(self.name, pid)) 147 | if not self.check_switch_started(pid): 148 | error("P4 switch {} did not start correctly.\n".format(self.name)) 149 | exit(1) 150 | info("P4 switch {} has been started.\n".format(self.name)) 151 | 152 | def stop(self): 153 | "Terminate P4 switch." 154 | self.output.flush() 155 | self.cmd('kill %' + self.sw_path) 156 | self.cmd('wait') 157 | self.deleteIntfs() 158 | 159 | def attach(self, intf): 160 | "Connect a data port" 161 | assert(0) 162 | 163 | def detach(self, intf): 164 | "Disconnect a data port" 165 | assert(0) 166 | -------------------------------------------------------------------------------- /Exercises/utils/p4runtime_lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Exercises/utils/p4runtime_lib/__init__.py -------------------------------------------------------------------------------- /Exercises/utils/p4runtime_lib/bmv2.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-present Open Networking Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from switch import SwitchConnection 16 | from p4.tmp import p4config_pb2 17 | 18 | 19 | def buildDeviceConfig(bmv2_json_file_path=None): 20 | "Builds the device config for BMv2" 21 | device_config = p4config_pb2.P4DeviceConfig() 22 | device_config.reassign = True 23 | with open(bmv2_json_file_path) as f: 24 | device_config.device_data = f.read() 25 | return device_config 26 | 27 | 28 | class Bmv2SwitchConnection(SwitchConnection): 29 | def buildDeviceConfig(self, **kwargs): 30 | return buildDeviceConfig(**kwargs) 31 | -------------------------------------------------------------------------------- /Exercises/utils/p4runtime_lib/convert.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-present Open Networking Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | import re 16 | import socket 17 | 18 | import math 19 | 20 | ''' 21 | This package contains several helper functions for encoding to and decoding from byte strings: 22 | - integers 23 | - IPv4 address strings 24 | - Ethernet address strings 25 | ''' 26 | 27 | mac_pattern = re.compile('^([\da-fA-F]{2}:){5}([\da-fA-F]{2})$') 28 | def matchesMac(mac_addr_string): 29 | return mac_pattern.match(mac_addr_string) is not None 30 | 31 | def encodeMac(mac_addr_string): 32 | return mac_addr_string.replace(':', '').decode('hex') 33 | 34 | def decodeMac(encoded_mac_addr): 35 | return ':'.join(s.encode('hex') for s in encoded_mac_addr) 36 | 37 | ip_pattern = re.compile('^(\d{1,3}\.){3}(\d{1,3})$') 38 | def matchesIPv4(ip_addr_string): 39 | return ip_pattern.match(ip_addr_string) is not None 40 | 41 | def encodeIPv4(ip_addr_string): 42 | return socket.inet_aton(ip_addr_string) 43 | 44 | def decodeIPv4(encoded_ip_addr): 45 | return socket.inet_ntoa(encoded_ip_addr) 46 | 47 | def bitwidthToBytes(bitwidth): 48 | return int(math.ceil(bitwidth / 8.0)) 49 | 50 | def encodeNum(number, bitwidth): 51 | byte_len = bitwidthToBytes(bitwidth) 52 | num_str = '%x' % number 53 | if number >= 2 ** bitwidth: 54 | raise Exception("Number, %d, does not fit in %d bits" % (number, bitwidth)) 55 | return ('0' * (byte_len * 2 - len(num_str)) + num_str).decode('hex') 56 | 57 | def decodeNum(encoded_number): 58 | return int(encoded_number.encode('hex'), 16) 59 | 60 | def encode(x, bitwidth): 61 | 'Tries to infer the type of `x` and encode it' 62 | byte_len = bitwidthToBytes(bitwidth) 63 | if (type(x) == list or type(x) == tuple) and len(x) == 1: 64 | x = x[0] 65 | encoded_bytes = None 66 | if type(x) == str: 67 | if matchesMac(x): 68 | encoded_bytes = encodeMac(x) 69 | elif matchesIPv4(x): 70 | encoded_bytes = encodeIPv4(x) 71 | else: 72 | # Assume that the string is already encoded 73 | encoded_bytes = x 74 | elif type(x) == int: 75 | encoded_bytes = encodeNum(x, bitwidth) 76 | else: 77 | raise Exception("Encoding objects of %r is not supported" % type(x)) 78 | assert(len(encoded_bytes) == byte_len) 79 | return encoded_bytes 80 | 81 | if __name__ == '__main__': 82 | # TODO These tests should be moved out of main eventually 83 | mac = "aa:bb:cc:dd:ee:ff" 84 | enc_mac = encodeMac(mac) 85 | assert(enc_mac == '\xaa\xbb\xcc\xdd\xee\xff') 86 | dec_mac = decodeMac(enc_mac) 87 | assert(mac == dec_mac) 88 | 89 | ip = "10.0.0.1" 90 | enc_ip = encodeIPv4(ip) 91 | assert(enc_ip == '\x0a\x00\x00\x01') 92 | dec_ip = decodeIPv4(enc_ip) 93 | assert(ip == dec_ip) 94 | 95 | num = 1337 96 | byte_len = 5 97 | enc_num = encodeNum(num, byte_len * 8) 98 | assert(enc_num == '\x00\x00\x00\x05\x39') 99 | dec_num = decodeNum(enc_num) 100 | assert(num == dec_num) 101 | 102 | assert(matchesIPv4('10.0.0.1')) 103 | assert(not matchesIPv4('10.0.0.1.5')) 104 | assert(not matchesIPv4('1000.0.0.1')) 105 | assert(not matchesIPv4('10001')) 106 | 107 | assert(encode(mac, 6 * 8) == enc_mac) 108 | assert(encode(ip, 4 * 8) == enc_ip) 109 | assert(encode(num, 5 * 8) == enc_num) 110 | assert(encode((num,), 5 * 8) == enc_num) 111 | assert(encode([num], 5 * 8) == enc_num) 112 | 113 | num = 256 114 | byte_len = 2 115 | try: 116 | enc_num = encodeNum(num, 8) 117 | raise Exception("expected exception") 118 | except Exception as e: 119 | print e 120 | -------------------------------------------------------------------------------- /Exercises/utils/p4runtime_lib/error_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013-present Barefoot Networks, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import sys 17 | 18 | from google.rpc import status_pb2, code_pb2 19 | import grpc 20 | from p4 import p4runtime_pb2 21 | 22 | # Used to indicate that the gRPC error Status object returned by the server has 23 | # an incorrect format. 24 | class P4RuntimeErrorFormatException(Exception): 25 | def __init__(self, message): 26 | super(P4RuntimeErrorFormatException, self).__init__(message) 27 | 28 | 29 | # Parse the binary details of the gRPC error. This is required to print some 30 | # helpful debugging information in tha case of batched Write / Read 31 | # requests. Returns None if there are no useful binary details and throws 32 | # P4RuntimeErrorFormatException if the error is not formatted 33 | # properly. Otherwise, returns a list of tuples with the first element being the 34 | # index of the operation in the batch that failed and the second element being 35 | # the p4.Error Protobuf message. 36 | def parseGrpcErrorBinaryDetails(grpc_error): 37 | if grpc_error.code() != grpc.StatusCode.UNKNOWN: 38 | return None 39 | 40 | error = None 41 | # The gRPC Python package does not have a convenient way to access the 42 | # binary details for the error: they are treated as trailing metadata. 43 | for meta in grpc_error.trailing_metadata(): 44 | if meta[0] == "grpc-status-details-bin": 45 | error = status_pb2.Status() 46 | error.ParseFromString(meta[1]) 47 | break 48 | if error is None: # no binary details field 49 | return None 50 | if len(error.details) == 0: 51 | # binary details field has empty Any details repeated field 52 | return None 53 | 54 | indexed_p4_errors = [] 55 | for idx, one_error_any in enumerate(error.details): 56 | p4_error = p4runtime_pb2.Error() 57 | if not one_error_any.Unpack(p4_error): 58 | raise P4RuntimeErrorFormatException( 59 | "Cannot convert Any message to p4.Error") 60 | if p4_error.canonical_code == code_pb2.OK: 61 | continue 62 | indexed_p4_errors += [(idx, p4_error)] 63 | 64 | return indexed_p4_errors 65 | 66 | 67 | # P4Runtime uses a 3-level message in case of an error during the processing of 68 | # a write batch. This means that some care is required when printing the 69 | # exception if we do not want to end-up with a non-helpful message in case of 70 | # failure as only the first level will be printed. In this function, we extract 71 | # the nested error message when present (one for each operation included in the 72 | # batch) in order to print error code + user-facing message. See P4Runtime 73 | # documentation for more details on error-reporting. 74 | def printGrpcError(grpc_error): 75 | print "gRPC Error", grpc_error.details(), 76 | status_code = grpc_error.code() 77 | print "({})".format(status_code.name), 78 | traceback = sys.exc_info()[2] 79 | print "[{}:{}]".format( 80 | traceback.tb_frame.f_code.co_filename, traceback.tb_lineno) 81 | if status_code != grpc.StatusCode.UNKNOWN: 82 | return 83 | p4_errors = parseGrpcErrorBinaryDetails(grpc_error) 84 | if p4_errors is None: 85 | return 86 | print "Errors in batch:" 87 | for idx, p4_error in p4_errors: 88 | code_name = code_pb2._CODE.values_by_number[ 89 | p4_error.canonical_code].name 90 | print "\t* At index {}: {}, '{}'\n".format( 91 | idx, code_name, p4_error.message) 92 | -------------------------------------------------------------------------------- /Exercises/utils/p4runtime_lib/switch.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-present Open Networking Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | from Queue import Queue 16 | from abc import abstractmethod 17 | from datetime import datetime 18 | 19 | import grpc 20 | from p4 import p4runtime_pb2 21 | from p4.tmp import p4config_pb2 22 | 23 | MSG_LOG_MAX_LEN = 1024 24 | 25 | # List of all active connections 26 | connections = [] 27 | 28 | def ShutdownAllSwitchConnections(): 29 | for c in connections: 30 | c.shutdown() 31 | 32 | class SwitchConnection(object): 33 | 34 | def __init__(self, name=None, address='127.0.0.1:50051', device_id=0, 35 | proto_dump_file=None): 36 | self.name = name 37 | self.address = address 38 | self.device_id = device_id 39 | self.p4info = None 40 | self.channel = grpc.insecure_channel(self.address) 41 | if proto_dump_file is not None: 42 | interceptor = GrpcRequestLogger(proto_dump_file) 43 | self.channel = grpc.intercept_channel(self.channel, interceptor) 44 | self.client_stub = p4runtime_pb2.P4RuntimeStub(self.channel) 45 | self.requests_stream = IterableQueue() 46 | self.stream_msg_resp = self.client_stub.StreamChannel(iter(self.requests_stream)) 47 | self.proto_dump_file = proto_dump_file 48 | connections.append(self) 49 | 50 | @abstractmethod 51 | def buildDeviceConfig(self, **kwargs): 52 | return p4config_pb2.P4DeviceConfig() 53 | 54 | def shutdown(self): 55 | self.requests_stream.close() 56 | self.stream_msg_resp.cancel() 57 | 58 | def MasterArbitrationUpdate(self, dry_run=False, **kwargs): 59 | request = p4runtime_pb2.StreamMessageRequest() 60 | request.arbitration.device_id = self.device_id 61 | request.arbitration.election_id.high = 0 62 | request.arbitration.election_id.low = 1 63 | 64 | if dry_run: 65 | print "P4Runtime MasterArbitrationUpdate: ", request 66 | else: 67 | self.requests_stream.put(request) 68 | for item in self.stream_msg_resp: 69 | return item # just one 70 | 71 | def SetForwardingPipelineConfig(self, p4info, dry_run=False, **kwargs): 72 | device_config = self.buildDeviceConfig(**kwargs) 73 | request = p4runtime_pb2.SetForwardingPipelineConfigRequest() 74 | request.election_id.low = 1 75 | request.device_id = self.device_id 76 | config = request.config 77 | 78 | config.p4info.CopyFrom(p4info) 79 | config.p4_device_config = device_config.SerializeToString() 80 | 81 | request.action = p4runtime_pb2.SetForwardingPipelineConfigRequest.VERIFY_AND_COMMIT 82 | if dry_run: 83 | print "P4Runtime SetForwardingPipelineConfig:", request 84 | else: 85 | self.client_stub.SetForwardingPipelineConfig(request) 86 | 87 | def WriteTableEntry(self, table_entry, dry_run=False): 88 | request = p4runtime_pb2.WriteRequest() 89 | request.device_id = self.device_id 90 | request.election_id.low = 1 91 | update = request.updates.add() 92 | update.type = p4runtime_pb2.Update.INSERT 93 | update.entity.table_entry.CopyFrom(table_entry) 94 | if dry_run: 95 | print "P4Runtime Write:", request 96 | else: 97 | self.client_stub.Write(request) 98 | 99 | def ReadTableEntries(self, table_id=None, dry_run=False): 100 | request = p4runtime_pb2.ReadRequest() 101 | request.device_id = self.device_id 102 | entity = request.entities.add() 103 | table_entry = entity.table_entry 104 | if table_id is not None: 105 | table_entry.table_id = table_id 106 | else: 107 | table_entry.table_id = 0 108 | if dry_run: 109 | print "P4Runtime Read:", request 110 | else: 111 | for response in self.client_stub.Read(request): 112 | yield response 113 | 114 | def ReadCounters(self, counter_id=None, index=None, dry_run=False): 115 | request = p4runtime_pb2.ReadRequest() 116 | request.device_id = self.device_id 117 | entity = request.entities.add() 118 | counter_entry = entity.counter_entry 119 | if counter_id is not None: 120 | counter_entry.counter_id = counter_id 121 | else: 122 | counter_entry.counter_id = 0 123 | if index is not None: 124 | counter_entry.index.index = index 125 | if dry_run: 126 | print "P4Runtime Read:", request 127 | else: 128 | for response in self.client_stub.Read(request): 129 | yield response 130 | 131 | 132 | class GrpcRequestLogger(grpc.UnaryUnaryClientInterceptor, 133 | grpc.UnaryStreamClientInterceptor): 134 | """Implementation of a gRPC interceptor that logs request to a file""" 135 | 136 | def __init__(self, log_file): 137 | self.log_file = log_file 138 | with open(self.log_file, 'w') as f: 139 | # Clear content if it exists. 140 | f.write("") 141 | 142 | def log_message(self, method_name, body): 143 | with open(self.log_file, 'a') as f: 144 | ts = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] 145 | msg = str(body) 146 | f.write("\n[%s] %s\n---\n" % (ts, method_name)) 147 | if len(msg) < MSG_LOG_MAX_LEN: 148 | f.write(str(body)) 149 | else: 150 | f.write("Message too long (%d bytes)! Skipping log...\n" % len(msg)) 151 | f.write('---\n') 152 | 153 | def intercept_unary_unary(self, continuation, client_call_details, request): 154 | self.log_message(client_call_details.method, request) 155 | return continuation(client_call_details, request) 156 | 157 | def intercept_unary_stream(self, continuation, client_call_details, request): 158 | self.log_message(client_call_details.method, request) 159 | return continuation(client_call_details, request) 160 | 161 | class IterableQueue(Queue): 162 | _sentinel = object() 163 | 164 | def __iter__(self): 165 | return iter(self.get, self._sentinel) 166 | 167 | def close(self): 168 | self.put(self._sentinel) 169 | -------------------------------------------------------------------------------- /Exercises/utils/p4runtime_switch.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017-present Barefoot Networks, Inc. 2 | # Copyright 2017-present Open Networking Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | import sys, os, tempfile, socket 18 | from time import sleep 19 | 20 | from mininet.node import Switch 21 | from mininet.moduledeps import pathCheck 22 | from mininet.log import info, error, debug 23 | 24 | from p4_mininet import P4Switch, SWITCH_START_TIMEOUT 25 | from netstat import check_listening_on_port 26 | 27 | class P4RuntimeSwitch(P4Switch): 28 | "BMv2 switch with gRPC support" 29 | next_grpc_port = 50051 30 | next_thrift_port = 9090 31 | 32 | def __init__(self, name, sw_path = None, json_path = None, 33 | grpc_port = None, 34 | thrift_port = None, 35 | pcap_dump = False, 36 | log_console = False, 37 | verbose = False, 38 | device_id = None, 39 | enable_debugger = False, 40 | log_file = None, 41 | **kwargs): 42 | Switch.__init__(self, name, **kwargs) 43 | assert (sw_path) 44 | self.sw_path = sw_path 45 | # make sure that the provided sw_path is valid 46 | pathCheck(sw_path) 47 | 48 | if json_path is not None: 49 | # make sure that the provided JSON file exists 50 | if not os.path.isfile(json_path): 51 | error("Invalid JSON file.\n") 52 | exit(1) 53 | self.json_path = json_path 54 | else: 55 | self.json_path = None 56 | 57 | if grpc_port is not None: 58 | self.grpc_port = grpc_port 59 | else: 60 | self.grpc_port = P4RuntimeSwitch.next_grpc_port 61 | P4RuntimeSwitch.next_grpc_port += 1 62 | 63 | if thrift_port is not None: 64 | self.thrift_port = thrift_port 65 | else: 66 | self.thrift_port = P4RuntimeSwitch.next_thrift_port 67 | P4RuntimeSwitch.next_thrift_port += 1 68 | 69 | if check_listening_on_port(self.grpc_port): 70 | error('%s cannot bind port %d because it is bound by another process\n' % (self.name, self.grpc_port)) 71 | exit(1) 72 | 73 | self.verbose = verbose 74 | logfile = "/tmp/p4s.{}.log".format(self.name) 75 | self.output = open(logfile, 'w') 76 | self.pcap_dump = pcap_dump 77 | self.enable_debugger = enable_debugger 78 | self.log_console = log_console 79 | if log_file is not None: 80 | self.log_file = log_file 81 | else: 82 | self.log_file = "/tmp/p4s.{}.log".format(self.name) 83 | if device_id is not None: 84 | self.device_id = device_id 85 | P4Switch.device_id = max(P4Switch.device_id, device_id) 86 | else: 87 | self.device_id = P4Switch.device_id 88 | P4Switch.device_id += 1 89 | self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) 90 | 91 | 92 | def check_switch_started(self, pid): 93 | for _ in range(SWITCH_START_TIMEOUT * 2): 94 | if not os.path.exists(os.path.join("/proc", str(pid))): 95 | return False 96 | if check_listening_on_port(self.grpc_port): 97 | return True 98 | sleep(0.5) 99 | 100 | def start(self, controllers): 101 | info("Starting P4 switch {}.\n".format(self.name)) 102 | args = [self.sw_path] 103 | for port, intf in self.intfs.items(): 104 | if not intf.IP(): 105 | args.extend(['-i', str(port) + "@" + intf.name]) 106 | if self.pcap_dump: 107 | args.append("--pcap %s" % self.pcap_dump) 108 | if self.nanomsg: 109 | args.extend(['--nanolog', self.nanomsg]) 110 | args.extend(['--device-id', str(self.device_id)]) 111 | P4Switch.device_id += 1 112 | if self.json_path: 113 | args.append(self.json_path) 114 | else: 115 | args.append("--no-p4") 116 | if self.enable_debugger: 117 | args.append("--debugger") 118 | if self.log_console: 119 | args.append("--log-console") 120 | if self.thrift_port: 121 | args.append('--thrift-port ' + str(self.thrift_port)) 122 | if self.grpc_port: 123 | args.append("-- --grpc-server-addr 0.0.0.0:" + str(self.grpc_port)) 124 | cmd = ' '.join(args) 125 | info(cmd + "\n") 126 | 127 | 128 | pid = None 129 | with tempfile.NamedTemporaryFile() as f: 130 | self.cmd(cmd + ' >' + self.log_file + ' 2>&1 & echo $! >> ' + f.name) 131 | pid = int(f.read()) 132 | debug("P4 switch {} PID is {}.\n".format(self.name, pid)) 133 | if not self.check_switch_started(pid): 134 | error("P4 switch {} did not start correctly.\n".format(self.name)) 135 | exit(1) 136 | info("P4 switch {} has been started.\n".format(self.name)) 137 | 138 | -------------------------------------------------------------------------------- /Final_Project/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Final_Project/.DS_Store -------------------------------------------------------------------------------- /Final_Project/BPFabric Data Plane Programmability for Software Defined Network.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Final_Project/BPFabric Data Plane Programmability for Software Defined Network.pdf -------------------------------------------------------------------------------- /Final_Project/__pycache__/environment.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Final_Project/__pycache__/environment.cpython-38.pyc -------------------------------------------------------------------------------- /Final_Project/__pycache__/service_batch_generator.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Final_Project/__pycache__/service_batch_generator.cpython-38.pyc -------------------------------------------------------------------------------- /Final_Project/comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Final_Project/comparison.png -------------------------------------------------------------------------------- /Final_Project/main_article_results/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Final_Project/main_article_results/.DS_Store -------------------------------------------------------------------------------- /Final_Project/main_article_results/agent_loss_arr.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Final_Project/main_article_results/agent_loss_arr.npy -------------------------------------------------------------------------------- /Final_Project/main_article_results/estimated_value_arr.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Final_Project/main_article_results/estimated_value_arr.npy -------------------------------------------------------------------------------- /Final_Project/main_article_results/lagrangian_arr.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Final_Project/main_article_results/lagrangian_arr.npy -------------------------------------------------------------------------------- /Final_Project/main_article_results/non_zero_arr.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Final_Project/main_article_results/non_zero_arr.npy -------------------------------------------------------------------------------- /Final_Project/main_article_results/penalty_arr.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Final_Project/main_article_results/penalty_arr.npy -------------------------------------------------------------------------------- /Final_Project/main_article_results/reward_arr.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Final_Project/main_article_results/reward_arr.npy -------------------------------------------------------------------------------- /Final_Project/main_article_results/ve_loss_arr.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/Final_Project/main_article_results/ve_loss_arr.npy -------------------------------------------------------------------------------- /Final_Project/service_batch_generator.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class ServiceBatchGenerator(object): 5 | """ 6 | Implementation of a random service chain generator 7 | 8 | Attributes: 9 | state[batch_size, max_service_length] -- Batch of random service chains 10 | service_length[batch_size] -- Array containing services length 11 | """ 12 | 13 | def __init__(self, batch_size, min_service_length, max_service_length, vocab_size): 14 | """ 15 | Args: 16 | batch_size(int) -- Number of service chains to be generated 17 | min_service_length(int) -- Minimum service length 18 | max_service_length(int) -- Maximum service length 19 | vocab_size(int) -- Size of the VNF dictionary 20 | """ 21 | self.batch_size = batch_size 22 | self.min_service_length = min_service_length 23 | self.max_service_length = max_service_length 24 | self.vocab_size = vocab_size 25 | 26 | self.service_length = np.zeros(self.batch_size, dtype='int32') 27 | self.state = np.zeros( 28 | (self.batch_size, self.max_service_length), dtype='int32') 29 | 30 | def getNewState(self): 31 | """ Generate new batch of service chain """ 32 | 33 | # Clean attributes 34 | self.state = np.zeros( 35 | (self.batch_size, self.max_service_length), dtype='int32') 36 | self.service_length = np.zeros(self.batch_size, dtype='int32') 37 | 38 | # Compute random services 39 | for batch in range(self.batch_size): 40 | self.service_length[batch] = np.random.randint( 41 | self.min_service_length, self.max_service_length+1, dtype='int32') 42 | for i in range(self.service_length[batch]): 43 | vnf_id = np.random.randint(1, self.vocab_size, dtype='int32') 44 | self.state[batch][i] = vnf_id 45 | 46 | 47 | if __name__ == "__main__": 48 | 49 | # Define generator 50 | batch_size = 5 51 | min_service_length = 2 52 | max_service_length = 6 53 | vocab_size = 8 54 | 55 | env = ServiceBatchGenerator( 56 | batch_size, min_service_length, max_service_length, vocab_size) 57 | env.getNewState() 58 | 59 | print(env.state) 60 | -------------------------------------------------------------------------------- /P4_tutorial.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/P4_tutorial.pdf -------------------------------------------------------------------------------- /cheat_sheet_src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/cheat_sheet_src/.DS_Store -------------------------------------------------------------------------------- /cheat_sheet_src/main.tex: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % P4 Cheat Sheet 3 | % 4 | % By P4.org 5 | % 6 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 7 | 8 | \documentclass{article} 9 | 10 | \usepackage{fontspec} 11 | \setmainfont{Utopia} 12 | \setsansfont{Free Helvetian} 13 | \setmonofont{Liberation Mono} 14 | 15 | \usepackage[landscape]{geometry} 16 | \usepackage{url} 17 | \usepackage{multicol} 18 | \usepackage{amsmath} 19 | \usepackage{amsfonts} 20 | \usepackage{tikz} 21 | \usetikzlibrary{shapes} 22 | \usetikzlibrary{decorations.pathmorphing} 23 | \usepackage{amsmath,amssymb} 24 | 25 | \usepackage{colortbl} 26 | \usepackage{xcolor} 27 | \usepackage{mathtools} 28 | \usepackage{amsmath,amssymb} 29 | \usepackage{enumitem} 30 | 31 | % Define Colors 32 | \usepackage{color} 33 | \definecolor{eclipseBlue}{RGB}{42,0.0,255} 34 | \definecolor{eclipseGreen}{RGB}{63,127,95} 35 | \definecolor{eclipsePurple}{RGB}{127,0,85} 36 | 37 | \usepackage{listings} 38 | 39 | % Define Language 40 | \lstdefinelanguage{p4} 41 | { 42 | % list of keywords 43 | morekeywords={ 44 | action, apply, bit, bool, const, control, default, else, enum, error, extern, exit, false, header, if, in, inout, int, match_kind, package, parser, out, return, select, state, struct, switch, table, transition, true, tuple, typedef, varbit, verify, void, 45 | }, 46 | sensitive=true, % keywords are case-sensitive 47 | morecomment=[l]{//}, % l is for line comment 48 | morecomment=[s]{/*}{*/}, % s is for start and end delimiter 49 | morestring=[b]" % defines that strings are enclosed in double quotes 50 | } 51 | 52 | % Set Language 53 | \lstset{ 54 | language={p4}, 55 | basicstyle=\small\ttfamily, % Global Code Style 56 | captionpos=b, % Position of the Caption (t for top, b for bottom) 57 | extendedchars=true, % Allows 256 instead of 128 ASCII characters 58 | tabsize=2, % number of spaces indented when discovering a tab 59 | columns=fixed, % make all characters equal width 60 | keepspaces=true, % does not ignore spaces to fit width, convert tabs to spaces 61 | showstringspaces=false, % lets spaces in strings appear as real spaces 62 | breaklines=true, % wrap lines if they don't fit 63 | commentstyle=\color{eclipseBlue}, % style of comments 64 | keywordstyle=\color{eclipsePurple}, % style of keywords 65 | stringstyle=\color{eclipseGreen}, % style of strings 66 | } 67 | 68 | \title{P4 Cheat Sheet} 69 | \usepackage[brazilian]{babel} 70 | 71 | \advance\topmargin-.8in 72 | \advance\textheight3in 73 | \advance\textwidth3in 74 | \advance\oddsidemargin-1.5in 75 | \advance\evensidemargin-1.5in 76 | \parindent0pt 77 | \parskip2pt 78 | \newcommand{\hr}{\centerline{\rule{3.5in}{1pt}}} 79 | %\colorbox[HTML]{e4e4e4}{\makebox[\textwidth-2\fboxsep][l]{texto} 80 | \begin{document} 81 | 82 | \begin{center}{\huge{\bf \textsf{P4 Language Cheat Sheet}}}\\[.5em] 83 | %{\large By P4.org} 84 | \end{center} 85 | \begin{multicols*}{3} 86 | 87 | \tikzstyle{mybox} = [draw=black, fill=white, very thick, 88 | rectangle, rounded corners, inner sep=10pt, inner ysep=10pt] 89 | \tikzstyle{fancytitle} =[fill=black, text=white, font=\bfseries] 90 | \tikzstyle{mybox2} = [draw=black, fill=white, very thick, rectangle split, 91 | rectangle split parts=2, 92 | rounded corners, inner sep=10pt, inner ysep=10pt] 93 | \tikzstyle{fancytitle2} =[fill=black, text=white, font=\bfseries] 94 | 95 | %------------ DATA TYPES --------------- 96 | \begin{tikzpicture} 97 | \node [mybox] (box){% 98 | \begin{minipage}{0.3\textwidth} 99 | \lstinputlisting{src/data_types.txt} 100 | \end{minipage} 101 | }; 102 | \node[fancytitle, right=10pt] at (box.north west) {Basic Data Types}; 103 | \end{tikzpicture} 104 | 105 | %------------ P4 Parsing --------------- 106 | \begin{tikzpicture} 107 | \node [mybox] (box){% 108 | \begin{minipage}{0.3\textwidth} 109 | \lstinputlisting{src/parsers.txt} 110 | \end{minipage} 111 | }; 112 | \node[fancytitle, right=10pt] at (box.north west) {Parsing}; 113 | \end{tikzpicture} 114 | 115 | %------------ Expressions --------------------- 116 | \begin{tikzpicture} 117 | \node [mybox] (box){% 118 | \begin{minipage}{0.3\textwidth} 119 | \lstinputlisting{src/expressions.txt} 120 | \end{minipage} 121 | }; 122 | \node[fancytitle, right=10pt] at (box.north west) {Statements \& Expressions}; 123 | \end{tikzpicture} 124 | 125 | %------------ Actions --------------------- 126 | \begin{tikzpicture} 127 | \node [mybox] (box){% 128 | \begin{minipage}{0.3\textwidth} 129 | \lstinputlisting{src/actions.txt} 130 | \end{minipage} 131 | }; 132 | \node[fancytitle, right=10pt] at (box.north west) {Actions}; 133 | \end{tikzpicture} 134 | 135 | %------------ Tables --------------------- 136 | \begin{tikzpicture} 137 | \node [mybox] (box){% 138 | \begin{minipage}{0.3\textwidth} 139 | \lstinputlisting{src/tables.txt} 140 | \end{minipage} 141 | }; 142 | \node[fancytitle, right=10pt] at (box.north west) {Tables}; 143 | \end{tikzpicture} 144 | 145 | %------------ Control Flow --------------------- 146 | \begin{tikzpicture} 147 | \node [mybox] (box){% 148 | \begin{minipage}{0.3\textwidth} 149 | \lstinputlisting{src/control_flow.txt} 150 | \end{minipage} 151 | }; 152 | \node[fancytitle, right=10pt] at (box.north west) {Control Flow}; 153 | \end{tikzpicture} 154 | 155 | %------------ Deparsing --------------------- 156 | \begin{tikzpicture} 157 | \node [mybox] (box){% 158 | \begin{minipage}{0.3\textwidth} 159 | \lstinputlisting{src/deparsing.txt} 160 | \end{minipage} 161 | }; 162 | \node[fancytitle, right=10pt] at (box.north west) {Deparsing}; 163 | \end{tikzpicture} 164 | 165 | %------------ Header Stacks --------------------- 166 | \begin{tikzpicture} 167 | \node [mybox] (box){% 168 | \begin{minipage}{0.3\textwidth} 169 | \lstinputlisting{src/header_stack.txt} 170 | \end{minipage} 171 | }; 172 | \node[fancytitle, right=10pt] at (box.north west) {Header Stacks}; 173 | \end{tikzpicture} 174 | 175 | %------------ Advanced Parsing --------------------- 176 | \begin{tikzpicture} 177 | \node [mybox] (box){% 178 | \begin{minipage}{0.3\textwidth} 179 | \lstinputlisting{src/adv_parsing.txt} 180 | \end{minipage} 181 | }; 182 | \node[fancytitle, right=10pt] at (box.north west) {Advanced Parsing}; 183 | \end{tikzpicture} 184 | 185 | %------------ V1Model - Architecture --------------------- 186 | \begin{tikzpicture} 187 | \node [mybox] (box){% 188 | \begin{minipage}{0.3\textwidth} 189 | \lstinputlisting{src/architecture.txt} 190 | \end{minipage} 191 | }; 192 | \node[fancytitle, right=10pt] at (box.north west) {V1Model - Architecture}; 193 | \end{tikzpicture} 194 | 195 | %------------ V1Model - Standard Metadata --------------------- 196 | \begin{tikzpicture} 197 | \node [mybox] (box){% 198 | \begin{minipage}{0.3\textwidth} 199 | \lstinputlisting{src/v1model_std_metadata.txt} 200 | \end{minipage} 201 | }; 202 | \node[fancytitle, right=10pt] at (box.north west) {V1Model - Standard Metadata}; 203 | \end{tikzpicture} 204 | 205 | %------------ V1Model - Counter Externs --------------------- 206 | \begin{tikzpicture} 207 | \node [mybox] (box){% 208 | \begin{minipage}{0.3\textwidth} 209 | \lstinputlisting{src/counters.txt} 210 | \end{minipage} 211 | }; 212 | \node[fancytitle, right=10pt] at (box.north west) {V1Model - Counters \& Registers}; 213 | \end{tikzpicture} 214 | 215 | 216 | 217 | \end{multicols*} 218 | \end{document} 219 | Contact GitHub API Training Shop Blog About 220 | © 2016 GitHub, Inc. Terms Privacy Security Status Help -------------------------------------------------------------------------------- /cheat_sheet_src/src/actions.txt: -------------------------------------------------------------------------------- 1 | // Inputs provided by control-plane 2 | action set_next_hop(bit<32> next_hop) { 3 | if (next_hop == 0) { 4 | metadata.next_hop = hdr.ipv4.dst; 5 | } else { 6 | metadata.next_hop = next_hop; 7 | } 8 | } 9 | 10 | // Inputs provided by data-plane 11 | action swap_mac(inout bit<48> x, 12 | inout bit<48> y) { 13 | bit<48> tmp = x; 14 | x = y; 15 | y = tmp; 16 | } 17 | 18 | // Inputs provided by control/data-plane 19 | action forward(in bit<9> p, bit<48> d) { 20 | standard_metadata.egress_spec = p; 21 | headers.ethernet.dstAddr = d; 22 | } 23 | 24 | // Remove header from packet 25 | action decap_ip_ip() { 26 | hdr.ipv4 = hdr.inner_ipv4; 27 | hdr.inner_ipv4.setInvalid(); 28 | } 29 | -------------------------------------------------------------------------------- /cheat_sheet_src/src/adv_parsing.txt: -------------------------------------------------------------------------------- 1 | // common defns for IPv4 and IPv6 2 | header ip46_t { 3 | bit<4> version; 4 | bit<4> reserved; 5 | } 6 | 7 | // header stack parsing 8 | state parse_labels { 9 | packet.extract(hdr.labels.next); 10 | transition select(hdr.labels.last.bos) { 11 | 0: parse_labels; // create loop 12 | 1: guess_labels_payload; 13 | } 14 | } 15 | 16 | // lookahead parsing 17 | state guess_labels_payload { 18 | transition select(packet.lookahead().version) { 19 | 4 : parse_inner_ipv4; 20 | 6 : parse_inner_ipv6; 21 | default : parse_inner_ethernet; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /cheat_sheet_src/src/architecture.txt: -------------------------------------------------------------------------------- 1 | // common externs 2 | extern void truncate(in bit<32> length); 3 | extern void resubmit(in T x); 4 | extern void recirculate(in T x); 5 | enum CloneType { I2E, E2I } 6 | extern void clone(in CloneType type, 7 | in bit<32> session); 8 | 9 | // v1model pipeline elements 10 | parser Parser( 11 | packet_in pkt, 12 | out H hdr, 13 | inout M meta, 14 | inout standard_metadata_t std_meta 15 | ); 16 | control VerifyChecksum( 17 | inout H hdr, 18 | inout M meta 19 | ); 20 | control Ingress( 21 | inout H hdr, 22 | inout M meta, 23 | inout standard_metadata_t std_meta 24 | ); 25 | control Egress( 26 | inout H hdr, 27 | inout M meta, 28 | inout standard_metadata_t std_meta 29 | ); 30 | control ComputeChecksum( 31 | inout H hdr, 32 | inout M meta 33 | ); 34 | control Deparser( 35 | packet_out b, in H hdr 36 | ); 37 | 38 | // v1model switch 39 | package V1Switch( 40 | Parser p, 41 | VerifyChecksum vr, 42 | Ingress ig, 43 | Egress eg, 44 | ComputeChecksum ck, 45 | Deparser d 46 | ); 47 | -------------------------------------------------------------------------------- /cheat_sheet_src/src/control_flow.txt: -------------------------------------------------------------------------------- 1 | apply { 2 | // branch on header validity 3 | if (hdr.ipv4.isValid()) { 4 | ipv4_lpm.apply(); 5 | } 6 | // branch on table hit result 7 | if (local_ip_table.apply().hit) { 8 | send_to_cpu(); 9 | } 10 | // branch on table action invocation 11 | switch (table1.apply().action_run) { 12 | action1: { table2.apply(); } 13 | action2: { table3.apply(); } 14 | } 15 | } -------------------------------------------------------------------------------- /cheat_sheet_src/src/counters.txt: -------------------------------------------------------------------------------- 1 | // counters 2 | counter(8192, CounterType.packets) c; 3 | 4 | action count(bit<32> index) { 5 | //increment counter at index 6 | c.count(index); 7 | } 8 | 9 | // registers 10 | register>(16384) r; 11 | 12 | action ipg(out bit<48> ival, bit<32> x) { 13 | bit<48> last; 14 | bit<48> now; 15 | r.read(last, x); 16 | now = std_meta.ingress_global_timestamp; 17 | ival = now - last; 18 | r.write(x, now); 19 | } 20 | -------------------------------------------------------------------------------- /cheat_sheet_src/src/data_types.txt: -------------------------------------------------------------------------------- 1 | // typedef: introduces alternate type name 2 | typedef bit<48> macAddr_t; 3 | typedef bit<32> ip4Addr_t; 4 | 5 | // headers: ordered collection of members 6 | // operations test and set validity bits: 7 | // isValid(), setValid(), setInvalid() 8 | header ethernet_t { 9 | macAddr_t dstAddr; 10 | macAddr_t srcAddr; 11 | bit<16> type; 12 | } 13 | 14 | // variable declaration and member access 15 | ethernet_t ethernet; 16 | macAddr_t src = ethernet.srcAddr; 17 | 18 | // struct: unordered collection of members 19 | struct headers_t { 20 | ethernet_t ethernet; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /cheat_sheet_src/src/deparsing.txt: -------------------------------------------------------------------------------- 1 | // packet_out: extern for output packet 2 | extern packet_out { 3 | void emit(in T hdr); 4 | } 5 | 6 | apply { 7 | // insert headers into pkt if valid 8 | packet.emit(hdr.ethernet); 9 | } -------------------------------------------------------------------------------- /cheat_sheet_src/src/expressions.txt: -------------------------------------------------------------------------------- 1 | // Local metadata declaration, assignment 2 | bit<16> tmp1; bit<16> tmp2; 3 | tmp1 = hdr.ethernet.type; 4 | 5 | // bit slicing, concatenation 6 | tmp2 = tmp1[7:0] ++ tmp1[15:8]; 7 | 8 | // addition, subtraction, casts 9 | tmp2 = tmp1 + tmp1 - (bit<16>)tmp1[7:0]; 10 | 11 | // bitwise operators 12 | tmp2 = (~tmp1 & tmp1) | (tmp1 ^ tmp1); 13 | tmp2 = tmp1 << 3; -------------------------------------------------------------------------------- /cheat_sheet_src/src/header_stack.txt: -------------------------------------------------------------------------------- 1 | // header stack declaration 2 | header label_t { 3 | bit<20> label; 4 | bit bos; 5 | } 6 | struct header_t { 7 | label_t[10] labels; 8 | } 9 | header_t hdr; 10 | 11 | // remove from header stack 12 | action pop_label() { 13 | hdr.labels.pop_front(1); 14 | } 15 | 16 | // add to header stack 17 | action push_label(in bit<20> label) { 18 | hdr.labels.push_front(1); 19 | hdr.labels[0].setValid(); 20 | hdr.labels[0] = { label, 0}; 21 | } 22 | -------------------------------------------------------------------------------- /cheat_sheet_src/src/parsers.txt: -------------------------------------------------------------------------------- 1 | // packet_in: extern for input packet 2 | extern packet_in { 3 | void extract(out T hdr); 4 | void extract(out T hdr,in bit<32> n); 5 | T lookahead(); 6 | void advance(in bit<32> n); 7 | bit<32> length(); 8 | } 9 | 10 | // parser: begins in special "start" state 11 | state start { 12 | transition parse_ethernet; 13 | } 14 | 15 | // User-defined parser state 16 | state parse_ethernet { 17 | packet.extract(hdr.ethernet); 18 | transition select(hdr.ethernet.type) { 19 | 0x800: parse_ipv4; 20 | default: accept; 21 | } 22 | } -------------------------------------------------------------------------------- /cheat_sheet_src/src/tables.txt: -------------------------------------------------------------------------------- 1 | table ipv4_lpm { 2 | key = { 3 | hdr.ipv4.dstAddr : lpm; 4 | // standard match kinds: 5 | // exact, ternary, lpm 6 | } 7 | // actions that can be invoked 8 | actions = { 9 | ipv4_forward; 10 | drop; 11 | NoAction; 12 | } 13 | // table properties 14 | size = 1024; 15 | default_action = NoAction(); 16 | } -------------------------------------------------------------------------------- /cheat_sheet_src/src/v1model_std_metadata.txt: -------------------------------------------------------------------------------- 1 | struct standard_metadata_t { 2 | // For more details see docs/simple_switch.md 3 | // in https://github.com/p4lang/behavioral-model 4 | 5 | // Should only read, ingress or egress 6 | bit<9> ingress_port; 7 | bit<32> instance_type; 8 | bit<32> packet_length; 9 | bit<48> ingress_global_timestamp; 10 | bit<1> checksum_error; 11 | error parser_error; 12 | 13 | // In ingress, read or write. 14 | // In egress, should only read. 15 | bit<9> egress_spec; 16 | bit<16> mcast_grp; 17 | 18 | // Should only read, only in egress 19 | bit<9> egress_port; 20 | bit<16> egress_rid; 21 | bit<48> egress_global_timestamp; 22 | bit<32> enq_timestamp; 23 | bit<19> enq_qdepth; 24 | bit<32> deq_timedelta; 25 | bit<19> deq_qdepth; 26 | } 27 | -------------------------------------------------------------------------------- /mininet/appcontroller.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | from shortest_path import ShortestPath 4 | 5 | 6 | class AppController: 7 | 8 | def __init__(self, manifest=None, target=None, topo=None, net=None, links=None): 9 | self.manifest = manifest 10 | self.target = target 11 | self.conf = manifest['targets'][target] 12 | self.topo = topo 13 | self.net = net 14 | self.links = links 15 | 16 | def read_entries(self, filename): 17 | entries = [] 18 | with open(filename, 'r') as f: 19 | for line in f: 20 | line = line.strip() 21 | if line == '': continue 22 | entries.append(line) 23 | return entries 24 | 25 | def add_entries(self, thrift_port=9090, sw=None, entries=None): 26 | assert entries 27 | if sw: thrift_port = sw.thrift_port 28 | 29 | print('\n'.join(entries)) 30 | p = subprocess.Popen(['simple_switch_CLI', '--thrift-port', str(thrift_port)], stdin=subprocess.PIPE) 31 | p.communicate(input='\n'.join(entries)) 32 | 33 | def read_register(self, register, idx, thrift_port=9090, sw=None): 34 | if sw: thrift_port = sw.thrift_port 35 | p = subprocess.Popen(['simple_switch_CLI', '--thrift-port', str(thrift_port)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 36 | stdout, stderr = p.communicate(input="register_read %s %d" % (register, idx)) 37 | reg_val = [l for l in stdout.split('\n') if ' %s[%d]' % (register, idx) in l][0].split('= ', 1)[1] 38 | return int(reg_val) 39 | 40 | def start(self): 41 | shortestpath = ShortestPath(self.links) 42 | entries = {} 43 | for sw in self.topo.switches(): 44 | entries[sw] = [] 45 | if 'switches' in self.conf and sw in self.conf['switches'] and 'entries' in self.conf['switches'][sw]: 46 | extra_entries = self.conf['switches'][sw]['entries'] 47 | if type(extra_entries) == list: # array of entries 48 | entries[sw] += extra_entries 49 | else: # path to file that contains entries 50 | entries[sw] += self.read_entries(extra_entries) 51 | #entries[sw] += [ 52 | # 'table_set_default send_frame _drop', 53 | # 'table_set_default forward _drop', 54 | # 'table_set_default ipv4_lpm _drop'] 55 | 56 | for host_name in self.topo._host_links: 57 | h = self.net.get(host_name) 58 | for link in list(self.topo._host_links[host_name].values()): 59 | sw = link['sw'] 60 | #entries[sw].append('table_add send_frame rewrite_mac %d => %s' % (link['sw_port'], link['sw_mac'])) 61 | #entries[sw].append('table_add forward set_dmac %s => %s' % (link['host_ip'], link['host_mac'])) 62 | #entries[sw].append('table_add ipv4_lpm set_nhop %s/32 => %s %d' % (link['host_ip'], link['host_ip'], link['sw_port'])) 63 | iface = h.intfNames()[link['idx']] 64 | # use mininet to set ip and mac to let it know the change 65 | h.setIP(link['host_ip'], 24) 66 | h.setMAC(link['host_mac']) 67 | #h.cmd('ifconfig %s %s hw ether %s' % (iface, link['host_ip'], link['host_mac'])) 68 | h.cmd('arp -i %s -s %s %s' % (iface, link['sw_ip'], link['sw_mac'])) 69 | h.cmd('ethtool --offload %s rx off tx off' % iface) 70 | h.cmd('ip route add %s dev %s' % (link['sw_ip'], iface)) 71 | h.setDefaultRoute("via %s" % link['sw_ip']) 72 | 73 | for h in self.net.hosts: 74 | h_link = list(self.topo._host_links[h.name].values())[0] 75 | for sw in self.net.switches: 76 | path = shortestpath.get(sw.name, h.name, exclude=lambda n: n[0]=='h') 77 | if not path: continue 78 | if not path[1][0] == 's': continue # next hop is a switch 79 | sw_link = self.topo._sw_links[sw.name][path[1]] 80 | #entries[sw.name].append('table_add send_frame rewrite_mac %d => %s' % (sw_link[0]['port'], sw_link[0]['mac'])) 81 | #entries[sw.name].append('table_add forward set_dmac %s => %s' % (h_link['host_ip'], sw_link[1]['mac'])) 82 | #entries[sw.name].append('table_add ipv4_lpm set_nhop %s/32 => %s %d' % (h_link['host_ip'], h_link['host_ip'], sw_link[0]['port'])) 83 | 84 | for h2 in self.net.hosts: 85 | if h == h2: continue 86 | path = shortestpath.get(h.name, h2.name, exclude=lambda n: n[0]=='h') 87 | if not path: continue 88 | h_link = self.topo._host_links[h.name][path[1]] 89 | h2_link = list(self.topo._host_links[h2.name].values())[0] 90 | h.cmd('ip route add %s via %s' % (h2_link['host_ip'], h_link['sw_ip'])) 91 | 92 | 93 | print("**********") 94 | print("Configuring entries in p4 tables") 95 | for sw_name in entries: 96 | print() 97 | print("Configuring switch... %s" % sw_name) 98 | sw = self.net.get(sw_name) 99 | if entries[sw_name]: 100 | self.add_entries(sw=sw, entries=entries[sw_name]) 101 | print("Configuration complete.") 102 | print("**********") 103 | 104 | def stop(self): 105 | pass 106 | -------------------------------------------------------------------------------- /mininet/apptopo.py: -------------------------------------------------------------------------------- 1 | from mininet.topo import Topo 2 | 3 | 4 | class AppTopo(Topo): 5 | 6 | def __init__(self, links, latencies={}, manifest=None, target=None, 7 | log_dir="/tmp", bws={}, **opts): 8 | Topo.__init__(self, **opts) 9 | 10 | nodes = sum(list(map(list, list(zip(*links)))), []) 11 | host_names = sorted(list(set([n for n in nodes if n[0] == 'h']))) 12 | sw_names = sorted(list(set([n for n in nodes if n[0] == 's']))) 13 | sw_ports = dict([(sw, []) for sw in sw_names]) 14 | 15 | self._host_links = {} 16 | self._sw_links = dict([(sw, {}) for sw in sw_names]) 17 | 18 | for sw_name in sw_names: 19 | self.addSwitch(sw_name, log_file="%s/%s.log" %(log_dir, sw_name)) 20 | 21 | for host_name in host_names: 22 | host_num = int(host_name[1:]) 23 | 24 | self.addHost(host_name) 25 | 26 | self._host_links[host_name] = {} 27 | host_links = [l for l in links if l[0]==host_name or l[1]==host_name] 28 | 29 | sw_idx = 0 30 | for link in host_links: 31 | sw = link[0] if link[0] != host_name else link[1] 32 | sw_num = int(sw[1:]) 33 | assert sw[0]=='s', "Hosts should be connected to switches, not " + str(sw) 34 | host_ip = "10.0.%d.%d" % (sw_num, host_num) 35 | host_mac = '00:00:00:00:%02x:%02x' % (sw_num, host_num) 36 | delay_key = ''.join([host_name, sw]) 37 | delay = latencies[delay_key] if delay_key in latencies else '0ms' 38 | bw = bws[delay_key] if delay_key in bws else None 39 | sw_ports[sw].append(host_name) 40 | self._host_links[host_name][sw] = dict( 41 | idx=sw_idx, 42 | host_mac = host_mac, 43 | host_ip = host_ip, 44 | sw = sw, 45 | sw_mac = "00:00:00:00:%02x:%02x" % (sw_num, host_num), 46 | sw_ip = "10.0.%d.%d" % (sw_num, 254), 47 | sw_port = sw_ports[sw].index(host_name)+1 48 | ) 49 | self.addLink(host_name, sw, delay=delay, bw=bw, 50 | addr1=host_mac, addr2=self._host_links[host_name][sw]['sw_mac']) 51 | sw_idx += 1 52 | 53 | for link in links: # only check switch-switch links 54 | sw1, sw2 = link 55 | if sw1[0] != 's' or sw2[0] != 's': continue 56 | 57 | delay_key = ''.join(sorted([sw1, sw2])) 58 | delay = latencies[delay_key] if delay_key in latencies else '0ms' 59 | bw = bws[delay_key] if delay_key in bws else None 60 | 61 | self.addLink(sw1, sw2, delay=delay, bw=bw)#, max_queue_size=10) 62 | sw_ports[sw1].append(sw2) 63 | sw_ports[sw2].append(sw1) 64 | 65 | sw1_num, sw2_num = int(sw1[1:]), int(sw2[1:]) 66 | sw1_port = dict(mac="00:00:00:%02x:%02x:00" % (sw1_num, sw2_num), port=sw_ports[sw1].index(sw2)+1) 67 | sw2_port = dict(mac="00:00:00:%02x:%02x:00" % (sw2_num, sw1_num), port=sw_ports[sw2].index(sw1)+1) 68 | 69 | self._sw_links[sw1][sw2] = [sw1_port, sw2_port] 70 | self._sw_links[sw2][sw1] = [sw2_port, sw1_port] 71 | 72 | -------------------------------------------------------------------------------- /mininet/p4_mininet.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013-present Barefoot Networks, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import os 17 | import socket 18 | import tempfile 19 | from sys import exit 20 | from time import sleep 21 | 22 | from mininet.log import debug, error, info 23 | from mininet.moduledeps import pathCheck 24 | from mininet.node import Host, Switch 25 | 26 | 27 | class P4Host(Host): 28 | def config(self, **params): 29 | r = super(P4Host, self).config(**params) 30 | 31 | for off in ["rx", "tx", "sg"]: 32 | cmd = "/sbin/ethtool --offload %s %s off" % (self.defaultIntf().name, off) 33 | self.cmd(cmd) 34 | 35 | # disable IPv6 36 | self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1") 37 | self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1") 38 | self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") 39 | 40 | return r 41 | 42 | def describe(self, sw_addr=None, sw_mac=None): 43 | print("**********") 44 | print("Network configuration for: %s" % self.name) 45 | print("Default interface: %s\t%s\t%s" %( 46 | self.defaultIntf().name, 47 | self.defaultIntf().IP(), 48 | self.defaultIntf().MAC() 49 | )) 50 | if sw_addr is not None or sw_mac is not None: 51 | print("Default route to switch: %s (%s)" % (sw_addr, sw_mac)) 52 | print("**********") 53 | 54 | class P4Switch(Switch): 55 | """P4 virtual switch""" 56 | device_id = 0 57 | 58 | def __init__(self, name, sw_path = None, json_path = None, 59 | log_file = None, 60 | thrift_port = None, 61 | pcap_dump = False, 62 | log_console = False, 63 | verbose = False, 64 | device_id = None, 65 | enable_debugger = False, 66 | **kwargs): 67 | Switch.__init__(self, name, **kwargs) 68 | assert(sw_path) 69 | assert(json_path) 70 | # make sure that the provided sw_path is valid 71 | pathCheck(sw_path) 72 | # make sure that the provided JSON file exists 73 | if not os.path.isfile(json_path): 74 | error("Invalid JSON file.\n") 75 | exit(1) 76 | self.sw_path = sw_path 77 | self.json_path = json_path 78 | self.verbose = verbose 79 | self.log_file = log_file 80 | if self.log_file is None: 81 | self.log_file = "/tmp/p4s.{}.log".format(self.name) 82 | self.output = open(self.log_file, 'w') 83 | self.thrift_port = thrift_port 84 | self.pcap_dump = pcap_dump 85 | self.enable_debugger = enable_debugger 86 | self.log_console = log_console 87 | if device_id is not None: 88 | self.device_id = device_id 89 | P4Switch.device_id = max(P4Switch.device_id, device_id) 90 | else: 91 | self.device_id = P4Switch.device_id 92 | P4Switch.device_id += 1 93 | self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) 94 | 95 | @classmethod 96 | def setup(cls): 97 | pass 98 | 99 | def check_switch_started(self, pid): 100 | """While the process is running (pid exists), we check if the Thrift 101 | server has been started. If the Thrift server is ready, we assume that 102 | the switch was started successfully. This is only reliable if the Thrift 103 | server is started at the end of the init process""" 104 | while True: 105 | if not os.path.exists(os.path.join("/proc", str(pid))): 106 | return False 107 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 108 | sock.settimeout(0.5) 109 | result = sock.connect_ex(("localhost", self.thrift_port)) 110 | if result == 0: 111 | return True 112 | 113 | def start(self, controllers): 114 | "Start up a new P4 switch" 115 | info("Starting P4 switch {}.\n".format(self.name)) 116 | args = [self.sw_path] 117 | for port, intf in list(self.intfs.items()): 118 | if not intf.IP(): 119 | args.extend(['-i', str(port) + "@" + intf.name]) 120 | if self.pcap_dump: 121 | args.append("--pcap") 122 | # args.append("--useFiles") 123 | if self.thrift_port: 124 | args.extend(['--thrift-port', str(self.thrift_port)]) 125 | if self.nanomsg: 126 | args.extend(['--nanolog', self.nanomsg]) 127 | args.extend(['--device-id', str(self.device_id)]) 128 | P4Switch.device_id += 1 129 | args.append(self.json_path) 130 | if self.enable_debugger: 131 | args.append("--debugger") 132 | if self.log_console: 133 | args.append("--log-console") 134 | info(' '.join(args) + "\n") 135 | 136 | pid = None 137 | with tempfile.NamedTemporaryFile() as f: 138 | # self.cmd(' '.join(args) + ' > /dev/null 2>&1 &') 139 | self.cmd(' '.join(args) + ' >' + self.log_file + ' 2>&1 & echo $! >> ' + f.name) 140 | pid = int(f.read()) 141 | debug("P4 switch {} PID is {}.\n".format(self.name, pid)) 142 | sleep(1) 143 | if not self.check_switch_started(pid): 144 | error("P4 switch {} did not start correctly." 145 | "Check the switch log file.\n".format(self.name)) 146 | exit(1) 147 | info("P4 switch {} has been started.\n".format(self.name)) 148 | 149 | def stop(self): 150 | "Terminate P4 switch." 151 | self.output.flush() 152 | self.cmd('kill %' + self.sw_path) 153 | self.cmd('wait') 154 | self.deleteIntfs() 155 | 156 | def attach(self, intf): 157 | "Connect a data port" 158 | assert(0) 159 | 160 | def detach(self, intf): 161 | "Disconnect a data port" 162 | assert(0) 163 | -------------------------------------------------------------------------------- /mininet/shortest_path.py: -------------------------------------------------------------------------------- 1 | class ShortestPath: 2 | 3 | def __init__(self, edges=[]): 4 | self.neighbors = {} 5 | for edge in edges: 6 | self.addEdge(*edge) 7 | 8 | def addEdge(self, a, b): 9 | if a not in self.neighbors: self.neighbors[a] = [] 10 | if b not in self.neighbors[a]: self.neighbors[a].append(b) 11 | 12 | if b not in self.neighbors: self.neighbors[b] = [] 13 | if a not in self.neighbors[b]: self.neighbors[b].append(a) 14 | 15 | def get(self, a, b, exclude=lambda node: False): 16 | # Shortest path from a to b 17 | return self._recPath(a, b, [], exclude) 18 | 19 | def _recPath(self, a, b, visited, exclude): 20 | if a == b: return [a] 21 | new_visited = visited + [a] 22 | paths = [] 23 | for neighbor in self.neighbors[a]: 24 | if neighbor in new_visited: continue 25 | if exclude(neighbor) and neighbor != b: continue 26 | path = self._recPath(neighbor, b, new_visited, exclude) 27 | if path: paths.append(path) 28 | 29 | paths.sort(key=len) 30 | return [a] + paths[0] if len(paths) else None 31 | 32 | if __name__ == '__main__': 33 | 34 | edges = [ 35 | (1, 2), 36 | (1, 3), 37 | (1, 5), 38 | (2, 4), 39 | (3, 4), 40 | (3, 5), 41 | (3, 6), 42 | (4, 6), 43 | (5, 6), 44 | (7, 8) 45 | 46 | ] 47 | sp = ShortestPath(edges) 48 | 49 | assert sp.get(1, 1) == [1] 50 | assert sp.get(2, 2) == [2] 51 | 52 | assert sp.get(1, 2) == [1, 2] 53 | assert sp.get(2, 1) == [2, 1] 54 | 55 | assert sp.get(1, 3) == [1, 3] 56 | assert sp.get(3, 1) == [3, 1] 57 | 58 | assert sp.get(4, 6) == [4, 6] 59 | assert sp.get(6, 4) == [6, 4] 60 | 61 | assert sp.get(2, 6) == [2, 4, 6] 62 | assert sp.get(6, 2) == [6, 4, 2] 63 | 64 | assert sp.get(1, 6) in [[1, 3, 6], [1, 5, 6]] 65 | assert sp.get(6, 1) in [[6, 3, 1], [6, 5, 1]] 66 | 67 | assert sp.get(2, 5) == [2, 1, 5] 68 | assert sp.get(5, 2) == [5, 1, 2] 69 | 70 | assert sp.get(4, 5) in [[4, 3, 5], [4, 6, 5]] 71 | assert sp.get(5, 4) in [[5, 3, 4], [6, 6, 4]] 72 | 73 | assert sp.get(7, 8) == [7, 8] 74 | assert sp.get(8, 7) == [8, 7] 75 | 76 | assert sp.get(1, 7) == None 77 | assert sp.get(7, 2) == None 78 | 79 | -------------------------------------------------------------------------------- /mininet/single_switch_mininet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2013-present Barefoot Networks, Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | import argparse 19 | from subprocess import PIPE, Popen 20 | from time import sleep 21 | 22 | from mininet.cli import CLI 23 | from mininet.log import setLogLevel 24 | from mininet.net import Mininet 25 | from mininet.topo import Topo 26 | from p4_mininet import P4Host, P4Switch 27 | 28 | parser = argparse.ArgumentParser(description='Mininet demo') 29 | parser.add_argument('--behavioral-exe', help='Path to behavioral executable', 30 | type=str, action="store", required=True) 31 | parser.add_argument('--thrift-port', help='Thrift server port for table updates', 32 | type=int, action="store", default=9090) 33 | parser.add_argument('--num-hosts', help='Number of hosts to connect to switch', 34 | type=int, action="store", default=2) 35 | parser.add_argument('--mode', choices=['l2', 'l3'], type=str, default='l3') 36 | parser.add_argument('--json', help='Path to JSON config file', 37 | type=str, action="store", required=True) 38 | parser.add_argument('--log-file', help='Path to write the switch log file', 39 | type=str, action="store", required=False) 40 | parser.add_argument('--pcap-dump', help='Dump packets on interfaces to pcap files', 41 | type=str, action="store", required=False, default=False) 42 | parser.add_argument('--switch-config', help='simple_switch_CLI script to configure switch', 43 | type=str, action="store", required=False, default=False) 44 | parser.add_argument('--cli-message', help='Message to print before starting CLI', 45 | type=str, action="store", required=False, default=False) 46 | 47 | args = parser.parse_args() 48 | 49 | 50 | class SingleSwitchTopo(Topo): 51 | "Single switch connected to n (< 256) hosts." 52 | def __init__(self, sw_path, json_path, log_file, 53 | thrift_port, pcap_dump, n, **opts): 54 | # Initialize topology and default options 55 | Topo.__init__(self, **opts) 56 | 57 | switch = self.addSwitch('s1', 58 | sw_path = sw_path, 59 | json_path = json_path, 60 | log_console = True, 61 | log_file = log_file, 62 | thrift_port = thrift_port, 63 | enable_debugger = False, 64 | pcap_dump = pcap_dump) 65 | 66 | for h in range(n): 67 | host = self.addHost('h%d' % (h + 1), 68 | ip = "10.0.%d.10/24" % h, 69 | mac = '00:04:00:00:00:%02x' %h) 70 | print("Adding host", str(host)) 71 | self.addLink(host, switch) 72 | 73 | def main(): 74 | num_hosts = args.num_hosts 75 | mode = args.mode 76 | 77 | topo = SingleSwitchTopo(args.behavioral_exe, 78 | args.json, 79 | args.log_file, 80 | args.thrift_port, 81 | args.pcap_dump, 82 | num_hosts) 83 | net = Mininet(topo = topo, 84 | host = P4Host, 85 | switch = P4Switch, 86 | controller = None) 87 | net.start() 88 | 89 | 90 | sw_mac = ["00:aa:bb:00:00:%02x" % n for n in range(num_hosts)] 91 | 92 | sw_addr = ["10.0.%d.1" % n for n in range(num_hosts)] 93 | 94 | for n in range(num_hosts): 95 | h = net.get('h%d' % (n + 1)) 96 | if mode == "l2": 97 | h.setDefaultRoute("dev %s" % h.defaultIntf().name) 98 | else: 99 | h.setARP(sw_addr[n], sw_mac[n]) 100 | h.setDefaultRoute("dev %s via %s" % (h.defaultIntf().name, sw_addr[n])) 101 | 102 | for n in range(num_hosts): 103 | h = net.get('h%d' % (n + 1)) 104 | h.describe(sw_addr[n], sw_mac[n]) 105 | 106 | sleep(1) 107 | 108 | if args.switch_config is not None: 109 | print() 110 | print("Reading switch configuration script:", args.switch_config) 111 | with open(args.switch_config, 'r') as config_file: 112 | switch_config = config_file.read() 113 | 114 | print("Configuring switch...") 115 | proc = Popen(["simple_switch_CLI"], stdin=PIPE) 116 | proc.communicate(input=switch_config) 117 | 118 | print("Configuration complete.") 119 | print() 120 | 121 | print("Ready !") 122 | 123 | if args.cli_message is not None: 124 | with open(args.cli_message, 'r') as message_file: 125 | print(message_file.read()) 126 | 127 | CLI( net ) 128 | net.stop() 129 | 130 | if __name__ == '__main__': 131 | setLogLevel( 'info' ) 132 | main() 133 | -------------------------------------------------------------------------------- /p4-cheat-sheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImanRHT/SDN_Programmable_Networks/da8fa46fad88ab408054e80e9953ae3ecbf9feb0/p4-cheat-sheet.pdf --------------------------------------------------------------------------------