]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
76917807 | 2 | |
2dc95d98 ZJS |
3 | /* Temporary work-around for broken glibc vs. linux kernel header definitions |
4 | * This is already fixed upstream, remove this when distributions have updated. | |
5 | */ | |
08ce521f LP |
6 | #define _NET_IF_H 1 |
7 | ||
a8fbdf54 | 8 | #include <alloca.h> |
76917807 | 9 | #include <arpa/inet.h> |
a8fbdf54 TA |
10 | #include <endian.h> |
11 | #include <errno.h> | |
a8fbdf54 TA |
12 | #include <stddef.h> |
13 | #include <string.h> | |
14 | #include <sys/socket.h> | |
08ce521f | 15 | #include <net/if.h> |
08ce521f | 16 | #ifndef IFNAMSIZ |
6f270e6b | 17 | #define IFNAMSIZ 16 |
08ce521f | 18 | #endif |
6f270e6b | 19 | #include <linux/if.h> |
76917807 LP |
20 | #include <linux/netfilter_ipv4/ip_tables.h> |
21 | #include <linux/netfilter/nf_nat.h> | |
22 | #include <linux/netfilter/xt_addrtype.h> | |
23 | #include <libiptc/libiptc.h> | |
24 | ||
b5efdb8a | 25 | #include "alloc-util.h" |
12c2884c | 26 | #include "firewall-util.h" |
a8fbdf54 TA |
27 | #include "in-addr-util.h" |
28 | #include "macro.h" | |
d31645ad | 29 | #include "socket-util.h" |
76917807 LP |
30 | |
31 | DEFINE_TRIVIAL_CLEANUP_FUNC(struct xtc_handle*, iptc_free); | |
32 | ||
33 | static int entry_fill_basics( | |
34 | struct ipt_entry *entry, | |
35 | int protocol, | |
36 | const char *in_interface, | |
37 | const union in_addr_union *source, | |
38 | unsigned source_prefixlen, | |
39 | const char *out_interface, | |
40 | const union in_addr_union *destination, | |
41 | unsigned destination_prefixlen) { | |
42 | ||
43 | assert(entry); | |
44 | ||
d31645ad | 45 | if (out_interface && !ifname_valid(out_interface)) |
76917807 | 46 | return -EINVAL; |
d31645ad | 47 | if (in_interface && !ifname_valid(in_interface)) |
76917807 LP |
48 | return -EINVAL; |
49 | ||
50 | entry->ip.proto = protocol; | |
51 | ||
52 | if (in_interface) { | |
0b777d20 DT |
53 | size_t l; |
54 | ||
55 | l = strlen(in_interface); | |
56 | assert(l < sizeof entry->ip.iniface); | |
57 | assert(l < sizeof entry->ip.iniface_mask); | |
58 | ||
76917807 | 59 | strcpy(entry->ip.iniface, in_interface); |
0b777d20 | 60 | memset(entry->ip.iniface_mask, 0xFF, l + 1); |
76917807 LP |
61 | } |
62 | if (source) { | |
63 | entry->ip.src = source->in; | |
5a941f5f | 64 | in4_addr_prefixlen_to_netmask(&entry->ip.smsk, source_prefixlen); |
76917807 LP |
65 | } |
66 | ||
67 | if (out_interface) { | |
f2850127 | 68 | size_t l = strlen(out_interface); |
172378e0 SL |
69 | assert(l < sizeof entry->ip.outiface); |
70 | assert(l < sizeof entry->ip.outiface_mask); | |
f2850127 | 71 | |
76917807 | 72 | strcpy(entry->ip.outiface, out_interface); |
f2850127 | 73 | memset(entry->ip.outiface_mask, 0xFF, l + 1); |
76917807 LP |
74 | } |
75 | if (destination) { | |
76 | entry->ip.dst = destination->in; | |
5a941f5f | 77 | in4_addr_prefixlen_to_netmask(&entry->ip.dmsk, destination_prefixlen); |
76917807 LP |
78 | } |
79 | ||
80 | return 0; | |
81 | } | |
82 | ||
83 | int fw_add_masquerade( | |
84 | bool add, | |
85 | int af, | |
86 | int protocol, | |
87 | const union in_addr_union *source, | |
88 | unsigned source_prefixlen, | |
89 | const char *out_interface, | |
90 | const union in_addr_union *destination, | |
91 | unsigned destination_prefixlen) { | |
92 | ||
d14be488 | 93 | static const xt_chainlabel chain = "POSTROUTING"; |
76917807 LP |
94 | _cleanup_(iptc_freep) struct xtc_handle *h = NULL; |
95 | struct ipt_entry *entry, *mask; | |
96 | struct ipt_entry_target *t; | |
97 | size_t sz; | |
98 | struct nf_nat_ipv4_multi_range_compat *mr; | |
99 | int r; | |
100 | ||
101 | if (af != AF_INET) | |
15411c0c | 102 | return -EOPNOTSUPP; |
76917807 | 103 | |
4c701096 | 104 | if (!IN_SET(protocol, 0, IPPROTO_TCP, IPPROTO_UDP)) |
15411c0c | 105 | return -EOPNOTSUPP; |
76917807 LP |
106 | |
107 | h = iptc_init("nat"); | |
108 | if (!h) | |
109 | return -errno; | |
110 | ||
111 | sz = XT_ALIGN(sizeof(struct ipt_entry)) + | |
112 | XT_ALIGN(sizeof(struct ipt_entry_target)) + | |
113 | XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); | |
114 | ||
115 | /* Put together the entry we want to add or remove */ | |
116 | entry = alloca0(sz); | |
117 | entry->next_offset = sz; | |
118 | entry->target_offset = XT_ALIGN(sizeof(struct ipt_entry)); | |
119 | r = entry_fill_basics(entry, protocol, NULL, source, source_prefixlen, out_interface, destination, destination_prefixlen); | |
120 | if (r < 0) | |
121 | return r; | |
122 | ||
123 | /* Fill in target part */ | |
124 | t = ipt_get_target(entry); | |
125 | t->u.target_size = | |
126 | XT_ALIGN(sizeof(struct ipt_entry_target)) + | |
127 | XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); | |
128 | strncpy(t->u.user.name, "MASQUERADE", sizeof(t->u.user.name)); | |
129 | mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; | |
130 | mr->rangesize = 1; | |
131 | ||
132 | /* Create a search mask entry */ | |
133 | mask = alloca(sz); | |
134 | memset(mask, 0xFF, sz); | |
135 | ||
136 | if (add) { | |
d14be488 | 137 | if (iptc_check_entry(chain, entry, (unsigned char*) mask, h)) |
76917807 LP |
138 | return 0; |
139 | if (errno != ENOENT) /* if other error than not existing yet, fail */ | |
140 | return -errno; | |
141 | ||
d14be488 | 142 | if (!iptc_insert_entry(chain, entry, 0, h)) |
76917807 LP |
143 | return -errno; |
144 | } else { | |
d14be488 | 145 | if (!iptc_delete_entry(chain, entry, (unsigned char*) mask, h)) { |
76917807 LP |
146 | if (errno == ENOENT) /* if it's already gone, all is good! */ |
147 | return 0; | |
148 | ||
149 | return -errno; | |
150 | } | |
151 | } | |
152 | ||
153 | if (!iptc_commit(h)) | |
154 | return -errno; | |
155 | ||
156 | return 0; | |
157 | } | |
158 | ||
159 | int fw_add_local_dnat( | |
160 | bool add, | |
161 | int af, | |
162 | int protocol, | |
163 | const char *in_interface, | |
164 | const union in_addr_union *source, | |
165 | unsigned source_prefixlen, | |
166 | const union in_addr_union *destination, | |
167 | unsigned destination_prefixlen, | |
168 | uint16_t local_port, | |
169 | const union in_addr_union *remote, | |
170 | uint16_t remote_port, | |
171 | const union in_addr_union *previous_remote) { | |
172 | ||
d14be488 | 173 | static const xt_chainlabel chain_pre = "PREROUTING", chain_output = "OUTPUT"; |
76917807 LP |
174 | _cleanup_(iptc_freep) struct xtc_handle *h = NULL; |
175 | struct ipt_entry *entry, *mask; | |
176 | struct ipt_entry_target *t; | |
177 | struct ipt_entry_match *m; | |
178 | struct xt_addrtype_info_v1 *at; | |
179 | struct nf_nat_ipv4_multi_range_compat *mr; | |
180 | size_t sz, msz; | |
181 | int r; | |
182 | ||
183 | assert(add || !previous_remote); | |
184 | ||
185 | if (af != AF_INET) | |
15411c0c | 186 | return -EOPNOTSUPP; |
76917807 | 187 | |
4c701096 | 188 | if (!IN_SET(protocol, IPPROTO_TCP, IPPROTO_UDP)) |
15411c0c | 189 | return -EOPNOTSUPP; |
76917807 LP |
190 | |
191 | if (local_port <= 0) | |
192 | return -EINVAL; | |
193 | ||
194 | if (remote_port <= 0) | |
195 | return -EINVAL; | |
196 | ||
197 | h = iptc_init("nat"); | |
198 | if (!h) | |
199 | return -errno; | |
200 | ||
201 | sz = XT_ALIGN(sizeof(struct ipt_entry)) + | |
202 | XT_ALIGN(sizeof(struct ipt_entry_match)) + | |
203 | XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + | |
204 | XT_ALIGN(sizeof(struct ipt_entry_target)) + | |
205 | XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); | |
206 | ||
207 | if (protocol == IPPROTO_TCP) | |
208 | msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + | |
209 | XT_ALIGN(sizeof(struct xt_tcp)); | |
210 | else | |
211 | msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + | |
212 | XT_ALIGN(sizeof(struct xt_udp)); | |
213 | ||
214 | sz += msz; | |
215 | ||
216 | /* Fill in basic part */ | |
217 | entry = alloca0(sz); | |
218 | entry->next_offset = sz; | |
219 | entry->target_offset = | |
220 | XT_ALIGN(sizeof(struct ipt_entry)) + | |
221 | XT_ALIGN(sizeof(struct ipt_entry_match)) + | |
222 | XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + | |
223 | msz; | |
224 | r = entry_fill_basics(entry, protocol, in_interface, source, source_prefixlen, NULL, destination, destination_prefixlen); | |
225 | if (r < 0) | |
226 | return r; | |
227 | ||
228 | /* Fill in first match */ | |
229 | m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry))); | |
230 | m->u.match_size = msz; | |
231 | if (protocol == IPPROTO_TCP) { | |
232 | struct xt_tcp *tcp; | |
233 | ||
234 | strncpy(m->u.user.name, "tcp", sizeof(m->u.user.name)); | |
235 | tcp = (struct xt_tcp*) m->data; | |
236 | tcp->dpts[0] = tcp->dpts[1] = local_port; | |
237 | tcp->spts[0] = 0; | |
238 | tcp->spts[1] = 0xFFFF; | |
239 | ||
240 | } else { | |
241 | struct xt_udp *udp; | |
242 | ||
243 | strncpy(m->u.user.name, "udp", sizeof(m->u.user.name)); | |
244 | udp = (struct xt_udp*) m->data; | |
245 | udp->dpts[0] = udp->dpts[1] = local_port; | |
246 | udp->spts[0] = 0; | |
247 | udp->spts[1] = 0xFFFF; | |
248 | } | |
249 | ||
250 | /* Fill in second match */ | |
251 | m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry)) + msz); | |
252 | m->u.match_size = | |
253 | XT_ALIGN(sizeof(struct ipt_entry_match)) + | |
254 | XT_ALIGN(sizeof(struct xt_addrtype_info_v1)); | |
255 | strncpy(m->u.user.name, "addrtype", sizeof(m->u.user.name)); | |
256 | m->u.user.revision = 1; | |
257 | at = (struct xt_addrtype_info_v1*) m->data; | |
258 | at->dest = XT_ADDRTYPE_LOCAL; | |
259 | ||
260 | /* Fill in target part */ | |
261 | t = ipt_get_target(entry); | |
262 | t->u.target_size = | |
263 | XT_ALIGN(sizeof(struct ipt_entry_target)) + | |
264 | XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); | |
265 | strncpy(t->u.user.name, "DNAT", sizeof(t->u.user.name)); | |
266 | mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; | |
267 | mr->rangesize = 1; | |
268 | mr->range[0].flags = NF_NAT_RANGE_PROTO_SPECIFIED|NF_NAT_RANGE_MAP_IPS; | |
269 | mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; | |
270 | if (protocol == IPPROTO_TCP) | |
fe2e4b69 | 271 | mr->range[0].min.tcp.port = mr->range[0].max.tcp.port = htobe16(remote_port); |
76917807 | 272 | else |
fe2e4b69 | 273 | mr->range[0].min.udp.port = mr->range[0].max.udp.port = htobe16(remote_port); |
76917807 LP |
274 | |
275 | mask = alloca0(sz); | |
276 | memset(mask, 0xFF, sz); | |
277 | ||
278 | if (add) { | |
279 | /* Add the PREROUTING rule, if it is missing so far */ | |
d14be488 | 280 | if (!iptc_check_entry(chain_pre, entry, (unsigned char*) mask, h)) { |
76917807 LP |
281 | if (errno != ENOENT) |
282 | return -EINVAL; | |
283 | ||
d14be488 | 284 | if (!iptc_insert_entry(chain_pre, entry, 0, h)) |
76917807 LP |
285 | return -errno; |
286 | } | |
287 | ||
288 | /* If a previous remote is set, remove its entry */ | |
289 | if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { | |
290 | mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; | |
291 | ||
d14be488 | 292 | if (!iptc_delete_entry(chain_pre, entry, (unsigned char*) mask, h)) { |
76917807 LP |
293 | if (errno != ENOENT) |
294 | return -errno; | |
295 | } | |
296 | ||
297 | mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; | |
298 | } | |
299 | ||
300 | /* Add the OUTPUT rule, if it is missing so far */ | |
301 | if (!in_interface) { | |
302 | ||
303 | /* Don't apply onto loopback addresses */ | |
304 | if (!destination) { | |
305 | entry->ip.dst.s_addr = htobe32(0x7F000000); | |
306 | entry->ip.dmsk.s_addr = htobe32(0xFF000000); | |
307 | entry->ip.invflags = IPT_INV_DSTIP; | |
308 | } | |
309 | ||
d14be488 | 310 | if (!iptc_check_entry(chain_output, entry, (unsigned char*) mask, h)) { |
76917807 LP |
311 | if (errno != ENOENT) |
312 | return -errno; | |
313 | ||
d14be488 | 314 | if (!iptc_insert_entry(chain_output, entry, 0, h)) |
76917807 LP |
315 | return -errno; |
316 | } | |
317 | ||
318 | /* If a previous remote is set, remove its entry */ | |
319 | if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { | |
320 | mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; | |
321 | ||
d14be488 | 322 | if (!iptc_delete_entry(chain_output, entry, (unsigned char*) mask, h)) { |
76917807 LP |
323 | if (errno != ENOENT) |
324 | return -errno; | |
325 | } | |
326 | } | |
327 | } | |
328 | } else { | |
d14be488 | 329 | if (!iptc_delete_entry(chain_pre, entry, (unsigned char*) mask, h)) { |
76917807 LP |
330 | if (errno != ENOENT) |
331 | return -errno; | |
332 | } | |
333 | ||
334 | if (!in_interface) { | |
335 | if (!destination) { | |
336 | entry->ip.dst.s_addr = htobe32(0x7F000000); | |
337 | entry->ip.dmsk.s_addr = htobe32(0xFF000000); | |
338 | entry->ip.invflags = IPT_INV_DSTIP; | |
339 | } | |
340 | ||
d14be488 | 341 | if (!iptc_delete_entry(chain_output, entry, (unsigned char*) mask, h)) { |
76917807 LP |
342 | if (errno != ENOENT) |
343 | return -errno; | |
344 | } | |
345 | } | |
346 | } | |
347 | ||
348 | if (!iptc_commit(h)) | |
349 | return -errno; | |
350 | ||
351 | return 0; | |
352 | } |