[systemd-devel] [RFC][PATCH] networkd: add a basic network daemon

Tom Gundersen teg at jklm.no
Tue Nov 5 16:33:50 PST 2013


This daemon listens for and configures network devices tagged with
'systemd-networkd'. By default, no devices are tagged so this daemon
can safely run in parallel with existing network daemons/scripts.

Networks are configured in /etc/systemd/network/*.network. The first .network
file that matches a given link is applied. The matching logic is similar to
the one for .link files, but additionally supports matching on interface name.

The mid-term aim is to provide an alternative to ad-hoc scripts currently used
in initrd's and for wired setups that don't change much (e.g., as seen on
servers/and some embedded systems).

Currently, static addresses and a gateway can be configured, mostly as a proof
of concept. I expect to expand on this as soon as we are agreed on the basic
design.

Comments, testing and contributions greatly appreciated!



Short-term TODO:
 - make rtnl calls asynchronous
 - bridging support
 - add DHCPv4/6 and IPv4LL support (via a library)
 - more/better IPv6 handling
 - more routing support
 - more dynamic behavior (e.g., react to link-sense and add a cli)

Example .network file:

[Match]
MACAddress=
Path=
Driver=
Type=
Name=

[Network]
Description=

[IP]
Gateway=192.168.1.1
Address=label at 192.168.1.23/24
Address=fe80::9aee:94ff:fe3f:c618/64
---
 .gitignore                        |   2 +
 Makefile.am                       |  54 ++++++++
 src/network/.gitignore            |   1 +
 src/network/Makefile              |   1 +
 src/network/networkd-address.c    | 252 ++++++++++++++++++++++++++++++++++++++
 src/network/networkd-gperf.gperf  |  25 ++++
 src/network/networkd-link.c       | 116 ++++++++++++++++++
 src/network/networkd-manager.c    | 207 +++++++++++++++++++++++++++++++
 src/network/networkd-network.c    | 217 ++++++++++++++++++++++++++++++++
 src/network/networkd.c            |  58 +++++++++
 src/network/networkd.h            | 141 +++++++++++++++++++++
 src/network/test-network.c        |  81 ++++++++++++
 units/.gitignore                  |   1 +
 units/systemd-networkd.service.in |  19 +++
 14 files changed, 1175 insertions(+)
 create mode 100644 src/network/.gitignore
 create mode 120000 src/network/Makefile
 create mode 100644 src/network/networkd-address.c
 create mode 100644 src/network/networkd-gperf.gperf
 create mode 100644 src/network/networkd-link.c
 create mode 100644 src/network/networkd-manager.c
 create mode 100644 src/network/networkd-network.c
 create mode 100644 src/network/networkd.c
 create mode 100644 src/network/networkd.h
 create mode 100644 src/network/test-network.c
 create mode 100644 units/systemd-networkd.service.in

diff --git a/.gitignore b/.gitignore
index dd6432c..bdfbe11 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,6 +56,7 @@
 /systemd-machined
 /systemd-modules-load
 /systemd-multi-seat-x
+/systemd-networkd
 /systemd-notify
 /systemd-nspawn
 /systemd-quotacheck
@@ -131,6 +132,7 @@
 /test-loopback
 /test-mmap-cache
 /test-namespace
+/test-network
 /test-ns
 /test-path-util
 /test-prioq
diff --git a/Makefile.am b/Makefile.am
index e29439d..324c077 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -173,6 +173,7 @@ AM_CPPFLAGS = \
 	-DKEXEC=\"$(KEXEC)\" \
 	-I $(top_srcdir)/src \
 	-I $(top_srcdir)/src/shared \
+	-I $(top_srcdir)/src/network \
 	-I $(top_srcdir)/src/login \
 	-I $(top_srcdir)/src/journal \
 	-I $(top_srcdir)/src/systemd \
@@ -3765,6 +3766,59 @@ EXTRA_DIST += \
 endif
 
 # ------------------------------------------------------------------------------
+rootlibexec_PROGRAMS += \
+	systemd-networkd
+
+systemd_networkd_SOURCES = \
+	src/network/networkd.h \
+	src/network/networkd.c \
+	src/network/networkd-link.c \
+	src/network/networkd-network.c \
+	src/network/networkd-address.c \
+	src/network/networkd-manager.c
+
+nodist_systemd_networkd_SOURCES = \
+	src/network/networkd-gperf.c
+
+systemd_networkd_LDADD = \
+	libudev.la \
+	libudev-private.la \
+	libsystemd-shared.la \
+	libsystemd-bus.la \
+	libsystemd-rtnl.la
+
+nodist_systemunit_DATA += \
+	units/systemd-networkd.service
+
+MULTI_USER_TARGET_WANTS += \
+	systemd-networkd.service
+
+test_network_SOURCES = \
+	src/network/test-network.c \
+	src/network/networkd.h \
+	src/network/networkd-link.c \
+	src/network/networkd-network.c \
+	src/network/networkd-address.c \
+	src/network/networkd-manager.c \
+	src/network/networkd-gperf.c
+
+test_network_LDADD = \
+	libsystemd-shared.la \
+	libudev-private.la \
+	libsystemd-bus.la \
+	libsystemd-rtnl.la \
+	libudev.la
+
+tests += test-network
+
+EXTRA_DIST += \
+	src/network/networkd-gperf.gperf \
+	units/systemd-networkd.service.in
+
+CLEANFILES += \
+	src/network/networkd-gperf.c
+
+# ------------------------------------------------------------------------------
 if ENABLE_LOGIND
 systemd_logind_SOURCES = \
 	src/login/logind.c \
diff --git a/src/network/.gitignore b/src/network/.gitignore
new file mode 100644
index 0000000..b2a8c1a
--- /dev/null
+++ b/src/network/.gitignore
@@ -0,0 +1 @@
+/networkd-gperf.c
diff --git a/src/network/Makefile b/src/network/Makefile
new file mode 120000
index 0000000..d0b0e8e
--- /dev/null
+++ b/src/network/Makefile
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
new file mode 100644
index 0000000..b2502c9
--- /dev/null
+++ b/src/network/networkd-address.c
@@ -0,0 +1,252 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Tom Gundersen <teg at jklm.no>
+
+  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 <net/if.h>
+
+#include "networkd.h"
+
+#include "utf8.h"
+#include "util.h"
+#include "conf-parser.h"
+
+int address_new(Address **ret) {
+        _cleanup_address_free_ Address *address = NULL;
+
+        address = new0(Address, 1);
+        if (!address)
+                return -ENOMEM;
+
+        *ret = address;
+        address = NULL;
+
+        return 0;
+}
+
+void address_free(Address *address) {
+        if (!address)
+                return;
+
+        free(address->label);
+        free(address);
+}
+
+int address_configure(Manager *manager, Address *address, Link *link) {
+        _cleanup_sd_rtnl_message_unref_ sd_rtnl_message *req = NULL;
+        int r;
+
+        r = sd_rtnl_message_addr_new(RTM_NEWADDR, link->ifindex,
+                        address->family, address->prefixlen,
+                        IFA_F_PERMANENT, RT_SCOPE_UNIVERSE, &req);
+        if (r < 0) {
+                log_error("Could not allocate RTM_NEWADDR message: %s",
+                          strerror(-r));
+                return r;
+        }
+
+        r = sd_rtnl_message_append(req, IFA_LOCAL, &address->in_addr);
+        if (r < 0) {
+                log_error("Could not append IFA_LOCAL attribute: %s",
+                          strerror(-r));
+                return r;
+        }
+
+        if (address->family == AF_INET) {
+                struct in_addr broadcast;
+
+                broadcast.s_addr = address->in_addr.in.s_addr |
+                                   htonl(0xfffffffflu >> address->prefixlen);
+
+                r = sd_rtnl_message_append(req, IFA_BROADCAST, &broadcast);
+                if (r < 0) {
+                        log_error("Could not append IFA_BROADCAST attribute: %s",
+                                  strerror(-r));
+                        return r;
+                }
+        }
+
+        if (address->label) {
+                r = sd_rtnl_message_append(req, IFA_LABEL, address->label);
+                if (r < 0) {
+                        log_error("Could not append IFA_LABEL attribute: %s",
+                                  strerror(-r));
+                        return r;
+                }
+        }
+
+        r = sd_rtnl_send_with_reply_and_block(manager->rtnl, req, 0, NULL);
+        if (r < 0) {
+                log_error("Could not configure address: %s", strerror(-r));
+                return r != -EEXIST ? r : 0;
+        }
+
+        log_info("Configured interface address");
+
+        return 0;
+}
+
+int config_parse_gateway(const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+        Address **address = data;
+        _cleanup_address_free_ Address *n = NULL;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        r = address_new(&n);
+        if (r < 0)
+                return r;
+
+        /* IPv4 */
+        r = inet_pton(AF_INET, rvalue, &n->in_addr.in);
+        if (r > 0) {
+                /* successfully parsed IPv4 address */
+                n->family = AF_INET;
+        } else  if (r < 0) {
+                log_error("could not parse gateway address '%s'", rvalue);
+                return -errno;
+        } else {
+                /* not an IPv4 address, so let's try IPv6 */
+                r = inet_pton(AF_INET6, rvalue, &n->in_addr.in6);
+                if (r > 0) {
+                        /* successfully parsed IPv6 address */
+                        n->family = AF_INET6;
+                } else if (r < 0) {
+                        log_error("could not parse gateway address '%s'", rvalue);
+                        return -errno;
+                } else {
+                        log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+                                   "Gateway address is invalid, "
+                                   "ignoring assignment: %s", rvalue);
+                        return 0;
+                }
+        }
+
+        address_free(*address);
+        *address = n;
+        n = NULL;
+
+        return 0;
+}
+
+int config_parse_inaddr(const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+        Address **addrs = data;
+        _cleanup_address_free_ Address *n = NULL;
+        _cleanup_free_ char *address = NULL;
+        const char *s, *e;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        r = address_new(&n);
+        if (r < 0)
+                return r;
+
+        /* Address=label at address/prefixlen */
+
+        /* find the _last_ '@' in case the label contains @'s */
+        s = strrchr(rvalue, '@');
+        if (s) {
+                n->label = strndup(rvalue, s - rvalue);
+                if (!n->label)
+                        return log_oom();
+                if (!ascii_is_valid(n->label) || strlen(n->label) >= IFNAMSIZ) {
+                        log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+                                   "Interface name is not ASCII clean or is too long, "
+                                   "ignoring assignment: %s", n->label);
+                        return 0;
+                }
+                s++;
+        } else
+                s = rvalue;
+
+        /* find the _last_ '/' in case the label contains /'s */
+        e = strrchr(rvalue, '/');
+        if (e) {
+                unsigned i;
+                r = safe_atou(e + 1, &i);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+                                   "Interface prefix length is invalid, "
+                                   "ignoring assignment: %s", e + 1);
+                        return 0;
+                }
+
+                n->prefixlen = (unsigned char) i;
+                address = strndup(s, e - s);
+                if (!address)
+                        return log_oom();
+        } else {
+                address = strdup(s);
+                if (!address)
+                        return log_oom();
+        }
+
+        /* IPv4 */
+        r = inet_pton(AF_INET, address, &n->in_addr.in);
+        if (r > 0) {
+                /* successfully parsed IPv4 address */
+                n->family = AF_INET;
+        } else  if (r < 0) {
+                log_error("could not parse IP address '%s'", address);
+                return -errno;
+        } else {
+                /* not an IPv4 address, so let's try IPv6 */
+                r = inet_pton(AF_INET6, address, &n->in_addr.in6);
+                if (r > 0) {
+                        /* successfully parsed IPv6 address */
+                        n->family = AF_INET6;
+                } else if (r < 0) {
+                        log_error("could not parse IP address '%s'", address);
+                        return -errno;
+                } else {
+                        log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+                                   "Interface address is invalid, "
+                                   "ignoring assignment: %s", address);
+                        return 0;
+                }
+        }
+
+        LIST_PREPEND(addresses, *addrs, n);
+        n = NULL;
+
+        return 0;
+}
diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf
new file mode 100644
index 0000000..2cb371b
--- /dev/null
+++ b/src/network/networkd-gperf.gperf
@@ -0,0 +1,25 @@
+%{
+#include <stddef.h>
+#include "conf-parser.h"
+#include "networkd.h"
+#include "net-util.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name network_gperf_hash
+%define lookup-function-name network_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+Match.MACAddress,                   config_parse_hwaddr,        0, offsetof(Network, match_mac)
+Match.Path,                         config_parse_string,        0, offsetof(Network, match_path)
+Match.Driver,                       config_parse_string,        0, offsetof(Network, match_driver)
+Match.Type,                         config_parse_string,        0, offsetof(Network, match_type)
+Match.Name,                         config_parse_ifname,        0, offsetof(Network, match_name)
+Network.Description,                config_parse_string,        0, offsetof(Network, description)
+IP.Address,                         config_parse_inaddr,        0, offsetof(Network, addresses)
+IP.Gateway,                         config_parse_gateway,       0, offsetof(Network, gateway)
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
new file mode 100644
index 0000000..2e8216e
--- /dev/null
+++ b/src/network/networkd-link.c
@@ -0,0 +1,116 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Tom Gundersen <teg at jklm.no>
+
+  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 <netinet/ether.h>
+#include <linux/if.h>
+
+#include "networkd.h"
+#include "libudev-private.h"
+#include "util.h"
+
+int link_new(struct udev_device *device, Link **ret) {
+        _cleanup_link_free_ Link *link = NULL;
+        int ifindex;
+
+        assert(device);
+        assert(ret);
+
+        link = new0(Link, 1);
+        if (!link)
+                return -ENOMEM;
+
+        ifindex = udev_device_get_ifindex(device);
+        if (ifindex <= 0)
+                return -EINVAL;
+
+        link->ifindex = ifindex;
+
+        *ret = link;
+        link = NULL;
+
+        return 0;
+}
+
+void link_free(Link *link) {
+        if (!link)
+                return;
+
+        network_free(link->network);
+
+        free(link);
+}
+
+int link_add(Manager *m, struct udev_device *device) {
+        Link *link;
+        Network *network;
+        int r, ifindex;
+
+        assert(m);
+        assert(device);
+
+        ifindex = udev_device_get_ifindex(device);
+        link = hashmap_get(m->links, &ifindex);
+        if (link)
+                return 0;
+
+        r = link_new(device, &link);
+        if (r < 0) {
+                log_error("could not create link: %s", strerror(-r));
+                return r;
+        }
+
+        r = hashmap_put(m->links, &link->ifindex, link);
+        if (r < 0) {
+                log_error("could not add link to hashmap: %s", strerror(-r));
+                return r;
+        }
+
+        r = network_get(m, device, &network);
+        if (r < 0)
+                return r == -ENOENT ? 0 : r;
+
+        r = network_apply(m, network, link);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+int link_up(Manager *manager, Link *link) {
+        _cleanup_sd_rtnl_message_unref_ sd_rtnl_message *req = NULL;
+        int r;
+
+        r = sd_rtnl_message_link_new(RTM_NEWLINK, link->ifindex, 0, IFF_UP, &req);
+        if (r < 0) {
+                log_error("Could not allocate RTM_NEWLINK message");
+                return r;
+        }
+
+        r = sd_rtnl_send_with_reply_and_block(manager->rtnl, req, 0, NULL);
+        if (r < 0) {
+                log_error("Could not UP link: %s", strerror(-r));
+                return r;
+        }
+
+        log_info("Link is UP");
+
+        return 0;
+}
diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c
new file mode 100644
index 0000000..3bf01c0
--- /dev/null
+++ b/src/network/networkd-manager.c
@@ -0,0 +1,207 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Tom Gundersen <teg at jklm.no>
+
+  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 "path-util.h"
+#include "networkd.h"
+#include "libudev-private.h"
+
+int manager_new(Manager **ret) {
+        _cleanup_manager_free_ Manager *m = NULL;
+        int r;
+
+        m = new0(Manager, 1);
+        if (!m)
+                return -ENOMEM;
+
+        r = sd_event_new(&m->event);
+        if (r < 0)
+                return r;
+
+        r = sd_rtnl_open(0, &m->rtnl);
+        if (r < 0)
+                return r;
+
+        m->udev = udev_new();
+        if (!m->udev)
+                return -ENOMEM;
+
+        m->udev_monitor = udev_monitor_new_from_netlink(m->udev, "udev");
+        if (!m->udev_monitor)
+                return -ENOMEM;
+
+        m->links = hashmap_new(uint64_hash_func, uint64_compare_func);
+        if (!m->links)
+                return -ENOMEM;
+
+        LIST_HEAD_INIT(m->networks);
+
+        m->network_dirs = strv_new("/etc/systemd/network/",
+                                   "/run/systemd/network/",
+                                   "/usr/lib/systemd/network",
+#ifdef HAVE_SPLIT_USER
+                                   "/lib/systemd/network",
+#endif
+                                   NULL);
+        if (!m->network_dirs)
+                return -ENOMEM;
+
+        if (!path_strv_canonicalize_uniq(m->network_dirs))
+                return -ENOMEM;
+
+        *ret = m;
+        m = NULL;
+
+        return 0;
+}
+
+void manager_free(Manager *m) {
+        udev_monitor_unref(m->udev_monitor);
+        udev_unref(m->udev);
+        sd_event_source_unref(m->udev_event_source);
+        sd_event_unref(m->event);
+        hashmap_free(m->links);
+        strv_free(m->network_dirs);
+        sd_rtnl_unref(m->rtnl);
+
+        free(m);
+}
+
+static int manager_process_link(Manager *m, struct udev_device *device) {
+        Link *link;
+        int r;
+
+        if (streq_ptr(udev_device_get_action(device), "remove")) {
+                link = hashmap_get(m->links, udev_device_get_syspath(device));
+                if (!link)
+                        return 0;
+
+                link_free(link);
+        } else {
+                r = link_add(m, device);
+                if (r < 0) {
+                        log_error("Could not handle link %s: %s",
+                                  udev_device_get_syspath(device),
+                                  strerror(-r));
+                }
+        }
+
+        return 0;
+}
+
+int manager_udev_enumerate_links(Manager *m) {
+        struct udev_list_entry *item = NULL, *first = NULL;
+        struct udev_enumerate *e;
+        int r;
+
+        assert(m);
+
+        e = udev_enumerate_new(m->udev);
+        if (!e) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        r = udev_enumerate_add_match_subsystem(e, "net");
+        if (r < 0)
+                goto finish;
+
+        r = udev_enumerate_add_match_tag(e, "systemd-networkd");
+        if (r < 0)
+                goto finish;
+
+        r = udev_enumerate_scan_devices(e);
+        if (r < 0)
+                goto finish;
+
+        first = udev_enumerate_get_list_entry(e);
+        udev_list_entry_foreach(item, first) {
+                struct udev_device *d;
+                int k;
+
+                d = udev_device_new_from_syspath(m->udev, udev_list_entry_get_name(item));
+                if (!d) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                k = manager_process_link(m, d);
+                udev_device_unref(d);
+
+                if (k < 0)
+                        r = k;
+        }
+
+finish:
+        if (e)
+                udev_enumerate_unref(e);
+
+        return r;
+}
+
+static int manager_dispatch_link_udev(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
+        Manager *m = userdata;
+        struct udev_monitor *monitor = m->udev_monitor;
+        struct udev_device *device;
+        int r;
+
+        device = udev_monitor_receive_device(monitor);
+        if (!device)
+                return -ENOMEM;
+
+        r = manager_process_link(m, device);
+        if (r < 0)
+                return r;
+
+        udev_device_unref(device);
+
+        return 0;
+}
+
+int manager_udev_listen(Manager *m) {
+        int r;
+
+        r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_monitor, "net", NULL);
+        if (r < 0) {
+                log_error("Could not add udev monitor filter: %s", strerror(-r));
+                return r;
+        }
+
+        r = udev_monitor_filter_add_match_tag(m->udev_monitor, "systemd-networkd");
+        if (r < 0) {
+                log_error("Could not add udev monitor filter: %s", strerror(-r));
+                return r;
+        }
+
+        r = udev_monitor_enable_receiving(m->udev_monitor);
+        if (r < 0) {
+                log_error("Could not enable udev monitor");
+                return r;
+        }
+
+        sd_event_add_io(m->event,
+                        udev_monitor_get_fd(m->udev_monitor),
+                        EPOLLIN,
+                        manager_dispatch_link_udev,
+                        (void *)m,
+                        &m->udev_event_source);
+
+        return 0;
+}
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
new file mode 100644
index 0000000..38411e4
--- /dev/null
+++ b/src/network/networkd-network.c
@@ -0,0 +1,217 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Tom Gundersen <teg at jklm.no>
+
+  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 "networkd.h"
+#include "net-util.h"
+#include "path-util.h"
+#include "conf-files.h"
+#include "conf-parser.h"
+
+static int network_load_one(Manager *manager, const char *filename) {
+        _cleanup_network_free_ Network *network = NULL;
+        FILE *file;
+        int r;
+
+        file = fopen(filename, "re");
+        if (!file) {
+                if (errno == ENOENT)
+                        return 0;
+                else
+                        return errno;
+        }
+
+        network = new0(Network, 1);
+        if (!network)
+                return log_oom();
+
+        LIST_HEAD_INIT(network->addresses);
+
+        r = config_parse(NULL, filename, file, "Match\0Network\0IP\0", config_item_perf_lookup,
+                        (void*) network_gperf_lookup, false, false, network);
+        if (r < 0) {
+                log_warning("Colud not parse config file %s: %s", filename, strerror(-r));
+                return r;
+        } else
+                log_debug("Parsed configuration file %s", filename);
+
+        network->filename = strdup(filename);
+
+        LIST_PREPEND(networks, manager->networks, network);
+        network = NULL;
+
+        return 0;
+}
+
+static void networks_free(Manager *manager) {
+        Network *network, *network_next;
+
+        if (!manager)
+                return;
+
+        LIST_FOREACH_SAFE(networks, network, network_next, manager->networks) {
+                network_free(network);
+        }
+}
+
+int network_load(Manager *manager) {
+        char **files, **f;
+        int r;
+
+        assert(manager);
+
+        networks_free(manager);
+
+        /* update timestamp */
+        paths_check_timestamp(manager->network_dirs, &manager->network_dirs_ts_usec, true);
+
+        r = conf_files_list_strv(&files, ".network", NULL, (const char **)manager->network_dirs);
+        if (r < 0) {
+                log_error("failed to enumerate network files: %s", strerror(-r));
+                return r;
+        }
+
+        STRV_FOREACH_BACKWARDS(f, files) {
+                r = network_load_one(manager, *f);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+bool network_should_reload(Manager *manager) {
+        return paths_check_timestamp(manager->network_dirs, &manager->network_dirs_ts_usec, false);
+}
+
+void network_free(Network *network) {
+        if (!network)
+                return;
+
+        free(network->filename);
+
+        free(network->match_mac);
+        free(network->match_path);
+        free(network->match_driver);
+        free(network->match_type);
+        free(network->match_name);
+
+        free(network);
+}
+
+int network_get(Manager *manager, struct udev_device *device, Network **ret) {
+        Network *network;
+
+        assert(manager);
+        assert(device);
+        assert(ret);
+
+        if (network_should_reload(manager))
+                network_load(manager);
+
+        LIST_FOREACH(networks, network, manager->networks) {
+                if (net_match_config(network->match_mac, network->match_path,
+                                        network->match_driver, network->match_type,
+                                        network->match_name,
+                                        udev_device_get_sysattr_value(device, "address"),
+                                        udev_device_get_property_value(device, "ID_PATH"),
+                                        udev_device_get_driver(device),
+                                        udev_device_get_devtype(device),
+                                        udev_device_get_sysname(device))) {
+                        log_debug("Network file %s applies to link %s",
+                                        network->filename,
+                                        udev_device_get_sysname(device));
+                        *ret = network;
+                        return 0;
+                }
+        }
+
+        *ret = NULL;
+
+        return -ENOENT;
+}
+
+static int gateway_configure(Manager *manager, Link *link, Address *gateway) {
+        _cleanup_sd_rtnl_message_unref_ sd_rtnl_message *req = NULL;
+        int r;
+
+        assert(manager);
+        assert(link);
+        assert(link->ifindex > 0);
+        assert(gateway->family == AF_INET || gateway->family == AF_INET6);
+
+        r = sd_rtnl_message_route_new(RTM_NEWROUTE, gateway->family, 0, 0, 0,
+                                      RT_TABLE_MAIN, RT_SCOPE_UNIVERSE, RTPROT_BOOT,
+                                      RTN_UNICAST, 0, &req);
+        if (r < 0) {
+                log_error("Could not create RTM_NEWROUTE message: %s", strerror(-r));
+                return r;
+        }
+
+        r = sd_rtnl_message_append(req, RTA_GATEWAY, &gateway->in_addr);
+        if (r < 0) {
+                log_error("Could not append RTA_GATEWAY attribute: %s", strerror(-r));
+                return r;
+        }
+
+        r = sd_rtnl_message_append(req, RTA_OIF, &link->ifindex);
+        if (r < 0) {
+                log_error("Could not append RTA_OIF attribute: %s", strerror(-r));
+                return r;
+        }
+
+        r = sd_rtnl_send_with_reply_and_block(manager->rtnl, req, 0, NULL);
+        if (r < 0) {
+                log_error("Colud not configure gateway: %s", strerror(-r));
+                return r;
+        }
+
+        log_info("added default gateway via ifindex %u", (unsigned) link->ifindex);
+
+        return 0;
+}
+
+int network_apply(Manager *manager, Network *network, Link *link) {
+        Address *address;
+        int r;
+
+        log_info("Network '%s' being applied to link '%u'",
+                        network->description, (unsigned) link->ifindex);
+
+        link->network = network;
+
+        LIST_FOREACH(addresses, address, network->addresses) {
+                r = address_configure(manager, address, link);
+                if (r < 0)
+                        return r;
+        }
+
+        r = link_up(manager, link);
+        if (r < 0)
+                return r;
+
+        if (network->gateway) {
+                r = gateway_configure(manager, link, network->gateway);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
diff --git a/src/network/networkd.c b/src/network/networkd.c
new file mode 100644
index 0000000..ed93ffb
--- /dev/null
+++ b/src/network/networkd.c
@@ -0,0 +1,58 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Tom Gundersen <teg at jklm.no>
+
+  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 "sd-event.h"
+
+#include "networkd.h"
+
+int main(int argc, char *argv[]) {
+        _cleanup_manager_free_ Manager *m;
+        int r;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        if (argc != 1) {
+                log_error("This program takes no arguments.");
+                return EXIT_FAILURE;
+        }
+
+        r = manager_new(&m);
+        if (r < 0)
+                return EXIT_FAILURE;
+
+        r = manager_udev_listen(m);
+        if (r < 0)
+                return EXIT_FAILURE;
+
+        r = manager_udev_enumerate_links(m);
+        if (r < 0)
+                return EXIT_FAILURE;
+
+        r = sd_event_loop(m->event);
+        if (r < 0)
+                return EXIT_FAILURE;
+
+        return  EXIT_SUCCESS;
+}
diff --git a/src/network/networkd.h b/src/network/networkd.h
new file mode 100644
index 0000000..1cb7c70
--- /dev/null
+++ b/src/network/networkd.h
@@ -0,0 +1,141 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Tom Gundersen <teg at jklm.no>
+
+  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/>.
+***/
+
+#pragma once
+
+#include <arpa/inet.h>
+
+#include "sd-event.h"
+#include "sd-rtnl.h"
+#include "udev.h"
+
+#include "hashmap.h"
+#include "list.h"
+
+typedef struct Network Network;
+typedef struct Link Link;
+typedef struct Address Address;
+typedef struct Manager Manager;
+
+struct Network {
+        char *filename;
+
+        struct ether_addr *match_mac;
+        char *match_path;
+        char *match_driver;
+        char *match_type;
+        char *match_name;
+
+        char *description;
+
+        LIST_HEAD(Address, addresses);
+
+        Address *gateway;
+
+        LIST_FIELDS(Network, networks);
+};
+
+struct Address {
+        unsigned char family;
+        unsigned char prefixlen;
+        char *label;
+
+        union {
+                struct in_addr in;
+                struct in6_addr in6;
+        } in_addr;
+
+        LIST_FIELDS(Address, addresses);
+};
+
+struct Link {
+        uint64_t ifindex;
+
+        unsigned flags;
+
+        Network *network;
+};
+
+struct Manager {
+        sd_rtnl *rtnl;
+        sd_event *event;
+        struct udev *udev;
+        struct udev_monitor *udev_monitor;
+        sd_event_source *udev_event_source;
+
+        Hashmap *links;
+        LIST_HEAD(Network, networks);
+
+        char **network_dirs;
+        usec_t network_dirs_ts_usec;
+};
+
+/* Manager */
+
+int manager_new(Manager **ret);
+void manager_free(Manager *m);
+
+int manager_udev_enumerate_links(Manager *m);
+int manager_udev_listen(Manager *m);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
+#define _cleanup_manager_free_ _cleanup_(manager_freep)
+
+/* Network */
+
+int network_load(Manager *manager);
+bool network_should_reload(Manager *manager);
+
+void network_free(Network *network);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Network*, network_free);
+#define _cleanup_network_free_ _cleanup_(network_freep)
+
+int network_get(Manager *manager, struct udev_device *device, Network **ret);
+int network_apply(Manager *manager, Network *network, Link *link);
+
+const struct ConfigPerfItem* network_gperf_lookup(const char *key, unsigned length);
+
+/* Address */
+int address_new(Address **ret);
+void address_free(Address *address);
+int address_configure(Manager *manager, Address *address, Link *link);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Address*, address_free);
+#define _cleanup_address_free_ _cleanup_(address_freep)
+
+int config_parse_gateway(const char *unit, const char *filename, unsigned line,
+                        const char *section, const char *lvalue, int ltype,
+                        const char *rvalue, void *data, void *userdata);
+
+int config_parse_inaddr(const char *unit, const char *filename, unsigned line,
+                        const char *section, const char *lvalue, int ltype,
+                        const char *rvalue, void *data, void *userdata);
+
+/* Link */
+
+int link_new(struct udev_device *device, Link **ret);
+void link_free(Link *link);
+int link_add(Manager *manager, struct udev_device *device);
+int link_up(Manager *manager, Link *link);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free);
+#define _cleanup_link_free_ _cleanup_(link_freep)
diff --git a/src/network/test-network.c b/src/network/test-network.c
new file mode 100644
index 0000000..ca98981
--- /dev/null
+++ b/src/network/test-network.c
@@ -0,0 +1,81 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Tom Gundersen <teg at jklm.no>
+
+  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 "networkd.h"
+
+static void test_cleanups(struct udev_device *loopback) {
+        _cleanup_manager_free_ Manager *manager = NULL;
+        _cleanup_link_free_ Link *link = NULL;
+
+        manager_new(&manager);
+        link_new(loopback, &link);
+}
+
+static void test_link(struct udev_device *loopback) {
+        _cleanup_link_free_ Link *link = NULL;
+
+        assert(link_new(loopback, &link) >= 0);
+        assert(link);
+
+        link_free(link);
+        link = NULL;
+}
+
+static void test_network_load(Manager *manager) {
+        assert(network_should_reload(manager) == true);
+        assert(network_load(manager) >= 0);
+        assert(network_should_reload(manager) == false);
+}
+
+static void test_network_get(Manager *manager, struct udev_device *loopback) {
+        Network *network;
+
+        /* let's assume that the test machine does not have a .network file
+           that applies to the loopback device... */
+        assert(network_get(manager, loopback, &network) == -ENOENT);
+        assert(!network);
+}
+
+int main(void) {
+        _cleanup_manager_free_ Manager *manager = NULL;
+        struct udev *udev;
+        struct udev_device *loopback;
+
+        assert(manager_new(&manager) >= 0);
+
+        test_network_load(manager);
+
+        udev = udev_new();
+        assert(udev);
+
+        loopback = udev_device_new_from_syspath(udev, "/sys/class/net/lo");
+        assert(loopback);
+        assert(udev_device_get_ifindex(loopback) == 1);
+
+        test_network_get(manager, loopback);
+
+        test_link(loopback);
+
+        test_cleanups(loopback);
+
+        assert(manager_udev_enumerate_links(manager) >= 0);
+        assert(manager_udev_listen(manager) >= 0);
+}
diff --git a/units/.gitignore b/units/.gitignore
index 5fd9cca..c5ac8fe 100644
--- a/units/.gitignore
+++ b/units/.gitignore
@@ -60,6 +60,7 @@
 /initrd-parse-etc.service
 /initrd-switch-root.service
 /initrd-udevadm-cleanup-db.service
+/systemd-networkd.service
 /systemd-nspawn at .service
 /systemd-machined.service
 /kmod-static-nodes.service
diff --git a/units/systemd-networkd.service.in b/units/systemd-networkd.service.in
new file mode 100644
index 0000000..52c04ed
--- /dev/null
+++ b/units/systemd-networkd.service.in
@@ -0,0 +1,19 @@
+#  This file is part of systemd.
+#
+#  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.
+
+[Unit]
+Description=Network Service
+Documentation=man:systemd-networkd.service(8)
+DefaultDependencies=no
+After=sysinit.target
+Before=network.target
+Wants=network.target
+
+[Service]
+Restart=always
+RestartSec=0
+ExecStart=@rootlibexecdir@/systemd-networkd
-- 
1.8.4.2



More information about the systemd-devel mailing list