1 | /* SPDX-License-Identifier: GPL-2.0 */ |
2 | #include <linux/module.h> |
3 | #include <linux/netfilter/nf_tables.h> |
4 | #include <net/netfilter/nf_tables.h> |
5 | #include <net/netfilter/nf_tables_core.h> |
6 | #include <net/netfilter/nf_socket.h> |
7 | #include <net/inet_sock.h> |
8 | #include <net/tcp.h> |
9 | |
10 | struct nft_socket { |
11 | enum nft_socket_keys key:8; |
12 | u8 level; |
13 | u8 len; |
14 | union { |
15 | u8 dreg; |
16 | }; |
17 | }; |
18 | |
19 | static void nft_socket_wildcard(const struct nft_pktinfo *pkt, |
20 | struct nft_regs *regs, struct sock *sk, |
21 | u32 *dest) |
22 | { |
23 | switch (nft_pf(pkt)) { |
24 | case NFPROTO_IPV4: |
25 | nft_reg_store8(dreg: dest, inet_sk(sk)->inet_rcv_saddr == 0); |
26 | break; |
27 | #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) |
28 | case NFPROTO_IPV6: |
29 | nft_reg_store8(dreg: dest, val: ipv6_addr_any(a: &sk->sk_v6_rcv_saddr)); |
30 | break; |
31 | #endif |
32 | default: |
33 | regs->verdict.code = NFT_BREAK; |
34 | return; |
35 | } |
36 | } |
37 | |
38 | #ifdef CONFIG_SOCK_CGROUP_DATA |
39 | static noinline bool |
40 | nft_sock_get_eval_cgroupv2(u32 *dest, struct sock *sk, const struct nft_pktinfo *pkt, u32 level) |
41 | { |
42 | struct cgroup *cgrp; |
43 | u64 cgid; |
44 | |
45 | if (!sk_fullsock(sk)) |
46 | return false; |
47 | |
48 | cgrp = cgroup_ancestor(cgrp: sock_cgroup_ptr(skcd: &sk->sk_cgrp_data), ancestor_level: level); |
49 | if (!cgrp) |
50 | return false; |
51 | |
52 | cgid = cgroup_id(cgrp); |
53 | memcpy(dest, &cgid, sizeof(u64)); |
54 | return true; |
55 | } |
56 | #endif |
57 | |
58 | static struct sock *nft_socket_do_lookup(const struct nft_pktinfo *pkt) |
59 | { |
60 | const struct net_device *indev = nft_in(pkt); |
61 | const struct sk_buff *skb = pkt->skb; |
62 | struct sock *sk = NULL; |
63 | |
64 | if (!indev) |
65 | return NULL; |
66 | |
67 | switch (nft_pf(pkt)) { |
68 | case NFPROTO_IPV4: |
69 | sk = nf_sk_lookup_slow_v4(net: nft_net(pkt), skb, indev); |
70 | break; |
71 | #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) |
72 | case NFPROTO_IPV6: |
73 | sk = nf_sk_lookup_slow_v6(net: nft_net(pkt), skb, indev); |
74 | break; |
75 | #endif |
76 | default: |
77 | WARN_ON_ONCE(1); |
78 | break; |
79 | } |
80 | |
81 | return sk; |
82 | } |
83 | |
84 | static void nft_socket_eval(const struct nft_expr *expr, |
85 | struct nft_regs *regs, |
86 | const struct nft_pktinfo *pkt) |
87 | { |
88 | const struct nft_socket *priv = nft_expr_priv(expr); |
89 | struct sk_buff *skb = pkt->skb; |
90 | struct sock *sk = skb->sk; |
91 | u32 *dest = ®s->data[priv->dreg]; |
92 | |
93 | if (sk && !net_eq(net1: nft_net(pkt), net2: sock_net(sk))) |
94 | sk = NULL; |
95 | |
96 | if (!sk) |
97 | sk = nft_socket_do_lookup(pkt); |
98 | |
99 | if (!sk) { |
100 | regs->verdict.code = NFT_BREAK; |
101 | return; |
102 | } |
103 | |
104 | switch(priv->key) { |
105 | case NFT_SOCKET_TRANSPARENT: |
106 | nft_reg_store8(dreg: dest, val: inet_sk_transparent(sk)); |
107 | break; |
108 | case NFT_SOCKET_MARK: |
109 | if (sk_fullsock(sk)) { |
110 | *dest = READ_ONCE(sk->sk_mark); |
111 | } else { |
112 | regs->verdict.code = NFT_BREAK; |
113 | return; |
114 | } |
115 | break; |
116 | case NFT_SOCKET_WILDCARD: |
117 | if (!sk_fullsock(sk)) { |
118 | regs->verdict.code = NFT_BREAK; |
119 | return; |
120 | } |
121 | nft_socket_wildcard(pkt, regs, sk, dest); |
122 | break; |
123 | #ifdef CONFIG_SOCK_CGROUP_DATA |
124 | case NFT_SOCKET_CGROUPV2: |
125 | if (!nft_sock_get_eval_cgroupv2(dest, sk, pkt, level: priv->level)) { |
126 | regs->verdict.code = NFT_BREAK; |
127 | return; |
128 | } |
129 | break; |
130 | #endif |
131 | default: |
132 | WARN_ON(1); |
133 | regs->verdict.code = NFT_BREAK; |
134 | } |
135 | |
136 | if (sk != skb->sk) |
137 | sock_gen_put(sk); |
138 | } |
139 | |
140 | static const struct nla_policy nft_socket_policy[NFTA_SOCKET_MAX + 1] = { |
141 | [NFTA_SOCKET_KEY] = NLA_POLICY_MAX(NLA_BE32, 255), |
142 | [NFTA_SOCKET_DREG] = { .type = NLA_U32 }, |
143 | [NFTA_SOCKET_LEVEL] = NLA_POLICY_MAX(NLA_BE32, 255), |
144 | }; |
145 | |
146 | static int nft_socket_init(const struct nft_ctx *ctx, |
147 | const struct nft_expr *expr, |
148 | const struct nlattr * const tb[]) |
149 | { |
150 | struct nft_socket *priv = nft_expr_priv(expr); |
151 | unsigned int len; |
152 | |
153 | if (!tb[NFTA_SOCKET_DREG] || !tb[NFTA_SOCKET_KEY]) |
154 | return -EINVAL; |
155 | |
156 | switch(ctx->family) { |
157 | case NFPROTO_IPV4: |
158 | #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) |
159 | case NFPROTO_IPV6: |
160 | #endif |
161 | case NFPROTO_INET: |
162 | break; |
163 | default: |
164 | return -EOPNOTSUPP; |
165 | } |
166 | |
167 | priv->key = ntohl(nla_get_be32(tb[NFTA_SOCKET_KEY])); |
168 | switch(priv->key) { |
169 | case NFT_SOCKET_TRANSPARENT: |
170 | case NFT_SOCKET_WILDCARD: |
171 | len = sizeof(u8); |
172 | break; |
173 | case NFT_SOCKET_MARK: |
174 | len = sizeof(u32); |
175 | break; |
176 | #ifdef CONFIG_CGROUPS |
177 | case NFT_SOCKET_CGROUPV2: { |
178 | unsigned int level; |
179 | |
180 | if (!tb[NFTA_SOCKET_LEVEL]) |
181 | return -EINVAL; |
182 | |
183 | level = ntohl(nla_get_be32(tb[NFTA_SOCKET_LEVEL])); |
184 | if (level > 255) |
185 | return -EOPNOTSUPP; |
186 | |
187 | priv->level = level; |
188 | len = sizeof(u64); |
189 | break; |
190 | } |
191 | #endif |
192 | default: |
193 | return -EOPNOTSUPP; |
194 | } |
195 | |
196 | priv->len = len; |
197 | return nft_parse_register_store(ctx, attr: tb[NFTA_SOCKET_DREG], dreg: &priv->dreg, |
198 | NULL, type: NFT_DATA_VALUE, len); |
199 | } |
200 | |
201 | static int nft_socket_dump(struct sk_buff *skb, |
202 | const struct nft_expr *expr, bool reset) |
203 | { |
204 | const struct nft_socket *priv = nft_expr_priv(expr); |
205 | |
206 | if (nla_put_be32(skb, attrtype: NFTA_SOCKET_KEY, htonl(priv->key))) |
207 | return -1; |
208 | if (nft_dump_register(skb, attr: NFTA_SOCKET_DREG, reg: priv->dreg)) |
209 | return -1; |
210 | if (priv->key == NFT_SOCKET_CGROUPV2 && |
211 | nla_put_be32(skb, attrtype: NFTA_SOCKET_LEVEL, htonl(priv->level))) |
212 | return -1; |
213 | return 0; |
214 | } |
215 | |
216 | static bool nft_socket_reduce(struct nft_regs_track *track, |
217 | const struct nft_expr *expr) |
218 | { |
219 | const struct nft_socket *priv = nft_expr_priv(expr); |
220 | const struct nft_socket *socket; |
221 | |
222 | if (!nft_reg_track_cmp(track, expr, dreg: priv->dreg)) { |
223 | nft_reg_track_update(track, expr, dreg: priv->dreg, len: priv->len); |
224 | return false; |
225 | } |
226 | |
227 | socket = nft_expr_priv(expr: track->regs[priv->dreg].selector); |
228 | if (priv->key != socket->key || |
229 | priv->dreg != socket->dreg || |
230 | priv->level != socket->level) { |
231 | nft_reg_track_update(track, expr, dreg: priv->dreg, len: priv->len); |
232 | return false; |
233 | } |
234 | |
235 | if (!track->regs[priv->dreg].bitwise) |
236 | return true; |
237 | |
238 | return nft_expr_reduce_bitwise(track, expr); |
239 | } |
240 | |
241 | static int nft_socket_validate(const struct nft_ctx *ctx, |
242 | const struct nft_expr *expr, |
243 | const struct nft_data **data) |
244 | { |
245 | return nft_chain_validate_hooks(chain: ctx->chain, |
246 | hook_flags: (1 << NF_INET_PRE_ROUTING) | |
247 | (1 << NF_INET_LOCAL_IN) | |
248 | (1 << NF_INET_LOCAL_OUT)); |
249 | } |
250 | |
251 | static struct nft_expr_type nft_socket_type; |
252 | static const struct nft_expr_ops nft_socket_ops = { |
253 | .type = &nft_socket_type, |
254 | .size = NFT_EXPR_SIZE(sizeof(struct nft_socket)), |
255 | .eval = nft_socket_eval, |
256 | .init = nft_socket_init, |
257 | .dump = nft_socket_dump, |
258 | .validate = nft_socket_validate, |
259 | .reduce = nft_socket_reduce, |
260 | }; |
261 | |
262 | static struct nft_expr_type nft_socket_type __read_mostly = { |
263 | .name = "socket" , |
264 | .ops = &nft_socket_ops, |
265 | .policy = nft_socket_policy, |
266 | .maxattr = NFTA_SOCKET_MAX, |
267 | .owner = THIS_MODULE, |
268 | }; |
269 | |
270 | static int __init nft_socket_module_init(void) |
271 | { |
272 | return nft_register_expr(&nft_socket_type); |
273 | } |
274 | |
275 | static void __exit nft_socket_module_exit(void) |
276 | { |
277 | nft_unregister_expr(&nft_socket_type); |
278 | } |
279 | |
280 | module_init(nft_socket_module_init); |
281 | module_exit(nft_socket_module_exit); |
282 | |
283 | MODULE_LICENSE("GPL" ); |
284 | MODULE_AUTHOR("Máté Eckl" ); |
285 | MODULE_DESCRIPTION("nf_tables socket match module" ); |
286 | MODULE_ALIAS_NFT_EXPR("socket" ); |
287 | |