[systemd-commits] 6 commits - .gitignore Makefile.am TODO configure.ac man/systemd-nspawn.xml man/systemd.network.xml network/80-container-ve.network src/import src/libsystemd src/machine src/network src/nspawn src/shared src/systemd src/test

Lennart Poettering lennart at kemper.freedesktop.org
Tue Jan 13 04:57:13 PST 2015


 .gitignore                               |    1 
 Makefile.am                              |   48 +++
 TODO                                     |    8 
 configure.ac                             |   16 +
 man/systemd-nspawn.xml                   |   26 +
 man/systemd.network.xml                  |   24 +
 network/80-container-ve.network          |    2 
 src/import/import-dkr.c                  |    2 
 src/libsystemd/sd-rtnl/sd-rtnl.c         |   51 ++-
 src/machine/machine-dbus.c               |   12 
 src/network/networkd-address.c           |   62 ++++
 src/network/networkd-link.c              |  140 ++++++----
 src/network/networkd-network-gperf.gperf |    2 
 src/network/networkd-network.c           |   12 
 src/network/networkd.h                   |    7 
 src/nspawn/nspawn.c                      |  408 +++++++++++++++++++++++++++----
 src/shared/fw-util.c                     |  344 ++++++++++++++++++++++++++
 src/shared/fw-util.h                     |   82 ++++++
 src/shared/in-addr-util.c                |   54 +++-
 src/shared/in-addr-util.h                |    6 
 src/systemd/sd-rtnl.h                    |    1 
 src/test/test-fw-util.c                  |   60 ++++
 22 files changed, 1230 insertions(+), 138 deletions(-)

New commits:
commit c14db9b32ab90738973071d31f259d1a457d7b4a
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 13 13:54:19 2015 +0100

    update TODO

diff --git a/TODO b/TODO
index 1d497b2..642561d 100644
--- a/TODO
+++ b/TODO
@@ -47,6 +47,14 @@ Release 219 preparations:
 
 Features:
 
+* import: add "pull-tar" support, for downloading/verifying tarballs
+
+* import: support compressed raw images
+
+* import: support verifiying raw images with gpg
+
+* core/cgroup: support net_cls modules, and support automatically allocating class ids, then add support for making firewall changes depending on it, to implement a per-service firewall
+
 * bus-proxy: reload policy when PID 1 reports a reload
 
 * the dbus1 connection user id is actually the euid, not the uid, and creds should return that

commit b4d8ef7c994c54abb7f389c47f7f099ce7ff9293
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 13 13:53:32 2015 +0100

    machined: refuse certain operation on non-container machines, since they cannot work elsewhere

diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c
index e7d4a3b..b46f0a8 100644
--- a/src/machine/machine-dbus.c
+++ b/src/machine/machine-dbus.c
@@ -175,6 +175,9 @@ int bus_machine_method_get_addresses(sd_bus *bus, sd_bus_message *message, void
         assert(message);
         assert(m);
 
+        if (m->class != MACHINE_CONTAINER)
+                return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting IP address data is only supported on container machines.");
+
         r = readlink_malloc("/proc/self/ns/net", &us);
         if (r < 0)
                 return sd_bus_error_set_errno(error, r);
@@ -319,6 +322,9 @@ int bus_machine_method_get_os_release(sd_bus *bus, sd_bus_message *message, void
         assert(message);
         assert(m);
 
+        if (m->class != MACHINE_CONTAINER)
+                return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting OS release data is only supported on container machines.");
+
         r = namespace_open(m->leader, NULL, &mntns_fd, NULL, &root_fd);
         if (r < 0)
                 return r;
@@ -403,6 +409,9 @@ int bus_machine_method_open_pty(sd_bus *bus, sd_bus_message *message, void *user
         assert(message);
         assert(m);
 
+        if (m->class != MACHINE_CONTAINER)
+                return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Opening pseudo TTYs is only supported on container machines.");
+
         master = openpt_in_namespace(m->leader, O_RDWR|O_NOCTTY|O_CLOEXEC);
         if (master < 0)
                 return master;
@@ -431,6 +440,9 @@ int bus_machine_method_open_login(sd_bus *bus, sd_bus_message *message, void *us
         const char *p;
         int r;
 
+        if (m->class != MACHINE_CONTAINER)
+                return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Opening logins is only supported on container machines.");
+
         r = bus_verify_polkit_async(
                         message,
                         CAP_SYS_ADMIN,

commit 6da023a048c7456c2ca849d9437a88817a1189d6
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 13 13:52:49 2015 +0100

    import: make sure we don't mangle file ownerships with the local passwd database when untarring

diff --git a/src/import/import-dkr.c b/src/import/import-dkr.c
index a3f390c..b54a1a6 100644
--- a/src/import/import-dkr.c
+++ b/src/import/import-dkr.c
@@ -493,7 +493,7 @@ static int dkr_import_job_run_tar(DkrImportJob *job) {
                 if (null_fd != STDOUT_FILENO)
                         safe_close(null_fd);
 
-                execlp("tar", "tar", "-C", job->temp_path, gzip ? "-xz" : "-x", NULL);
+                execlp("tar", "tar", "-C", job->temp_path, gzip ? "-xpz" : "-px", "--numeric-owner", NULL);
                 _exit(EXIT_FAILURE);
         }
 

commit 6d0b55c272ea31d025e8b3c311cea8cda0bfefd7
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 13 13:51:51 2015 +0100

    nspawn: add new option "--port=" for exposing container ports on the local host
    
    This exposes an IP port on the container as local port using DNAT.

diff --git a/Makefile.am b/Makefile.am
index 9d07a2b..00218b7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2658,6 +2658,11 @@ systemd_nspawn_LDADD += \
 	$(SECCOMP_LIBS)
 endif
 
+if HAVE_LIBIPTC
+systemd_nspawn_LDADD += \
+	libsystemd-fw.la
+endif
+
 # ------------------------------------------------------------------------------
 systemd_run_SOURCES = \
 	src/run/run.c
diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml
index fa0680f..7d67472 100644
--- a/man/systemd-nspawn.xml
+++ b/man/systemd-nspawn.xml
@@ -443,6 +443,32 @@
                         </varlistentry>
 
                         <varlistentry>
+                                <term><option>-p</option></term>
+                                <term><option>--port=</option></term>
+
+                                <listitem><para>If private networking
+                                is enabled, maps an IP port on the
+                                host onto an IP port on the
+                                container. Takes a protocol specifier
+                                (either <literal>tcp</literal> or
+                                <literal>udp</literal>), separated by
+                                a colon from a host port number in the
+                                range 1 to 65535, separated by a colon
+                                from a container port number in the
+                                range from 1 to 65535. The protocol
+                                specifier and its separating colon may
+                                be omitted, in which case
+                                <literal>tcp</literal> is assumed.
+                                The container port number and its
+                                colon may be ommitted, in which case
+                                the same port as the host port is
+                                implied. This option is only supported
+                                if private networking is used, such as
+                                <option>--network-veth</option> or
+                                <option>--network-bridge=</option>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
                                 <term><option>-Z</option></term>
                                 <term><option>--selinux-context=</option></term>
 
diff --git a/src/libsystemd/sd-rtnl/sd-rtnl.c b/src/libsystemd/sd-rtnl/sd-rtnl.c
index 7d388c9..a45ca5e 100644
--- a/src/libsystemd/sd-rtnl/sd-rtnl.c
+++ b/src/libsystemd/sd-rtnl/sd-rtnl.c
@@ -94,52 +94,79 @@ static int rtnl_compute_groups_ap(uint32_t *_groups, unsigned n_groups, va_list
         return 0;
 }
 
-int sd_rtnl_open(sd_rtnl **ret, unsigned n_groups, ...) {
+static int rtnl_open_fd_ap(sd_rtnl **ret, int fd, unsigned n_groups, va_list ap) {
         _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL;
-        va_list ap;
         socklen_t addrlen;
         int r, one = 1;
 
         assert_return(ret, -EINVAL);
+        assert_return(fd >= 0, -EINVAL);
 
         r = sd_rtnl_new(&rtnl);
         if (r < 0)
                 return r;
 
-        rtnl->fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_ROUTE);
-        if (rtnl->fd < 0)
-                return -errno;
-
-        r = setsockopt(rtnl->fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
+        r = setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
         if (r < 0)
                 return -errno;
 
-        r = setsockopt(rtnl->fd, SOL_NETLINK, NETLINK_PKTINFO, &one, sizeof(one));
+        r = setsockopt(fd, SOL_NETLINK, NETLINK_PKTINFO, &one, sizeof(one));
         if (r < 0)
                 return -errno;
 
-        va_start(ap, n_groups);
         r = rtnl_compute_groups_ap(&rtnl->sockaddr.nl.nl_groups, n_groups, ap);
-        va_end(ap);
         if (r < 0)
                 return r;
 
         addrlen = sizeof(rtnl->sockaddr);
 
-        r = bind(rtnl->fd, &rtnl->sockaddr.sa, addrlen);
+        r = bind(fd, &rtnl->sockaddr.sa, addrlen);
         if (r < 0)
                 return -errno;
 
-        r = getsockname(rtnl->fd, &rtnl->sockaddr.sa, &addrlen);
+        r = getsockname(fd, &rtnl->sockaddr.sa, &addrlen);
         if (r < 0)
                 return r;
 
+        rtnl->fd = fd;
+
         *ret = rtnl;
         rtnl = NULL;
 
         return 0;
 }
 
+int sd_rtnl_open_fd(sd_rtnl **ret, int fd, unsigned n_groups, ...) {
+        va_list ap;
+        int r;
+
+        va_start(ap, n_groups);
+        r = rtnl_open_fd_ap(ret, fd, n_groups, ap);
+        va_end(ap);
+
+        return r;
+}
+
+int sd_rtnl_open(sd_rtnl **ret, unsigned n_groups, ...) {
+        va_list ap;
+        int fd, r;
+
+        fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_ROUTE);
+        if (fd < 0)
+                return -errno;
+
+        va_start(ap, n_groups);
+        r = rtnl_open_fd_ap(ret, fd, n_groups, ap);
+        va_end(ap);
+
+        if (r < 0) {
+                safe_close(fd);
+                return r;
+        }
+
+        return 0;
+}
+
 int sd_rtnl_inc_rcvbuf(const sd_rtnl *const rtnl, const int size) {
         return fd_inc_rcvbuf(rtnl->fd, size);
 }
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index 59f6f38..2b1feb6 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -93,11 +93,22 @@
 #include "cap-list.h"
 #include "btrfs-util.h"
 #include "machine-image.h"
+#include "list.h"
+#include "in-addr-util.h"
+#include "fw-util.h"
+#include "local-addresses.h"
 
 #ifdef HAVE_SECCOMP
 #include "seccomp-util.h"
 #endif
 
+typedef struct ExposePort {
+        int protocol;
+        uint16_t host_port;
+        uint16_t container_port;
+        LIST_FIELDS(struct ExposePort, ports);
+} ExposePort;
+
 typedef enum ContainerStatus {
         CONTAINER_TERMINATED,
         CONTAINER_REBOOTED
@@ -172,6 +183,7 @@ static const char *arg_network_bridge = NULL;
 static unsigned long arg_personality = 0xffffffffLU;
 static char *arg_image = NULL;
 static Volatile arg_volatile = VOLATILE_NO;
+static ExposePort *arg_expose_ports = NULL;
 
 static void help(void) {
         printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n"
@@ -203,6 +215,8 @@ static void help(void) {
                "                            Add a virtual ethernet connection between host\n"
                "                            and container and add it to an existing bridge on\n"
                "                            the host\n"
+               "  -p --port=[PROTOCOL:]HOSTPORT[:CONTAINERPORT]\n"
+               "                            Expose a container IP port ont the host\n"
                "  -Z --selinux-context=SECLABEL\n"
                "                            Set the SELinux security context to be used by\n"
                "                            processes in the container\n"
@@ -225,8 +239,8 @@ static void help(void) {
                "     --register=BOOLEAN     Register container as machine\n"
                "     --keep-unit            Do not register a scope for the machine, reuse\n"
                "                            the service unit nspawn is running in\n"
-               "     --volatile[=MODE]      Run the system in volatile mode\n",
-               program_invocation_short_name);
+               "     --volatile[=MODE]      Run the system in volatile mode\n"
+               , program_invocation_short_name);
 }
 
 static int set_sanitized_path(char **b, const char *path) {
@@ -309,6 +323,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "personality",           required_argument, NULL, ARG_PERSONALITY       },
                 { "image",                 required_argument, NULL, 'i'                   },
                 { "volatile",              optional_argument, NULL, ARG_VOLATILE          },
+                { "port",                  required_argument, NULL, 'p'                   },
                 {}
         };
 
@@ -318,7 +333,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "+hD:u:bL:M:jS:Z:qi:x", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, "+hD:u:bL:M:jS:Z:qi:xp:", options, NULL)) >= 0)
 
                 switch (c) {
 
@@ -643,6 +658,65 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
+                case 'p': {
+                        const char *split, *e;
+                        uint16_t container_port, host_port;
+                        int protocol;
+                        ExposePort *p;
+
+                        if ((e = startswith(optarg, "tcp:")))
+                                protocol = IPPROTO_TCP;
+                        else if ((e = startswith(optarg, "udp:")))
+                                protocol = IPPROTO_UDP;
+                        else {
+                                e = optarg;
+                                protocol = IPPROTO_TCP;
+                        }
+
+                        split = strchr(e, ':');
+                        if (split) {
+                                char v[split - e + 1];
+
+                                memcpy(v, e, split - e);
+                                v[split - e] = 0;
+
+                                r = safe_atou16(v, &host_port);
+                                if (r < 0 || host_port <= 0) {
+                                        log_error("Failed to parse host port: %s", optarg);
+                                        return -EINVAL;
+                                }
+
+                                r = safe_atou16(split + 1, &container_port);
+                        } else {
+                                r = safe_atou16(e, &container_port);
+                                host_port = container_port;
+                        }
+
+                        if (r < 0 || container_port <= 0) {
+                                log_error("Failed to parse host port: %s", optarg);
+                                return -EINVAL;
+                        }
+
+                        LIST_FOREACH(ports, p, arg_expose_ports) {
+                                if (p->protocol == protocol && p->host_port == host_port) {
+                                        log_error("Duplicate port specification: %s", optarg);
+                                        return -EINVAL;
+                                }
+                        }
+
+                        p = new(ExposePort, 1);
+                        if (!p)
+                                return log_oom();
+
+                        p->protocol = protocol;
+                        p->host_port = host_port;
+                        p->container_port = container_port;
+
+                        LIST_PREPEND(ports, arg_expose_ports, p);
+
+                        break;
+                }
+
                 case '?':
                         return -EINVAL;
 
@@ -698,6 +772,11 @@ static int parse_argv(int argc, char *argv[]) {
                 return -EINVAL;
         }
 
+        if (arg_expose_ports && !arg_private_network) {
+                log_error("Cannot use --port= without private networking.");
+                return -EINVAL;
+        }
+
         arg_retain = (arg_retain | plus | (arg_private_network ? 1ULL << CAP_NET_ADMIN : 0)) & ~minus;
 
         return 1;
@@ -1349,8 +1428,8 @@ static int setup_dev_console(const char *dest, const char *console) {
 
 static int setup_kmsg(const char *dest, int kmsg_socket) {
         _cleanup_free_ char *from = NULL, *to = NULL;
-        int r, fd, k;
         _cleanup_umask_ mode_t u;
+        int r, fd, k;
         union {
                 struct cmsghdr cmsghdr;
                 uint8_t buf[CMSG_SPACE(sizeof(int))];
@@ -1401,7 +1480,7 @@ static int setup_kmsg(const char *dest, int kmsg_socket) {
 
         /* Store away the fd in the socket, so that it stays open as
          * long as we run the child */
-        k = sendmsg(kmsg_socket, &mh, MSG_DONTWAIT|MSG_NOSIGNAL);
+        k = sendmsg(kmsg_socket, &mh, MSG_NOSIGNAL);
         safe_close(fd);
 
         if (k < 0)
@@ -1412,6 +1491,198 @@ static int setup_kmsg(const char *dest, int kmsg_socket) {
         return 0;
 }
 
+static int send_rtnl(int send_fd) {
+        union {
+                struct cmsghdr cmsghdr;
+                uint8_t buf[CMSG_SPACE(sizeof(int))];
+        } control = {};
+        struct msghdr mh = {
+                .msg_control = &control,
+                .msg_controllen = sizeof(control),
+        };
+        struct cmsghdr *cmsg;
+        _cleanup_close_ int fd = -1;
+        ssize_t k;
+
+        assert(send_fd >= 0);
+
+        if (!arg_expose_ports)
+                return 0;
+
+        fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_ROUTE);
+        if (fd < 0)
+                return log_error_errno(errno, "failed to allocate container netlink: %m");
+
+        cmsg = CMSG_FIRSTHDR(&mh);
+        cmsg->cmsg_level = SOL_SOCKET;
+        cmsg->cmsg_type = SCM_RIGHTS;
+        cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+        memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
+
+        mh.msg_controllen = cmsg->cmsg_len;
+
+        /* Store away the fd in the socket, so that it stays open as
+         * long as we run the child */
+        k = sendmsg(send_fd, &mh, MSG_NOSIGNAL);
+        if (k < 0)
+                return log_error_errno(errno, "Failed to send netlink fd: %m");
+
+        return 0;
+}
+
+static int flush_ports(union in_addr_union *exposed) {
+        ExposePort *p;
+        int r, af = AF_INET;
+
+        assert(exposed);
+
+        if (!arg_expose_ports)
+                return 0;
+
+        if (in_addr_is_null(af, exposed))
+                return 0;
+
+        log_debug("Lost IP address.");
+
+        LIST_FOREACH(ports, p, arg_expose_ports) {
+                r = fw_add_local_dnat(false,
+                                      af,
+                                      p->protocol,
+                                      NULL,
+                                      NULL, 0,
+                                      NULL, 0,
+                                      p->host_port,
+                                      exposed,
+                                      p->container_port,
+                                      NULL);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to modify firewall: %m");
+        }
+
+        *exposed = IN_ADDR_NULL;
+        return 0;
+}
+
+static int expose_ports(sd_rtnl *rtnl, union in_addr_union *exposed) {
+        _cleanup_free_ struct local_address *addresses = NULL;
+        _cleanup_free_ char *pretty = NULL;
+        union in_addr_union new_exposed;
+        ExposePort *p;
+        bool add;
+        int af = AF_INET, r;
+
+        assert(exposed);
+
+        /* Invoked each time an address is added or removed inside the
+         * container */
+
+        if (!arg_expose_ports)
+                return 0;
+
+        r = local_addresses(rtnl, 0, af, &addresses);
+        if (r < 0)
+                return log_error_errno(r, "Failed to enumerate local addresses: %m");
+
+        add = r > 0 &&
+                addresses[0].family == af &&
+                addresses[0].scope < RT_SCOPE_LINK;
+
+        if (!add)
+                return flush_ports(exposed);
+
+        new_exposed = addresses[0].address;
+        if (in_addr_equal(af, exposed, &new_exposed))
+                return 0;
+
+        in_addr_to_string(af, &new_exposed, &pretty);
+        log_debug("New container IP is %s.", strna(pretty));
+
+        LIST_FOREACH(ports, p, arg_expose_ports) {
+
+                r = fw_add_local_dnat(true,
+                                      af,
+                                      p->protocol,
+                                      NULL,
+                                      NULL, 0,
+                                      NULL, 0,
+                                      p->host_port,
+                                      &new_exposed,
+                                      p->container_port,
+                                      in_addr_is_null(af, exposed) ? NULL : exposed);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to modify firewall: %m");
+        }
+
+        *exposed = new_exposed;
+        return 0;
+}
+
+static int on_address_change(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
+        union in_addr_union *exposed = userdata;
+
+        assert(rtnl);
+        assert(m);
+        assert(exposed);
+
+        expose_ports(rtnl, exposed);
+        return 0;
+}
+
+static int watch_rtnl(sd_event *event, int recv_fd, union in_addr_union *exposed, sd_rtnl **ret) {
+        union {
+                struct cmsghdr cmsghdr;
+                uint8_t buf[CMSG_SPACE(sizeof(int))];
+        } control = {};
+        struct msghdr mh = {
+                .msg_control = &control,
+                .msg_controllen = sizeof(control),
+        };
+        struct cmsghdr *cmsg;
+        _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL;
+        int fd, r;
+        ssize_t k;
+
+        assert(event);
+        assert(recv_fd >= 0);
+        assert(ret);
+
+        if (!arg_expose_ports)
+                return 0;
+
+        k = recvmsg(recv_fd, &mh, MSG_NOSIGNAL);
+        if (k < 0)
+                return log_error_errno(errno, "Failed to recv netlink fd: %m");
+
+        cmsg = CMSG_FIRSTHDR(&mh);
+        assert(cmsg->cmsg_level == SOL_SOCKET);
+        assert(cmsg->cmsg_type == SCM_RIGHTS);
+        assert(cmsg->cmsg_len = CMSG_LEN(sizeof(int)));
+        memcpy(&fd, CMSG_DATA(cmsg), sizeof(int));
+
+        r = sd_rtnl_open_fd(&rtnl, fd, 1, RTNLGRP_IPV4_IFADDR);
+        if (r < 0) {
+                safe_close(fd);
+                return log_error_errno(r, "Failed to create rtnl object: %m");
+        }
+
+        r = sd_rtnl_add_match(rtnl, RTM_NEWADDR, on_address_change, exposed);
+        if (r < 0)
+                return log_error_errno(r, "Failed to subscribe to RTM_NEWADDR messages: %m");
+
+        r = sd_rtnl_add_match(rtnl, RTM_DELADDR, on_address_change, exposed);
+        if (r < 0)
+                return log_error_errno(r, "Failed to subscribe to RTM_DELADDR messages: %m");
+
+        r = sd_rtnl_attach_event(rtnl, event, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add to even loop: %m");
+
+        *ret = rtnl;
+        rtnl = NULL;
+
+        return 0;
+}
+
 static int setup_hostname(void) {
 
         if (arg_share_system)
@@ -3059,7 +3330,6 @@ int main(int argc, char *argv[]) {
         _cleanup_free_ char *device_path = NULL, *root_device = NULL, *home_device = NULL, *srv_device = NULL, *console = NULL;
         bool root_device_rw = true, home_device_rw = true, srv_device_rw = true;
         _cleanup_close_ int master = -1, image_fd = -1;
-        _cleanup_close_pair_ int kmsg_socket_pair[2] = { -1, -1 };
         _cleanup_fdset_free_ FDSet *fds = NULL;
         int r, n_fd_passed, loop_nr = -1;
         char veth_name[IFNAMSIZ];
@@ -3067,6 +3337,7 @@ int main(int argc, char *argv[]) {
         sigset_t mask, mask_chld;
         pid_t pid = 0;
         int ret = EXIT_SUCCESS;
+        union in_addr_union exposed = {};
 
         log_parse_environment();
         log_open();
@@ -3233,11 +3504,6 @@ int main(int argc, char *argv[]) {
                 goto finish;
         }
 
-        if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, kmsg_socket_pair) < 0) {
-                r = log_error_errno(errno, "Failed to create kmsg socket pair: %m");
-                goto finish;
-        }
-
         assert_se(sigemptyset(&mask) == 0);
         sigset_add_many(&mask, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, -1);
         assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
@@ -3246,6 +3512,7 @@ int main(int argc, char *argv[]) {
         assert_se(sigaddset(&mask_chld, SIGCHLD) == 0);
 
         for (;;) {
+                _cleanup_close_pair_ int kmsg_socket_pair[2] = { -1, -1 }, rtnl_socket_pair[2] = { -1, -1 };
                 ContainerStatus container_status;
                 _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL;
                 struct sigaction sa = {
@@ -3259,6 +3526,16 @@ int main(int argc, char *argv[]) {
                         goto finish;
                 }
 
+                if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, kmsg_socket_pair) < 0) {
+                        r = log_error_errno(errno, "Failed to create kmsg socket pair: %m");
+                        goto finish;
+                }
+
+                if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, rtnl_socket_pair) < 0) {
+                        r = log_error_errno(errno, "Failed to create rtnl socket pair: %m");
+                        goto finish;
+                }
+
                 /* Child can be killed before execv(), so handle SIGCHLD
                  * in order to interrupt parent's blocking calls and
                  * give it a chance to call wait() and terminate. */
@@ -3317,6 +3594,7 @@ int main(int argc, char *argv[]) {
                         close_nointr(STDERR_FILENO);
 
                         kmsg_socket_pair[0] = safe_close(kmsg_socket_pair[0]);
+                        rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]);
 
                         reset_all_signal_handlers();
                         reset_signal_mask();
@@ -3412,9 +3690,12 @@ int main(int argc, char *argv[]) {
 
                         if (setup_kmsg(arg_directory, kmsg_socket_pair[1]) < 0)
                                 _exit(EXIT_FAILURE);
-
                         kmsg_socket_pair[1] = safe_close(kmsg_socket_pair[1]);
 
+                        if (send_rtnl(rtnl_socket_pair[1]) < 0)
+                                _exit(EXIT_FAILURE);
+                        rtnl_socket_pair[1] = safe_close(rtnl_socket_pair[1]);
+
                         /* Tell the parent that we are ready, and that
                          * it can cgroupify us to that we lack access
                          * to certain devices and resources. */
@@ -3585,13 +3866,13 @@ int main(int argc, char *argv[]) {
                 fdset_free(fds);
                 fds = NULL;
 
+                kmsg_socket_pair[1] = safe_close(kmsg_socket_pair[1]);
+                rtnl_socket_pair[1] = safe_close(rtnl_socket_pair[1]);
+
                 /* Wait for the most basic Child-setup to be done,
                  * before we add hardware to it, and place it in a
                  * cgroup. */
                 if (barrier_sync_next(&barrier)) {
-                        _cleanup_event_unref_ sd_event *event = NULL;
-                        _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
-                        char last_char = 0;
                         int ifi = 0;
 
                         r = move_network_interfaces(pid);
@@ -3631,52 +3912,67 @@ int main(int argc, char *argv[]) {
                         (void) barrier_place(&barrier);
 
                         /* And wait that the child is completely ready now. */
-                        (void) barrier_place_and_sync(&barrier);
+                        if (barrier_place_and_sync(&barrier)) {
+                                _cleanup_event_unref_ sd_event *event = NULL;
+                                _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
+                                _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL;
+                                char last_char = 0;
 
-                        sd_notify(false,
-                                  "READY=1\n"
-                                  "STATUS=Container running.");
+                                sd_notify(false,
+                                          "READY=1\n"
+                                          "STATUS=Container running.");
 
-                        r = sd_event_new(&event);
-                        if (r < 0) {
-                                log_error_errno(r, "Failed to get default event source: %m");
-                                goto finish;
-                        }
+                                r = sd_event_new(&event);
+                                if (r < 0) {
+                                        log_error_errno(r, "Failed to get default event source: %m");
+                                        goto finish;
+                                }
 
-                        if (arg_boot) {
-                                /* Try to kill the init system on SIGINT or SIGTERM */
-                                sd_event_add_signal(event, NULL, SIGINT, on_orderly_shutdown, UINT32_TO_PTR(pid));
-                                sd_event_add_signal(event, NULL, SIGTERM, on_orderly_shutdown, UINT32_TO_PTR(pid));
-                        } else {
-                                /* Immediately exit */
-                                sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
-                                sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
-                        }
+                                if (arg_boot) {
+                                        /* Try to kill the init system on SIGINT or SIGTERM */
+                                        sd_event_add_signal(event, NULL, SIGINT, on_orderly_shutdown, UINT32_TO_PTR(pid));
+                                        sd_event_add_signal(event, NULL, SIGTERM, on_orderly_shutdown, UINT32_TO_PTR(pid));
+                                } else {
+                                        /* Immediately exit */
+                                        sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
+                                        sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
+                                }
 
-                        /* simply exit on sigchld */
-                        sd_event_add_signal(event, NULL, SIGCHLD, NULL, NULL);
+                                /* simply exit on sigchld */
+                                sd_event_add_signal(event, NULL, SIGCHLD, NULL, NULL);
 
-                        r = pty_forward_new(event, master, true, &forward);
-                        if (r < 0) {
-                                log_error_errno(r, "Failed to create PTY forwarder: %m");
-                                goto finish;
-                        }
+                                if (arg_expose_ports) {
+                                        r = watch_rtnl(event, rtnl_socket_pair[0], &exposed, &rtnl);
+                                        if (r < 0)
+                                                goto finish;
 
-                        r = sd_event_loop(event);
-                        if (r < 0) {
-                                log_error_errno(r, "Failed to run event loop: %m");
-                                goto finish;
-                        }
+                                        (void) expose_ports(rtnl, &exposed);
+                                }
 
-                        pty_forward_get_last_char(forward, &last_char);
+                                rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]);
 
-                        forward = pty_forward_free(forward);
+                                r = pty_forward_new(event, master, true, &forward);
+                                if (r < 0) {
+                                        log_error_errno(r, "Failed to create PTY forwarder: %m");
+                                        goto finish;
+                                }
 
-                        if (!arg_quiet && last_char != '\n')
-                                putc('\n', stdout);
+                                r = sd_event_loop(event);
+                                if (r < 0) {
+                                        log_error_errno(r, "Failed to run event loop: %m");
+                                        goto finish;
+                                }
+
+                                pty_forward_get_last_char(forward, &last_char);
+
+                                forward = pty_forward_free(forward);
+
+                                if (!arg_quiet && last_char != '\n')
+                                        putc('\n', stdout);
 
-                        /* Kill if it is not dead yet anyway */
-                        terminate_machine(pid);
+                                /* Kill if it is not dead yet anyway */
+                                terminate_machine(pid);
+                        }
                 }
 
                 /* Normally redundant, but better safe than sorry */
@@ -3714,6 +4010,8 @@ int main(int argc, char *argv[]) {
                         r = 0;
                         break;
                 }
+
+                flush_ports(&exposed);
         }
 
 finish:
@@ -3753,5 +4051,13 @@ finish:
         strv_free(arg_bind_ro);
         strv_free(arg_tmpfs);
 
+        flush_ports(&exposed);
+
+        while (arg_expose_ports) {
+                ExposePort *p = arg_expose_ports;
+                LIST_REMOVE(ports, arg_expose_ports, p);
+                free(p);
+        }
+
         return r < 0 ? EXIT_FAILURE : ret;
 }
diff --git a/src/systemd/sd-rtnl.h b/src/systemd/sd-rtnl.h
index b8836e2..7919172 100644
--- a/src/systemd/sd-rtnl.h
+++ b/src/systemd/sd-rtnl.h
@@ -42,6 +42,7 @@ typedef int (*sd_rtnl_message_handler_t)(sd_rtnl *rtnl, sd_rtnl_message *m, void
 
 /* bus */
 int sd_rtnl_open(sd_rtnl **nl, unsigned n_groups, ...);
+int sd_rtnl_open_fd(sd_rtnl **nl, int fd, unsigned n_groups, ...);
 int sd_rtnl_inc_rcvbuf(const sd_rtnl *const rtnl, const int size);
 
 sd_rtnl *sd_rtnl_ref(sd_rtnl *nl);

commit 5a8bcb674f71a20e95df55319b34c556638378ce
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 13 13:47:08 2015 +0100

    networkd: add minimal IP forwarding and masquerading support to .network files
    
    This adds two new settings to networkd's .network files:
    IPForwarding=yes and IPMasquerade=yes. The former controls the
    "forwarding" sysctl setting of the interface, thus controlling whether
    IP forwarding shall be enabled on the specific interface. The latter
    controls whether a firewall rule shall be installed that exposes traffic
    coming from the interface as coming from the local host to all other
    interfaces.
    
    This also enables both options by default for container network
    interfaces, thus making "systemd-nspawn --network-veth" have network
    connectivity out of the box.

diff --git a/Makefile.am b/Makefile.am
index 205dafa..9d07a2b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5403,6 +5403,11 @@ systemd_networkd_SOURCES = \
 systemd_networkd_LDADD = \
 	libsystemd-networkd-core.la
 
+if HAVE_LIBIPTC
+systemd_networkd_LDADD += \
+	libsystemd-fw.la
+endif
+
 noinst_LTLIBRARIES += \
 	libsystemd-networkd-core.la
 
@@ -5493,6 +5498,11 @@ test_network_SOURCES = \
 test_network_LDADD = \
 	libsystemd-networkd-core.la
 
+if HAVE_LIBIPTC
+test_network_LDADD += \
+	libsystemd-fw.la
+endif
+
 test_network_tables_SOURCES = \
 	src/network/test-network-tables.c \
 	src/shared/test-tables.h
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index ea278c7..d540268 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -345,6 +345,30 @@
                                         </listitem>
                                 </varlistentry>
                                 <varlistentry>
+                                        <term><varname>IPForward=</varname></term>
+                                        <listitem><para>Configures IP
+                                        forwarding for the network
+                                        interface. If enabled incoming
+                                        packets on the network
+                                        interface will be forwarded to
+                                        other interfaces according to
+                                        the routing table. Takes a
+                                        boolean
+                                        argument.</para></listitem>
+                                </varlistentry>
+                                <varlistentry>
+                                        <term><varname>IPMasquerade=</varname></term>
+                                        <listitem><para>Configures IP
+                                        masquerading for the network
+                                        interface. If enabled packets
+                                        forwarded from the network
+                                        interface will be appear as
+                                        coming from the local
+                                        host. Takes a boolean
+                                        argument. Implies
+                                        <varname>IPForward=yes</varname>.</para></listitem>
+                                </varlistentry>
+                                <varlistentry>
                                         <term><varname>Bridge=</varname></term>
                                         <listitem>
                                                 <para>The name of the bridge to add the link to.</para>
diff --git a/network/80-container-ve.network b/network/80-container-ve.network
index cb04c7c..fe24eb4 100644
--- a/network/80-container-ve.network
+++ b/network/80-container-ve.network
@@ -13,3 +13,5 @@ Driver=veth
 Address=0.0.0.0/28
 IPv4LL=yes
 DHCPServer=yes
+IPForward=yes
+IPMasquerade=yes
diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
index b28c2e0..b4eb91e 100644
--- a/src/network/networkd-address.c
+++ b/src/network/networkd-address.c
@@ -21,13 +21,13 @@
 
 #include <net/if.h>
 
-#include "networkd.h"
-#include "networkd-link.h"
-
 #include "utf8.h"
 #include "util.h"
 #include "conf-parser.h"
+#include "fw-util.h"
 #include "network-internal.h"
+#include "networkd.h"
+#include "networkd-link.h"
 
 static void address_init(Address *address) {
         assert(address);
@@ -103,6 +103,54 @@ void address_free(Address *address) {
         free(address);
 }
 
+int address_establish(Address *address, Link *link) {
+        bool masq;
+        int r;
+
+        assert(address);
+        assert(link);
+
+        masq = link->network &&
+                link->network->ip_masquerade &&
+                address->family == AF_INET &&
+                address->scope < RT_SCOPE_LINK;
+
+        /* Add firewall entry if this is requested */
+        if (address->ip_forward_done != masq) {
+                union in_addr_union masked = address->in_addr;
+                in_addr_mask(address->family, &masked, address->prefixlen);
+
+                r = fw_add_masquerade(masq, AF_INET, 0, &masked, address->prefixlen, NULL, NULL, 0);
+                if (r < 0)
+                        log_link_warning_errno(link, r, "Could not enable IP masquerading: %m");
+
+                address->ip_forward_done = masq;
+        }
+
+        return 0;
+}
+
+int address_release(Address *address, Link *link) {
+        int r;
+
+        assert(address);
+        assert(link);
+
+        /* Remove masquerading firewall entry if it was added */
+        if (address->ip_forward_done) {
+                union in_addr_union masked = address->in_addr;
+                in_addr_mask(address->family, &masked, address->prefixlen);
+
+                r = fw_add_masquerade(false, AF_INET, 0, &masked, address->prefixlen, NULL, NULL, 0);
+                if (r < 0)
+                        log_link_warning_errno(link, r, "Failed to disable IP masquerading: %m");
+
+                address->ip_forward_done = false;
+        }
+
+        return 0;
+}
+
 int address_drop(Address *address, Link *link,
                  sd_rtnl_message_handler_t callback) {
         _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL;
@@ -115,6 +163,8 @@ int address_drop(Address *address, Link *link,
         assert(link->manager);
         assert(link->manager->rtnl);
 
+        address_release(address, link);
+
         r = sd_rtnl_message_new_addr(link->manager->rtnl, &req, RTM_DELADDR,
                                      link->ifindex, address->family);
         if (r < 0)
@@ -333,6 +383,8 @@ int address_configure(Address *address, Link *link,
 
         link_ref(link);
 
+        address_establish(address, link);
+
         return 0;
 }
 
@@ -549,8 +601,7 @@ bool address_equal(Address *a1, Address *a2) {
                         return (b1 >> (32 - a1->prefixlen)) == (b2 >> (32 - a1->prefixlen));
                 }
 
-        case AF_INET6:
-        {
+        case AF_INET6: {
                 uint64_t *b1, *b2;
 
                 b1 = (uint64_t*)&a1->in_addr.in6;
@@ -558,6 +609,7 @@ bool address_equal(Address *a1, Address *a2) {
 
                 return (((b1[0] ^ b2[0]) | (b1[1] ^ b2[1])) == 0UL);
         }
+
         default:
                 assert_not_reached("Invalid address family");
         }
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 63c7a8b..12944a0 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -23,16 +23,16 @@
 #include <linux/if.h>
 #include <unistd.h>
 
-#include "networkd-link.h"
-#include "networkd-netdev.h"
-#include "libudev-private.h"
-#include "udev-util.h"
 #include "util.h"
 #include "virt.h"
+#include "fileio.h"
 #include "bus-util.h"
+#include "udev-util.h"
+#include "libudev-private.h"
 #include "network-internal.h"
+#include "networkd-link.h"
+#include "networkd-netdev.h"
 #include "conf-parser.h"
-
 #include "dhcp-lease-internal.h"
 
 static bool link_dhcp6_enabled(Link *link) {
@@ -82,12 +82,22 @@ static bool link_lldp_enabled(Link *link) {
         if (!link->network)
                 return false;
 
-        if(link->network->bridge)
+        if (link->network->bridge)
                 return false;
 
         return link->network->lldp;
 }
 
+static bool link_ip_forward_enabled(Link *link) {
+        if (link->flags & IFF_LOOPBACK)
+                return false;
+
+        if (!link->network)
+                return false;
+
+        return link->network->ip_forward;
+}
+
 #define FLAG_STRING(string, flag, old, new) \
         (((old ^ new) & flag) \
                 ? ((old & flag) ? (" -" string) : (" +" string)) \
@@ -653,9 +663,7 @@ static int link_enter_set_addresses(Link *link) {
         LIST_FOREACH(addresses, ad, link->network->static_addresses) {
                 r = address_configure(ad, link, &address_handler);
                 if (r < 0) {
-                        log_link_warning(link,
-                                         "could not set addresses: %s",
-                                         strerror(-r));
+                        log_link_warning_errno(link, r, "Could not set addresses: %m");
                         link_enter_failed(link);
                         return r;
                 }
@@ -1217,6 +1225,18 @@ static int link_enter_join_netdev(Link *link) {
         return 0;
 }
 
+static int link_set_ip_forward(Link *link) {
+        const char *p = NULL;
+        int r;
+
+        p = strappenda("/proc/sys/net/ipv4/conf/", link->ifname, "/forwarding");
+        r = write_string_file_no_create(p, link_ip_forward_enabled(link) ? "1" : "0");
+        if (r < 0)
+                log_link_warning_errno(link, r, "Cannot configure IP forwarding for interface: %m");
+
+        return 0;
+}
+
 static int link_configure(Link *link) {
         int r;
 
@@ -1228,6 +1248,10 @@ static int link_configure(Link *link) {
         if (r < 0)
                 return r;
 
+        r = link_set_ip_forward(link);
+        if (r < 0)
+                return r;
+
         if (link_ipv4ll_enabled(link)) {
                 r = ipv4ll_configure(link);
                 if (r < 0)
@@ -1364,16 +1388,27 @@ int link_initialized(Link *link, struct udev_device *device) {
         return 0;
 }
 
+static Address* link_get_equal_address(Link *link, Address *needle) {
+        Address *i;
+
+        assert(link);
+        assert(needle);
+
+        LIST_FOREACH(addresses, i, link->addresses)
+                if (address_equal(i, needle))
+                        return i;
+
+        return NULL;
+}
+
 int link_rtnl_process_address(sd_rtnl *rtnl, sd_rtnl_message *message, void *userdata) {
         Manager *m = userdata;
         Link *link = NULL;
         uint16_t type;
         _cleanup_address_free_ Address *address = NULL;
-        Address *ad;
-        char buf[INET6_ADDRSTRLEN];
-        char valid_buf[FORMAT_TIMESPAN_MAX];
+        Address *existing;
+        char buf[INET6_ADDRSTRLEN], valid_buf[FORMAT_TIMESPAN_MAX];
         const char *valid_str = NULL;
-        bool address_dropped = false;
         int r, ifindex;
 
         assert(rtnl);
@@ -1415,50 +1450,42 @@ int link_rtnl_process_address(sd_rtnl *rtnl, sd_rtnl_message *message, void *use
 
         r = sd_rtnl_message_addr_get_family(message, &address->family);
         if (r < 0 || !IN_SET(address->family, AF_INET, AF_INET6)) {
-                log_link_warning(link,
-                                 "rtnl: received address with invalid family, ignoring");
+                log_link_warning(link, "rtnl: received address with invalid family, ignoring");
                 return 0;
         }
 
         r = sd_rtnl_message_addr_get_prefixlen(message, &address->prefixlen);
         if (r < 0) {
-                log_link_warning(link,
-                                 "rtnl: received address with invalid prefixlen, ignoring");
+                log_link_warning(link, "rtnl: received address with invalid prefixlen, ignoring");
                 return 0;
         }
 
         r = sd_rtnl_message_addr_get_scope(message, &address->scope);
         if (r < 0) {
-                log_link_warning(link,
-                                 "rtnl: received address with invalid scope, ignoring");
+                log_link_warning(link, "rtnl: received address with invalid scope, ignoring");
                 return 0;
         }
 
         r = sd_rtnl_message_addr_get_flags(message, &address->flags);
         if (r < 0) {
-                log_link_warning(link,
-                                 "rtnl: received address with invalid flags, ignoring");
+                log_link_warning(link, "rtnl: received address with invalid flags, ignoring");
                 return 0;
         }
 
         switch (address->family) {
         case AF_INET:
-                r = sd_rtnl_message_read_in_addr(message, IFA_LOCAL,
-                                                 &address->in_addr.in);
+                r = sd_rtnl_message_read_in_addr(message, IFA_LOCAL, &address->in_addr.in);
                 if (r < 0) {
-                        log_link_warning(link,
-                                         "rtnl: received address without valid address, ignoring");
+                        log_link_warning(link, "rtnl: received address without valid address, ignoring");
                         return 0;
                 }
 
                 break;
 
         case AF_INET6:
-                r = sd_rtnl_message_read_in6_addr(message, IFA_ADDRESS,
-                                                  &address->in_addr.in6);
+                r = sd_rtnl_message_read_in6_addr(message, IFA_ADDRESS, &address->in_addr.in6);
                 if (r < 0) {
-                        log_link_warning(link,
-                                         "rtnl: received address without valid address, ignoring");
+                        log_link_warning(link, "rtnl: received address without valid address, ignoring");
                         return 0;
                 }
 
@@ -1468,14 +1495,12 @@ int link_rtnl_process_address(sd_rtnl *rtnl, sd_rtnl_message *message, void *use
                 assert_not_reached("invalid address family");
         }
 
-        if (!inet_ntop(address->family, &address->in_addr, buf,
-                       INET6_ADDRSTRLEN)) {
+        if (!inet_ntop(address->family, &address->in_addr, buf, INET6_ADDRSTRLEN)) {
                 log_link_warning(link, "could not print address");
                 return 0;
         }
 
-        r = sd_rtnl_message_read_cache_info(message, IFA_CACHEINFO,
-                                            &address->cinfo);
+        r = sd_rtnl_message_read_cache_info(message, IFA_CACHEINFO, &address->cinfo);
         if (r >= 0) {
                 if (address->cinfo.ifa_valid == CACHE_INFO_INFINITY_LIFE_TIME)
                         valid_str = "ever";
@@ -1485,43 +1510,40 @@ int link_rtnl_process_address(sd_rtnl *rtnl, sd_rtnl_message *message, void *use
                                                     USEC_PER_SEC);
         }
 
-        LIST_FOREACH(addresses, ad, link->addresses) {
-                if (address_equal(ad, address)) {
-                        LIST_REMOVE(addresses, link->addresses, ad);
+        existing = link_get_equal_address(link, address);
+
+        switch (type) {
+        case RTM_NEWADDR:
+                if (existing) {
+                        log_link_debug(link, "Updating address: %s/%u (valid for %s)", buf, address->prefixlen, valid_str);
 
-                        address_free(ad);
 
-                        address_dropped = true;
+                        existing->scope = address->scope;
+                        existing->flags = address->flags;
+                        existing->cinfo = address->cinfo;
 
-                        break;
-                }
-        }
+                } else {
+                        log_link_debug(link, "Adding address: %s/%u (valid for %s)", buf, address->prefixlen, valid_str);
 
-        switch (type) {
-        case RTM_NEWADDR:
-                if (!address_dropped)
-                        log_link_debug(link, "added address: %s/%u (valid for %s)",
-                                       buf, address->prefixlen, valid_str);
-                else
-                        log_link_debug(link, "updated address: %s/%u (valid for %s)",
-                                       buf, address->prefixlen, valid_str);
+                        LIST_PREPEND(addresses, link->addresses, address);
+                        address_establish(address, link);
 
-                LIST_PREPEND(addresses, link->addresses, address);
-                address = NULL;
+                        address = NULL;
 
-                link_save(link);
+                        link_save(link);
+                }
 
                 break;
+
         case RTM_DELADDR:
-                if (address_dropped) {
-                        log_link_debug(link, "removed address: %s/%u (valid for %s)",
-                                       buf, address->prefixlen, valid_str);
 
-                        link_save(link);
+                if (existing) {
+                        log_link_debug(link, "Removing address: %s/%u (valid for %s)", buf, address->prefixlen, valid_str);
+                        address_release(existing, link);
+                        LIST_REMOVE(addresses, link->addresses, existing);
+                        address_free(existing);
                 } else
-                        log_link_warning(link,
-                                         "removing non-existent address: %s/%u (valid for %s)",
-                                         buf, address->prefixlen, valid_str);
+                        log_link_warning(link, "Removing non-existent address: %s/%u (valid for %s)", buf, address->prefixlen, valid_str);
 
                 break;
         default:
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 5094b48..3eb37b4 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -44,6 +44,8 @@ Network.Domains,             config_parse_domains,               0,
 Network.DNS,                 config_parse_strv,                  0,                             offsetof(Network, dns)
 Network.LLMNR,               config_parse_llmnr,                 0,                             offsetof(Network, llmnr)
 Network.NTP,                 config_parse_strv,                  0,                             offsetof(Network, ntp)
+Network.IPForward,           config_parse_bool,                  0,                             offsetof(Network, ip_forward)
+Network.IPMasquerade,        config_parse_bool,                  0,                             offsetof(Network, ip_masquerade)
 Address.Address,             config_parse_address,               0,                             0
 Address.Peer,                config_parse_address,               0,                             0
 Address.Broadcast,           config_parse_broadcast,             0,                             0
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 966b59b..d6504cc 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -22,14 +22,14 @@
 #include <ctype.h>
 #include <net/if.h>
 
-#include "networkd.h"
-#include "networkd-netdev.h"
-#include "networkd-link.h"
-#include "network-internal.h"
 #include "path-util.h"
 #include "conf-files.h"
 #include "conf-parser.h"
 #include "util.h"
+#include "networkd.h"
+#include "networkd-netdev.h"
+#include "networkd-link.h"
+#include "network-internal.h"
 
 static int network_load_one(Manager *manager, const char *filename) {
         _cleanup_network_free_ Network *network = NULL;
@@ -109,6 +109,10 @@ static int network_load_one(Manager *manager, const char *filename) {
         if (r < 0)
                 return r;
 
+        /* IPMasquerade=yes implies IPForward=yes */
+        if (network->ip_masquerade)
+                network->ip_forward = true;
+
         LIST_PREPEND(networks, manager->networks, network);
 
         LIST_FOREACH(routes, route, network->static_routes) {
diff --git a/src/network/networkd.h b/src/network/networkd.h
index 7107c5f..39b2d2b 100644
--- a/src/network/networkd.h
+++ b/src/network/networkd.h
@@ -120,6 +120,9 @@ struct Network {
 
         unsigned cost;
 
+        bool ip_masquerade;
+        bool ip_forward;
+
         struct ether_addr *mac;
         unsigned mtu;
 
@@ -157,6 +160,8 @@ struct Address {
         union in_addr_union in_addr;
         union in_addr_union in_addr_peer;
 
+        bool ip_forward_done;
+
         LIST_FIELDS(Address, addresses);
 };
 
@@ -326,6 +331,8 @@ void address_free(Address *address);
 int address_configure(Address *address, Link *link, sd_rtnl_message_handler_t callback);
 int address_update(Address *address, Link *link, sd_rtnl_message_handler_t callback);
 int address_drop(Address *address, Link *link, sd_rtnl_message_handler_t callback);
+int address_establish(Address *address, Link *link);
+int address_release(Address *address, Link *link);
 bool address_equal(Address *a1, Address *a2);
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(Address*, address_free);
diff --git a/src/shared/in-addr-util.c b/src/shared/in-addr-util.c
index b02e751..d88864b 100644
--- a/src/shared/in-addr-util.c
+++ b/src/shared/in-addr-util.c
@@ -300,3 +300,39 @@ int in_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask
         in_addr_prefixlen_to_netmask(mask, prefixlen);
         return 0;
 }
+
+int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen) {
+        assert(addr);
+
+        if (family == AF_INET) {
+                struct in_addr mask;
+
+                if (!in_addr_prefixlen_to_netmask(&mask, prefixlen))
+                        return -EINVAL;
+
+                addr->in.s_addr &= mask.s_addr;
+                return 0;
+        }
+
+        if (family == AF_INET6) {
+                unsigned i;
+
+                for (i = 0; i < 16; i++) {
+                        uint8_t mask;
+
+                        if (prefixlen >= 8) {
+                                mask = 0xFF;
+                                prefixlen -= 8;
+                        } else {
+                                mask = 0xFF << (8 - prefixlen);
+                                prefixlen = 0;
+                        }
+
+                        addr->in6.s6_addr[i] &= mask;
+                }
+
+                return 0;
+        }
+
+        return -EAFNOSUPPORT;
+}
diff --git a/src/shared/in-addr-util.h b/src/shared/in-addr-util.h
index 4cf4418..51af088 100644
--- a/src/shared/in-addr-util.h
+++ b/src/shared/in-addr-util.h
@@ -43,8 +43,11 @@ 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);
+int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen);
 
 static inline size_t FAMILY_ADDRESS_SIZE(int family) {
         assert(family == AF_INET || family == AF_INET6);
         return family == AF_INET6 ? 16 : 4;
 }
+
+#define IN_ADDR_NULL ((union in_addr_union) {})

commit 76917807eb50ccde58901e8bec7ed3d408d1cc22
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 13 13:44:30 2015 +0100

    shared: add minimal firewall manipulation helpers for establishing NAT rules, using libiptc

diff --git a/.gitignore b/.gitignore
index 2394e7c..caef831 100644
--- a/.gitignore
+++ b/.gitignore
@@ -182,6 +182,7 @@
 /test-fdset
 /test-fileio
 /test-fstab-util
+/test-fw-util
 /test-hashmap
 /test-hostname
 /test-icmp6-rs
diff --git a/Makefile.am b/Makefile.am
index 7f9dc26..205dafa 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -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
 
diff --git a/configure.ac b/configure.ac
index ddc604b..5057f8e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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
index 0000000..ceb1ae5
--- /dev/null
+++ b/src/shared/fw-util.c
@@ -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
index 0000000..58b4c20
--- /dev/null
+++ b/src/shared/fw-util.h
@@ -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
diff --git a/src/shared/in-addr-util.c b/src/shared/in-addr-util.c
index 9dc9ec8..b02e751 100644
--- a/src/shared/in-addr-util.c
+++ b/src/shared/in-addr-util.c
@@ -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;
 }
diff --git a/src/shared/in-addr-util.h b/src/shared/in-addr-util.h
index 8da030c..4cf4418 100644
--- a/src/shared/in-addr-util.h
+++ b/src/shared/in-addr-util.h
@@ -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
index 0000000..ab891aa
--- /dev/null
+++ b/src/test/test-fw-util.c
@@ -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;
+}



More information about the systemd-commits mailing list