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