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