diff --git a/P4D2_2017_Fall/utils/netstat.py b/P4D2_2017_Fall/utils/netstat.py new file mode 100644 index 0000000..bb12ffd --- /dev/null +++ b/P4D2_2017_Fall/utils/netstat.py @@ -0,0 +1,21 @@ +# Copyright 2017-present Open Networking Foundation +# +# 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 psutil +def check_listening_on_port(port): + for c in psutil.net_connections(kind='inet'): + if c.status == 'LISTEN' and c.laddr[1] == port: + return True + return False diff --git a/P4D2_2017_Fall/utils/p4_mininet.py b/P4D2_2017_Fall/utils/p4_mininet.py new file mode 100644 index 0000000..b7fbbcd --- /dev/null +++ b/P4D2_2017_Fall/utils/p4_mininet.py @@ -0,0 +1,162 @@ +# Copyright 2013-present 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. +# + +from mininet.net import Mininet +from mininet.node import Switch, Host +from mininet.log import setLogLevel, info, error, debug +from mininet.moduledeps import pathCheck +from sys import exit +import os +import tempfile +import socket +from time import sleep + +from netstat import check_listening_on_port + +SWITCH_START_TIMEOUT = 10 # seconds + +class P4Host(Host): + def config(self, **params): + r = super(Host, self).config(**params) + + self.defaultIntf().rename("eth0") + + for off in ["rx", "tx", "sg"]: + cmd = "/sbin/ethtool --offload eth0 %s off" % off + self.cmd(cmd) + + # disable IPv6 + self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1") + self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1") + self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") + + return r + + def describe(self): + print "**********" + print self.name + print "default interface: %s\t%s\t%s" %( + self.defaultIntf().name, + self.defaultIntf().IP(), + self.defaultIntf().MAC() + ) + print "**********" + +class P4Switch(Switch): + """P4 virtual switch""" + device_id = 0 + + def __init__(self, name, sw_path = None, json_path = None, + thrift_port = None, + pcap_dump = False, + log_console = False, + verbose = False, + device_id = None, + enable_debugger = False, + **kwargs): + Switch.__init__(self, name, **kwargs) + assert(sw_path) + assert(json_path) + # make sure that the provided sw_path is valid + pathCheck(sw_path) + # make sure that the provided JSON file exists + if not os.path.isfile(json_path): + error("Invalid JSON file.\n") + exit(1) + self.sw_path = sw_path + self.json_path = json_path + self.verbose = verbose + logfile = "/tmp/p4s.{}.log".format(self.name) + self.output = open(logfile, 'w') + self.thrift_port = thrift_port + if check_listening_on_port(self.thrift_port): + error('%s cannot bind port %d because it is bound by another process\n' % (self.name, self.grpc_port)) + exit(1) + self.pcap_dump = pcap_dump + self.enable_debugger = enable_debugger + self.log_console = log_console + if device_id is not None: + self.device_id = device_id + P4Switch.device_id = max(P4Switch.device_id, device_id) + else: + self.device_id = P4Switch.device_id + P4Switch.device_id += 1 + self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) + + @classmethod + def setup(cls): + pass + + def check_switch_started(self, pid): + """While the process is running (pid exists), we check if the Thrift + server has been started. If the Thrift server is ready, we assume that + the switch was started successfully. This is only reliable if the Thrift + server is started at the end of the init process""" + while True: + if not os.path.exists(os.path.join("/proc", str(pid))): + return False + if check_listening_on_port(self.thrift_port): + return True + sleep(0.5) + + def start(self, controllers): + "Start up a new P4 switch" + info("Starting P4 switch {}.\n".format(self.name)) + args = [self.sw_path] + for port, intf in self.intfs.items(): + if not intf.IP(): + args.extend(['-i', str(port) + "@" + intf.name]) + if self.pcap_dump: + args.append("--pcap") + # args.append("--useFiles") + if self.thrift_port: + args.extend(['--thrift-port', str(self.thrift_port)]) + if self.nanomsg: + args.extend(['--nanolog', self.nanomsg]) + args.extend(['--device-id', str(self.device_id)]) + P4Switch.device_id += 1 + args.append(self.json_path) + if self.enable_debugger: + args.append("--debugger") + if self.log_console: + args.append("--log-console") + logfile = "/tmp/p4s.{}.log".format(self.name) + info(' '.join(args) + "\n") + + pid = None + with tempfile.NamedTemporaryFile() as f: + # self.cmd(' '.join(args) + ' > /dev/null 2>&1 &') + self.cmd(' '.join(args) + ' >' + logfile + ' 2>&1 & echo $! >> ' + f.name) + pid = int(f.read()) + debug("P4 switch {} PID is {}.\n".format(self.name, pid)) + if not self.check_switch_started(pid): + error("P4 switch {} did not start correctly.\n".format(self.name)) + exit(1) + info("P4 switch {} has been started.\n".format(self.name)) + + def stop(self): + "Terminate P4 switch." + self.output.flush() + self.cmd('kill %' + self.sw_path) + self.cmd('wait') + self.deleteIntfs() + + def attach(self, intf): + "Connect a data port" + assert(0) + + def detach(self, intf): + "Disconnect a data port" + assert(0) diff --git a/P4D2_2017_Fall/utils/p4runtime_switch.py b/P4D2_2017_Fall/utils/p4runtime_switch.py index ea468d8..df919ed 100644 --- a/P4D2_2017_Fall/utils/p4runtime_switch.py +++ b/P4D2_2017_Fall/utils/p4runtime_switch.py @@ -21,18 +21,8 @@ from mininet.node import Switch from mininet.moduledeps import pathCheck from mininet.log import info, error, debug -# this path is needed to import p4_mininet.py from the bmv2 repo -sys.path.append('/home/vagrant/behavioral-model/mininet') -from p4_mininet import P4Switch - -SWITCH_START_TIMEOUT = 10 # seconds - -import psutil -def check_listening_on_port(port): - for c in psutil.net_connections(kind='inet'): - if c.status == 'LISTEN' and c.laddr[1] == port: - return True - return False +from p4_mininet import P4Switch, SWITCH_START_TIMEOUT +from netstat import check_listening_on_port class P4RuntimeSwitch(P4Switch): "BMv2 switch with gRPC support" diff --git a/P4D2_2017_Fall/utils/run_exercise.py b/P4D2_2017_Fall/utils/run_exercise.py index dcb22bc..84997d7 100755 --- a/P4D2_2017_Fall/utils/run_exercise.py +++ b/P4D2_2017_Fall/utils/run_exercise.py @@ -22,8 +22,6 @@ import os, sys, json, subprocess, re, argparse from time import sleep -# this path is needed to import p4_mininet.py from the bmv2 repo -sys.path.append('/home/vagrant/behavioral-model/mininet') from p4_mininet import P4Switch, P4Host from mininet.net import Mininet