[systemd-commits] 9 commits - src/libsystemd-network src/network src/systemd

Patrik Flykt pflykt at kemper.freedesktop.org
Mon Jan 26 23:36:28 PST 2015


 src/libsystemd-network/sd-dhcp6-lease.c    |   26 -
 src/libsystemd-network/sd-icmp6-nd.c       |  387 ++++++++++++++++++++++++++++-
 src/libsystemd-network/test-dhcp6-client.c |   45 +--
 src/libsystemd-network/test-icmp6-rs.c     |  219 +++++++++++++++-
 src/network/networkd-dhcp6.c               |  165 +++++++++++-
 src/systemd/sd-dhcp6-lease.h               |   13 
 src/systemd/sd-icmp6-nd.h                  |   30 +-
 7 files changed, 803 insertions(+), 82 deletions(-)

New commits:
commit c62c4628d9dbc27effd36143c75abe528f561867
Author: Patrik Flykt <patrik.flykt at linux.intel.com>
Date:   Tue Jan 20 19:36:04 2015 +0200

    networkd-dhcp6: Assign DHCPv6 addresses and prefix lengths
    
    Once IPv6 addresses have been acquired, assign these to the interface
    with the prefix lengths taken from the ICMPv6 Router Advertisement
    handling code. The preferred and valid IPv6 address lifetimes are
    handed to the kernel which will clean up them if not renewed in time.
    
    When a prefix announced via Router Advertisements expires, find all
    addresses that match that prefix and update the address to have a
    prefix length of 128 causing the prefix to be off-link.

diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c
index c31bd4e..bcfad4c 100644
--- a/src/network/networkd-dhcp6.c
+++ b/src/network/networkd-dhcp6.c
@@ -28,7 +28,137 @@
 #include "sd-icmp6-nd.h"
 #include "sd-dhcp6-client.h"
 
+static int dhcp6_lease_information_acquired(sd_dhcp6_client *client,
+                                        Link *link) {
+        return 0;
+}
+
+static int dhcp6_address_handler(sd_rtnl *rtnl, sd_rtnl_message *m,
+                                 void *userdata) {
+        _cleanup_link_unref_ Link *link = userdata;
+        int r;
+
+        assert(link);
+
+        r = sd_rtnl_message_get_errno(m);
+        if (r < 0 && r != -EEXIST) {
+                log_link_error(link, "Could not set DHCPv6 address: %s",
+                               strerror(-r));
+
+                link_enter_failed(link);
+
+        } else if (r >= 0)
+                link_rtnl_process_address(rtnl, m, link->manager);
+
+        return 1;
+}
+
+static int dhcp6_address_update(Link *link, struct in6_addr *ip6_addr,
+                                uint8_t prefixlen, uint32_t lifetime_preferred,
+                                uint32_t lifetime_valid) {
+        int r;
+        _cleanup_address_free_ Address *addr = NULL;
+
+        r = address_new_dynamic(&addr);
+        if (r < 0)
+                return r;
+
+        addr->family = AF_INET6;
+        memcpy(&addr->in_addr.in6, ip6_addr, sizeof(*ip6_addr));
+        addr->prefixlen = prefixlen;
+
+        addr->cinfo.ifa_prefered = lifetime_preferred;
+        addr->cinfo.ifa_valid = lifetime_valid;
+
+        log_link_struct(link, LOG_INFO, "MESSAGE=%-*s: DHCPv6 address "SD_ICMP6_ADDRESS_FORMAT_STR"/%d timeout preferred %d valid %d",
+                        IFNAMSIZ,
+                        link->ifname, SD_ICMP6_ADDRESS_FORMAT_VAL(addr->in_addr.in6),
+                        addr->prefixlen, lifetime_preferred, lifetime_valid,
+                        NULL);
+
+        r = address_update(addr, link, dhcp6_address_handler);
+        if (r < 0)
+                log_link_warning(link, "Could not assign DHCPv6 address: %s",
+                                strerror(-r));
+
+        return r;
+}
+
+static int dhcp6_prefix_expired(Link *link) {
+        int r;
+        sd_dhcp6_lease *lease;
+        struct in6_addr *expired_prefix, ip6_addr;
+        uint8_t expired_prefixlen;
+        uint32_t lifetime_preferred, lifetime_valid;
+
+        r = sd_icmp6_ra_get_expired_prefix(link->icmp6_router_discovery,
+                                        &expired_prefix, &expired_prefixlen);
+        if (r < 0)
+                return r;
+
+        r = sd_dhcp6_client_get_lease(link->dhcp6_client, &lease);
+        if (r < 0)
+                return r;
+
+        sd_dhcp6_lease_reset_address_iter(lease);
+
+        while (sd_dhcp6_lease_get_address(lease, &ip6_addr,
+                                                &lifetime_preferred,
+                                                &lifetime_valid) >= 0) {
+
+                r = sd_icmp6_prefix_match(expired_prefix, expired_prefixlen,
+                                        &ip6_addr);
+                if (r >= 0) {
+                        r = dhcp6_address_update(link, &ip6_addr, 128,
+                                                lifetime_preferred,
+                                                lifetime_valid);
+
+                        return r;
+                }
+        }
+
+        return 0;
+}
+
+static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link) {
+        int r;
+        sd_dhcp6_lease *lease;
+        struct in6_addr ip6_addr;
+        uint32_t lifetime_preferred, lifetime_valid;
+        uint8_t prefixlen;
+
+        r = sd_dhcp6_client_get_lease(client, &lease);
+        if (r < 0)
+                return r;
+
+        sd_dhcp6_lease_reset_address_iter(lease);
+
+        while (sd_dhcp6_lease_get_address(lease, &ip6_addr,
+                                                &lifetime_preferred,
+                                                &lifetime_valid) >= 0) {
+
+                r = sd_icmp6_ra_get_prefixlen(link->icmp6_router_discovery,
+                                        &ip6_addr, &prefixlen);
+                if (r < 0 && r != -EADDRNOTAVAIL) {
+                        log_link_warning(link, "Could not get prefix information: %s",
+                                        strerror(-r));
+                        return r;
+                }
+
+                if (r == -EADDRNOTAVAIL)
+                        prefixlen = 128;
+
+                r = dhcp6_address_update(link, &ip6_addr, prefixlen,
+                                        lifetime_preferred, lifetime_valid);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
 static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) {
+        int r;
         Link *link = userdata;
 
         assert(link);
@@ -42,9 +172,23 @@ static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) {
         case DHCP6_EVENT_STOP:
         case DHCP6_EVENT_RESEND_EXPIRE:
         case DHCP6_EVENT_RETRANS_MAX:
+                log_link_debug(link, "DHCPv6 event %d", event);
+                break;
+
         case DHCP6_EVENT_IP_ACQUIRE:
+                r = dhcp6_lease_address_acquired(client, link);
+                if (r < 0) {
+                        link_enter_failed(link);
+                        return;
+                }
+
+                /* fall through */
         case DHCP6_EVENT_INFORMATION_REQUEST:
-                log_link_debug(link, "DHCPv6 event %d", event);
+                r = dhcp6_lease_information_acquired(client, link);
+                if (r < 0) {
+                        link_enter_failed(link);
+                        return;
+                }
 
                 break;
 
@@ -72,7 +216,8 @@ static int dhcp6_configure(Link *link, int event) {
                 r = sd_dhcp6_client_get_information_request(link->dhcp6_client,
                                                         &information_request);
                 if (r < 0) {
-                        log_link_warning(link, "Could not get DHCPv6 Information request setting");
+                        log_link_warning(link, "Could not get DHCPv6 Information request setting: %s",
+                                        strerror(-r));
                         link->dhcp6_client =
                                 sd_dhcp6_client_unref(link->dhcp6_client);
                         return r;
@@ -84,7 +229,8 @@ static int dhcp6_configure(Link *link, int event) {
                 r = sd_dhcp6_client_set_information_request(link->dhcp6_client,
                                                         false);
                 if (r < 0) {
-                        log_link_warning(link, "Could not unset DHCPv6 Information request");
+                        log_link_warning(link, "Could not unset DHCPv6 Information request: %s",
+                                        strerror(-r));
                         link->dhcp6_client =
                                 sd_dhcp6_client_unref(link->dhcp6_client);
                         return r;
@@ -92,7 +238,8 @@ static int dhcp6_configure(Link *link, int event) {
 
                 r = sd_dhcp6_client_start(link->dhcp6_client);
                 if (r < 0) {
-                        log_link_warning(link, "Could not restart DHCPv6 after enabling Information request");
+                        log_link_warning(link, "Could not restart DHCPv6 after enabling Information request: %s",
+                                        strerror(-r));
                         link->dhcp6_client =
                                 sd_dhcp6_client_unref(link->dhcp6_client);
                         return r;
@@ -166,6 +313,13 @@ static void icmp6_router_handler(sd_icmp6_nd *nd, int event, void *userdata) {
         case ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT:
         case ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER:
         case ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED:
+                dhcp6_configure(link, event);
+
+                break;
+
+        case ICMP6_EVENT_ROUTER_ADVERTISMENT_PREFIX_EXPIRED:
+                dhcp6_prefix_expired(link);
+
                 break;
 
         default:
@@ -176,10 +330,9 @@ static void icmp6_router_handler(sd_icmp6_nd *nd, int event, void *userdata) {
                         log_link_warning(link, "ICMPv6 unknown event: %d",
                                          event);
 
-                return;
+                break;
         }
 
-        dhcp6_configure(link, event);
 }
 
 int icmp6_configure(Link *link) {

commit bd1957e906fbf92fca6c97bc6cadcf89248d2419
Author: Patrik Flykt <patrik.flykt at linux.intel.com>
Date:   Tue Jan 20 19:36:03 2015 +0200

    sd-icmp6-nd: Add support for fetching the latest expired prefix
    
    Keep the expired prefix for the duration of the prefix expiration event
    and remove it afterwards.

diff --git a/src/libsystemd-network/sd-icmp6-nd.c b/src/libsystemd-network/sd-icmp6-nd.c
index eb00b1a..2f867e8 100644
--- a/src/libsystemd-network/sd-icmp6-nd.c
+++ b/src/libsystemd-network/sd-icmp6-nd.c
@@ -65,6 +65,7 @@ struct sd_icmp6_nd {
         int index;
         struct ether_addr mac_addr;
         uint32_t mtu;
+        ICMP6Prefix *expired_prefix;
         LIST_HEAD(ICMP6Prefix, prefixes);
         int fd;
         sd_event_source *recv;
@@ -266,8 +267,11 @@ static int icmp6_ra_prefix_timeout(sd_event_source *s, uint64_t usec,
 
                 LIST_REMOVE(prefixes, nd->prefixes, prefix);
 
+                nd->expired_prefix = prefix;
                 icmp6_nd_notify(nd,
                                 ICMP6_EVENT_ROUTER_ADVERTISMENT_PREFIX_EXPIRED);
+                nd->expired_prefix = NULL;
+
                 prefix = icmp6_prefix_unref(prefix);
 
                 break;
@@ -372,6 +376,22 @@ int sd_icmp6_ra_get_prefixlen(sd_icmp6_nd *nd, const struct in6_addr *addr,
         return 0;
 }
 
+int sd_icmp6_ra_get_expired_prefix(sd_icmp6_nd *nd, struct in6_addr **addr,
+                                uint8_t *prefixlen)
+{
+        assert_return(nd, -EINVAL);
+        assert_return(addr, -EINVAL);
+        assert_return(prefixlen, -EINVAL);
+
+        if (!nd->expired_prefix)
+                return -EADDRNOTAVAIL;
+
+        *addr = &nd->expired_prefix->addr;
+        *prefixlen = nd->expired_prefix->len;
+
+        return 0;
+}
+
 static int icmp6_ra_prefix_update(sd_icmp6_nd *nd, ssize_t len,
                                   const struct nd_opt_prefix_info *prefix_opt) {
         int r;
diff --git a/src/systemd/sd-icmp6-nd.h b/src/systemd/sd-icmp6-nd.h
index 86cf25a..79b4074 100644
--- a/src/systemd/sd-icmp6-nd.h
+++ b/src/systemd/sd-icmp6-nd.h
@@ -58,6 +58,8 @@ int sd_icmp6_prefix_match(struct in6_addr *prefix, uint8_t prefixlen,
 int sd_icmp6_ra_get_mtu(sd_icmp6_nd *nd, uint32_t *mtu);
 int sd_icmp6_ra_get_prefixlen(sd_icmp6_nd *nd, const struct in6_addr *addr,
                         uint8_t *prefixlen);
+int sd_icmp6_ra_get_expired_prefix(sd_icmp6_nd *nd, struct in6_addr **addr,
+                                uint8_t *prefixlen);
 
 int sd_icmp6_nd_stop(sd_icmp6_nd *nd);
 int sd_icmp6_router_solicitation_start(sd_icmp6_nd *nd);

commit 99af546d0ee63ac4af3f234a448d4a3b7bdcfab4
Author: Patrik Flykt <patrik.flykt at linux.intel.com>
Date:   Tue Jan 20 19:36:02 2015 +0200

    test-icmp6-nd: Add test cases for prefixes
    
    Add test cases that feeds an Router Advertisement to the ICMPv6 code
    and verify that the correct prefix lengths are returned given an IPv6
    address.
    
    Enhance the prefix verification test by adding a shorter prefix and
    check that the intended prefix lengths are now updated.

diff --git a/src/libsystemd-network/test-icmp6-rs.c b/src/libsystemd-network/test-icmp6-rs.c
index 9d50c57..8ba2110 100644
--- a/src/libsystemd-network/test-icmp6-rs.c
+++ b/src/libsystemd-network/test-icmp6-rs.c
@@ -34,6 +34,9 @@ static bool verbose = false;
 static sd_event_source *test_hangcheck;
 static int test_fd[2];
 
+typedef int (*send_ra_t)(uint8_t flags);
+static send_ra_t send_ra_function;
+
 static int test_rs_hangcheck(sd_event_source *s, uint64_t usec,
                              void *userdata) {
         assert_se(false);
@@ -50,6 +53,191 @@ int dhcp_network_icmp6_bind_router_solicitation(int index) {
         return test_fd[0];
 }
 
+static int send_ra_short_prefix(uint8_t flags) {
+        uint8_t advertisement[] = {
+                0x86, 0x00, 0xbe, 0xd7, 0x40, 0xc0, 0x00, 0xb4,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+                0x03, 0x04, 0x34, 0xc0, 0x00, 0x00, 0x01, 0xf4,
+                0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00,
+                0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+        };
+
+        assert_se(write(test_fd[1], advertisement, sizeof(advertisement)) ==
+               sizeof(advertisement));
+
+        return 0;
+}
+
+static void test_short_prefix_cb(sd_icmp6_nd *nd, int event, void *userdata) {
+        sd_event *e = userdata;
+        struct {
+                struct in6_addr addr;
+                uint8_t prefixlen;
+                bool success;
+        } addrs[] = {
+                { { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
+                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
+                  52, true },
+                { { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0x0d, 0xad,
+                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
+                  64, false },
+                { { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d,
+                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
+                  60, true },
+                { { { { 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x9d, 0xab, 0xcd,
+                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
+                  64, true },
+                { { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xed,
+                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } },
+                  52, true },
+        };
+        uint8_t prefixlen;
+        unsigned int i;
+
+        for (i = 0; i < ELEMENTSOF(addrs); i++) {
+                printf("  %s prefix %02x%02x:%02x%02x:%02x%02x:%02x%02x",
+                        __FUNCTION__,
+                        addrs[i].addr.s6_addr[0], addrs[i].addr.s6_addr[1],
+                        addrs[i].addr.s6_addr[2], addrs[i].addr.s6_addr[3],
+                        addrs[i].addr.s6_addr[4], addrs[i].addr.s6_addr[5],
+                        addrs[i].addr.s6_addr[6], addrs[i].addr.s6_addr[7]);
+
+                if (addrs[i].success) {
+                        assert_se(sd_icmp6_ra_get_prefixlen(nd, &addrs[i].addr,
+                                                                &prefixlen) >= 0);
+                        assert_se(addrs[i].prefixlen == prefixlen);
+                        printf("/%d onlink\n", prefixlen);
+                } else {
+                        assert_se(sd_icmp6_ra_get_prefixlen(nd, &addrs[i].addr,
+                                                                &prefixlen) == -EADDRNOTAVAIL);
+                        printf("/128 offlink\n");
+                }
+        }
+
+        sd_event_exit(e, 0);
+}
+
+static int send_ra_prefixes(uint8_t flags) {
+        uint8_t advertisement[] = {
+                0x86, 0x00, 0xbe, 0xd7, 0x40, 0xc0, 0x00, 0xb4,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x03, 0x04, 0x3f, 0xc0, 0x00, 0x00, 0x01, 0xf4,
+                0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00,
+                0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x03, 0x04, 0x40, 0x00, 0x00, 0x00, 0x02, 0x58,
+                0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x00,
+                0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0x0d, 0xad,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x03, 0x04, 0x3c, 0x80, 0x00, 0x00, 0x03, 0x84,
+                0x00, 0x00, 0x03, 0x20, 0x00, 0x00, 0x00, 0x00,
+                0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x03, 0x84,
+                0x00, 0x00, 0x03, 0x20, 0x00, 0x00, 0x00, 0x00,
+                0x20, 0x01, 0x0d, 0xb8, 0x00, 0x9d, 0xab, 0xcd,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
+                0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+                0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
+                0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74,
+                0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53
+        };
+
+        assert_se(write(test_fd[1], advertisement, sizeof(advertisement)) ==
+               sizeof(advertisement));
+
+        return 0;
+}
+
+static void test_prefixes_cb(sd_icmp6_nd *nd, int event, void *userdata) {
+        sd_event *e = userdata;
+        struct {
+                struct in6_addr addr;
+                uint8_t prefixlen;
+                bool success;
+        } addrs[] = {
+                { { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
+                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
+                  63, true },
+                { { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0x0d, 0xad,
+                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
+                  64, false },
+                { { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d,
+                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
+                  60, true },
+                { { { { 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x9d, 0xab, 0xcd,
+                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
+                  64, true },
+                { { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xed,
+                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } },
+                  63, false },
+        };
+        uint8_t prefixlen;
+        unsigned int i;
+
+        for (i = 0; i < ELEMENTSOF(addrs); i++) {
+                printf("  %s prefix %02x%02x:%02x%02x:%02x%02x:%02x%02x",
+                        __FUNCTION__,
+                        addrs[i].addr.s6_addr[0], addrs[i].addr.s6_addr[1],
+                        addrs[i].addr.s6_addr[2], addrs[i].addr.s6_addr[3],
+                        addrs[i].addr.s6_addr[4], addrs[i].addr.s6_addr[5],
+                        addrs[i].addr.s6_addr[6], addrs[i].addr.s6_addr[7]);
+
+                if (addrs[i].success) {
+                        assert_se(sd_icmp6_ra_get_prefixlen(nd, &addrs[i].addr,
+                                                                &prefixlen) >= 0);
+                        assert_se(addrs[i].prefixlen == prefixlen);
+                        printf("/%d onlink\n", prefixlen);
+                } else {
+                        assert_se(sd_icmp6_ra_get_prefixlen(nd, &addrs[i].addr,
+                                                                &prefixlen) == -EADDRNOTAVAIL);
+                        printf("/128 offlink\n");
+                }
+        }
+
+        send_ra_function = send_ra_short_prefix;
+        assert_se(sd_icmp6_nd_set_callback(nd, test_short_prefix_cb, e) >= 0);
+        assert_se(sd_icmp6_nd_stop(nd) >= 0);
+        assert_se(sd_icmp6_router_solicitation_start(nd) >= 0);
+}
+
+static void test_prefixes(void) {
+        sd_event *e;
+        sd_icmp6_nd *nd;
+
+        if (verbose)
+                printf("* %s\n", __FUNCTION__);
+
+        send_ra_function = send_ra_prefixes;
+
+        assert_se(sd_event_new(&e) >= 0);
+
+        assert_se(sd_icmp6_nd_new(&nd) >= 0);
+        assert_se(nd);
+
+        assert_se(sd_icmp6_nd_attach_event(nd, e, 0) >= 0);
+
+        assert_se(sd_icmp6_nd_set_index(nd, 42) >= 0);
+        assert_se(sd_icmp6_nd_set_mac(nd, &mac_addr) >= 0);
+        assert_se(sd_icmp6_nd_set_callback(nd, test_prefixes_cb, e) >= 0);
+
+        assert_se(sd_icmp6_router_solicitation_start(nd) >= 0);
+
+        sd_event_loop(e);
+
+        nd = sd_icmp6_nd_unref(nd);
+        assert_se(!nd);
+
+        close(test_fd[1]);
+
+        sd_event_unref(e);
+}
+
 static int send_ra(uint8_t flags) {
         uint8_t advertisement[] = {
                 0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4,
@@ -79,7 +267,7 @@ static int send_ra(uint8_t flags) {
 }
 
 int dhcp_network_icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
-        return send_ra(0);
+        return send_ra_function(0);
 }
 
 static void test_rs_done(sd_icmp6_nd *nd, int event, void *userdata) {
@@ -113,13 +301,18 @@ static void test_rs_done(sd_icmp6_nd *nd, int event, void *userdata) {
         sd_event_exit(e, 0);
 }
 
-static void test_rs(sd_event *e) {
-        usec_t time_now = now(clock_boottime_or_monotonic());
+static void test_rs(void) {
+        sd_event *e;
         sd_icmp6_nd *nd;
+        usec_t time_now = now(clock_boottime_or_monotonic());
 
         if (verbose)
                 printf("* %s\n", __FUNCTION__);
 
+        send_ra_function = send_ra;
+
+        assert_se(sd_event_new(&e) >= 0);
+
         assert_se(sd_icmp6_nd_new(&nd) >= 0);
         assert_se(nd);
 
@@ -147,18 +340,18 @@ static void test_rs(sd_event *e) {
         assert_se(!nd);
 
         close(test_fd[1]);
+
+        sd_event_unref(e);
 }
 
 int main(int argc, char *argv[]) {
-        sd_event *e;
-
-        assert_se(sd_event_new(&e) >= 0);
 
         log_set_max_level(LOG_DEBUG);
         log_parse_environment();
         log_open();
 
-        test_rs(e);
+        test_rs();
+        test_prefixes();
 
         return 0;
 }

commit d77bde34cfcf91246a075fd47919294894bf6f8a
Author: Patrik Flykt <patrik.flykt at linux.intel.com>
Date:   Tue Jan 20 19:36:01 2015 +0200

    sd-icmp6-nd: Parse ICMPv6 prefix information
    
    Save each new onlink IPv6 prefix and attach an expiry timer to it.
    If the prefixes overlap, take the shorter prefix and write a debug
    message about the event. Once the prefix is resent in a Router
    Advertisement, update the timer. Add a new event for the expiring
    prefix.
    
    Add two helper functions, one for returning a prefix length given a
    Router Advertisement and the other for generic prefix matching given
    an IPv6 prefix and address.

diff --git a/src/libsystemd-network/sd-icmp6-nd.c b/src/libsystemd-network/sd-icmp6-nd.c
index 8b567e3..eb00b1a 100644
--- a/src/libsystemd-network/sd-icmp6-nd.c
+++ b/src/libsystemd-network/sd-icmp6-nd.c
@@ -249,6 +249,201 @@ int sd_icmp6_ra_get_mtu(sd_icmp6_nd *nd, uint32_t *mtu) {
         return 0;
 }
 
+static int icmp6_ra_prefix_timeout(sd_event_source *s, uint64_t usec,
+                                   void *userdata) {
+        sd_icmp6_nd *nd = userdata;
+        ICMP6Prefix *prefix, *p;
+
+        assert(nd);
+
+        LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
+                if (prefix->timeout_valid != s)
+                        continue;
+
+                log_icmp6_nd(nd, "Prefix expired "SD_ICMP6_ADDRESS_FORMAT_STR"/%d",
+                             SD_ICMP6_ADDRESS_FORMAT_VAL(prefix->addr),
+                             prefix->len);
+
+                LIST_REMOVE(prefixes, nd->prefixes, prefix);
+
+                icmp6_nd_notify(nd,
+                                ICMP6_EVENT_ROUTER_ADVERTISMENT_PREFIX_EXPIRED);
+                prefix = icmp6_prefix_unref(prefix);
+
+                break;
+        }
+
+        return 0;
+}
+
+static int icmp6_ra_prefix_set_timeout(sd_icmp6_nd *nd,
+                                       ICMP6Prefix *prefix,
+                                       usec_t valid) {
+        usec_t time_now;
+        int r;
+
+        assert_return(prefix, -EINVAL);
+
+        r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
+        if (r < 0)
+                return r;
+
+        prefix->timeout_valid = sd_event_source_unref(prefix->timeout_valid);
+
+        r = sd_event_add_time(nd->event, &prefix->timeout_valid,
+                        clock_boottime_or_monotonic(), time_now + valid,
+                        USEC_PER_SEC, icmp6_ra_prefix_timeout, nd);
+        if (r < 0)
+                goto error;
+
+        r = sd_event_source_set_priority(prefix->timeout_valid,
+                                        nd->event_priority);
+        if (r < 0)
+                goto error;
+
+        r = sd_event_source_set_description(prefix->timeout_valid,
+                                        "icmp6-prefix-timeout");
+
+error:
+        if (r < 0)
+                prefix->timeout_valid =
+                        sd_event_source_unref(prefix->timeout_valid);
+
+        return r;
+}
+
+static int icmp6_prefix_match(const struct in6_addr *prefix, uint8_t prefixlen,
+                              const struct in6_addr *addr,
+                              uint8_t addr_prefixlen) {
+        uint8_t bytes, mask, len;
+
+        assert_return(prefix, -EINVAL);
+        assert_return(addr, -EINVAL);
+
+        len = MIN(prefixlen, addr_prefixlen);
+
+        bytes = len / 8;
+        mask = 0xff << (8 - len % 8);
+
+        if (memcmp(prefix, addr, bytes) != 0 ||
+            (prefix->s6_addr[bytes] & mask) != (addr->s6_addr[bytes] & mask))
+                return -EADDRNOTAVAIL;
+
+        return 0;
+}
+
+static int icmp6_ra_prefix_match(ICMP6Prefix *head, const struct in6_addr *addr,
+                                 uint8_t addr_len, ICMP6Prefix **result) {
+        ICMP6Prefix *prefix;
+
+        LIST_FOREACH(prefixes, prefix, head) {
+                if (icmp6_prefix_match(&prefix->addr, prefix->len, addr,
+                                       addr_len) >= 0) {
+                        *result = prefix;
+                        return 0;
+                }
+        }
+
+        return -EADDRNOTAVAIL;
+}
+
+int sd_icmp6_prefix_match(struct in6_addr *prefix, uint8_t prefixlen,
+                          struct in6_addr *addr) {
+        return icmp6_prefix_match(prefix, prefixlen, addr,
+                                  sizeof(addr->s6_addr) * 8);
+}
+
+int sd_icmp6_ra_get_prefixlen(sd_icmp6_nd *nd, const struct in6_addr *addr,
+                              uint8_t *prefixlen) {
+        int r;
+        ICMP6Prefix *prefix;
+
+        assert_return(nd, -EINVAL);
+        assert_return(addr, -EINVAL);
+        assert_return(prefixlen, -EINVAL);
+
+        r = icmp6_ra_prefix_match(nd->prefixes, addr,
+                                  sizeof(addr->s6_addr) * 8, &prefix);
+        if (r < 0)
+                return r;
+
+        *prefixlen = prefix->len;
+
+        return 0;
+}
+
+static int icmp6_ra_prefix_update(sd_icmp6_nd *nd, ssize_t len,
+                                  const struct nd_opt_prefix_info *prefix_opt) {
+        int r;
+        ICMP6Prefix *prefix;
+        uint32_t lifetime;
+        char time_string[FORMAT_TIMESPAN_MAX];
+
+        assert_return(nd, -EINVAL);
+        assert_return(prefix_opt, -EINVAL);
+
+        if (len < prefix_opt->nd_opt_pi_len)
+                return -ENOMSG;
+
+        if (!(prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK))
+                return 0;
+
+        lifetime = be32toh(prefix_opt->nd_opt_pi_valid_time);
+
+        r = icmp6_ra_prefix_match(nd->prefixes,
+                                  &prefix_opt->nd_opt_pi_prefix,
+                                  prefix_opt->nd_opt_pi_prefix_len, &prefix);
+
+        if (r < 0 && r != -EADDRNOTAVAIL)
+                return r;
+
+        /* if router advertisment prefix valid timeout is zero, the timeout
+           callback will be called immediately to clean up the prefix */
+
+        if (r == -EADDRNOTAVAIL) {
+                r = icmp6_prefix_new(&prefix);
+                if (r < 0)
+                        return r;
+
+                prefix->len = prefix_opt->nd_opt_pi_prefix_len;
+
+                memcpy(&prefix->addr, &prefix_opt->nd_opt_pi_prefix,
+                        sizeof(prefix->addr));
+
+                log_icmp6_nd(nd, "New prefix "SD_ICMP6_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
+                             SD_ICMP6_ADDRESS_FORMAT_VAL(prefix->addr),
+                             prefix->len, lifetime,
+                             format_timespan(time_string, FORMAT_TIMESPAN_MAX,
+                                             lifetime * USEC_PER_SEC, 0));
+
+                LIST_PREPEND(prefixes, nd->prefixes, prefix);
+
+        } else {
+                if (prefix->len != prefix_opt->nd_opt_pi_prefix_len) {
+                        uint8_t prefixlen;
+
+                        prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len);
+
+                        log_icmp6_nd(nd, "Prefix length mismatch %d/%d using %d",
+                                     prefix->len,
+                                     prefix_opt->nd_opt_pi_prefix_len,
+                                     prefixlen);
+
+                        prefix->len = prefixlen;
+                }
+
+                log_icmp6_nd(nd, "Update prefix "SD_ICMP6_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
+                             SD_ICMP6_ADDRESS_FORMAT_VAL(prefix->addr),
+                             prefix->len, lifetime,
+                             format_timespan(time_string, FORMAT_TIMESPAN_MAX,
+                                             lifetime * USEC_PER_SEC, 0));
+        }
+
+        r = icmp6_ra_prefix_set_timeout(nd, prefix, lifetime * USEC_PER_SEC);
+
+        return r;
+}
+
 static int icmp6_ra_parse(sd_icmp6_nd *nd, struct nd_router_advert *ra,
                           ssize_t len) {
         void *opt;
@@ -270,6 +465,7 @@ static int icmp6_ra_parse(sd_icmp6_nd *nd, struct nd_router_advert *ra,
         while (len != 0 && len >= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS) {
                 struct nd_opt_mtu *opt_mtu;
                 uint32_t mtu;
+                struct nd_opt_prefix_info *opt_prefix;
 
                 if (opt_hdr->nd_opt_len == 0)
                         return -ENOMSG;
@@ -289,6 +485,12 @@ static int icmp6_ra_parse(sd_icmp6_nd *nd, struct nd_router_advert *ra,
 
                         break;
 
+                case ND_OPT_PREFIX_INFORMATION:
+                        opt_prefix = opt;
+
+                        icmp6_ra_prefix_update(nd, len, opt_prefix);
+
+                        break;
                 }
 
                 len -= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS;
diff --git a/src/systemd/sd-icmp6-nd.h b/src/systemd/sd-icmp6-nd.h
index 73ebccf..86cf25a 100644
--- a/src/systemd/sd-icmp6-nd.h
+++ b/src/systemd/sd-icmp6-nd.h
@@ -27,10 +27,11 @@
 #include "sd-event.h"
 
 enum {
-        ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE    = 0,
-        ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT = 1,
-        ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER   = 2,
-        ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED = 3,
+        ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE           = 0,
+        ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT        = 1,
+        ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER          = 2,
+        ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED        = 3,
+        ICMP6_EVENT_ROUTER_ADVERTISMENT_PREFIX_EXPIRED = 4,
 };
 
 typedef struct sd_icmp6_nd sd_icmp6_nd;
@@ -51,9 +52,26 @@ sd_icmp6_nd *sd_icmp6_nd_ref(sd_icmp6_nd *nd);
 sd_icmp6_nd *sd_icmp6_nd_unref(sd_icmp6_nd *nd);
 int sd_icmp6_nd_new(sd_icmp6_nd **ret);
 
+int sd_icmp6_prefix_match(struct in6_addr *prefix, uint8_t prefixlen,
+                        struct in6_addr *addr);
+
 int sd_icmp6_ra_get_mtu(sd_icmp6_nd *nd, uint32_t *mtu);
+int sd_icmp6_ra_get_prefixlen(sd_icmp6_nd *nd, const struct in6_addr *addr,
+                        uint8_t *prefixlen);
 
 int sd_icmp6_nd_stop(sd_icmp6_nd *nd);
 int sd_icmp6_router_solicitation_start(sd_icmp6_nd *nd);
 
+#define SD_ICMP6_ADDRESS_FORMAT_STR "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x"
+
+#define SD_ICMP6_ADDRESS_FORMAT_VAL(address)   \
+        be16toh((address).s6_addr16[0]),        \
+        be16toh((address).s6_addr16[1]),        \
+        be16toh((address).s6_addr16[2]),        \
+        be16toh((address).s6_addr16[3]),        \
+        be16toh((address).s6_addr16[4]),        \
+        be16toh((address).s6_addr16[5]),        \
+        be16toh((address).s6_addr16[6]),        \
+        be16toh((address).s6_addr16[7])
+
 #endif

commit 8d7f2c6a47db4bdb753b9d523d3bcb1105827991
Author: Patrik Flykt <patrik.flykt at linux.intel.com>
Date:   Tue Jan 20 19:36:00 2015 +0200

    test-icmp6-rs: Add trivial test case for an MTU that is not present

diff --git a/src/libsystemd-network/test-icmp6-rs.c b/src/libsystemd-network/test-icmp6-rs.c
index be64d33..9d50c57 100644
--- a/src/libsystemd-network/test-icmp6-rs.c
+++ b/src/libsystemd-network/test-icmp6-rs.c
@@ -93,6 +93,8 @@ static void test_rs_done(sd_icmp6_nd *nd, int event, void *userdata) {
                 { ND_RA_FLAG_OTHER, ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER },
                 { ND_RA_FLAG_MANAGED, ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED }
         };
+        uint32_t mtu;
+
         assert_se(nd);
 
         assert_se(event == flag_event[idx].event);
@@ -101,10 +103,14 @@ static void test_rs_done(sd_icmp6_nd *nd, int event, void *userdata) {
         if (verbose)
                 printf("  got event %d\n", event);
 
-        if (idx < 3)
+        if (idx < 3) {
                 send_ra(flag_event[idx].flag);
-        else
-                sd_event_exit(e, 0);
+                return;
+        }
+
+        assert_se(sd_icmp6_ra_get_mtu(nd, &mtu) == -ENOMSG);
+
+        sd_event_exit(e, 0);
 }
 
 static void test_rs(sd_event *e) {

commit d14b5bc621fc1fa57ef0db3ccba6957efed8e7d4
Author: Patrik Flykt <patrik.flykt at linux.intel.com>
Date:   Tue Jan 20 19:35:59 2015 +0200

    sd-icmp6-nd: Add helper function to get the IPv6 link MTU
    
    Update MTU according to the latest value received.

diff --git a/src/libsystemd-network/sd-icmp6-nd.c b/src/libsystemd-network/sd-icmp6-nd.c
index 85b8ff9..8b567e3 100644
--- a/src/libsystemd-network/sd-icmp6-nd.c
+++ b/src/libsystemd-network/sd-icmp6-nd.c
@@ -237,6 +237,18 @@ int sd_icmp6_nd_new(sd_icmp6_nd **ret) {
         return 0;
 }
 
+int sd_icmp6_ra_get_mtu(sd_icmp6_nd *nd, uint32_t *mtu) {
+        assert_return(nd, -EINVAL);
+        assert_return(mtu, -EINVAL);
+
+        if (nd->mtu == 0)
+                return -ENOMSG;
+
+        *mtu = nd->mtu;
+
+        return 0;
+}
+
 static int icmp6_ra_parse(sd_icmp6_nd *nd, struct nd_router_advert *ra,
                           ssize_t len) {
         void *opt;
@@ -256,11 +268,26 @@ static int icmp6_ra_parse(sd_icmp6_nd *nd, struct nd_router_advert *ra,
         opt_hdr = opt;
 
         while (len != 0 && len >= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS) {
+                struct nd_opt_mtu *opt_mtu;
+                uint32_t mtu;
 
                 if (opt_hdr->nd_opt_len == 0)
                         return -ENOMSG;
 
                 switch (opt_hdr->nd_opt_type) {
+                case ND_OPT_MTU:
+                        opt_mtu = opt;
+
+                        mtu = be32toh(opt_mtu->nd_opt_mtu_mtu);
+
+                        if (mtu != nd->mtu) {
+                                nd->mtu = MAX(mtu, IP6_MIN_MTU);
+
+                                log_icmp6_nd(nd, "Router Advertisement link MTU %d using %d",
+                                             mtu, nd->mtu);
+                        }
+
+                        break;
 
                 }
 
diff --git a/src/systemd/sd-icmp6-nd.h b/src/systemd/sd-icmp6-nd.h
index 73f91aa..73ebccf 100644
--- a/src/systemd/sd-icmp6-nd.h
+++ b/src/systemd/sd-icmp6-nd.h
@@ -51,6 +51,8 @@ sd_icmp6_nd *sd_icmp6_nd_ref(sd_icmp6_nd *nd);
 sd_icmp6_nd *sd_icmp6_nd_unref(sd_icmp6_nd *nd);
 int sd_icmp6_nd_new(sd_icmp6_nd **ret);
 
+int sd_icmp6_ra_get_mtu(sd_icmp6_nd *nd, uint32_t *mtu);
+
 int sd_icmp6_nd_stop(sd_icmp6_nd *nd);
 int sd_icmp6_router_solicitation_start(sd_icmp6_nd *nd);
 

commit 5624c4801e6281189a876fb2bdd65ff276cae514
Author: Patrik Flykt <patrik.flykt at linux.intel.com>
Date:   Tue Jan 20 19:35:58 2015 +0200

    sd-icmp6-nd: Add link and prefix structures for ICMPv6
    
    Each ICMPv6 structure has an interface index and will therefore be
    associated with an IPv6 link containing a list of of prefixes.

diff --git a/src/libsystemd-network/sd-icmp6-nd.c b/src/libsystemd-network/sd-icmp6-nd.c
index cac431b..85b8ff9 100644
--- a/src/libsystemd-network/sd-icmp6-nd.c
+++ b/src/libsystemd-network/sd-icmp6-nd.c
@@ -44,6 +44,18 @@ enum icmp6_nd_state {
 #define ICMP6_ND_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr))
 #define ICMP6_OPT_LEN_UNITS 8
 
+typedef struct ICMP6Prefix ICMP6Prefix;
+
+struct ICMP6Prefix {
+        RefCount n_ref;
+
+        LIST_FIELDS(ICMP6Prefix, prefixes);
+
+        uint8_t len;
+        sd_event_source *timeout_valid;
+        struct in6_addr addr;
+};
+
 struct sd_icmp6_nd {
         RefCount n_ref;
 
@@ -52,6 +64,8 @@ struct sd_icmp6_nd {
         int event_priority;
         int index;
         struct ether_addr mac_addr;
+        uint32_t mtu;
+        LIST_HEAD(ICMP6Prefix, prefixes);
         int fd;
         sd_event_source *recv;
         sd_event_source *timeout;
@@ -62,6 +76,35 @@ struct sd_icmp6_nd {
 
 #define log_icmp6_nd(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "ICMPv6 CLIENT: " fmt, ##__VA_ARGS__)
 
+static ICMP6Prefix *icmp6_prefix_unref(ICMP6Prefix *prefix) {
+        if (prefix && REFCNT_DEC(prefix->n_ref) <= 0) {
+                prefix->timeout_valid =
+                        sd_event_source_unref(prefix->timeout_valid);
+
+                free(prefix);
+        }
+
+        return NULL;
+}
+
+static int icmp6_prefix_new(ICMP6Prefix **ret) {
+        _cleanup_free_ ICMP6Prefix *prefix = NULL;
+
+        assert(ret);
+
+        prefix = new0(ICMP6Prefix, 1);
+        if (!prefix)
+                return -ENOMEM;
+
+        prefix->n_ref = REFCNT_INIT;
+        LIST_INIT(prefixes, prefix);
+
+        *ret = prefix;
+        prefix = NULL;
+
+        return 0;
+}
+
 static void icmp6_nd_notify(sd_icmp6_nd *nd, int event)
 {
         if (nd->callback)
@@ -152,10 +195,17 @@ static int icmp6_nd_init(sd_icmp6_nd *nd) {
 
 sd_icmp6_nd *sd_icmp6_nd_unref(sd_icmp6_nd *nd) {
         if (nd && REFCNT_DEC(nd->n_ref) == 0) {
+                ICMP6Prefix *prefix, *p;
 
                 icmp6_nd_init(nd);
                 sd_icmp6_nd_detach_event(nd);
 
+                LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
+                        LIST_REMOVE(prefixes, nd->prefixes, prefix);
+
+                        prefix = icmp6_prefix_unref(prefix);
+                }
+
                 free(nd);
         }
 
@@ -179,6 +229,8 @@ int sd_icmp6_nd_new(sd_icmp6_nd **ret) {
         nd->index = -1;
         nd->fd = -1;
 
+        LIST_HEAD_INIT(nd->prefixes);
+
         *ret = nd;
         nd = NULL;
 

commit 09667885bad3a8cb7f1a002766f9e59140775526
Author: Patrik Flykt <patrik.flykt at linux.intel.com>
Date:   Tue Jan 20 19:35:57 2015 +0200

    sd-icmp6-nd: Update Router Advertisement handling
    
    As the IPv6 prefixes are needed, update the ICMPv6 Router Advertisement
    code to dynamically allocate a suitably sized buffer. Iterate through
    the ICMPv6 options one by one returning error if the option length is
    too big to fit the buffer.

diff --git a/src/libsystemd-network/sd-icmp6-nd.c b/src/libsystemd-network/sd-icmp6-nd.c
index d2bff8f..cac431b 100644
--- a/src/libsystemd-network/sd-icmp6-nd.c
+++ b/src/libsystemd-network/sd-icmp6-nd.c
@@ -18,9 +18,11 @@
 ***/
 
 #include <netinet/icmp6.h>
+#include <netinet/ip6.h>
 #include <string.h>
 #include <stdbool.h>
 #include <netinet/in.h>
+#include <sys/ioctl.h>
 
 #include "socket-util.h"
 #include "refcnt.h"
@@ -38,6 +40,10 @@ enum icmp6_nd_state {
         ICMP6_ROUTER_ADVERTISMENT_LISTEN        = 11,
 };
 
+#define IP6_MIN_MTU (unsigned)1280
+#define ICMP6_ND_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr))
+#define ICMP6_OPT_LEN_UNITS 8
+
 struct sd_icmp6_nd {
         RefCount n_ref;
 
@@ -179,44 +185,100 @@ int sd_icmp6_nd_new(sd_icmp6_nd **ret) {
         return 0;
 }
 
+static int icmp6_ra_parse(sd_icmp6_nd *nd, struct nd_router_advert *ra,
+                          ssize_t len) {
+        void *opt;
+        struct nd_opt_hdr *opt_hdr;
+
+        assert_return(nd, -EINVAL);
+        assert_return(ra, -EINVAL);
+
+        len -= sizeof(*ra);
+        if (len < ICMP6_OPT_LEN_UNITS) {
+                log_icmp6_nd(nd, "Router Advertisement below minimum length");
+
+                return -ENOMSG;
+        }
+
+        opt = ra + 1;
+        opt_hdr = opt;
+
+        while (len != 0 && len >= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS) {
+
+                if (opt_hdr->nd_opt_len == 0)
+                        return -ENOMSG;
+
+                switch (opt_hdr->nd_opt_type) {
+
+                }
+
+                len -= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS;
+                opt = (void *)((char *)opt +
+                        opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS);
+                opt_hdr = opt;
+        }
+
+        if (len > 0)
+                log_icmp6_nd(nd, "Router Advertisement contains %zd bytes of trailing garbage", len);
+
+        return 0;
+}
+
 static int icmp6_router_advertisment_recv(sd_event_source *s, int fd,
                                           uint32_t revents, void *userdata)
 {
         sd_icmp6_nd *nd = userdata;
+        int r, buflen = 0;
         ssize_t len;
-        struct nd_router_advert ra;
+        _cleanup_free_ struct nd_router_advert *ra = NULL;
         int event = ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE;
 
         assert(s);
         assert(nd);
         assert(nd->event);
 
-        /* only interested in Managed/Other flag */
-        len = read(fd, &ra, sizeof(ra));
-        if ((size_t)len < sizeof(ra))
+        r = ioctl(fd, FIONREAD, &buflen);
+        if (r < 0 || buflen <= 0)
+                buflen = ICMP6_ND_RECV_SIZE;
+
+        ra = malloc(buflen);
+        if (!ra)
+                return -ENOMEM;
+
+        len = read(fd, ra, buflen);
+        if (len < 0) {
+                log_icmp6_nd(nd, "Could not receive message from UDP socket: %m");
                 return 0;
+        }
 
-        if (ra.nd_ra_type != ND_ROUTER_ADVERT)
+        if (ra->nd_ra_type != ND_ROUTER_ADVERT)
                 return 0;
 
-        if (ra.nd_ra_code != 0)
+        if (ra->nd_ra_code != 0)
                 return 0;
 
         nd->timeout = sd_event_source_unref(nd->timeout);
 
         nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
 
-        if (ra.nd_ra_flags_reserved & ND_RA_FLAG_OTHER )
+        if (ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER )
                 event = ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER;
 
-        if (ra.nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
+        if (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
                 event = ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED;
 
         log_icmp6_nd(nd, "Received Router Advertisement flags %s/%s",
-                     (ra.nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)? "MANAGED":
-                     "none",
-                     (ra.nd_ra_flags_reserved & ND_RA_FLAG_OTHER)? "OTHER":
-                     "none");
+                     ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED? "MANAGED": "none",
+                     ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER? "OTHER": "none");
+
+        if (event != ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE) {
+                r = icmp6_ra_parse(nd, ra, len);
+                if (r < 0) {
+                        log_icmp6_nd(nd, "Could not parse Router Advertisement: %s",
+                                     strerror(-r));
+                        return 0;
+                }
+        }
 
         icmp6_nd_notify(nd, event);
 

commit e7504d95479455be5cef120fc8e0a48fd74ad5ca
Author: Patrik Flykt <patrik.flykt at linux.intel.com>
Date:   Tue Jan 20 19:35:56 2015 +0200

    sd-dhcp6-lease: Revise address iteration functions
    
    Revise the address iteration functions so that one helper function
    resets the iterator to the start of the address list while the
    second one fetches addresses one by one.
    
    The test case is also updated.

diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c
index 78e1589..2442269 100644
--- a/src/libsystemd-network/sd-dhcp6-lease.c
+++ b/src/libsystemd-network/sd-dhcp6-lease.c
@@ -146,10 +146,9 @@ int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid) {
         return 0;
 }
 
-int sd_dhcp6_lease_get_next_address(sd_dhcp6_lease *lease,
-                                    struct in6_addr *addr,
-                                    uint32_t *lifetime_preferred,
-                                    uint32_t *lifetime_valid) {
+int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, struct in6_addr *addr,
+                               uint32_t *lifetime_preferred,
+                               uint32_t *lifetime_valid) {
         assert_return(lease, -EINVAL);
         assert_return(addr, -EINVAL);
         assert_return(lifetime_preferred, -EINVAL);
@@ -169,22 +168,9 @@ int sd_dhcp6_lease_get_next_address(sd_dhcp6_lease *lease,
         return 0;
 }
 
-int sd_dhcp6_lease_get_first_address(sd_dhcp6_lease *lease,
-                                     struct in6_addr *addr,
-                                     uint32_t *lifetime_preferred,
-                                     uint32_t *lifetime_valid) {
-        assert_return(lease, -EINVAL);
-        assert_return(addr, -EINVAL);
-        assert_return(lifetime_preferred, -EINVAL);
-        assert_return(lifetime_valid, -EINVAL);
-
-        if (!lease->ia.addresses)
-                return -ENOMSG;
-
-        lease->addr_iter = lease->ia.addresses;
-
-        return sd_dhcp6_lease_get_next_address(lease, addr, lifetime_preferred,
-                                               lifetime_valid);
+void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease) {
+        if (lease)
+                lease->addr_iter = lease->ia.addresses;
 }
 
 sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease) {
diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c
index 7590839..9386f31 100644
--- a/src/libsystemd-network/test-dhcp6-client.c
+++ b/src/libsystemd-network/test-dhcp6-client.c
@@ -286,26 +286,27 @@ static int test_advertise_option(sd_event *e) {
 
         assert_se(opt_clientid);
 
-        assert_se(sd_dhcp6_lease_get_first_address(lease, &addr, &lt_pref,
-                                                   &lt_valid) >= 0);
+        sd_dhcp6_lease_reset_address_iter(lease);
+        assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+                                             &lt_valid) >= 0);
         assert_se(!memcmp(&addr, &msg_advertise[42], sizeof(addr)));
         assert_se(lt_pref == 150);
         assert_se(lt_valid == 180);
-        assert_se(sd_dhcp6_lease_get_next_address(lease, &addr, &lt_pref,
-                                                  &lt_valid) == -ENOMSG);
+        assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+                                             &lt_valid) == -ENOMSG);
 
-        assert_se(sd_dhcp6_lease_get_first_address(lease, &addr, &lt_pref,
-                                                   &lt_valid) >= 0);
+        sd_dhcp6_lease_reset_address_iter(lease);
+        assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+                                             &lt_valid) >= 0);
         assert_se(!memcmp(&addr, &msg_advertise[42], sizeof(addr)));
-        assert_se(sd_dhcp6_lease_get_next_address(lease, &addr, &lt_pref,
-                                                  &lt_valid) == -ENOMSG);
-        assert_se(sd_dhcp6_lease_get_next_address(lease, &addr, &lt_pref,
-                                                  &lt_valid) == -ENOMSG);
-        assert_se(sd_dhcp6_lease_get_first_address(lease, &addr, &lt_pref,
-                                                   &lt_valid) >= 0);
+        assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+                                             &lt_valid) == -ENOMSG);
+        sd_dhcp6_lease_reset_address_iter(lease);
+        assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+                                             &lt_valid) >= 0);
         assert_se(!memcmp(&addr, &msg_advertise[42], sizeof(addr)));
-        assert_se(sd_dhcp6_lease_get_next_address(lease, &addr, &lt_pref,
-                                                  &lt_valid) == -ENOMSG);
+        assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+                                             &lt_valid) == -ENOMSG);
 
         assert_se(dhcp6_lease_get_serverid(lease, &opt, &len) >= 0);
         assert_se(len == 14);
@@ -439,14 +440,15 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option,
         assert_se(found_clientid && found_iana && found_serverid &&
                   found_elapsed_time);
 
-        assert_se(sd_dhcp6_lease_get_first_address(lease, &addr, &lt_pref,
-                                                   &lt_valid) >= 0);
+        sd_dhcp6_lease_reset_address_iter(lease);
+        assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+                                             &lt_valid) >= 0);
         assert_se(!memcmp(&addr, &msg_advertise[42], sizeof(addr)));
         assert_se(lt_pref == 150);
         assert_se(lt_valid == 180);
 
-        assert_se(sd_dhcp6_lease_get_next_address(lease, &addr, &lt_pref,
-                                                  &lt_valid) == -ENOMSG);
+        assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+                                             &lt_valid) == -ENOMSG);
 
         return 0;
 }
@@ -587,11 +589,10 @@ static int test_client_verify_information_request(DHCP6Message *information_requ
         assert_se(r == -ENOMSG);
         assert_se(found_clientid && found_elapsed_time);
 
-        assert_se(sd_dhcp6_lease_get_first_address(lease, &addr, &lt_pref,
-                                                   &lt_valid) == -ENOMSG);
+        sd_dhcp6_lease_reset_address_iter(lease);
 
-        assert_se(sd_dhcp6_lease_get_next_address(lease, &addr, &lt_pref,
-                                                  &lt_valid) == -ENOMSG);
+        assert_se(sd_dhcp6_lease_get_address(lease, &addr, &lt_pref,
+                                             &lt_valid) == -ENOMSG);
 
         return 0;
 }
diff --git a/src/systemd/sd-dhcp6-lease.h b/src/systemd/sd-dhcp6-lease.h
index 1126f1a..716d767 100644
--- a/src/systemd/sd-dhcp6-lease.h
+++ b/src/systemd/sd-dhcp6-lease.h
@@ -27,14 +27,11 @@
 
 typedef struct sd_dhcp6_lease sd_dhcp6_lease;
 
-int sd_dhcp6_lease_get_first_address(sd_dhcp6_lease *lease,
-                                     struct in6_addr *addr,
-                                     uint32_t *lifetime_preferred,
-                                     uint32_t *lifetime_valid);
-int sd_dhcp6_lease_get_next_address(sd_dhcp6_lease *lease,
-                                    struct in6_addr *addr,
-                                    uint32_t *lifetime_preferred,
-                                    uint32_t *lifetime_valid);
+void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease);
+int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease,
+                               struct in6_addr *addr,
+                               uint32_t *lifetime_preferred,
+                               uint32_t *lifetime_valid);
 
 sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease);
 sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease);



More information about the systemd-commits mailing list