Commit | Line | Data |
---|---|---|
290180e2 PNA |
1 | /* SPDX-License-Identifier: GPL-2.0 */ |
2 | #include <linux/kernel.h> | |
3 | #include <linux/init.h> | |
4 | #include <linux/module.h> | |
5 | #include <linux/spinlock.h> | |
6 | #include <linux/netlink.h> | |
7 | #include <linux/netfilter.h> | |
8 | #include <linux/netfilter/nf_tables.h> | |
9 | #include <net/netfilter/nf_tables.h> | |
10 | #include <net/netfilter/nf_conntrack.h> | |
11 | #include <net/netfilter/nf_conntrack_count.h> | |
12 | #include <net/netfilter/nf_conntrack_core.h> | |
13 | #include <net/netfilter/nf_conntrack_tuple.h> | |
14 | #include <net/netfilter/nf_conntrack_zones.h> | |
15 | ||
16 | struct nft_connlimit { | |
cb2b36f5 YHW |
17 | struct nf_conncount_list list; |
18 | u32 limit; | |
19 | bool invert; | |
290180e2 PNA |
20 | }; |
21 | ||
22 | static inline void nft_connlimit_do_eval(struct nft_connlimit *priv, | |
23 | struct nft_regs *regs, | |
24 | const struct nft_pktinfo *pkt, | |
25 | const struct nft_set_ext *ext) | |
26 | { | |
27 | const struct nf_conntrack_zone *zone = &nf_ct_zone_dflt; | |
28 | const struct nf_conntrack_tuple *tuple_ptr; | |
29 | struct nf_conntrack_tuple tuple; | |
30 | enum ip_conntrack_info ctinfo; | |
31 | const struct nf_conn *ct; | |
32 | unsigned int count; | |
290180e2 PNA |
33 | |
34 | tuple_ptr = &tuple; | |
35 | ||
36 | ct = nf_ct_get(pkt->skb, &ctinfo); | |
37 | if (ct != NULL) { | |
38 | tuple_ptr = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; | |
39 | zone = nf_ct_zone(ct); | |
40 | } else if (!nf_ct_get_tuplepr(pkt->skb, skb_network_offset(pkt->skb), | |
41 | nft_pf(pkt), nft_net(pkt), &tuple)) { | |
42 | regs->verdict.code = NF_DROP; | |
43 | return; | |
44 | } | |
45 | ||
df4a9025 | 46 | if (nf_conncount_add(nft_net(pkt), &priv->list, tuple_ptr, zone)) { |
290180e2 | 47 | regs->verdict.code = NF_DROP; |
290180e2 PNA |
48 | return; |
49 | } | |
df4a9025 FW |
50 | |
51 | count = priv->list.count; | |
290180e2 PNA |
52 | |
53 | if ((count > priv->limit) ^ priv->invert) { | |
54 | regs->verdict.code = NFT_BREAK; | |
55 | return; | |
56 | } | |
57 | } | |
58 | ||
59 | static int nft_connlimit_do_init(const struct nft_ctx *ctx, | |
60 | const struct nlattr * const tb[], | |
61 | struct nft_connlimit *priv) | |
62 | { | |
63 | bool invert = false; | |
64 | u32 flags, limit; | |
65 | ||
66 | if (!tb[NFTA_CONNLIMIT_COUNT]) | |
67 | return -EINVAL; | |
68 | ||
69 | limit = ntohl(nla_get_be32(tb[NFTA_CONNLIMIT_COUNT])); | |
70 | ||
71 | if (tb[NFTA_CONNLIMIT_FLAGS]) { | |
72 | flags = ntohl(nla_get_be32(tb[NFTA_CONNLIMIT_FLAGS])); | |
73 | if (flags & ~NFT_CONNLIMIT_F_INV) | |
74 | return -EOPNOTSUPP; | |
75 | if (flags & NFT_CONNLIMIT_F_INV) | |
76 | invert = true; | |
77 | } | |
78 | ||
cb2b36f5 | 79 | nf_conncount_list_init(&priv->list); |
290180e2 PNA |
80 | priv->limit = limit; |
81 | priv->invert = invert; | |
82 | ||
83 | return nf_ct_netns_get(ctx->net, ctx->family); | |
84 | } | |
85 | ||
86 | static void nft_connlimit_do_destroy(const struct nft_ctx *ctx, | |
87 | struct nft_connlimit *priv) | |
88 | { | |
89 | nf_ct_netns_put(ctx->net, ctx->family); | |
cb2b36f5 | 90 | nf_conncount_cache_free(&priv->list); |
290180e2 PNA |
91 | } |
92 | ||
93 | static int nft_connlimit_do_dump(struct sk_buff *skb, | |
94 | struct nft_connlimit *priv) | |
95 | { | |
96 | if (nla_put_be32(skb, NFTA_CONNLIMIT_COUNT, htonl(priv->limit))) | |
97 | goto nla_put_failure; | |
98 | if (priv->invert && | |
99 | nla_put_be32(skb, NFTA_CONNLIMIT_FLAGS, htonl(NFT_CONNLIMIT_F_INV))) | |
100 | goto nla_put_failure; | |
101 | ||
102 | return 0; | |
103 | ||
104 | nla_put_failure: | |
105 | return -1; | |
106 | } | |
107 | ||
108 | static inline void nft_connlimit_obj_eval(struct nft_object *obj, | |
109 | struct nft_regs *regs, | |
110 | const struct nft_pktinfo *pkt) | |
111 | { | |
112 | struct nft_connlimit *priv = nft_obj_data(obj); | |
113 | ||
114 | nft_connlimit_do_eval(priv, regs, pkt, NULL); | |
115 | } | |
116 | ||
117 | static int nft_connlimit_obj_init(const struct nft_ctx *ctx, | |
118 | const struct nlattr * const tb[], | |
119 | struct nft_object *obj) | |
120 | { | |
121 | struct nft_connlimit *priv = nft_obj_data(obj); | |
122 | ||
123 | return nft_connlimit_do_init(ctx, tb, priv); | |
124 | } | |
125 | ||
126 | static void nft_connlimit_obj_destroy(const struct nft_ctx *ctx, | |
127 | struct nft_object *obj) | |
128 | { | |
129 | struct nft_connlimit *priv = nft_obj_data(obj); | |
130 | ||
131 | nft_connlimit_do_destroy(ctx, priv); | |
132 | } | |
133 | ||
134 | static int nft_connlimit_obj_dump(struct sk_buff *skb, | |
135 | struct nft_object *obj, bool reset) | |
136 | { | |
137 | struct nft_connlimit *priv = nft_obj_data(obj); | |
138 | ||
139 | return nft_connlimit_do_dump(skb, priv); | |
140 | } | |
141 | ||
142 | static const struct nla_policy nft_connlimit_policy[NFTA_CONNLIMIT_MAX + 1] = { | |
143 | [NFTA_CONNLIMIT_COUNT] = { .type = NLA_U32 }, | |
144 | [NFTA_CONNLIMIT_FLAGS] = { .type = NLA_U32 }, | |
145 | }; | |
146 | ||
147 | static struct nft_object_type nft_connlimit_obj_type; | |
148 | static const struct nft_object_ops nft_connlimit_obj_ops = { | |
149 | .type = &nft_connlimit_obj_type, | |
150 | .size = sizeof(struct nft_connlimit), | |
151 | .eval = nft_connlimit_obj_eval, | |
152 | .init = nft_connlimit_obj_init, | |
153 | .destroy = nft_connlimit_obj_destroy, | |
154 | .dump = nft_connlimit_obj_dump, | |
155 | }; | |
156 | ||
157 | static struct nft_object_type nft_connlimit_obj_type __read_mostly = { | |
158 | .type = NFT_OBJECT_CONNLIMIT, | |
159 | .ops = &nft_connlimit_obj_ops, | |
160 | .maxattr = NFTA_CONNLIMIT_MAX, | |
161 | .policy = nft_connlimit_policy, | |
162 | .owner = THIS_MODULE, | |
163 | }; | |
164 | ||
165 | static void nft_connlimit_eval(const struct nft_expr *expr, | |
166 | struct nft_regs *regs, | |
167 | const struct nft_pktinfo *pkt) | |
168 | { | |
169 | struct nft_connlimit *priv = nft_expr_priv(expr); | |
170 | ||
171 | nft_connlimit_do_eval(priv, regs, pkt, NULL); | |
172 | } | |
173 | ||
174 | static int nft_connlimit_dump(struct sk_buff *skb, const struct nft_expr *expr) | |
175 | { | |
176 | struct nft_connlimit *priv = nft_expr_priv(expr); | |
177 | ||
178 | return nft_connlimit_do_dump(skb, priv); | |
179 | } | |
180 | ||
181 | static int nft_connlimit_init(const struct nft_ctx *ctx, | |
182 | const struct nft_expr *expr, | |
183 | const struct nlattr * const tb[]) | |
184 | { | |
185 | struct nft_connlimit *priv = nft_expr_priv(expr); | |
186 | ||
187 | return nft_connlimit_do_init(ctx, tb, priv); | |
188 | } | |
189 | ||
190 | static void nft_connlimit_destroy(const struct nft_ctx *ctx, | |
191 | const struct nft_expr *expr) | |
192 | { | |
193 | struct nft_connlimit *priv = nft_expr_priv(expr); | |
194 | ||
195 | nft_connlimit_do_destroy(ctx, priv); | |
196 | } | |
197 | ||
198 | static int nft_connlimit_clone(struct nft_expr *dst, const struct nft_expr *src) | |
199 | { | |
200 | struct nft_connlimit *priv_dst = nft_expr_priv(dst); | |
201 | struct nft_connlimit *priv_src = nft_expr_priv(src); | |
202 | ||
cb2b36f5 | 203 | nf_conncount_list_init(&priv_dst->list); |
290180e2 PNA |
204 | priv_dst->limit = priv_src->limit; |
205 | priv_dst->invert = priv_src->invert; | |
206 | ||
207 | return 0; | |
208 | } | |
209 | ||
210 | static void nft_connlimit_destroy_clone(const struct nft_ctx *ctx, | |
211 | const struct nft_expr *expr) | |
212 | { | |
213 | struct nft_connlimit *priv = nft_expr_priv(expr); | |
214 | ||
cb2b36f5 | 215 | nf_conncount_cache_free(&priv->list); |
290180e2 PNA |
216 | } |
217 | ||
218 | static bool nft_connlimit_gc(struct net *net, const struct nft_expr *expr) | |
219 | { | |
220 | struct nft_connlimit *priv = nft_expr_priv(expr); | |
34a4c95a | 221 | bool ret; |
290180e2 | 222 | |
34a4c95a PNA |
223 | local_bh_disable(); |
224 | ret = nf_conncount_gc_list(net, &priv->list); | |
225 | local_bh_enable(); | |
226 | ||
227 | return ret; | |
290180e2 PNA |
228 | } |
229 | ||
230 | static struct nft_expr_type nft_connlimit_type; | |
231 | static const struct nft_expr_ops nft_connlimit_ops = { | |
232 | .type = &nft_connlimit_type, | |
233 | .size = NFT_EXPR_SIZE(sizeof(struct nft_connlimit)), | |
234 | .eval = nft_connlimit_eval, | |
235 | .init = nft_connlimit_init, | |
236 | .destroy = nft_connlimit_destroy, | |
237 | .clone = nft_connlimit_clone, | |
238 | .destroy_clone = nft_connlimit_destroy_clone, | |
239 | .dump = nft_connlimit_dump, | |
240 | .gc = nft_connlimit_gc, | |
241 | }; | |
242 | ||
243 | static struct nft_expr_type nft_connlimit_type __read_mostly = { | |
244 | .name = "connlimit", | |
245 | .ops = &nft_connlimit_ops, | |
246 | .policy = nft_connlimit_policy, | |
247 | .maxattr = NFTA_CONNLIMIT_MAX, | |
248 | .flags = NFT_EXPR_STATEFUL | NFT_EXPR_GC, | |
249 | .owner = THIS_MODULE, | |
250 | }; | |
251 | ||
252 | static int __init nft_connlimit_module_init(void) | |
253 | { | |
254 | int err; | |
255 | ||
256 | err = nft_register_obj(&nft_connlimit_obj_type); | |
257 | if (err < 0) | |
258 | return err; | |
259 | ||
260 | err = nft_register_expr(&nft_connlimit_type); | |
261 | if (err < 0) | |
262 | goto err1; | |
263 | ||
264 | return 0; | |
265 | err1: | |
266 | nft_unregister_obj(&nft_connlimit_obj_type); | |
267 | return err; | |
268 | } | |
269 | ||
270 | static void __exit nft_connlimit_module_exit(void) | |
271 | { | |
272 | nft_unregister_expr(&nft_connlimit_type); | |
273 | nft_unregister_obj(&nft_connlimit_obj_type); | |
274 | } | |
275 | ||
276 | module_init(nft_connlimit_module_init); | |
277 | module_exit(nft_connlimit_module_exit); | |
278 | ||
279 | MODULE_LICENSE("GPL"); | |
280 | MODULE_AUTHOR("Pablo Neira Ayuso"); | |
281 | MODULE_ALIAS_NFT_EXPR("connlimit"); | |
282 | MODULE_ALIAS_NFT_OBJ(NFT_OBJECT_CONNLIMIT); | |
4cacc395 | 283 | MODULE_DESCRIPTION("nftables connlimit rule support"); |