1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | #include <net/ip.h> |
3 | #include <net/tcp.h> |
4 | |
5 | #include <net/netfilter/nf_tables.h> |
6 | #include <linux/netfilter/nfnetlink_osf.h> |
7 | |
8 | struct nft_osf { |
9 | u8 dreg; |
10 | u8 ttl; |
11 | u32 flags; |
12 | }; |
13 | |
14 | static const struct nla_policy nft_osf_policy[NFTA_OSF_MAX + 1] = { |
15 | [NFTA_OSF_DREG] = { .type = NLA_U32 }, |
16 | [NFTA_OSF_TTL] = { .type = NLA_U8 }, |
17 | [NFTA_OSF_FLAGS] = { .type = NLA_U32 }, |
18 | }; |
19 | |
20 | static void nft_osf_eval(const struct nft_expr *expr, struct nft_regs *regs, |
21 | const struct nft_pktinfo *pkt) |
22 | { |
23 | struct nft_osf *priv = nft_expr_priv(expr); |
24 | u32 *dest = ®s->data[priv->dreg]; |
25 | struct sk_buff *skb = pkt->skb; |
26 | char os_match[NFT_OSF_MAXGENRELEN]; |
27 | const struct tcphdr *tcp; |
28 | struct nf_osf_data data; |
29 | struct tcphdr _tcph; |
30 | |
31 | if (pkt->tprot != IPPROTO_TCP) { |
32 | regs->verdict.code = NFT_BREAK; |
33 | return; |
34 | } |
35 | |
36 | tcp = skb_header_pointer(skb, offset: ip_hdrlen(skb), |
37 | len: sizeof(struct tcphdr), buffer: &_tcph); |
38 | if (!tcp) { |
39 | regs->verdict.code = NFT_BREAK; |
40 | return; |
41 | } |
42 | if (!tcp->syn) { |
43 | regs->verdict.code = NFT_BREAK; |
44 | return; |
45 | } |
46 | |
47 | if (!nf_osf_find(skb, nf_osf_fingers, ttl_check: priv->ttl, data: &data)) { |
48 | strscpy_pad((char *)dest, "unknown" , NFT_OSF_MAXGENRELEN); |
49 | } else { |
50 | if (priv->flags & NFT_OSF_F_VERSION) |
51 | snprintf(buf: os_match, NFT_OSF_MAXGENRELEN, fmt: "%s:%s" , |
52 | data.genre, data.version); |
53 | else |
54 | strscpy(os_match, data.genre, NFT_OSF_MAXGENRELEN); |
55 | |
56 | strscpy_pad((char *)dest, os_match, NFT_OSF_MAXGENRELEN); |
57 | } |
58 | } |
59 | |
60 | static int nft_osf_init(const struct nft_ctx *ctx, |
61 | const struct nft_expr *expr, |
62 | const struct nlattr * const tb[]) |
63 | { |
64 | struct nft_osf *priv = nft_expr_priv(expr); |
65 | u32 flags; |
66 | u8 ttl; |
67 | |
68 | if (!tb[NFTA_OSF_DREG]) |
69 | return -EINVAL; |
70 | |
71 | if (tb[NFTA_OSF_TTL]) { |
72 | ttl = nla_get_u8(nla: tb[NFTA_OSF_TTL]); |
73 | if (ttl > 2) |
74 | return -EINVAL; |
75 | priv->ttl = ttl; |
76 | } |
77 | |
78 | if (tb[NFTA_OSF_FLAGS]) { |
79 | flags = ntohl(nla_get_be32(tb[NFTA_OSF_FLAGS])); |
80 | if (flags != NFT_OSF_F_VERSION) |
81 | return -EINVAL; |
82 | priv->flags = flags; |
83 | } |
84 | |
85 | return nft_parse_register_store(ctx, attr: tb[NFTA_OSF_DREG], dreg: &priv->dreg, |
86 | NULL, type: NFT_DATA_VALUE, |
87 | NFT_OSF_MAXGENRELEN); |
88 | } |
89 | |
90 | static int nft_osf_dump(struct sk_buff *skb, |
91 | const struct nft_expr *expr, bool reset) |
92 | { |
93 | const struct nft_osf *priv = nft_expr_priv(expr); |
94 | |
95 | if (nla_put_u8(skb, attrtype: NFTA_OSF_TTL, value: priv->ttl)) |
96 | goto nla_put_failure; |
97 | |
98 | if (nla_put_u32(skb, attrtype: NFTA_OSF_FLAGS, ntohl((__force __be32)priv->flags))) |
99 | goto nla_put_failure; |
100 | |
101 | if (nft_dump_register(skb, attr: NFTA_OSF_DREG, reg: priv->dreg)) |
102 | goto nla_put_failure; |
103 | |
104 | return 0; |
105 | |
106 | nla_put_failure: |
107 | return -1; |
108 | } |
109 | |
110 | static int nft_osf_validate(const struct nft_ctx *ctx, |
111 | const struct nft_expr *expr, |
112 | const struct nft_data **data) |
113 | { |
114 | unsigned int hooks; |
115 | |
116 | switch (ctx->family) { |
117 | case NFPROTO_IPV4: |
118 | case NFPROTO_IPV6: |
119 | case NFPROTO_INET: |
120 | hooks = (1 << NF_INET_LOCAL_IN) | |
121 | (1 << NF_INET_PRE_ROUTING) | |
122 | (1 << NF_INET_FORWARD); |
123 | break; |
124 | default: |
125 | return -EOPNOTSUPP; |
126 | } |
127 | |
128 | return nft_chain_validate_hooks(chain: ctx->chain, hook_flags: hooks); |
129 | } |
130 | |
131 | static bool nft_osf_reduce(struct nft_regs_track *track, |
132 | const struct nft_expr *expr) |
133 | { |
134 | struct nft_osf *priv = nft_expr_priv(expr); |
135 | struct nft_osf *osf; |
136 | |
137 | if (!nft_reg_track_cmp(track, expr, dreg: priv->dreg)) { |
138 | nft_reg_track_update(track, expr, dreg: priv->dreg, NFT_OSF_MAXGENRELEN); |
139 | return false; |
140 | } |
141 | |
142 | osf = nft_expr_priv(expr: track->regs[priv->dreg].selector); |
143 | if (priv->flags != osf->flags || |
144 | priv->ttl != osf->ttl) { |
145 | nft_reg_track_update(track, expr, dreg: priv->dreg, NFT_OSF_MAXGENRELEN); |
146 | return false; |
147 | } |
148 | |
149 | if (!track->regs[priv->dreg].bitwise) |
150 | return true; |
151 | |
152 | return false; |
153 | } |
154 | |
155 | static struct nft_expr_type nft_osf_type; |
156 | static const struct nft_expr_ops nft_osf_op = { |
157 | .eval = nft_osf_eval, |
158 | .size = NFT_EXPR_SIZE(sizeof(struct nft_osf)), |
159 | .init = nft_osf_init, |
160 | .dump = nft_osf_dump, |
161 | .type = &nft_osf_type, |
162 | .validate = nft_osf_validate, |
163 | .reduce = nft_osf_reduce, |
164 | }; |
165 | |
166 | static struct nft_expr_type nft_osf_type __read_mostly = { |
167 | .ops = &nft_osf_op, |
168 | .name = "osf" , |
169 | .owner = THIS_MODULE, |
170 | .policy = nft_osf_policy, |
171 | .maxattr = NFTA_OSF_MAX, |
172 | }; |
173 | |
174 | static int __init nft_osf_module_init(void) |
175 | { |
176 | return nft_register_expr(&nft_osf_type); |
177 | } |
178 | |
179 | static void __exit nft_osf_module_exit(void) |
180 | { |
181 | return nft_unregister_expr(&nft_osf_type); |
182 | } |
183 | |
184 | module_init(nft_osf_module_init); |
185 | module_exit(nft_osf_module_exit); |
186 | |
187 | MODULE_LICENSE("GPL" ); |
188 | MODULE_AUTHOR("Fernando Fernandez <ffmancera@riseup.net>" ); |
189 | MODULE_ALIAS_NFT_EXPR("osf" ); |
190 | MODULE_DESCRIPTION("nftables passive OS fingerprint support" ); |
191 | |