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