1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * xt_connmark - Netfilter module to operate on connection marks |
4 | * |
5 | * Copyright (C) 2002,2004 MARA Systems AB <https://www.marasystems.com> |
6 | * by Henrik Nordstrom <hno@marasystems.com> |
7 | * Copyright © CC Computer Consultants GmbH, 2007 - 2008 |
8 | * Jan Engelhardt <jengelh@medozas.de> |
9 | */ |
10 | |
11 | #include <linux/module.h> |
12 | #include <linux/skbuff.h> |
13 | #include <net/netfilter/nf_conntrack.h> |
14 | #include <net/netfilter/nf_conntrack_ecache.h> |
15 | #include <linux/netfilter/x_tables.h> |
16 | #include <linux/netfilter/xt_connmark.h> |
17 | |
18 | MODULE_AUTHOR("Henrik Nordstrom <hno@marasystems.com>" ); |
19 | MODULE_DESCRIPTION("Xtables: connection mark operations" ); |
20 | MODULE_LICENSE("GPL" ); |
21 | MODULE_ALIAS("ipt_CONNMARK" ); |
22 | MODULE_ALIAS("ip6t_CONNMARK" ); |
23 | MODULE_ALIAS("ipt_connmark" ); |
24 | MODULE_ALIAS("ip6t_connmark" ); |
25 | |
26 | static unsigned int |
27 | connmark_tg_shift(struct sk_buff *skb, const struct xt_connmark_tginfo2 *info) |
28 | { |
29 | enum ip_conntrack_info ctinfo; |
30 | u_int32_t new_targetmark; |
31 | struct nf_conn *ct; |
32 | u_int32_t newmark; |
33 | u_int32_t oldmark; |
34 | |
35 | ct = nf_ct_get(skb, ctinfo: &ctinfo); |
36 | if (ct == NULL) |
37 | return XT_CONTINUE; |
38 | |
39 | switch (info->mode) { |
40 | case XT_CONNMARK_SET: |
41 | oldmark = READ_ONCE(ct->mark); |
42 | newmark = (oldmark & ~info->ctmask) ^ info->ctmark; |
43 | if (info->shift_dir == D_SHIFT_RIGHT) |
44 | newmark >>= info->shift_bits; |
45 | else |
46 | newmark <<= info->shift_bits; |
47 | |
48 | if (READ_ONCE(ct->mark) != newmark) { |
49 | WRITE_ONCE(ct->mark, newmark); |
50 | nf_conntrack_event_cache(event: IPCT_MARK, ct); |
51 | } |
52 | break; |
53 | case XT_CONNMARK_SAVE: |
54 | new_targetmark = (skb->mark & info->nfmask); |
55 | if (info->shift_dir == D_SHIFT_RIGHT) |
56 | new_targetmark >>= info->shift_bits; |
57 | else |
58 | new_targetmark <<= info->shift_bits; |
59 | |
60 | newmark = (READ_ONCE(ct->mark) & ~info->ctmask) ^ |
61 | new_targetmark; |
62 | if (READ_ONCE(ct->mark) != newmark) { |
63 | WRITE_ONCE(ct->mark, newmark); |
64 | nf_conntrack_event_cache(event: IPCT_MARK, ct); |
65 | } |
66 | break; |
67 | case XT_CONNMARK_RESTORE: |
68 | new_targetmark = (READ_ONCE(ct->mark) & info->ctmask); |
69 | if (info->shift_dir == D_SHIFT_RIGHT) |
70 | new_targetmark >>= info->shift_bits; |
71 | else |
72 | new_targetmark <<= info->shift_bits; |
73 | |
74 | newmark = (skb->mark & ~info->nfmask) ^ |
75 | new_targetmark; |
76 | skb->mark = newmark; |
77 | break; |
78 | } |
79 | return XT_CONTINUE; |
80 | } |
81 | |
82 | static unsigned int |
83 | connmark_tg(struct sk_buff *skb, const struct xt_action_param *par) |
84 | { |
85 | const struct xt_connmark_tginfo1 *info = par->targinfo; |
86 | const struct xt_connmark_tginfo2 info2 = { |
87 | .ctmark = info->ctmark, |
88 | .ctmask = info->ctmask, |
89 | .nfmask = info->nfmask, |
90 | .mode = info->mode, |
91 | }; |
92 | |
93 | return connmark_tg_shift(skb, info: &info2); |
94 | } |
95 | |
96 | static unsigned int |
97 | connmark_tg_v2(struct sk_buff *skb, const struct xt_action_param *par) |
98 | { |
99 | const struct xt_connmark_tginfo2 *info = par->targinfo; |
100 | |
101 | return connmark_tg_shift(skb, info); |
102 | } |
103 | |
104 | static int connmark_tg_check(const struct xt_tgchk_param *par) |
105 | { |
106 | int ret; |
107 | |
108 | ret = nf_ct_netns_get(net: par->net, nfproto: par->family); |
109 | if (ret < 0) |
110 | pr_info_ratelimited("cannot load conntrack support for proto=%u\n" , |
111 | par->family); |
112 | return ret; |
113 | } |
114 | |
115 | static void connmark_tg_destroy(const struct xt_tgdtor_param *par) |
116 | { |
117 | nf_ct_netns_put(net: par->net, nfproto: par->family); |
118 | } |
119 | |
120 | static bool |
121 | connmark_mt(const struct sk_buff *skb, struct xt_action_param *par) |
122 | { |
123 | const struct xt_connmark_mtinfo1 *info = par->matchinfo; |
124 | enum ip_conntrack_info ctinfo; |
125 | const struct nf_conn *ct; |
126 | |
127 | ct = nf_ct_get(skb, ctinfo: &ctinfo); |
128 | if (ct == NULL) |
129 | return false; |
130 | |
131 | return ((READ_ONCE(ct->mark) & info->mask) == info->mark) ^ info->invert; |
132 | } |
133 | |
134 | static int connmark_mt_check(const struct xt_mtchk_param *par) |
135 | { |
136 | int ret; |
137 | |
138 | ret = nf_ct_netns_get(net: par->net, nfproto: par->family); |
139 | if (ret < 0) |
140 | pr_info_ratelimited("cannot load conntrack support for proto=%u\n" , |
141 | par->family); |
142 | return ret; |
143 | } |
144 | |
145 | static void connmark_mt_destroy(const struct xt_mtdtor_param *par) |
146 | { |
147 | nf_ct_netns_put(net: par->net, nfproto: par->family); |
148 | } |
149 | |
150 | static struct xt_target connmark_tg_reg[] __read_mostly = { |
151 | { |
152 | .name = "CONNMARK" , |
153 | .revision = 1, |
154 | .family = NFPROTO_UNSPEC, |
155 | .checkentry = connmark_tg_check, |
156 | .target = connmark_tg, |
157 | .targetsize = sizeof(struct xt_connmark_tginfo1), |
158 | .destroy = connmark_tg_destroy, |
159 | .me = THIS_MODULE, |
160 | }, |
161 | { |
162 | .name = "CONNMARK" , |
163 | .revision = 2, |
164 | .family = NFPROTO_UNSPEC, |
165 | .checkentry = connmark_tg_check, |
166 | .target = connmark_tg_v2, |
167 | .targetsize = sizeof(struct xt_connmark_tginfo2), |
168 | .destroy = connmark_tg_destroy, |
169 | .me = THIS_MODULE, |
170 | } |
171 | }; |
172 | |
173 | static struct xt_match connmark_mt_reg __read_mostly = { |
174 | .name = "connmark" , |
175 | .revision = 1, |
176 | .family = NFPROTO_UNSPEC, |
177 | .checkentry = connmark_mt_check, |
178 | .match = connmark_mt, |
179 | .matchsize = sizeof(struct xt_connmark_mtinfo1), |
180 | .destroy = connmark_mt_destroy, |
181 | .me = THIS_MODULE, |
182 | }; |
183 | |
184 | static int __init connmark_mt_init(void) |
185 | { |
186 | int ret; |
187 | |
188 | ret = xt_register_targets(target: connmark_tg_reg, |
189 | ARRAY_SIZE(connmark_tg_reg)); |
190 | if (ret < 0) |
191 | return ret; |
192 | ret = xt_register_match(target: &connmark_mt_reg); |
193 | if (ret < 0) { |
194 | xt_unregister_targets(target: connmark_tg_reg, |
195 | ARRAY_SIZE(connmark_tg_reg)); |
196 | return ret; |
197 | } |
198 | return 0; |
199 | } |
200 | |
201 | static void __exit connmark_mt_exit(void) |
202 | { |
203 | xt_unregister_match(target: &connmark_mt_reg); |
204 | xt_unregister_targets(target: connmark_tg_reg, ARRAY_SIZE(connmark_tg_reg)); |
205 | } |
206 | |
207 | module_init(connmark_mt_init); |
208 | module_exit(connmark_mt_exit); |
209 | |