From 8012cd391932d58b44332df106d426a360faf0a6 Mon Sep 17 00:00:00 2001
From: Tom Gundersen <teg@jklm.no>
Date: Mon, 28 Sep 2015 13:38:43 +0200
Subject: networkd: link - only consider configured when all addresses are
 ready

We were considering a link configured whilst its IPv6 addresses were still
tentative.

Fixes issue #650.
---
 src/network/networkd-address.c |  9 +++++++++
 src/network/networkd-dhcp4.c   |  2 +-
 src/network/networkd-dhcp6.c   |  2 +-
 src/network/networkd-ipv4ll.c  |  6 +++---
 src/network/networkd-link.c    | 13 ++++++++++---
 src/network/networkd-link.h    |  2 +-
 src/network/networkd-manager.c |  3 ++-
 7 files changed, 27 insertions(+), 10 deletions(-)

(limited to 'src')

diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
index 316ae2e4cb..f1b364f40e 100644
--- a/src/network/networkd-address.c
+++ b/src/network/networkd-address.c
@@ -283,11 +283,20 @@ static int address_release(Address *address, Link *link) {
 }
 
 int address_drop(Address *address) {
+        Link *link;
+        bool ready;
+
         assert(address);
 
+        ready = address_is_ready(address);
+        link = address->link;
+
         address_release(address, address->link);
         address_free(address);
 
+        if (link && !ready)
+                link_check_ready(link);
+
         return 0;
 }
 
diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
index 5d9bfcea7c..e2f7d69666 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -45,7 +45,7 @@ static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m,
 
         if (!link->dhcp4_messages) {
                 link->dhcp4_configured = true;
-                link_client_handler(link);
+                link_check_ready(link);
         }
 
         return 1;
diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c
index 2f9ecf7a89..8115232e1d 100644
--- a/src/network/networkd-dhcp6.c
+++ b/src/network/networkd-dhcp6.c
@@ -176,7 +176,7 @@ static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) {
                 return;
         }
 
-        link_client_handler(link);
+        link_check_ready(link);
 }
 
 static int dhcp6_configure(Link *link, int event) {
diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c
index 01ee9f9f4a..752c09d6b1 100644
--- a/src/network/networkd-ipv4ll.c
+++ b/src/network/networkd-ipv4ll.c
@@ -67,7 +67,7 @@ static int ipv4ll_address_lost(Link *link) {
 
         route_remove(route, link, &link_route_remove_handler);
 
-        link_client_handler(link);
+        link_check_ready(link);
 
         return 0;
 }
@@ -88,7 +88,7 @@ static int ipv4ll_route_handler(sd_netlink *rtnl, sd_netlink_message *m, void *u
         link->ipv4ll_route = true;
 
         if (link->ipv4ll_address == true)
-                link_client_handler(link);
+                link_check_ready(link);
 
         return 1;
 }
@@ -110,7 +110,7 @@ static int ipv4ll_address_handler(sd_netlink *rtnl, sd_netlink_message *m, void
         link->ipv4ll_address = true;
 
         if (link->ipv4ll_route == true)
-                link_client_handler(link);
+                link_check_ready(link);
 
         return 1;
 }
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 9d97089576..9661704f52 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -503,7 +503,10 @@ static int link_enter_configured(Link *link) {
         return 0;
 }
 
-void link_client_handler(Link *link) {
+void link_check_ready(Link *link) {
+        Address *a;
+        Iterator i;
+
         assert(link);
         assert(link->network);
 
@@ -523,6 +526,10 @@ void link_client_handler(Link *link) {
              !link->dhcp4_configured && !link->dhcp6_configured))
                 return;
 
+        SET_FOREACH(a, link->addresses, i)
+                if (!address_is_ready(a))
+                        return;
+
         if (link->state != LINK_STATE_CONFIGURED)
                 link_enter_configured(link);
 
@@ -550,7 +557,7 @@ static int route_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata
         if (link->link_messages == 0) {
                 log_link_debug(link, "Routes set");
                 link->static_configured = true;
-                link_client_handler(link);
+                link_check_ready(link);
         }
 
         return 1;
@@ -579,7 +586,7 @@ static int link_enter_set_routes(Link *link) {
 
         if (link->link_messages == 0) {
                 link->static_configured = true;
-                link_client_handler(link);
+                link_check_ready(link);
         } else
                 log_link_debug(link, "Setting routes");
 
diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h
index b81bae3830..a94bb2f714 100644
--- a/src/network/networkd-link.h
+++ b/src/network/networkd-link.h
@@ -126,7 +126,7 @@ int link_route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, void *use
 void link_enter_failed(Link *link);
 int link_initialized(Link *link, struct udev_device *device);
 
-void link_client_handler(Link *link);
+void link_check_ready(Link *link);
 
 int link_update(Link *link, sd_netlink_message *message);
 
diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c
index 07e47b668c..2cd4f4fef7 100644
--- a/src/network/networkd-manager.c
+++ b/src/network/networkd-manager.c
@@ -404,6 +404,7 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
                         address->flags = flags;
                         address->cinfo = cinfo;
 
+                        link_check_ready(link);
                 } else {
                         r = address_add(link, family, &in_addr, prefixlen, &address);
                         if (r < 0) {
@@ -416,7 +417,7 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
                         address->flags = flags;
                         address->cinfo = cinfo;
 
-                        link_save(link);
+                        link_check_ready(link);
                 }
 
                 break;
-- 
cgit v1.2.3-54-g00ecf


From 36c32f6120a0c3fe19be5aeaa1926e179e8c29ba Mon Sep 17 00:00:00 2001
From: Tom Gundersen <teg@jklm.no>
Date: Mon, 28 Sep 2015 17:16:12 +0200
Subject: networkd: address - factor out address_update()

Call back into link_check_ready() whenever an address state change may have
made a link ready.
---
 src/network/networkd-address.c | 20 +++++++++++++++++++-
 src/network/networkd-address.h |  3 ++-
 src/network/networkd-dhcp4.c   |  4 ++--
 src/network/networkd-dhcp6.c   |  8 ++++----
 src/network/networkd-manager.c | 18 ++++--------------
 5 files changed, 31 insertions(+), 22 deletions(-)

(limited to 'src')

diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
index f1b364f40e..98ff027536 100644
--- a/src/network/networkd-address.c
+++ b/src/network/networkd-address.c
@@ -282,6 +282,24 @@ static int address_release(Address *address, Link *link) {
         return 0;
 }
 
+int address_update(Address *address, unsigned char flags, unsigned char scope, struct ifa_cacheinfo *cinfo) {
+        bool ready;
+
+        assert(address);
+        assert(cinfo);
+
+        ready = address_is_ready(address);
+
+        address->flags = flags;
+        address->scope = scope;
+        address->cinfo = *cinfo;
+
+        if (!ready && address_is_ready(address) && address->link)
+                link_check_ready(address->link);
+
+        return 0;
+}
+
 int address_drop(Address *address) {
         Link *link;
         bool ready;
@@ -359,7 +377,7 @@ int address_remove(Address *address, Link *link,
         return 0;
 }
 
-int address_update(Address *address, Link *link,
+int address_change(Address *address, Link *link,
                    sd_netlink_message_handler_t callback) {
         _cleanup_netlink_message_unref_ sd_netlink_message *req = NULL;
         int r;
diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h
index 425344fe48..07a7ad2026 100644
--- a/src/network/networkd-address.h
+++ b/src/network/networkd-address.h
@@ -62,9 +62,10 @@ int address_new(Address **ret);
 void address_free(Address *address);
 int address_add(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret);
 int address_get(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret);
+int address_update(Address *address, unsigned char flags, unsigned char scope, struct ifa_cacheinfo *cinfo);
 int address_drop(Address *address);
 int address_configure(Address *address, Link *link, sd_netlink_message_handler_t callback);
-int address_update(Address *address, Link *link, sd_netlink_message_handler_t callback);
+int address_change(Address *address, Link *link, sd_netlink_message_handler_t callback);
 int address_remove(Address *address, Link *link, sd_netlink_message_handler_t callback);
 bool address_equal(Address *a1, Address *a2);
 bool address_is_ready(const Address *a);
diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
index e2f7d69666..5c91d9609f 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -299,9 +299,9 @@ static int dhcp4_update_address(Link *link,
         addr->prefixlen = prefixlen;
         addr->broadcast.s_addr = address->s_addr | ~netmask->s_addr;
 
-        /* use update rather than configure so that we will update the
+        /* use change rather than configure so that we will update the
          * lifetime of an existing address if it has already been configured */
-        r = address_update(addr, link, &dhcp4_address_handler);
+        r = address_change(addr, link, &dhcp4_address_handler);
         if (r < 0)
                 return r;
 
diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c
index 8115232e1d..95cdb80b0a 100644
--- a/src/network/networkd-dhcp6.c
+++ b/src/network/networkd-dhcp6.c
@@ -63,7 +63,7 @@ static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m,
         return 1;
 }
 
-static int dhcp6_address_update(Link *link, struct in6_addr *ip6_addr,
+static int dhcp6_address_change(Link *link, struct in6_addr *ip6_addr,
                                 uint8_t prefixlen, uint32_t lifetime_preferred,
                                 uint32_t lifetime_valid) {
         int r;
@@ -87,7 +87,7 @@ static int dhcp6_address_update(Link *link, struct in6_addr *ip6_addr,
                       SD_ICMP6_ND_ADDRESS_FORMAT_VAL(addr->in_addr.in6),
                       addr->prefixlen, lifetime_preferred, lifetime_valid);
 
-        r = address_update(addr, link, dhcp6_address_handler);
+        r = address_change(addr, link, dhcp6_address_handler);
         if (r < 0)
                 log_link_warning_errno(link, r, "Could not assign DHCPv6 address: %m");
 
@@ -121,7 +121,7 @@ static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link) {
                 if (r == -EADDRNOTAVAIL)
                         prefixlen = 128;
 
-                r = dhcp6_address_update(link, &ip6_addr, prefixlen,
+                r = dhcp6_address_change(link, &ip6_addr, prefixlen,
                                         lifetime_preferred, lifetime_valid);
                 if (r < 0)
                         return r;
@@ -300,7 +300,7 @@ static int dhcp6_prefix_expired(Link *link) {
 
                 log_link_info(link, "IPv6 prefix length updated "SD_ICMP6_ND_ADDRESS_FORMAT_STR"/%d", SD_ICMP6_ND_ADDRESS_FORMAT_VAL(ip6_addr), 128);
 
-                dhcp6_address_update(link, &ip6_addr, 128, lifetime_preferred, lifetime_valid);
+                dhcp6_address_change(link, &ip6_addr, 128, lifetime_preferred, lifetime_valid);
         }
 
         return 0;
diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c
index 2cd4f4fef7..9a7e71fce8 100644
--- a/src/network/networkd-manager.c
+++ b/src/network/networkd-manager.c
@@ -397,29 +397,19 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
 
         switch (type) {
         case RTM_NEWADDR:
-                if (address) {
+                if (address)
                         log_link_debug(link, "Updating address: %s/%u (valid for %s)", buf, prefixlen, valid_str);
-
-                        address->scope = scope;
-                        address->flags = flags;
-                        address->cinfo = cinfo;
-
-                        link_check_ready(link);
-                } else {
+                else {
                         r = address_add(link, family, &in_addr, prefixlen, &address);
                         if (r < 0) {
                                 log_link_warning_errno(link, r, "Failed to add address %s/%u: %m", buf, prefixlen);
                                 return 0;
                         } else
                                 log_link_debug(link, "Adding address: %s/%u (valid for %s)", buf, prefixlen, valid_str);
-
-                        address->scope = scope;
-                        address->flags = flags;
-                        address->cinfo = cinfo;
-
-                        link_check_ready(link);
                 }
 
+                address_update(address, scope, flags, &cinfo);
+
                 break;
 
         case RTM_DELADDR:
-- 
cgit v1.2.3-54-g00ecf


From 6666907869fb3bc7fe6a6025540db5b887c7a78b Mon Sep 17 00:00:00 2001
From: Tom Gundersen <teg@jklm.no>
Date: Thu, 1 Oct 2015 17:31:14 +0200
Subject: networkd: address - merge _change() into _configure()

These functions are almost entirely the same, so avoid duplication.
---
 src/network/networkd-address.c | 79 ++++--------------------------------------
 src/network/networkd-address.h |  3 +-
 src/network/networkd-dhcp4.c   |  6 ++--
 src/network/networkd-dhcp6.c   |  2 +-
 src/network/networkd-ipv4ll.c  |  2 +-
 src/network/networkd-link.c    |  2 +-
 6 files changed, 14 insertions(+), 80 deletions(-)

(limited to 'src')

diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
index 98ff027536..7c74a0309d 100644
--- a/src/network/networkd-address.c
+++ b/src/network/networkd-address.c
@@ -377,74 +377,6 @@ int address_remove(Address *address, Link *link,
         return 0;
 }
 
-int address_change(Address *address, Link *link,
-                   sd_netlink_message_handler_t callback) {
-        _cleanup_netlink_message_unref_ sd_netlink_message *req = NULL;
-        int r;
-
-        assert(address);
-        assert(address->family == AF_INET || address->family == AF_INET6);
-        assert(link->ifindex > 0);
-        assert(link->manager);
-        assert(link->manager->rtnl);
-
-        r = sd_rtnl_message_new_addr_update(link->manager->rtnl, &req,
-                                     link->ifindex, address->family);
-        if (r < 0)
-                return log_error_errno(r, "Could not allocate RTM_NEWADDR message: %m");
-
-        r = sd_rtnl_message_addr_set_prefixlen(req, address->prefixlen);
-        if (r < 0)
-                return log_error_errno(r, "Could not set prefixlen: %m");
-
-        address->flags |= IFA_F_PERMANENT;
-
-        r = sd_rtnl_message_addr_set_flags(req, address->flags & 0xff);
-        if (r < 0)
-                return log_error_errno(r, "Could not set flags: %m");
-
-        if (address->flags & ~0xff && link->rtnl_extended_attrs) {
-                r = sd_netlink_message_append_u32(req, IFA_FLAGS, address->flags);
-                if (r < 0)
-                        return log_error_errno(r, "Could not set extended flags: %m");
-        }
-
-        r = sd_rtnl_message_addr_set_scope(req, address->scope);
-        if (r < 0)
-                return log_error_errno(r, "Could not set scope: %m");
-
-        if (address->family == AF_INET)
-                r = sd_netlink_message_append_in_addr(req, IFA_LOCAL, &address->in_addr.in);
-        else if (address->family == AF_INET6)
-                r = sd_netlink_message_append_in6_addr(req, IFA_LOCAL, &address->in_addr.in6);
-        if (r < 0)
-                return log_error_errno(r, "Could not append IFA_LOCAL attribute: %m");
-
-        if (address->family == AF_INET) {
-                r = sd_netlink_message_append_in_addr(req, IFA_BROADCAST, &address->broadcast);
-                if (r < 0)
-                        return log_error_errno(r, "Could not append IFA_BROADCAST attribute: %m");
-        }
-
-        if (address->label) {
-                r = sd_netlink_message_append_string(req, IFA_LABEL, address->label);
-                if (r < 0)
-                        return log_error_errno(r, "Could not append IFA_LABEL attribute: %m");
-        }
-
-        r = sd_netlink_message_append_cache_info(req, IFA_CACHEINFO, &address->cinfo);
-        if (r < 0)
-                return log_error_errno(r, "Could not append IFA_CACHEINFO attribute: %m");
-
-        r = sd_netlink_call_async(link->manager->rtnl, req, callback, link, 0, NULL);
-        if (r < 0)
-                return log_error_errno(r, "Could not send rtnetlink message: %m");
-
-        link_ref(link);
-
-        return 0;
-}
-
 static int address_acquire(Link *link, Address *original, Address **ret) {
         union in_addr_union in_addr = {};
         struct in_addr broadcast = {};
@@ -504,8 +436,7 @@ static int address_acquire(Link *link, Address *original, Address **ret) {
         return 0;
 }
 
-int address_configure(Address *address, Link *link,
-                      sd_netlink_message_handler_t callback) {
+int address_configure(Address *address, Link *link, sd_netlink_message_handler_t callback, bool update) {
         _cleanup_netlink_message_unref_ sd_netlink_message *req = NULL;
         int r;
 
@@ -520,8 +451,12 @@ int address_configure(Address *address, Link *link,
         if (r < 0)
                 return r;
 
-        r = sd_rtnl_message_new_addr(link->manager->rtnl, &req, RTM_NEWADDR,
-                                     link->ifindex, address->family);
+        if (update)
+                r = sd_rtnl_message_new_addr_update(link->manager->rtnl, &req,
+                                                    link->ifindex, address->family);
+        else
+                r = sd_rtnl_message_new_addr(link->manager->rtnl, &req, RTM_NEWADDR,
+                                             link->ifindex, address->family);
         if (r < 0)
                 return log_error_errno(r, "Could not allocate RTM_NEWADDR message: %m");
 
diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h
index 07a7ad2026..cb29acc7be 100644
--- a/src/network/networkd-address.h
+++ b/src/network/networkd-address.h
@@ -64,8 +64,7 @@ int address_add(Link *link, int family, const union in_addr_union *in_addr, unsi
 int address_get(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret);
 int address_update(Address *address, unsigned char flags, unsigned char scope, struct ifa_cacheinfo *cinfo);
 int address_drop(Address *address);
-int address_configure(Address *address, Link *link, sd_netlink_message_handler_t callback);
-int address_change(Address *address, Link *link, sd_netlink_message_handler_t callback);
+int address_configure(Address *address, Link *link, sd_netlink_message_handler_t callback, bool update);
 int address_remove(Address *address, Link *link, sd_netlink_message_handler_t callback);
 bool address_equal(Address *a1, Address *a2);
 bool address_is_ready(const Address *a);
diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
index 5c91d9609f..6ba16581f8 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -299,9 +299,9 @@ static int dhcp4_update_address(Link *link,
         addr->prefixlen = prefixlen;
         addr->broadcast.s_addr = address->s_addr | ~netmask->s_addr;
 
-        /* use change rather than configure so that we will update the
-         * lifetime of an existing address if it has already been configured */
-        r = address_change(addr, link, &dhcp4_address_handler);
+        /* allow reusing an existing address and simply update its lifetime
+         * in case it already exists */
+        r = address_configure(addr, link, &dhcp4_address_handler, true);
         if (r < 0)
                 return r;
 
diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c
index 95cdb80b0a..fb93e6606e 100644
--- a/src/network/networkd-dhcp6.c
+++ b/src/network/networkd-dhcp6.c
@@ -87,7 +87,7 @@ static int dhcp6_address_change(Link *link, struct in6_addr *ip6_addr,
                       SD_ICMP6_ND_ADDRESS_FORMAT_VAL(addr->in_addr.in6),
                       addr->prefixlen, lifetime_preferred, lifetime_valid);
 
-        r = address_change(addr, link, dhcp6_address_handler);
+        r = address_configure(addr, link, dhcp6_address_handler, true);
         if (r < 0)
                 log_link_warning_errno(link, r, "Could not assign DHCPv6 address: %m");
 
diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c
index 752c09d6b1..26397b273f 100644
--- a/src/network/networkd-ipv4ll.c
+++ b/src/network/networkd-ipv4ll.c
@@ -143,7 +143,7 @@ static int ipv4ll_address_claimed(sd_ipv4ll *ll, Link *link) {
         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, ipv4ll_address_handler);
+        r = address_configure(ll_addr, link, ipv4ll_address_handler, false);
         if (r < 0)
                 return r;
 
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 9661704f52..1ac76c4255 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -743,7 +743,7 @@ static int link_enter_set_addresses(Link *link) {
         link_set_state(link, LINK_STATE_SETTING_ADDRESSES);
 
         LIST_FOREACH(addresses, ad, link->network->static_addresses) {
-                r = address_configure(ad, link, &address_handler);
+                r = address_configure(ad, link, &address_handler, false);
                 if (r < 0) {
                         log_link_warning_errno(link, r, "Could not set addresses: %m");
                         link_enter_failed(link);
-- 
cgit v1.2.3-54-g00ecf


From fcf50cff129c1b0c6c415428e659da40c3053e6e Mon Sep 17 00:00:00 2001
From: Tom Gundersen <teg@jklm.no>
Date: Wed, 30 Sep 2015 14:07:12 +0200
Subject: networkd: address - rework firewall rules lifetime

Establish the firewall rule before creating the address, and do not create the address
if the firewall rule could not be created. Also, only drop the firewall rule once
the address has been removed from the kernel.
---
 src/network/networkd-address.c | 30 ++++++++++++++----------------
 1 file changed, 14 insertions(+), 16 deletions(-)

(limited to 'src')

diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
index 7c74a0309d..c6de89a7f0 100644
--- a/src/network/networkd-address.c
+++ b/src/network/networkd-address.c
@@ -206,9 +206,9 @@ static int address_establish(Address *address, Link *link) {
         assert(link);
 
         masq = link->network &&
-                link->network->ip_masquerade &&
-                address->family == AF_INET &&
-                address->scope < RT_SCOPE_LINK;
+               link->network->ip_masquerade &&
+               address->family == AF_INET &&
+               address->scope < RT_SCOPE_LINK;
 
         /* Add firewall entry if this is requested */
         if (address->ip_masquerade_done != masq) {
@@ -251,21 +251,17 @@ int address_add(Link *link, int family, const union in_addr_union *in_addr, unsi
 
         address->link = link;
 
-        r = address_establish(address, link);
-        if (r < 0)
-                return r;
-
         *ret = address;
         address = NULL;
 
         return 0;
 }
 
-static int address_release(Address *address, Link *link) {
+static int address_release(Address *address) {
         int r;
 
         assert(address);
-        assert(link);
+        assert(address->link);
 
         /* Remove masquerading firewall entry if it was added */
         if (address->ip_masquerade_done) {
@@ -274,7 +270,7 @@ static int address_release(Address *address, Link *link) {
 
                 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");
+                        log_link_warning_errno(address->link, r, "Failed to disable IP masquerading: %m");
 
                 address->ip_masquerade_done = false;
         }
@@ -309,7 +305,7 @@ int address_drop(Address *address) {
         ready = address_is_ready(address);
         link = address->link;
 
-        address_release(address, address->link);
+        address_release(address);
         address_free(address);
 
         if (link && !ready)
@@ -350,8 +346,6 @@ int address_remove(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)
@@ -513,14 +507,18 @@ int address_configure(Address *address, Link *link, sd_netlink_message_handler_t
         if (r < 0)
                 return log_error_errno(r, "Could not append IFA_CACHEINFO attribute: %m");
 
-        r = sd_netlink_call_async(link->manager->rtnl, req, callback, link, 0, NULL);
+        r = address_establish(address, link);
         if (r < 0)
+                return r;
+
+        r = sd_netlink_call_async(link->manager->rtnl, req, callback, link, 0, NULL);
+        if (r < 0) {
+                address_release(address);
                 return log_error_errno(r, "Could not send rtnetlink message: %m");
+        }
 
         link_ref(link);
 
-        address_establish(address, link);
-
         return 0;
 }
 
-- 
cgit v1.2.3-54-g00ecf


From adda1ed94a04742ddacdc76dfa311816e1ed9f68 Mon Sep 17 00:00:00 2001
From: Tom Gundersen <teg@jklm.no>
Date: Wed, 30 Sep 2015 14:01:44 +0200
Subject: networkd: address - distinguish between addresses added by us and by
 others

We only keep the addresses that we added ourselves in link->addresses, and
introduce a new set link->addresses_foreign to keep addresses of unknown
origin.

Only functional change is that "foreign" addresses no longer prevent a link
from entering "configured" state.
---
 src/network/networkd-address.c | 44 +++++++++++++++++++++++++++++++++---------
 src/network/networkd-address.h |  5 +++--
 src/network/networkd-link.c    | 18 ++++++++++++++++-
 src/network/networkd-link.h    |  1 +
 src/network/networkd-manager.c |  3 ++-
 5 files changed, 58 insertions(+), 13 deletions(-)

(limited to 'src')

diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
index c6de89a7f0..da458dcb64 100644
--- a/src/network/networkd-address.c
+++ b/src/network/networkd-address.c
@@ -95,8 +95,10 @@ void address_free(Address *address) {
                                        UINT_TO_PTR(address->section));
         }
 
-        if (address->link)
+        if (address->link) {
                 set_remove(address->link->addresses, address);
+                set_remove(address->link->addresses_foreign, address);
+        }
 
         free(address);
 }
@@ -225,13 +227,17 @@ static int address_establish(Address *address, Link *link) {
         return 0;
 }
 
-int address_add(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret) {
+static int address_add_internal(Link *link, Set **addresses,
+                                int family,
+                                const union in_addr_union *in_addr,
+                                unsigned char prefixlen,
+                                Address **ret) {
         _cleanup_address_free_ Address *address = NULL;
         int r;
 
         assert(link);
+        assert(addresses);
         assert(in_addr);
-        assert(ret);
 
         r = address_new(&address);
         if (r < 0)
@@ -241,22 +247,32 @@ int address_add(Link *link, int family, const union in_addr_union *in_addr, unsi
         address->in_addr = *in_addr;
         address->prefixlen = prefixlen;
 
-        r = set_ensure_allocated(&link->addresses, &address_hash_ops);
+        r = set_ensure_allocated(addresses, &address_hash_ops);
         if (r < 0)
                 return r;
 
-        r = set_put(link->addresses, address);
+        r = set_put(*addresses, address);
         if (r < 0)
                 return r;
 
         address->link = link;
 
-        *ret = address;
+        if (ret)
+                *ret = address;
+
         address = NULL;
 
         return 0;
 }
 
+int address_add_foreign(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret) {
+        return address_add_internal(link, &link->addresses_foreign, family, in_addr, prefixlen, ret);
+}
+
+static int address_add(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret) {
+        return address_add_internal(link, &link->addresses, family, in_addr, prefixlen, ret);
+}
+
 static int address_release(Address *address) {
         int r;
 
@@ -286,6 +302,7 @@ int address_update(Address *address, unsigned char flags, unsigned char scope, s
 
         ready = address_is_ready(address);
 
+        address->added = true;
         address->flags = flags;
         address->scope = scope;
         address->cinfo = *cinfo;
@@ -326,8 +343,11 @@ int address_get(Link *link, int family, const union in_addr_union *in_addr, unsi
         address.prefixlen = prefixlen;
 
         existing = set_get(link->addresses, &address);
-        if (!existing)
-                return -ENOENT;
+        if (!existing) {
+                existing = set_get(link->addresses_foreign, &address);
+                if (!existing)
+                        return -ENOENT;
+        }
 
         *ret = existing;
 
@@ -519,6 +539,12 @@ int address_configure(Address *address, Link *link, sd_netlink_message_handler_t
 
         link_ref(link);
 
+        r = address_add(link, address->family, &address->in_addr, address->prefixlen, NULL);
+        if (r < 0) {
+                address_release(address);
+                return log_error_errno(r, "Could not add address: %m");
+        }
+
         return 0;
 }
 
@@ -702,5 +728,5 @@ int config_parse_label(const char *unit,
 bool address_is_ready(const Address *a) {
         assert(a);
 
-        return !(a->flags & (IFA_F_TENTATIVE | IFA_F_DEPRECATED));
+        return a->added && !(a->flags & (IFA_F_TENTATIVE | IFA_F_DEPRECATED));
 }
diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h
index cb29acc7be..fd309bebb6 100644
--- a/src/network/networkd-address.h
+++ b/src/network/networkd-address.h
@@ -52,7 +52,8 @@ struct Address {
         union in_addr_union in_addr;
         union in_addr_union in_addr_peer;
 
-        bool ip_masquerade_done;
+        bool added:1;
+        bool ip_masquerade_done:1;
 
         LIST_FIELDS(Address, addresses);
 };
@@ -60,7 +61,7 @@ struct Address {
 int address_new_static(Network *network, unsigned section, Address **ret);
 int address_new(Address **ret);
 void address_free(Address *address);
-int address_add(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret);
+int address_add_foreign(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret);
 int address_get(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret);
 int address_update(Address *address, unsigned char flags, unsigned char scope, struct ifa_cacheinfo *cinfo);
 int address_drop(Address *address);
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 1ac76c4255..c0f27aaca8 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -297,6 +297,11 @@ static void link_free(Link *link) {
 
         set_free(link->addresses);
 
+        while (!set_isempty(link->addresses_foreign))
+                address_free(set_first(link->addresses_foreign));
+
+        set_free(link->addresses_foreign);
+
         while ((address = link->pool_addresses)) {
                 LIST_REMOVE(addresses, link->pool_addresses, address);
                 address_free(address);
@@ -508,7 +513,9 @@ void link_check_ready(Link *link) {
         Iterator i;
 
         assert(link);
-        assert(link->network);
+
+        if (!link->network)
+                return;
 
         if (!link->static_configured)
                 return;
@@ -2324,6 +2331,15 @@ static void link_update_operstate(Link *link) {
                                 scope = address->scope;
                 }
 
+                /* for operstate we also take foreign addresses into account */
+                SET_FOREACH(address, link->addresses_foreign, i) {
+                        if (!address_is_ready(address))
+                                continue;
+
+                        if (address->scope < scope)
+                                scope = address->scope;
+                }
+
                 if (scope < RT_SCOPE_SITE)
                         /* universally accessible addresses found */
                         operstate = LINK_OPERSTATE_ROUTABLE;
diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h
index a94bb2f714..90ad08a306 100644
--- a/src/network/networkd-link.h
+++ b/src/network/networkd-link.h
@@ -84,6 +84,7 @@ struct Link {
         unsigned enslaving;
 
         Set *addresses;
+        Set *addresses_foreign;
 
         sd_dhcp_client *dhcp_client;
         sd_dhcp_lease *dhcp_lease;
diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c
index 9a7e71fce8..2b83ee81ed 100644
--- a/src/network/networkd-manager.c
+++ b/src/network/networkd-manager.c
@@ -400,7 +400,8 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
                 if (address)
                         log_link_debug(link, "Updating address: %s/%u (valid for %s)", buf, prefixlen, valid_str);
                 else {
-                        r = address_add(link, family, &in_addr, prefixlen, &address);
+                        /* An address appeared that we did not request */
+                        r = address_add_foreign(link, family, &in_addr, prefixlen, &address);
                         if (r < 0) {
                                 log_link_warning_errno(link, r, "Failed to add address %s/%u: %m", buf, prefixlen);
                                 return 0;
-- 
cgit v1.2.3-54-g00ecf


From ed9e361a8a798f9fee353b5c7e0e23308e0d329f Mon Sep 17 00:00:00 2001
From: Tom Gundersen <teg@jklm.no>
Date: Fri, 9 Oct 2015 23:43:52 +0200
Subject: networkd: route - simplify route_new()

---
 src/network/networkd-dhcp4.c  | 16 ++++++++++------
 src/network/networkd-ipv4ll.c |  5 +++--
 src/network/networkd-route.c  |  7 ++++---
 src/network/networkd-route.h  |  2 +-
 4 files changed, 18 insertions(+), 12 deletions(-)

(limited to 'src')

diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
index 6ba16581f8..22594a9415 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -72,11 +72,13 @@ static int link_set_dhcp_routes(Link *link) {
                 if (r < 0)
                         return log_link_warning_errno(link, r, "DHCP error: could not get address: %m");
 
-                r = route_new(&route, RTPROT_DHCP);
+                r = route_new(&route);
                 if (r < 0)
                         return log_link_error_errno(link, r, "Could not allocate route: %m");
 
-                r = route_new(&route_gw, RTPROT_DHCP);
+                route->protocol = RTPROT_DHCP;
+
+                r = route_new(&route_gw);
                 if (r < 0)
                         return log_link_error_errno(link, r,  "Could not allocate route: %m");
 
@@ -88,6 +90,7 @@ static int link_set_dhcp_routes(Link *link) {
                 route_gw->dst_prefixlen = 32;
                 route_gw->prefsrc_addr.in = address;
                 route_gw->scope = RT_SCOPE_LINK;
+                route_gw->protocol = RTPROT_DHCP;
                 route_gw->metrics = link->network->dhcp_route_metric;
 
                 r = route_configure(route_gw, link, &dhcp4_route_handler);
@@ -120,11 +123,12 @@ static int link_set_dhcp_routes(Link *link) {
         for (i = 0; i < n; i++) {
                 _cleanup_route_free_ Route *route = NULL;
 
-                r = route_new(&route, RTPROT_DHCP);
+                r = route_new(&route);
                 if (r < 0)
                         return log_link_error_errno(link, r, "Could not allocate route: %m");
 
                 route->family = AF_INET;
+                route->protocol = RTPROT_DHCP;
                 route->in_addr.in = static_routes[i].gw_addr;
                 route->dst_addr.in = static_routes[i].dst_addr;
                 route->dst_prefixlen = static_routes[i].dst_prefixlen;
@@ -162,7 +166,7 @@ static int dhcp_lease_lost(Link *link) {
                         for (i = 0; i < n; i++) {
                                 _cleanup_route_free_ Route *route = NULL;
 
-                                r = route_new(&route, RTPROT_UNSPEC);
+                                r = route_new(&route);
                                 if (r >= 0) {
                                         route->family = AF_INET;
                                         route->in_addr.in = routes[i].gw_addr;
@@ -183,7 +187,7 @@ static int dhcp_lease_lost(Link *link) {
                         _cleanup_route_free_ Route *route_gw = NULL;
                         _cleanup_route_free_ Route *route = NULL;
 
-                        r = route_new(&route_gw, RTPROT_UNSPEC);
+                        r = route_new(&route_gw);
                         if (r >= 0) {
                                 route_gw->family = AF_INET;
                                 route_gw->dst_addr.in = gateway;
@@ -194,7 +198,7 @@ static int dhcp_lease_lost(Link *link) {
                                            &link_route_remove_handler);
                         }
 
-                        r = route_new(&route, RTPROT_UNSPEC);
+                        r = route_new(&route);
                         if (r >= 0) {
                                 route->family = AF_INET;
                                 route->in_addr.in = gateway;
diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c
index 26397b273f..2fdb77ef6c 100644
--- a/src/network/networkd-ipv4ll.c
+++ b/src/network/networkd-ipv4ll.c
@@ -55,7 +55,7 @@ static int ipv4ll_address_lost(Link *link) {
 
         address_remove(address, link, &link_address_remove_handler);
 
-        r = route_new(&route, RTPROT_UNSPEC);
+        r = route_new(&route);
         if (r < 0) {
                 log_link_error_errno(link, r, "Could not allocate route: %m");
                 return r;
@@ -149,12 +149,13 @@ static int ipv4ll_address_claimed(sd_ipv4ll *ll, Link *link) {
 
         link->ipv4ll_address = false;
 
-        r = route_new(&route, RTPROT_STATIC);
+        r = route_new(&route);
         if (r < 0)
                 return r;
 
         route->family = AF_INET;
         route->scope = RT_SCOPE_LINK;
+        route->protocol = RTPROT_STATIC;
         route->metrics = IPV4LL_ROUTE_METRIC;
 
         r = route_configure(route, link, ipv4ll_route_handler);
diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c
index 1c8302ffaa..66b165f661 100644
--- a/src/network/networkd-route.c
+++ b/src/network/networkd-route.c
@@ -26,7 +26,7 @@
 #include "networkd.h"
 #include "networkd-route.h"
 
-int route_new(Route **ret, unsigned char rtm_protocol) {
+int route_new(Route **ret) {
         _cleanup_route_free_ Route *route = NULL;
 
         route = new0(Route, 1);
@@ -35,7 +35,7 @@ int route_new(Route **ret, unsigned char rtm_protocol) {
 
         route->family = AF_UNSPEC;
         route->scope = RT_SCOPE_UNIVERSE;
-        route->protocol = rtm_protocol;
+        route->protocol = RTPROT_UNSPEC;
 
         *ret = route;
         route = NULL;
@@ -58,10 +58,11 @@ int route_new_static(Network *network, unsigned section, Route **ret) {
                 }
         }
 
-        r = route_new(&route, RTPROT_STATIC);
+        r = route_new(&route);
         if (r < 0)
                 return r;
 
+        route->protocol = RTPROT_STATIC;
         route->network = network;
 
         LIST_PREPEND(routes, network->static_routes, route);
diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h
index e3ed1be866..7f2d7f37f4 100644
--- a/src/network/networkd-route.h
+++ b/src/network/networkd-route.h
@@ -46,7 +46,7 @@ struct Route {
 };
 
 int route_new_static(Network *network, unsigned section, Route **ret);
-int route_new(Route **ret, unsigned char rtm_protocol);
+int route_new(Route **ret);
 void route_free(Route *route);
 int route_configure(Route *route, Link *link, sd_netlink_message_handler_t callback);
 int route_remove(Route *route, Link *link, sd_netlink_message_handler_t callback);
-- 
cgit v1.2.3-54-g00ecf


From bb7ae737a36769553475b1ada882b06869b75b00 Mon Sep 17 00:00:00 2001
From: Tom Gundersen <teg@jklm.no>
Date: Sat, 3 Oct 2015 18:40:28 +0200
Subject: networkd: route - add hash_ops

---
 src/network/networkd-route.c | 73 +++++++++++++++++++++++++++++++++++++++++++-
 src/network/networkd-route.h |  3 ++
 2 files changed, 75 insertions(+), 1 deletion(-)

(limited to 'src')

diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c
index 66b165f661..aa4ec230ab 100644
--- a/src/network/networkd-route.c
+++ b/src/network/networkd-route.c
@@ -19,9 +19,10 @@
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
-#include "util.h"
 #include "conf-parser.h"
+#include "in-addr-util.h"
 #include "netlink-util.h"
+#include "util.h"
 
 #include "networkd.h"
 #include "networkd-route.h"
@@ -36,6 +37,7 @@ int route_new(Route **ret) {
         route->family = AF_UNSPEC;
         route->scope = RT_SCOPE_UNIVERSE;
         route->protocol = RTPROT_UNSPEC;
+        route->table = RT_TABLE_DEFAULT;
 
         *ret = route;
         route = NULL;
@@ -94,6 +96,75 @@ void route_free(Route *route) {
         free(route);
 }
 
+static void route_hash_func(const void *b, struct siphash *state) {
+        const Route *route = b;
+
+        assert(route);
+
+        siphash24_compress(&route->family, sizeof(route->family), state);
+
+        switch (route->family) {
+        case AF_INET:
+        case AF_INET6:
+                /* Equality of routes are given by the 4-touple
+                   (dst_prefix,dst_prefixlen,tos,priority,table) */
+                siphash24_compress(&route->dst_addr, FAMILY_ADDRESS_SIZE(route->family), state);
+                siphash24_compress(&route->dst_prefixlen, sizeof(route->dst_prefixlen), state);
+                siphash24_compress(&route->tos, sizeof(route->tos), state);
+                siphash24_compress(&route->priority, sizeof(route->priority), state);
+                siphash24_compress(&route->table, sizeof(route->table), state);
+
+                break;
+        default:
+                /* treat any other address family as AF_UNSPEC */
+                break;
+        }
+}
+
+static int route_compare_func(const void *_a, const void *_b) {
+        const Route *a = _a, *b = _b;
+
+        if (a->family < b->family)
+                return -1;
+        if (a->family > b->family)
+                return 1;
+
+        switch (a->family) {
+        case AF_INET:
+        case AF_INET6:
+                //TODO: check IPv6 routes
+                if (a->dst_prefixlen < b->dst_prefixlen)
+                        return -1;
+                if (a->dst_prefixlen > b->dst_prefixlen)
+                        return 1;
+
+                if (a->tos < b->tos)
+                        return -1;
+                if (a->tos > b->tos)
+                        return 1;
+
+                if (a->priority < b->priority)
+                        return -1;
+                if (a->priority > b->priority)
+                        return 1;
+
+                if (a->table < b->table)
+                        return -1;
+                if (a->table > b->table)
+                        return 1;
+
+                return memcmp(&a->dst_addr, &b->dst_addr, FAMILY_ADDRESS_SIZE(a->family));
+        default:
+                /* treat any other address family as AF_UNSPEC */
+                return 0;
+        }
+}
+
+static const struct hash_ops route_hash_ops = {
+        .hash = route_hash_func,
+        .compare = route_compare_func
+};
+
 int route_remove(Route *route, Link *link,
                sd_netlink_message_handler_t callback) {
         _cleanup_netlink_message_unref_ sd_netlink_message *req = NULL;
diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h
index 7f2d7f37f4..c9972e4933 100644
--- a/src/network/networkd-route.h
+++ b/src/network/networkd-route.h
@@ -36,6 +36,9 @@ struct Route {
         unsigned char scope;
         uint32_t metrics;
         unsigned char protocol;  /* RTPROT_* */
+        unsigned char tos;
+        unsigned char priority;
+        unsigned char table;
 
         union in_addr_union in_addr;
         union in_addr_union dst_addr;
-- 
cgit v1.2.3-54-g00ecf


From e7780c8d44ee32ecf6127f45c0188483c041e2e5 Mon Sep 17 00:00:00 2001
From: Tom Gundersen <teg@jklm.no>
Date: Wed, 30 Sep 2015 15:32:16 +0200
Subject: networkd: link - serialize addresses

---
 src/network/networkd-address.c | 11 ++++++++++-
 src/network/networkd-link.c    | 21 +++++++++++++++++++--
 2 files changed, 29 insertions(+), 3 deletions(-)

(limited to 'src')

diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
index da458dcb64..8b3694d07f 100644
--- a/src/network/networkd-address.c
+++ b/src/network/networkd-address.c
@@ -98,6 +98,7 @@ void address_free(Address *address) {
         if (address->link) {
                 set_remove(address->link->addresses, address);
                 set_remove(address->link->addresses_foreign, address);
+                link_save(address->link);
         }
 
         free(address);
@@ -270,7 +271,15 @@ int address_add_foreign(Link *link, int family, const union in_addr_union *in_ad
 }
 
 static int address_add(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret) {
-        return address_add_internal(link, &link->addresses, family, in_addr, prefixlen, ret);
+        int r;
+
+        r = address_add_internal(link, &link->addresses, family, in_addr, prefixlen, ret);
+        if (r < 0)
+                return r;
+
+        link_save(link);
+
+        return 0;
 }
 
 static int address_release(Address *address) {
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index c0f27aaca8..1b55f99646 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -2364,6 +2364,8 @@ int link_save(Link *link) {
         _cleanup_free_ char *temp_path = NULL;
         _cleanup_fclose_ FILE *f = NULL;
         const char *admin_state, *oper_state;
+        Address *a;
+        Iterator i;
         int r;
 
         assert(link);
@@ -2541,11 +2543,27 @@ int link_save(Link *link) {
 
                 fprintf(f, "LLMNR=%s\n",
                         resolve_support_to_string(link->network->llmnr));
+
+                fprintf(f, "ADDRESSES=");
+                space = false;
+                SET_FOREACH(a, link->addresses, i) {
+                        _cleanup_free_ char *address_str = NULL;
+
+                        r = in_addr_to_string(a->family, &a->in_addr, &address_str);
+                        if (r < 0)
+                                goto fail;
+
+                        if (space)
+                                fputc(' ', f);
+                        fprintf(f, "%s%s/%u", space ? " " : "", address_str, a->prefixlen);
+                        space = true;
+                }
+
+                fputs("\n", f);
         }
 
         if (!hashmap_isempty(link->bound_to_links)) {
                 Link *carrier;
-                Iterator i;
                 bool space = false;
 
                 fputs("CARRIER_BOUND_TO=", f);
@@ -2561,7 +2579,6 @@ int link_save(Link *link) {
 
         if (!hashmap_isempty(link->bound_by_links)) {
                 Link *carrier;
-                Iterator i;
                 bool space = false;
 
                 fputs("CARRIER_BOUND_BY=", f);
-- 
cgit v1.2.3-54-g00ecf


From 84de38c56915e14c148f558c6acc489a00755696 Mon Sep 17 00:00:00 2001
From: Tom Gundersen <teg@jklm.no>
Date: Wed, 30 Sep 2015 18:17:43 +0200
Subject: networkd: manager/link - only serialize once per event-loop iteration

Every time the state is written out we may trigger third-party apps, so
let's be a bit more careful about writing this out unnecessarily.
---
 src/network/networkd-address.c |   6 +-
 src/network/networkd-link.c    | 162 +++++++++-------
 src/network/networkd-link.h    |   3 +
 src/network/networkd-manager.c | 414 +++++++++++++++++++++++------------------
 src/network/networkd-network.c |   7 +-
 src/network/networkd.h         |   7 +-
 6 files changed, 337 insertions(+), 262 deletions(-)

(limited to 'src')

diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
index 8b3694d07f..5d443e9b9b 100644
--- a/src/network/networkd-address.c
+++ b/src/network/networkd-address.c
@@ -98,7 +98,6 @@ void address_free(Address *address) {
         if (address->link) {
                 set_remove(address->link->addresses, address);
                 set_remove(address->link->addresses_foreign, address);
-                link_save(address->link);
         }
 
         free(address);
@@ -277,7 +276,8 @@ static int address_add(Link *link, int family, const union in_addr_union *in_add
         if (r < 0)
                 return r;
 
-        link_save(link);
+        link_update_operstate(link);
+        link_dirty(link);
 
         return 0;
 }
@@ -334,6 +334,8 @@ int address_drop(Address *address) {
         address_release(address);
         address_free(address);
 
+        link_update_operstate(link);
+
         if (link && !ready)
                 link_check_ready(link);
 
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 1b55f99646..ec4b082542 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -130,6 +130,57 @@ static IPv6PrivacyExtensions link_ipv6_privacy_extensions(Link *link) {
         return link->network->ipv6_privacy_extensions;
 }
 
+void link_update_operstate(Link *link) {
+        LinkOperationalState operstate;
+        assert(link);
+
+        if (link->kernel_operstate == IF_OPER_DORMANT)
+                operstate = LINK_OPERSTATE_DORMANT;
+        else if (link_has_carrier(link)) {
+                Address *address;
+                uint8_t scope = RT_SCOPE_NOWHERE;
+                Iterator i;
+
+                /* if we have carrier, check what addresses we have */
+                SET_FOREACH(address, link->addresses, i) {
+                        if (!address_is_ready(address))
+                                continue;
+
+                        if (address->scope < scope)
+                                scope = address->scope;
+                }
+
+                /* for operstate we also take foreign addresses into account */
+                SET_FOREACH(address, link->addresses_foreign, i) {
+                        if (!address_is_ready(address))
+                                continue;
+
+                        if (address->scope < scope)
+                                scope = address->scope;
+                }
+
+                if (scope < RT_SCOPE_SITE)
+                        /* universally accessible addresses found */
+                        operstate = LINK_OPERSTATE_ROUTABLE;
+                else if (scope < RT_SCOPE_HOST)
+                        /* only link or site local addresses found */
+                        operstate = LINK_OPERSTATE_DEGRADED;
+                else
+                        /* no useful addresses found */
+                        operstate = LINK_OPERSTATE_CARRIER;
+        } else if (link->flags & IFF_UP)
+                operstate = LINK_OPERSTATE_NO_CARRIER;
+        else
+                operstate = LINK_OPERSTATE_OFF;
+
+        if (link->operstate != operstate) {
+                link->operstate = operstate;
+                link_send_changed(link, "OperationalState", NULL);
+                link_dirty(link);
+                manager_dirty(link->manager);
+        }
+}
+
 #define FLAG_STRING(string, flag, old, new) \
         (((old ^ new) & flag) \
                 ? ((old & flag) ? (" -" string) : (" +" string)) \
@@ -202,7 +253,7 @@ static int link_update_flags(Link *link, sd_netlink_message *m) {
         link->flags = flags;
         link->kernel_operstate = operstate;
 
-        link_save(link);
+        link_update_operstate(link);
 
         return 0;
 }
@@ -326,6 +377,7 @@ static void link_free(Link *link) {
 
         free(link->ifname);
 
+        (void)unlink(link->state_file);
         free(link->state_file);
 
         udev_device_unref(link->udev_device);
@@ -404,7 +456,7 @@ static void link_enter_unmanaged(Link *link) {
 
         link_set_state(link, LINK_STATE_UNMANAGED);
 
-        link_save(link);
+        link_dirty(link);
 }
 
 static int link_stop_clients(Link *link) {
@@ -462,7 +514,7 @@ void link_enter_failed(Link *link) {
 
         link_stop_clients(link);
 
-        link_save(link);
+        link_dirty(link);
 }
 
 static Address* link_find_dhcp_server_address(Link *link) {
@@ -503,7 +555,7 @@ static int link_enter_configured(Link *link) {
 
         link_set_state(link, LINK_STATE_CONFIGURED);
 
-        link_save(link);
+        link_dirty(link);
 
         return 0;
 }
@@ -1460,14 +1512,14 @@ static int link_new_bound_by_list(Link *link) {
         }
 
         if (list_updated)
-                link_save(link);
+                link_dirty(link);
 
         HASHMAP_FOREACH (carrier, link->bound_by_links, i) {
                 r = link_put_carrier(carrier, link, &carrier->bound_to_links);
                 if (r < 0)
                         return r;
 
-                link_save(carrier);
+                link_dirty(carrier);
         }
 
         return 0;
@@ -1502,14 +1554,14 @@ static int link_new_bound_to_list(Link *link) {
         }
 
         if (list_updated)
-                link_save(link);
+                link_dirty(link);
 
         HASHMAP_FOREACH (carrier, link->bound_to_links, i) {
                 r = link_put_carrier(carrier, link, &carrier->bound_by_links);
                 if (r < 0)
                         return r;
 
-                link_save(carrier);
+                link_dirty(carrier);
         }
 
         return 0;
@@ -1545,7 +1597,7 @@ static void link_free_bound_to_list(Link *link) {
                 hashmap_remove(link->bound_to_links, INT_TO_PTR(bound_to->ifindex));
 
                 if (hashmap_remove(bound_to->bound_by_links, INT_TO_PTR(link->ifindex)))
-                        link_save(bound_to);
+                        link_dirty(bound_to);
         }
 
         return;
@@ -1559,7 +1611,7 @@ static void link_free_bound_by_list(Link *link) {
                 hashmap_remove(link->bound_by_links, INT_TO_PTR(bound_by->ifindex));
 
                 if (hashmap_remove(bound_by->bound_to_links, INT_TO_PTR(link->ifindex))) {
-                        link_save(bound_by);
+                        link_dirty(bound_by);
                         link_handle_bound_to_list(bound_by);
                 }
         }
@@ -1583,7 +1635,7 @@ static void link_free_carrier_maps(Link *link) {
         }
 
         if (list_updated)
-                link_save(link);
+                link_dirty(link);
 
         return;
 }
@@ -1598,6 +1650,7 @@ void link_drop(Link *link) {
 
         log_link_debug(link, "Link removed");
 
+        (void)unlink(link->state_file);
         link_unref(link);
 
         return;
@@ -1667,7 +1720,7 @@ static int link_enter_join_netdev(Link *link) {
 
         link_set_state(link, LINK_STATE_ENSLAVING);
 
-        link_save(link);
+        link_dirty(link);
 
         if (!link->network->bridge &&
             !link->network->bond &&
@@ -2311,55 +2364,6 @@ int link_update(Link *link, sd_netlink_message *m) {
         return 0;
 }
 
-static void link_update_operstate(Link *link) {
-        LinkOperationalState operstate;
-        assert(link);
-
-        if (link->kernel_operstate == IF_OPER_DORMANT)
-                operstate = LINK_OPERSTATE_DORMANT;
-        else if (link_has_carrier(link)) {
-                Address *address;
-                uint8_t scope = RT_SCOPE_NOWHERE;
-                Iterator i;
-
-                /* if we have carrier, check what addresses we have */
-                SET_FOREACH(address, link->addresses, i) {
-                        if (!address_is_ready(address))
-                                continue;
-
-                        if (address->scope < scope)
-                                scope = address->scope;
-                }
-
-                /* for operstate we also take foreign addresses into account */
-                SET_FOREACH(address, link->addresses_foreign, i) {
-                        if (!address_is_ready(address))
-                                continue;
-
-                        if (address->scope < scope)
-                                scope = address->scope;
-                }
-
-                if (scope < RT_SCOPE_SITE)
-                        /* universally accessible addresses found */
-                        operstate = LINK_OPERSTATE_ROUTABLE;
-                else if (scope < RT_SCOPE_HOST)
-                        /* only link or site local addresses found */
-                        operstate = LINK_OPERSTATE_DEGRADED;
-                else
-                        /* no useful addresses found */
-                        operstate = LINK_OPERSTATE_CARRIER;
-        } else if (link->flags & IFF_UP)
-                operstate = LINK_OPERSTATE_NO_CARRIER;
-        else
-                operstate = LINK_OPERSTATE_OFF;
-
-        if (link->operstate != operstate) {
-                link->operstate = operstate;
-                link_send_changed(link, "OperationalState", NULL);
-        }
-}
-
 int link_save(Link *link) {
         _cleanup_free_ char *temp_path = NULL;
         _cleanup_fclose_ FILE *f = NULL;
@@ -2373,12 +2377,6 @@ int link_save(Link *link) {
         assert(link->lease_file);
         assert(link->manager);
 
-        link_update_operstate(link);
-
-        r = manager_save(link->manager);
-        if (r < 0)
-                return r;
-
         if (link->state == LINK_STATE_LINGER) {
                 unlink(link->state_file);
                 return 0;
@@ -2553,8 +2551,6 @@ int link_save(Link *link) {
                         if (r < 0)
                                 goto fail;
 
-                        if (space)
-                                fputc(' ', f);
                         fprintf(f, "%s%s/%u", space ? " " : "", address_str, a->prefixlen);
                         space = true;
                 }
@@ -2645,6 +2641,34 @@ fail:
         return log_link_error_errno(link, r, "Failed to save link data to %s: %m", link->state_file);
 }
 
+/* The serialized state in /run is no longer up-to-date. */
+void link_dirty(Link *link) {
+        int r;
+
+        assert(link);
+
+        r = set_ensure_allocated(&link->manager->dirty_links, NULL);
+        if (r < 0)
+                /* allocation errors are ignored */
+                return;
+
+        r = set_put(link->manager->dirty_links, link);
+        if (r < 0)
+                /* allocation errors are ignored */
+                return;
+
+        link_ref(link);
+}
+
+/* The serialized state in /run is up-to-date */
+void link_clean(Link *link) {
+        assert(link);
+        assert(link->manager);
+
+        set_remove(link->manager->dirty_links, link);
+        link_unref(link);
+}
+
 static const char* const link_state_table[_LINK_STATE_MAX] = {
         [LINK_STATE_PENDING] = "pending",
         [LINK_STATE_ENSLAVING] = "configuring",
diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h
index 90ad08a306..af2ba11701 100644
--- a/src/network/networkd-link.h
+++ b/src/network/networkd-link.h
@@ -129,8 +129,11 @@ int link_initialized(Link *link, struct udev_device *device);
 
 void link_check_ready(Link *link);
 
+void link_update_operstate(Link *link);
 int link_update(Link *link, sd_netlink_message *message);
 
+void link_dirty(Link *link);
+void link_clean(Link *link);
 int link_save(Link *link);
 
 int link_carrier_reset(Link *link);
diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c
index 2b83ee81ed..76d5aa5585 100644
--- a/src/network/networkd-manager.c
+++ b/src/network/networkd-manager.c
@@ -573,6 +573,213 @@ static int manager_connect_rtnl(Manager *m) {
         return 0;
 }
 
+static int set_put_in_addr(Set *s, const struct in_addr *address) {
+        char *p;
+        int r;
+
+        assert(s);
+
+        r = in_addr_to_string(AF_INET, (const union in_addr_union*) address, &p);
+        if (r < 0)
+                return r;
+
+        r = set_consume(s, p);
+        if (r == -EEXIST)
+                return 0;
+
+        return r;
+}
+
+static int set_put_in_addrv(Set *s, const struct in_addr *addresses, int n) {
+        int r, i, c = 0;
+
+        assert(s);
+        assert(n <= 0 || addresses);
+
+        for (i = 0; i < n; i++) {
+                r = set_put_in_addr(s, addresses+i);
+                if (r < 0)
+                        return r;
+
+                c += r;
+        }
+
+        return c;
+}
+
+static void print_string_set(FILE *f, const char *field, Set *s) {
+        bool space = false;
+        Iterator i;
+        char *p;
+
+        if (set_isempty(s))
+                return;
+
+        fputs(field, f);
+
+        SET_FOREACH(p, s, i) {
+                if (space)
+                        fputc(' ', f);
+                fputs(p, f);
+                space = true;
+        }
+        fputc('\n', f);
+}
+
+static int manager_save(Manager *m) {
+        _cleanup_set_free_free_ Set *dns = NULL, *ntp = NULL, *domains = NULL;
+        Link *link;
+        Iterator i;
+        _cleanup_free_ char *temp_path = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        LinkOperationalState operstate = LINK_OPERSTATE_OFF;
+        const char *operstate_str;
+        int r;
+
+        assert(m);
+        assert(m->state_file);
+
+        /* We add all NTP and DNS server to a set, to filter out duplicates */
+        dns = set_new(&string_hash_ops);
+        if (!dns)
+                return -ENOMEM;
+
+        ntp = set_new(&string_hash_ops);
+        if (!ntp)
+                return -ENOMEM;
+
+        domains = set_new(&string_hash_ops);
+        if (!domains)
+                return -ENOMEM;
+
+        HASHMAP_FOREACH(link, m->links, i) {
+                if (link->flags & IFF_LOOPBACK)
+                        continue;
+
+                if (link->operstate > operstate)
+                        operstate = link->operstate;
+
+                if (!link->network)
+                        continue;
+
+                /* First add the static configured entries */
+                r = set_put_strdupv(dns, link->network->dns);
+                if (r < 0)
+                        return r;
+
+                r = set_put_strdupv(ntp, link->network->ntp);
+                if (r < 0)
+                        return r;
+
+                r = set_put_strdupv(domains, link->network->domains);
+                if (r < 0)
+                        return r;
+
+                if (!link->dhcp_lease)
+                        continue;
+
+                /* Secondly, add the entries acquired via DHCP */
+                if (link->network->dhcp_dns) {
+                        const struct in_addr *addresses;
+
+                        r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses);
+                        if (r > 0) {
+                                r = set_put_in_addrv(dns, addresses, r);
+                                if (r < 0)
+                                        return r;
+                        } else if (r < 0 && r != -ENODATA)
+                                return r;
+                }
+
+                if (link->network->dhcp_ntp) {
+                        const struct in_addr *addresses;
+
+                        r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses);
+                        if (r > 0) {
+                                r = set_put_in_addrv(ntp, addresses, r);
+                                if (r < 0)
+                                        return r;
+                        } else if (r < 0 && r != -ENODATA)
+                                return r;
+                }
+
+                if (link->network->dhcp_domains) {
+                        const char *domainname;
+
+                        r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname);
+                        if (r >= 0) {
+                                r = set_put_strdup(domains, domainname);
+                                if (r < 0)
+                                        return r;
+                        } else if (r != -ENODATA)
+                                return r;
+                }
+        }
+
+        operstate_str = link_operstate_to_string(operstate);
+        assert(operstate_str);
+
+        r = fopen_temporary(m->state_file, &f, &temp_path);
+        if (r < 0)
+                return r;
+
+        fchmod(fileno(f), 0644);
+
+        fprintf(f,
+                "# This is private data. Do not parse.\n"
+                "OPER_STATE=%s\n", operstate_str);
+
+        print_string_set(f, "DNS=", dns);
+        print_string_set(f, "NTP=", ntp);
+        print_string_set(f, "DOMAINS=", domains);
+
+        r = fflush_and_check(f);
+        if (r < 0)
+                goto fail;
+
+        if (rename(temp_path, m->state_file) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        if (m->operational_state != operstate) {
+                m->operational_state = operstate;
+                r = manager_send_changed(m, "OperationalState", NULL);
+                if (r < 0)
+                        log_error_errno(r, "Could not emit changed OperationalState: %m");
+        }
+
+        m->dirty = false;
+
+        return 0;
+
+fail:
+        (void) unlink(m->state_file);
+        (void) unlink(temp_path);
+
+        return log_error_errno(r, "Failed to save network state to %s: %m", m->state_file);
+}
+
+static int manager_dirty_handler(sd_event_source *s, void *userdata) {
+        Manager *m = userdata;
+        Link *link;
+        Iterator i;
+        int r;
+
+        assert(m);
+
+        if (m->dirty)
+                manager_save(m);
+
+        SET_FOREACH(link, m->dirty_links, i) {
+                r = link_save(link);
+                if (r >= 0)
+                        link_clean(link);
+        }
+
+        return 1;
+}
+
 int manager_new(Manager **ret) {
         _cleanup_manager_free_ Manager *m = NULL;
         int r;
@@ -594,6 +801,10 @@ int manager_new(Manager **ret) {
         sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
         sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
 
+        r = sd_event_add_post(m->event, NULL, manager_dirty_handler, m);
+        if (r < 0)
+                return r;
+
         r = manager_connect_rtnl(m);
         if (r < 0)
                 return r;
@@ -688,8 +899,19 @@ static bool manager_check_idle(void *userdata) {
 }
 
 int manager_run(Manager *m) {
+        Link *link;
+        Iterator i;
+
         assert(m);
 
+        /* The dirty handler will deal with future serialization, but the first one
+           must be done explicitly. */
+
+        manager_save(m);
+
+        HASHMAP_FOREACH(link, m->links, i)
+                link_save(link);
+
         if (m->bus)
                 return bus_event_loop_with_idle(
                                 m->event,
@@ -795,191 +1017,6 @@ int manager_rtnl_enumerate_addresses(Manager *m) {
         return r;
 }
 
-static int set_put_in_addr(Set *s, const struct in_addr *address) {
-        char *p;
-        int r;
-
-        assert(s);
-
-        r = in_addr_to_string(AF_INET, (const union in_addr_union*) address, &p);
-        if (r < 0)
-                return r;
-
-        r = set_consume(s, p);
-        if (r == -EEXIST)
-                return 0;
-
-        return r;
-}
-
-static int set_put_in_addrv(Set *s, const struct in_addr *addresses, int n) {
-        int r, i, c = 0;
-
-        assert(s);
-        assert(n <= 0 || addresses);
-
-        for (i = 0; i < n; i++) {
-                r = set_put_in_addr(s, addresses+i);
-                if (r < 0)
-                        return r;
-
-                c += r;
-        }
-
-        return c;
-}
-
-static void print_string_set(FILE *f, const char *field, Set *s) {
-        bool space = false;
-        Iterator i;
-        char *p;
-
-        if (set_isempty(s))
-                return;
-
-        fputs(field, f);
-
-        SET_FOREACH(p, s, i) {
-                if (space)
-                        fputc(' ', f);
-                fputs(p, f);
-                space = true;
-        }
-        fputc('\n', f);
-}
-
-int manager_save(Manager *m) {
-        _cleanup_set_free_free_ Set *dns = NULL, *ntp = NULL, *domains = NULL;
-        Link *link;
-        Iterator i;
-        _cleanup_free_ char *temp_path = NULL;
-        _cleanup_fclose_ FILE *f = NULL;
-        LinkOperationalState operstate = LINK_OPERSTATE_OFF;
-        const char *operstate_str;
-        int r;
-
-        assert(m);
-        assert(m->state_file);
-
-        /* We add all NTP and DNS server to a set, to filter out duplicates */
-        dns = set_new(&string_hash_ops);
-        if (!dns)
-                return -ENOMEM;
-
-        ntp = set_new(&string_hash_ops);
-        if (!ntp)
-                return -ENOMEM;
-
-        domains = set_new(&string_hash_ops);
-        if (!domains)
-                return -ENOMEM;
-
-        HASHMAP_FOREACH(link, m->links, i) {
-                if (link->flags & IFF_LOOPBACK)
-                        continue;
-
-                if (link->operstate > operstate)
-                        operstate = link->operstate;
-
-                if (!link->network)
-                        continue;
-
-                /* First add the static configured entries */
-                r = set_put_strdupv(dns, link->network->dns);
-                if (r < 0)
-                        return r;
-
-                r = set_put_strdupv(ntp, link->network->ntp);
-                if (r < 0)
-                        return r;
-
-                r = set_put_strdupv(domains, link->network->domains);
-                if (r < 0)
-                        return r;
-
-                if (!link->dhcp_lease)
-                        continue;
-
-                /* Secondly, add the entries acquired via DHCP */
-                if (link->network->dhcp_dns) {
-                        const struct in_addr *addresses;
-
-                        r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses);
-                        if (r > 0) {
-                                r = set_put_in_addrv(dns, addresses, r);
-                                if (r < 0)
-                                        return r;
-                        } else if (r < 0 && r != -ENODATA)
-                                return r;
-                }
-
-                if (link->network->dhcp_ntp) {
-                        const struct in_addr *addresses;
-
-                        r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses);
-                        if (r > 0) {
-                                r = set_put_in_addrv(ntp, addresses, r);
-                                if (r < 0)
-                                        return r;
-                        } else if (r < 0 && r != -ENODATA)
-                                return r;
-                }
-
-                if (link->network->dhcp_domains) {
-                        const char *domainname;
-
-                        r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname);
-                        if (r >= 0) {
-                                r = set_put_strdup(domains, domainname);
-                                if (r < 0)
-                                        return r;
-                        } else if (r != -ENODATA)
-                                return r;
-                }
-        }
-
-        operstate_str = link_operstate_to_string(operstate);
-        assert(operstate_str);
-
-        r = fopen_temporary(m->state_file, &f, &temp_path);
-        if (r < 0)
-                return r;
-
-        fchmod(fileno(f), 0644);
-
-        fprintf(f,
-                "# This is private data. Do not parse.\n"
-                "OPER_STATE=%s\n", operstate_str);
-
-        print_string_set(f, "DNS=", dns);
-        print_string_set(f, "NTP=", ntp);
-        print_string_set(f, "DOMAINS=", domains);
-
-        r = fflush_and_check(f);
-        if (r < 0)
-                goto fail;
-
-        if (rename(temp_path, m->state_file) < 0) {
-                r = -errno;
-                goto fail;
-        }
-
-        if (m->operational_state != operstate) {
-                m->operational_state = operstate;
-                r = manager_send_changed(m, "OperationalState", NULL);
-                if (r < 0)
-                        log_error_errno(r, "Could not emit changed OperationalState: %m");
-        }
-
-        return 0;
-
-fail:
-        (void) unlink(m->state_file);
-        (void) unlink(temp_path);
-
-        return log_error_errno(r, "Failed to save network state to %s: %m", m->state_file);
-}
-
 int manager_address_pool_acquire(Manager *m, int family, unsigned prefixlen, union in_addr_union *found) {
         AddressPool *p;
         int r;
@@ -1036,3 +1073,10 @@ Link* manager_find_uplink(Manager *m, Link *exclude) {
 
         return NULL;
 }
+
+void manager_dirty(Manager *manager) {
+        assert(manager);
+
+        /* the serialized state in /run is no longer up-to-date */
+        manager->dirty = true;
+}
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 5b8ca305bc..97ada56866 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -369,10 +369,9 @@ int network_apply(Manager *manager, Network *network, Link *link) {
                 route->protocol = RTPROT_STATIC;
         }
 
-        if (network->dns || network->ntp) {
-                r = link_save(link);
-                if (r < 0)
-                        return r;
+        if (network->dns || network->ntp || network->domains) {
+                manager_dirty(manager);
+                link_dirty(link);
         }
 
         return 0;
diff --git a/src/network/networkd.h b/src/network/networkd.h
index cbec6d5b7e..6c5a9939be 100644
--- a/src/network/networkd.h
+++ b/src/network/networkd.h
@@ -48,7 +48,10 @@ struct Manager {
         struct udev_monitor *udev_monitor;
         sd_event_source *udev_event_source;
 
-        bool enumerating;
+        bool enumerating:1;
+        bool dirty:1;
+
+        Set *dirty_links;
 
         char *state_file;
         LinkOperationalState operational_state;
@@ -83,7 +86,7 @@ int manager_rtnl_enumerate_addresses(Manager *m);
 int manager_rtnl_process_address(sd_netlink *nl, sd_netlink_message *message, void *userdata);
 
 int manager_send_changed(Manager *m, const char *property, ...) _sentinel_;
-int manager_save(Manager *m);
+void manager_dirty(Manager *m);
 
 int manager_address_pool_acquire(Manager *m, int family, unsigned prefixlen, union in_addr_union *found);
 
-- 
cgit v1.2.3-54-g00ecf