| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/14 15:17 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"IPV6_SUBTREES",
"IPV6_MULTIPLE_TABLES"
],
"FocusSymbols": [
"ipv6_saddr_rule5p5",
"ipv6_fl_get_saddr",
"ipv6_dev_get_saddr",
"rt6_fill_node"
],
"Reasoning": "The patch implements RFC6724 rule 5.5 for IPv6 source address selection by introducing `ipv6_saddr_rule5p5` and `ipv6_fl_get_saddr`. It modifies core IPv6 routing and address selection logic, which is reachable from userspace via socket operations (e.g., connect, sendmsg) and netlink route dumps (`rt6_fill_node`). The feature requires `IPV6_SUBTREES` and `IPV6_MULTIPLE_TABLES` to be enabled.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/14 15:17 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 9a4d5442c02b39b92582fe11ec8af22b4fadc878\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Jul 14 15:17:25 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/networking/ipv6-addrsel.rst b/Documentation/networking/ipv6-addrsel.rst\nnew file mode 100644\nindex 00000000000000..bed032e695705c\n--- /dev/null\n+++ b/Documentation/networking/ipv6-addrsel.rst\n@@ -0,0 +1,75 @@\n+.. SPDX-License-Identifier: GPL-2.0\n+\n+====================================\n+IPv6 source address selection trivia\n+====================================\n+\n+\n+RFC6724 rule 5.5 support\n+------------------------\n+\n+RFC6724 rule 5.5 is a very short paragraph in a complex RFC that has turned\n+out quite tricky, but also immensely useful in multihoming scenarios. For\n+reference, it says:\n+\n+::\n+\n+ Rule 5.5: Prefer addresses in a prefix advertised by the next-hop.\n+ If SA or SA's prefix is assigned by the selected next-hop that will\n+ be used to send to D and SB or SB's prefix is assigned by a different\n+ next-hop, then prefer SA. Similarly, if SB or SB's prefix is\n+ assigned by the next-hop that will be used to send to D and SA or\n+ SA's prefix is assigned by a different next-hop, then prefer SB.\n+\n+The way this works on Linux is as follows:\n+\n+- prior to any source address selection happening, when receiving a RA, more\n+ than the installation of a default route (or ::/128 route) needs to happen:\n+ for each PIO, a source-specific (subtree) route is *additionally* installed.\n+ The effect of this is that *after* a source address has been selected, one\n+ of the routers that advertised it will remain in use (this is *not* RFC 6724\n+ related, but rather RFC 8028.) At the same time, these extra routes serve\n+ to remember which router advertised what.\n+\n+- per usual, a route lookup for the IPv6 destination address in consideration\n+ is done first. This is passed around in kernel as a dst_entry.\n+\n+- the source address selection code iterates through the various rules in\n+ RFC 6724.\n+\n+- if/when rule 5.5 is reached, first of all, there is a check if *any* source\n+ specific routes exist in the routing table. If there are none, the entire\n+ code for 5.5 is skipped because it cannot have any effect, but is not free\n+ to execute (can involve multiple routing lookups.) **In applications that\n+ use a lot of unbound (e.g. UDP) sockets, installing subtree routes should\n+ therefore be avoided to not incur this cost on each source address selection\n+ pass.** Alternatively, applications should bind their sockets to a specific\n+ source address such that the selection code is never hit.\n+\n+- if subtree routes do exist, the source address selection code now repeats\n+ the routing lookup done before source address selection is entered, except\n+ with the source address under consideration filled in. This lookup will hit\n+ the subtree routes that were installed (see first item), giving a fresh\n+ dst_entry. If the new dst_entry matches the original dst_entry, that means\n+ the original router has in fact sent RAs with PIOs for this source address,\n+ so it is preferred. Otherwise it is not.\n+\n+\n+There are a few caveats to consider:\n+\n+- the kernel currently does not create the subtree routes mentioned in the\n+ first item. This is a separate work item, partially done at the time of\n+ writing this. But this can equally well be performed in userspace processing\n+ of RAs, e.g. NetworkManager or plain static configuration.\n+\n+- since addresses can also be acquired from DHCPv6, even RA/PIO combinations\n+ that didn't result in the creation of any addresses (e.g. A=0) should have\n+ subtree routes added. Those routes *may* be relevant for DHCPv6-generated\n+ addresses.\n+\n+- the \"announce check\" lookup does not backtrack. Only the destination prefix\n+ that provided the \"unspecific\" (::/128) match is checked for source prefixes\n+ to see what routers advertised what. This means that for e.g. RIOs, subtree\n+ routes also have to be created. (Backtracking for this case would further\n+ increase the cost of source address selection, for a pretty rare situation\n+ that has an easy fix/workaround.)\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex f3218abefd0c6b..4edf48362a0732 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -18912,6 +18912,7 @@ F:\tDocumentation/netlink/specs/rt-addr.yaml\n F:\tDocumentation/netlink/specs/rt-neigh.yaml\n F:\tDocumentation/netlink/specs/rt-route.yaml\n F:\tDocumentation/netlink/specs/rt-rule.yaml\n+F:\tDocumentation/networking/ipv6-addrsel.rst\n F:\tinclude/linux/inetdevice.h\n F:\tinclude/linux/mroute*\n F:\tinclude/net/addrconf.h\ndiff --git a/include/net/addrconf.h b/include/net/addrconf.h\nindex 8ced27a8229b6e..7bc0711b871796 100644\n--- a/include/net/addrconf.h\n+++ b/include/net/addrconf.h\n@@ -142,6 +142,10 @@ struct inet6_ifaddr *ipv6_get_ifaddr(struct net *net,\n int ipv6_dev_get_saddr(struct net *net, const struct net_device *dev,\n \t\t const struct in6_addr *daddr, unsigned int srcprefs,\n \t\t struct in6_addr *saddr);\n+int ipv6_fl_get_saddr(struct net *net, const struct dst_entry *dst,\n+\t\t const struct net_device *dst_dev,\n+\t\t const struct sock *sk, unsigned int srcprefs,\n+\t\t struct flowi6 *fl6);\n int ipv6_get_lladdr(struct net_device *dev, struct in6_addr *addr,\n \t\t u32 banned_flags);\n bool inet_rcv_saddr_equal(const struct sock *sk, const struct sock *sk2,\ndiff --git a/include/net/ip6_route.h b/include/net/ip6_route.h\nindex 09ffe0f13ce752..fe13cb1d3257db 100644\n--- a/include/net/ip6_route.h\n+++ b/include/net/ip6_route.h\n@@ -140,32 +140,6 @@ void rt6_flush_exceptions(struct fib6_info *f6i);\n void rt6_age_exceptions(struct fib6_info *f6i, struct fib6_gc_args *gc_args,\n \t\t\tunsigned long now);\n \n-static inline int ip6_route_get_saddr(struct net *net, struct fib6_info *f6i,\n-\t\t\t\t const struct in6_addr *daddr,\n-\t\t\t\t unsigned int prefs, int l3mdev_index,\n-\t\t\t\t struct in6_addr *saddr)\n-{\n-\tstruct net_device *l3mdev;\n-\tstruct net_device *dev;\n-\tbool same_vrf;\n-\tint err = 0;\n-\n-\trcu_read_lock();\n-\n-\tl3mdev = dev_get_by_index_rcu(net, l3mdev_index);\n-\tif (!f6i || !f6i-\u003efib6_prefsrc.plen || l3mdev)\n-\t\tdev = f6i ? fib6_info_nh_dev(f6i) : NULL;\n-\tsame_vrf = !l3mdev || l3mdev_master_dev_rcu(dev) == l3mdev;\n-\tif (f6i \u0026\u0026 f6i-\u003efib6_prefsrc.plen \u0026\u0026 same_vrf)\n-\t\t*saddr = f6i-\u003efib6_prefsrc.addr;\n-\telse\n-\t\terr = ipv6_dev_get_saddr(net, same_vrf ? dev : l3mdev, daddr, prefs, saddr);\n-\n-\trcu_read_unlock();\n-\n-\treturn err;\n-}\n-\n struct rt6_info *rt6_lookup(struct net *net, const struct in6_addr *daddr,\n \t\t\t const struct in6_addr *saddr, int oif,\n \t\t\t const struct sk_buff *skb, int flags);\ndiff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig\nindex c3806c6ac96f95..39659e9b53a3db 100644\n--- a/net/ipv6/Kconfig\n+++ b/net/ipv6/Kconfig\n@@ -243,16 +243,26 @@ config IPV6_MULTIPLE_TABLES\n config IPV6_SUBTREES\n \tbool \"IPv6: source address based routing\"\n \tdepends on IPV6_MULTIPLE_TABLES\n+\tdefault y\n \thelp\n \t Enable routing by source address or prefix.\n \n \t The destination address is still the primary routing key, so mixing\n \t normal and source prefix specific routes in the same routing table\n-\t may sometimes lead to unintended routing behavior. This can be\n-\t avoided by defining different routing tables for the normal and\n-\t source prefix specific routes.\n+\t may sometimes lead to unintended routing behavior if the order of\n+\t lookups is misunderstood. To achieve full separation based on source\n+\t address, different routing tables should be defined for each source\n+\t prefix.\n \n-\t If unsure, say N.\n+\t Subtrees specifically address scenarios where local connectivity\n+\t between source prefixes is shared and unrestricted, but some\n+\t destinations (e.g. the default route) have more fine-grained rules.\n+\n+\t This feature is required to correctly support RFC6724 rule 5.5 in\n+\t source address selection. Rule 5.5 is a necessity for good\n+\t multihoming, renumbering and source prefix liveliness checks.\n+\n+\t If unsure, say Y.\n \n config IPV6_MROUTE\n \tbool \"IPv6: multicast routing\"\ndiff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c\nindex f1fe9ede1edb37..f5c04928aa142f 100644\n--- a/net/ipv6/addrconf.c\n+++ b/net/ipv6/addrconf.c\n@@ -1522,6 +1522,7 @@ enum {\n \tIPV6_SADDR_RULE_HOA,\n #endif\n \tIPV6_SADDR_RULE_OIF,\n+\tIPV6_SADDR_RULE_PIO_BY_NEXTHOP,\n \tIPV6_SADDR_RULE_LABEL,\n \tIPV6_SADDR_RULE_PRIVACY,\n \tIPV6_SADDR_RULE_ORCHID,\n@@ -1542,11 +1543,14 @@ struct ipv6_saddr_score {\n };\n \n struct ipv6_saddr_dst {\n-\tconst struct in6_addr *addr;\n+\tstruct flowi6 *fl6;\n+\tconst struct dst_entry *dst;\n+\tconst struct sock *sk;\n \tint ifindex;\n \tint scope;\n \tint label;\n \tunsigned int prefs;\n+\tstruct net *net;\n };\n \n static inline int ipv6_saddr_preferred(int type)\n@@ -1591,6 +1595,80 @@ static bool ipv6_allow_optimistic_dad(const struct net *net,\n #endif\n }\n \n+/* \"source address is preferable if the chosen nexthop advertised it as a PIO\"\n+ * =\u003e consider 'advertised as a PIO' to be 'the routes for the source's subtree\n+ * include one with the same nexthop'\n+ *\n+ * NB: there is no backtracking in the subtree here, this is intentional -\n+ * each prefix seen (and accepted) in PIOs creates essentially a \"zone\" which\n+ * is our search scope.\n+ */\n+static int ipv6_saddr_rule5p5(struct ipv6_saddr_score *score,\n+\t\t\t struct ipv6_saddr_dst *saddr_dst)\n+{\n+\tconst struct rt6_info *rt, *cmp_rt;\n+\tstruct dst_entry *cmp_dst;\n+\tstruct fib6_info *f6i;\n+\tint ret = 0;\n+\n+\trt = container_of(saddr_dst-\u003edst, struct rt6_info, dst);\n+\n+\t/* fl6-\u003esaddr is ::, cf. check at the top of ipv6_common_get_saddr() */\n+\tsaddr_dst-\u003efl6-\u003esaddr = score-\u003eifa-\u003eaddr;\n+\tcmp_dst = ip6_route_output(saddr_dst-\u003enet, saddr_dst-\u003esk,\n+\t\t\t\t saddr_dst-\u003efl6);\n+\tmemset(\u0026saddr_dst-\u003efl6-\u003esaddr, 0, sizeof(saddr_dst-\u003efl6-\u003esaddr));\n+\n+\tif (cmp_dst-\u003eerror)\n+\t\tgoto out_release_dst;\n+\n+\tcmp_rt = container_of(cmp_dst, struct rt6_info, dst);\n+\n+\t/* this must work if _any_ nexthop matches; the non-subtree best may\n+\t * not be in same order as subtree best\n+\t */\n+\tfor (f6i = rcu_dereference(cmp_rt-\u003efrom); f6i;\n+\t f6i = rcu_dereference(f6i-\u003efib6_next)) {\n+\t\tstruct fib6_nh *f6n = f6i-\u003efib6_nh;\n+\t\tstruct fib6_info *sibling;\n+\n+\t\t/* non-subtree route: says nothing about router advertising this source */\n+\t\tif (f6i-\u003efib6_src.plen == 0)\n+\t\t\tcontinue;\n+\n+\t\tif (f6n-\u003enh_common.nhc_dev != saddr_dst-\u003edst-\u003edev ||\n+\t\t f6n-\u003enh_common.nhc_gw_family != AF_INET6)\n+\t\t\tcontinue;\n+\n+\t\tif (ipv6_addr_equal(\u0026f6n-\u003enh_common.nhc_gw.ipv6,\n+\t\t\t\t \u0026rt-\u003ert6i_gateway)) {\n+\t\t\tret = 1;\n+\t\t\tgoto out_release_dst;\n+\t\t}\n+\n+\t\tif (!f6i-\u003efib6_nsiblings)\n+\t\t\tcontinue;\n+\n+\t\tlist_for_each_entry(sibling, \u0026f6i-\u003efib6_siblings, fib6_siblings) {\n+\t\t\tf6n = sibling-\u003efib6_nh;\n+\n+\t\t\tif (f6n-\u003enh_common.nhc_dev != saddr_dst-\u003edst-\u003edev ||\n+\t\t\t f6n-\u003enh_common.nhc_gw_family != AF_INET6)\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (ipv6_addr_equal(\u0026f6n-\u003enh_common.nhc_gw.ipv6,\n+\t\t\t\t\t \u0026rt-\u003ert6i_gateway)) {\n+\t\t\t\tret = 1;\n+\t\t\t\tgoto out_release_dst;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+out_release_dst:\n+\tdst_release(cmp_dst);\n+\treturn ret;\n+}\n+\n static int ipv6_get_saddr_eval(struct net *net,\n \t\t\t struct ipv6_saddr_score *score,\n \t\t\t struct ipv6_saddr_dst *dst,\n@@ -1619,7 +1697,7 @@ static int ipv6_get_saddr_eval(struct net *net,\n \t\tbreak;\n \tcase IPV6_SADDR_RULE_LOCAL:\n \t\t/* Rule 1: Prefer same address */\n-\t\tret = ipv6_addr_equal(\u0026score-\u003eifa-\u003eaddr, dst-\u003eaddr);\n+\t\tret = ipv6_addr_equal(\u0026score-\u003eifa-\u003eaddr, \u0026dst-\u003efl6-\u003edaddr);\n \t\tbreak;\n \tcase IPV6_SADDR_RULE_SCOPE:\n \t\t/* Rule 2: Prefer appropriate scope\n@@ -1675,6 +1753,24 @@ static int ipv6_get_saddr_eval(struct net *net,\n \t\tret = (!dst-\u003eifindex ||\n \t\t dst-\u003eifindex == score-\u003eifa-\u003eidev-\u003edev-\u003eifindex);\n \t\tbreak;\n+\tcase IPV6_SADDR_RULE_PIO_BY_NEXTHOP:\n+\t\t/* Rule 5.5: Prefer sources advertised by chosen next-hop */\n+\n+\t\t/* Without subtrees, the source address will make no difference\n+\t\t * in the ip6_route_output call in rule5p5. Therefore the rule\n+\t\t * 5.5 check becomes useless. This wouldn't result in any\n+\t\t * errors, but ip6_route_output isn't free, so if subtrees are\n+\t\t * disabled save some cycles by skipping this entirely.\n+\t\t *\n+\t\t * This is done through subtrees_enabled to have the code\n+\t\t * compiled regardless.\n+\t\t */\n+\t\tif (fib6_routes_require_src(net) \u0026\u0026 dst-\u003edst\n+\t\t \u0026\u0026 !dst-\u003edst-\u003eerror)\n+\t\t\tret = ipv6_saddr_rule5p5(score, dst);\n+\t\telse\n+\t\t\tret = 1;\n+\t\tbreak;\n \tcase IPV6_SADDR_RULE_LABEL:\n \t\t/* Rule 6: Prefer matching label */\n \t\tret = ipv6_addr_label(net,\n@@ -1697,11 +1793,11 @@ static int ipv6_get_saddr_eval(struct net *net,\n \t\t *\t non-ORCHID vs non-ORCHID\n \t\t */\n \t\tret = !(ipv6_addr_orchid(\u0026score-\u003eifa-\u003eaddr) ^\n-\t\t\tipv6_addr_orchid(dst-\u003eaddr));\n+\t\t\tipv6_addr_orchid(\u0026dst-\u003efl6-\u003edaddr));\n \t\tbreak;\n \tcase IPV6_SADDR_RULE_PREFIX:\n \t\t/* Rule 8: Use longest matching prefix */\n-\t\tret = ipv6_addr_diff(\u0026score-\u003eifa-\u003eaddr, dst-\u003eaddr);\n+\t\tret = ipv6_addr_diff(\u0026score-\u003eifa-\u003eaddr, \u0026dst-\u003efl6-\u003edaddr);\n \t\tif (ret \u003e score-\u003eifa-\u003eprefix_len)\n \t\t\tret = score-\u003eifa-\u003eprefix_len;\n \t\tscore-\u003ematchlen = ret;\n@@ -1819,9 +1915,10 @@ static int ipv6_get_saddr_master(struct net *net,\n \treturn hiscore_idx;\n }\n \n-int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,\n-\t\t const struct in6_addr *daddr, unsigned int prefs,\n-\t\t struct in6_addr *saddr)\n+int ipv6_fl_get_saddr(struct net *net, const struct dst_entry *dst_entry,\n+\t\t const struct net_device *dst_dev,\n+\t\t const struct sock *sk, unsigned int prefs,\n+\t\t struct flowi6 *fl6)\n {\n \tstruct ipv6_saddr_score scores[2], *hiscore;\n \tstruct ipv6_saddr_dst dst;\n@@ -1832,11 +1929,18 @@ int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,\n \tint hiscore_idx = 0;\n \tint ret = 0;\n \n-\tdst_type = __ipv6_addr_type(daddr);\n-\tdst.addr = daddr;\n+\t/* we should never end up here with a non-empty saddr. */\n+\tif (WARN_ON_ONCE(!ipv6_addr_any(\u0026fl6-\u003esaddr)))\n+\t\treturn 0;\n+\n+\tdst_type = __ipv6_addr_type(\u0026fl6-\u003edaddr);\n+\tdst.net = net;\n+\tdst.fl6 = fl6;\n+\tdst.sk = sk;\n+\tdst.dst = dst_entry;\n \tdst.ifindex = dst_dev ? dst_dev-\u003eifindex : 0;\n \tdst.scope = __ipv6_addr_src_scope(dst_type);\n-\tdst.label = ipv6_addr_label(net, daddr, dst_type, dst.ifindex);\n+\tdst.label = ipv6_addr_label(net, \u0026fl6-\u003edaddr, dst_type, dst.ifindex);\n \tdst.prefs = prefs;\n \n \tscores[hiscore_idx].rule = -1;\n@@ -1911,11 +2015,29 @@ int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,\n \tif (!hiscore-\u003eifa)\n \t\tret = -EADDRNOTAVAIL;\n \telse\n-\t\t*saddr = hiscore-\u003eifa-\u003eaddr;\n+\t\tfl6-\u003esaddr = hiscore-\u003eifa-\u003eaddr;\n \n \trcu_read_unlock();\n \treturn ret;\n }\n+EXPORT_SYMBOL(ipv6_fl_get_saddr);\n+\n+int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,\n+\t\t const struct in6_addr *daddr, unsigned int prefs,\n+\t\t struct in6_addr *saddr)\n+{\n+\tstruct flowi6 fl6;\n+\tint ret;\n+\n+\tmemset(\u0026fl6, 0, sizeof(fl6));\n+\tfl6.daddr = *daddr;\n+\n+\tret = ipv6_fl_get_saddr(net, NULL, dst_dev, NULL, prefs, \u0026fl6);\n+\tif (!ret)\n+\t\t*saddr = fl6.saddr;\n+\n+\treturn ret;\n+}\n EXPORT_SYMBOL(ipv6_dev_get_saddr);\n \n static int __ipv6_get_lladdr(struct inet6_dev *idev, struct in6_addr *addr,\ndiff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c\nindex a130cdfaebfbee..273f2bfc528669 100644\n--- a/net/ipv6/ip6_fib.c\n+++ b/net/ipv6/ip6_fib.c\n@@ -1656,8 +1656,11 @@ static struct fib6_node *fib6_node_lookup_1(struct fib6_node *root,\n \t\t\t\t\tstruct fib6_node *sfn;\n \t\t\t\t\tsfn = fib6_node_lookup_1(subtree,\n \t\t\t\t\t\t\t\t args + 1);\n-\t\t\t\t\tif (!sfn)\n+\t\t\t\t\tif (!sfn) {\n+\t\t\t\t\t\tif (fn-\u003efn_flags \u0026 RTN_RTINFO)\n+\t\t\t\t\t\t\treturn fn;\n \t\t\t\t\t\tgoto backtrack;\n+\t\t\t\t\t}\n \t\t\t\t\tfn = sfn;\n \t\t\t\t}\n #endif\ndiff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c\nindex 368e4fa3b43ca2..03b8d42d9df9ef 100644\n--- a/net/ipv6/ip6_output.c\n+++ b/net/ipv6/ip6_output.c\n@@ -1143,27 +1143,40 @@ static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk,\n \tint flags = 0;\n \n \t/* The correct way to handle this would be to do\n-\t * ip6_route_get_saddr, and then ip6_route_output; however,\n+\t * ipv6_fl_get_saddr, and then ip6_route_output; however,\n \t * the route-specific preferred source forces the\n-\t * ip6_route_output call _before_ ip6_route_get_saddr.\n+\t * ip6_route_output call _before_ ipv6_fl_get_saddr.\n \t *\n \t * In source specific routing (no src=any default route),\n \t * ip6_route_output will fail given src=any saddr, though, so\n \t * that's why we try it again later.\n \t */\n \tif (ipv6_addr_any(\u0026fl6-\u003esaddr)) {\n+\t\tstruct net_device *l3mdev;\n+\t\tstruct net_device *dev;\n \t\tstruct fib6_info *from;\n \t\tstruct rt6_info *rt;\n+\t\tbool same_vrf;\n \n \t\t*dst = ip6_route_output(net, sk, fl6);\n \t\trt = (*dst)-\u003eerror ? NULL : dst_rt6_info(*dst);\n \n \t\trcu_read_lock();\n \t\tfrom = rt ? rcu_dereference(rt-\u003efrom) : NULL;\n-\t\terr = ip6_route_get_saddr(net, from, \u0026fl6-\u003edaddr,\n-\t\t\t\t\t sk ? READ_ONCE(inet6_sk(sk)-\u003esrcprefs) : 0,\n-\t\t\t\t\t fl6-\u003eflowi6_l3mdev,\n-\t\t\t\t\t \u0026fl6-\u003esaddr);\n+\n+\t\tl3mdev = dev_get_by_index_rcu(net, fl6-\u003eflowi6_l3mdev);\n+\t\tif (!from || !from-\u003efib6_prefsrc.plen || l3mdev)\n+\t\t\tdev = from ? fib6_info_nh_dev(from) : NULL;\n+\t\tsame_vrf = !l3mdev || l3mdev_master_dev_rcu(dev) == l3mdev;\n+\t\tif (from \u0026\u0026 from-\u003efib6_prefsrc.plen \u0026\u0026 same_vrf) {\n+\t\t\tfl6-\u003esaddr = from-\u003efib6_prefsrc.addr;\n+\t\t\terr = 0;\n+\t\t} else\n+\t\t\terr = ipv6_fl_get_saddr(net, *dst,\n+\t\t\t\t\t\tsame_vrf ? dev : l3mdev, sk,\n+\t\t\t\t\t\tsk ? READ_ONCE(inet6_sk(sk)-\u003esrcprefs) : 0,\n+\t\t\t\t\t\tfl6);\n+\n \t\trcu_read_unlock();\n \n \t\tif (err)\ndiff --git a/net/ipv6/route.c b/net/ipv6/route.c\nindex a1301334da48c0..704afe2ddea7c2 100644\n--- a/net/ipv6/route.c\n+++ b/net/ipv6/route.c\n@@ -5850,9 +5850,19 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb,\n \t\t\tif (nla_put_u32(skb, RTA_IIF, iif))\n \t\t\t\tgoto nla_put_failure;\n \t} else if (dest) {\n-\t\tstruct in6_addr saddr_buf;\n-\t\tif (ip6_route_get_saddr(net, rt, dest, 0, 0, \u0026saddr_buf) == 0 \u0026\u0026\n-\t\t nla_put_in6_addr(skb, RTA_PREFSRC, \u0026saddr_buf))\n+\t\tstruct in6_addr saddr_buf, *saddr = NULL;\n+\n+\t\tif (rt-\u003efib6_prefsrc.plen) {\n+\t\t\tsaddr = \u0026rt-\u003efib6_prefsrc.addr;\n+\t\t} else {\n+\t\t\tstruct net_device *dev = fib6_info_nh_dev(rt);\n+\n+\t\t\tif (ipv6_dev_get_saddr(net, dev, dest, 0,\n+\t\t\t\t\t \u0026saddr_buf) == 0)\n+\t\t\t\tsaddr = \u0026saddr_buf;\n+\t\t}\n+\n+\t\tif (saddr \u0026\u0026 nla_put_in6_addr(skb, RTA_PREFSRC, saddr))\n \t\t\tgoto nla_put_failure;\n \t}\n \ndiff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile\nindex 708d960ae07d5b..c07d2aebad6957 100644\n--- a/tools/testing/selftests/net/Makefile\n+++ b/tools/testing/selftests/net/Makefile\n@@ -53,6 +53,7 @@ TEST_PROGS := \\\n \tipv6_flowlabel.sh \\\n \tipv6_force_forwarding.sh \\\n \tipv6_route_update_soft_lockup.sh \\\n+\tipv6_saddr_rfc6724rule5p5.py \\\n \tipvtap_test.sh \\\n \tl2_tos_ttl_inherit.sh \\\n \tl2tp.sh \\\ndiff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config\nindex e1ce35c2abbef3..b0b66412b9f3e2 100644\n--- a/tools/testing/selftests/net/config\n+++ b/tools/testing/selftests/net/config\n@@ -48,6 +48,7 @@ CONFIG_IPV6_ROUTER_PREF=y\n CONFIG_IPV6_RPL_LWTUNNEL=y\n CONFIG_IPV6_SEG6_LWTUNNEL=y\n CONFIG_IPV6_SIT=y\n+CONFIG_IPV6_SUBTREES=y\n CONFIG_IPV6_VTI=y\n CONFIG_IPVLAN=m\n CONFIG_IPVTAP=m\ndiff --git a/tools/testing/selftests/net/ipv6_saddr_rfc6724rule5p5.py b/tools/testing/selftests/net/ipv6_saddr_rfc6724rule5p5.py\nnew file mode 100644\nindex 00000000000000..682b39ba991da9\n--- /dev/null\n+++ b/tools/testing/selftests/net/ipv6_saddr_rfc6724rule5p5.py\n@@ -0,0 +1,231 @@\n+#!/usr/bin/env python3\n+# SPDX-License-Identifier: GPL-2.0\n+# Copyright (C) 2023-2026 David 'equinox' Lamparter\n+\"\"\"\n+RFC 6724 (IPv6 source address selection) rule 5.5 tests\n+\n+For reference:\n+\n+ Rule 5.5: Prefer addresses in a prefix advertised by the next-hop.\n+ If SA or SA's prefix is assigned by the selected next-hop that will\n+ be used to send to D and SB or SB's prefix is assigned by a different\n+ next-hop, then prefer SA. Similarly, if SB or SB's prefix is\n+ assigned by the next-hop that will be used to send to D and SA or\n+ SA's prefix is assigned by a different next-hop, then prefer SB.\n+\n+(and since it provides the \"counterpoint\":)\n+\n+ Rule 8: Use longest matching prefix.\n+ If CommonPrefixLen(SA, D) \u003e CommonPrefixLen(SB, D), then prefer SA.\n+ Similarly, if CommonPrefixLen(SB, D) \u003e CommonPrefixLen(SA, D), then\n+ prefer SB.\n+\n+Note rule 5.5 was originally optional but made mandatory by\n+draft-ietf-6man-rfc6724-update (which at the point of creation of this test\n+was already \"done\" at the IETF but waiting in the RFC editor queue due to a\n+blocking dependency.)\n+\"\"\"\n+\n+from socket import socket, AF_INET6, SOCK_DGRAM\n+from functools import wraps\n+from typing import Callable\n+\n+from lib.py import ksft_run, ksft_exit, ksft_eq\n+from lib.py import NetNS, NetNSEnter\n+from lib.py import ip\n+\n+\n+def select_addr(dest):\n+ \"\"\"\n+ connect() + getsockname() to figure out what was selected as source address\n+ \"\"\"\n+ sock = socket(AF_INET6, SOCK_DGRAM, 0)\n+ sock.connect((dest, 12345))\n+ return sock.getsockname()[0]\n+\n+\n+def in_netns(func: Callable[[], None]) -\u003e Callable[[], None]:\n+ \"\"\"\n+ python decorator to put test function in netns\n+ \"\"\"\n+\n+ @wraps(func)\n+ def wrapped() -\u003e None:\n+ with NetNS() as testns:\n+ with NetNSEnter(str(testns)):\n+ func()\n+\n+ return wrapped\n+\n+\n+@in_netns\n+def test_basic() -\u003e None:\n+ \"\"\"\n+ Simple \u0026 most common case for RFC6724 rule 5.5: multiple default routes\n+ \"\"\"\n+ ip(\"link add type veth\")\n+ ip(\"link set veth0 up\")\n+ ip(\"link set veth1 up\")\n+ ip(\"addr add 2001:db8:10::1/64 dev veth0 nodad\")\n+ ip(\"addr add 2001:db8:1000::1/64 dev veth0 nodad\")\n+ ip(\"-6 route add default via fe80::1 dev veth0 metric 100\")\n+ ip(\"-6 route add default via fe80::2 dev veth0 metric 200\")\n+ ip(\"-6 route add default from 2001:db8:10::/48 via fe80::1 dev veth0\")\n+ ip(\"-6 route add default from 2001:db8:1000::/48 via fe80::2 dev veth0\")\n+\n+ ksft_eq(select_addr(\"2001:db8:11::\"), \"2001:db8:10::1\", \"baseline pass\")\n+ # rule 8 would result in the use of the :1001: address, but rule 5.5 applies before.\n+ ksft_eq(select_addr(\"2001:db8:1001::\"), \"2001:db8:10::1\", \"rule 5.5 \u003e rule 8\")\n+\n+ ip(\"-6 route del default via fe80::1 dev veth0 metric 100\")\n+\n+ ksft_eq(select_addr(\"2001:db8:11::\"), \"2001:db8:1000::1\", \"baseline pass\")\n+ ksft_eq(select_addr(\"2001:db8:1001::\"), \"2001:db8:1000::1\", \"rule 5.5 \u003e rule 8\")\n+\n+\n+@in_netns\n+def test_nh_obj() -\u003e None:\n+ \"\"\"\n+ Same as above, but with nexthop objects for the default route\n+\n+ NB: The kernel doesn't currently allow nexthop objects for subtree routes.\n+ \"\"\"\n+\n+ ip(\"link add type veth\")\n+ ip(\"link set veth0 up\")\n+ ip(\"link set veth1 up\")\n+ ip(\"addr add 2001:db8:10::1/64 dev veth0 nodad\")\n+ ip(\"addr add 2001:db8:1000::1/64 dev veth0 nodad\")\n+\n+ # distinct nexthop objects are used, because what matters is the nexthop\n+ # itself, not the nexthop object. To cover everything, make a group.\n+ ip(\"nexthop add id 101 via fe80::1 dev veth0\")\n+ ip(\"nexthop add id 201 group 101\")\n+ ip(\"nexthop add id 102 via fe80::2 dev veth0\")\n+ ip(\"nexthop add id 202 group 102\")\n+\n+ ip(\"-6 route add default nhid 201 metric 100\")\n+ ip(\"-6 route add default nhid 202 metric 200\")\n+ ip(\"-6 route add default from 2001:db8:10::/48 via fe80::1 dev veth0\")\n+ ip(\"-6 route add default from 2001:db8:1000::/48 via fe80::2 dev veth0\")\n+\n+ ksft_eq(select_addr(\"2001:db8:11::\"), \"2001:db8:10::1\", \"baseline pass\")\n+ # rule 8 would result in the use of the :1001: address, but rule 5.5 applies before.\n+ ksft_eq(select_addr(\"2001:db8:1001::\"), \"2001:db8:10::1\", \"rule 5.5 \u003e rule 8\")\n+\n+ ip(\"-6 route del default nhid 201 metric 100\")\n+\n+ ksft_eq(select_addr(\"2001:db8:11::\"), \"2001:db8:1000::1\", \"baseline pass\")\n+ ksft_eq(select_addr(\"2001:db8:1001::\"), \"2001:db8:1000::1\", \"rule 5.5 \u003e rule 8\")\n+\n+\n+@in_netns\n+def test_low_metric() -\u003e None:\n+ \"\"\"\n+ Check that subtree routes take effect even if they are higher metric\n+\n+ For checking that \"router advertised prefix\", metric is irrelevant. It\n+ matters for the initial unspecific lookup to find a nexthop to begin with.\n+ (The later source address check lookup doesn't change the nexthop, i.e.\n+ the effects of metrics are already done.)\n+ \"\"\"\n+ ip(\"link add type veth\")\n+ ip(\"link set veth0 up\")\n+ ip(\"link set veth1 up\")\n+ ip(\"addr add 2001:db8:10::1/64 dev veth0 nodad\")\n+ ip(\"addr add 2001:db8:1000::1/64 dev veth0 nodad\")\n+ ip(\"-6 route add default via fe80::1 dev veth0 metric 100\")\n+ ip(\"-6 route add default via fe80::2 dev veth0 metric 200\")\n+ ip(\"-6 route add default from 2001:db8:10::/48 via fe80::1 dev veth0\")\n+ ip(\"-6 route add default from 2001:db8:1000::/48 via fe80::2 dev veth0 metric 1000\")\n+ ip(\"-6 route add default from 2001:db8:1000::/48 via fe80::3 dev veth0 metric 50\")\n+\n+ ksft_eq(select_addr(\"2001:db8:11::\"), \"2001:db8:10::1\", \"baseline pass\")\n+ # rule 8 would result in the use of the :1001: address, but rule 5.5 applies before.\n+ ksft_eq(select_addr(\"2001:db8:1001::\"), \"2001:db8:10::1\", \"rule 5.5 \u003e rule 8\")\n+\n+ ip(\"-6 route del default via fe80::1 dev veth0 metric 100\")\n+\n+ ksft_eq(select_addr(\"2001:db8:11::\"), \"2001:db8:1000::1\", \"baseline pass\")\n+ ksft_eq(select_addr(\"2001:db8:1001::\"), \"2001:db8:1000::1\", \"rule 5.5 \u003e rule 8\")\n+\n+\n+@in_netns\n+def test_no_subtree() -\u003e None:\n+ \"\"\"\n+ Ensure that matching on a non-subtree route doesn't trigger rule 5.5\n+\n+ (This was non-obviously broken in earlier versions of the implementations,\n+ a non-subtree route would still match. Make sure it doesn't break again.)\n+ \"\"\"\n+ ip(\"link add type veth\")\n+ ip(\"link set veth0 up\")\n+ ip(\"link set veth1 up\")\n+ ip(\"addr add 2001:db8:10::1/64 dev veth0 nodad\")\n+ ip(\"addr add 2001:db8:1000::1/64 dev veth0 nodad\")\n+ ip(\"-6 route add default via fe80::1 dev veth0 metric 100\")\n+ ip(\"-6 route add default via fe80::2 dev veth0 metric 200\")\n+ ip(\"-6 route add default from 2001:db8:10::/48 via fe80::1 dev veth0\")\n+\n+ ksft_eq(select_addr(\"2001:db8:11::\"), \"2001:db8:10::1\", \"baseline pass\")\n+ ksft_eq(\n+ select_addr(\"2001:db8:1001::\"),\n+ \"2001:db8:10::1\",\n+ \"rule 5.5 \u003e rule 8, ignoring non-SADR\",\n+ )\n+\n+\n+@in_netns\n+def test_longer() -\u003e None:\n+ \"\"\"\n+ Check functionality for non-default destination.\n+\n+ This is expected to be very rare in actual practice, and doesn't do\n+ backtracking (also refer to kernel docs.)\n+ \"\"\"\n+ ip(\"link add type veth\")\n+ ip(\"link set veth0 up\")\n+ ip(\"link set veth1 up\")\n+ ip(\"addr add 2001:db8:10::1/64 dev veth0 nodad\")\n+ ip(\"addr add 2001:db8:1000::1/64 dev veth0 nodad\")\n+ ip(\"-6 route add default via fe80::1 dev veth0 metric 100\")\n+ ip(\"-6 route add 2001:db8:500::/48 via fe80::2 dev veth0 metric 100\")\n+ ip(\"-6 route add default from 2001:db8:10::/48 via fe80::1 dev veth0\")\n+ ip(\"-6 route add 2001:db8:500::/48 from 2001:db8:1000::/48 via fe80::2 dev veth0\")\n+\n+ ksft_eq(select_addr(\"2001:db8:11::\"), \"2001:db8:10::1\", \"baseline pass\")\n+ ksft_eq(select_addr(\"2001:db8:500::\"), \"2001:db8:1000::1\", \"rule 5.5\")\n+ ksft_eq(select_addr(\"2001:db8:500:aaa::\"), \"2001:db8:1000::1\", \"rule 5.5\")\n+\n+ ip(\"-6 route add 2001:db8:500:aaa::/64 via fe80::2 dev veth0 metric 100\")\n+\n+ ksft_eq(select_addr(\"2001:db8:11::\"), \"2001:db8:10::1\", \"baseline pass\")\n+ ksft_eq(select_addr(\"2001:db8:500::\"), \"2001:db8:1000::1\", \"rule 5.5\")\n+ ksft_eq(select_addr(\"2001:db8:500:aaa::\"), \"2001:db8:10::1\", \"no backtracking\")\n+\n+ ip(\"-6 route del 2001:db8:500::/48 from 2001:db8:1000::/48 via fe80::2 dev veth0\")\n+ ip(\"-6 route add default from 2001:db8:1000::/48 via fe80::2 dev veth0\")\n+\n+ ksft_eq(select_addr(\"2001:db8:11::\"), \"2001:db8:10::1\", \"baseline pass\")\n+ ksft_eq(select_addr(\"2001:db8:500::\"), \"2001:db8:10::1\", \"no backtracking\")\n+ ksft_eq(select_addr(\"2001:db8:500:aaa::\"), \"2001:db8:10::1\", \"no backtracking\")\n+\n+\n+def main() -\u003e None:\n+ \"\"\"\n+ RFC6724 rule 5.5 test driver\n+ \"\"\"\n+ ksft_run(\n+ [\n+ test_basic,\n+ test_nh_obj,\n+ test_low_metric,\n+ test_no_subtree,\n+ test_longer,\n+ ]\n+ )\n+ ksft_exit()\n+\n+\n+if __name__ == \"__main__\":\n+ main()\n"
} |
| 2/1 | 2026/07/14 15:17 | agent | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: {
"EnableConfigs": [
"IPV6_SUBTREES",
"IPV6_MULTIPLE_TABLES"
],
"FocusSymbols": [
"ipv6_saddr_rule5p5",
"ipv6_fl_get_saddr",
"ipv6_dev_get_saddr",
"rt6_fill_node"
],
"Reasoning": "The patch implements RFC6724 rule 5.5 for IPv6 source address selection by introducing `ipv6_saddr_rule5p5` and `ipv6_fl_get_saddr`. It modifies core IPv6 routing and address selection logic, which is reachable from userspace via socket operations (e.g., connect, sendmsg) and netlink route dumps (`rt6_fill_node`). The feature requires `IPV6_SUBTREES` and `IPV6_MULTIPLE_TABLES` to be enabled.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 9a4d5442c02b39b92582fe11ec8af22b4fadc878
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Jul 14 15:17:25 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/networking/ipv6-addrsel.rst b/Documentation/networking/ipv6-addrsel.rst
new file mode 100644
index 00000000000000..bed032e695705c
--- /dev/null
+++ b/Documentation/networking/ipv6-addrsel.rst
@@ -0,0 +1,75 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+====================================
+IPv6 source address selection trivia
+====================================
+
+
+RFC6724 rule 5.5 support
+------------------------
+
+RFC6724 rule 5.5 is a very short paragraph in a complex RFC that has turned
+out quite tricky, but also immensely useful in multihoming scenarios. For
+reference, it says:
+
+::
+
+ Rule 5.5: Prefer addresses in a prefix advertised by the next-hop.
+ If SA or SA's prefix is assigned by the selected next-hop that will
+ be used to send to D and SB or SB's prefix is assigned by a different
+ next-hop, then prefer SA. Similarly, if SB or SB's prefix is
+ assigned by the next-hop that will be used to send to D and SA or
+ SA's prefix is assigned by a different next-hop, then prefer SB.
+
+The way this works on Linux is as follows:
+
+- prior to any source address selection happening, when receiving a RA, more
+ than the installation of a default route (or ::/128 route) needs to happen:
+ for each PIO, a source-specific (subtree) route is *additionally* installed.
+ The effect of this is that *after* a source address has been selected, one
+ of the routers that advertised it will remain in use (this is *not* RFC 6724
+ related, but rather RFC 8028.) At the same time, these extra routes serve
+ to remember which router advertised what.
+
+- per usual, a route lookup for the IPv6 destination address in consideration
+ is done first. This is passed around in kernel as a dst_entry.
+
+- the source address selection code iterates through the various rules in
+ RFC 6724.
+
+- if/when rule 5.5 is reached, first of all, there is a check if *any* source
+ specific routes exist in the routing table. If there are none, the entire
+ code for 5.5 is skipped because it cannot have any effect, but is not free
+ to execute (can involve multiple routing lookups.) **In applications that
+ use a lot of unbound (e.g. UDP) sockets, installing subtree routes should
+ therefore be avoided to not incur this cost on each source address selection
+ pass.** Alternatively, applications should bind their sockets to a specific
+ source address such that the selection code is never hit.
+
+- if subtree routes do exist, the source address selection code now repeats
+ the routing lookup done before source address selection is entered, except
+ with the source address under consideration filled in. This lookup will hit
+ the subtree routes that were installed (see first item), giving a fresh
+ dst_entry. If the new dst_entry matches the original dst_entry, that means
+ the original router has in fact sent RAs with PIOs for this source address,
+ so it is preferred. Otherwise it is not.
+
+
+There are a few caveats to consider:
+
+- the kernel currently does not create the subtree routes mentioned in the
+ first item. This is a separate work item, partially done at the time of
+ writing this. But this can equally well be performed in userspace processing
+ of RAs, e.g. NetworkManager or plain static configuration.
+
+- since addresses can also be acquired from DHCPv6, even RA/PIO combinations
+ that didn't result in the creation of any addresses (e.g. A=0) should have
+ subtree routes added. Those routes *may* be relevant for DHCPv6-generated
+ addresses.
+
+- the "announce check" lookup does not backtrack. Only the destination prefix
+ that provided the "unspecific" (::/128) match is checked for source prefixes
+ to see what routers advertised what. This means that for e.g. RIOs, subtree
+ routes also have to be created. (Backtracking for this case would further
+ increase the cost of source address selection, for a pretty rare situation
+ that has an easy fix/workaround.)
diff --git a/MAINTAINERS b/MAINTAINERS
index f3218abefd0c6b..4edf48362a0732 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18912,6 +18912,7 @@ F: Documentation/netlink/specs/rt-addr.yaml
F: Documentation/netlink/specs/rt-neigh.yaml
F: Documentation/netlink/specs/rt-route.yaml
F: Documentation/netlink/specs/rt-rule.yaml
+F: Documentation/networking/ipv6-addrsel.rst
F: include/linux/inetdevice.h
F: include/linux/mroute*
F: include/net/addrconf.h
diff --git a/include/net/addrconf.h b/include/net/addrconf.h
index 8ced27a8229b6e..7bc0711b871796 100644
--- a/include/net/addrconf.h
+++ b/include/net/addrconf.h
@@ -142,6 +142,10 @@ struct inet6_ifaddr *ipv6_get_ifaddr(struct net *net,
int ipv6_dev_get_saddr(struct net *net, const struct net_device *dev,
const struct in6_addr *daddr, unsigned int srcprefs,
struct in6_addr *saddr);
+int ipv6_fl_get_saddr(struct net *net, const struct dst_entry *dst,
+ const struct net_device *dst_dev,
+ const struct sock *sk, unsigned int srcprefs,
+ struct flowi6 *fl6);
int ipv6_get_lladdr(struct net_device *dev, struct in6_addr *addr,
u32 banned_flags);
bool inet_rcv_saddr_equal(const struct sock *sk, const struct sock *sk2,
diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index 09ffe0f13ce752..fe13cb1d3257db 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -140,32 +140,6 @@ void rt6_flush_exceptions(struct fib6_info *f6i);
void rt6_age_exceptions(struct fib6_info *f6i, struct fib6_gc_args *gc_args,
unsigned long now);
-static inline int ip6_route_get_saddr(struct net *net, struct fib6_info *f6i,
- const struct in6_addr *daddr,
- unsigned int prefs, int l3mdev_index,
- struct in6_addr *saddr)
-{
- struct net_device *l3mdev;
- struct net_device *dev;
- bool same_vrf;
- int err = 0;
-
- rcu_read_lock();
-
- l3mdev = dev_get_by_index_rcu(net, l3mdev_index);
- if (!f6i || !f6i->fib6_prefsrc.plen || l3mdev)
- dev = f6i ? fib6_info_nh_dev(f6i) : NULL;
- same_vrf = !l3mdev || l3mdev_master_dev_rcu(dev) == l3mdev;
- if (f6i && f6i->fib6_prefsrc.plen && same_vrf)
- *saddr = f6i->fib6_prefsrc.addr;
- else
- err = ipv6_dev_get_saddr(net, same_vrf ? dev : l3mdev, daddr, prefs, saddr);
-
- rcu_read_unlock();
-
- return err;
-}
-
struct rt6_info *rt6_lookup(struct net *net, const struct in6_addr *daddr,
const struct in6_addr *saddr, int oif,
const struct sk_buff *skb, int flags);
diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig
index c3806c6ac96f95..39659e9b53a3db 100644
--- a/net/ipv6/Kconfig
+++ b/net/ipv6/Kconfig
@@ -243,16 +243,26 @@ config IPV6_MULTIPLE_TABLES
config IPV6_SUBTREES
bool "IPv6: source address based routing"
depends on IPV6_MULTIPLE_TABLES
+ default y
help
Enable routing by source address or prefix.
The destination address is still the primary routing key, so mixing
normal and source prefix specific routes in the same routing table
- may sometimes lead to unintended routing behavior. This can be
- avoided by defining different routing tables for the normal and
- source prefix specific routes.
+ may sometimes lead to unintended routing behavior if the order of
+ lookups is misunderstood. To achieve full separation based on source
+ address, different routing tables should be defined for each source
+ prefix.
- If unsure, say N.
+ Subtrees specifically address scenarios where local connectivity
+ between source prefixes is shared and unrestricted, but some
+ destinations (e.g. the default route) have more fine-grained rules.
+
+ This feature is required to correctly support RFC6724 rule 5.5 in
+ source address selection. Rule 5.5 is a necessity for good
+ multihoming, renumbering and source prefix liveliness checks.
+
+ If unsure, say Y.
config IPV6_MROUTE
bool "IPv6: multicast routing"
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index f1fe9ede1edb37..f5c04928aa142f 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -1522,6 +1522,7 @@ enum {
IPV6_SADDR_RULE_HOA,
#endif
IPV6_SADDR_RULE_OIF,
+ IPV6_SADDR_RULE_PIO_BY_NEXTHOP,
IPV6_SADDR_RULE_LABEL,
IPV6_SADDR_RULE_PRIVACY,
IPV6_SADDR_RULE_ORCHID,
@@ -1542,11 +1543,14 @@ struct ipv6_saddr_score {
};
struct ipv6_saddr_dst {
- const struct in6_addr *addr;
+ struct flowi6 *fl6;
+ const struct dst_entry *dst;
+ const struct sock *sk;
int ifindex;
int scope;
int label;
unsigned int prefs;
+ struct net *net;
};
static inline int ipv6_saddr_preferred(int type)
@@ -1591,6 +1595,80 @@ static bool ipv6_allow_optimistic_dad(const struct net *net,
#endif
}
+/* "source address is preferable if the chosen nexthop advertised it as a PIO"
+ * => consider 'advertised as a PIO' to be 'the routes for the source's subtree
+ * include one with the same nexthop'
+ *
+ * NB: there is no backtracking in the subtree here, this is intentional -
+ * each prefix seen (and accepted) in PIOs creates essentially a "zone" which
+ * is our search scope.
+ */
+static int ipv6_saddr_rule5p5(struct ipv6_saddr_score *score,
+ struct ipv6_saddr_dst *saddr_dst)
+{
+ const struct rt6_info *rt, *cmp_rt;
+ struct dst_entry *cmp_dst;
+ struct fib6_info *f6i;
+ int ret = 0;
+
+ rt = container_of(saddr_dst->dst, struct rt6_info, dst);
+
+ /* fl6->saddr is ::, cf. check at the top of ipv6_common_get_saddr() */
+ saddr_dst->fl6->saddr = score->ifa->addr;
+ cmp_dst = ip6_route_output(saddr_dst->net, saddr_dst->sk,
+ saddr_dst->fl6);
+ memset(&saddr_dst->fl6->saddr, 0, sizeof(saddr_dst->fl6->saddr));
+
+ if (cmp_dst->error)
+ goto out_release_dst;
+
+ cmp_rt = container_of(cmp_dst, struct rt6_info, dst);
+
+ /* this must work if _any_ nexthop matches; the non-subtree best may
+ * not be in same order as subtree best
+ */
+ for (f6i = rcu_dereference(cmp_rt->from); f6i;
+ f6i = rcu_dereference(f6i->fib6_next)) {
+ struct fib6_nh *f6n = f6i->fib6_nh;
+ struct fib6_info *sibling;
+
+ /* non-subtree route: says nothing about router advertising this source */
+ if (f6i->fib6_src.plen == 0)
+ continue;
+
+ if (f6n->nh_common.nhc_dev != saddr_dst->dst->dev ||
+ f6n->nh_common.nhc_gw_family != AF_INET6)
+ continue;
+
+ if (ipv6_addr_equal(&f6n->nh_common.nhc_gw.ipv6,
+ &rt->rt6i_gateway)) {
+ ret = 1;
+ goto out_release_dst;
+ }
+
+ if (!f6i->fib6_nsiblings)
+ continue;
+
+ list_for_each_entry(sibling, &f6i->fib6_siblings, fib6_siblings) {
+ f6n = sibling->fib6_nh;
+
+ if (f6n->nh_common.nhc_dev != saddr_dst->dst->dev ||
+ f6n->nh_common.nhc_gw_family != AF_INET6)
+ continue;
+
+ if (ipv6_addr_equal(&f6n->nh_common.nhc_gw.ipv6,
+ &rt->rt6i_gateway)) {
+ ret = 1;
+ goto out_release_dst;
+ }
+ }
+ }
+
+out_release_dst:
+ dst_release(cmp_dst);
+ return ret;
+}
+
static int ipv6_get_saddr_eval(struct net *net,
struct ipv6_saddr_score *score,
struct ipv6_saddr_dst *dst,
@@ -1619,7 +1697,7 @@ static int ipv6_get_saddr_eval(struct net *net,
break;
case IPV6_SADDR_RULE_LOCAL:
/* Rule 1: Prefer same address */
- ret = ipv6_addr_equal(&score->ifa->addr, dst->addr);
+ ret = ipv6_addr_equal(&score->ifa->addr, &dst->fl6->daddr);
break;
case IPV6_SADDR_RULE_SCOPE:
/* Rule 2: Prefer appropriate scope
@@ -1675,6 +1753,24 @@ static int ipv6_get_saddr_eval(struct net *net,
ret = (!dst->ifindex ||
dst->ifindex == score->ifa->idev->dev->ifindex);
break;
+ case IPV6_SADDR_RULE_PIO_BY_NEXTHOP:
+ /* Rule 5.5: Prefer sources advertised by chosen next-hop */
+
+ /* Without subtrees, the source address will make no difference
+ * in the ip6_route_output call in rule5p5. Therefore the rule
+ * 5.5 check becomes useless. This wouldn't result in any
+ * errors, but ip6_route_output isn't free, so if subtrees are
+ * disabled save some cycles by skipping this entirely.
+ *
+ * This is done through subtrees_enabled to have the code
+ * compiled regardless.
+ */
+ if (fib6_routes_require_src(net) && dst->dst
+ && !dst->dst->error)
+ ret = ipv6_saddr_rule5p5(score, dst);
+ else
+ ret = 1;
+ break;
case IPV6_SADDR_RULE_LABEL:
/* Rule 6: Prefer matching label */
ret = ipv6_addr_label(net,
@@ -1697,11 +1793,11 @@ static int ipv6_get_saddr_eval(struct net *net,
* non-ORCHID vs non-ORCHID
*/
ret = !(ipv6_addr_orchid(&score->ifa->addr) ^
- ipv6_addr_orchid(dst->addr));
+ ipv6_addr_orchid(&dst->fl6->daddr));
break;
case IPV6_SADDR_RULE_PREFIX:
/* Rule 8: Use longest matching prefix */
- ret = ipv6_addr_diff(&score->ifa->addr, dst->addr);
+ ret = ipv6_addr_diff(&score->ifa->addr, &dst->fl6->daddr);
if (ret > score->ifa->prefix_len)
ret = score->ifa->prefix_len;
score->matchlen = ret;
@@ -1819,9 +1915,10 @@ static int ipv6_get_saddr_master(struct net *net,
return hiscore_idx;
}
-int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,
- const struct in6_addr *daddr, unsigned int prefs,
- struct in6_addr *saddr)
+int ipv6_fl_get_saddr(struct net *net, const struct dst_entry *dst_entry,
+ const struct net_device *dst_dev,
+ const struct sock *sk, unsigned int prefs,
+ struct flowi6 *fl6)
{
struct ipv6_saddr_score scores[2], *hiscore;
struct ipv6_saddr_dst dst;
@@ -1832,11 +1929,18 @@ int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,
int hiscore_idx = 0;
int ret = 0;
- dst_type = __ipv6_addr_type(daddr);
- dst.addr = daddr;
+ /* we should never end up here with a non-empty saddr. */
+ if (WARN_ON_ONCE(!ipv6_addr_any(&fl6->saddr)))
+ return 0;
+
+ dst_type = __ipv6_addr_type(&fl6->daddr);
+ dst.net = net;
+ dst.fl6 = fl6;
+ dst.sk = sk;
+ dst.dst = dst_entry;
dst.ifindex = dst_dev ? dst_dev->ifindex : 0;
dst.scope = __ipv6_addr_src_scope(dst_type);
- dst.label = ipv6_addr_label(net, daddr, dst_type, dst.ifindex);
+ dst.label = ipv6_addr_label(net, &fl6->daddr, dst_type, dst.ifindex);
dst.prefs = prefs;
scores[hiscore_idx].rule = -1;
@@ -1911,11 +2015,29 @@ int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,
if (!hiscore->ifa)
ret = -EADDRNOTAVAIL;
else
- *saddr = hiscore->ifa->addr;
+ fl6->saddr = hiscore->ifa->addr;
rcu_read_unlock();
return ret;
}
+EXPORT_SYMBOL(ipv6_fl_get_saddr);
+
+int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,
+ const struct in6_addr *daddr, unsigned int prefs,
+ struct in6_addr *saddr)
+{
+ struct flowi6 fl6;
+ int ret;
+
+ memset(&fl6, 0, sizeof(fl6));
+ fl6.daddr = *daddr;
+
+ ret = ipv6_fl_get_saddr(net, NULL, dst_dev, NULL, prefs, &fl6);
+ if (!ret)
+ *saddr = fl6.saddr;
+
+ return ret;
+}
EXPORT_SYMBOL(ipv6_dev_get_saddr);
static int __ipv6_get_lladdr(struct inet6_dev *idev, struct in6_addr *addr,
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index a130cdfaebfbee..273f2bfc528669 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -1656,8 +1656,11 @@ static struct fib6_node *fib6_node_lookup_1(struct fib6_node *root,
struct fib6_node *sfn;
sfn = fib6_node_lookup_1(subtree,
args + 1);
- if (!sfn)
+ if (!sfn) {
+ if (fn->fn_flags & RTN_RTINFO)
+ return fn;
goto backtrack;
+ }
fn = sfn;
}
#endif
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 368e4fa3b43ca2..03b8d42d9df9ef 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -1143,27 +1143,40 @@ static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk,
int flags = 0;
/* The correct way to handle this would be to do
- * ip6_route_get_saddr, and then ip6_route_output; however,
+ * ipv6_fl_get_saddr, and then ip6_route_output; however,
* the route-specific preferred source forces the
- * ip6_route_output call _before_ ip6_route_get_saddr.
+ * ip6_route_output call _before_ ipv6_fl_get_saddr.
*
* In source specific routing (no src=any default route),
* ip6_route_output will fail given src=any saddr, though, so
* that's why we try it again later.
*/
if (ipv6_addr_any(&fl6->saddr)) {
+ struct net_device *l3mdev;
+ struct net_device *dev;
struct fib6_info *from;
struct rt6_info *rt;
+ bool same_vrf;
*dst = ip6_route_output(net, sk, fl6);
rt = (*dst)->error ? NULL : dst_rt6_info(*dst);
rcu_read_lock();
from = rt ? rcu_dereference(rt->from) : NULL;
- err = ip6_route_get_saddr(net, from, &fl6->daddr,
- sk ? READ_ONCE(inet6_sk(sk)->srcprefs) : 0,
- fl6->flowi6_l3mdev,
- &fl6->saddr);
+
+ l3mdev = dev_get_by_index_rcu(net, fl6->flowi6_l3mdev);
+ if (!from || !from->fib6_prefsrc.plen || l3mdev)
+ dev = from ? fib6_info_nh_dev(from) : NULL;
+ same_vrf = !l3mdev || l3mdev_master_dev_rcu(dev) == l3mdev;
+ if (from && from->fib6_prefsrc.plen && same_vrf) {
+ fl6->saddr = from->fib6_prefsrc.addr;
+ err = 0;
+ } else
+ err = ipv6_fl_get_saddr(net, *dst,
+ same_vrf ? dev : l3mdev, sk,
+ sk ? READ_ONCE(inet6_sk(sk)->srcprefs) : 0,
+ fl6);
+
rcu_read_unlock();
if (err)
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index a1301334da48c0..704afe2ddea7c2 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -5850,9 +5850,19 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb,
if (nla_put_u32(skb, RTA_IIF, iif))
goto nla_put_failure;
} else if (dest) {
- struct in6_addr saddr_buf;
- if (ip6_route_get_saddr(net, rt, dest, 0, 0, &saddr_buf) == 0 &&
- nla_put_in6_addr(skb, RTA_PREFSRC, &saddr_buf))
+ struct in6_addr saddr_buf, *saddr = NULL;
+
+ if (rt->fib6_prefsrc.plen) {
+ saddr = &rt->fib6_prefsrc.addr;
+ } else {
+ struct net_device *dev = fib6_info_nh_dev(rt);
+
+ if (ipv6_dev_get_saddr(net, dev, dest, 0,
+ &saddr_buf) == 0)
+ saddr = &saddr_buf;
+ }
+
+ if (saddr && nla_put_in6_addr(skb, RTA_PREFSRC, saddr))
goto nla_put_failure;
}
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 708d960ae07d5b..c07d2aebad6957 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -53,6 +53,7 @@ TEST_PROGS := \
ipv6_flowlabel.sh \
ipv6_force_forwarding.sh \
ipv6_route_update_soft_lockup.sh \
+ ipv6_saddr_rfc6724rule5p5.py \
ipvtap_test.sh \
l2_tos_ttl_inherit.sh \
l2tp.sh \
diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index e1ce35c2abbef3..b0b66412b9f3e2 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -48,6 +48,7 @@ CONFIG_IPV6_ROUTER_PREF=y
CONFIG_IPV6_RPL_LWTUNNEL=y
CONFIG_IPV6_SEG6_LWTUNNEL=y
CONFIG_IPV6_SIT=y
+CONFIG_IPV6_SUBTREES=y
CONFIG_IPV6_VTI=y
CONFIG_IPVLAN=m
CONFIG_IPVTAP=m
diff --git a/tools/testing/selftests/net/ipv6_saddr_rfc6724rule5p5.py b/tools/testing/selftests/net/ipv6_saddr_rfc6724rule5p5.py
new file mode 100644
index 00000000000000..682b39ba991da9
--- /dev/null
+++ b/tools/testing/selftests/net/ipv6_saddr_rfc6724rule5p5.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2023-2026 David 'equinox' Lamparter
+"""
+RFC 6724 (IPv6 source address selection) rule 5.5 tests
+
+For reference:
+
+ Rule 5.5: Prefer addresses in a prefix advertised by the next-hop.
+ If SA or SA's prefix is assigned by the selected next-hop that will
+ be used to send to D and SB or SB's prefix is assigned by a different
+ next-hop, then prefer SA. Similarly, if SB or SB's prefix is
+ assigned by the next-hop that will be used to send to D and SA or
+ SA's prefix is assigned by a different next-hop, then prefer SB.
+
+(and since it provides the "counterpoint":)
+
+ Rule 8: Use longest matching prefix.
+ If CommonPrefixLen(SA, D) > CommonPrefixLen(SB, D), then prefer SA.
+ Similarly, if CommonPrefixLen(SB, D) > CommonPrefixLen(SA, D), then
+ prefer SB.
+
+Note rule 5.5 was originally optional but made mandatory by
+draft-ietf-6man-rfc6724-update (which at the point of creation of this test
+was already "done" at the IETF but waiting in the RFC editor queue due to a
+blocking dependency.)
+"""
+
+from socket import socket, AF_INET6, SOCK_DGRAM
+from functools import wraps
+from typing import Callable
+
+from lib.py import ksft_run, ksft_exit, ksft_eq
+from lib.py import NetNS, NetNSEnter
+from lib.py import ip
+
+
+def select_addr(dest):
+ """
+ connect() + getsockname() to figure out what was selected as source address
+ """
+ sock = socket(AF_INET6, SOCK_DGRAM, 0)
+ sock.connect((dest, 12345))
+ return sock.getsockname()[0]
+
+
+def in_netns(func: Callable[[], None]) -> Callable[[], None]:
+ """
+ python decorator to put test function in netns
+ """
+
+ @wraps(func)
+ def wrapped() -> None:
+ with NetNS() as testns:
+ with NetNSEnter(str(testns)):
+ func()
+
+ return wrapped
+
+
+@in_netns
+def test_basic() -> None:
+ """
+ Simple & most common case for RFC6724 rule 5.5: multiple default routes
+ """
+ ip("link add type veth")
+ ip("link set veth0 up")
+ ip("link set veth1 up")
+ ip("addr add 2001:db8:10::1/64 dev veth0 nodad")
+ ip("addr add 2001:db8:1000::1/64 dev veth0 nodad")
+ ip("-6 route add default via fe80::1 dev veth0 metric 100")
+ ip("-6 route add default via fe80::2 dev veth0 metric 200")
+ ip("-6 route add default from 2001:db8:10::/48 via fe80::1 dev veth0")
+ ip("-6 route add default from 2001:db8:1000::/48 via fe80::2 dev veth0")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:10::1", "baseline pass")
+ # rule 8 would result in the use of the :1001: address, but rule 5.5 applies before.
+ ksft_eq(select_addr("2001:db8:1001::"), "2001:db8:10::1", "rule 5.5 > rule 8")
+
+ ip("-6 route del default via fe80::1 dev veth0 metric 100")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:1000::1", "baseline pass")
+ ksft_eq(select_addr("2001:db8:1001::"), "2001:db8:1000::1", "rule 5.5 > rule 8")
+
+
+@in_netns
+def test_nh_obj() -> None:
+ """
+ Same as above, but with nexthop objects for the default route
+
+ NB: The kernel doesn't currently allow nexthop objects for subtree routes.
+ """
+
+ ip("link add type veth")
+ ip("link set veth0 up")
+ ip("link set veth1 up")
+ ip("addr add 2001:db8:10::1/64 dev veth0 nodad")
+ ip("addr add 2001:db8:1000::1/64 dev veth0 nodad")
+
+ # distinct nexthop objects are used, because what matters is the nexthop
+ # itself, not the nexthop object. To cover everything, make a group.
+ ip("nexthop add id 101 via fe80::1 dev veth0")
+ ip("nexthop add id 201 group 101")
+ ip("nexthop add id 102 via fe80::2 dev veth0")
+ ip("nexthop add id 202 group 102")
+
+ ip("-6 route add default nhid 201 metric 100")
+ ip("-6 route add default nhid 202 metric 200")
+ ip("-6 route add default from 2001:db8:10::/48 via fe80::1 dev veth0")
+ ip("-6 route add default from 2001:db8:1000::/48 via fe80::2 dev veth0")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:10::1", "baseline pass")
+ # rule 8 would result in the use of the :1001: address, but rule 5.5 applies before.
+ ksft_eq(select_addr("2001:db8:1001::"), "2001:db8:10::1", "rule 5.5 > rule 8")
+
+ ip("-6 route del default nhid 201 metric 100")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:1000::1", "baseline pass")
+ ksft_eq(select_addr("2001:db8:1001::"), "2001:db8:1000::1", "rule 5.5 > rule 8")
+
+
+@in_netns
+def test_low_metric() -> None:
+ """
+ Check that subtree routes take effect even if they are higher metric
+
+ For checking that "router advertised prefix", metric is irrelevant. It
+ matters for the initial unspecific lookup to find a nexthop to begin with.
+ (The later source address check lookup doesn't change the nexthop, i.e.
+ the effects of metrics are already done.)
+ """
+ ip("link add type veth")
+ ip("link set veth0 up")
+ ip("link set veth1 up")
+ ip("addr add 2001:db8:10::1/64 dev veth0 nodad")
+ ip("addr add 2001:db8:1000::1/64 dev veth0 nodad")
+ ip("-6 route add default via fe80::1 dev veth0 metric 100")
+ ip("-6 route add default via fe80::2 dev veth0 metric 200")
+ ip("-6 route add default from 2001:db8:10::/48 via fe80::1 dev veth0")
+ ip("-6 route add default from 2001:db8:1000::/48 via fe80::2 dev veth0 metric 1000")
+ ip("-6 route add default from 2001:db8:1000::/48 via fe80::3 dev veth0 metric 50")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:10::1", "baseline pass")
+ # rule 8 would result in the use of the :1001: address, but rule 5.5 applies before.
+ ksft_eq(select_addr("2001:db8:1001::"), "2001:db8:10::1", "rule 5.5 > rule 8")
+
+ ip("-6 route del default via fe80::1 dev veth0 metric 100")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:1000::1", "baseline pass")
+ ksft_eq(select_addr("2001:db8:1001::"), "2001:db8:1000::1", "rule 5.5 > rule 8")
+
+
+@in_netns
+def test_no_subtree() -> None:
+ """
+ Ensure that matching on a non-subtree route doesn't trigger rule 5.5
+
+ (This was non-obviously broken in earlier versions of the implementations,
+ a non-subtree route would still match. Make sure it doesn't break again.)
+ """
+ ip("link add type veth")
+ ip("link set veth0 up")
+ ip("link set veth1 up")
+ ip("addr add 2001:db8:10::1/64 dev veth0 nodad")
+ ip("addr add 2001:db8:1000::1/64 dev veth0 nodad")
+ ip("-6 route add default via fe80::1 dev veth0 metric 100")
+ ip("-6 route add default via fe80::2 dev veth0 metric 200")
+ ip("-6 route add default from 2001:db8:10::/48 via fe80::1 dev veth0")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:10::1", "baseline pass")
+ ksft_eq(
+ select_addr("2001:db8:1001::"),
+ "2001:db8:10::1",
+ "rule 5.5 > rule 8, ignoring non-SADR",
+ )
+
+
+@in_netns
+def test_longer() -> None:
+ """
+ Check functionality for non-default destination.
+
+ This is expected to be very rare in actual practice, and doesn't do
+ backtracking (also refer to kernel docs.)
+ """
+ ip("link add type veth")
+ ip("link set veth0 up")
+ ip("link set veth1 up")
+ ip("addr add 2001:db8:10::1/64 dev veth0 nodad")
+ ip("addr add 2001:db8:1000::1/64 dev veth0 nodad")
+ ip("-6 route add default via fe80::1 dev veth0 metric 100")
+ ip("-6 route add 2001:db8:500::/48 via fe80::2 dev veth0 metric 100")
+ ip("-6 route add default from 2001:db8:10::/48 via fe80::1 dev veth0")
+ ip("-6 route add 2001:db8:500::/48 from 2001:db8:1000::/48 via fe80::2 dev veth0")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:10::1", "baseline pass")
+ ksft_eq(select_addr("2001:db8:500::"), "2001:db8:1000::1", "rule 5.5")
+ ksft_eq(select_addr("2001:db8:500:aaa::"), "2001:db8:1000::1", "rule 5.5")
+
+ ip("-6 route add 2001:db8:500:aaa::/64 via fe80::2 dev veth0 metric 100")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:10::1", "baseline pass")
+ ksft_eq(select_addr("2001:db8:500::"), "2001:db8:1000::1", "rule 5.5")
+ ksft_eq(select_addr("2001:db8:500:aaa::"), "2001:db8:10::1", "no backtracking")
+
+ ip("-6 route del 2001:db8:500::/48 from 2001:db8:1000::/48 via fe80::2 dev veth0")
+ ip("-6 route add default from 2001:db8:1000::/48 via fe80::2 dev veth0")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:10::1", "baseline pass")
+ ksft_eq(select_addr("2001:db8:500::"), "2001:db8:10::1", "no backtracking")
+ ksft_eq(select_addr("2001:db8:500:aaa::"), "2001:db8:10::1", "no backtracking")
+
+
+def main() -> None:
+ """
+ RFC6724 rule 5.5 test driver
+ """
+ ksft_run(
+ [
+ test_basic,
+ test_nh_obj,
+ test_low_metric,
+ test_no_subtree,
+ test_longer,
+ ]
+ )
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
|
| 3/2 | 2026/07/14 15:17 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13149 output: 24 thoughts: 551 Thoughts: **My Analysis of the IPv6 Source Address Selection Patch** Okay, so I've got this patch here that's tweaking the IPv6 source address selection to align with RFC6724 rule 5.5. Looks like it's introducing `ipv6_fl_get_saddr`, and it's also altering the behavior of `ipv6_dev_get_saddr` and fundamentally changing `ip6_route_get_saddr` – removing and replacing it entirely, which is a significant architectural shift. It touches quite a few key files in the IPv6 networking stack, namely `net/ipv6/addrconf.c`, `net/ipv6/ip6_output.c`, `net/ipv6/route.c`, and `net/ipv6/ip6_fib.c`. My first concern, as always, is reachability and correctness. Given the core nature of these modifications, I need to verify this very carefully. It looks like the patch introduces `ipv6_saddr_rule5p5` within `net/ipv6/addrconf.c`. This is where the core logic of the new rule is implemented, specifically involving `ip6_route_output` and an iteration over `fib6_info`. I need to focus my fuzzing efforts. The critical functions here are: * `ipv6_saddr_rule5p5` * `ipv6_fl_get_saddr` * `ipv6_dev_get_saddr` * `ipv6_get_saddr_eval` Regarding the configuration, the patch seems to require `IPV6_SUBTREES`, indicated by changes in `net/ipv6/Kconfig`. It’s dependent on `IPV6_MULTIPLE_TABLES`. This is critical for RFC6724 rule 5.5 support. So, I need to ensure both `IPV6_SUBTREES` and `IPV6_MULTIPLE_TABLES` are enabled in my test setup. Now, let's see how `ipv6_saddr_rule5p5` is actually used. It's called from `ipv6_get_saddr_eval` under `IPV6_SADDR_RULE_PIO_BY_NEXTHOP`. `ipv6_get_saddr_eval` is then called from `ipv6_get_saddr_master`. Finally, `ipv6_get_saddr_master` is called by `ipv6_fl_get_saddr`, and crucially, `ipv6_fl_get_saddr` is called from `ip6_dst_lookup_tail` within `net/ipv6/ip6_output.c`. This last function is called from `ip6_dst_lookup_flow`. This provides the complete chain. I need to keep this chain in mind when configuring testcases and harnesses. Time to use my tools! |
| 4/2 | 2026/07/14 15:17 | tool | grepper |
0mArgs:{
"Expression": "ipv6_saddr_rule5p5"
}Results: {
"Output": "net/ipv6/addrconf.c=1582=static bool ipv6_allow_optimistic_dad(const struct net *net,\n--\nnet/ipv6/addrconf.c-1605- */\nnet/ipv6/addrconf.c:1606:static int ipv6_saddr_rule5p5(struct ipv6_saddr_score *score,\nnet/ipv6/addrconf.c-1607-\t\t\t struct ipv6_saddr_dst *saddr_dst)\n--\nnet/ipv6/addrconf.c=1672=static int ipv6_get_saddr_eval(struct net *net,\n--\nnet/ipv6/addrconf.c-1769-\t\t \u0026\u0026 !dst-\u003edst-\u003eerror)\nnet/ipv6/addrconf.c:1770:\t\t\tret = ipv6_saddr_rule5p5(score, dst);\nnet/ipv6/addrconf.c-1771-\t\telse\n"
} |
| 5/2 | 2026/07/14 15:17 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 14608 output: 23 thoughts: 45 Thoughts: **Examining the New IPv6 Source Address Features** Okay, so this patch introduces a couple of new pieces related to IPv6 source addresses: `ipv6_saddr_rule5p5` and `ipv6_fl_get_saddr`. My immediate thought is, let's understand how that second function, `ipv6_fl_get_saddr`, is being used. Where is this new functionality integrated into the existing flow setup? Understanding the call sites will be key to grasping its purpose and the implications of this change on existing network behavior and potential optimizations. I need to see the context. |
| 6/2 | 2026/07/14 15:17 | tool | grepper |
0mArgs:{
"Expression": "ipv6_fl_get_saddr"
}Results: {
"Output": "include/net/addrconf.h=142=int ipv6_dev_get_saddr(struct net *net, const struct net_device *dev,\n--\ninclude/net/addrconf.h-144-\t\t struct in6_addr *saddr);\ninclude/net/addrconf.h:145:int ipv6_fl_get_saddr(struct net *net, const struct dst_entry *dst,\ninclude/net/addrconf.h-146-\t\t const struct net_device *dst_dev,\n--\nnet/ipv6/addrconf.c=1896=static int ipv6_get_saddr_master(struct net *net,\n--\nnet/ipv6/addrconf.c-1917-\nnet/ipv6/addrconf.c:1918:int ipv6_fl_get_saddr(struct net *net, const struct dst_entry *dst_entry,\nnet/ipv6/addrconf.c-1919-\t\t const struct net_device *dst_dev,\n--\nnet/ipv6/addrconf.c-2022-}\nnet/ipv6/addrconf.c:2023:EXPORT_SYMBOL(ipv6_fl_get_saddr);\nnet/ipv6/addrconf.c-2024-\nnet/ipv6/addrconf.c=2025=int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,\n--\nnet/ipv6/addrconf.c-2034-\nnet/ipv6/addrconf.c:2035:\tret = ipv6_fl_get_saddr(net, NULL, dst_dev, NULL, prefs, \u0026fl6);\nnet/ipv6/addrconf.c-2036-\tif (!ret)\n--\nnet/ipv6/ip6_output.c=1135=static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk,\n--\nnet/ipv6/ip6_output.c-1145-\t/* The correct way to handle this would be to do\nnet/ipv6/ip6_output.c:1146:\t * ipv6_fl_get_saddr, and then ip6_route_output; however,\nnet/ipv6/ip6_output.c-1147-\t * the route-specific preferred source forces the\nnet/ipv6/ip6_output.c:1148:\t * ip6_route_output call _before_ ipv6_fl_get_saddr.\nnet/ipv6/ip6_output.c-1149-\t *\n--\nnet/ipv6/ip6_output.c-1174-\t\t} else\nnet/ipv6/ip6_output.c:1175:\t\t\terr = ipv6_fl_get_saddr(net, *dst,\nnet/ipv6/ip6_output.c-1176-\t\t\t\t\t\tsame_vrf ? dev : l3mdev, sk,\n"
} |
| 7/2 | 2026/07/14 15:17 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15553 output: 22 thoughts: 129 Thoughts: **Examining the IPv6 Source Address Selection Patch** Okay, so this patch is making some key changes to how IPv6 source addresses are handled. Specifically, I see it's refactoring `ipv6_dev_get_saddr` to call `ipv6_fl_get_saddr`. Then, it's also integrating `ipv6_fl_get_saddr` into `ip6_dst_lookup_tail`. This is promising; both of those functions are definitely within the hot path, so this should be hitting the desired use case and functionality. I need to confirm the configurations. It's using `IPV6_SUBTREES` and `IPV6_MULTIPLE_TABLES`. Hold on, I need to make sure those dependencies are properly handled. Yes, `IPV6_SUBTREES` relies on `IPV6_MULTIPLE_TABLES`. Alright, just need to quickly verify that in `net/ipv6/Kconfig` to be absolutely certain. |
| 8/2 | 2026/07/14 15:17 | tool | grepper |
0mArgs:{
"Expression": "config IPV6_SUBTREES"
}Results: {
"Output": "net/ipv6/Kconfig=237=config IPV6_MULTIPLE_TABLES\n--\nnet/ipv6/Kconfig-242-\nnet/ipv6/Kconfig:243:config IPV6_SUBTREES\nnet/ipv6/Kconfig-244-\tbool \"IPv6: source address based routing\"\n"
} |
| 9/2 | 2026/07/14 15:17 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 16011 output: 35 thoughts: 10 Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and presented as if it were my own internal monologue: **Checking for Config Overlap** Alright, I'm diving in. Before I make any significant changes or assumptions, a quick sanity check is essential. My first thought is: "Let's see if there are any *other* configuration files or settings at play here." I need to be sure I'm not overlooking something, especially in a complex system. I'm thinking about inheritance, cascading configurations, environment variables, or even some obscure overrides that might be influencing the current behavior. I need to ensure there are no conflicting configs or that I am not editing the wrong config in the first place, or that I can account for all the configs used. It's a fundamental step to avoid wasted time and potential conflicts down the line. It's about due diligence and preventing a frustrating debugging session later. |
| 10/2 | 2026/07/14 15:17 | tool | read-file |
0mArgs:{
"File": "net/ipv6/Kconfig",
"FirstLine": 237,
"LineCount": 20
}Results: {
"Contents": " 237:\tconfig IPV6_MULTIPLE_TABLES\n 238:\t\tbool \"IPv6: Multiple Routing Tables\"\n 239:\t\tselect FIB_RULES\n 240:\t\thelp\n 241:\t\t Support multiple routing tables.\n 242:\t\n 243:\tconfig IPV6_SUBTREES\n 244:\t\tbool \"IPv6: source address based routing\"\n 245:\t\tdepends on IPV6_MULTIPLE_TABLES\n 246:\t\tdefault y\n 247:\t\thelp\n 248:\t\t Enable routing by source address or prefix.\n 249:\t\n 250:\t\t The destination address is still the primary routing key, so mixing\n 251:\t\t normal and source prefix specific routes in the same routing table\n 252:\t\t may sometimes lead to unintended routing behavior if the order of\n 253:\t\t lookups is misunderstood. To achieve full separation based on source\n 254:\t\t address, different routing tables should be defined for each source\n 255:\t\t prefix.\n 256:\t\n"
} |
| 11/2 | 2026/07/14 15:17 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 16543 output: 22 thoughts: 171 Thoughts: **Analyzing IPv6 Routing Configuration** Okay, so I'm focusing on these IPv6 configurations: `IPV6_MULTIPLE_TABLES` and `IPV6_SUBTREES`. I need to remember that `IPV6_SUBTREES` is automatically set to "y" (enabled) if `IPV6_MULTIPLE_TABLES` is enabled. That's a key dependency. Now I need to examine these related functions, in the context of those configs. I see that `ipv6_saddr_rule5p5` and `ipv6_fl_get_saddr` are important related functions. They are *FocusSymbols* for this investigation. I'll need to dig into their behaviour. I also want to make sure I don't miss anything that has been modified, so I'll check other functions related to this process. It looks like `rt6_fill_node` in `net/ipv6/route.c` and `fib6_node_lookup_1` in `net/ipv6/ip6_fib.c` are also worth considering. I need to see how these functions interact with the configuration and the *FocusSymbols*. |
| 12/2 | 2026/07/14 15:18 | tool | grepper |
0mArgs:{
"Expression": "fib6_node_lookup_1"
}Results: {
"Output": "net/ipv6/ip6_fib.c=1606=struct lookup_args {\n--\nnet/ipv6/ip6_fib.c-1610-\nnet/ipv6/ip6_fib.c:1611:static struct fib6_node *fib6_node_lookup_1(struct fib6_node *root,\nnet/ipv6/ip6_fib.c-1612-\t\t\t\t\t struct lookup_args *args)\n--\nnet/ipv6/ip6_fib.c-1656-\t\t\t\t\tstruct fib6_node *sfn;\nnet/ipv6/ip6_fib.c:1657:\t\t\t\t\tsfn = fib6_node_lookup_1(subtree,\nnet/ipv6/ip6_fib.c-1658-\t\t\t\t\t\t\t\t args + 1);\n--\nnet/ipv6/ip6_fib.c=1683=struct fib6_node *fib6_node_lookup(struct fib6_node *root,\n--\nnet/ipv6/ip6_fib.c-1703-\nnet/ipv6/ip6_fib.c:1704:\tfn = fib6_node_lookup_1(root, daddr ? args : args + 1);\nnet/ipv6/ip6_fib.c-1705-\tif (!fn || fn-\u003efn_flags \u0026 RTN_TL_ROOT)\n"
} |
| 13/2 | 2026/07/14 15:18 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 17340 output: 20 thoughts: 28 Thoughts: Okay, here's my take on those thoughts, framed as my own: **Analyzing `fib6_node_lookup_1` and `rt6_fill_node`** Right, so I'm looking at this code and immediately I see that `fib6_node_lookup_1` is declared as a static function. That's interesting, means it's scoped within this particular compilation unit, which limits its visibility and potential for reuse elsewhere in the codebase. I need to keep that in mind when tracing calls to it; it probably plays a key role in some specific routing table functionality within this module. And then, I see that we have `rt6_fill_node` as another modified function that I will be focusing on. I should probably focus on how these two functions interact. I'll need to dig deeper to see exactly how these two functions fit together in the bigger picture, understand the purpose of `rt6_fill_node` and how `fib6_node_lookup_1` is being used inside it. Is `fib6_node_lookup_1` a helper function for building routing table entries? Are there any data structures being passed between them, and how are these modified structures being used? Does the modification of `rt6_fill_node` change or introduce bugs? Knowing that these two are being modified simultaneously raises the red flag of an API change, or an underlying fundamental change to how we are doing this. It's time to follow the trail of logic and data. |
| 14/2 | 2026/07/14 15:18 | tool | grepper |
0mArgs:{
"Expression": "rt6_fill_node"
}Results: {
"Output": "net/ipv6/route.c=109=static size_t rt6_nlmsg_size(struct fib6_info *f6i);\nnet/ipv6/route.c:110:static int rt6_fill_node(struct net *net, struct sk_buff *skb,\nnet/ipv6/route.c-111-\t\t\t struct fib6_info *rt, struct dst_entry *dst,\n--\nnet/ipv6/route.c=4007=static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg)\n--\nnet/ipv6/route.c-4028-\nnet/ipv6/route.c:4029:\t\t\tif (rt6_fill_node(net, skb, rt, NULL,\nnet/ipv6/route.c-4030-\t\t\t\t\t NULL, NULL, 0, RTM_DELROUTE,\n--\nnet/ipv6/route.c=5699=static size_t rt6_nlmsg_size(struct fib6_info *f6i)\n--\nnet/ipv6/route.c-5744-\nnet/ipv6/route.c:5745:static int rt6_fill_node_nexthop(struct sk_buff *skb, struct nexthop *nh,\nnet/ipv6/route.c-5746-\t\t\t\t unsigned char *flags)\n--\nnet/ipv6/route.c-5773-\nnet/ipv6/route.c:5774:static int rt6_fill_node(struct net *net, struct sk_buff *skb,\nnet/ipv6/route.c-5775-\t\t\t struct fib6_info *rt, struct dst_entry *dst,\n--\nnet/ipv6/route.c-5937-\t\tif (READ_ONCE(net-\u003eipv4.sysctl_nexthop_compat_mode) \u0026\u0026\nnet/ipv6/route.c:5938:\t\t rt6_fill_node_nexthop(skb, rt-\u003enh, \u0026nh_flags) \u003c 0)\nnet/ipv6/route.c-5939-\t\t\tgoto nla_put_failure;\n--\nnet/ipv6/route.c=6029=static int rt6_nh_dump_exceptions(struct fib6_nh *nh, void *arg)\n--\nnet/ipv6/route.c-6063-\nnet/ipv6/route.c:6064:\t\t\terr = rt6_fill_node(dump-\u003enet, dump-\u003eskb, w-\u003ert,\nnet/ipv6/route.c-6065-\t\t\t\t\t \u0026rt6_ex-\u003ert6i-\u003edst, NULL, NULL, 0,\n--\nnet/ipv6/route.c=6081=int rt6_dump_route(struct fib6_info *rt, void *p_arg, unsigned int skip)\n--\nnet/ipv6/route.c-6112-\t\t} else {\nnet/ipv6/route.c:6113:\t\t\tif (rt6_fill_node(net, arg-\u003eskb, rt, NULL, NULL, NULL,\nnet/ipv6/route.c-6114-\t\t\t\t\t 0, RTM_NEWROUTE,\n--\nnet/ipv6/route.c=6223=static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,\n--\nnet/ipv6/route.c-6345-\t\tif (fibmatch)\nnet/ipv6/route.c:6346:\t\t\terr = rt6_fill_node(net, skb, from, NULL, NULL, NULL,\nnet/ipv6/route.c-6347-\t\t\t\t\t iif, RTM_NEWROUTE,\n--\nnet/ipv6/route.c-6350-\t\telse\nnet/ipv6/route.c:6351:\t\t\terr = rt6_fill_node(net, skb, from, dst, \u0026fl6.daddr,\nnet/ipv6/route.c-6352-\t\t\t\t\t \u0026fl6.saddr, iif, RTM_NEWROUTE,\n--\nnet/ipv6/route.c=6370=void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,\n--\nnet/ipv6/route.c-6388-\nnet/ipv6/route.c:6389:\terr = rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0,\nnet/ipv6/route.c-6390-\t\t\t event, info-\u003eportid, seq, nlm_flags);\n--\nnet/ipv6/route.c=6411=void fib6_rt_update(struct net *net, struct fib6_info *rt,\n--\nnet/ipv6/route.c-6421-\nnet/ipv6/route.c:6422:\terr = rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0,\nnet/ipv6/route.c-6423-\t\t\t RTM_NEWROUTE, info-\u003eportid, seq, NLM_F_REPLACE);\n--\nnet/ipv6/route.c=6437=void fib6_info_hw_flags_set(struct net *net, struct fib6_info *f6i,\n--\nnet/ipv6/route.c-6474-\nnet/ipv6/route.c:6475:\terr = rt6_fill_node(net, skb, f6i, NULL, NULL, NULL, 0, RTM_NEWROUTE, 0,\nnet/ipv6/route.c-6476-\t\t\t 0, 0);\n--\nnet/netfilter/ipvs/ip_vs_ctl.c=67=static void __ip_vs_del_service(struct ip_vs_service *svc, bool cleanup);\n--\nnet/netfilter/ipvs/ip_vs_ctl.c-70-#ifdef CONFIG_IP_VS_IPV6\nnet/netfilter/ipvs/ip_vs_ctl.c:71:/* Taken from rt6_fill_node() in net/ipv6/route.c, is there a better way? */\nnet/netfilter/ipvs/ip_vs_ctl.c-72-static bool __ip_vs_addr_is_local_v6(struct net *net,\n"
} |
| 15/2 | 2026/07/14 15:18 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 19315 output: 209 thoughts: 1610 Thoughts: **Analysis of the IPv6 Source Address Selection Patch** Okay, so this patch is modifying `rt6_fill_node`, a static function, and updating its source address selection logic. It's swapping out `ip6_route_get_saddr` for `ipv6_dev_get_saddr`, which is interesting. I need to understand the implications of this change. My initial focus goes to the functions involved: `ipv6_fl_get_saddr`, `ipv6_dev_get_saddr`, and `ipv6_saddr_rule5p5`. `ipv6_fl_get_saddr` is exported, making it a good starting point. `ipv6_saddr_rule5p5` is static but accessible through Syzkaller, so it's still a viable target. I also know that `ipv6_dev_get_saddr` is used in many places. I quickly verify that all of these key functions are reachable, including `ipv6_saddr_rule5p5` (reachable via `ipv6_get_saddr_eval`), `ipv6_dev_get_saddr`, `ipv6_fl_get_saddr` (used in `ip6_dst_lookup_tail`), and `rt6_fill_node` (used by `inet6_rtm_getroute`, etc.). This tells me the patch is correctly integrated and the new logic should be exercised. I also confirm `fib6_node_lookup_1` is reachable. Now, I zero in on focus symbols for further investigation. The key symbols identified are: `ipv6_saddr_rule5p5`, `ipv6_fl_get_saddr`, `ipv6_dev_get_saddr`, `rt6_fill_node`, and `fib6_node_lookup_1`. `ipv6_saddr_rule5p5` is especially relevant because it's a new function introduced by the patch. I check for related configurations, and I see that `IPV6_SUBTREES` and `IPV6_MULTIPLE_TABLES` are crucial, and `IPV6_SUBTREES` should already be enabled. I quickly confirm that all focus symbols, including the static ones, are accessible to Syzkaller. Now, let's consider the “hot path” implications. `ipv6_saddr_rule5p5`, `ipv6_fl_get_saddr`, and `ipv6_dev_get_saddr` are related to source address selection. Route lookup is on a path that is called more than one time. Therefore I have to consider excluding it. `rt6_fill_node` is involved in netlink route dumping, so not a hot path. Therefore the focus symbols are: `ipv6_saddr_rule5p5` `ipv6_fl_get_saddr` `ipv6_dev_get_saddr` `rt6_fill_node` This patch is implementing RFC6724 rule 5.5. It modifies core IPv6 routing logic. Okay, let's enable `IPV6_SUBTREES` and `IPV6_MULTIPLE_TABLES`. I will check if there are other configs. No. Everything seems to be in order. Let's proceed. |
| 16/2 | 2026/07/14 15:18 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"IPV6_SUBTREES",
"IPV6_MULTIPLE_TABLES"
],
"FocusSymbols": [
"ipv6_saddr_rule5p5",
"ipv6_fl_get_saddr",
"ipv6_dev_get_saddr",
"rt6_fill_node"
],
"Reasoning": "The patch implements RFC6724 rule 5.5 for IPv6 source address selection by introducing `ipv6_saddr_rule5p5` and `ipv6_fl_get_saddr`. It modifies core IPv6 routing and address selection logic, which is reachable from userspace via socket operations (e.g., connect, sendmsg) and netlink route dumps (`rt6_fill_node`). The feature requires `IPV6_SUBTREES` and `IPV6_MULTIPLE_TABLES` to be enabled.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"IPV6_SUBTREES",
"IPV6_MULTIPLE_TABLES"
],
"FocusSymbols": [
"ipv6_saddr_rule5p5",
"ipv6_fl_get_saddr",
"ipv6_dev_get_saddr",
"rt6_fill_node"
],
"Reasoning": "The patch implements RFC6724 rule 5.5 for IPv6 source address selection by introducing `ipv6_saddr_rule5p5` and `ipv6_fl_get_saddr`. It modifies core IPv6 routing and address selection logic, which is reachable from userspace via socket operations (e.g., connect, sendmsg) and netlink route dumps (`rt6_fill_node`). The feature requires `IPV6_SUBTREES` and `IPV6_MULTIPLE_TABLES` to be enabled.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|