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 _insert_and_get_addrs_ipv4(test_addrs: list[str], scopes: list[str]) -> list[str]: 318 with NetNS() as ns, NetNSEnter(str(ns)): 319 dev_name = "dummy_dev" 320 321 ip(f"link add name {dev_name} type dummy", ns=str(ns)) 322 for test_addr, scope in zip(test_addrs, scopes): 323 ip(f"address add {test_addr}/24 dev {dev_name} scope {scope}", ns=str(ns)) 324 325 rtnl = RtnlAddrFamily() 326 addrs = rtnl.getaddr({"ifa-family": socket.AF_INET}, dump=True) 327 return [addr["address"] for addr in addrs] 328 329 330def ipv4_verify_same_scope_addr_order() -> None: 331 """ 332 After inserting multiple same scope IPv4 addresses, their order 333 must be the same as the insertion order. The only aspect affecting 334 this are primary addresses, which precede secondary ones. 335 """ 336 337 primary_first = ["192.0.2.1", "203.0.113.1", "192.0.2.2", "203.0.113.2"] 338 scopes = ["global"] * 4 339 expected_result = primary_first 340 resulting_list = _insert_and_get_addrs_ipv4(primary_first, scopes) 341 ksft_eq(resulting_list, expected_result, "Unexpected IPv4 address order") 342 343 subnet_first = ["192.0.2.1", "192.0.2.2", "203.0.113.1", "203.0.113.2"] 344 # Scope and expected result stay the same. 345 resulting_list = _insert_and_get_addrs_ipv4(subnet_first, scopes) 346 ksft_eq(resulting_list, expected_result, "Unexpected IPv4 address order") 347 348 349def ipv4_verify_inter_scope_addr_order() -> None: 350 """ 351 When IPv4 addresses from different scopes are inserted, 352 primary link local addresses must precede global ones. 353 354 Address ordering across different scopes has also 355 been attempted to be patched, bringing in a new risk of a user-space 356 regression, similar to the same scope equivalent. This will further 357 consolidate the implementation differences of both protocols. 358 """ 359 360 test_addrs = ["192.0.2.1", "203.0.113.1", "192.0.2.2", "203.0.113.2"] 361 362 link_first = ["link", "global", "link", "global"] 363 expected_result = test_addrs 364 resulting_list = _insert_and_get_addrs_ipv4(test_addrs, link_first) 365 ksft_eq(resulting_list, expected_result, "Unexpected IPv4 address order across scopes") 366 367 global_first = ["global", "link", "global", "link"] 368 expected_result = ["203.0.113.1", "192.0.2.1", "192.0.2.2", "203.0.113.2"] 369 resulting_list = _insert_and_get_addrs_ipv4(test_addrs, global_first) 370 ksft_eq(resulting_list, expected_result, "Unexpected IPv4 address order across scopes") 371 372 373def _insert_and_get_addrs_ipv6(test_addrs: list[str]) -> list[str]: 374 with NetNS() as ns, NetNSEnter(str(ns)): 375 dev_name = "dummy_dev" 376 377 ip(f"link add name {dev_name} type dummy", ns=str(ns)) 378 for test_addr in test_addrs: 379 ip(f"address add {test_addr}/64 dev {dev_name}", ns=str(ns)) 380 381 rtnl = RtnlAddrFamily() 382 addrs = rtnl.getaddr({"ifa-family": socket.AF_INET6}, dump=True) 383 return [addr["address"] for addr in addrs] 384 385 386def ipv6_verify_same_scope_addr_order() -> None: 387 """ 388 After inserting multiple same scope IPv6 addresses, their order 389 must be the _reverse_ of the insertion order. 390 391 While this behaviour is different from how IPv4 acts, 392 updating the IPv6 implementation to act the same way 393 has proved to cause user-space application regressions 394 (particularly in NetworkManager). This behaviour is being 395 tested to consolidate it as being expected and correct. 396 """ 397 398 addr_list = ["2001:db8::1", "2001:db8::2", "2001:db8::3"] 399 expected_result = addr_list[::-1] 400 resulting_list = _insert_and_get_addrs_ipv6(addr_list) 401 ksft_eq(resulting_list, expected_result, "Unexpected IPv6 address order") 402 403 404def ipv6_verify_inter_scope_addr_order() -> None: 405 """ 406 Inserted IPv6 addresses from different scopes must have 407 global primary address precede link local ones. This again 408 is the _reverse_ of how IPv4 addresses are ordered. 409 410 To prevent potential user-space regressions with IPv6 411 addresses, the inter-scope insertion order is also being tested. 412 """ 413 414 global_first = ["2001:db8::1", "fe80::1", "2001:db8::2", "fe80::2"] 415 # Not only must the global addresses be first, but their insertion order must be reversed. 416 expected_result = ["2001:db8::2", "2001:db8::1", "fe80::2", "fe80::1"] 417 resulting_list = _insert_and_get_addrs_ipv6(global_first) 418 ksft_eq(resulting_list, expected_result, "Unexpected IPv6 address order across scopes") 419 420 link_first = ["fe80::1", "2001:db8::1", "fe80::2", "2001:db8::2"] 421 # Expected result stays the same. 422 resulting_list = _insert_and_get_addrs_ipv6(link_first) 423 ksft_eq(resulting_list, expected_result, "Unexpected IPv6 address order across scopes") 424 425 426def main() -> None: 427 ksft_run([dump_mcaddr_check, dump_mcaddr6_check, ipv4_devconf_notify, 428 ipv6_route_del_reason_expired, 429 ipv6_route_del_reason_ra_withdrawn, 430 ipv6_route_del_reason_absent, 431 ipv4_verify_same_scope_addr_order, ipv4_verify_inter_scope_addr_order, 432 ipv6_verify_same_scope_addr_order, ipv6_verify_inter_scope_addr_order]) 433 ksft_exit() 434 435if __name__ == "__main__": 436 main() 437