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