1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3 4import socket 5import struct 6import time 7from lib.py import bkg, ip, ksft_exit, ksft_run, ksft_eq, ksft_ge, ksft_true, KsftSkipEx 8from lib.py import ksft_not_in, ksft_not_none 9from lib.py import CmdExitFailure, NetNS, NetNSEnter, RtnlAddrFamily, RtnlRouteFamily 10from lib.py import defer 11 12IPV4_ALL_HOSTS_MULTICAST = b'\xe0\x00\x00\x01' 13IPV4_TEST_MULTICAST = b'\xef\x01\x01\x01' 14IPV6_TEST_MULTICAST = bytes.fromhex('ff020000000000000000000000000123') 15 16 17def _users_for(rtnl: RtnlAddrFamily, family: int, grp: bytes, ifindex: int): 18 """Return mc-users for grp on ifindex, or 0 if absent.""" 19 20 addrs = rtnl.getmulticast({"ifa-family": family}, dump=True) 21 matches = [addr for addr in addrs 22 if addr['multicast'] == grp and addr['ifa-index'] == ifindex] 23 if not matches: 24 return 0 25 if 'mc-users' not in matches[0]: 26 return None 27 28 return matches[0]['mc-users'] 29 30 31def dump_mcaddr_check() -> None: 32 """ 33 Verify IPv4 multicast addresses and their user counts in RTM_GETMULTICAST. 34 """ 35 36 with NetNS() as ns: 37 with NetNSEnter(str(ns)): 38 ip("link set lo up") 39 rtnl = RtnlAddrFamily() 40 lo_idx = socket.if_nametoindex('lo') 41 addresses = rtnl.getmulticast({"ifa-family": socket.AF_INET}, dump=True) 42 43 all_host_multicasts = [ 44 addr for addr in addresses 45 if addr['multicast'] == IPV4_ALL_HOSTS_MULTICAST 46 ] 47 48 ksft_ge(len(all_host_multicasts), 1, 49 "No interface found with the IPv4 all-hosts multicast address") 50 51 mreq = IPV4_TEST_MULTICAST + socket.inet_aton('127.0.0.1') 52 before = _users_for(rtnl, socket.AF_INET, IPV4_TEST_MULTICAST, lo_idx) 53 if before is None: 54 raise KsftSkipEx("kernel does not expose IFA_MC_USERS") 55 56 s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 57 s2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 58 try: 59 s1.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) 60 s2.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) 61 62 after_join = _users_for(rtnl, socket.AF_INET, 63 IPV4_TEST_MULTICAST, lo_idx) 64 if after_join is None: 65 raise KsftSkipEx("kernel does not expose IFA_MC_USERS") 66 ksft_eq(after_join - before, 2, 67 f"users delta != 2 after two joins " 68 f"(before={before}, after={after_join})") 69 finally: 70 s1.close() 71 s2.close() 72 73 74def dump_mcaddr6_check() -> None: 75 """ 76 Verify IPv6 multicast addresses and their user counts in RTM_GETMULTICAST. 77 """ 78 79 with NetNS() as ns: 80 with NetNSEnter(str(ns)): 81 ip("link set lo up") 82 rtnl = RtnlAddrFamily() 83 lo_idx = socket.if_nametoindex('lo') 84 before = _users_for(rtnl, socket.AF_INET6, 85 IPV6_TEST_MULTICAST, lo_idx) 86 if before is None: 87 raise KsftSkipEx("kernel does not expose IFA_MC_USERS for IPv6") 88 89 mreq = IPV6_TEST_MULTICAST + struct.pack('=I', lo_idx) 90 s1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 91 s2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 92 try: 93 s1.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) 94 s2.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) 95 96 after_join = _users_for(rtnl, socket.AF_INET6, 97 IPV6_TEST_MULTICAST, lo_idx) 98 if after_join is None: 99 raise KsftSkipEx("kernel does not expose IFA_MC_USERS for IPv6") 100 ksft_eq(after_join - before, 2, 101 f"IPv6 users delta != 2 after two joins " 102 f"(before={before}, after={after_join})") 103 finally: 104 s1.close() 105 s2.close() 106 107 108def ipv4_devconf_notify() -> None: 109 """ 110 Configure an interface and set ipv4-devconf values through netlink 111 to verify that the appropriate netlink notifications are being sent. 112 """ 113 114 with NetNS() as ns: 115 with NetNSEnter(str(ns)): 116 ifname = "dummy1" 117 ip(f"link add name {ifname} type dummy", ns=str(ns)) 118 119 with bkg("ip monitor", ns=str(ns)) as cmd_obj: 120 time.sleep(1) 121 try: 122 ip(f"link set dev {ifname} inet forwarding on") 123 ip(f"link set dev {ifname} inet proxy_arp on") 124 ip(f"link set dev {ifname} inet rp_filter 1") 125 ip(f"link set dev {ifname} inet ignore_routes_with_linkdown on") 126 except CmdExitFailure: 127 raise KsftSkipEx("iproute2 does not support IPv4 devconf attributes") 128 time.sleep(1) 129 130 ksft_true(f"inet {ifname} ignore_routes_with_linkdown on" in cmd_obj.stdout, 131 f"No 'ignore_routes_with_linkdown on' notificiation found for interface {ifname}") 132 ksft_true(f"inet {ifname} rp_filter strict" in cmd_obj.stdout, 133 f"No 'rp_filter strict' notificiation found for interface {ifname}") 134 ksft_true(f"inet {ifname} proxy_neigh on" in cmd_obj.stdout, 135 f"No 'proxy_neigh on' notificiation found for interface {ifname}") 136 ksft_true(f"inet {ifname} forwarding on" in cmd_obj.stdout, 137 f"No 'forwarding on' notificiation found for interface {ifname}") 138 139def _rtnl_route_subscribe(ns): 140 with NetNSEnter(str(ns)): 141 rtnl = RtnlRouteFamily() 142 defer(rtnl.close) 143 rtnl.ntf_subscribe("rtnlgrp-ipv6-route") 144 return rtnl 145 146 147def _wait_route_ntf(rtnl, name, dst_len, dst=None, deadline=10): 148 """Return the attrs of the first matching notification, None on timeout.""" 149 150 for msg in rtnl.poll_ntf(duration=deadline): 151 if msg['name'] != name: 152 continue 153 attrs = msg['msg'] 154 if attrs['rtm-dst-len'] != dst_len: 155 continue 156 if dst is not None and attrs.get('dst') != dst: 157 continue 158 return attrs 159 return None 160 161 162def _collect_route_ntfs(rtnl, name, want, deadline=10): 163 """Gather attrs of matching notifications, keyed by (dst_len, dst).""" 164 165 seen = {} 166 for msg in rtnl.poll_ntf(duration=deadline): 167 if msg['name'] != name: 168 continue 169 attrs = msg['msg'] 170 key = (attrs['rtm-dst-len'], attrs.get('dst')) 171 if key in want: 172 seen[key] = attrs 173 if len(seen) == len(want): 174 break 175 return seen 176 177 178def _write_ipv6_sysctl(name, value): 179 with open(f"/proc/sys/net/ipv6/{name}", "w") as f: 180 f.write(f"{value}\n") 181 182 183def ipv6_route_del_reason_expired() -> None: 184 """An expired route reports RTA_DEL_REASON == expired.""" 185 186 with NetNS() as ns: 187 rtnl = _rtnl_route_subscribe(ns) 188 with NetNSEnter(str(ns)): 189 _write_ipv6_sysctl("route/gc_interval", 2) 190 ip("link add name dummy1 type dummy", ns=str(ns)) 191 ip("link set dev dummy1 up", ns=str(ns)) 192 ip("-6 route add 2001:db8:2::/64 dev dummy1 expires 2", ns=str(ns)) 193 194 attrs = _wait_route_ntf(rtnl, 'delroute-ntf', 64, '2001:db8:2::', 195 deadline=15) 196 ksft_not_none(attrs, "no RTM_DELROUTE for the expired route") 197 if attrs is not None: 198 ksft_eq(attrs.get('del-reason'), 'expired') 199 200 201def _send_ra(sock, ifindex, lifetime, rio=None, pio=None): 202 """The kernel fills in the ICMPv6 checksum on raw ICMPv6 sockets.""" 203 204 # type, code, cksum, hop limit, flags, router lifetime, 205 # reachable time, retrans timer 206 ra = struct.pack('!BBHBBHII', 134, 0, 0, 64, 0, lifetime, 0, 0) 207 if rio is not None: 208 prefix, plen, rio_lifetime = rio 209 # RFC 4191 route information option, /64 prefix (8 bytes) 210 ra += struct.pack('!BBBBI', 24, 2, plen, 0, rio_lifetime) 211 ra += socket.inet_pton(socket.AF_INET6, prefix)[:8] 212 if pio is not None: 213 prefix, plen, valid_lft = pio 214 # RFC 4861 prefix information option, on-link only (L set, A clear) 215 ra += struct.pack('!BBBBIII', 3, 4, plen, 0x80, valid_lft, 0, 0) 216 ra += socket.inet_pton(socket.AF_INET6, prefix) 217 sock.sendto(ra, ('ff02::1', 0, 0, ifindex)) 218 219 220def _ra_router_sock(ns_r, ifname): 221 with NetNSEnter(str(ns_r)): 222 sock = socket.socket(socket.AF_INET6, socket.SOCK_RAW, 223 socket.IPPROTO_ICMPV6) 224 sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 255) 225 defer(sock.close) 226 return sock, socket.if_nametoindex(ifname) 227 228 229def _ra_advertise_routes(rtnl, sock, ifindex, want, **ra_opts): 230 """ 231 Sending fails with EADDRNOTAVAIL while the router's link-local 232 address is still tentative. addrconf_dad_start() only queues 233 addrconf_dad_work(), and IFA_F_TENTATIVE is cleared when that work 234 item runs, so retry until it does. 235 """ 236 237 seen = {} 238 for _ in range(10): 239 try: 240 _send_ra(sock, ifindex, **ra_opts) 241 except OSError: 242 time.sleep(0.2) 243 continue 244 seen.update(_collect_route_ntfs(rtnl, 'newroute-ntf', 245 want - set(seen.keys()), deadline=2)) 246 if len(seen) == len(want): 247 break 248 return seen 249 250 251def ipv6_route_del_reason_ra_withdrawn() -> None: 252 """ 253 Routes withdrawn by a zero-lifetime RA (router lifetime, RFC 4861 254 PIO, RFC 4191 RIO) report RTA_DEL_REASON == ra-withdrawn. 255 """ 256 257 # (rtm-dst-len, dst); the default route carries no RTA_DST 258 routes = {(0, None), (64, '2001:db8:6::'), (64, '2001:db8:5::')} 259 260 with NetNS() as ns_h, NetNS() as ns_r: 261 ip(f"link add veth0 netns {ns_h} type veth peer name veth1 netns {ns_r}") 262 with NetNSEnter(str(ns_h)): 263 _write_ipv6_sysctl("conf/veth0/accept_ra", 2) 264 _write_ipv6_sysctl("conf/veth0/forwarding", 0) 265 try: 266 _write_ipv6_sysctl("conf/veth0/accept_ra_rt_info_max_plen", 64) 267 except FileNotFoundError: 268 raise KsftSkipEx("no CONFIG_IPV6_ROUTE_INFO") 269 with NetNSEnter(str(ns_r)): 270 # skip the DAD probe so the router's link-local source only 271 # has to wait for addrconf_dad_work() to clear IFA_F_TENTATIVE 272 _write_ipv6_sysctl("conf/veth1/accept_dad", 0) 273 ip("link set dev veth0 up", ns=str(ns_h)) 274 ip("link set dev veth1 up", ns=str(ns_r)) 275 276 rtnl = _rtnl_route_subscribe(ns_h) 277 sock, ifindex = _ra_router_sock(ns_r, "veth1") 278 279 seen = _ra_advertise_routes(rtnl, sock, ifindex, routes, 280 lifetime=1800, 281 rio=('2001:db8:5::', 64, 600), 282 pio=('2001:db8:6::', 64, 600)) 283 ksft_eq(set(seen), routes, "not all RA routes were installed") 284 if set(seen) != routes: 285 return 286 287 _send_ra(sock, ifindex, 0, rio=('2001:db8:5::', 64, 0), 288 pio=('2001:db8:6::', 64, 0)) 289 seen = _collect_route_ntfs(rtnl, 'delroute-ntf', routes) 290 for key in routes: 291 attrs = seen.get(key) 292 ksft_not_none(attrs, f"no RTM_DELROUTE for {key}") 293 if attrs is not None: 294 ksft_eq(attrs.get('del-reason'), 'ra-withdrawn') 295 296 297def ipv6_route_del_reason_absent() -> None: 298 """ 299 A deletion path that records no cause (here a userspace request) 300 must not carry RTA_DEL_REASON at all. 301 """ 302 303 with NetNS() as ns: 304 rtnl = _rtnl_route_subscribe(ns) 305 ip("link add name dummy1 type dummy", ns=str(ns)) 306 ip("link set dev dummy1 up", ns=str(ns)) 307 ip("-6 route add 2001:db8:1::/64 dev dummy1", ns=str(ns)) 308 ip("-6 route del 2001:db8:1::/64 dev dummy1", ns=str(ns)) 309 310 attrs = _wait_route_ntf(rtnl, 'delroute-ntf', 64, '2001:db8:1::') 311 ksft_not_none(attrs, "no RTM_DELROUTE for 2001:db8:1::/64") 312 if attrs is not None: 313 ksft_not_in('del-reason', attrs, 314 "user deletion must not carry del-reason") 315 316 317def main() -> None: 318 ksft_run([dump_mcaddr_check, dump_mcaddr6_check, ipv4_devconf_notify, 319 ipv6_route_del_reason_expired, 320 ipv6_route_del_reason_ra_withdrawn, 321 ipv6_route_del_reason_absent]) 322 ksft_exit() 323 324if __name__ == "__main__": 325 main() 326