diff --git a/Dockerfiles/p4bmv2/Dockerfile b/Dockerfiles/p4bmv2/Dockerfile new file mode 100644 index 0000000..b3333bc --- /dev/null +++ b/Dockerfiles/p4bmv2/Dockerfile @@ -0,0 +1,16 @@ +FROM p4lang/p4runtime-sh:latest as deps + +# p4lang/behavioral-model is included by p4lang/p4c +FROM p4lang/p4c +RUN apt-get update -y && apt-get install -y iproute2 vim +COPY ./wait.sh /usr/bin/wait.sh +RUN chmod +x /usr/bin/wait.sh + +ENV VENV /p4runtime-sh/venv +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl git python3 python3-venv && rm -rf /var/cache/apt/* /var/lib/apt/lists/* +COPY --from=deps /p4runtime-sh /p4runtime-sh +COPY ./p4runtime-sh /p4runtime-sh/p4runtime-sh +RUN pip3 install p4runtime-shell && chmod +x /p4runtime-sh/p4runtime-sh + +RUN apt-get update -y && apt-get install -y tcpdump psmisc iputils-ping net-tools diff --git a/Dockerfiles/p4bmv2/Makefile b/Dockerfiles/p4bmv2/Makefile new file mode 100644 index 0000000..399d485 --- /dev/null +++ b/Dockerfiles/p4bmv2/Makefile @@ -0,0 +1,8 @@ +IMG=tinynetwork/p4bmv2:develop +build: + docker build -t $(IMG) . +push: + docker push $(IMG) +all: build push +run: + docker run --rm -it --privileged $(IMG) bash diff --git a/Dockerfiles/p4bmv2/p4runtime-sh b/Dockerfiles/p4bmv2/p4runtime-sh new file mode 100644 index 0000000..cfe5f85 --- /dev/null +++ b/Dockerfiles/p4bmv2/p4runtime-sh @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +# Copyright 2019 Barefoot Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import p4runtime_sh.shell as sh +sh.main() diff --git a/Dockerfiles/p4bmv2/wait.sh b/Dockerfiles/p4bmv2/wait.sh new file mode 100644 index 0000000..aff0916 --- /dev/null +++ b/Dockerfiles/p4bmv2/wait.sh @@ -0,0 +1,11 @@ +#!/bin/bash +for i in `seq 10`; do + echo EOF | bm_CLI + if [ "$?" == "0" ]; then + echo "bm_CLI is ready!" + exit 0 + fi + sleep 1 +done +echo "bm_CLI is not ready" +exit 1 diff --git a/examples/basic_p4/l3switch/Makefile b/examples/basic_p4/l3switch/Makefile new file mode 100644 index 0000000..7e8918d --- /dev/null +++ b/examples/basic_p4/l3switch/Makefile @@ -0,0 +1,15 @@ +c: + docker cp main.py P4:/ + docker exec P4 killall -9 python3 || true + docker exec -it P4 python3 /main.py + +d: + docker cp main.p4 P4:/ + docker exec P4 killall -9 simple_switch_grpc || true + docker exec P4 p4c --target bmv2 --arch v1model /main.p4 --p4runtime-files p4info.txt -o / + docker exec -it P4 simple_switch_grpc /main.json \ + -i 1@vm1 -i 2@vm2 -i 3@vm3 \ + --nanolog ipc:///tmp/bm-0-log.ipc \ + --log-console -L debug \ + --notifications-addr ipc:///tmp/bmv2-0-notifications.ipc \ + -- --cpu-port 255 diff --git a/examples/basic_p4/l3switch/README.md b/examples/basic_p4/l3switch/README.md new file mode 100644 index 0000000..919f445 --- /dev/null +++ b/examples/basic_p4/l3switch/README.md @@ -0,0 +1,78 @@ +# P4Runtime and BM-CLI mapping + +## Running p4runtime-shell + +``` +docker exec P4 simple_switch_grpc /main.json -i 1@vm1 -i 2@vm2 -i 3@vm3 --nanolog ipc:///tmp/bm-0-log.ipc --log-console -L debug --notifications-addr ipc:///tmp/bmv2-0-notifications.ipc -- --cpu-port 255 +// copy p4info.txt and main.json +docker run -it --net container:P4 --name p4r -v /tmp/P4runtime-nanoswitch/:/tmp/ myproj/p4rt-sh-dev /bin/bash +source $VENV/bin/activate +/p4runtime-sh/p4runtime-sh --grpc-addr 0.0.0.0:9559 --device-id 0 --election-id 0,1 --config /p4info.txt,/main.json +``` + +for all in one image +``` +/p4runtime-sh/p4runtime-sh --grpc-addr 0.0.0.0:9559 --device-id 0 --election-id 0,1 --config /p4c/p4info.txt,/main.json +``` + +## Misc + +- https://github.com/p4lang/p4runtime-shell#available-commands +- https://github.com/p4lang/p4runtime-shell/blob/main/usage/packet_io.md + +``` +tables +actions +packet_in.sniff(lambda m: print(m), timeout=1) +``` + +## Modifying Default Table Entry + +bm cli of `simple_switch` +``` +table_set_default MyIngress.ipv4_lpm MyIngress.drop +``` + +p4runtime cli of `simple_switch_grpc` +``` +te = table_entry["MyIngress.ipv4_lpm"](action="MyIngress.drop", is_default=True) +te.modify +``` + +## Inserting Table Entry + +bm cli of `simple_switch` +``` +table_add MyIngress.ipv4_lpm MyIngress.ipv4_forward 192.168.10.10/32 => 10:10:10:10:10:10 1 +table_add MyIngress.ipv4_lpm MyIngress.ipv4_forward 192.168.20.20/32 => 20:20:20:20:20:20 2 +table_add MyIngress.ipv4_lpm MyIngress.ipv4_forward 192.168.30.30/32 => 30:30:30:30:30:30 3 +``` + +p4runtime cli of `simple_switch_grpc` +``` +te1 = table_entry["MyIngress.ipv4_lpm"](action="MyIngress.ipv4_forward") +te1.match["hdr.ipv4.dstAddr"] = "192.168.20.20/32" +te1.action["dstAddr"] = '20:20:20:20:20:20' +te1.action["port"] = "2" +te1.insert() + +te2 = table_entry["MyIngress.ipv4_lpm"](action="MyIngress.ipv4_forward") +te2.match["hdr.ipv4.dstAddr"] = "192.168.10.10/32" +te2.action["dstAddr"] = '10:10:10:10:10:10' +te2.action["port"] = "1" +te2.insert() + +te3 = table_entry["MyIngress.ipv4_lpm"](action="MyIngress.ipv4_forward") +te3.match["hdr.ipv4.dstAddr"] = "192.168.30.30/32" +te3.action["dstAddr"] = '30:30:30:30:30:30' +te3.action["port"] = "3" +te3.insert() + +te4 = table_entry["MyIngress.ipv4_lpm"](action="MyIngress.ipv4_forward") +te4.match["hdr.ipv4.dstAddr"] = "192.168.40.40/32" +te4.action["dstAddr"] = 'aa:aa:aa:aa:aa:aa' +te4.action["port"] = "255" +te4.insert() + +for te in table_entry["MyIngress.ipv4_lpm"].read(): print(te) +``` diff --git a/examples/basic_p4/l3switch/main.p4 b/examples/basic_p4/l3switch/main.p4 new file mode 100644 index 0000000..7e7d66f --- /dev/null +++ b/examples/basic_p4/l3switch/main.p4 @@ -0,0 +1,193 @@ +/* -*- P4_16 -*- */ +#include +#include +#define CPU_PORT 255 +const bit<16> TYPE_IPV4 = 0x800; + +/************************************************************************* +*********************** H E A D E R S *********************************** +*************************************************************************/ + +typedef bit<9> egressSpec_t; +typedef bit<48> macAddr_t; +typedef bit<32> ip4Addr_t; + +@controller_header("packet_in") +header packet_in_header_t { + bit<9> ingress_port; + bit<7> _pad; +} + +header ethernet_t { + macAddr_t dstAddr; + macAddr_t srcAddr; + bit<16> etherType; +} + +header ipv4_t { + bit<4> version; + bit<4> ihl; + bit<8> diffserv; + bit<16> totalLen; + bit<16> identification; + bit<3> flags; + bit<13> fragOffset; + bit<8> ttl; + bit<8> protocol; + bit<16> hdrChecksum; + ip4Addr_t srcAddr; + ip4Addr_t dstAddr; +} + +struct metadata { + bit<9> ingress_port; + bit<7> _pad; +} + +struct headers { + ethernet_t ethernet; + ipv4_t ipv4; + packet_in_header_t packet_in; +} + +/************************************************************************* +*********************** P A R S E R *********************************** +*************************************************************************/ + +parser MyParser(packet_in packet, + out headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + + state start { + transition parse_ethernet; + } + + state parse_ethernet { + packet.extract(hdr.ethernet); + transition select(hdr.ethernet.etherType) { + TYPE_IPV4: parse_ipv4; + default: accept; + } + } + + state parse_ipv4 { + packet.extract(hdr.ipv4); + transition accept; + } + +} + +/************************************************************************* +************ C H E C K S U M V E R I F I C A T I O N ************* +*************************************************************************/ + +control MyVerifyChecksum(inout headers hdr, inout metadata meta) { + apply { } +} + + +/************************************************************************* +************** I N G R E S S P R O C E S S I N G ******************* +*************************************************************************/ + +control MyIngress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + action drop() { + mark_to_drop(standard_metadata); + } + + action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) { + standard_metadata.egress_spec = port; + hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; + hdr.ethernet.dstAddr = dstAddr; + hdr.ipv4.ttl = hdr.ipv4.ttl - 1; + } + + action to_controller() { + standard_metadata.egress_spec = CPU_PORT; + hdr.packet_in.setValid(); + hdr.packet_in.ingress_port = standard_metadata.ingress_port; + } + + table ipv4_lpm { + key = { + hdr.ipv4.dstAddr: lpm; + } + actions = { + ipv4_forward; + drop; + NoAction; + to_controller; + } + size = 1024; + default_action = drop(); + } + + apply { + if (hdr.ipv4.isValid()) { + ipv4_lpm.apply(); + } + } +} + +/************************************************************************* +**************** E G R E S S P R O C E S S I N G ******************* +*************************************************************************/ + +control MyEgress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + apply { + } +} + +/************************************************************************* +************* C H E C K S U M C O M P U T A T I O N ************** +*************************************************************************/ + +control MyComputeChecksum(inout headers hdr, inout metadata meta) { + apply { + update_checksum( + hdr.ipv4.isValid(), + { hdr.ipv4.version, + hdr.ipv4.ihl, + hdr.ipv4.diffserv, + hdr.ipv4.totalLen, + hdr.ipv4.identification, + hdr.ipv4.flags, + hdr.ipv4.fragOffset, + hdr.ipv4.ttl, + hdr.ipv4.protocol, + hdr.ipv4.srcAddr, + hdr.ipv4.dstAddr }, + hdr.ipv4.hdrChecksum, + HashAlgorithm.csum16); + } +} + +/************************************************************************* +*********************** D E P A R S E R ******************************* +*************************************************************************/ + +control MyDeparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr.packet_in); + packet.emit(hdr.ethernet); + packet.emit(hdr.ipv4); + } +} + +/************************************************************************* +*********************** S W I T C H ******************************* +*************************************************************************/ + +V1Switch( + MyParser(), + MyVerifyChecksum(), + MyIngress(), + MyEgress(), + MyComputeChecksum(), + MyDeparser() +) main; diff --git a/examples/basic_p4/l3switch/main.py b/examples/basic_p4/l3switch/main.py new file mode 100644 index 0000000..31f35d4 --- /dev/null +++ b/examples/basic_p4/l3switch/main.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +import os +import time +import struct +import pprint +import subprocess +import threading +from fcntl import ioctl +import p4runtime_sh.shell as sh +from p4runtime_sh.p4runtime import P4RuntimeClient +from p4.v1 import p4runtime_pb2 +from p4.config.v1 import p4info_pb2 + + +def exec(cmd): + subprocess.check_call(cmd, shell=True) + +def openTun(tunName, macaddr, ipaddr): + tun = open("/dev/net/tun", "r+b", buffering=0) + LINUX_IFF_TAP = 0x0002 + LINUX_IFF_NO_PI = 0x1000 + LINUX_TUNSETIFF = 0x400454CA + flags = LINUX_IFF_TAP | LINUX_IFF_NO_PI + ifs = struct.pack("16sH22s", tunName.encode("utf-8"), flags, b"") + ioctl(tun, LINUX_TUNSETIFF, ifs) + subprocess.check_call(f'ip link set {tunName} address {macaddr}', shell=True) + subprocess.check_call(f'ip link set {tunName} up', shell=True) + subprocess.check_call(f'ip addr add {ipaddr} dev {tunName}', shell=True) + return tun + + +sh.setup( + device_id=0, + grpc_addr='localhost:9559', + election_id=(0, 1), + config=sh.FwdPipeConfig('/p4c/p4info.txt', '/main.json')) +te1 = sh.TableEntry("MyIngress.ipv4_lpm")(action="MyIngress.ipv4_forward") +te1.match["hdr.ipv4.dstAddr"] = "10.0.1.2/32" +te1.action["dstAddr"] = '10:10:10:10:10:10' +te1.action["port"] = "1" +te1.insert() +te2 = sh.TableEntry("MyIngress.ipv4_lpm")(action="MyIngress.ipv4_forward") +te2.match["hdr.ipv4.dstAddr"] = "10.0.2.2/32" +te2.action["dstAddr"] = '20:20:20:20:20:20' +te2.action["port"] = "2" +te2.insert() +te3 = sh.TableEntry("MyIngress.ipv4_lpm")(action="MyIngress.ipv4_forward") +te3.match["hdr.ipv4.dstAddr"] = "10.0.3.2/32" +te3.action["dstAddr"] = '30:30:30:30:30:30' +te3.action["port"] = "3" +te3.insert() +te = sh.TableEntry("MyIngress.ipv4_lpm")(action="MyIngress.to_controller") +te.match["hdr.ipv4.dstAddr"] = "10.0.1.1/32" +te.insert() +te = sh.TableEntry("MyIngress.ipv4_lpm")(action="MyIngress.to_controller") +te.match["hdr.ipv4.dstAddr"] = "10.0.2.1/32" +te.insert() +te = sh.TableEntry("MyIngress.ipv4_lpm")(action="MyIngress.to_controller") +te.match["hdr.ipv4.dstAddr"] = "10.0.3.1/32" +te.insert() +sh.teardown() + +## PREPARE TAP +swp = [ + openTun("swp1", "52:54:00:00:00:01", "10.0.1.1/24"), + openTun("swp2", "52:54:00:00:00:02", "10.0.2.1/24"), + openTun("swp3", "52:54:00:00:00:03", "10.0.3.1/24"), +] +exec('ip nei replace 10.0.1.2 lladdr 10:10:10:10:10:10 dev swp1') +exec('ip nei replace 10.0.2.2 lladdr 20:20:20:20:20:20 dev swp2') +exec('ip nei replace 10.0.3.2 lladdr 30:30:30:30:30:30 dev swp3') + +## MAIN ROUTING +client = P4RuntimeClient( + device_id=0, + grpc_addr='localhost:9559', + election_id=(0, 1)) + +def loop_packet_in(): + while True: + rep = client.get_stream_packet("packet", timeout=1) + if rep is not None: + v = struct.unpack("@c", rep.packet.metadata[0].value) + v = int.from_bytes(v[0], "little") + print(f"PacketIN({v})") + swp[v-1].write(rep.packet.payload) + +def loop_packet_out(portIdx): + port = swp[portIdx-1] + while True: + data = os.read(port.fileno(), 1500) + print(f"PacketOut({portIdx})") + req = p4runtime_pb2.StreamMessageRequest() + req.packet.payload = data + # XXX(slankdev) + # metadata = p4runtime_pb2.PacketMetadata() + # metadata.metadata_id = 1 + # metadata.value = portIdx + # req.packet.metadata.append(metadata) + # metadata.metadata_id = 3 + # metadata.value = mcast_grp + # req.packet.metadata.append(metadata) + client.stream_out_q.put(req) + +thread1 = threading.Thread(target=loop_packet_in) +thread1.start() +threading.Thread(target=loop_packet_out, args=(1,)).start() +threading.Thread(target=loop_packet_out, args=(2,)).start() +threading.Thread(target=loop_packet_out, args=(3,)).start() +thread1.join() diff --git a/examples/basic_p4/l3switch/runtime.txt b/examples/basic_p4/l3switch/runtime.txt new file mode 100644 index 0000000..ec3ec9f --- /dev/null +++ b/examples/basic_p4/l3switch/runtime.txt @@ -0,0 +1,4 @@ +table_set_default MyIngress.ipv4_lpm MyIngress.drop +table_add MyIngress.ipv4_lpm MyIngress.ipv4_forward 192.168.10.10/32 => 10:10:10:10:10:10 1 +table_add MyIngress.ipv4_lpm MyIngress.ipv4_forward 192.168.20.20/32 => 20:20:20:20:20:20 2 +table_add MyIngress.ipv4_lpm MyIngress.ipv4_forward 192.168.30.30/32 => 30:30:30:30:30:30 3 diff --git a/examples/basic_p4/l3switch/spec.yaml b/examples/basic_p4/l3switch/spec.yaml new file mode 100644 index 0000000..acbb6e9 --- /dev/null +++ b/examples/basic_p4/l3switch/spec.yaml @@ -0,0 +1,62 @@ +nodes: +- name: P4 + image: tinynetwork/p4bmv2:develop + docker_run_extra_args: --entrypoint bash + interfaces: + - { name: vm1, type: direct, args: VM1#net0, addr: 52:54:00:00:00:01 } + - { name: vm2, type: direct, args: VM2#net0, addr: 52:54:00:00:00:02 } + - { name: vm3, type: direct, args: VM3#net0, addr: 52:54:00:00:00:03 } + sysctls: + - sysctl: net.ipv4.ip_forward=0 + - sysctl: net.ipv6.conf.all.forwarding=0 +- name: VM1 + image: nicolaka/netshoot + docker_run_extra_args: --entrypoint bash + interfaces: + - { name: net0, type: direct, args: P4#vm1, addr: 10:10:10:10:10:10 } +- name: VM2 + image: nicolaka/netshoot + docker_run_extra_args: --entrypoint bash + interfaces: + - { name: net0, type: direct, args: P4#vm2, addr: 20:20:20:20:20:20 } +- name: VM3 + image: nicolaka/netshoot + docker_run_extra_args: --entrypoint bash + interfaces: + - { name: net0, type: direct, args: P4#vm3, addr: 30:30:30:30:30:30 } + +node_configs: +- name: P4 + cmds: + - cmd: echo hello + # - cmd: >- + # p4c --target bmv2 --arch v1model /main.p4 + # --p4runtime-files p4info.txt -o / + # - cmd: >- + # nohup simple_switch_grpc /main.json -i 1@vm1 -i 2@vm2 -i 3@vm3 + # --nanolog ipc:///tmp/bm-0-log.ipc --log-console -L debug + # --notifications-addr ipc:///tmp/bmv2-0-notifications.ipc + # -- --cpu-port 255 & + # - cmd: >- + # nohup python3 /main.py & +- name: VM1 + cmds: + - cmd: ip addr add 10.0.1.2/24 dev net0 + - cmd: ip neigh replace 10.0.1.1 lladdr 52:54:00:00:00:01 dev net0 + - cmd: ip route add default via 10.0.1.1 +- name: VM2 + cmds: + - cmd: ip addr add 10.0.2.2/24 dev net0 + - cmd: ip neigh replace 10.0.2.1 lladdr 52:54:00:00:00:02 dev net0 + - cmd: ip route add default via 10.0.2.1 +- name: VM3 + cmds: + - cmd: ip addr add 10.0.3.2/24 dev net0 + - cmd: ip neigh replace 10.0.3.1 lladdr 52:54:00:00:00:03 dev net0 + - cmd: ip route add default via 10.0.3.1 + +postinit: + cmds: + - cmd: docker cp main.py P4:/main.py + - cmd: docker cp main.p4 P4:/main.p4 + - cmd: docker cp runtime.txt P4:/runtime.txt