diff --git a/doc/plan.org b/doc/plan.org index 7ba0279..e7901ff 100644 --- a/doc/plan.org +++ b/doc/plan.org @@ -1432,7 +1432,6 @@ Please make sure that it is installed and available in your $PATH: steps - Longer than /96: suffix support - ** Motivation TBD ** Translation mechanisms @@ -1856,10 +1855,23 @@ libnanomsg-dev libjudy-dev ***** TODO Case IPv4 initiator - Needs upper level protol **** TODO General network matching -***** TODO Create table -***** TODO Fill it up from the controller -**** TODO tcp session +***** DONE Create table(s) +***** DONE Fill it up from the controller: general network +***** TODO Create controller session handler +****** Controller Logic + - controller selects "outgoing" IPv4 address range => base for sessions + - IPv4 addresses can be "random" (in our test case), but need + to be unique + - switch does not need to know about the "range", only about + sessions + - on session create, controller selects "random" ip (ring?) + - on session create, controller selects "random port" (next in range?) + - on session create controller adds choice into 2 tables: + incoming, outgoing +***** DONE Feed back to controller: implemented in switch +***** TODO Create session in the controller +**** TODO tcp session **** TODO udp session **** TODO tcp session ** TODO Comparison with existing tools (Performance, Features) diff --git a/p4app/controller.py b/p4app/controller.py index b09f256..3439e93 100755 --- a/p4app/controller.py +++ b/p4app/controller.py @@ -26,7 +26,8 @@ cpu_fields = { 0: 'UNSET', 1: 'ICMP6_NS', 2: 'ICMP6_GENERAL', - 3: 'DEBUG' + 3: 'DEBUG', + 8: 'NAT64_TCP_SESSION' } table_id_fields = { @@ -73,7 +74,7 @@ class CpuHeader(Packet): class L2Controller(object): def __init__(self, sw_name): # Command line mapping - self.modes = ['base', 'router', "range_router" ] + self.modes = ['base', 'router', "range_router", "session_router" ] # Reverse maps the cpu header self.task = dict(reversed(item) for item in cpu_fields.items()) @@ -112,11 +113,13 @@ class L2Controller(object): # /96 after the /40 pool we use above self.info['nat64_prefix_dynamic'] = ipaddress.ip_network("2001:db8:100::/96") + self.info['nat64_tcp_session'] = {} self.v6_routes = {} self.v6_routes[None] = [] self.v6_routes['base'] = [] + self.ports = [] for port in range(1,3): @@ -130,8 +133,10 @@ class L2Controller(object): self.v6_routes['router'] = self.v6_routes['base'] + # only 1 route to avoid table duplicate/conflict self.v6_routes['range_router'] = self.v6_routes['base'][0:1] + self.v6_routes['session_router'] = self.v6_routes['range_router'] self.v4_routes = {} self.v4_routes[None] = [] @@ -147,6 +152,7 @@ class L2Controller(object): self.v4_routes['router'] = self.v4_routes['base'] self.v4_routes['range_router'] = self.v4_routes['base'] + self.v4_routes['session_router'] = self.v4_routes['base'] self.v6_addresses = {} self.v6_addresses[None] = [] @@ -159,9 +165,12 @@ class L2Controller(object): self.v4_addresses[mode] = [ net['net'][self.info['switch_suffix']] for net in self.v4_routes[mode] ] self.nat64_map = {} + self.nat64_session_net = {} + # init default for mode in self.modes: self.nat64_map[mode] = [] + self.nat64_session_net[mode] = [] # specific settings - mapping 256 IPv6 IPs max statically (based on /24) for mode in ["range_router"]: @@ -183,6 +192,16 @@ class L2Controller(object): "v4_dst": v4_dst }) + # allow session translation + # TODO: maybe support multiple networks + for mode in ["session_router"]: + self.nat64_session_net[mode] = [ + { 'v4_net': self.info['v4_nat64_map'].next(), + 'v6_net': self.info['nat64_prefix_dynamic'], + 'v4_idx': 1 + } + ] + self.init_boilerplate(sw_name) self.init_other_port_multicast_groups() @@ -313,6 +332,23 @@ class L2Controller(object): for nat64map in self.nat64_map[self.mode]: self.static_nat64_mapping(**nat64map) + # NAT64 session based + self.controller.table_clear("nat64_session") + + # These will be only populated dynamically + self.controller.table_clear("nat64_tcp_session") + self.controller.table_clear("nat46_tcp_session") + + for net in self.nat64_session_net[mode]: + # Only for matching / if selecting + self.controller.table_add("nat64_session", "NoAction", + [ + net['v6_net'] + ] + ) + + + def static_nat64_mapping(self, v6_src, v6_dst, v4_src, v4_dst): """ Currently using destination only matching due to non priority @@ -475,6 +511,67 @@ class L2Controller(object): self.send_pkg(answer) + def nat64_tcp_session_entry(self, ipv6_src_addr, ipv6_dst_addr, + tcp_src_port, tcp_dst_port): + + return "{}:{} - {}:{}".format(ipv6_src_addr, tcp_src_port, + ipv6_dst_addr, tcp_dst_port) + + + def nat64_create_tcp_session(self, pkg): + """ + session: + hdr.ipv6.src_addr: exact; + hdr.ipv6.dst_addr: exact; + hdr.tcp.src_port: exact; + hdr.tcp.dst_port: exact; + """ + + + id = self.nat64_tcp_session_entry(pkg[IPv6].src, + pkg[TCP].src_port, + pkg[IPv6].dst, + pkg[TCP].dst_port) + + + # Has already been added? then it's a race condition and + # it needs to go back to the switch + + + # Not in the table? create an entry! + if not id in self.info['nat64_tcp_session']: + + # FIXME: Change randomly later, supporting 1:N mappings + # Keep the same for the moment + tcp_src_port = pkg[TCP].src_port + tcp_dst_port = pkg[TCP].dst_port + + # FIXME: range, reuse, etc. + ipv4_src_addr = self.nat64_session_net[self.mode] + self.nat64_session_net[self.mode] += 1 + + + self.controller.table_add("nat64_tcp_session", + "nat64_tcp_session_translate", + [ + pkg[IPv6].src, + pkg[TCP].src_port, + pkg[IPv6].dst, + pkg[TCP].dst_port + ], + [ + ipv4_src_addr, + tcp_src_port, + ipv4_dst_addr, + tcp_dst_port + ] + ) + + + self.send_pkg(pkg) + + + def send_pkg(self, pkg): self.debug_print_pkg(pkg, "OUTGOING") sendp(pkg, iface=self.intf, verbose=False) @@ -494,6 +591,7 @@ class L2Controller(object): ether_orig = Ether(src=packet.src, dst=packet.dst, type=cpu_header.type) + # Note to myself: this is actually broken for ARP if cpu_header.type == 0x0800: orig_packet = ether_orig / IP(cpu_header.load) elif cpu_header.type == 0x86dd: @@ -520,6 +618,8 @@ class L2Controller(object): elif cpu_header.task == self.task['ICMP6_GENERAL']: if ICMPv6EchoRequest in orig_packet: self.handle_icmp6_echo_request(orig_packet) + elif cpu_header.task == self.task['NAT64_TCP_SESSION']: + self.nat64_create_tcp_session(orig_packet) else: log.info("unhandled reassambled={} from table {}".format(orig_packet.__repr__(), table_id_fields[cpu_header.table_id])) diff --git a/p4src/nat64.p4 b/p4src/nat64.p4 index 530e3f2..79163e3 100644 --- a/p4src/nat64.p4 +++ b/p4src/nat64.p4 @@ -298,7 +298,6 @@ Echo or Echo Reply Message NoAction; } size = NAT64_TABLE_SIZE; - //default_action = controller_debug_table_id(TABLE_NAT64_TCP); default_action = nat64_tcp_session_create; }