Path: blob/main/tests/sys/netinet6/test_ip6_output.py
104924 views
import errno1import ipaddress2import socket3import struct4import time5from ctypes import c_byte6from ctypes import c_uint7from ctypes import Structure89import pytest10from atf_python.sys.net.rtsock import SaHelper11from atf_python.sys.net.tools import ToolsHelper12from atf_python.sys.net.vnet import SingleVnetTestTemplate13from atf_python.sys.net.vnet import VnetTestTemplate141516class In6Pktinfo(Structure):17_fields_ = [18("ipi6_addr", c_byte * 16),19("ipi6_ifindex", c_uint),20]212223class VerboseSocketServer:24def __init__(self, ip: str, port: int, ifname: str = None):25self.ip = ip26self.port = port2728s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)29s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_RECVPKTINFO, 1)30addr = ipaddress.ip_address(ip)31if addr.is_link_local and ifname:32ifindex = socket.if_nametoindex(ifname)33addr_tuple = (ip, port, 0, ifindex)34elif addr.is_multicast and ifname:35ifindex = socket.if_nametoindex(ifname)36mreq = socket.inet_pton(socket.AF_INET6, ip) + struct.pack("I", ifindex)37s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)38print("## JOINED group {} % {}".format(ip, ifname))39addr_tuple = ("::", port, 0, ifindex)40else:41addr_tuple = (ip, port, 0, 0)42print("## Listening on [{}]:{}".format(addr_tuple[0], port))43s.bind(addr_tuple)44self.socket = s4546def recv(self):47# data = self.socket.recv(4096)48# print("RX: " + data)49data, ancdata, msg_flags, address = self.socket.recvmsg(4096, 128)50# Assume ancdata has just 1 item51info = In6Pktinfo.from_buffer_copy(ancdata[0][2])52dst_ip = socket.inet_ntop(socket.AF_INET6, info.ipi6_addr)53dst_iface = socket.if_indextoname(info.ipi6_ifindex)5455tx_obj = {56"data": data,57"src_ip": address[0],58"dst_ip": dst_ip,59"dst_iface": dst_iface,60}61return tx_obj626364class BaseTestIP6Ouput(VnetTestTemplate):65TOPOLOGY = {66"vnet1": {"ifaces": ["if1", "if2", "if3"]},67"vnet2": {"ifaces": ["if1", "if2", "if3"]},68"if1": {"prefixes6": [("2001:db8:a::1/64", "2001:db8:a::2/64")]},69"if2": {"prefixes6": [("2001:db8:b::1/64", "2001:db8:b::2/64")]},70"if3": {"prefixes6": [("2001:db8:c::1/64", "2001:db8:c::2/64")]},71}72DEFAULT_PORT = 453657374def _vnet2_handler(self, vnet, ip: str, os_ifname: str = None):75"""Generic listener that sends first received packet with metadata76back to the sender via pipw77"""78ll_data = ToolsHelper.get_linklocals()79# Start listener80ss = VerboseSocketServer(ip, self.DEFAULT_PORT, os_ifname)81vnet.pipe.send(ll_data)8283tx_obj = ss.recv()84tx_obj["dst_iface_alias"] = vnet.iface_map[tx_obj["dst_iface"]].alias85vnet.pipe.send(tx_obj)868788class TestIP6Output(BaseTestIP6Ouput):89def vnet2_handler(self, vnet):90ip = str(vnet.iface_alias_map["if2"].first_ipv6.ip)91self._vnet2_handler(vnet, ip, None)9293@pytest.mark.require_user("root")94def test_output6_base(self):95"""Tests simple UDP output"""96second_vnet = self.vnet_map["vnet2"]9798# Pick target on if2 vnet2's end99ifaddr = ipaddress.ip_interface(self.TOPOLOGY["if2"]["prefixes6"][0][1])100ip = str(ifaddr.ip)101102s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)103data = bytes("AAAA", "utf-8")104print("## TX packet to {},{}".format(ip, self.DEFAULT_PORT))105106# Wait for the child to become ready107self.wait_object(second_vnet.pipe)108s.sendto(data, (ip, self.DEFAULT_PORT))109110# Wait for the received object111rx_obj = self.wait_object(second_vnet.pipe)112assert rx_obj["dst_ip"] == ip113assert rx_obj["dst_iface_alias"] == "if2"114115@pytest.mark.require_user("root")116def test_output6_nhop(self):117"""Tests UDP output with custom nhop set"""118second_vnet = self.vnet_map["vnet2"]119120# Pick target on if2 vnet2's end121ifaddr = ipaddress.ip_interface(self.TOPOLOGY["if2"]["prefixes6"][0][1])122ip_dst = str(ifaddr.ip)123# Pick nexthop on if1124ifaddr = ipaddress.ip_interface(self.TOPOLOGY["if1"]["prefixes6"][0][1])125ip_next = str(ifaddr.ip)126sin6_next = SaHelper.ip6_sa(ip_next, 0)127128s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, 0)129s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_NEXTHOP, sin6_next)130131# Wait for the child to become ready132self.wait_object(second_vnet.pipe)133data = bytes("AAAA", "utf-8")134s.sendto(data, (ip_dst, self.DEFAULT_PORT))135136# Wait for the received object137rx_obj = self.wait_object(second_vnet.pipe)138assert rx_obj["dst_ip"] == ip_dst139assert rx_obj["dst_iface_alias"] == "if1"140141@pytest.mark.parametrize(142"params",143[144# esrc: src-ip, if: src-interface, esrc: expected-src,145# eif: expected-rx-interface146pytest.param({"esrc": "2001:db8:b::1", "eif": "if2"}, id="empty"),147pytest.param(148{"src": "2001:db8:c::1", "esrc": "2001:db8:c::1", "eif": "if2"},149id="iponly1",150),151pytest.param(152{153"src": "2001:db8:c::1",154"if": "if3",155"ex": errno.EHOSTUNREACH,156},157id="ipandif",158),159pytest.param(160{161"src": "2001:db8:c::aaaa",162"ex": errno.EADDRNOTAVAIL,163},164id="nolocalip",165),166pytest.param(167{"if": "if2", "src": "2001:db8:b::1", "eif": "if2"}, id="ifsame"168),169],170)171@pytest.mark.require_user("root")172def test_output6_pktinfo(self, params):173"""Tests simple UDP output"""174second_vnet = self.vnet_map["vnet2"]175vnet = self.vnet176177# Pick target on if2 vnet2's end178ifaddr = ipaddress.ip_interface(self.TOPOLOGY["if2"]["prefixes6"][0][1])179dst_ip = str(ifaddr.ip)180181src_ip = params.get("src", "")182src_ifname = params.get("if", "")183expected_ip = params.get("esrc", "")184expected_ifname = params.get("eif", "")185errno = params.get("ex", 0)186187pktinfo = In6Pktinfo()188if src_ip:189for i, b in enumerate(socket.inet_pton(socket.AF_INET6, src_ip)):190pktinfo.ipi6_addr[i] = b191if src_ifname:192os_ifname = vnet.iface_alias_map[src_ifname].name193pktinfo.ipi6_ifindex = socket.if_nametoindex(os_ifname)194195# Wait for the child to become ready196self.wait_object(second_vnet.pipe)197data = bytes("AAAA", "utf-8")198199s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, 0)200try:201s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_PKTINFO, bytes(pktinfo))202aux = (socket.IPPROTO_IPV6, socket.IPV6_PKTINFO, bytes(pktinfo))203s.sendto(data, (dst_ip, self.DEFAULT_PORT))204except OSError as e:205if not errno:206raise207assert e.errno == errno208print("Correctly raised {}".format(e))209return210211# Wait for the received object212rx_obj = self.wait_object(second_vnet.pipe)213214assert rx_obj["dst_ip"] == dst_ip215if expected_ip:216assert rx_obj["src_ip"] == expected_ip217if expected_ifname:218assert rx_obj["dst_iface_alias"] == expected_ifname219220221class TestIP6OutputLL(BaseTestIP6Ouput):222def vnet2_handler(self, vnet):223"""Generic listener that sends first received packet with metadata224back to the sender via pipw225"""226os_ifname = vnet.iface_alias_map["if2"].name227ll_data = ToolsHelper.get_linklocals()228ll_ip, _ = ll_data[os_ifname][0]229self._vnet2_handler(vnet, ll_ip, os_ifname)230231@pytest.mark.require_user("root")232def test_output6_linklocal(self):233"""Tests simple UDP output"""234second_vnet = self.vnet_map["vnet2"]235236# Wait for the child to become ready237ll_data = self.wait_object(second_vnet.pipe)238239# Pick LL address on if2 vnet2's end240ip, _ = ll_data[second_vnet.iface_alias_map["if2"].name][0]241# Get local interface scope242os_ifname = self.vnet.iface_alias_map["if2"].name243scopeid = socket.if_nametoindex(os_ifname)244245s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)246data = bytes("AAAA", "utf-8")247target = (ip, self.DEFAULT_PORT, 0, scopeid)248print("## TX packet to {}%{},{}".format(ip, scopeid, target[1]))249250s.sendto(data, target)251252# Wait for the received object253rx_obj = self.wait_object(second_vnet.pipe)254assert rx_obj["dst_ip"] == ip255assert rx_obj["dst_iface_alias"] == "if2"256257258class TestIP6OutputNhopLL(BaseTestIP6Ouput):259def vnet2_handler(self, vnet):260"""Generic listener that sends first received packet with metadata261back to the sender via pipw262"""263ip = str(vnet.iface_alias_map["if2"].first_ipv6.ip)264self._vnet2_handler(vnet, ip, None)265266@pytest.mark.require_user("root")267def test_output6_nhop_linklocal(self):268"""Tests UDP output with custom link-local nhop set"""269second_vnet = self.vnet_map["vnet2"]270271# Wait for the child to become ready272ll_data = self.wait_object(second_vnet.pipe)273274# Pick target on if2 vnet2's end275ifaddr = ipaddress.ip_interface(self.TOPOLOGY["if2"]["prefixes6"][0][1])276ip_dst = str(ifaddr.ip)277# Pick nexthop on if1278ip_next, _ = ll_data[second_vnet.iface_alias_map["if1"].name][0]279# Get local interfaces280os_ifname = self.vnet.iface_alias_map["if1"].name281scopeid = socket.if_nametoindex(os_ifname)282sin6_next = SaHelper.ip6_sa(ip_next, scopeid)283284s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, 0)285s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_NEXTHOP, sin6_next)286287data = bytes("AAAA", "utf-8")288s.sendto(data, (ip_dst, self.DEFAULT_PORT))289290# Wait for the received object291rx_obj = self.wait_object(second_vnet.pipe)292assert rx_obj["dst_ip"] == ip_dst293assert rx_obj["dst_iface_alias"] == "if1"294295296class TestIP6OutputScope(BaseTestIP6Ouput):297def vnet2_handler(self, vnet):298"""Generic listener that sends first received packet with metadata299back to the sender via pipw300"""301bind_ip, bind_ifp = self.wait_object(vnet.pipe)302if bind_ip is None:303os_ifname = vnet.iface_alias_map[bind_ifp].name304ll_data = ToolsHelper.get_linklocals()305bind_ip, _ = ll_data[os_ifname][0]306if bind_ifp is not None:307bind_ifp = vnet.iface_alias_map[bind_ifp].name308print("## BIND {}%{}".format(bind_ip, bind_ifp))309self._vnet2_handler(vnet, bind_ip, bind_ifp)310311@pytest.mark.parametrize(312"params",313[314# sif/dif: source/destination interface (for link-local addr)315# sip/dip: source/destination ip (for non-LL addr)316# ex: OSError errno that sendto() must raise317pytest.param({"sif": "if2", "dif": "if2"}, id="same"),318pytest.param(319{320"sif": "if1",321"dif": "if2",322"ex": errno.EHOSTUNREACH,323},324id="ll_differentif1",325),326pytest.param(327{328"sif": "if1",329"dip": "2001:db8:b::2",330"ex": errno.EHOSTUNREACH,331},332id="ll_differentif2",333),334pytest.param(335{336"sip": "2001:db8:a::1",337"dif": "if2",338},339id="gu_to_ll",340),341],342)343@pytest.mark.require_user("root")344def test_output6_linklocal_scope(self, params):345"""Tests simple UDP output"""346second_vnet = self.vnet_map["vnet2"]347348src_ifp = params.get("sif")349src_ip = params.get("sip")350dst_ifp = params.get("dif")351dst_ip = params.get("dip")352errno = params.get("ex", 0)353354# Sent ifp/IP to bind on355second_vnet = self.vnet_map["vnet2"]356second_vnet.pipe.send((dst_ip, dst_ifp))357358# Wait for the child to become ready359ll_data = self.wait_object(second_vnet.pipe)360361if dst_ip is None:362# Pick LL address on dst_ifp vnet2's end363dst_ip, _ = ll_data[second_vnet.iface_alias_map[dst_ifp].name][0]364# Get local interface scope365os_ifname = self.vnet.iface_alias_map[dst_ifp].name366scopeid = socket.if_nametoindex(os_ifname)367target = (dst_ip, self.DEFAULT_PORT, 0, scopeid)368else:369target = (dst_ip, self.DEFAULT_PORT, 0, 0)370371# Bind372if src_ip is None:373ll_data = ToolsHelper.get_linklocals()374os_ifname = self.vnet.iface_alias_map[src_ifp].name375src_ip, _ = ll_data[os_ifname][0]376scopeid = socket.if_nametoindex(os_ifname)377src = (src_ip, self.DEFAULT_PORT, 0, scopeid)378else:379src = (src_ip, self.DEFAULT_PORT, 0, 0)380381s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)382s.bind(src)383data = bytes("AAAA", "utf-8")384print("## TX packet {} -> {}".format(src, target))385386try:387s.sendto(data, target)388except OSError as e:389if not errno:390raise391assert e.errno == errno392print("Correctly raised {}".format(e))393return394395# Wait for the received object396rx_obj = self.wait_object(second_vnet.pipe)397assert rx_obj["dst_ip"] == dst_ip398assert rx_obj["src_ip"] == src_ip399# assert rx_obj["dst_iface_alias"] == "if2"400401402class TestIP6OutputMulticast(BaseTestIP6Ouput):403def vnet2_handler(self, vnet):404group = self.wait_object(vnet.pipe)405os_ifname = vnet.iface_alias_map["if2"].name406self._vnet2_handler(vnet, group, os_ifname)407408@pytest.mark.parametrize("group_scope", ["ff02", "ff05", "ff08", "ff0e"])409@pytest.mark.require_user("root")410def test_output6_multicast(self, group_scope):411"""Tests simple UDP output"""412second_vnet = self.vnet_map["vnet2"]413414group = "{}::3456".format(group_scope)415second_vnet.pipe.send(group)416417# Pick target on if2 vnet2's end418ip = group419os_ifname = self.vnet.iface_alias_map["if2"].name420ifindex = socket.if_nametoindex(os_ifname)421optval = struct.pack("I", ifindex)422423s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)424s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, optval)425426data = bytes("AAAA", "utf-8")427428# Wait for the child to become ready429self.wait_object(second_vnet.pipe)430431print("## TX packet to {},{}".format(ip, self.DEFAULT_PORT))432s.sendto(data, (ip, self.DEFAULT_PORT))433434# Wait for the received object435rx_obj = self.wait_object(second_vnet.pipe)436assert rx_obj["dst_ip"] == ip437assert rx_obj["dst_iface_alias"] == "if2"438439440class TestIP6OutputLoopback(SingleVnetTestTemplate):441IPV6_PREFIXES = ["2001:db8:a::1/64"]442DEFAULT_PORT = 45365443444@pytest.mark.parametrize(445"source_validation",446[447pytest.param(0, id="no_sav"),448pytest.param(1, id="sav"),449],450)451@pytest.mark.parametrize("scope", ["gu", "ll", "lo"])452def test_output6_self_tcp(self, scope, source_validation):453"""Tests IPv6 TCP connection to the local IPv6 address"""454455ToolsHelper.set_sysctl(456"net.inet6.ip6.source_address_validation", source_validation457)458459if scope == "gu":460ip = "2001:db8:a::1"461addr_tuple = (ip, self.DEFAULT_PORT)462elif scope == "ll":463os_ifname = self.vnet.iface_alias_map["if1"].name464ifindex = socket.if_nametoindex(os_ifname)465ll_data = ToolsHelper.get_linklocals()466ip, _ = ll_data[os_ifname][0]467addr_tuple = (ip, self.DEFAULT_PORT, 0, ifindex)468elif scope == "lo":469ip = "::1"470ToolsHelper.get_output("route add -6 ::1/128 -iface lo0")471ifindex = socket.if_nametoindex("lo0")472addr_tuple = (ip, self.DEFAULT_PORT)473else:474assert 0 == 1475print("address: {}".format(addr_tuple))476477start = time.perf_counter()478ss = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP)479ss.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_RECVPKTINFO, 1)480ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)481ss.bind(addr_tuple)482ss.listen()483s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP)484s.settimeout(2.0)485s.connect(addr_tuple)486conn, from_addr = ss.accept()487duration = time.perf_counter() - start488489assert from_addr[0] == ip490assert duration < 1.0491492@pytest.mark.parametrize(493"source_validation",494[495pytest.param(0, id="no_sav"),496pytest.param(1, id="sav"),497],498)499@pytest.mark.parametrize("scope", ["gu", "ll", "lo"])500def test_output6_self_udp(self, scope, source_validation):501"""Tests IPv6 UDP connection to the local IPv6 address"""502503ToolsHelper.set_sysctl(504"net.inet6.ip6.source_address_validation", source_validation505)506507if scope == "gu":508ip = "2001:db8:a::1"509addr_tuple = (ip, self.DEFAULT_PORT)510elif scope == "ll":511os_ifname = self.vnet.iface_alias_map["if1"].name512ifindex = socket.if_nametoindex(os_ifname)513ll_data = ToolsHelper.get_linklocals()514ip, _ = ll_data[os_ifname][0]515addr_tuple = (ip, self.DEFAULT_PORT, 0, ifindex)516elif scope == "lo":517ip = "::1"518ToolsHelper.get_output("route add -6 ::1/128 -iface lo0")519ifindex = socket.if_nametoindex("lo0")520addr_tuple = (ip, self.DEFAULT_PORT)521else:522assert 0 == 1523print("address: {}".format(addr_tuple))524525start = time.perf_counter()526ss = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP)527ss.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_RECVPKTINFO, 1)528ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)529ss.bind(addr_tuple)530ss.listen()531s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP)532s.settimeout(2.0)533s.connect(addr_tuple)534conn, from_addr = ss.accept()535duration = time.perf_counter() - start536537assert from_addr[0] == ip538assert duration < 1.0539540541