chiark / gitweb /
shared: add minimal firewall manipulation helpers for establishing NAT rules, using...
[elogind.git] / src / shared / fw-util.c
diff --git a/src/shared/fw-util.c b/src/shared/fw-util.c
new file mode 100644 (file)
index 0000000..ceb1ae5
--- /dev/null
@@ -0,0 +1,344 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2015 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter/nf_nat.h>
+#include <linux/netfilter/xt_addrtype.h>
+#include <libiptc/libiptc.h>
+
+#include "util.h"
+#include "fw-util.h"
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct xtc_handle*, iptc_free);
+
+static int entry_fill_basics(
+                struct ipt_entry *entry,
+                int protocol,
+                const char *in_interface,
+                const union in_addr_union *source,
+                unsigned source_prefixlen,
+                const char *out_interface,
+                const union in_addr_union *destination,
+                unsigned destination_prefixlen) {
+
+        assert(entry);
+
+        if (out_interface && strlen(out_interface) >= IFNAMSIZ)
+                return -EINVAL;
+
+        if (in_interface && strlen(in_interface) >= IFNAMSIZ)
+                return -EINVAL;
+
+        entry->ip.proto = protocol;
+
+        if (in_interface) {
+                strcpy(entry->ip.iniface, in_interface);
+                memset(entry->ip.iniface_mask, 0xFF, strlen(in_interface)+1);
+        }
+        if (source) {
+                entry->ip.src = source->in;
+                in_addr_prefixlen_to_netmask(&entry->ip.smsk, source_prefixlen);
+        }
+
+        if (out_interface) {
+                strcpy(entry->ip.outiface, out_interface);
+                memset(entry->ip.outiface_mask, 0xFF, strlen(out_interface)+1);
+        }
+        if (destination) {
+                entry->ip.dst = destination->in;
+                in_addr_prefixlen_to_netmask(&entry->ip.dmsk, destination_prefixlen);
+        }
+
+        return 0;
+}
+
+int fw_add_masquerade(
+                bool add,
+                int af,
+                int protocol,
+                const union in_addr_union *source,
+                unsigned source_prefixlen,
+                const char *out_interface,
+                const union in_addr_union *destination,
+                unsigned destination_prefixlen) {
+
+        _cleanup_(iptc_freep) struct xtc_handle *h = NULL;
+        struct ipt_entry *entry, *mask;
+        struct ipt_entry_target *t;
+        size_t sz;
+        struct nf_nat_ipv4_multi_range_compat *mr;
+        int r;
+
+        if (af != AF_INET)
+                return -ENOTSUP;
+
+        if (protocol != 0 && protocol != IPPROTO_TCP && protocol != IPPROTO_UDP)
+                return -ENOTSUP;
+
+        h = iptc_init("nat");
+        if (!h)
+                return -errno;
+
+        sz = XT_ALIGN(sizeof(struct ipt_entry)) +
+             XT_ALIGN(sizeof(struct ipt_entry_target)) +
+             XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat));
+
+        /* Put together the entry we want to add or remove */
+        entry = alloca0(sz);
+        entry->next_offset = sz;
+        entry->target_offset = XT_ALIGN(sizeof(struct ipt_entry));
+        r = entry_fill_basics(entry, protocol, NULL, source, source_prefixlen, out_interface, destination, destination_prefixlen);
+        if (r < 0)
+                return r;
+
+        /* Fill in target part */
+        t = ipt_get_target(entry);
+        t->u.target_size =
+                XT_ALIGN(sizeof(struct ipt_entry_target)) +
+                XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat));
+        strncpy(t->u.user.name, "MASQUERADE", sizeof(t->u.user.name));
+        mr = (struct nf_nat_ipv4_multi_range_compat*) t->data;
+        mr->rangesize = 1;
+
+        /* Create a search mask entry */
+        mask = alloca(sz);
+        memset(mask, 0xFF, sz);
+
+        if (add) {
+                if (iptc_check_entry("POSTROUTING", entry, (unsigned char*) mask, h))
+                        return 0;
+                if (errno != ENOENT) /* if other error than not existing yet, fail */
+                        return -errno;
+
+                if (!iptc_insert_entry("POSTROUTING", entry, 0, h))
+                        return -errno;
+        } else {
+                if (!iptc_delete_entry("POSTROUTING", entry, (unsigned char*) mask, h)) {
+                        if (errno == ENOENT) /* if it's already gone, all is good! */
+                                return 0;
+
+                        return -errno;
+                }
+        }
+
+        if (!iptc_commit(h))
+                return -errno;
+
+        return 0;
+}
+
+int fw_add_local_dnat(
+                bool add,
+                int af,
+                int protocol,
+                const char *in_interface,
+                const union in_addr_union *source,
+                unsigned source_prefixlen,
+                const union in_addr_union *destination,
+                unsigned destination_prefixlen,
+                uint16_t local_port,
+                const union in_addr_union *remote,
+                uint16_t remote_port,
+                const union in_addr_union *previous_remote) {
+
+
+        _cleanup_(iptc_freep) struct xtc_handle *h = NULL;
+        struct ipt_entry *entry, *mask;
+        struct ipt_entry_target *t;
+        struct ipt_entry_match *m;
+        struct xt_addrtype_info_v1 *at;
+        struct nf_nat_ipv4_multi_range_compat *mr;
+        size_t sz, msz;
+        int r;
+
+        assert(add || !previous_remote);
+
+        if (af != AF_INET)
+                return -ENOTSUP;
+
+        if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP)
+                return -ENOTSUP;
+
+        if (local_port <= 0)
+                return -EINVAL;
+
+        if (remote_port <= 0)
+                return -EINVAL;
+
+        h = iptc_init("nat");
+        if (!h)
+                return -errno;
+
+        sz = XT_ALIGN(sizeof(struct ipt_entry)) +
+             XT_ALIGN(sizeof(struct ipt_entry_match)) +
+             XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) +
+             XT_ALIGN(sizeof(struct ipt_entry_target)) +
+             XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat));
+
+        if (protocol == IPPROTO_TCP)
+                msz = XT_ALIGN(sizeof(struct ipt_entry_match)) +
+                      XT_ALIGN(sizeof(struct xt_tcp));
+        else
+                msz = XT_ALIGN(sizeof(struct ipt_entry_match)) +
+                      XT_ALIGN(sizeof(struct xt_udp));
+
+        sz += msz;
+
+        /* Fill in basic part */
+        entry = alloca0(sz);
+        entry->next_offset = sz;
+        entry->target_offset =
+                XT_ALIGN(sizeof(struct ipt_entry)) +
+                XT_ALIGN(sizeof(struct ipt_entry_match)) +
+                XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) +
+                msz;
+        r = entry_fill_basics(entry, protocol, in_interface, source, source_prefixlen, NULL, destination, destination_prefixlen);
+        if (r < 0)
+                return r;
+
+        /* Fill in first match */
+        m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry)));
+        m->u.match_size = msz;
+        if (protocol == IPPROTO_TCP) {
+                struct xt_tcp *tcp;
+
+                strncpy(m->u.user.name, "tcp", sizeof(m->u.user.name));
+                tcp = (struct xt_tcp*) m->data;
+                tcp->dpts[0] = tcp->dpts[1] = local_port;
+                tcp->spts[0] = 0;
+                tcp->spts[1] = 0xFFFF;
+
+        } else {
+                struct xt_udp *udp;
+
+                strncpy(m->u.user.name, "udp", sizeof(m->u.user.name));
+                udp = (struct xt_udp*) m->data;
+                udp->dpts[0] = udp->dpts[1] = local_port;
+                udp->spts[0] = 0;
+                udp->spts[1] = 0xFFFF;
+        }
+
+        /* Fill in second match */
+        m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry)) + msz);
+        m->u.match_size =
+                XT_ALIGN(sizeof(struct ipt_entry_match)) +
+                XT_ALIGN(sizeof(struct xt_addrtype_info_v1));
+        strncpy(m->u.user.name, "addrtype", sizeof(m->u.user.name));
+        m->u.user.revision = 1;
+        at = (struct xt_addrtype_info_v1*) m->data;
+        at->dest = XT_ADDRTYPE_LOCAL;
+
+        /* Fill in target part */
+        t = ipt_get_target(entry);
+        t->u.target_size =
+                XT_ALIGN(sizeof(struct ipt_entry_target)) +
+                XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat));
+        strncpy(t->u.user.name, "DNAT", sizeof(t->u.user.name));
+        mr = (struct nf_nat_ipv4_multi_range_compat*) t->data;
+        mr->rangesize = 1;
+        mr->range[0].flags = NF_NAT_RANGE_PROTO_SPECIFIED|NF_NAT_RANGE_MAP_IPS;
+        mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr;
+        if (protocol == IPPROTO_TCP)
+                mr->range[0].min.tcp.port = mr->range[0].max.tcp.port = htons(remote_port);
+        else
+                mr->range[0].min.udp.port = mr->range[0].max.udp.port = htons(remote_port);
+
+        mask = alloca0(sz);
+        memset(mask, 0xFF, sz);
+
+        if (add) {
+                /* Add the PREROUTING rule, if it is missing so far */
+                if (!iptc_check_entry("PREROUTING", entry, (unsigned char*) mask, h)) {
+                        if (errno != ENOENT)
+                                return -EINVAL;
+
+                        if (!iptc_insert_entry("PREROUTING", entry, 0, h))
+                                return -errno;
+                }
+
+                /* If a previous remote is set, remove its entry */
+                if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) {
+                        mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr;
+
+                        if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) {
+                                if (errno != ENOENT)
+                                        return -errno;
+                        }
+
+                        mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr;
+                }
+
+                /* Add the OUTPUT rule, if it is missing so far */
+                if (!in_interface) {
+
+                        /* Don't apply onto loopback addresses */
+                        if (!destination) {
+                                entry->ip.dst.s_addr = htobe32(0x7F000000);
+                                entry->ip.dmsk.s_addr = htobe32(0xFF000000);
+                                entry->ip.invflags = IPT_INV_DSTIP;
+                        }
+
+                        if (!iptc_check_entry("OUTPUT", entry, (unsigned char*) mask, h)) {
+                                if (errno != ENOENT)
+                                        return -errno;
+
+                                if (!iptc_insert_entry("OUTPUT", entry, 0, h))
+                                        return -errno;
+                        }
+
+                        /* If a previous remote is set, remove its entry */
+                        if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) {
+                                mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr;
+
+                                if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) {
+                                        if (errno != ENOENT)
+                                                return -errno;
+                                }
+                        }
+                }
+        } else {
+                if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) {
+                        if (errno != ENOENT)
+                                return -errno;
+                }
+
+                if (!in_interface) {
+                        if (!destination) {
+                                entry->ip.dst.s_addr = htobe32(0x7F000000);
+                                entry->ip.dmsk.s_addr = htobe32(0xFF000000);
+                                entry->ip.invflags = IPT_INV_DSTIP;
+                        }
+
+                        if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) {
+                                if (errno != ENOENT)
+                                        return -errno;
+                        }
+                }
+        }
+
+        if (!iptc_commit(h))
+                return -errno;
+
+        return 0;
+}