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

netfilter: nft_exthdr: add TCP option matching

This patch implements the kernel side of the TCP option patch.

Signed-off-by: Manuel Messner <mm@skelett.io>
Reviewed-by: Florian Westphal <fw@strlen.de>
Acked-by: Phil Sutter <phil@nwl.cc>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

authored by

Manuel Messner and committed by
Pablo Neira Ayuso
935b7f64 edee4f1e

+124 -16
+16 -1
include/uapi/linux/netfilter/nf_tables.h
··· 709 709 }; 710 710 711 711 /** 712 - * enum nft_exthdr_attributes - nf_tables IPv6 extension header expression netlink attributes 712 + * enum nft_exthdr_op - nf_tables match options 713 + * 714 + * @NFT_EXTHDR_OP_IPV6: match against ipv6 extension headers 715 + * @NFT_EXTHDR_OP_TCP: match against tcp options 716 + */ 717 + enum nft_exthdr_op { 718 + NFT_EXTHDR_OP_IPV6, 719 + NFT_EXTHDR_OP_TCPOPT, 720 + __NFT_EXTHDR_OP_MAX 721 + }; 722 + #define NFT_EXTHDR_OP_MAX (__NFT_EXTHDR_OP_MAX - 1) 723 + 724 + /** 725 + * enum nft_exthdr_attributes - nf_tables extension header expression netlink attributes 713 726 * 714 727 * @NFTA_EXTHDR_DREG: destination register (NLA_U32: nft_registers) 715 728 * @NFTA_EXTHDR_TYPE: extension header type (NLA_U8) 716 729 * @NFTA_EXTHDR_OFFSET: extension header offset (NLA_U32) 717 730 * @NFTA_EXTHDR_LEN: extension header length (NLA_U32) 718 731 * @NFTA_EXTHDR_FLAGS: extension header flags (NLA_U32) 732 + * @NFTA_EXTHDR_OP: option match type (NLA_U8) 719 733 */ 720 734 enum nft_exthdr_attributes { 721 735 NFTA_EXTHDR_UNSPEC, ··· 738 724 NFTA_EXTHDR_OFFSET, 739 725 NFTA_EXTHDR_LEN, 740 726 NFTA_EXTHDR_FLAGS, 727 + NFTA_EXTHDR_OP, 741 728 __NFTA_EXTHDR_MAX 742 729 }; 743 730 #define NFTA_EXTHDR_MAX (__NFTA_EXTHDR_MAX - 1)
+2 -2
net/netfilter/Kconfig
··· 467 467 This option enables support for the "netdev" table. 468 468 469 469 config NFT_EXTHDR 470 - tristate "Netfilter nf_tables IPv6 exthdr module" 470 + tristate "Netfilter nf_tables exthdr module" 471 471 help 472 472 This option adds the "exthdr" expression that you can use to match 473 - IPv6 extension headers. 473 + IPv6 extension headers and tcp options. 474 474 475 475 config NFT_META 476 476 tristate "Netfilter nf_tables meta module"
+106 -13
net/netfilter/nft_exthdr.c
··· 15 15 #include <linux/netfilter.h> 16 16 #include <linux/netfilter/nf_tables.h> 17 17 #include <net/netfilter/nf_tables.h> 18 - // FIXME: 19 - #include <net/ipv6.h> 18 + #include <net/tcp.h> 20 19 21 20 struct nft_exthdr { 22 21 u8 type; 23 22 u8 offset; 24 23 u8 len; 24 + u8 op; 25 25 enum nft_registers dreg:8; 26 26 u8 flags; 27 27 }; 28 28 29 - static void nft_exthdr_eval(const struct nft_expr *expr, 30 - struct nft_regs *regs, 31 - const struct nft_pktinfo *pkt) 29 + static unsigned int optlen(const u8 *opt, unsigned int offset) 30 + { 31 + /* Beware zero-length options: make finite progress */ 32 + if (opt[offset] <= TCPOPT_NOP || opt[offset + 1] == 0) 33 + return 1; 34 + else 35 + return opt[offset + 1]; 36 + } 37 + 38 + static void nft_exthdr_ipv6_eval(const struct nft_expr *expr, 39 + struct nft_regs *regs, 40 + const struct nft_pktinfo *pkt) 32 41 { 33 42 struct nft_exthdr *priv = nft_expr_priv(expr); 34 43 u32 *dest = &regs->data[priv->dreg]; ··· 61 52 regs->verdict.code = NFT_BREAK; 62 53 } 63 54 55 + static void nft_exthdr_tcp_eval(const struct nft_expr *expr, 56 + struct nft_regs *regs, 57 + const struct nft_pktinfo *pkt) 58 + { 59 + u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE]; 60 + struct nft_exthdr *priv = nft_expr_priv(expr); 61 + unsigned int i, optl, tcphdr_len, offset; 62 + u32 *dest = &regs->data[priv->dreg]; 63 + struct tcphdr *tcph; 64 + u8 *opt; 65 + 66 + if (!pkt->tprot_set || pkt->tprot != IPPROTO_TCP) 67 + goto err; 68 + 69 + tcph = skb_header_pointer(pkt->skb, pkt->xt.thoff, sizeof(*tcph), buff); 70 + if (!tcph) 71 + goto err; 72 + 73 + tcphdr_len = __tcp_hdrlen(tcph); 74 + if (tcphdr_len < sizeof(*tcph)) 75 + goto err; 76 + 77 + tcph = skb_header_pointer(pkt->skb, pkt->xt.thoff, tcphdr_len, buff); 78 + if (!tcph) 79 + goto err; 80 + 81 + opt = (u8 *)tcph; 82 + for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) { 83 + optl = optlen(opt, i); 84 + 85 + if (priv->type != opt[i]) 86 + continue; 87 + 88 + if (i + optl > tcphdr_len || priv->len + priv->offset > optl) 89 + goto err; 90 + 91 + offset = i + priv->offset; 92 + dest[priv->len / NFT_REG32_SIZE] = 0; 93 + memcpy(dest, opt + offset, priv->len); 94 + 95 + return; 96 + } 97 + 98 + err: 99 + regs->verdict.code = NFT_BREAK; 100 + } 101 + 64 102 static const struct nla_policy nft_exthdr_policy[NFTA_EXTHDR_MAX + 1] = { 65 103 [NFTA_EXTHDR_DREG] = { .type = NLA_U32 }, 66 104 [NFTA_EXTHDR_TYPE] = { .type = NLA_U8 }, ··· 121 65 const struct nlattr * const tb[]) 122 66 { 123 67 struct nft_exthdr *priv = nft_expr_priv(expr); 124 - u32 offset, len, flags = 0; 68 + u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6; 125 69 int err; 126 70 127 - if (tb[NFTA_EXTHDR_DREG] == NULL || 128 - tb[NFTA_EXTHDR_TYPE] == NULL || 129 - tb[NFTA_EXTHDR_OFFSET] == NULL || 130 - tb[NFTA_EXTHDR_LEN] == NULL) 71 + if (!tb[NFTA_EXTHDR_DREG] || 72 + !tb[NFTA_EXTHDR_TYPE] || 73 + !tb[NFTA_EXTHDR_OFFSET] || 74 + !tb[NFTA_EXTHDR_LEN]) 131 75 return -EINVAL; 132 76 133 77 err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset); ··· 147 91 return -EINVAL; 148 92 } 149 93 94 + if (tb[NFTA_EXTHDR_OP]) { 95 + err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op); 96 + if (err < 0) 97 + return err; 98 + } 99 + 150 100 priv->type = nla_get_u8(tb[NFTA_EXTHDR_TYPE]); 151 101 priv->offset = offset; 152 102 priv->len = len; 153 103 priv->dreg = nft_parse_register(tb[NFTA_EXTHDR_DREG]); 154 104 priv->flags = flags; 105 + priv->op = op; 155 106 156 107 return nft_validate_register_store(ctx, priv->dreg, NULL, 157 108 NFT_DATA_VALUE, priv->len); ··· 178 115 goto nla_put_failure; 179 116 if (nla_put_be32(skb, NFTA_EXTHDR_FLAGS, htonl(priv->flags))) 180 117 goto nla_put_failure; 118 + if (nla_put_be32(skb, NFTA_EXTHDR_OP, htonl(priv->op))) 119 + goto nla_put_failure; 181 120 return 0; 182 121 183 122 nla_put_failure: ··· 187 122 } 188 123 189 124 static struct nft_expr_type nft_exthdr_type; 190 - static const struct nft_expr_ops nft_exthdr_ops = { 125 + static const struct nft_expr_ops nft_exthdr_ipv6_ops = { 191 126 .type = &nft_exthdr_type, 192 127 .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), 193 - .eval = nft_exthdr_eval, 128 + .eval = nft_exthdr_ipv6_eval, 194 129 .init = nft_exthdr_init, 195 130 .dump = nft_exthdr_dump, 196 131 }; 197 132 133 + static const struct nft_expr_ops nft_exthdr_tcp_ops = { 134 + .type = &nft_exthdr_type, 135 + .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), 136 + .eval = nft_exthdr_tcp_eval, 137 + .init = nft_exthdr_init, 138 + .dump = nft_exthdr_dump, 139 + }; 140 + 141 + static const struct nft_expr_ops * 142 + nft_exthdr_select_ops(const struct nft_ctx *ctx, 143 + const struct nlattr * const tb[]) 144 + { 145 + u32 op; 146 + 147 + if (!tb[NFTA_EXTHDR_OP]) 148 + return &nft_exthdr_ipv6_ops; 149 + 150 + op = ntohl(nla_get_u32(tb[NFTA_EXTHDR_OP])); 151 + switch (op) { 152 + case NFT_EXTHDR_OP_TCPOPT: 153 + return &nft_exthdr_tcp_ops; 154 + case NFT_EXTHDR_OP_IPV6: 155 + return &nft_exthdr_ipv6_ops; 156 + } 157 + 158 + return ERR_PTR(-EOPNOTSUPP); 159 + } 160 + 198 161 static struct nft_expr_type nft_exthdr_type __read_mostly = { 199 162 .name = "exthdr", 200 - .ops = &nft_exthdr_ops, 163 + .select_ops = &nft_exthdr_select_ops, 201 164 .policy = nft_exthdr_policy, 202 165 .maxattr = NFTA_EXTHDR_MAX, 203 166 .owner = THIS_MODULE,