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_in, 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') 15ETH_ALL_HOSTS_MULTICAST = bytes.fromhex('01005e000001') 16ETH_TEST_MULTICAST_STR = '01:00:5e:01:01:01' 17ETH_TEST_MULTICAST = bytes.fromhex(ETH_TEST_MULTICAST_STR.replace(':', '')) 18ETH_PEER_MULTICAST_STR = '01:00:5e:02:02:02' 19ETH_PEER_MULTICAST = bytes.fromhex(ETH_PEER_MULTICAST_STR.replace(':', '')) 20 21 22def _users_for(rtnl: RtnlAddrFamily, family: int, grp: bytes, ifindex: int): 23 """Return mc-users for grp on ifindex, or 0 if absent.""" 24 25 addrs = rtnl.getmulticast({"ifa-family": family}, dump=True) 26 matches = [addr for addr in addrs 27 if addr['multicast'] == grp and addr['ifa-index'] == ifindex] 28 if not matches: 29 return 0 30 if 'mc-users' not in matches[0]: 31 return None 32 33 return matches[0]['mc-users'] 34 35 36def dump_mcaddr_check() -> None: 37 """ 38 Verify IPv4 multicast addresses and their user counts in RTM_GETMULTICAST. 39 """ 40 41 with NetNS() as ns: 42 with NetNSEnter(str(ns)): 43 ip("link set lo up") 44 rtnl = RtnlAddrFamily() 45 lo_idx = socket.if_nametoindex('lo') 46 addresses = rtnl.getmulticast({"ifa-family": socket.AF_INET}, dump=True) 47 48 all_host_multicasts = [ 49 addr for addr in addresses 50 if addr['multicast'] == IPV4_ALL_HOSTS_MULTICAST 51 ] 52 53 ksft_ge(len(all_host_multicasts), 1, 54 "No interface found with the IPv4 all-hosts multicast address") 55 56 mreq = IPV4_TEST_MULTICAST + socket.inet_aton('127.0.0.1') 57 before = _users_for(rtnl, socket.AF_INET, IPV4_TEST_MULTICAST, lo_idx) 58 if before is None: 59 raise KsftSkipEx("kernel does not expose IFA_MC_USERS") 60 61 s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 62 s2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 63 try: 64 s1.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) 65 s2.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) 66 67 after_join = _users_for(rtnl, socket.AF_INET, 68 IPV4_TEST_MULTICAST, lo_idx) 69 if after_join is None: 70 raise KsftSkipEx("kernel does not expose IFA_MC_USERS") 71 ksft_eq(after_join - before, 2, 72 f"users delta != 2 after two joins " 73 f"(before={before}, after={after_join})") 74 finally: 75 s1.close() 76 s2.close() 77 78 79def dump_mcaddr6_check() -> None: 80 """ 81 Verify IPv6 multicast addresses and their user counts in RTM_GETMULTICAST. 82 """ 83 84 with NetNS() as ns: 85 with NetNSEnter(str(ns)): 86 ip("link set lo up") 87 rtnl = RtnlAddrFamily() 88 lo_idx = socket.if_nametoindex('lo') 89 before = _users_for(rtnl, socket.AF_INET6, 90 IPV6_TEST_MULTICAST, lo_idx) 91 if before is None: 92 raise KsftSkipEx("kernel does not expose IFA_MC_USERS for IPv6") 93 94 mreq = IPV6_TEST_MULTICAST + struct.pack('=I', lo_idx) 95 s1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 96 s2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 97 try: 98 s1.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) 99 s2.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq) 100 101 after_join = _users_for(rtnl, socket.AF_INET6, 102 IPV6_TEST_MULTICAST, lo_idx) 103 if after_join is None: 104 raise KsftSkipEx("kernel does not expose IFA_MC_USERS for IPv6") 105 ksft_eq(after_join - before, 2, 106 f"IPv6 users delta != 2 after two joins " 107 f"(before={before}, after={after_join})") 108 finally: 109 s1.close() 110 s2.close() 111 112 113def dump_mcaddr_l2_check() -> None: 114 """ 115 Verify link-layer multicast addresses in an AF_PACKET RTM_GETMULTICAST 116 dump: the ifa-index filter, mc-users, the global flag and 117 target-netnsid. 118 """ 119 120 with NetNS() as ns, NetNSEnter(str(ns)): 121 for ifname in ("dummy1", "dummy2"): 122 ip(f"link add name {ifname} type dummy") 123 ip(f"link set {ifname} up") 124 dev_idx = socket.if_nametoindex("dummy1") 125 ip(f"maddr add {ETH_TEST_MULTICAST_STR} dev dummy1") 126 127 rtnl = RtnlAddrFamily() 128 defer(rtnl.close) 129 addresses = rtnl.getmulticast( 130 {"ifa-family": socket.AF_PACKET, "ifa-index": dev_idx}, 131 dump=True) 132 133 # dummy2 has entries as well, only dummy1 may be listed 134 ksft_eq({addr['ifa-index'] for addr in addresses}, {dev_idx}, 135 "AF_PACKET multicast dump ignored ifa-index filter") 136 137 entries = {addr['multicast']: addr for addr in addresses} 138 139 # Bringing an Ethernet device up joins 224.0.0.1, which maps 140 # to 01:00:5e:00:00:01 in the device multicast list. 141 all_hosts = entries.get(ETH_ALL_HOSTS_MULTICAST) 142 ksft_not_none(all_hosts, 143 "dummy1 does not have the all-hosts link-layer address") 144 if all_hosts is not None: 145 ksft_not_in('global', all_hosts['flags'], 146 "protocol entry is global") 147 148 static = entries.get(ETH_TEST_MULTICAST) 149 ksft_not_none(static, "dummy1 does not have the SIOCADDMULTI address") 150 if static is not None: 151 ksft_eq(static['mc-users'], 1, 152 "unexpected mc-users for the SIOCADDMULTI address") 153 ksft_in('global', static['flags'], 154 "SIOCADDMULTI entry is not global") 155 156 # target-netnsid dumps another netns, ifa-index is relative to it 157 with NetNS() as peer: 158 ip(f"netns set {peer} 5") 159 ip("link add name dummy3 type dummy", ns=peer) 160 ip("link set dummy3 up", ns=peer) 161 ip(f"maddr add {ETH_PEER_MULTICAST_STR} dev dummy3", ns=peer) 162 peer_idx = ip("link show dummy3", json=True, ns=peer)[0]['ifindex'] 163 164 addresses = rtnl.getmulticast( 165 {"ifa-family": socket.AF_PACKET, "target-netnsid": 5, 166 "ifa-index": peer_idx}, dump=True) 167 ksft_eq({(addr['ifa-index'], addr['target-netnsid']) 168 for addr in addresses}, {(peer_idx, 5)}, 169 "target-netnsid did not dump the peer netns") 170 # dummy1 in this netns can have the same ifindex as dummy3 171 ksft_in(ETH_PEER_MULTICAST, 172 {addr['multicast'] for addr in addresses}, 173 "target-netnsid did not dump the peer device") 174 175 176def ipv4_devconf_notify() -> None: 177 """ 178 Configure an interface and set ipv4-devconf values through netlink 179 to verify that the appropriate netlink notifications are being sent. 180 """ 181 182 with NetNS() as ns: 183 with NetNSEnter(str(ns)): 184 ifname = "dummy1" 185 ip(f"link add name {ifname} type dummy", ns=str(ns)) 186 187 with bkg("ip monitor", ns=str(ns)) as cmd_obj: 188 time.sleep(1) 189 try: 190 ip(f"link set dev {ifname} inet forwarding on") 191 ip(f"link set dev {ifname} inet proxy_arp on") 192 ip(f"link set dev {ifname} inet rp_filter 1") 193 ip(f"link set dev {ifname} inet ignore_routes_with_linkdown on") 194 except CmdExitFailure: 195 raise KsftSkipEx("iproute2 does not support IPv4 devconf attributes") 196 time.sleep(1) 197 198 ksft_true(f"inet {ifname} ignore_routes_with_linkdown on" in cmd_obj.stdout, 199 f"No 'ignore_routes_with_linkdown on' notificiation found for interface {ifname}") 200 ksft_true(f"inet {ifname} rp_filter strict" in cmd_obj.stdout, 201 f"No 'rp_filter strict' notificiation found for interface {ifname}") 202 ksft_true(f"inet {ifname} proxy_neigh on" in cmd_obj.stdout, 203 f"No 'proxy_neigh on' notificiation found for interface {ifname}") 204 ksft_true(f"inet {ifname} forwarding on" in cmd_obj.stdout, 205 f"No 'forwarding on' notificiation found for interface {ifname}") 206 207def _rtnl_route_subscribe(ns): 208 with NetNSEnter(str(ns)): 209 rtnl = RtnlRouteFamily() 210 defer(rtnl.close) 211 rtnl.ntf_subscribe("rtnlgrp-ipv6-route") 212 return rtnl 213 214 215def _wait_route_ntf(rtnl, name, dst_len, dst=None, deadline=10): 216 """Return the attrs of the first matching notification, None on timeout.""" 217 218 for msg in rtnl.poll_ntf(duration=deadline): 219 if msg['name'] != name: 220 continue 221 attrs = msg['msg'] 222 if attrs['rtm-dst-len'] != dst_len: 223 continue 224 if dst is not None and attrs.get('dst') != dst: 225 continue 226 return attrs 227 return None 228 229 230def _collect_route_ntfs(rtnl, name, want, deadline=10): 231 """Gather attrs of matching notifications, keyed by (dst_len, dst).""" 232 233 seen = {} 234 for msg in rtnl.poll_ntf(duration=deadline): 235 if msg['name'] != name: 236 continue 237 attrs = msg['msg'] 238 key = (attrs['rtm-dst-len'], attrs.get('dst')) 239 if key in want: 240 seen[key] = attrs 241 if len(seen) == len(want): 242 break 243 return seen 244 245 246def _write_ipv6_sysctl(name, value): 247 with open(f"/proc/sys/net/ipv6/{name}", "w") as f: 248 f.write(f"{value}\n") 249 250 251def ipv6_route_del_reason_expired() -> None: 252 """An expired route reports RTA_DEL_REASON == expired.""" 253 254 with NetNS() as ns: 255 rtnl = _rtnl_route_subscribe(ns) 256 with NetNSEnter(str(ns)): 257 _write_ipv6_sysctl("route/gc_interval", 2) 258 ip("link add name dummy1 type dummy", ns=str(ns)) 259 ip("link set dev dummy1 up", ns=str(ns)) 260 ip("-6 route add 2001:db8:2::/64 dev dummy1 expires 2", ns=str(ns)) 261 262 attrs = _wait_route_ntf(rtnl, 'delroute-ntf', 64, '2001:db8:2::', 263 deadline=15) 264 ksft_not_none(attrs, "no RTM_DELROUTE for the expired route") 265 if attrs is not None: 266 ksft_eq(attrs.get('del-reason'), 'expired') 267 268 269def _send_ra(sock, ifindex, lifetime, rio=None, pio=None): 270 """The kernel fills in the ICMPv6 checksum on raw ICMPv6 sockets.""" 271 272 # type, code, cksum, hop limit, flags, router lifetime, 273 # reachable time, retrans timer 274 ra = struct.pack('!BBHBBHII', 134, 0, 0, 64, 0, lifetime, 0, 0) 275 if rio is not None: 276 prefix, plen, rio_lifetime = rio 277 # RFC 4191 route information option, /64 prefix (8 bytes) 278 ra += struct.pack('!BBBBI', 24, 2, plen, 0, rio_lifetime) 279 ra += socket.inet_pton(socket.AF_INET6, prefix)[:8] 280 if pio is not None: 281 prefix, plen, valid_lft = pio 282 # RFC 4861 prefix information option, on-link only (L set, A clear) 283 ra += struct.pack('!BBBBIII', 3, 4, plen, 0x80, valid_lft, 0, 0) 284 ra += socket.inet_pton(socket.AF_INET6, prefix) 285 sock.sendto(ra, ('ff02::1', 0, 0, ifindex)) 286 287 288def _ra_router_sock(ns_r, ifname): 289 with NetNSEnter(str(ns_r)): 290 sock = socket.socket(socket.AF_INET6, socket.SOCK_RAW, 291 socket.IPPROTO_ICMPV6) 292 sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 255) 293 defer(sock.close) 294 return sock, socket.if_nametoindex(ifname) 295 296 297def _ra_advertise_routes(rtnl, sock, ifindex, want, **ra_opts): 298 """ 299 Sending fails with EADDRNOTAVAIL while the router's link-local 300 address is still tentative. addrconf_dad_start() only queues 301 addrconf_dad_work(), and IFA_F_TENTATIVE is cleared when that work 302 item runs, so retry until it does. 303 """ 304 305 seen = {} 306 for _ in range(10): 307 try: 308 _send_ra(sock, ifindex, **ra_opts) 309 except OSError: 310 time.sleep(0.2) 311 continue 312 seen.update(_collect_route_ntfs(rtnl, 'newroute-ntf', 313 want - set(seen.keys()), deadline=2)) 314 if len(seen) == len(want): 315 break 316 return seen 317 318 319def ipv6_route_del_reason_ra_withdrawn() -> None: 320 """ 321 Routes withdrawn by a zero-lifetime RA (router lifetime, RFC 4861 322 PIO, RFC 4191 RIO) report RTA_DEL_REASON == ra-withdrawn. 323 """ 324 325 # (rtm-dst-len, dst); the default route carries no RTA_DST 326 routes = {(0, None), (64, '2001:db8:6::'), (64, '2001:db8:5::')} 327 328 with NetNS() as ns_h, NetNS() as ns_r: 329 ip(f"link add veth0 netns {ns_h} type veth peer name veth1 netns {ns_r}") 330 with NetNSEnter(str(ns_h)): 331 _write_ipv6_sysctl("conf/veth0/accept_ra", 2) 332 _write_ipv6_sysctl("conf/veth0/forwarding", 0) 333 try: 334 _write_ipv6_sysctl("conf/veth0/accept_ra_rt_info_max_plen", 64) 335 except FileNotFoundError: 336 raise KsftSkipEx("no CONFIG_IPV6_ROUTE_INFO") 337 with NetNSEnter(str(ns_r)): 338 # skip the DAD probe so the router's link-local source only 339 # has to wait for addrconf_dad_work() to clear IFA_F_TENTATIVE 340 _write_ipv6_sysctl("conf/veth1/accept_dad", 0) 341 ip("link set dev veth0 up", ns=str(ns_h)) 342 ip("link set dev veth1 up", ns=str(ns_r)) 343 344 rtnl = _rtnl_route_subscribe(ns_h) 345 sock, ifindex = _ra_router_sock(ns_r, "veth1") 346 347 seen = _ra_advertise_routes(rtnl, sock, ifindex, routes, 348 lifetime=1800, 349 rio=('2001:db8:5::', 64, 600), 350 pio=('2001:db8:6::', 64, 600)) 351 ksft_eq(set(seen), routes, "not all RA routes were installed") 352 if set(seen) != routes: 353 return 354 355 _send_ra(sock, ifindex, 0, rio=('2001:db8:5::', 64, 0), 356 pio=('2001:db8:6::', 64, 0)) 357 seen = _collect_route_ntfs(rtnl, 'delroute-ntf', routes) 358 for key in routes: 359 attrs = seen.get(key) 360 ksft_not_none(attrs, f"no RTM_DELROUTE for {key}") 361 if attrs is not None: 362 ksft_eq(attrs.get('del-reason'), 'ra-withdrawn') 363 364 365def ipv6_route_del_reason_absent() -> None: 366 """ 367 A deletion path that records no cause (here a userspace request) 368 must not carry RTA_DEL_REASON at all. 369 """ 370 371 with NetNS() as ns: 372 rtnl = _rtnl_route_subscribe(ns) 373 ip("link add name dummy1 type dummy", ns=str(ns)) 374 ip("link set dev dummy1 up", ns=str(ns)) 375 ip("-6 route add 2001:db8:1::/64 dev dummy1", ns=str(ns)) 376 ip("-6 route del 2001:db8:1::/64 dev dummy1", ns=str(ns)) 377 378 attrs = _wait_route_ntf(rtnl, 'delroute-ntf', 64, '2001:db8:1::') 379 ksft_not_none(attrs, "no RTM_DELROUTE for 2001:db8:1::/64") 380 if attrs is not None: 381 ksft_not_in('del-reason', attrs, 382 "user deletion must not carry del-reason") 383 384 385def _insert_and_get_addrs_ipv4(test_addrs: list[str], scopes: list[str]) -> list[str]: 386 with NetNS() as ns, NetNSEnter(str(ns)): 387 dev_name = "dummy_dev" 388 389 ip(f"link add name {dev_name} type dummy", ns=str(ns)) 390 for test_addr, scope in zip(test_addrs, scopes): 391 ip(f"address add {test_addr}/24 dev {dev_name} scope {scope}", ns=str(ns)) 392 393 rtnl = RtnlAddrFamily() 394 addrs = rtnl.getaddr({"ifa-family": socket.AF_INET}, dump=True) 395 return [addr["address"] for addr in addrs] 396 397 398def ipv4_verify_same_scope_addr_order() -> None: 399 """ 400 After inserting multiple same scope IPv4 addresses, their order 401 must be the same as the insertion order. The only aspect affecting 402 this are primary addresses, which precede secondary ones. 403 """ 404 405 primary_first = ["192.0.2.1", "203.0.113.1", "192.0.2.2", "203.0.113.2"] 406 scopes = ["global"] * 4 407 expected_result = primary_first 408 resulting_list = _insert_and_get_addrs_ipv4(primary_first, scopes) 409 ksft_eq(resulting_list, expected_result, "Unexpected IPv4 address order") 410 411 subnet_first = ["192.0.2.1", "192.0.2.2", "203.0.113.1", "203.0.113.2"] 412 # Scope and expected result stay the same. 413 resulting_list = _insert_and_get_addrs_ipv4(subnet_first, scopes) 414 ksft_eq(resulting_list, expected_result, "Unexpected IPv4 address order") 415 416 417def ipv4_verify_inter_scope_addr_order() -> None: 418 """ 419 When IPv4 addresses from different scopes are inserted, 420 primary link local addresses must precede global ones. 421 422 Address ordering across different scopes has also 423 been attempted to be patched, bringing in a new risk of a user-space 424 regression, similar to the same scope equivalent. This will further 425 consolidate the implementation differences of both protocols. 426 """ 427 428 test_addrs = ["192.0.2.1", "203.0.113.1", "192.0.2.2", "203.0.113.2"] 429 430 link_first = ["link", "global", "link", "global"] 431 expected_result = test_addrs 432 resulting_list = _insert_and_get_addrs_ipv4(test_addrs, link_first) 433 ksft_eq(resulting_list, expected_result, "Unexpected IPv4 address order across scopes") 434 435 global_first = ["global", "link", "global", "link"] 436 expected_result = ["203.0.113.1", "192.0.2.1", "192.0.2.2", "203.0.113.2"] 437 resulting_list = _insert_and_get_addrs_ipv4(test_addrs, global_first) 438 ksft_eq(resulting_list, expected_result, "Unexpected IPv4 address order across scopes") 439 440 441def _insert_and_get_addrs_ipv6(test_addrs: list[str]) -> list[str]: 442 with NetNS() as ns, NetNSEnter(str(ns)): 443 dev_name = "dummy_dev" 444 445 ip(f"link add name {dev_name} type dummy", ns=str(ns)) 446 for test_addr in test_addrs: 447 ip(f"address add {test_addr}/64 dev {dev_name}", ns=str(ns)) 448 449 rtnl = RtnlAddrFamily() 450 addrs = rtnl.getaddr({"ifa-family": socket.AF_INET6}, dump=True) 451 return [addr["address"] for addr in addrs] 452 453 454def ipv6_verify_same_scope_addr_order() -> None: 455 """ 456 After inserting multiple same scope IPv6 addresses, their order 457 must be the _reverse_ of the insertion order. 458 459 While this behaviour is different from how IPv4 acts, 460 updating the IPv6 implementation to act the same way 461 has proved to cause user-space application regressions 462 (particularly in NetworkManager). This behaviour is being 463 tested to consolidate it as being expected and correct. 464 """ 465 466 addr_list = ["2001:db8::1", "2001:db8::2", "2001:db8::3"] 467 expected_result = addr_list[::-1] 468 resulting_list = _insert_and_get_addrs_ipv6(addr_list) 469 ksft_eq(resulting_list, expected_result, "Unexpected IPv6 address order") 470 471 472def ipv6_verify_inter_scope_addr_order() -> None: 473 """ 474 Inserted IPv6 addresses from different scopes must have 475 global primary address precede link local ones. This again 476 is the _reverse_ of how IPv4 addresses are ordered. 477 478 To prevent potential user-space regressions with IPv6 479 addresses, the inter-scope insertion order is also being tested. 480 """ 481 482 global_first = ["2001:db8::1", "fe80::1", "2001:db8::2", "fe80::2"] 483 # Not only must the global addresses be first, but their insertion order must be reversed. 484 expected_result = ["2001:db8::2", "2001:db8::1", "fe80::2", "fe80::1"] 485 resulting_list = _insert_and_get_addrs_ipv6(global_first) 486 ksft_eq(resulting_list, expected_result, "Unexpected IPv6 address order across scopes") 487 488 link_first = ["fe80::1", "2001:db8::1", "fe80::2", "2001:db8::2"] 489 # Expected result stays the same. 490 resulting_list = _insert_and_get_addrs_ipv6(link_first) 491 ksft_eq(resulting_list, expected_result, "Unexpected IPv6 address order across scopes") 492 493 494def main() -> None: 495 ksft_run([dump_mcaddr_check, dump_mcaddr6_check, dump_mcaddr_l2_check, 496 ipv4_devconf_notify, 497 ipv6_route_del_reason_expired, 498 ipv6_route_del_reason_ra_withdrawn, 499 ipv6_route_del_reason_absent, 500 ipv4_verify_same_scope_addr_order, ipv4_verify_inter_scope_addr_order, 501 ipv6_verify_same_scope_addr_order, ipv6_verify_inter_scope_addr_order]) 502 ksft_exit() 503 504if __name__ == "__main__": 505 main() 506