Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfilter: nf_tables: Add synproxy support

Add synproxy support for nf_tables. This behaves like the iptables
synproxy target but it is structured in a way that allows us to propose
improvements in the future.

Signed-off-by: Fernando Fernandez Mancera <ffmancera@riseup.net>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

authored by

Fernando Fernandez Mancera and committed by
Pablo Neira Ayuso
ad49d86e 6f7b841b

+325
+1
include/net/netfilter/nf_conntrack_synproxy.h
··· 2 2 #ifndef _NF_CONNTRACK_SYNPROXY_H 3 3 #define _NF_CONNTRACK_SYNPROXY_H 4 4 5 + #include <net/netfilter/nf_conntrack_seqadj.h> 5 6 #include <net/netns/generic.h> 6 7 7 8 struct nf_conn_synproxy {
+5
include/net/netfilter/nf_synproxy.h
··· 39 39 const struct nf_hook_state *nhs); 40 40 int nf_synproxy_ipv6_init(struct synproxy_net *snet, struct net *net); 41 41 void nf_synproxy_ipv6_fini(struct synproxy_net *snet, struct net *net); 42 + #else 43 + static inline int 44 + nf_synproxy_ipv6_init(struct synproxy_net *snet, struct net *net) { return 0; } 45 + static inline void 46 + nf_synproxy_ipv6_fini(struct synproxy_net *snet, struct net *net) {}; 42 47 #endif /* CONFIG_IPV6 */ 43 48 44 49 #endif /* _NF_SYNPROXY_SHARED_H */
+4
include/uapi/linux/netfilter/nf_synproxy.h
··· 9 9 #define NF_SYNPROXY_OPT_SACK_PERM 0x04 10 10 #define NF_SYNPROXY_OPT_TIMESTAMP 0x08 11 11 #define NF_SYNPROXY_OPT_ECN 0x10 12 + #define NF_SYNPROXY_OPT_MASK (NF_SYNPROXY_OPT_MSS | \ 13 + NF_SYNPROXY_OPT_WSCALE | \ 14 + NF_SYNPROXY_OPT_SACK_PERM | \ 15 + NF_SYNPROXY_OPT_TIMESTAMP) 12 16 13 17 struct nf_synproxy_info { 14 18 __u8 options;
+16
include/uapi/linux/netfilter/nf_tables.h
··· 1552 1552 }; 1553 1553 1554 1554 /** 1555 + * enum nft_synproxy_attributes - nf_tables synproxy expression netlink attributes 1556 + * 1557 + * @NFTA_SYNPROXY_MSS: mss value sent to the backend (NLA_U16) 1558 + * @NFTA_SYNPROXY_WSCALE: wscale value sent to the backend (NLA_U8) 1559 + * @NFTA_SYNPROXY_FLAGS: flags (NLA_U32) 1560 + */ 1561 + enum nft_synproxy_attributes { 1562 + NFTA_SYNPROXY_UNSPEC, 1563 + NFTA_SYNPROXY_MSS, 1564 + NFTA_SYNPROXY_WSCALE, 1565 + NFTA_SYNPROXY_FLAGS, 1566 + __NFTA_SYNPROXY_MAX, 1567 + }; 1568 + #define NFTA_SYNPROXY_MAX (__NFTA_SYNPROXY_MAX - 1) 1569 + 1570 + /** 1555 1571 * enum nft_device_attributes - nf_tables device netlink attributes 1556 1572 * 1557 1573 * @NFTA_DEVICE_NAME: name of this device (NLA_STRING)
+11
net/netfilter/Kconfig
··· 651 651 help 652 652 This makes transparent proxy support available in nftables. 653 653 654 + config NFT_SYNPROXY 655 + tristate "Netfilter nf_tables SYNPROXY expression support" 656 + depends on NF_CONNTRACK && NETFILTER_ADVANCED 657 + select NETFILTER_SYNPROXY 658 + select SYN_COOKIES 659 + help 660 + The SYNPROXY expression allows you to intercept TCP connections and 661 + establish them using syncookies before they are passed on to the 662 + server. This allows to avoid conntrack and server resource usage 663 + during SYN-flood attacks. 664 + 654 665 if NF_TABLES_NETDEV 655 666 656 667 config NF_DUP_NETDEV
+1
net/netfilter/Makefile
··· 110 110 obj-$(CONFIG_NFT_OSF) += nft_osf.o 111 111 obj-$(CONFIG_NFT_TPROXY) += nft_tproxy.o 112 112 obj-$(CONFIG_NFT_XFRM) += nft_xfrm.o 113 + obj-$(CONFIG_NFT_SYNPROXY) += nft_synproxy.o 113 114 114 115 obj-$(CONFIG_NFT_NAT) += nft_chain_nat.o 115 116
+287
net/netfilter/nft_synproxy.c
··· 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 + 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 + if (opts->options & NF_SYNPROXY_OPT_TIMESTAMP) 35 + synproxy_init_timestamp_cookie(info, opts); 36 + else 37 + opts->options &= ~(NF_SYNPROXY_OPT_WSCALE | 38 + NF_SYNPROXY_OPT_SACK_PERM | 39 + NF_SYNPROXY_OPT_ECN); 40 + } 41 + 42 + static void nft_synproxy_eval_v4(const struct nft_expr *expr, 43 + struct nft_regs *regs, 44 + const struct nft_pktinfo *pkt, 45 + const struct tcphdr *tcp, 46 + struct tcphdr *_tcph, 47 + struct synproxy_options *opts) 48 + { 49 + struct nft_synproxy *priv = nft_expr_priv(expr); 50 + struct nf_synproxy_info info = priv->info; 51 + struct net *net = nft_net(pkt); 52 + struct synproxy_net *snet = synproxy_pernet(net); 53 + struct sk_buff *skb = pkt->skb; 54 + 55 + if (tcp->syn) { 56 + /* Initial SYN from client */ 57 + nft_synproxy_tcp_options(opts, tcp, snet, &info, priv); 58 + synproxy_send_client_synack(net, skb, tcp, opts); 59 + consume_skb(skb); 60 + regs->verdict.code = NF_STOLEN; 61 + } else if (tcp->ack) { 62 + /* ACK from client */ 63 + if (synproxy_recv_client_ack(net, skb, tcp, opts, 64 + ntohl(tcp->seq))) { 65 + consume_skb(skb); 66 + regs->verdict.code = NF_STOLEN; 67 + } else { 68 + regs->verdict.code = NF_DROP; 69 + } 70 + } 71 + } 72 + 73 + #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) 74 + static void nft_synproxy_eval_v6(const struct nft_expr *expr, 75 + struct nft_regs *regs, 76 + const struct nft_pktinfo *pkt, 77 + const struct tcphdr *tcp, 78 + struct tcphdr *_tcph, 79 + struct synproxy_options *opts) 80 + { 81 + struct nft_synproxy *priv = nft_expr_priv(expr); 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, priv); 90 + synproxy_send_client_synack_ipv6(net, skb, 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, 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_eval(const struct nft_expr *expr, 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 = pkt->xt.thoff; 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, nft_hook(pkt), thoff, IPPROTO_TCP)) { 122 + regs->verdict.code = NF_DROP; 123 + return; 124 + } 125 + 126 + tcp = skb_header_pointer(skb, pkt->xt.thoff, 127 + sizeof(struct tcphdr), 128 + &_tcph); 129 + if (!tcp) { 130 + regs->verdict.code = NF_DROP; 131 + return; 132 + } 133 + 134 + if (!synproxy_parse_options(skb, thoff, tcp, &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(expr, regs, pkt, tcp, &_tcph, &opts); 142 + return; 143 + #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) 144 + case htons(ETH_P_IPV6): 145 + nft_synproxy_eval_v6(expr, regs, pkt, tcp, &_tcph, &opts); 146 + return; 147 + #endif 148 + } 149 + regs->verdict.code = NFT_BREAK; 150 + } 151 + 152 + static int nft_synproxy_init(const struct nft_ctx *ctx, 153 + const struct nft_expr *expr, 154 + const struct nlattr * const tb[]) 155 + { 156 + struct synproxy_net *snet = synproxy_pernet(ctx->net); 157 + struct nft_synproxy *priv = nft_expr_priv(expr); 158 + u32 flags; 159 + int err; 160 + 161 + if (tb[NFTA_SYNPROXY_MSS]) 162 + priv->info.mss = ntohs(nla_get_be16(tb[NFTA_SYNPROXY_MSS])); 163 + if (tb[NFTA_SYNPROXY_WSCALE]) 164 + priv->info.wscale = nla_get_u8(tb[NFTA_SYNPROXY_WSCALE]); 165 + if (tb[NFTA_SYNPROXY_FLAGS]) { 166 + flags = ntohl(nla_get_be32(tb[NFTA_SYNPROXY_FLAGS])); 167 + if (flags & ~NF_SYNPROXY_OPT_MASK) 168 + return -EOPNOTSUPP; 169 + priv->info.options = flags; 170 + } 171 + 172 + err = nf_ct_netns_get(ctx->net, ctx->family); 173 + if (err) 174 + return err; 175 + 176 + switch (ctx->family) { 177 + case NFPROTO_IPV4: 178 + err = nf_synproxy_ipv4_init(snet, ctx->net); 179 + if (err) 180 + goto nf_ct_failure; 181 + break; 182 + #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) 183 + case NFPROTO_IPV6: 184 + err = nf_synproxy_ipv6_init(snet, ctx->net); 185 + if (err) 186 + goto nf_ct_failure; 187 + break; 188 + #endif 189 + case NFPROTO_INET: 190 + case NFPROTO_BRIDGE: 191 + err = nf_synproxy_ipv4_init(snet, ctx->net); 192 + if (err) 193 + goto nf_ct_failure; 194 + err = nf_synproxy_ipv6_init(snet, ctx->net); 195 + if (err) 196 + goto nf_ct_failure; 197 + break; 198 + } 199 + 200 + return 0; 201 + 202 + nf_ct_failure: 203 + nf_ct_netns_put(ctx->net, ctx->family); 204 + return err; 205 + } 206 + 207 + static void nft_synproxy_destroy(const struct nft_ctx *ctx, 208 + const struct nft_expr *expr) 209 + { 210 + struct synproxy_net *snet = synproxy_pernet(ctx->net); 211 + 212 + switch (ctx->family) { 213 + case NFPROTO_IPV4: 214 + nf_synproxy_ipv4_fini(snet, ctx->net); 215 + break; 216 + #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) 217 + case NFPROTO_IPV6: 218 + nf_synproxy_ipv6_fini(snet, ctx->net); 219 + break; 220 + #endif 221 + case NFPROTO_INET: 222 + case NFPROTO_BRIDGE: 223 + nf_synproxy_ipv4_fini(snet, ctx->net); 224 + nf_synproxy_ipv6_fini(snet, ctx->net); 225 + break; 226 + } 227 + nf_ct_netns_put(ctx->net, ctx->family); 228 + } 229 + 230 + static int nft_synproxy_dump(struct sk_buff *skb, const struct nft_expr *expr) 231 + { 232 + const struct nft_synproxy *priv = nft_expr_priv(expr); 233 + 234 + if (nla_put_be16(skb, NFTA_SYNPROXY_MSS, htons(priv->info.mss)) || 235 + nla_put_u8(skb, NFTA_SYNPROXY_WSCALE, priv->info.wscale) || 236 + nla_put_be32(skb, NFTA_SYNPROXY_FLAGS, htonl(priv->info.options))) 237 + goto nla_put_failure; 238 + 239 + return 0; 240 + 241 + nla_put_failure: 242 + return -1; 243 + } 244 + 245 + static int nft_synproxy_validate(const struct nft_ctx *ctx, 246 + const struct nft_expr *expr, 247 + const struct nft_data **data) 248 + { 249 + return nft_chain_validate_hooks(ctx->chain, (1 << NF_INET_LOCAL_IN) | 250 + (1 << NF_INET_FORWARD)); 251 + } 252 + 253 + static struct nft_expr_type nft_synproxy_type; 254 + static const struct nft_expr_ops nft_synproxy_ops = { 255 + .eval = nft_synproxy_eval, 256 + .size = NFT_EXPR_SIZE(sizeof(struct nft_synproxy)), 257 + .init = nft_synproxy_init, 258 + .destroy = nft_synproxy_destroy, 259 + .dump = nft_synproxy_dump, 260 + .type = &nft_synproxy_type, 261 + .validate = nft_synproxy_validate, 262 + }; 263 + 264 + static struct nft_expr_type nft_synproxy_type __read_mostly = { 265 + .ops = &nft_synproxy_ops, 266 + .name = "synproxy", 267 + .owner = THIS_MODULE, 268 + .policy = nft_synproxy_policy, 269 + .maxattr = NFTA_SYNPROXY_MAX, 270 + }; 271 + 272 + static int __init nft_synproxy_module_init(void) 273 + { 274 + return nft_register_expr(&nft_synproxy_type); 275 + } 276 + 277 + static void __exit nft_synproxy_module_exit(void) 278 + { 279 + return nft_unregister_expr(&nft_synproxy_type); 280 + } 281 + 282 + module_init(nft_synproxy_module_init); 283 + module_exit(nft_synproxy_module_exit); 284 + 285 + MODULE_LICENSE("GPL"); 286 + MODULE_AUTHOR("Fernando Fernandez <ffmancera@riseup.net>"); 287 + MODULE_ALIAS_NFT_EXPR("synproxy");