1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <linux/types.h> |
3 | #include <net/ip.h> |
4 | #include <net/tcp.h> |
5 | #include <net/netlink.h> |
6 | #include <net/netfilter/nf_tables.h> |
7 | #include <net/netfilter/nf_conntrack.h> |
8 | #include <net/netfilter/nf_conntrack_synproxy.h> |
9 | #include <net/netfilter/nf_synproxy.h> |
10 | #include <linux/netfilter/nf_tables.h> |
11 | #include <linux/netfilter/nf_synproxy.h> |
12 | |
13 | struct nft_synproxy { |
14 | struct nf_synproxy_info info; |
15 | }; |
16 | |
17 | static const struct nla_policy nft_synproxy_policy[NFTA_SYNPROXY_MAX + 1] = { |
18 | [NFTA_SYNPROXY_MSS] = { .type = NLA_U16 }, |
19 | [NFTA_SYNPROXY_WSCALE] = { .type = NLA_U8 }, |
20 | [NFTA_SYNPROXY_FLAGS] = { .type = NLA_U32 }, |
21 | }; |
22 | |
23 | static void nft_synproxy_tcp_options(struct synproxy_options *opts, |
24 | const struct tcphdr *tcp, |
25 | struct synproxy_net *snet, |
26 | struct nf_synproxy_info *info, |
27 | const struct nft_synproxy *priv) |
28 | { |
29 | this_cpu_inc(snet->stats->syn_received); |
30 | if (tcp->ece && tcp->cwr) |
31 | opts->options |= NF_SYNPROXY_OPT_ECN; |
32 | |
33 | opts->options &= priv->info.options; |
34 | opts->mss_encode = opts->mss_option; |
35 | opts->mss_option = info->mss; |
36 | if (opts->options & NF_SYNPROXY_OPT_TIMESTAMP) |
37 | synproxy_init_timestamp_cookie(info, opts); |
38 | else |
39 | opts->options &= ~(NF_SYNPROXY_OPT_WSCALE | |
40 | NF_SYNPROXY_OPT_SACK_PERM | |
41 | NF_SYNPROXY_OPT_ECN); |
42 | } |
43 | |
44 | static void nft_synproxy_eval_v4(const struct nft_synproxy *priv, |
45 | struct nft_regs *regs, |
46 | const struct nft_pktinfo *pkt, |
47 | const struct tcphdr *tcp, |
48 | struct tcphdr *_tcph, |
49 | struct synproxy_options *opts) |
50 | { |
51 | struct nf_synproxy_info info = priv->info; |
52 | struct net *net = nft_net(pkt); |
53 | struct synproxy_net *snet = synproxy_pernet(net); |
54 | struct sk_buff *skb = pkt->skb; |
55 | |
56 | if (tcp->syn) { |
57 | /* Initial SYN from client */ |
58 | nft_synproxy_tcp_options(opts, tcp, snet, info: &info, priv); |
59 | synproxy_send_client_synack(net, skb, th: tcp, opts); |
60 | consume_skb(skb); |
61 | regs->verdict.code = NF_STOLEN; |
62 | } else if (tcp->ack) { |
63 | /* ACK from client */ |
64 | if (synproxy_recv_client_ack(net, skb, th: tcp, opts, |
65 | ntohl(tcp->seq))) { |
66 | consume_skb(skb); |
67 | regs->verdict.code = NF_STOLEN; |
68 | } else { |
69 | regs->verdict.code = NF_DROP; |
70 | } |
71 | } |
72 | } |
73 | |
74 | #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) |
75 | static void nft_synproxy_eval_v6(const struct nft_synproxy *priv, |
76 | struct nft_regs *regs, |
77 | const struct nft_pktinfo *pkt, |
78 | const struct tcphdr *tcp, |
79 | struct tcphdr *_tcph, |
80 | struct synproxy_options *opts) |
81 | { |
82 | struct nf_synproxy_info info = priv->info; |
83 | struct net *net = nft_net(pkt); |
84 | struct synproxy_net *snet = synproxy_pernet(net); |
85 | struct sk_buff *skb = pkt->skb; |
86 | |
87 | if (tcp->syn) { |
88 | /* Initial SYN from client */ |
89 | nft_synproxy_tcp_options(opts, tcp, snet, info: &info, priv); |
90 | synproxy_send_client_synack_ipv6(net, skb, th: tcp, opts); |
91 | consume_skb(skb); |
92 | regs->verdict.code = NF_STOLEN; |
93 | } else if (tcp->ack) { |
94 | /* ACK from client */ |
95 | if (synproxy_recv_client_ack_ipv6(net, skb, th: tcp, opts, |
96 | ntohl(tcp->seq))) { |
97 | consume_skb(skb); |
98 | regs->verdict.code = NF_STOLEN; |
99 | } else { |
100 | regs->verdict.code = NF_DROP; |
101 | } |
102 | } |
103 | } |
104 | #endif /* CONFIG_NF_TABLES_IPV6*/ |
105 | |
106 | static void nft_synproxy_do_eval(const struct nft_synproxy *priv, |
107 | struct nft_regs *regs, |
108 | const struct nft_pktinfo *pkt) |
109 | { |
110 | struct synproxy_options opts = {}; |
111 | struct sk_buff *skb = pkt->skb; |
112 | int thoff = nft_thoff(pkt); |
113 | const struct tcphdr *tcp; |
114 | struct tcphdr _tcph; |
115 | |
116 | if (pkt->tprot != IPPROTO_TCP) { |
117 | regs->verdict.code = NFT_BREAK; |
118 | return; |
119 | } |
120 | |
121 | if (nf_ip_checksum(skb, hook: nft_hook(pkt), dataoff: thoff, IPPROTO_TCP)) { |
122 | regs->verdict.code = NF_DROP; |
123 | return; |
124 | } |
125 | |
126 | tcp = skb_header_pointer(skb, offset: thoff, |
127 | len: sizeof(struct tcphdr), |
128 | buffer: &_tcph); |
129 | if (!tcp) { |
130 | regs->verdict.code = NF_DROP; |
131 | return; |
132 | } |
133 | |
134 | if (!synproxy_parse_options(skb, doff: thoff, th: tcp, opts: &opts)) { |
135 | regs->verdict.code = NF_DROP; |
136 | return; |
137 | } |
138 | |
139 | switch (skb->protocol) { |
140 | case htons(ETH_P_IP): |
141 | nft_synproxy_eval_v4(priv, regs, pkt, tcp, tcph: &_tcph, opts: &opts); |
142 | return; |
143 | #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) |
144 | case htons(ETH_P_IPV6): |
145 | nft_synproxy_eval_v6(priv, regs, pkt, tcp, tcph: &_tcph, opts: &opts); |
146 | return; |
147 | #endif |
148 | } |
149 | regs->verdict.code = NFT_BREAK; |
150 | } |
151 | |
152 | static int nft_synproxy_do_init(const struct nft_ctx *ctx, |
153 | const struct nlattr * const tb[], |
154 | struct nft_synproxy *priv) |
155 | { |
156 | struct synproxy_net *snet = synproxy_pernet(net: ctx->net); |
157 | u32 flags; |
158 | int err; |
159 | |
160 | if (tb[NFTA_SYNPROXY_MSS]) |
161 | priv->info.mss = ntohs(nla_get_be16(tb[NFTA_SYNPROXY_MSS])); |
162 | if (tb[NFTA_SYNPROXY_WSCALE]) |
163 | priv->info.wscale = nla_get_u8(nla: tb[NFTA_SYNPROXY_WSCALE]); |
164 | if (tb[NFTA_SYNPROXY_FLAGS]) { |
165 | flags = ntohl(nla_get_be32(tb[NFTA_SYNPROXY_FLAGS])); |
166 | if (flags & ~NF_SYNPROXY_OPT_MASK) |
167 | return -EOPNOTSUPP; |
168 | priv->info.options = flags; |
169 | } |
170 | |
171 | err = nf_ct_netns_get(net: ctx->net, nfproto: ctx->family); |
172 | if (err) |
173 | return err; |
174 | |
175 | switch (ctx->family) { |
176 | case NFPROTO_IPV4: |
177 | err = nf_synproxy_ipv4_init(snet, net: ctx->net); |
178 | if (err) |
179 | goto nf_ct_failure; |
180 | break; |
181 | #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) |
182 | case NFPROTO_IPV6: |
183 | err = nf_synproxy_ipv6_init(snet, net: ctx->net); |
184 | if (err) |
185 | goto nf_ct_failure; |
186 | break; |
187 | #endif |
188 | case NFPROTO_INET: |
189 | err = nf_synproxy_ipv4_init(snet, net: ctx->net); |
190 | if (err) |
191 | goto nf_ct_failure; |
192 | err = nf_synproxy_ipv6_init(snet, net: ctx->net); |
193 | if (err) { |
194 | nf_synproxy_ipv4_fini(snet, net: ctx->net); |
195 | goto nf_ct_failure; |
196 | } |
197 | break; |
198 | } |
199 | |
200 | return 0; |
201 | |
202 | nf_ct_failure: |
203 | nf_ct_netns_put(net: ctx->net, nfproto: ctx->family); |
204 | return err; |
205 | } |
206 | |
207 | static void nft_synproxy_do_destroy(const struct nft_ctx *ctx) |
208 | { |
209 | struct synproxy_net *snet = synproxy_pernet(net: ctx->net); |
210 | |
211 | switch (ctx->family) { |
212 | case NFPROTO_IPV4: |
213 | nf_synproxy_ipv4_fini(snet, net: ctx->net); |
214 | break; |
215 | #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) |
216 | case NFPROTO_IPV6: |
217 | nf_synproxy_ipv6_fini(snet, net: ctx->net); |
218 | break; |
219 | #endif |
220 | case NFPROTO_INET: |
221 | nf_synproxy_ipv4_fini(snet, net: ctx->net); |
222 | nf_synproxy_ipv6_fini(snet, net: ctx->net); |
223 | break; |
224 | } |
225 | nf_ct_netns_put(net: ctx->net, nfproto: ctx->family); |
226 | } |
227 | |
228 | static int nft_synproxy_do_dump(struct sk_buff *skb, struct nft_synproxy *priv) |
229 | { |
230 | if (nla_put_be16(skb, attrtype: NFTA_SYNPROXY_MSS, htons(priv->info.mss)) || |
231 | nla_put_u8(skb, attrtype: NFTA_SYNPROXY_WSCALE, value: priv->info.wscale) || |
232 | nla_put_be32(skb, attrtype: NFTA_SYNPROXY_FLAGS, htonl(priv->info.options))) |
233 | goto nla_put_failure; |
234 | |
235 | return 0; |
236 | |
237 | nla_put_failure: |
238 | return -1; |
239 | } |
240 | |
241 | static void nft_synproxy_eval(const struct nft_expr *expr, |
242 | struct nft_regs *regs, |
243 | const struct nft_pktinfo *pkt) |
244 | { |
245 | const struct nft_synproxy *priv = nft_expr_priv(expr); |
246 | |
247 | nft_synproxy_do_eval(priv, regs, pkt); |
248 | } |
249 | |
250 | static int nft_synproxy_validate(const struct nft_ctx *ctx, |
251 | const struct nft_expr *expr, |
252 | const struct nft_data **data) |
253 | { |
254 | if (ctx->family != NFPROTO_IPV4 && |
255 | ctx->family != NFPROTO_IPV6 && |
256 | ctx->family != NFPROTO_INET) |
257 | return -EOPNOTSUPP; |
258 | |
259 | return nft_chain_validate_hooks(chain: ctx->chain, hook_flags: (1 << NF_INET_LOCAL_IN) | |
260 | (1 << NF_INET_FORWARD)); |
261 | } |
262 | |
263 | static int nft_synproxy_init(const struct nft_ctx *ctx, |
264 | const struct nft_expr *expr, |
265 | const struct nlattr * const tb[]) |
266 | { |
267 | struct nft_synproxy *priv = nft_expr_priv(expr); |
268 | |
269 | return nft_synproxy_do_init(ctx, tb, priv); |
270 | } |
271 | |
272 | static void nft_synproxy_destroy(const struct nft_ctx *ctx, |
273 | const struct nft_expr *expr) |
274 | { |
275 | nft_synproxy_do_destroy(ctx); |
276 | } |
277 | |
278 | static int nft_synproxy_dump(struct sk_buff *skb, |
279 | const struct nft_expr *expr, bool reset) |
280 | { |
281 | struct nft_synproxy *priv = nft_expr_priv(expr); |
282 | |
283 | return nft_synproxy_do_dump(skb, priv); |
284 | } |
285 | |
286 | static struct nft_expr_type nft_synproxy_type; |
287 | static const struct nft_expr_ops nft_synproxy_ops = { |
288 | .eval = nft_synproxy_eval, |
289 | .size = NFT_EXPR_SIZE(sizeof(struct nft_synproxy)), |
290 | .init = nft_synproxy_init, |
291 | .destroy = nft_synproxy_destroy, |
292 | .dump = nft_synproxy_dump, |
293 | .type = &nft_synproxy_type, |
294 | .validate = nft_synproxy_validate, |
295 | .reduce = NFT_REDUCE_READONLY, |
296 | }; |
297 | |
298 | static struct nft_expr_type nft_synproxy_type __read_mostly = { |
299 | .ops = &nft_synproxy_ops, |
300 | .name = "synproxy" , |
301 | .owner = THIS_MODULE, |
302 | .policy = nft_synproxy_policy, |
303 | .maxattr = NFTA_SYNPROXY_MAX, |
304 | }; |
305 | |
306 | static int nft_synproxy_obj_init(const struct nft_ctx *ctx, |
307 | const struct nlattr * const tb[], |
308 | struct nft_object *obj) |
309 | { |
310 | struct nft_synproxy *priv = nft_obj_data(obj); |
311 | |
312 | return nft_synproxy_do_init(ctx, tb, priv); |
313 | } |
314 | |
315 | static void nft_synproxy_obj_destroy(const struct nft_ctx *ctx, |
316 | struct nft_object *obj) |
317 | { |
318 | nft_synproxy_do_destroy(ctx); |
319 | } |
320 | |
321 | static int nft_synproxy_obj_dump(struct sk_buff *skb, |
322 | struct nft_object *obj, bool reset) |
323 | { |
324 | struct nft_synproxy *priv = nft_obj_data(obj); |
325 | |
326 | return nft_synproxy_do_dump(skb, priv); |
327 | } |
328 | |
329 | static void nft_synproxy_obj_eval(struct nft_object *obj, |
330 | struct nft_regs *regs, |
331 | const struct nft_pktinfo *pkt) |
332 | { |
333 | const struct nft_synproxy *priv = nft_obj_data(obj); |
334 | |
335 | nft_synproxy_do_eval(priv, regs, pkt); |
336 | } |
337 | |
338 | static void nft_synproxy_obj_update(struct nft_object *obj, |
339 | struct nft_object *newobj) |
340 | { |
341 | struct nft_synproxy *newpriv = nft_obj_data(obj: newobj); |
342 | struct nft_synproxy *priv = nft_obj_data(obj); |
343 | |
344 | priv->info = newpriv->info; |
345 | } |
346 | |
347 | static struct nft_object_type nft_synproxy_obj_type; |
348 | static const struct nft_object_ops nft_synproxy_obj_ops = { |
349 | .type = &nft_synproxy_obj_type, |
350 | .size = sizeof(struct nft_synproxy), |
351 | .init = nft_synproxy_obj_init, |
352 | .destroy = nft_synproxy_obj_destroy, |
353 | .dump = nft_synproxy_obj_dump, |
354 | .eval = nft_synproxy_obj_eval, |
355 | .update = nft_synproxy_obj_update, |
356 | }; |
357 | |
358 | static struct nft_object_type nft_synproxy_obj_type __read_mostly = { |
359 | .type = NFT_OBJECT_SYNPROXY, |
360 | .ops = &nft_synproxy_obj_ops, |
361 | .maxattr = NFTA_SYNPROXY_MAX, |
362 | .policy = nft_synproxy_policy, |
363 | .owner = THIS_MODULE, |
364 | }; |
365 | |
366 | static int __init nft_synproxy_module_init(void) |
367 | { |
368 | int err; |
369 | |
370 | err = nft_register_obj(obj_type: &nft_synproxy_obj_type); |
371 | if (err < 0) |
372 | return err; |
373 | |
374 | err = nft_register_expr(&nft_synproxy_type); |
375 | if (err < 0) |
376 | goto err; |
377 | |
378 | return 0; |
379 | |
380 | err: |
381 | nft_unregister_obj(obj_type: &nft_synproxy_obj_type); |
382 | return err; |
383 | } |
384 | |
385 | static void __exit nft_synproxy_module_exit(void) |
386 | { |
387 | nft_unregister_expr(&nft_synproxy_type); |
388 | nft_unregister_obj(obj_type: &nft_synproxy_obj_type); |
389 | } |
390 | |
391 | module_init(nft_synproxy_module_init); |
392 | module_exit(nft_synproxy_module_exit); |
393 | |
394 | MODULE_LICENSE("GPL" ); |
395 | MODULE_AUTHOR("Fernando Fernandez <ffmancera@riseup.net>" ); |
396 | MODULE_ALIAS_NFT_EXPR("synproxy" ); |
397 | MODULE_ALIAS_NFT_OBJ(NFT_OBJECT_SYNPROXY); |
398 | MODULE_DESCRIPTION("nftables SYNPROXY expression support" ); |
399 | |