1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * |
4 | * Generic part shared by ipv4 and ipv6 backends. |
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 <net/netfilter/nf_tables_core.h> |
14 | #include <net/netfilter/nf_tables.h> |
15 | #include <linux/in.h> |
16 | #include <net/xfrm.h> |
17 | |
18 | static const struct nla_policy nft_xfrm_policy[NFTA_XFRM_MAX + 1] = { |
19 | [NFTA_XFRM_KEY] = NLA_POLICY_MAX(NLA_BE32, 255), |
20 | [NFTA_XFRM_DIR] = { .type = NLA_U8 }, |
21 | [NFTA_XFRM_SPNUM] = NLA_POLICY_MAX(NLA_BE32, 255), |
22 | [NFTA_XFRM_DREG] = { .type = NLA_U32 }, |
23 | }; |
24 | |
25 | struct nft_xfrm { |
26 | enum nft_xfrm_keys key:8; |
27 | u8 dreg; |
28 | u8 dir; |
29 | u8 spnum; |
30 | u8 len; |
31 | }; |
32 | |
33 | static int nft_xfrm_get_init(const struct nft_ctx *ctx, |
34 | const struct nft_expr *expr, |
35 | const struct nlattr * const tb[]) |
36 | { |
37 | struct nft_xfrm *priv = nft_expr_priv(expr); |
38 | unsigned int len = 0; |
39 | u32 spnum = 0; |
40 | u8 dir; |
41 | |
42 | if (!tb[NFTA_XFRM_KEY] || !tb[NFTA_XFRM_DIR] || !tb[NFTA_XFRM_DREG]) |
43 | return -EINVAL; |
44 | |
45 | switch (ctx->family) { |
46 | case NFPROTO_IPV4: |
47 | case NFPROTO_IPV6: |
48 | case NFPROTO_INET: |
49 | break; |
50 | default: |
51 | return -EOPNOTSUPP; |
52 | } |
53 | |
54 | priv->key = ntohl(nla_get_be32(tb[NFTA_XFRM_KEY])); |
55 | switch (priv->key) { |
56 | case NFT_XFRM_KEY_REQID: |
57 | case NFT_XFRM_KEY_SPI: |
58 | len = sizeof(u32); |
59 | break; |
60 | case NFT_XFRM_KEY_DADDR_IP4: |
61 | case NFT_XFRM_KEY_SADDR_IP4: |
62 | len = sizeof(struct in_addr); |
63 | break; |
64 | case NFT_XFRM_KEY_DADDR_IP6: |
65 | case NFT_XFRM_KEY_SADDR_IP6: |
66 | len = sizeof(struct in6_addr); |
67 | break; |
68 | default: |
69 | return -EINVAL; |
70 | } |
71 | |
72 | dir = nla_get_u8(nla: tb[NFTA_XFRM_DIR]); |
73 | switch (dir) { |
74 | case XFRM_POLICY_IN: |
75 | case XFRM_POLICY_OUT: |
76 | priv->dir = dir; |
77 | break; |
78 | default: |
79 | return -EINVAL; |
80 | } |
81 | |
82 | if (tb[NFTA_XFRM_SPNUM]) |
83 | spnum = ntohl(nla_get_be32(tb[NFTA_XFRM_SPNUM])); |
84 | |
85 | if (spnum >= XFRM_MAX_DEPTH) |
86 | return -ERANGE; |
87 | |
88 | priv->spnum = spnum; |
89 | |
90 | priv->len = len; |
91 | return nft_parse_register_store(ctx, attr: tb[NFTA_XFRM_DREG], dreg: &priv->dreg, |
92 | NULL, type: NFT_DATA_VALUE, len); |
93 | } |
94 | |
95 | /* Return true if key asks for daddr/saddr and current |
96 | * state does have a valid address (BEET, TUNNEL). |
97 | */ |
98 | static bool xfrm_state_addr_ok(enum nft_xfrm_keys k, u8 family, u8 mode) |
99 | { |
100 | switch (k) { |
101 | case NFT_XFRM_KEY_DADDR_IP4: |
102 | case NFT_XFRM_KEY_SADDR_IP4: |
103 | if (family == NFPROTO_IPV4) |
104 | break; |
105 | return false; |
106 | case NFT_XFRM_KEY_DADDR_IP6: |
107 | case NFT_XFRM_KEY_SADDR_IP6: |
108 | if (family == NFPROTO_IPV6) |
109 | break; |
110 | return false; |
111 | default: |
112 | return true; |
113 | } |
114 | |
115 | return mode == XFRM_MODE_BEET || mode == XFRM_MODE_TUNNEL; |
116 | } |
117 | |
118 | static void nft_xfrm_state_get_key(const struct nft_xfrm *priv, |
119 | struct nft_regs *regs, |
120 | const struct xfrm_state *state) |
121 | { |
122 | u32 *dest = ®s->data[priv->dreg]; |
123 | |
124 | if (!xfrm_state_addr_ok(k: priv->key, |
125 | family: state->props.family, |
126 | mode: state->props.mode)) { |
127 | regs->verdict.code = NFT_BREAK; |
128 | return; |
129 | } |
130 | |
131 | switch (priv->key) { |
132 | case NFT_XFRM_KEY_UNSPEC: |
133 | case __NFT_XFRM_KEY_MAX: |
134 | WARN_ON_ONCE(1); |
135 | break; |
136 | case NFT_XFRM_KEY_DADDR_IP4: |
137 | *dest = (__force __u32)state->id.daddr.a4; |
138 | return; |
139 | case NFT_XFRM_KEY_DADDR_IP6: |
140 | memcpy(dest, &state->id.daddr.in6, sizeof(struct in6_addr)); |
141 | return; |
142 | case NFT_XFRM_KEY_SADDR_IP4: |
143 | *dest = (__force __u32)state->props.saddr.a4; |
144 | return; |
145 | case NFT_XFRM_KEY_SADDR_IP6: |
146 | memcpy(dest, &state->props.saddr.in6, sizeof(struct in6_addr)); |
147 | return; |
148 | case NFT_XFRM_KEY_REQID: |
149 | *dest = state->props.reqid; |
150 | return; |
151 | case NFT_XFRM_KEY_SPI: |
152 | *dest = (__force __u32)state->id.spi; |
153 | return; |
154 | } |
155 | |
156 | regs->verdict.code = NFT_BREAK; |
157 | } |
158 | |
159 | static void nft_xfrm_get_eval_in(const struct nft_xfrm *priv, |
160 | struct nft_regs *regs, |
161 | const struct nft_pktinfo *pkt) |
162 | { |
163 | const struct sec_path *sp = skb_sec_path(skb: pkt->skb); |
164 | const struct xfrm_state *state; |
165 | |
166 | if (sp == NULL || sp->len <= priv->spnum) { |
167 | regs->verdict.code = NFT_BREAK; |
168 | return; |
169 | } |
170 | |
171 | state = sp->xvec[priv->spnum]; |
172 | nft_xfrm_state_get_key(priv, regs, state); |
173 | } |
174 | |
175 | static void nft_xfrm_get_eval_out(const struct nft_xfrm *priv, |
176 | struct nft_regs *regs, |
177 | const struct nft_pktinfo *pkt) |
178 | { |
179 | const struct dst_entry *dst = skb_dst(skb: pkt->skb); |
180 | int i; |
181 | |
182 | for (i = 0; dst && dst->xfrm; |
183 | dst = ((const struct xfrm_dst *)dst)->child, i++) { |
184 | if (i < priv->spnum) |
185 | continue; |
186 | |
187 | nft_xfrm_state_get_key(priv, regs, state: dst->xfrm); |
188 | return; |
189 | } |
190 | |
191 | regs->verdict.code = NFT_BREAK; |
192 | } |
193 | |
194 | static void nft_xfrm_get_eval(const struct nft_expr *expr, |
195 | struct nft_regs *regs, |
196 | const struct nft_pktinfo *pkt) |
197 | { |
198 | const struct nft_xfrm *priv = nft_expr_priv(expr); |
199 | |
200 | switch (priv->dir) { |
201 | case XFRM_POLICY_IN: |
202 | nft_xfrm_get_eval_in(priv, regs, pkt); |
203 | break; |
204 | case XFRM_POLICY_OUT: |
205 | nft_xfrm_get_eval_out(priv, regs, pkt); |
206 | break; |
207 | default: |
208 | WARN_ON_ONCE(1); |
209 | regs->verdict.code = NFT_BREAK; |
210 | break; |
211 | } |
212 | } |
213 | |
214 | static int nft_xfrm_get_dump(struct sk_buff *skb, |
215 | const struct nft_expr *expr, bool reset) |
216 | { |
217 | const struct nft_xfrm *priv = nft_expr_priv(expr); |
218 | |
219 | if (nft_dump_register(skb, attr: NFTA_XFRM_DREG, reg: priv->dreg)) |
220 | return -1; |
221 | |
222 | if (nla_put_be32(skb, attrtype: NFTA_XFRM_KEY, htonl(priv->key))) |
223 | return -1; |
224 | if (nla_put_u8(skb, attrtype: NFTA_XFRM_DIR, value: priv->dir)) |
225 | return -1; |
226 | if (nla_put_be32(skb, attrtype: NFTA_XFRM_SPNUM, htonl(priv->spnum))) |
227 | return -1; |
228 | |
229 | return 0; |
230 | } |
231 | |
232 | static int nft_xfrm_validate(const struct nft_ctx *ctx, const struct nft_expr *expr, |
233 | const struct nft_data **data) |
234 | { |
235 | const struct nft_xfrm *priv = nft_expr_priv(expr); |
236 | unsigned int hooks; |
237 | |
238 | switch (priv->dir) { |
239 | case XFRM_POLICY_IN: |
240 | hooks = (1 << NF_INET_FORWARD) | |
241 | (1 << NF_INET_LOCAL_IN) | |
242 | (1 << NF_INET_PRE_ROUTING); |
243 | break; |
244 | case XFRM_POLICY_OUT: |
245 | hooks = (1 << NF_INET_FORWARD) | |
246 | (1 << NF_INET_LOCAL_OUT) | |
247 | (1 << NF_INET_POST_ROUTING); |
248 | break; |
249 | default: |
250 | WARN_ON_ONCE(1); |
251 | return -EINVAL; |
252 | } |
253 | |
254 | return nft_chain_validate_hooks(chain: ctx->chain, hook_flags: hooks); |
255 | } |
256 | |
257 | static bool nft_xfrm_reduce(struct nft_regs_track *track, |
258 | const struct nft_expr *expr) |
259 | { |
260 | const struct nft_xfrm *priv = nft_expr_priv(expr); |
261 | const struct nft_xfrm *xfrm; |
262 | |
263 | if (!nft_reg_track_cmp(track, expr, dreg: priv->dreg)) { |
264 | nft_reg_track_update(track, expr, dreg: priv->dreg, len: priv->len); |
265 | return false; |
266 | } |
267 | |
268 | xfrm = nft_expr_priv(expr: track->regs[priv->dreg].selector); |
269 | if (priv->key != xfrm->key || |
270 | priv->dreg != xfrm->dreg || |
271 | priv->dir != xfrm->dir || |
272 | priv->spnum != xfrm->spnum) { |
273 | nft_reg_track_update(track, expr, dreg: priv->dreg, len: priv->len); |
274 | return false; |
275 | } |
276 | |
277 | if (!track->regs[priv->dreg].bitwise) |
278 | return true; |
279 | |
280 | return nft_expr_reduce_bitwise(track, expr); |
281 | } |
282 | |
283 | static struct nft_expr_type nft_xfrm_type; |
284 | static const struct nft_expr_ops nft_xfrm_get_ops = { |
285 | .type = &nft_xfrm_type, |
286 | .size = NFT_EXPR_SIZE(sizeof(struct nft_xfrm)), |
287 | .eval = nft_xfrm_get_eval, |
288 | .init = nft_xfrm_get_init, |
289 | .dump = nft_xfrm_get_dump, |
290 | .validate = nft_xfrm_validate, |
291 | .reduce = nft_xfrm_reduce, |
292 | }; |
293 | |
294 | static struct nft_expr_type nft_xfrm_type __read_mostly = { |
295 | .name = "xfrm" , |
296 | .ops = &nft_xfrm_get_ops, |
297 | .policy = nft_xfrm_policy, |
298 | .maxattr = NFTA_XFRM_MAX, |
299 | .owner = THIS_MODULE, |
300 | }; |
301 | |
302 | static int __init nft_xfrm_module_init(void) |
303 | { |
304 | return nft_register_expr(&nft_xfrm_type); |
305 | } |
306 | |
307 | static void __exit nft_xfrm_module_exit(void) |
308 | { |
309 | nft_unregister_expr(&nft_xfrm_type); |
310 | } |
311 | |
312 | module_init(nft_xfrm_module_init); |
313 | module_exit(nft_xfrm_module_exit); |
314 | |
315 | MODULE_LICENSE("GPL" ); |
316 | MODULE_DESCRIPTION("nf_tables: xfrm/IPSec matching" ); |
317 | MODULE_AUTHOR("Florian Westphal <fw@strlen.de>" ); |
318 | MODULE_AUTHOR("Máté Eckl <ecklm94@gmail.com>" ); |
319 | MODULE_ALIAS_NFT_EXPR("xfrm" ); |
320 | |