chiark / gitweb /
networkd: link - split out ipv4ll handling
[elogind.git] / src / network / networkd-link.c
index 94683a54036b729eecfaca95986051a3a3e7aeb1..3bef1a57a6ca535051c8b648c5fa08140ffd4cc2 100644 (file)
@@ -23,7 +23,7 @@
 #include <linux/if.h>
 #include <unistd.h>
 
-#include "networkd.h"
+#include "networkd-link.h"
 #include "networkd-netdev.h"
 #include "libudev-private.h"
 #include "udev-util.h"
 #include "network-internal.h"
 #include "conf-parser.h"
 
-#include "network-util.h"
 #include "dhcp-lease-internal.h"
 
-static int ipv4ll_address_update(Link *link, bool deprecate);
-static bool ipv4ll_is_bound(sd_ipv4ll *ll);
-
 static int link_new(Manager *manager, sd_rtnl_message *message, Link **ret) {
         _cleanup_link_unref_ Link *link = NULL;
         uint16_t type;
-        char *ifname;
+        const char *ifname;
         int r, ifindex;
 
         assert(manager);
-        assert(manager->links);
         assert(message);
         assert(ret);
 
@@ -82,17 +77,19 @@ static int link_new(Manager *manager, sd_rtnl_message *message, Link **ret) {
         if (r < 0)
                 log_debug_link(link, "MAC address not found for new device, continuing without");
 
-        r = asprintf(&link->state_file, "/run/systemd/netif/links/%"PRIu64,
-                     link->ifindex);
+        r = asprintf(&link->state_file, "/run/systemd/netif/links/%d", link->ifindex);
         if (r < 0)
                 return -ENOMEM;
 
-        r = asprintf(&link->lease_file, "/run/systemd/netif/leases/%"PRIu64,
-                     link->ifindex);
+        r = asprintf(&link->lease_file, "/run/systemd/netif/leases/%d", link->ifindex);
         if (r < 0)
                 return -ENOMEM;
 
-        r = hashmap_put(manager->links, &link->ifindex, link);
+        r = hashmap_ensure_allocated(&manager->links, NULL, NULL);
+        if (r < 0)
+                return r;
+
+        r = hashmap_put(manager->links, INT_TO_PTR(link->ifindex), link);
         if (r < 0)
                 return r;
 
@@ -129,7 +126,7 @@ static void link_free(Link *link) {
         sd_icmp6_nd_unref(link->icmp6_router_discovery);
 
         if (link->manager)
-                hashmap_remove(link->manager->links, &link->ifindex);
+                hashmap_remove(link->manager->links, INT_TO_PTR(link->ifindex));
 
         free(link->ifname);
 
@@ -157,15 +154,12 @@ Link *link_ref(Link *link) {
 
 int link_get(Manager *m, int ifindex, Link **ret) {
         Link *link;
-        uint64_t ifindex_64;
 
         assert(m);
-        assert(m->links);
         assert(ifindex);
         assert(ret);
 
-        ifindex_64 = ifindex;
-        link = hashmap_get(m->links, &ifindex_64);
+        link = hashmap_get(m->links, INT_TO_PTR(ifindex));
         if (!link)
                 return -ENODEV;
 
@@ -207,9 +201,7 @@ static int link_stop_clients(Link *link) {
         if (!link->network)
                 return 0;
 
-        if (IN_SET(link->network->dhcp, DHCP_SUPPORT_BOTH, DHCP_SUPPORT_V6)) {
-                assert(link->dhcp_client);
-
+        if (link->dhcp_client) {
                 k = sd_dhcp_client_stop(link->dhcp_client);
                 if (k < 0) {
                         log_warning_link(link, "Could not stop DHCPv4 client: %s", strerror(-r));
@@ -217,9 +209,7 @@ static int link_stop_clients(Link *link) {
                 }
         }
 
-        if (link->network->ipv4ll) {
-                assert(link->ipv4ll);
-
+        if (link->ipv4ll) {
                 k = sd_ipv4ll_stop(link->ipv4ll);
                 if (k < 0) {
                         log_warning_link(link, "Could not stop IPv4 link-local: %s", strerror(-r));
@@ -227,9 +217,7 @@ static int link_stop_clients(Link *link) {
                 }
         }
 
-        if (link->network->dhcp_server) {
-                assert(link->dhcp_server);
-
+        if (link->dhcp_server) {
                 k = sd_dhcp_server_stop(link->dhcp_server);
                 if (k < 0) {
                         log_warning_link(link, "Could not stop DHCPv4 server: %s", strerror(-r));
@@ -237,8 +225,7 @@ static int link_stop_clients(Link *link) {
                 }
         }
 
-        if (IN_SET(link->network->dhcp, DHCP_SUPPORT_BOTH, DHCP_SUPPORT_V6)) {
-                assert(link->icmp6_router_discovery);
+        if(link->icmp6_router_discovery) {
 
                 if (link->dhcp6_client) {
                         k = sd_dhcp6_client_stop(link->dhcp6_client);
@@ -258,7 +245,7 @@ static int link_stop_clients(Link *link) {
         return r;
 }
 
-static void link_enter_failed(Link *link) {
+void link_enter_failed(Link *link) {
         assert(link);
 
         if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
@@ -273,6 +260,17 @@ static void link_enter_failed(Link *link) {
         link_save(link);
 }
 
+void link_client_handler(Link *link) {
+        assert(link);
+
+        if (link->ipv4ll_address && link->ipv4ll_route)
+                log_debug_link(link, "IPv4LL configured");
+        else
+                log_debug_link(link, "IPv4LL not configured");
+
+        return;
+}
+
 static Address* link_find_dhcp_server_address(Link *link) {
         Address *address;
 
@@ -285,7 +283,7 @@ static Address* link_find_dhcp_server_address(Link *link) {
                 if (address->family != AF_INET)
                         continue;
 
-                if (in_addr_null(address->family, &address->in_addr))
+                if (in_addr_is_null(address->family, &address->in_addr))
                         continue;
 
                 return address;
@@ -323,7 +321,7 @@ static int link_enter_configured(Link *link) {
 
                 log_debug_link(link, "offering DHCPv4 leases");
 
-                r = sd_dhcp_server_set_address(link->dhcp_server, &address->in_addr.in);
+                r = sd_dhcp_server_set_address(link->dhcp_server, &address->in_addr.in, address->prefixlen);
                 if (r < 0)
                         return r;
 
@@ -415,7 +413,7 @@ static int link_set_dhcp_routes(Link *link) {
         for (i = 0; i < n; i++) {
                 _cleanup_route_free_ Route *route = NULL;
 
-                r = route_new_dynamic(&route);
+                r = route_new_dynamic(&route, RTPROT_DHCP);
                 if (r < 0) {
                         log_error_link(link, "Could not allocate route: %s",
                                        strerror(-r));
@@ -451,12 +449,6 @@ static int link_enter_set_routes(Link *link) {
 
         link->state = LINK_STATE_SETTING_ROUTES;
 
-        if (!link->network->static_routes && !link->dhcp_lease &&
-            (!link->ipv4ll || ipv4ll_is_bound(link->ipv4ll) == false))
-                return link_enter_configured(link);
-
-        log_debug_link(link, "setting routes");
-
         LIST_FOREACH(routes, rt, link->network->static_routes) {
                 r = route_configure(rt, link, &route_handler);
                 if (r < 0) {
@@ -469,41 +461,6 @@ static int link_enter_set_routes(Link *link) {
                 link->route_messages ++;
         }
 
-        if (link->ipv4ll && !link->dhcp_lease) {
-                _cleanup_route_free_ Route *route = NULL;
-                struct in_addr addr;
-
-                r = sd_ipv4ll_get_address(link->ipv4ll, &addr);
-                if (r < 0 && r != -ENOENT) {
-                        log_warning_link(link, "IPV4LL error: no address: %s",
-                                        strerror(-r));
-                        return r;
-                }
-
-                if (r != -ENOENT) {
-                        r = route_new_dynamic(&route);
-                        if (r < 0) {
-                                log_error_link(link, "Could not allocate route: %s",
-                                               strerror(-r));
-                                return r;
-                        }
-
-                        route->family = AF_INET;
-                        route->scope = RT_SCOPE_LINK;
-                        route->metrics = IPV4LL_ROUTE_METRIC;
-
-                        r = route_configure(route, link, &route_handler);
-                        if (r < 0) {
-                                log_warning_link(link,
-                                                 "could not set routes: %s", strerror(-r));
-                                link_enter_failed(link);
-                                return r;
-                        }
-
-                        link->route_messages ++;
-                }
-        }
-
         if (link->dhcp_lease) {
                 _cleanup_route_free_ Route *route = NULL;
                 _cleanup_route_free_ Route *route_gw = NULL;
@@ -517,14 +474,14 @@ static int link_enter_set_routes(Link *link) {
                 }
 
                 if (r >= 0) {
-                        r = route_new_dynamic(&route);
+                        r = route_new_dynamic(&route, RTPROT_DHCP);
                         if (r < 0) {
                                 log_error_link(link, "Could not allocate route: %s",
                                                strerror(-r));
                                 return r;
                         }
 
-                        r = route_new_dynamic(&route_gw);
+                        r = route_new_dynamic(&route_gw, RTPROT_DHCP);
                         if (r < 0) {
                                 log_error_link(link, "Could not allocate route: %s",
                                                strerror(-r));
@@ -570,12 +527,13 @@ static int link_enter_set_routes(Link *link) {
 
         if (link->route_messages == 0) {
                 link_enter_configured(link);
-        }
+        } else
+                log_debug_link(link, "setting routes");
 
         return 0;
 }
 
-static int route_drop_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
+int link_route_drop_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
         _cleanup_link_unref_ Link *link = userdata;
         int r;
 
@@ -598,7 +556,7 @@ static int route_drop_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata)
         return 1;
 }
 
-static int link_get_address_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
+int link_get_address_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
         _cleanup_link_unref_ Link *link = userdata;
         int r;
 
@@ -672,12 +630,6 @@ static int link_enter_set_addresses(Link *link) {
 
         link->state = LINK_STATE_SETTING_ADDRESSES;
 
-        if (!link->network->static_addresses && !link->dhcp_lease &&
-                (!link->ipv4ll || ipv4ll_is_bound(link->ipv4ll) == false))
-                return link_enter_set_routes(link);
-
-        log_debug_link(link, "setting addresses");
-
         LIST_FOREACH(addresses, ad, link->network->static_addresses) {
                 r = address_configure(ad, link, &address_handler);
                 if (r < 0) {
@@ -690,42 +642,6 @@ static int link_enter_set_addresses(Link *link) {
                 link->addr_messages ++;
         }
 
-        if (link->ipv4ll && !link->dhcp_lease) {
-                _cleanup_address_free_ Address *ll_addr = NULL;
-                struct in_addr addr;
-
-                r = sd_ipv4ll_get_address(link->ipv4ll, &addr);
-                if (r < 0 && r != -ENOENT) {
-                        log_warning_link(link, "IPV4LL error: no address: %s",
-                                        strerror(-r));
-                        return r;
-                }
-
-                if (r != -ENOENT) {
-                        r = address_new_dynamic(&ll_addr);
-                        if (r < 0) {
-                                log_error_link(link, "Could not allocate address: %s", strerror(-r));
-                                return r;
-                        }
-
-                        ll_addr->family = AF_INET;
-                        ll_addr->in_addr.in = addr;
-                        ll_addr->prefixlen = 16;
-                        ll_addr->broadcast.s_addr = ll_addr->in_addr.in.s_addr | htonl(0xfffffffflu >> ll_addr->prefixlen);
-                        ll_addr->scope = RT_SCOPE_LINK;
-
-                        r = address_configure(ll_addr, link, &address_handler);
-                        if (r < 0) {
-                                log_warning_link(link,
-                                         "could not set addresses: %s", strerror(-r));
-                                link_enter_failed(link);
-                                return r;
-                        }
-
-                        link->addr_messages ++;
-                }
-        }
-
         if (link->dhcp_lease) {
                 _cleanup_address_free_ Address *address = NULL;
                 struct in_addr addr;
@@ -756,7 +672,7 @@ static int link_enter_set_addresses(Link *link) {
                         return r;
                 }
 
-                prefixlen = net_netmask_to_prefixlen(&netmask);
+                prefixlen = in_addr_netmask_to_prefixlen(&netmask);
 
                 r = address_new_dynamic(&address);
                 if (r < 0) {
@@ -785,33 +701,15 @@ static int link_enter_set_addresses(Link *link) {
                 link->addr_messages ++;
         }
 
-        return 0;
-}
-
-static int address_update_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
-        _cleanup_link_unref_ Link *link = userdata;
-        int r;
-
-        assert(m);
-        assert(link);
-        assert(link->ifname);
-
-        if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
-                return 1;
-
-        r = sd_rtnl_message_get_errno(m);
-        if (r < 0 && r != -ENOENT)
-                log_struct_link(LOG_WARNING, link,
-                                "MESSAGE=%-*s: could not update address: %s",
-                                IFNAMSIZ,
-                                link->ifname, strerror(-r),
-                                "ERRNO=%d", -r,
-                                NULL);
+        if (link->addr_messages == 0) {
+                link_enter_set_routes(link);
+        } else
+                log_debug_link(link, "setting addresses");
 
-        return 1;
+        return 0;
 }
 
-static int address_drop_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
+int link_address_drop_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
         _cleanup_link_unref_ Link *link = userdata;
         int r;
 
@@ -845,7 +743,9 @@ static int set_hostname_handler(sd_bus *bus, sd_bus_message *m, void *userdata,
 
         r = sd_bus_message_get_errno(m);
         if (r < 0)
-                log_warning_link(link, "Could not set hostname: %s", strerror(-r));
+                r = -r;
+        if (r > 0)
+                log_warning_link(link, "Could not set hostname: %s", strerror(r));
 
         return 1;
 }
@@ -969,14 +869,14 @@ static int dhcp_lease_lost(Link *link) {
                         for (i = 0; i < n; i++) {
                                 _cleanup_route_free_ Route *route = NULL;
 
-                                r = route_new_dynamic(&route);
+                                r = route_new_dynamic(&route, RTPROT_UNSPEC);
                                 if (r >= 0) {
                                         route->family = AF_INET;
                                         route->in_addr.in = routes[i].gw_addr;
                                         route->dst_addr.in = routes[i].dst_addr;
                                         route->dst_prefixlen = routes[i].dst_prefixlen;
 
-                                        route_drop(route, link, &route_drop_handler);
+                                        route_drop(route, link, &link_route_drop_handler);
                                 }
                         }
                 }
@@ -989,34 +889,34 @@ static int dhcp_lease_lost(Link *link) {
                         _cleanup_route_free_ Route *route_gw = NULL;
                         _cleanup_route_free_ Route *route = NULL;
 
-                        r = route_new_dynamic(&route_gw);
+                        r = route_new_dynamic(&route_gw, RTPROT_UNSPEC);
                         if (r >= 0) {
                                 route_gw->family = AF_INET;
                                 route_gw->dst_addr.in = gateway;
                                 route_gw->dst_prefixlen = 32;
                                 route_gw->scope = RT_SCOPE_LINK;
 
-                                route_drop(route_gw, link, &route_drop_handler);
+                                route_drop(route_gw, link, &link_route_drop_handler);
                         }
 
-                        r = route_new_dynamic(&route);
+                        r = route_new_dynamic(&route, RTPROT_UNSPEC);
                         if (r >= 0) {
                                 route->family = AF_INET;
                                 route->in_addr.in = gateway;
 
-                                route_drop(route, link, &route_drop_handler);
+                                route_drop(route, link, &link_route_drop_handler);
                         }
                 }
 
                 sd_dhcp_lease_get_address(link->dhcp_lease, &addr);
                 sd_dhcp_lease_get_netmask(link->dhcp_lease, &netmask);
-                prefixlen = net_netmask_to_prefixlen(&netmask);
+                prefixlen = in_addr_netmask_to_prefixlen(&netmask);
 
                 address->family = AF_INET;
                 address->in_addr.in = addr;
                 address->prefixlen = prefixlen;
 
-                address_drop(address, link, &address_drop_handler);
+                address_drop(address, link, &link_address_drop_handler);
         }
 
         if (link->network->dhcp_mtu) {
@@ -1100,7 +1000,7 @@ static int dhcp_lease_acquired(sd_dhcp_client *client, Link *link) {
                 return r;
         }
 
-        prefixlen = net_netmask_to_prefixlen(&netmask);
+        prefixlen = in_addr_netmask_to_prefixlen(&netmask);
 
         r = sd_dhcp_lease_get_router(lease, &gateway);
         if (r < 0 && r != -ENOENT) {
@@ -1180,9 +1080,6 @@ static void dhcp_handler(sd_dhcp_client *client, int event, void *userdata) {
                 return;
 
         switch (event) {
-                case DHCP_EVENT_NO_LEASE:
-                        log_debug_link(link, "IP address in use.");
-                        break;
                 case DHCP_EVENT_EXPIRED:
                 case DHCP_EVENT_STOP:
                 case DHCP_EVENT_IP_CHANGE:
@@ -1208,17 +1105,6 @@ static void dhcp_handler(sd_dhcp_client *client, int event, void *userdata) {
                                 }
                         }
 
-                        if (event == DHCP_EVENT_EXPIRED && link->network->ipv4ll) {
-                                if (!sd_ipv4ll_is_running(link->ipv4ll))
-                                        r = sd_ipv4ll_start(link->ipv4ll);
-                                else if (ipv4ll_is_bound(link->ipv4ll))
-                                        r = ipv4ll_address_update(link, false);
-                                if (r < 0) {
-                                        link_enter_failed(link);
-                                        return;
-                                }
-                        }
-
                         break;
                 case DHCP_EVENT_RENEW:
                         r = dhcp_lease_renew(client, link);
@@ -1233,16 +1119,6 @@ static void dhcp_handler(sd_dhcp_client *client, int event, void *userdata) {
                                 link_enter_failed(link);
                                 return;
                         }
-                        if (link->ipv4ll) {
-                                if (ipv4ll_is_bound(link->ipv4ll))
-                                        r = ipv4ll_address_update(link, true);
-                                else
-                                        r = sd_ipv4ll_stop(link->ipv4ll);
-                                if (r < 0) {
-                                        link_enter_failed(link);
-                                        return;
-                                }
-                        }
                         break;
                 default:
                         if (event < 0)
@@ -1255,155 +1131,6 @@ static void dhcp_handler(sd_dhcp_client *client, int event, void *userdata) {
         return;
 }
 
-static int ipv4ll_address_update(Link *link, bool deprecate) {
-        int r;
-        struct in_addr addr;
-
-        assert(link);
-
-        r = sd_ipv4ll_get_address(link->ipv4ll, &addr);
-        if (r >= 0) {
-                _cleanup_address_free_ Address *address = NULL;
-
-                log_debug_link(link, "IPv4 link-local %s %u.%u.%u.%u",
-                               deprecate ? "deprecate" : "approve",
-                               ADDRESS_FMT_VAL(addr));
-
-                r = address_new_dynamic(&address);
-                if (r < 0) {
-                        log_error_link(link, "Could not allocate address: %s", strerror(-r));
-                        return r;
-                }
-
-                address->family = AF_INET;
-                address->in_addr.in = addr;
-                address->prefixlen = 16;
-                address->scope = RT_SCOPE_LINK;
-                address->cinfo.ifa_prefered = deprecate ? 0 : CACHE_INFO_INFINITY_LIFE_TIME;
-                address->broadcast.s_addr = address->in_addr.in.s_addr | htonl(0xfffffffflu >> address->prefixlen);
-
-                address_update(address, link, &address_update_handler);
-        }
-
-        return 0;
-
-}
-
-static int ipv4ll_address_lost(Link *link) {
-        int r;
-        struct in_addr addr;
-
-        assert(link);
-
-        r = sd_ipv4ll_get_address(link->ipv4ll, &addr);
-        if (r >= 0) {
-                _cleanup_address_free_ Address *address = NULL;
-                _cleanup_route_free_ Route *route = NULL;
-
-                log_debug_link(link, "IPv4 link-local release %u.%u.%u.%u",
-                                ADDRESS_FMT_VAL(addr));
-
-                r = address_new_dynamic(&address);
-                if (r < 0) {
-                        log_error_link(link, "Could not allocate address: %s", strerror(-r));
-                        return r;
-                }
-
-                address->family = AF_INET;
-                address->in_addr.in = addr;
-                address->prefixlen = 16;
-                address->scope = RT_SCOPE_LINK;
-
-                address_drop(address, link, &address_drop_handler);
-
-                r = route_new_dynamic(&route);
-                if (r < 0) {
-                        log_error_link(link, "Could not allocate route: %s",
-                                       strerror(-r));
-                        return r;
-                }
-
-                route->family = AF_INET;
-                route->scope = RT_SCOPE_LINK;
-                route->metrics = 99;
-
-                route_drop(route, link, &route_drop_handler);
-        }
-
-        return 0;
-}
-
-static bool ipv4ll_is_bound(sd_ipv4ll *ll) {
-        int r;
-        struct in_addr addr;
-
-        assert(ll);
-
-        r = sd_ipv4ll_get_address(ll, &addr);
-        if (r < 0)
-                return false;
-        return true;
-}
-
-static int ipv4ll_address_claimed(sd_ipv4ll *ll, Link *link) {
-        struct in_addr address;
-        int r;
-
-        assert(ll);
-        assert(link);
-
-        r = sd_ipv4ll_get_address(ll, &address);
-        if (r < 0)
-                return r;
-
-        log_struct_link(LOG_INFO, link,
-                        "MESSAGE=%-*s: IPv4 link-local address %u.%u.%u.%u",
-                        IFNAMSIZ,
-                        link->ifname,
-                        ADDRESS_FMT_VAL(address),
-                        NULL);
-
-       link_enter_set_addresses(link);
-
-       return 0;
-}
-
-static void ipv4ll_handler(sd_ipv4ll *ll, int event, void *userdata){
-        Link *link = userdata;
-        int r;
-
-        assert(link);
-        assert(link->network);
-        assert(link->manager);
-
-        if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
-                return;
-
-        switch(event) {
-                case IPV4LL_EVENT_STOP:
-                case IPV4LL_EVENT_CONFLICT:
-                        r = ipv4ll_address_lost(link);
-                        if (r < 0) {
-                                link_enter_failed(link);
-                                return;
-                        }
-                        break;
-                case IPV4LL_EVENT_BIND:
-                        r = ipv4ll_address_claimed(ll, link);
-                        if (r < 0) {
-                                link_enter_failed(link);
-                                return;
-                        }
-                        break;
-                default:
-                        if (event < 0)
-                                log_warning_link(link, "IPv4 link-local error: %s", strerror(-event));
-                        else
-                                log_warning_link(link, "IPv4 link-local unknown event: %d", event);
-                        break;
-        }
-}
-
 static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) {
         Link *link = userdata;
 
@@ -1737,8 +1464,6 @@ static int link_joined(Link *link) {
         assert(link->state == LINK_STATE_ENSLAVING);
         assert(link->network);
 
-        log_debug_link(link, "joined netdev");
-
         if (!(link->flags & IFF_UP)) {
                 r = link_up(link);
                 if (r < 0) {
@@ -1774,7 +1499,8 @@ static int netdev_join_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata
                                 NULL);
                 link_enter_failed(link);
                 return 1;
-        }
+        } else
+                log_debug_link(link, "joined netdev");
 
         if (link->enslaving <= 0)
                 link_joined(link);
@@ -1783,7 +1509,7 @@ static int netdev_join_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata
 }
 
 static int link_enter_join_netdev(Link *link) {
-        NetDev *vlan, *macvlan, *vxlan;
+        NetDev *netdev;
         Iterator i;
         int r;
 
@@ -1797,10 +1523,7 @@ static int link_enter_join_netdev(Link *link) {
 
         if (!link->network->bridge &&
             !link->network->bond &&
-            !link->network->tunnel &&
-            hashmap_isempty(link->network->vlans) &&
-            hashmap_isempty(link->network->macvlans) &&
-            hashmap_isempty(link->network->vxlans))
+            hashmap_isempty(link->network->stacked_netdevs))
                 return link_joined(link);
 
         if (link->network->bond) {
@@ -1808,7 +1531,7 @@ static int link_enter_join_netdev(Link *link) {
                                 "MESSAGE=%-*s: enslaving by '%s'",
                                 IFNAMSIZ,
                                 link->ifname, link->network->bond->ifname,
-                                NETDEV(link->network->bond),
+                                NETDEVIF(link->network->bond),
                                 NULL);
 
                 r = netdev_join(link->network->bond, link, &netdev_join_handler);
@@ -1817,7 +1540,7 @@ static int link_enter_join_netdev(Link *link) {
                                         "MESSAGE=%-*s: could not join netdev '%s': %s",
                                         IFNAMSIZ,
                                         link->ifname, link->network->bond->ifname, strerror(-r),
-                                        NETDEV(link->network->bond),
+                                        NETDEVIF(link->network->bond),
                                         NULL);
                         link_enter_failed(link);
                         return r;
@@ -1831,7 +1554,7 @@ static int link_enter_join_netdev(Link *link) {
                                 "MESSAGE=%-*s: enslaving by '%s'",
                                 IFNAMSIZ,
                                 link->ifname, link->network->bridge->ifname,
-                                NETDEV(link->network->bridge),
+                                NETDEVIF(link->network->bridge),
                                 NULL);
 
                 r = netdev_join(link->network->bridge, link, &netdev_join_handler);
@@ -1840,30 +1563,7 @@ static int link_enter_join_netdev(Link *link) {
                                         "MESSAGE=%-*s: could not join netdev '%s': %s",
                                         IFNAMSIZ,
                                         link->ifname, link->network->bridge->ifname, strerror(-r),
-                                        NETDEV(link->network->bridge),
-                                        NULL);
-                        link_enter_failed(link);
-                        return r;
-                }
-
-                link->enslaving ++;
-        }
-
-        if (link->network->tunnel) {
-                log_struct_link(LOG_DEBUG, link,
-                                "MESSAGE=%-*s: enslaving by '%s'",
-                                IFNAMSIZ,
-                                link->ifname, link->network->tunnel->ifname,
-                                NETDEV(link->network->tunnel),
-                                NULL);
-
-                r = netdev_join(link->network->tunnel, link, &netdev_join_handler);
-                if (r < 0) {
-                        log_struct_link(LOG_WARNING, link,
-                                        "MESSAGE=%-*s: could not join netdev '%s': %s",
-                                        IFNAMSIZ,
-                                        link->ifname, link->network->tunnel->ifname, strerror(-r),
-                                        NETDEV(link->network->tunnel),
+                                        NETDEVIF(link->network->bridge),
                                         NULL);
                         link_enter_failed(link);
                         return r;
@@ -1872,59 +1572,19 @@ static int link_enter_join_netdev(Link *link) {
                 link->enslaving ++;
         }
 
-        HASHMAP_FOREACH(vlan, link->network->vlans, i) {
+        HASHMAP_FOREACH(netdev, link->network->stacked_netdevs, i) {
                 log_struct_link(LOG_DEBUG, link,
                                 "MESSAGE=%-*s: enslaving by '%s'",
                                 IFNAMSIZ,
-                                link->ifname, vlan->ifname, NETDEV(vlan), NULL);
+                                link->ifname, netdev->ifname, NETDEVIF(netdev), NULL);
 
-                r = netdev_join(vlan, link, &netdev_join_handler);
+                r = netdev_join(netdev, link, &netdev_join_handler);
                 if (r < 0) {
                         log_struct_link(LOG_WARNING, link,
                                         "MESSAGE=%-*s: could not join netdev '%s': %s",
                                         IFNAMSIZ,
-                                        link->ifname, vlan->ifname, strerror(-r),
-                                        NETDEV(vlan), NULL);
-                        link_enter_failed(link);
-                        return r;
-                }
-
-                link->enslaving ++;
-        }
-
-        HASHMAP_FOREACH(macvlan, link->network->macvlans, i) {
-                log_struct_link(LOG_DEBUG, link,
-                                "MESSAGE=%-*s: enslaving by '%s'",
-                                IFNAMSIZ,
-                                link->ifname, macvlan->ifname, NETDEV(macvlan), NULL);
-
-                r = netdev_join(macvlan, link, &netdev_join_handler);
-                if (r < 0) {
-                        log_struct_link(LOG_WARNING, link,
-                                        "MESSAGE=%-*s: could not join netdev '%s': %s",
-                                        IFNAMSIZ,
-                                        link->ifname, macvlan->ifname, strerror(-r),
-                                        NETDEV(macvlan), NULL);
-                        link_enter_failed(link);
-                        return r;
-                }
-
-                link->enslaving ++;
-        }
-
-        HASHMAP_FOREACH(vxlan, link->network->vxlans, i) {
-                log_struct_link(LOG_DEBUG, link,
-                                "MESSAGE=%*s: enslaving by '%s'",
-                                IFNAMSIZ,
-                                link->ifname, vxlan->ifname, NETDEV(vxlan), NULL);
-
-                r = netdev_join(vxlan, link, &netdev_join_handler);
-                if (r < 0) {
-                        log_struct_link(LOG_WARNING, link,
-                                        "MESSAGE=%*s: could not join netdev '%s': %s",
-                                        IFNAMSIZ,
-                                        link->ifname, vxlan->ifname, strerror(-r),
-                                        NETDEV(vxlan), NULL);
+                                        link->ifname, netdev->ifname, strerror(-r),
+                                        NETDEVIF(netdev), NULL);
                         link_enter_failed(link);
                         return r;
                 }
@@ -1939,37 +1599,11 @@ static int link_configure(Link *link) {
         int r;
 
         assert(link);
+        assert(link->network);
         assert(link->state == LINK_STATE_INITIALIZING);
 
         if (link->network->ipv4ll) {
-                uint8_t seed[8];
-
-                r = sd_ipv4ll_new(&link->ipv4ll);
-                if (r < 0)
-                        return r;
-
-                if (link->udev_device) {
-                        r = net_get_unique_predictable_data(link->udev_device, seed);
-                        if (r >= 0) {
-                                r = sd_ipv4ll_set_address_seed(link->ipv4ll, seed);
-                                if (r < 0)
-                                        return r;
-                        }
-                }
-
-                r = sd_ipv4ll_attach_event(link->ipv4ll, NULL, 0);
-                if (r < 0)
-                        return r;
-
-                r = sd_ipv4ll_set_mac(link->ipv4ll, &link->mac);
-                if (r < 0)
-                        return r;
-
-                r = sd_ipv4ll_set_index(link->ipv4ll, link->ifindex);
-                if (r < 0)
-                        return r;
-
-                r = sd_ipv4ll_set_callback(link->ipv4ll, ipv4ll_handler, link);
+                r = ipv4ll_configure(link);
                 if (r < 0)
                         return r;
         }
@@ -1999,8 +1633,14 @@ static int link_configure(Link *link) {
                 if (r < 0)
                         return r;
 
+                if (link->mtu) {
+                        r = sd_dhcp_client_set_mtu(link->dhcp_client, link->mtu);
+                        if (r < 0)
+                                return r;
+                }
+
                 if (link->network->dhcp_mtu) {
-                        r = sd_dhcp_client_set_request_option(link->dhcp_client, 26);
+                        r = sd_dhcp_client_set_request_option(link->dhcp_client, DHCP_OPTION_INTERFACE_MTU);
                         if (r < 0)
                                 return r;
                 }
@@ -2293,7 +1933,7 @@ int link_add(Manager *m, sd_rtnl_message *message, Link **ret) {
 
         link = *ret;
 
-        log_debug_link(link, "link %"PRIu64" added", link->ifindex);
+        log_debug_link(link, "link %d added", link->ifindex);
 
         r = sd_rtnl_message_new_addr(m->rtnl, &req, RTM_GETADDR, link->ifindex, 0);
         if (r < 0)
@@ -2307,10 +1947,10 @@ int link_add(Manager *m, sd_rtnl_message *message, Link **ret) {
 
         if (detect_container(NULL) <= 0) {
                 /* not in a container, udev will be around */
-                sprintf(ifindex_str, "n%"PRIu64, link->ifindex);
+                sprintf(ifindex_str, "n%d", link->ifindex);
                 device = udev_device_new_from_device_id(m->udev, ifindex_str);
                 if (!device) {
-                        log_warning_link(link, "could not find udev device");
+                        log_warning_link(link, "could not find udev device: %m");
                         return -errno;
                 }
 
@@ -2337,7 +1977,8 @@ int link_add(Manager *m, sd_rtnl_message *message, Link **ret) {
 
 int link_update(Link *link, sd_rtnl_message *m) {
         struct ether_addr mac;
-        char *ifname;
+        const char *ifname;
+        uint32_t mtu;
         int r;
 
         assert(link);
@@ -2360,11 +2001,23 @@ int link_update(Link *link, sd_rtnl_message *m) {
                         return -ENOMEM;
         }
 
-        if (!link->original_mtu) {
-                r = sd_rtnl_message_read_u16(m, IFLA_MTU, &link->original_mtu);
-                if (r >= 0)
+        r = sd_rtnl_message_read_u32(m, IFLA_MTU, &mtu);
+        if (r >= 0 && mtu > 0) {
+                link->mtu = mtu;
+                if (!link->original_mtu) {
+                        link->original_mtu = mtu;
                         log_debug_link(link, "saved original MTU: %"
-                                       PRIu16, link->original_mtu);
+                                       PRIu32, link->original_mtu);
+                }
+
+                if (link->dhcp_client) {
+                        r = sd_dhcp_client_set_mtu(link->dhcp_client, link->mtu);
+                        if (r < 0) {
+                                log_warning_link(link, "Could not update MTU in DHCP client: %s",
+                                                 strerror(-r));
+                                return r;
+                        }
+                }
         }
 
         /* The kernel may broadcast NEWLINK messages without the MAC address
@@ -2419,27 +2072,6 @@ int link_update(Link *link, sd_rtnl_message *m) {
         return link_update_flags(link, m);
 }
 
-static void serialize_addresses(FILE *f, const char *key, Address *address) {
-        Address *ad;
-
-        assert(f);
-        assert(key);
-
-        if (!address)
-                return;
-
-        fprintf(f, "%s=", key);
-
-        LIST_FOREACH(addresses, ad, address) {
-                char buf[INET6_ADDRSTRLEN];
-
-                if (inet_ntop(ad->family, &ad->in_addr, buf, INET6_ADDRSTRLEN))
-                        fprintf(f, "%s%s", buf, (ad->addresses_next) ? " ": "");
-        }
-
-        fputs("\n", f);
-}
-
 static void link_update_operstate(Link *link) {
 
         assert(link);
@@ -2499,20 +2131,60 @@ int link_save(Link *link) {
 
         r = fopen_temporary(link->state_file, &f, &temp_path);
         if (r < 0)
-                goto finish;
+                return r;
 
         fchmod(fileno(f), 0644);
 
         fprintf(f,
                 "# This is private data. Do not parse.\n"
                 "ADMIN_STATE=%s\n"
-                "OPER_STATE=%s\n"
-                "FLAGS=%u\n",
-                admin_state, oper_state, link->flags);
+                "OPER_STATE=%s\n",
+                admin_state, oper_state);
 
         if (link->network) {
-                serialize_addresses(f, "DNS", link->network->dns);
-                serialize_addresses(f, "NTP", link->network->ntp);
+                char **address;
+
+                fputs("DNS=", f);
+
+                if (link->network->dhcp_dns &&
+                    link->dhcp_lease) {
+                        const struct in_addr *addresses;
+
+                        r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses);
+                        if (r > 0) {
+                                serialize_in_addrs(f, addresses, r);
+                                if (link->network->dns)
+                                        fputs(" ", f);
+                        }
+                }
+
+                STRV_FOREACH(address, link->network->dns)
+                        fprintf(f, "%s%s", *address,
+                                (address + 1 ? " " : ""));
+
+                fputs("\n", f);
+
+                fprintf(f, "NTP=");
+
+                if (link->network->dhcp_ntp &&
+                    link->dhcp_lease) {
+                        const struct in_addr *addresses;
+
+                        r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses);
+                        if (r > 0) {
+                                serialize_in_addrs(f, addresses, r);
+                                if (link->network->ntp)
+                                        fputs(" ", f);
+                        }
+                }
+
+                STRV_FOREACH(address, link->network->ntp)
+                        fprintf(f, "%s%s", *address,
+                                (address + 1 ? " " : ""));
+
+                fputs("\n", f);
+
+                fprintf(f, "LLMNR=%s\n", llmnr_support_to_string(link->network->llmnr));
         }
 
         if (link->dhcp_lease) {
@@ -2520,30 +2192,29 @@ int link_save(Link *link) {
 
                 r = dhcp_lease_save(link->dhcp_lease, link->lease_file);
                 if (r < 0)
-                        goto finish;
+                        goto fail;
 
                 fprintf(f,
-                        "DHCP_LEASE=%s\n"
-                        "DHCP_USE_DNS=%s\n"
-                        "DHCP_USE_NTP=%s\n",
-                        link->lease_file,
-                        yes_no(link->network->dhcp_dns),
-                        yes_no(link->network->dhcp_ntp));
+                        "DHCP_LEASE=%s\n",
+                        link->lease_file);
         } else
                 unlink(link->lease_file);
 
-        fflush(f);
+        r = fflush_and_check(f);
+        if (r < 0)
+                goto fail;
 
-        if (ferror(f) || rename(temp_path, link->state_file) < 0) {
+        if (rename(temp_path, link->state_file) < 0) {
                 r = -errno;
-                unlink(link->state_file);
-                unlink(temp_path);
+                goto fail;
         }
 
-finish:
-        if (r < 0)
-                log_error_link(link, "Failed to save link data to %s: %s", link->state_file, strerror(-r));
+        return 0;
 
+fail:
+        log_error_link(link, "Failed to save link data to %s: %s", link->state_file, strerror(-r));
+        unlink(link->state_file);
+        unlink(temp_path);
         return r;
 }
 
@@ -2569,55 +2240,3 @@ static const char* const link_operstate_table[_LINK_OPERSTATE_MAX] = {
 };
 
 DEFINE_STRING_TABLE_LOOKUP(link_operstate, LinkOperationalState);
-
-static const char* const dhcp_support_table[_DHCP_SUPPORT_MAX] = {
-        [DHCP_SUPPORT_NONE] = "none",
-        [DHCP_SUPPORT_BOTH] = "both",
-        [DHCP_SUPPORT_V4] = "v4",
-        [DHCP_SUPPORT_V6] = "v6",
-};
-
-DEFINE_STRING_TABLE_LOOKUP(dhcp_support, DHCPSupport);
-
-int config_parse_dhcp(
-                const char* unit,
-                const char *filename,
-                unsigned line,
-                const char *section,
-                unsigned section_line,
-                const char *lvalue,
-                int ltype,
-                const char *rvalue,
-                void *data,
-                void *userdata) {
-
-        DHCPSupport *dhcp = data;
-        int k;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        /* Our enum shall be a superset of booleans, hence first try
-         * to parse as boolean, and then as enum */
-
-        k = parse_boolean(rvalue);
-        if (k > 0)
-                *dhcp = DHCP_SUPPORT_BOTH;
-        else if (k == 0)
-                *dhcp = DHCP_SUPPORT_NONE;
-        else {
-                DHCPSupport s;
-
-                s = dhcp_support_from_string(rvalue);
-                if (s < 0){
-                        log_syntax(unit, LOG_ERR, filename, line, -s, "Failed to parse DHCP option, ignoring: %s", rvalue);
-                        return 0;
-                }
-
-                *dhcp = s;
-        }
-
-        return 0;
-}