chiark / gitweb /
shared: add minimal firewall manipulation helpers for establishing NAT rules, using...
authorLennart Poettering <lennart@poettering.net>
Tue, 13 Jan 2015 12:44:30 +0000 (13:44 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 13 Jan 2015 12:55:15 +0000 (13:55 +0100)
.gitignore
Makefile.am
configure.ac
src/shared/fw-util.c [new file with mode: 0644]
src/shared/fw-util.h [new file with mode: 0644]
src/shared/in-addr-util.c
src/shared/in-addr-util.h
src/test/test-fw-util.c [new file with mode: 0644]

index 2394e7c..caef831 100644 (file)
 /test-fdset
 /test-fileio
 /test-fstab-util
+/test-fw-util
 /test-hashmap
 /test-hostname
 /test-icmp6-rs
index 7f9dc26..205dafa 100644 (file)
@@ -982,6 +982,24 @@ libsystemd_label_la_LIBADD = \
 
 # -----------------------------------------------------------------------------
 
+if HAVE_LIBIPTC
+noinst_LTLIBRARIES += \
+       libsystemd-fw.la
+
+libsystemd_fw_la_SOURCES = \
+       src/shared/fw-util.h \
+       src/shared/fw-util.c
+
+libsystemd_fw_la_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(LIBIPTC_CFLAGS)
+
+libsystemd_fw_la_LIBADD = \
+       $(LIBIPTC_LIBS)
+endif
+
+# -----------------------------------------------------------------------------
+
 if ENABLE_LDCONFIG
 dist_systemunit_DATA += \
        units/ldconfig.service
@@ -1334,7 +1352,8 @@ manual_tests += \
        test-watchdog \
        test-log \
        test-ipcrm \
-       test-btrfs
+       test-btrfs \
+       test-fw-util
 
 if HAVE_KMOD
 manual_tests += \
@@ -1815,6 +1834,18 @@ test_btrfs_LDADD = \
        libsystemd-label.la \
        libsystemd-shared.la
 
+test_fw_util_SOURCES = \
+       src/test/test-fw-util.c
+
+test_fw_util_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(LIBIPTC_CFLAGS)
+
+test_fw_util_LDADD = \
+       libsystemd-fw.la \
+       libsystemd-shared.la \
+       $(LIBIPTC_LIBS)
+
 test_rtnl_manual_SOURCES = \
        src/test/test-rtnl-manual.c
 
index ddc604b..5057f8e 100644 (file)
@@ -870,6 +870,21 @@ fi
 AM_CONDITIONAL(HAVE_LIBIDN, [test "$have_libidn" = "yes"])
 
 # ------------------------------------------------------------------------------
+have_libiptc=no
+AC_ARG_ENABLE(libiptc, AS_HELP_STRING([--disable-libiptc], [Disable optional LIBIPTC support]))
+if test "x$enable_libiptc" != "xno"; then
+        PKG_CHECK_MODULES(LIBIPTC, [libiptc],
+               [AC_DEFINE(HAVE_LIBIPTC, 1, [Define if libiptc is available])
+                have_libiptc=yes
+                M4_DEFINES="$M4_DEFINES -DHAVE_LIBIPTC"],
+               [have_libiptc=no])
+        if test "x$have_libiptc" = "xno" -a "x$enable_libiptc" = "xyes"; then
+                AC_MSG_ERROR([*** libiptc support requested but libraries not found])
+        fi
+fi
+AM_CONDITIONAL(HAVE_LIBIPTC, [test "$have_libiptc" = "yes"])
+
+# ------------------------------------------------------------------------------
 have_binfmt=no
 AC_ARG_ENABLE(binfmt, AS_HELP_STRING([--disable-binfmt], [disable binfmt tool]))
 if test "x$enable_binfmt" != "xno"; then
@@ -1405,6 +1420,7 @@ AC_MSG_RESULT([
         GNUTLS:                  ${have_gnutls}
         libcurl:                 ${have_libcurl}
         libidn:                  ${have_libidn}
+        libiptc:                 ${have_libiptc}
         ELFUTILS:                ${have_elfutils}
         binfmt:                  ${have_binfmt}
         vconsole:                ${have_vconsole}
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;
+}
diff --git a/src/shared/fw-util.h b/src/shared/fw-util.h
new file mode 100644 (file)
index 0000000..58b4c20
--- /dev/null
@@ -0,0 +1,82 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  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 "in-addr-util.h"
+
+#ifdef HAVE_LIBIPTC
+
+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);
+
+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);
+
+#else
+
+static inline 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) {
+        return -ENOTSUP;
+}
+
+static inline 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) {
+        return -ENOSTUP;
+}
+
+#endif
index 9dc9ec8..b02e751 100644 (file)
@@ -243,12 +243,25 @@ int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *re
         return -EINVAL;
 }
 
-unsigned in_addr_netmask_to_prefixlen(const struct in_addr *addr) {
+unsigned char in_addr_netmask_to_prefixlen(const struct in_addr *addr) {
         assert(addr);
 
         return 32 - u32ctz(be32toh(addr->s_addr));
 }
 
+struct in_addr* in_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen) {
+        assert(addr);
+        assert(prefixlen <= 32);
+
+        /* Shifting beyond 32 is not defined, handle this specially. */
+        if (prefixlen == 0)
+                addr->s_addr = 0;
+        else
+                addr->s_addr = htobe32((0xffffffff << (32 - prefixlen)) & 0xffffffff);
+
+        return addr;
+}
+
 int in_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen) {
         uint8_t msb_octet = *(uint8_t*) addr;
 
@@ -284,9 +297,6 @@ int in_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask
         if (r < 0)
                 return r;
 
-        assert(prefixlen > 0 && prefixlen < 32);
-
-        mask->s_addr = htobe32((0xffffffff << (32 - prefixlen)) & 0xffffffff);
-
+        in_addr_prefixlen_to_netmask(mask, prefixlen);
         return 0;
 }
index 8da030c..4cf4418 100644 (file)
@@ -39,7 +39,8 @@ int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen);
 int in_addr_to_string(int family, const union in_addr_union *u, char **ret);
 int in_addr_from_string(int family, const char *s, union in_addr_union *ret);
 int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *ret);
-unsigned in_addr_netmask_to_prefixlen(const struct in_addr *addr);
+unsigned char in_addr_netmask_to_prefixlen(const struct in_addr *addr);
+struct in_addr* in_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen);
 int in_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen);
 int in_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask);
 
diff --git a/src/test/test-fw-util.c b/src/test/test-fw-util.c
new file mode 100644 (file)
index 0000000..ab891aa
--- /dev/null
@@ -0,0 +1,60 @@
+/*-*- 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 "log.h"
+#include "fw-util.h"
+
+#define MAKE_IN_ADDR_UNION(a,b,c,d) (union in_addr_union) { .in.s_addr = htobe32((uint32_t) (a) << 24 | (uint32_t) (b) << 16 | (uint32_t) (c) << 8 | (uint32_t) (d))}
+
+int main(int argc, char *argv[]) {
+        int r;
+        log_set_max_level(LOG_DEBUG);
+
+        r = fw_add_masquerade(true, AF_INET, 0, NULL, 0, "foobar", NULL, 0);
+        if (r < 0)
+                log_error_errno(r, "Failed to modify firewall: %m");
+
+        r = fw_add_masquerade(true, AF_INET, 0, NULL, 0, "foobar", NULL, 0);
+        if (r < 0)
+                log_error_errno(r, "Failed to modify firewall: %m");
+
+        r = fw_add_masquerade(false, AF_INET, 0, NULL, 0, "foobar", NULL, 0);
+        if (r < 0)
+                log_error_errno(r, "Failed to modify firewall: %m");
+
+        r = fw_add_local_dnat(true, AF_INET, IPPROTO_TCP, NULL, NULL, 0, NULL, 0, 4711, &MAKE_IN_ADDR_UNION(1, 2, 3, 4), 815, NULL);
+        if (r < 0)
+                log_error_errno(r, "Failed to modify firewall: %m");
+
+        r = fw_add_local_dnat(true, AF_INET, IPPROTO_TCP, NULL, NULL, 0, NULL, 0, 4711, &MAKE_IN_ADDR_UNION(1, 2, 3, 4), 815, NULL);
+        if (r < 0)
+                log_error_errno(r, "Failed to modify firewall: %m");
+
+        r = fw_add_local_dnat(true, AF_INET, IPPROTO_TCP, NULL, NULL, 0, NULL, 0, 4711, &MAKE_IN_ADDR_UNION(1, 2, 3, 5), 815, &MAKE_IN_ADDR_UNION(1, 2, 3, 4));
+        if (r < 0)
+                log_error_errno(r, "Failed to modify firewall: %m");
+
+        r = fw_add_local_dnat(false, AF_INET, IPPROTO_TCP, NULL, NULL, 0, NULL, 0, 4711, &MAKE_IN_ADDR_UNION(1, 2, 3, 5), 815, NULL);
+        if (r < 0)
+                log_error_errno(r, "Failed to modify firewall: %m");
+
+        return 0;
+}