Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfilter: add nf_ipv6_ops hook to fix xt_addrtype with IPv6

Quoting https://bugzilla.netfilter.org/show_bug.cgi?id=812:

[ ip6tables -m addrtype ]
When I tried to use in the nat/PREROUTING it messes up the
routing cache even if the rule didn't matched at all.
[..]
If I remove the --limit-iface-in from the non-working scenario, so just
use the -m addrtype --dst-type LOCAL it works!

This happens when LOCAL type matching is requested with --limit-iface-in,
and the default ipv6 route is via the interface the packet we test
arrived on.

Because xt_addrtype uses ip6_route_output, the ipv6 routing implementation
creates an unwanted cached entry, and the packet won't make it to the
real/expected destination.

Silently ignoring --limit-iface-in makes the routing work but it breaks
rule matching (--dst-type LOCAL with limit-iface-in is supposed to only
match if the dst address is configured on the incoming interface;
without --limit-iface-in it will match if the address is reachable
via lo).

The test should call ipv6_chk_addr() instead. However, this would add
a link-time dependency on ipv6.

There are two possible solutions:

1) Revert the commit that moved ipt_addrtype to xt_addrtype,
and put ipv6 specific code into ip6t_addrtype.
2) add new "nf_ipv6_ops" struct to register pointers to ipv6 functions.

While the former might seem preferable, Pablo pointed out that there
are more xt modules with link-time dependeny issues regarding ipv6,
so lets go for 2).

Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

authored by

Florian Westphal and committed by
Pablo Neira Ayuso
2a7851bf 497574c7

+44 -14
+16
include/linux/netfilter_ipv6.h
··· 17 17 18 18 extern int ipv6_netfilter_init(void); 19 19 extern void ipv6_netfilter_fini(void); 20 + 21 + /* 22 + * Hook functions for ipv6 to allow xt_* modules to be built-in even 23 + * if IPv6 is a module. 24 + */ 25 + struct nf_ipv6_ops { 26 + int (*chk_addr)(struct net *net, const struct in6_addr *addr, 27 + const struct net_device *dev, int strict); 28 + }; 29 + 30 + extern const struct nf_ipv6_ops __rcu *nf_ipv6_ops; 31 + static inline const struct nf_ipv6_ops *nf_get_ipv6_ops(void) 32 + { 33 + return rcu_dereference(nf_ipv6_ops); 34 + } 35 + 20 36 #else /* CONFIG_NETFILTER */ 21 37 static inline int ipv6_netfilter_init(void) { return 0; } 22 38 static inline void ipv6_netfilter_fini(void) { return; }
+1 -1
include/net/addrconf.h
··· 65 65 66 66 extern int ipv6_chk_addr(struct net *net, 67 67 const struct in6_addr *addr, 68 - struct net_device *dev, 68 + const struct net_device *dev, 69 69 int strict); 70 70 71 71 #if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
+1 -1
net/ipv6/addrconf.c
··· 1487 1487 } 1488 1488 1489 1489 int ipv6_chk_addr(struct net *net, const struct in6_addr *addr, 1490 - struct net_device *dev, int strict) 1490 + const struct net_device *dev, int strict) 1491 1491 { 1492 1492 struct inet6_ifaddr *ifp; 1493 1493 unsigned int hash = inet6_addr_hash(addr);
+7
net/ipv6/netfilter.c
··· 10 10 #include <linux/netfilter.h> 11 11 #include <linux/netfilter_ipv6.h> 12 12 #include <linux/export.h> 13 + #include <net/addrconf.h> 13 14 #include <net/dst.h> 14 15 #include <net/ipv6.h> 15 16 #include <net/ip6_route.h> ··· 187 186 return csum; 188 187 }; 189 188 189 + static const struct nf_ipv6_ops ipv6ops = { 190 + .chk_addr = ipv6_chk_addr, 191 + }; 192 + 190 193 static const struct nf_afinfo nf_ip6_afinfo = { 191 194 .family = AF_INET6, 192 195 .checksum = nf_ip6_checksum, ··· 203 198 204 199 int __init ipv6_netfilter_init(void) 205 200 { 201 + RCU_INIT_POINTER(nf_ipv6_ops, &ipv6ops); 206 202 return nf_register_afinfo(&nf_ip6_afinfo); 207 203 } 208 204 ··· 212 206 */ 213 207 void ipv6_netfilter_fini(void) 214 208 { 209 + RCU_INIT_POINTER(nf_ipv6_ops, NULL); 215 210 nf_unregister_afinfo(&nf_ip6_afinfo); 216 211 }
+2
net/netfilter/core.c
··· 30 30 31 31 const struct nf_afinfo __rcu *nf_afinfo[NFPROTO_NUMPROTO] __read_mostly; 32 32 EXPORT_SYMBOL(nf_afinfo); 33 + const struct nf_ipv6_ops __rcu *nf_ipv6_ops __read_mostly; 34 + EXPORT_SYMBOL_GPL(nf_ipv6_ops); 33 35 34 36 int nf_register_afinfo(const struct nf_afinfo *afinfo) 35 37 {
+17 -12
net/netfilter/xt_addrtype.c
··· 22 22 #include <net/ip6_fib.h> 23 23 #endif 24 24 25 + #include <linux/netfilter_ipv6.h> 25 26 #include <linux/netfilter/xt_addrtype.h> 26 27 #include <linux/netfilter/x_tables.h> 27 28 ··· 34 33 35 34 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) 36 35 static u32 match_lookup_rt6(struct net *net, const struct net_device *dev, 37 - const struct in6_addr *addr) 36 + const struct in6_addr *addr, u16 mask) 38 37 { 39 38 const struct nf_afinfo *afinfo; 40 39 struct flowi6 flow; 41 40 struct rt6_info *rt; 42 - u32 ret; 41 + u32 ret = 0; 43 42 int route_err; 44 43 45 44 memset(&flow, 0, sizeof(flow)); ··· 50 49 rcu_read_lock(); 51 50 52 51 afinfo = nf_get_afinfo(NFPROTO_IPV6); 53 - if (afinfo != NULL) 54 - route_err = afinfo->route(net, (struct dst_entry **)&rt, 55 - flowi6_to_flowi(&flow), !!dev); 56 - else 57 - route_err = 1; 52 + if (afinfo != NULL) { 53 + const struct nf_ipv6_ops *v6ops; 58 54 55 + if (dev && (mask & XT_ADDRTYPE_LOCAL)) { 56 + v6ops = nf_get_ipv6_ops(); 57 + if (v6ops && v6ops->chk_addr(net, addr, dev, true)) 58 + ret = XT_ADDRTYPE_LOCAL; 59 + } 60 + route_err = afinfo->route(net, (struct dst_entry **)&rt, 61 + flowi6_to_flowi(&flow), false); 62 + } else { 63 + route_err = 1; 64 + } 59 65 rcu_read_unlock(); 60 66 61 67 if (route_err) ··· 70 62 71 63 if (rt->rt6i_flags & RTF_REJECT) 72 64 ret = XT_ADDRTYPE_UNREACHABLE; 73 - else 74 - ret = 0; 75 65 76 - if (rt->rt6i_flags & RTF_LOCAL) 66 + if (dev == NULL && rt->rt6i_flags & RTF_LOCAL) 77 67 ret |= XT_ADDRTYPE_LOCAL; 78 68 if (rt->rt6i_flags & RTF_ANYCAST) 79 69 ret |= XT_ADDRTYPE_ANYCAST; 80 - 81 70 82 71 dst_release(&rt->dst); 83 72 return ret; ··· 95 90 96 91 if ((XT_ADDRTYPE_LOCAL | XT_ADDRTYPE_ANYCAST | 97 92 XT_ADDRTYPE_UNREACHABLE) & mask) 98 - return !!(mask & match_lookup_rt6(net, dev, addr)); 93 + return !!(mask & match_lookup_rt6(net, dev, addr, mask)); 99 94 return true; 100 95 } 101 96