from __future__ import unicode_literals import nnpy import struct from p4utils.utils.topology import Topology from p4utils.utils.sswitch_API import SimpleSwitchAPI from scapy.all import sniff, get_if_list, Ether, get_if_hwaddr, sendp from scapy.all import IP, Raw, IPv6, TCP, TCP_client from scapy.all import Ether, sniff, Packet, BitField import sys import re import logging import argparse import subprocess # Broken in python2 import ipaddress logging.basicConfig() log = logging.getLogger("main") class CpuHeader(Packet): name = 'CpuPacket' fields_desc = [BitField('ingress_port', 0, 16)] class L2Controller(object): def __init__(self, sw_name): # Command line mapping self.modes = ['base', 'router'] self.info={} self.info['ndp_multicast'] = ipaddress.ip_network("ff02::1:ff00:0/104") self.address_suffix = 42 # Network / egress self.v6_mask = "64" self.v6_routes = {} self.v6_routes[None] = [] self.v6_routes['base'] = [] self.v6_routes['base'].append({ "net": "2001:db8:61::", "port": "1"}) self.v6_routes['base'].append({ "net": "2001:db8:62::", "port": "2"}) self.v6_routes['router'] = self.v6_routes['base'] self.v4_mask = "24" self.v4_routes = {} self.v4_routes[None] = [] self.v4_routes['base'] = [] self.v4_routes['base'].append({ "net": "10.0.41.", "port": "3"}) self.v4_routes['base'].append({ "net": "10.0.42.", "port": "4"}) self.v4_routes['router'] = self.v4_routes['base'] self.v6_addresses = {} self.v6_addresses[None] = [] self.v6_addresses['base'] = [] self.v6_addresses['router'] = [ { "addr": "{}{}".format(n['net'], self.address_suffix), "port": n['port'] } for n in self.v6_routes['router'] ] self.v4_addresses = {} self.v4_addresses[None] = [] self.v4_addresses['base'] = [] self.v4_addresses['router'] = [ { "addr": "{}{}".format(n['net'], self.address_suffix), "port": n['port'] } for n in self.v4_routes['router'] ] self.init_boilerplate(sw_name) @staticmethod def prefix_to_net(net, mask): """ work around ipaddress bug """ net = "{}0/{}".format(net, mask) log.debug("net: {}".format(net)) return net @staticmethod def prefix_to_host(net, mask): """ work around ipaddress bug """ host = "{}1/{}".format(net, mask) log.debug("host: {}".format(host)) return "{}1/{}".format(net, mask) @staticmethod def add_host_ips(host, net, ipaddr, dev): subprocess.call(["mx", host, "ip", "addr", "flush", "dev", dev]) for v6dev in [ "lo", "default", "all", dev ]: subprocess.call(["mx", host, "sysctl", "net.ipv6.conf.{}.disable_ipv6=0".format(v6dev)]) # Set down & up to regain link local address subprocess.call(["mx", host, "ip", "link", "set", dev, "down"]) subprocess.call(["mx", host, "ip", "link", "set", dev, "up"]) # Now add global address subprocess.call(["mx", host, "ip", "addr", "add", ipaddr, "dev", dev]) def init_ndp(self): """ initialise neighbor discovery protocol""" # https://en.wikipedia.org/wiki/Solicited-node_multicast_address ndp_prefix = "ff02::1:ff00:0/104" all_ports = range(1,5) # create multicast nodes for rid in range(1,5): ports = [ x for x in all_ports if not x == rid ] n_handle = self.controller.mc_node_create(rid, ports) log.debug("Creating MC node rid={} ports={} handle={}".format(rid, ports, n_handle)) g_handle = self.controller.mc_mgrp_create(rid) log.debug("Creating MC group mgrp={} handle={} && associating afterwards".format(rid, g_handle)) self.controller.mc_node_associate(g_handle, n_handle) self.controller.table_clear("ndp") for port in all_ports: self.controller.table_add("ndp", "multicast_pkg", [ndp_prefix, str(port)], [str(port)]) # Special rule for switch entries self.controller.table_add("ndp_answer", "icmp6_neighbor_solicitation", ["ff02::1:ff00:42", "135"], ["2001:db8:61::42"]) def init_boilerplate(self, sw_name): self.topo = Topology(db="topology.db") self.sw_name = sw_name self.thrift_port = self.topo.get_thrift_port(sw_name) self.cpu_port = self.topo.get_cpu_port_index(self.sw_name) self.controller = SimpleSwitchAPI(self.thrift_port) self.intf = str(self.topo.get_cpu_port_intf(self.sw_name).replace("eth0", "eth1")) self.controller.reset_state() if self.cpu_port: self.controller.mirroring_add(100, self.cpu_port) # self.init_ndp() def config(self): self.fill_tables() self.config_hosts() def fill_tables(self): self.controller.table_clear("v6_routing") for v6route in self.v6_routes[self.mode]: net = self.prefix_to_net(v6route['net'], self.v6_mask) self.controller.table_add("v6_routing", "set_egress_port", [net], [v6route['port']]) self.controller.table_clear("v4_routing") for v4route in self.v4_routes[self.mode]: net = self.prefix_to_net(v4route['net'], self.v4_mask) self.controller.table_add("v4_routing", "set_egress_port", [net], [v4route['port']]) self.controller.table_clear("v6_addresses") for v6addr in self.v6_addresses[self.mode]: self.controller.table_add("v6_addresses", "controller_reply", [v6addr['addr']]) def gen_ndp_multicast_addr(self, addr): """ append the 24 bit of the address to the multicast address""" return ipaddress.ip_address(int(self.info['ndp_multicast']) + (int(addr) & 0xffffff)) def config_hosts(self): """ Assumptions: - all routes are networks (no /128 v6 or /32 v4 - hosts get the first ip address in the network """ for v6route in self.v6_routes[self.mode]: host = "h{}".format(v6route['port']) dev = "{}-eth0".format(host) #net = ipaddress.ip_network(v6route['net']) #ipaddr = "{}/{}".format(net[1],net.prefix_length) net = self.prefix_to_net(v6route['net'], self.v6_mask) ipaddr = self.prefix_to_host(v6route['net'], self.v6_mask) self.add_host_ips(host, net, ipaddr, dev) # mx h$i "ip -6 route add default via 2001:db8:6::42" #for v4route in self.v4_routes[self.mode]: # Todo: add v4 routes / ip addresses # Fake mac addresses - this gets REALLY messy now # for host in range(4): # macrange = [1, 2, 3, 4, 42] # for mac in macrange: # subprocess.call(["mx", host, "ip", "addr", "add", ipaddr, "dev", dev]) def debug_print_pkg(self, pkg, msg="INCOMING"): #log.debug("{}: {}".format(msg, self.debug_format_pkg(pkg))) log.debug("{}: {}".format(msg, pkg.__repr__())) def debug_format_pkg(self, pkg): packet = Ether(str(pkg)) if packet.type == 0x800: ip = pkg.getlayer(IP) elif packet.type == 0x86dd: ip = pkg.getlayer(IPv6) # tcp = pkg.getlayer(TCP) # raw = pkg.getlayer(Raw) # return "{}:{} => {}:{}: flags={} seq={} ack={} raw={}".format( # ip.src, tcp.sport, # ip.dst, tcp.dport, # tcp.flags, # tcp.seq, # tcp.ack, # raw) def recv_msg_cpu(self, pkg): packet = Ether(str(pkg)) self.debug_print_pkg(pkg) if packet.type == 0x0800: pass elif packet.type == 0x86dd: pass elif packet.type == 0x4242: # CPU header included print("Special handling needed") pass else: print("Broken pkg: {}".format(pkg)) return def run_cpu_port_loop(self): sniff(iface=self.intf, prn=self.recv_msg_cpu) def commandline(self): parser = argparse.ArgumentParser(description='controller++') parser.add_argument('--mode', help='Select mode / settings to use', choices=self.modes) parser.add_argument('--debug', help='Enable debug logging', action='store_true') self.args = parser.parse_args() self.mode = self.args.mode if __name__ == "__main__": import sys import os if "DEBUG" in os.environ: log.setLevel(logging.DEBUG) else: log.setLevel(logging.INFO) log.info("Booting...") log.debug("Debug enabled.") sw_name = "s1" controller = L2Controller(sw_name) controller.commandline() if controller.args.debug: log.setLevel(logging.DEBUG) controller.config() controller.run_cpu_port_loop()