2019-03-03 21:36:25 +00:00
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
2019-02-21 22:38:09 +00:00
|
|
|
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
|
2019-03-04 17:08:10 +00:00
|
|
|
from scapy.all import IP, Raw, IPv6, TCP, TCP_client, Ether
|
|
|
|
from scapy.all import sniff
|
2019-03-04 17:16:18 +00:00
|
|
|
from scapy.all import Packet, BitField, IntEnumField, ShortField, XShortEnumField, ShortEnumField
|
2019-03-04 17:31:20 +00:00
|
|
|
from scapy.all import ICMPv6ND_NS, ICMPv6ND_RS
|
2019-03-04 17:05:56 +00:00
|
|
|
from scapy.data import ETHER_TYPES
|
2019-02-21 22:38:09 +00:00
|
|
|
|
|
|
|
import sys
|
|
|
|
import re
|
|
|
|
|
|
|
|
import logging
|
2019-02-23 13:22:46 +00:00
|
|
|
import argparse
|
2019-02-23 14:13:47 +00:00
|
|
|
import subprocess
|
|
|
|
|
2019-03-03 21:36:25 +00:00
|
|
|
import ipaddress
|
2019-02-23 17:14:34 +00:00
|
|
|
|
2019-02-21 22:38:09 +00:00
|
|
|
logging.basicConfig()
|
|
|
|
log = logging.getLogger("main")
|
|
|
|
|
2019-03-03 21:36:25 +00:00
|
|
|
|
2019-03-04 16:50:38 +00:00
|
|
|
cpu_fields = {
|
|
|
|
1: 'ICMP6_NS',
|
|
|
|
2: 'ICMP6_GENERAL',
|
|
|
|
3: 'DEBUG'
|
|
|
|
}
|
|
|
|
|
2019-03-03 21:36:25 +00:00
|
|
|
|
|
|
|
class CpuHeader(Packet):
|
|
|
|
name = 'CpuPacket'
|
2019-03-04 15:23:28 +00:00
|
|
|
fields_desc = [
|
2019-03-04 17:16:18 +00:00
|
|
|
ShortEnumField('task', 1, cpu_fields ),
|
2019-03-04 17:05:56 +00:00
|
|
|
ShortField('ingress_port', 0),
|
|
|
|
XShortEnumField("type", 0x9000, ETHER_TYPES)
|
2019-03-04 15:23:28 +00:00
|
|
|
]
|
2019-03-03 21:36:25 +00:00
|
|
|
|
|
|
|
|
2019-02-21 22:38:09 +00:00
|
|
|
class L2Controller(object):
|
|
|
|
def __init__(self, sw_name):
|
2019-02-23 13:36:19 +00:00
|
|
|
# Command line mapping
|
2019-02-23 17:58:04 +00:00
|
|
|
self.modes = ['base', 'router']
|
|
|
|
|
2019-03-04 16:50:38 +00:00
|
|
|
# Reverse maps the cpu header
|
|
|
|
self.task = dict(reversed(item) for item in cpu_fields.items())
|
2019-03-04 15:23:28 +00:00
|
|
|
|
2019-03-03 21:36:25 +00:00
|
|
|
self.info={}
|
2019-03-04 14:30:38 +00:00
|
|
|
self.info['ndp_multicast'] = ipaddress.ip_network("ff02::1:ff00:0/104")
|
2019-03-03 21:36:25 +00:00
|
|
|
|
2019-03-04 14:32:27 +00:00
|
|
|
self.info['v6_mask'] = 64
|
2019-03-04 14:30:38 +00:00
|
|
|
self.info['v6_base'] = ipaddress.ip_network("2001:db8::/32")
|
|
|
|
self.info['v6_gen'] = self.info['v6_base'].subnets(new_prefix=self.info['v6_mask'])
|
2019-02-23 13:22:46 +00:00
|
|
|
|
2019-03-04 14:32:27 +00:00
|
|
|
self.info['v4_mask'] = 24
|
2019-03-04 14:30:38 +00:00
|
|
|
self.info['v4_base'] = ipaddress.ip_network("10.0.0.0/8")
|
|
|
|
self.info['v4_gen'] = self.info['v4_base'].subnets(new_prefix=self.info['v4_mask'])
|
|
|
|
|
2019-03-04 18:06:09 +00:00
|
|
|
self.info['switch_suffix'] = 0x42
|
2019-02-23 17:14:34 +00:00
|
|
|
|
2019-02-23 13:22:46 +00:00
|
|
|
self.v6_routes = {}
|
2019-02-23 14:32:03 +00:00
|
|
|
self.v6_routes[None] = []
|
2019-02-23 13:22:46 +00:00
|
|
|
self.v6_routes['base'] = []
|
2019-03-04 14:30:38 +00:00
|
|
|
|
|
|
|
for port in range(1,3):
|
|
|
|
net = self.info['v6_gen'].next()
|
|
|
|
self.v6_routes['base'].append({
|
|
|
|
"net": net,
|
|
|
|
"port": port}
|
|
|
|
)
|
|
|
|
|
2019-02-23 17:58:04 +00:00
|
|
|
self.v6_routes['router'] = self.v6_routes['base']
|
2019-02-23 13:36:19 +00:00
|
|
|
|
2019-02-23 13:22:46 +00:00
|
|
|
self.v4_routes = {}
|
2019-02-23 14:32:03 +00:00
|
|
|
self.v4_routes[None] = []
|
2019-02-23 13:22:46 +00:00
|
|
|
self.v4_routes['base'] = []
|
2019-03-04 14:30:38 +00:00
|
|
|
for port in range(3,5):
|
|
|
|
net = self.info['v4_gen'].next()
|
|
|
|
self.v4_routes['base'].append({
|
|
|
|
"net": net,
|
|
|
|
"port": port}
|
|
|
|
)
|
2019-02-23 17:58:04 +00:00
|
|
|
self.v4_routes['router'] = self.v4_routes['base']
|
|
|
|
|
|
|
|
self.v6_addresses = {}
|
|
|
|
self.v6_addresses[None] = []
|
|
|
|
self.v6_addresses['base'] = []
|
2019-03-04 14:30:38 +00:00
|
|
|
self.v6_addresses['router'] = [ net['net'][42] for net in self.v6_routes['router'] ]
|
2019-02-23 17:58:04 +00:00
|
|
|
|
|
|
|
self.v4_addresses = {}
|
|
|
|
self.v4_addresses[None] = []
|
|
|
|
self.v4_addresses['base'] = []
|
2019-03-04 14:30:38 +00:00
|
|
|
self.v4_addresses['router'] = [ net['net'][42] for net in self.v4_routes['router'] ]
|
2019-02-23 13:22:46 +00:00
|
|
|
|
2019-02-23 13:36:19 +00:00
|
|
|
self.init_boilerplate(sw_name)
|
|
|
|
|
2019-03-04 13:48:15 +00:00
|
|
|
def gen_ndp_multicast_addr(self, addr):
|
|
|
|
""" append the 24 bit of the address to the multicast address"""
|
2019-03-04 14:30:38 +00:00
|
|
|
|
|
|
|
last_24 = int(addr) & 0xffffff
|
|
|
|
addr = self.info['ndp_multicast'][last_24]
|
|
|
|
|
|
|
|
return addr
|
2019-03-04 13:48:15 +00:00
|
|
|
|
2019-02-26 14:08:53 +00:00
|
|
|
def init_ndp(self):
|
|
|
|
""" initialise neighbor discovery protocol"""
|
|
|
|
|
2019-02-26 14:30:47 +00:00
|
|
|
# https://en.wikipedia.org/wiki/Solicited-node_multicast_address
|
|
|
|
ndp_prefix = "ff02::1:ff00:0/104"
|
|
|
|
|
2019-02-26 14:08:53 +00:00
|
|
|
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)
|
|
|
|
|
2019-02-26 14:30:47 +00:00
|
|
|
|
|
|
|
self.controller.table_clear("ndp")
|
2019-02-26 14:49:47 +00:00
|
|
|
for port in all_ports:
|
|
|
|
self.controller.table_add("ndp", "multicast_pkg", [ndp_prefix, str(port)], [str(port)])
|
2019-02-26 14:30:47 +00:00
|
|
|
|
|
|
|
|
2019-02-28 09:56:22 +00:00
|
|
|
# Special rule for switch entries
|
|
|
|
self.controller.table_add("ndp_answer", "icmp6_neighbor_solicitation", ["ff02::1:ff00:42", "135"], ["2001:db8:61::42"])
|
|
|
|
|
2019-02-21 22:38:09 +00:00
|
|
|
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)
|
|
|
|
|
2019-03-03 21:37:58 +00:00
|
|
|
# self.init_ndp()
|
2019-02-26 14:08:53 +00:00
|
|
|
|
2019-02-23 13:39:26 +00:00
|
|
|
def config(self):
|
|
|
|
self.fill_tables()
|
|
|
|
self.config_hosts()
|
|
|
|
|
2019-03-04 13:48:15 +00:00
|
|
|
def listen_to_icmp6_multicast(self):
|
|
|
|
"""Only needed for debugging"""
|
|
|
|
|
2019-03-04 14:30:38 +00:00
|
|
|
net = self.info['ndp_multicast']
|
|
|
|
self.controller.table_add("v6_networks", "controller_debug", [str(net)])
|
2019-02-23 17:58:04 +00:00
|
|
|
|
2019-02-21 22:38:09 +00:00
|
|
|
def fill_tables(self):
|
2019-03-04 13:07:05 +00:00
|
|
|
self.controller.table_clear("v6_networks")
|
2019-02-23 13:22:46 +00:00
|
|
|
for v6route in self.v6_routes[self.mode]:
|
2019-03-04 14:30:38 +00:00
|
|
|
self.controller.table_add("v6_networks", "set_egress_port", [str(v6route['net'])], [str(v6route['port'])])
|
2019-03-04 13:07:05 +00:00
|
|
|
|
2019-03-04 13:48:15 +00:00
|
|
|
self.listen_to_icmp6_multicast()
|
2019-02-23 13:22:46 +00:00
|
|
|
|
2019-02-23 13:49:18 +00:00
|
|
|
self.controller.table_clear("v4_routing")
|
2019-02-23 13:22:46 +00:00
|
|
|
for v4route in self.v4_routes[self.mode]:
|
2019-03-04 14:30:38 +00:00
|
|
|
self.controller.table_add("v4_networks", "set_egress_port", [str(v4route['net'])], [str(v4route['port'])])
|
2019-02-23 13:22:46 +00:00
|
|
|
|
2019-02-23 17:58:04 +00:00
|
|
|
self.controller.table_clear("v6_addresses")
|
|
|
|
for v6addr in self.v6_addresses[self.mode]:
|
2019-03-04 14:30:38 +00:00
|
|
|
icmp6_addr = self.gen_ndp_multicast_addr(v6addr)
|
2019-02-23 17:58:04 +00:00
|
|
|
|
2019-03-04 18:02:28 +00:00
|
|
|
self.controller.table_add("v6_addresses", "controller_reply", [str(v6addr)], [str(self.task['ICMP6_GENERAL'])])
|
|
|
|
self.controller.table_add("v6_addresses", "controller_reply", [str(icmp6_addr)], [str(self.task['ICMP6_NS'])])
|
2019-02-23 17:58:04 +00:00
|
|
|
|
2019-02-23 13:22:46 +00:00
|
|
|
def config_hosts(self):
|
|
|
|
""" Assumptions:
|
|
|
|
- all routes are networks (no /128 v6 or /32 v4
|
|
|
|
- hosts get the first ip address in the network
|
|
|
|
"""
|
2019-02-23 14:13:47 +00:00
|
|
|
|
2019-02-23 13:49:18 +00:00
|
|
|
for v6route in self.v6_routes[self.mode]:
|
2019-02-23 14:13:47 +00:00
|
|
|
host = "h{}".format(v6route['port'])
|
|
|
|
dev = "{}-eth0".format(host)
|
2019-03-04 14:38:16 +00:00
|
|
|
net = v6route['net']
|
2019-03-04 14:38:42 +00:00
|
|
|
ipaddr = "{}/{}".format(net[1],net.prefixlen)
|
2019-02-23 13:49:18 +00:00
|
|
|
|
2019-03-04 14:34:12 +00:00
|
|
|
self.add_host_ips(host, str(net), str(ipaddr), dev)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def add_host_ips(host, net, ipaddr, dev):
|
2019-03-04 14:34:46 +00:00
|
|
|
log.debug("Config host: {} {}->{} on {}".format(host, net, ipaddr, dev))
|
2019-03-04 14:34:12 +00:00
|
|
|
|
|
|
|
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])
|
2019-02-23 14:13:47 +00:00
|
|
|
|
2019-02-21 22:38:09 +00:00
|
|
|
def debug_print_pkg(self, pkg, msg="INCOMING"):
|
2019-02-24 14:58:15 +00:00
|
|
|
log.debug("{}: {}".format(msg, pkg.__repr__()))
|
2019-02-21 22:38:09 +00:00
|
|
|
|
|
|
|
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))
|
|
|
|
|
2019-02-24 14:54:32 +00:00
|
|
|
self.debug_print_pkg(pkg)
|
2019-03-04 16:50:38 +00:00
|
|
|
print("p={}".format(pkg.__repr__()))
|
2019-02-24 14:53:10 +00:00
|
|
|
|
2019-03-03 21:36:25 +00:00
|
|
|
if packet.type == 0x0800:
|
2019-02-21 22:38:09 +00:00
|
|
|
pass
|
|
|
|
elif packet.type == 0x86dd:
|
|
|
|
pass
|
2019-03-03 21:36:25 +00:00
|
|
|
elif packet.type == 0x4242:
|
2019-03-04 15:38:06 +00:00
|
|
|
cpu_header = CpuHeader(packet.payload)
|
2019-03-04 17:39:09 +00:00
|
|
|
log.debug("cpu = {}".format(cpu_header.__repr__()))
|
2019-03-04 16:50:38 +00:00
|
|
|
|
2019-03-04 17:55:25 +00:00
|
|
|
ether_orig = Ether(src=packet.src, dst=packet.dst, type=cpu_header.type)
|
|
|
|
orig_packet = ether_orig / IPv6(cpu_header.load)
|
2019-03-04 17:58:22 +00:00
|
|
|
log.debug("o={}".format(orig_packet.__repr__()))
|
2019-03-04 17:55:25 +00:00
|
|
|
|
2019-03-04 17:58:22 +00:00
|
|
|
if cpu_header.task == self.task['DEBUG']:
|
2019-03-05 15:14:36 +00:00
|
|
|
log.debug("Debug purpose only")
|
2019-03-04 17:58:22 +00:00
|
|
|
elif cpu_header.task == self.task['ICMP6_NS']:
|
|
|
|
log.debug("Doing neighbor solicitation")
|
|
|
|
elif cpu_header.task == self.task['ICMP6_GENERAL']:
|
|
|
|
log.debug("Replying to ICMP packet")
|
2019-03-04 17:55:25 +00:00
|
|
|
|
2019-03-04 16:50:38 +00:00
|
|
|
|
2019-02-21 22:38:09 +00:00
|
|
|
else:
|
2019-03-04 13:24:02 +00:00
|
|
|
print("Broken pkg: {}".format(pkg.__repr__()))
|
2019-02-21 22:38:09 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
def run_cpu_port_loop(self):
|
|
|
|
sniff(iface=self.intf, prn=self.recv_msg_cpu)
|
|
|
|
|
2019-02-23 13:22:46 +00:00
|
|
|
def commandline(self):
|
|
|
|
parser = argparse.ArgumentParser(description='controller++')
|
|
|
|
parser.add_argument('--mode', help='Select mode / settings to use', choices=self.modes)
|
2019-02-23 17:25:53 +00:00
|
|
|
parser.add_argument('--debug', help='Enable debug logging', action='store_true')
|
2019-02-23 17:26:34 +00:00
|
|
|
|
|
|
|
self.args = parser.parse_args()
|
2019-02-23 17:27:09 +00:00
|
|
|
self.mode = self.args.mode
|
2019-03-04 13:57:27 +00:00
|
|
|
self.debug = self.args.debug
|
2019-02-23 13:22:46 +00:00
|
|
|
|
2019-02-21 22:38:09 +00:00
|
|
|
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"
|
2019-02-23 13:22:46 +00:00
|
|
|
controller = L2Controller(sw_name)
|
|
|
|
|
|
|
|
controller.commandline()
|
2019-02-23 17:25:53 +00:00
|
|
|
if controller.args.debug:
|
|
|
|
log.setLevel(logging.DEBUG)
|
2019-02-23 13:39:26 +00:00
|
|
|
controller.config()
|
2019-02-23 13:22:46 +00:00
|
|
|
controller.run_cpu_port_loop()
|