1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | |
3 | #include <linux/kernel.h> |
4 | #include <linux/init.h> |
5 | #include <linux/module.h> |
6 | #include <linux/netlink.h> |
7 | #include <linux/netfilter.h> |
8 | #include <linux/netfilter/nf_tables.h> |
9 | #include <linux/netfilter_ipv6.h> |
10 | #include <net/netfilter/nf_tables_core.h> |
11 | #include <net/netfilter/nf_tables.h> |
12 | #include <net/netfilter/nft_fib.h> |
13 | |
14 | #include <net/ip6_fib.h> |
15 | #include <net/ip6_route.h> |
16 | |
17 | static int get_ifindex(const struct net_device *dev) |
18 | { |
19 | return dev ? dev->ifindex : 0; |
20 | } |
21 | |
22 | static int nft_fib6_flowi_init(struct flowi6 *fl6, const struct nft_fib *priv, |
23 | const struct nft_pktinfo *pkt, |
24 | const struct net_device *dev, |
25 | struct ipv6hdr *iph) |
26 | { |
27 | int lookup_flags = 0; |
28 | |
29 | if (priv->flags & NFTA_FIB_F_DADDR) { |
30 | fl6->daddr = iph->daddr; |
31 | fl6->saddr = iph->saddr; |
32 | } else { |
33 | if (nft_hook(pkt) == NF_INET_FORWARD && |
34 | priv->flags & NFTA_FIB_F_IIF) |
35 | fl6->flowi6_iif = nft_out(pkt)->ifindex; |
36 | |
37 | fl6->daddr = iph->saddr; |
38 | fl6->saddr = iph->daddr; |
39 | } |
40 | |
41 | if (ipv6_addr_type(addr: &fl6->daddr) & IPV6_ADDR_LINKLOCAL) { |
42 | lookup_flags |= RT6_LOOKUP_F_IFACE; |
43 | fl6->flowi6_oif = get_ifindex(dev: dev ? dev : pkt->skb->dev); |
44 | } else if (priv->flags & NFTA_FIB_F_IIF) { |
45 | fl6->flowi6_l3mdev = l3mdev_master_ifindex_rcu(dev); |
46 | } |
47 | |
48 | if (ipv6_addr_type(addr: &fl6->saddr) & IPV6_ADDR_UNICAST) |
49 | lookup_flags |= RT6_LOOKUP_F_HAS_SADDR; |
50 | |
51 | if (priv->flags & NFTA_FIB_F_MARK) |
52 | fl6->flowi6_mark = pkt->skb->mark; |
53 | |
54 | fl6->flowlabel = (*(__be32 *)iph) & IPV6_FLOWINFO_MASK; |
55 | |
56 | return lookup_flags; |
57 | } |
58 | |
59 | static u32 __nft_fib6_eval_type(const struct nft_fib *priv, |
60 | const struct nft_pktinfo *pkt, |
61 | struct ipv6hdr *iph) |
62 | { |
63 | const struct net_device *dev = NULL; |
64 | int route_err, addrtype; |
65 | struct rt6_info *rt; |
66 | struct flowi6 fl6 = { |
67 | .flowi6_iif = LOOPBACK_IFINDEX, |
68 | .flowi6_proto = pkt->tprot, |
69 | .flowi6_uid = sock_net_uid(net: nft_net(pkt), NULL), |
70 | }; |
71 | u32 ret = 0; |
72 | |
73 | if (priv->flags & NFTA_FIB_F_IIF) |
74 | dev = nft_in(pkt); |
75 | else if (priv->flags & NFTA_FIB_F_OIF) |
76 | dev = nft_out(pkt); |
77 | |
78 | nft_fib6_flowi_init(fl6: &fl6, priv, pkt, dev, iph); |
79 | |
80 | if (dev && nf_ipv6_chk_addr(net: nft_net(pkt), addr: &fl6.daddr, dev, strict: true)) |
81 | ret = RTN_LOCAL; |
82 | |
83 | route_err = nf_ip6_route(net: nft_net(pkt), dst: (struct dst_entry **)&rt, |
84 | fl: flowi6_to_flowi(fl6: &fl6), strict: false); |
85 | if (route_err) |
86 | goto err; |
87 | |
88 | if (rt->rt6i_flags & RTF_REJECT) { |
89 | route_err = rt->dst.error; |
90 | dst_release(dst: &rt->dst); |
91 | goto err; |
92 | } |
93 | |
94 | if (ipv6_anycast_destination(dst: (struct dst_entry *)rt, daddr: &fl6.daddr)) |
95 | ret = RTN_ANYCAST; |
96 | else if (!dev && rt->rt6i_flags & RTF_LOCAL) |
97 | ret = RTN_LOCAL; |
98 | |
99 | dst_release(dst: &rt->dst); |
100 | |
101 | if (ret) |
102 | return ret; |
103 | |
104 | addrtype = ipv6_addr_type(addr: &fl6.daddr); |
105 | |
106 | if (addrtype & IPV6_ADDR_MULTICAST) |
107 | return RTN_MULTICAST; |
108 | if (addrtype & IPV6_ADDR_UNICAST) |
109 | return RTN_UNICAST; |
110 | |
111 | return RTN_UNSPEC; |
112 | err: |
113 | switch (route_err) { |
114 | case -EINVAL: |
115 | return RTN_BLACKHOLE; |
116 | case -EACCES: |
117 | return RTN_PROHIBIT; |
118 | case -EAGAIN: |
119 | return RTN_THROW; |
120 | default: |
121 | break; |
122 | } |
123 | |
124 | return RTN_UNREACHABLE; |
125 | } |
126 | |
127 | void nft_fib6_eval_type(const struct nft_expr *expr, struct nft_regs *regs, |
128 | const struct nft_pktinfo *pkt) |
129 | { |
130 | const struct nft_fib *priv = nft_expr_priv(expr); |
131 | int noff = skb_network_offset(skb: pkt->skb); |
132 | u32 *dest = ®s->data[priv->dreg]; |
133 | struct ipv6hdr *iph, _iph; |
134 | |
135 | iph = skb_header_pointer(skb: pkt->skb, offset: noff, len: sizeof(_iph), buffer: &_iph); |
136 | if (!iph) { |
137 | regs->verdict.code = NFT_BREAK; |
138 | return; |
139 | } |
140 | |
141 | *dest = __nft_fib6_eval_type(priv, pkt, iph); |
142 | } |
143 | EXPORT_SYMBOL_GPL(nft_fib6_eval_type); |
144 | |
145 | static bool nft_fib_v6_skip_icmpv6(const struct sk_buff *skb, u8 next, const struct ipv6hdr *iph) |
146 | { |
147 | if (likely(next != IPPROTO_ICMPV6)) |
148 | return false; |
149 | |
150 | if (ipv6_addr_type(addr: &iph->saddr) != IPV6_ADDR_ANY) |
151 | return false; |
152 | |
153 | return ipv6_addr_type(addr: &iph->daddr) & IPV6_ADDR_LINKLOCAL; |
154 | } |
155 | |
156 | void nft_fib6_eval(const struct nft_expr *expr, struct nft_regs *regs, |
157 | const struct nft_pktinfo *pkt) |
158 | { |
159 | const struct nft_fib *priv = nft_expr_priv(expr); |
160 | int noff = skb_network_offset(skb: pkt->skb); |
161 | const struct net_device *oif = NULL; |
162 | u32 *dest = ®s->data[priv->dreg]; |
163 | struct ipv6hdr *iph, _iph; |
164 | struct flowi6 fl6 = { |
165 | .flowi6_iif = LOOPBACK_IFINDEX, |
166 | .flowi6_proto = pkt->tprot, |
167 | .flowi6_uid = sock_net_uid(net: nft_net(pkt), NULL), |
168 | }; |
169 | struct rt6_info *rt; |
170 | int lookup_flags; |
171 | |
172 | if (priv->flags & NFTA_FIB_F_IIF) |
173 | oif = nft_in(pkt); |
174 | else if (priv->flags & NFTA_FIB_F_OIF) |
175 | oif = nft_out(pkt); |
176 | |
177 | iph = skb_header_pointer(skb: pkt->skb, offset: noff, len: sizeof(_iph), buffer: &_iph); |
178 | if (!iph) { |
179 | regs->verdict.code = NFT_BREAK; |
180 | return; |
181 | } |
182 | |
183 | lookup_flags = nft_fib6_flowi_init(fl6: &fl6, priv, pkt, dev: oif, iph); |
184 | |
185 | if (nft_hook(pkt) == NF_INET_PRE_ROUTING || |
186 | nft_hook(pkt) == NF_INET_INGRESS) { |
187 | if (nft_fib_is_loopback(skb: pkt->skb, in: nft_in(pkt)) || |
188 | nft_fib_v6_skip_icmpv6(skb: pkt->skb, next: pkt->tprot, iph)) { |
189 | nft_fib_store_result(reg: dest, priv, dev: nft_in(pkt)); |
190 | return; |
191 | } |
192 | } |
193 | |
194 | *dest = 0; |
195 | rt = (void *)ip6_route_lookup(net: nft_net(pkt), fl6: &fl6, skb: pkt->skb, |
196 | flags: lookup_flags); |
197 | if (rt->dst.error) |
198 | goto put_rt_err; |
199 | |
200 | /* Should not see RTF_LOCAL here */ |
201 | if (rt->rt6i_flags & (RTF_REJECT | RTF_ANYCAST | RTF_LOCAL)) |
202 | goto put_rt_err; |
203 | |
204 | if (oif && oif != rt->rt6i_idev->dev && |
205 | l3mdev_master_ifindex_rcu(dev: rt->rt6i_idev->dev) != oif->ifindex) |
206 | goto put_rt_err; |
207 | |
208 | nft_fib_store_result(reg: dest, priv, dev: rt->rt6i_idev->dev); |
209 | put_rt_err: |
210 | ip6_rt_put(rt); |
211 | } |
212 | EXPORT_SYMBOL_GPL(nft_fib6_eval); |
213 | |
214 | static struct nft_expr_type nft_fib6_type; |
215 | |
216 | static const struct nft_expr_ops nft_fib6_type_ops = { |
217 | .type = &nft_fib6_type, |
218 | .size = NFT_EXPR_SIZE(sizeof(struct nft_fib)), |
219 | .eval = nft_fib6_eval_type, |
220 | .init = nft_fib_init, |
221 | .dump = nft_fib_dump, |
222 | .validate = nft_fib_validate, |
223 | .reduce = nft_fib_reduce, |
224 | }; |
225 | |
226 | static const struct nft_expr_ops nft_fib6_ops = { |
227 | .type = &nft_fib6_type, |
228 | .size = NFT_EXPR_SIZE(sizeof(struct nft_fib)), |
229 | .eval = nft_fib6_eval, |
230 | .init = nft_fib_init, |
231 | .dump = nft_fib_dump, |
232 | .validate = nft_fib_validate, |
233 | .reduce = nft_fib_reduce, |
234 | }; |
235 | |
236 | static const struct nft_expr_ops * |
237 | nft_fib6_select_ops(const struct nft_ctx *ctx, |
238 | const struct nlattr * const tb[]) |
239 | { |
240 | enum nft_fib_result result; |
241 | |
242 | if (!tb[NFTA_FIB_RESULT]) |
243 | return ERR_PTR(error: -EINVAL); |
244 | |
245 | result = ntohl(nla_get_be32(tb[NFTA_FIB_RESULT])); |
246 | |
247 | switch (result) { |
248 | case NFT_FIB_RESULT_OIF: |
249 | return &nft_fib6_ops; |
250 | case NFT_FIB_RESULT_OIFNAME: |
251 | return &nft_fib6_ops; |
252 | case NFT_FIB_RESULT_ADDRTYPE: |
253 | return &nft_fib6_type_ops; |
254 | default: |
255 | return ERR_PTR(error: -EOPNOTSUPP); |
256 | } |
257 | } |
258 | |
259 | static struct nft_expr_type nft_fib6_type __read_mostly = { |
260 | .name = "fib" , |
261 | .select_ops = nft_fib6_select_ops, |
262 | .policy = nft_fib_policy, |
263 | .maxattr = NFTA_FIB_MAX, |
264 | .family = NFPROTO_IPV6, |
265 | .owner = THIS_MODULE, |
266 | }; |
267 | |
268 | static int __init nft_fib6_module_init(void) |
269 | { |
270 | return nft_register_expr(&nft_fib6_type); |
271 | } |
272 | |
273 | static void __exit nft_fib6_module_exit(void) |
274 | { |
275 | nft_unregister_expr(&nft_fib6_type); |
276 | } |
277 | module_init(nft_fib6_module_init); |
278 | module_exit(nft_fib6_module_exit); |
279 | |
280 | MODULE_LICENSE("GPL" ); |
281 | MODULE_AUTHOR("Florian Westphal <fw@strlen.de>" ); |
282 | MODULE_ALIAS_NFT_AF_EXPR(10, "fib" ); |
283 | MODULE_DESCRIPTION("nftables fib / ipv6 route lookup support" ); |
284 | |