Discussion:
[systemd-devel] [PATCH 03/24] sd-dhcp6-client: Add initial Router Advertisement test case
Patrik Flykt
2014-06-13 13:44:53 UTC
Permalink
Feed a Router Advertisement to the code and expect proper events
each time. The sending part is ignored, as all of it is static code
in the real dhcp_network_icmp6_send_rs() function.
---
Makefile.am | 13 ++-
src/libsystemd-network/test-dhcp6-rs.c | 155 +++++++++++++++++++++++++++++++++
2 files changed, 167 insertions(+), 1 deletion(-)
create mode 100644 src/libsystemd-network/test-dhcp6-rs.c

diff --git a/Makefile.am b/Makefile.am
index a8b5b79..96ef9ce 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2566,10 +2566,21 @@ test_ipv4ll_LDADD = \
libsystemd-internal.la \
libsystemd-shared.la

+test_dhcp6_rs_SOURCES = \
+ src/libsystemd-network/dhcp6-icmp6.h \
+ src/libsystemd-network/dhcp6-internal.h \
+ src/libsystemd-network/test-dhcp6-rs.c
+
+test_dhcp6_rs_LDADD = \
+ libsystemd-network.la \
+ libsystemd-internal.la \
+ libsystemd-shared.la
+
tests += \
test-dhcp-option \
test-dhcp-client \
- test-ipv4ll
+ test-ipv4ll \
+ test-dhcp6-rs

# ------------------------------------------------------------------------------
if ENABLE_GTK_DOC
diff --git a/src/libsystemd-network/test-dhcp6-rs.c b/src/libsystemd-network/test-dhcp6-rs.c
new file mode 100644
index 0000000..c59dd32
--- /dev/null
+++ b/src/libsystemd-network/test-dhcp6-rs.c
@@ -0,0 +1,155 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <netinet/icmp6.h>
+
+#include "socket-util.h"
+
+#include "dhcp6-internal.h"
+#include "icmp6-nd.h"
+
+static struct ether_addr mac_addr = {
+ .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
+};
+
+static bool verbose = false;
+static sd_event_source *test_hangcheck;
+static int test_fd[2];
+
+static int test_rs_hangcheck(sd_event_source *s, uint64_t usec,
+ void *userdata) {
+ assert(false);
+
+ return 0;
+}
+
+int dhcp_network_icmp6_bind_router_solicitation(int index) {
+ assert(index == 42);
+
+ if (socketpair(AF_UNIX, SOCK_DGRAM, 0, test_fd) < 0)
+ return -errno;
+
+ return test_fd[0];
+}
+
+static int send_ra(uint8_t flags) {
+ uint8_t advertisement[] = {
+ 0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x04, 0x40, 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,
+ 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,
+ };
+
+ advertisement[5] = flags;
+
+ assert(write(test_fd[1], advertisement, sizeof(advertisement)) ==
+ sizeof(advertisement));
+
+ if (verbose)
+ printf(" sent RA with flag 0x%02x\n", flags);
+
+ return 0;
+}
+
+int dhcp_network_icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
+ return send_ra(0);
+}
+
+static void test_rs_done(icmp6_nd *nd, int event, void *userdata) {
+ sd_event *e = userdata;
+ static int idx = 0;
+ struct {
+ uint8_t flag;
+ int event;
+ } flag_event[] = {
+ { 0, ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE },
+ { ND_RA_FLAG_OTHER, ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER },
+ { ND_RA_FLAG_MANAGED, ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED }
+ };
+ assert(nd);
+
+ assert(event == flag_event[idx].event);
+ idx++;
+
+ if (verbose)
+ printf(" got event %d\n", event);
+
+ if (idx < 3)
+ send_ra(flag_event[idx].flag);
+ else
+ sd_event_exit(e, 0);
+}
+
+static void test_rs(sd_event *e) {
+ usec_t time_now = now(CLOCK_MONOTONIC);
+ icmp6_nd *nd;
+
+ if (verbose)
+ printf("* %s\n", __FUNCTION__);
+
+ assert(icmp6_nd_new(&nd) >= 0);
+ assert(nd);
+
+ assert(icmp6_nd_attach_event(nd, e, 0) >= 0);
+
+ assert(icmp6_nd_set_index(nd, 42) >= 0);
+ assert(icmp6_nd_set_mac(nd, &mac_addr) >= 0);
+ assert(icmp6_nd_set_callback(nd, test_rs_done, e) >= 0);
+
+ assert(sd_event_add_time(e, &test_hangcheck, CLOCK_MONOTONIC,
+ time_now + 2 *USEC_PER_SEC, 0,
+ test_rs_hangcheck, NULL) >= 0);
+
+ assert(icmp6_router_solicitation_start(nd) >= 0);
+
+ sd_event_loop(e);
+
+ test_hangcheck = sd_event_source_unref(test_hangcheck);
+
+ nd = icmp6_nd_unref(nd);
+ assert(!nd);
+
+ close(test_fd[0]);
+ close(test_fd[1]);
+}
+
+int main(int argc, char *argv[]) {
+ sd_event *e;
+
+ assert(sd_event_new(&e) >= 0);
+
+ log_set_max_level(LOG_DEBUG);
+ log_parse_environment();
+ log_open();
+
+ test_rs(e);
+
+ return 0;
+}
--
1.9.1
Patrik Flykt
2014-06-13 13:44:52 UTC
Permalink
Provide functions to bind the ICMPv6 socket to the approriate interface
and set multicast sending and receiving according to RFC 3493, section
5.2. and RFC 3542, sections 3. and 3.3. Filter out all ICMPv6 messages
except Router Advertisements for the socket in question according to
RFC 3542, section 3.2.

Send Router Solicitations to the all routers multicast group as
described in RFC 4861, section 6. and act on the received Router
Advertisments according to section 6.3.7.

Implement a similar API for ICMPv6 handling as is done for DHCPv4 and
DHCPv6.
---
Makefile.am | 8 +-
src/libsystemd-network/dhcp6-internal.h | 29 +++
src/libsystemd-network/dhcp6-network.c | 131 +++++++++++++
src/libsystemd-network/icmp6-nd.c | 317 ++++++++++++++++++++++++++++++++
src/libsystemd-network/icmp6-nd.h | 59 ++++++
5 files changed, 543 insertions(+), 1 deletion(-)
create mode 100644 src/libsystemd-network/dhcp6-internal.h
create mode 100644 src/libsystemd-network/dhcp6-network.c
create mode 100644 src/libsystemd-network/icmp6-nd.c
create mode 100644 src/libsystemd-network/icmp6-nd.h

diff --git a/Makefile.am b/Makefile.am
index 4ff9f5a..a8b5b79 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2518,7 +2518,13 @@ libsystemd_network_la_SOURCES = \
src/libsystemd-network/ipv4ll-packet.c \
src/libsystemd-network/ipv4ll-internal.h \
src/libsystemd-network/network-internal.c \
- src/libsystemd-network/network-internal.h
+ src/libsystemd-network/network-internal.h \
+ src/systemd/sd-dhcp6-client.h \
+ src/libsystemd-network/sd-dhcp6-client.c \
+ src/libsystemd-network/icmp6-nd.h \
+ src/libsystemd-network/icmp6-nd.c \
+ src/libsystemd-network/dhcp6-internal.h \
+ src/libsystemd-network/dhcp6-network.c

libsystemd_network_la_LIBADD = \
libudev-internal.la \
diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
new file mode 100644
index 0000000..52283d7
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -0,0 +1,29 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <net/ethernet.h>
+
+#define log_dhcp6_client(p, fmt, ...) log_meta(LOG_DEBUG, __FILE__, __LINE__, __func__, "DHCPv6 CLIENT: " fmt, ##__VA_ARGS__)
+
+int dhcp_network_icmp6_bind_router_solicitation(int index);
+int dhcp_network_icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr);
diff --git a/src/libsystemd-network/dhcp6-network.c b/src/libsystemd-network/dhcp6-network.c
new file mode 100644
index 0000000..53ce23d
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-network.c
@@ -0,0 +1,131 @@
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <linux/if_packet.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+
+#include "socket-util.h"
+
+#include "dhcp6-internal.h"
+
+#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \
+ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } }
+
+#define IN6ADDR_ALL_NODES_MULTICAST_INIT \
+ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } }
+
+int dhcp_network_icmp6_bind_router_solicitation(int index)
+{
+ struct icmp6_filter filter = { };
+ struct ipv6_mreq mreq = {
+ .ipv6mr_multiaddr = IN6ADDR_ALL_NODES_MULTICAST_INIT,
+ .ipv6mr_interface = index,
+ };
+ _cleanup_close_ int s = -1;
+ int r, zero = 0, hops = 255;
+
+ s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ IPPROTO_ICMPV6);
+ if (s < 0)
+ return -errno;
+
+ ICMP6_FILTER_SETBLOCKALL(&filter);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
+ r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter,
+ sizeof(filter));
+ if (r < 0)
+ return -errno;
+
+ /* RFC 3315, section 6.7, bullet point 2 may indicate that an
+ IPV6_PKTINFO socket option also applies for ICMPv6 multicast.
+ Empirical experiments indicates otherwise and therefore an
+ IPV6_MULTICAST_IF socket option is used here instead */
+ r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index,
+ sizeof(index));
+ if (r < 0)
+ return -errno;
+
+ r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &zero,
+ sizeof(zero));
+ if (r < 0)
+ return -errno;
+
+ r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops,
+ sizeof(hops));
+ if (r < 0)
+ return -errno;
+
+ r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq,
+ sizeof(mreq));
+ if (r < 0)
+ return -errno;
+
+ r = s;
+ s = -1;
+ return r;
+}
+
+int dhcp_network_icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr)
+{
+ struct sockaddr_in6 dst = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT,
+ };
+ struct {
+ struct nd_router_solicit rs;
+ struct nd_opt_hdr rs_opt;
+ struct ether_addr rs_opt_mac;
+ } _packed_ rs = {
+ .rs.nd_rs_type = ND_ROUTER_SOLICIT,
+ };
+ struct iovec iov[1] = {
+ { &rs, },
+ };
+ struct msghdr msg = {
+ .msg_name = &dst,
+ .msg_namelen = sizeof(dst),
+ .msg_iov = iov,
+ .msg_iovlen = 1,
+ };
+ int r;
+
+ if (ether_addr) {
+ memcpy(&rs.rs_opt_mac, ether_addr, ETH_ALEN);
+ rs.rs_opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR;
+ rs.rs_opt.nd_opt_len = 1;
+ iov[0].iov_len = sizeof(rs);
+ } else
+ iov[0].iov_len = sizeof(rs.rs);
+
+ r = sendmsg(s, &msg, 0);
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
diff --git a/src/libsystemd-network/icmp6-nd.c b/src/libsystemd-network/icmp6-nd.c
new file mode 100644
index 0000000..a842a70
--- /dev/null
+++ b/src/libsystemd-network/icmp6-nd.c
@@ -0,0 +1,317 @@
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <netinet/icmp6.h>
+#include <string.h>
+
+#include "refcnt.h"
+#include "async.h"
+
+#include "dhcp6-internal.h"
+#include "icmp6-nd.h"
+
+#define ICMP6_ROUTER_SOLICITATION_INTERVAL 4 * USEC_PER_SEC
+#define ICMP6_MAX_ROUTER_SOLICITATIONS 3
+
+enum icmp6_nd_state {
+ ICMP6_NEIGHBOR_DISCOVERY_IDLE = 0,
+ ICMP6_ROUTER_SOLICITATION_SENT = 10,
+ ICMP6_ROUTER_ADVERTISMENT_LISTEN = 11,
+};
+
+struct icmp6_nd {
+ RefCount n_ref;
+
+ enum icmp6_nd_state state;
+ sd_event *event;
+ int event_priority;
+ int index;
+ struct ether_addr mac_addr;
+ int fd;
+ sd_event_source *recv;
+ sd_event_source *timeout;
+ int nd_sent;
+ icmp6_nd_callback_t callback;
+ void *userdata;
+};
+
+static void icmp6_nd_notify(icmp6_nd *nd, int event)
+{
+ if (nd->callback)
+ nd->callback(nd, event, nd->userdata);
+}
+
+int icmp6_nd_set_callback(icmp6_nd *nd, icmp6_nd_callback_t callback,
+ void *userdata) {
+ assert(nd);
+
+ nd->callback = callback;
+ nd->userdata = userdata;
+
+ return 0;
+}
+
+int icmp6_nd_set_index(icmp6_nd *nd, int interface_index) {
+ assert(nd);
+ assert(interface_index >= -1);
+
+ nd->index = interface_index;
+
+ return 0;
+}
+
+int icmp6_nd_set_mac(icmp6_nd *nd, const struct ether_addr *mac_addr) {
+ assert(nd);
+
+ if (mac_addr)
+ memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr));
+ else
+ memset(&nd->mac_addr, 0x00, sizeof(nd->mac_addr));
+
+ return 0;
+
+}
+
+int icmp6_nd_attach_event(icmp6_nd *nd, sd_event *event, int priority) {
+ int r;
+
+ assert_return(nd, -EINVAL);
+ assert_return(!nd->event, -EBUSY);
+
+ if (event)
+ nd->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&nd->event);
+ if (r < 0)
+ return 0;
+ }
+
+ nd->event_priority = priority;
+
+ return 0;
+}
+
+int icmp6_nd_detach_event(icmp6_nd *nd) {
+ assert_return(nd, -EINVAL);
+
+ nd->event = sd_event_unref(nd->event);
+
+ return 0;
+}
+
+sd_event *icmp6_nd_get_event(icmp6_nd *nd) {
+ assert(nd);
+
+ return nd->event;
+}
+
+icmp6_nd *icmp6_nd_ref(icmp6_nd *nd) {
+ assert (nd);
+
+ assert_se(REFCNT_INC(nd->n_ref) >= 2);
+
+ return nd;
+}
+
+static int icmp6_nd_init(icmp6_nd *nd) {
+ assert(nd);
+
+ nd->recv = sd_event_source_unref(nd->recv);
+ nd->fd = asynchronous_close(nd->fd);
+ nd->timeout = sd_event_source_unref(nd->timeout);
+
+ return 0;
+}
+
+icmp6_nd *icmp6_nd_unref(icmp6_nd *nd) {
+ if (nd && REFCNT_DEC(nd->n_ref) <= 0) {
+
+ icmp6_nd_init(nd);
+ icmp6_nd_detach_event(nd);
+
+ free(nd);
+ }
+
+ return NULL;
+}
+
+int icmp6_nd_new(icmp6_nd **ret) {
+ _cleanup_icmp6_nd_free_ icmp6_nd *nd = NULL;
+
+ assert(ret);
+
+ nd = new0(icmp6_nd, 1);
+ if (!nd)
+ return -ENOMEM;
+
+ nd->n_ref = REFCNT_INIT;
+
+ nd->index = -1;
+
+ *ret = nd;
+ nd = NULL;
+
+ return 0;
+}
+
+static int icmp6_router_advertisment_recv(sd_event_source *s, int fd,
+ uint32_t revents, void *userdata)
+{
+ icmp6_nd *nd = userdata;
+ ssize_t len;
+ struct nd_router_advert ra;
+ 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))
+ return 0;
+
+ if (ra.nd_ra_type != ND_ROUTER_ADVERT)
+ return 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 )
+ event = ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER;
+
+ if (ra.nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
+ event = ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED;
+
+ log_icmp6_nd(nd, "Received Router Advertisment 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");
+
+ icmp6_nd_notify(nd, event);
+
+ return 0;
+}
+
+static int icmp6_router_solicitation_timeout(sd_event_source *s, uint64_t usec,
+ void *userdata)
+{
+ icmp6_nd *nd = userdata;
+ uint64_t time_now, next_timeout;
+ struct ether_addr unset = { };
+ struct ether_addr *addr = NULL;
+ int r;
+
+ assert(s);
+ assert(nd);
+ assert(nd->event);
+
+ nd->timeout = sd_event_source_unref(nd->timeout);
+
+ if (nd->nd_sent >= ICMP6_MAX_ROUTER_SOLICITATIONS) {
+ icmp6_nd_notify(nd, ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT);
+ nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
+ } else {
+ if (memcmp(&nd->mac_addr, &unset, sizeof(struct ether_addr)))
+ addr = &nd->mac_addr;
+
+ r = dhcp_network_icmp6_send_router_solicitation(nd->fd, addr);
+ if (r < 0)
+ log_icmp6_nd(nd, "Error sending Router Solicitation");
+ else {
+ nd->state = ICMP6_ROUTER_SOLICITATION_SENT;
+ log_icmp6_nd(nd, "Sent Router Solicitation");
+ }
+
+ nd->nd_sent++;
+
+ r = sd_event_now(nd->event, CLOCK_MONOTONIC, &time_now);
+ if (r < 0) {
+ icmp6_nd_notify(nd, r);
+ return 0;
+ }
+
+ next_timeout = time_now + ICMP6_ROUTER_SOLICITATION_INTERVAL;
+
+ r = sd_event_add_time(nd->event, &nd->timeout, CLOCK_MONOTONIC,
+ next_timeout, 0,
+ icmp6_router_solicitation_timeout, nd);
+ if (r < 0) {
+ icmp6_nd_notify(nd, r);
+ return 0;
+ }
+
+ r = sd_event_source_set_priority(nd->timeout,
+ nd->event_priority);
+ if (r < 0) {
+ icmp6_nd_notify(nd, r);
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+int icmp6_router_solicitation_start(icmp6_nd *nd) {
+ int r;
+
+ assert(nd);
+ assert(nd->event);
+
+ if (nd->state != ICMP6_NEIGHBOR_DISCOVERY_IDLE)
+ return -EINVAL;
+
+ if (nd->index < 1)
+ return -EINVAL;
+
+ r = dhcp_network_icmp6_bind_router_solicitation(nd->index);
+ if (r < 0)
+ return r;
+
+ nd->fd = r;
+
+ r = sd_event_add_io(nd->event, &nd->recv, nd->fd, EPOLLIN,
+ icmp6_router_advertisment_recv, nd);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_source_set_priority(nd->recv, nd->event_priority);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_add_time(nd->event, &nd->timeout, CLOCK_MONOTONIC,
+ 0, 0, icmp6_router_solicitation_timeout, nd);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
+
+error:
+ if (r < 0)
+ icmp6_nd_init(nd);
+ else
+ log_dhcp6_client(client, "Start Router Solicitation");
+
+ return r;
+}
diff --git a/src/libsystemd-network/icmp6-nd.h b/src/libsystemd-network/icmp6-nd.h
new file mode 100644
index 0000000..a4e3124
--- /dev/null
+++ b/src/libsystemd-network/icmp6-nd.h
@@ -0,0 +1,59 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <netinet/in.h>
+#include <net/ethernet.h>
+
+#include "socket-util.h"
+#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,
+};
+
+#define log_icmp6_nd(p, fmt, ...) log_meta(LOG_DEBUG, __FILE__, __LINE__, __func__, "ICMPv6 CLIENT: " fmt, ##__VA_ARGS__)
+
+typedef struct icmp6_nd icmp6_nd;
+
+typedef void(*icmp6_nd_callback_t)(icmp6_nd *nd, int event, void *userdata);
+
+int icmp6_nd_set_callback(icmp6_nd *nd, icmp6_nd_callback_t cb, void *userdata);
+int icmp6_nd_set_index(icmp6_nd *nd, int interface_index);
+int icmp6_nd_set_mac(icmp6_nd *nd, const struct ether_addr *mac_addr);
+
+int icmp6_nd_attach_event(icmp6_nd *nd, sd_event *event, int priority);
+int icmp6_nd_detach_event(icmp6_nd *nd);
+sd_event *icmp6_nd_get_event(icmp6_nd *nd);
+
+icmp6_nd *icmp6_nd_ref(icmp6_nd *nd);
+icmp6_nd *icmp6_nd_unref(icmp6_nd *nd);
+int icmp6_nd_new(icmp6_nd **ret);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(icmp6_nd*, icmp6_nd_unref);
+#define _cleanup_icmp6_nd_free_ _cleanup_(icmp6_nd_unrefp)
+
+int icmp6_router_solicitation_start(icmp6_nd *nd);
--
1.9.1
Dan Williams
2014-06-13 17:17:44 UTC
Permalink
Post by Patrik Flykt
Provide functions to bind the ICMPv6 socket to the approriate interface
and set multicast sending and receiving according to RFC 3493, section
5.2. and RFC 3542, sections 3. and 3.3. Filter out all ICMPv6 messages
except Router Advertisements for the socket in question according to
RFC 3542, section 3.2.
Send Router Solicitations to the all routers multicast group as
described in RFC 4861, section 6. and act on the received Router
Advertisments according to section 6.3.7.
Implement a similar API for ICMPv6 handling as is done for DHCPv4 and
DHCPv6.
Two comments:

1) usage of struct ether_addr may prevent correct operation on
non-ethernet links, like Infiniband or PPP or GRE. They don't have
6-byte MAC addresses, so anywhere that currently uses a MAC address I'd
suggest passing "u8*, u8 len" instead, to allow for non-ethernet links.
See ndisc_fill_addr_option() in the kernel...

2) as I replied to Tom, could we keep RS/RA code together and not tie it
with DHCP stuff, since they aren't really related? DHCP is the consumer
of the M/O bits, but if DHCP isn't requested at all by the router via
the M/O bits, there's no reason for DHCP to ever be involved in the
process. I think it would be better to keep them fully separate.

Thanks!
Dan
Post by Patrik Flykt
---
Makefile.am | 8 +-
src/libsystemd-network/dhcp6-internal.h | 29 +++
src/libsystemd-network/dhcp6-network.c | 131 +++++++++++++
src/libsystemd-network/icmp6-nd.c | 317 ++++++++++++++++++++++++++++++++
src/libsystemd-network/icmp6-nd.h | 59 ++++++
5 files changed, 543 insertions(+), 1 deletion(-)
create mode 100644 src/libsystemd-network/dhcp6-internal.h
create mode 100644 src/libsystemd-network/dhcp6-network.c
create mode 100644 src/libsystemd-network/icmp6-nd.c
create mode 100644 src/libsystemd-network/icmp6-nd.h
diff --git a/Makefile.am b/Makefile.am
index 4ff9f5a..a8b5b79 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2518,7 +2518,13 @@ libsystemd_network_la_SOURCES = \
src/libsystemd-network/ipv4ll-packet.c \
src/libsystemd-network/ipv4ll-internal.h \
src/libsystemd-network/network-internal.c \
- src/libsystemd-network/network-internal.h
+ src/libsystemd-network/network-internal.h \
+ src/systemd/sd-dhcp6-client.h \
+ src/libsystemd-network/sd-dhcp6-client.c \
+ src/libsystemd-network/icmp6-nd.h \
+ src/libsystemd-network/icmp6-nd.c \
+ src/libsystemd-network/dhcp6-internal.h \
+ src/libsystemd-network/dhcp6-network.c
libsystemd_network_la_LIBADD = \
libudev-internal.la \
diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
new file mode 100644
index 0000000..52283d7
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -0,0 +1,29 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <net/ethernet.h>
+
+#define log_dhcp6_client(p, fmt, ...) log_meta(LOG_DEBUG, __FILE__, __LINE__, __func__, "DHCPv6 CLIENT: " fmt, ##__VA_ARGS__)
+
+int dhcp_network_icmp6_bind_router_solicitation(int index);
+int dhcp_network_icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr);
diff --git a/src/libsystemd-network/dhcp6-network.c b/src/libsystemd-network/dhcp6-network.c
new file mode 100644
index 0000000..53ce23d
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-network.c
@@ -0,0 +1,131 @@
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <linux/if_packet.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+
+#include "socket-util.h"
+
+#include "dhcp6-internal.h"
+
+#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \
+ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } }
+
+#define IN6ADDR_ALL_NODES_MULTICAST_INIT \
+ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } }
+
+int dhcp_network_icmp6_bind_router_solicitation(int index)
+{
+ struct icmp6_filter filter = { };
+ struct ipv6_mreq mreq = {
+ .ipv6mr_multiaddr = IN6ADDR_ALL_NODES_MULTICAST_INIT,
+ .ipv6mr_interface = index,
+ };
+ _cleanup_close_ int s = -1;
+ int r, zero = 0, hops = 255;
+
+ s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ IPPROTO_ICMPV6);
+ if (s < 0)
+ return -errno;
+
+ ICMP6_FILTER_SETBLOCKALL(&filter);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
+ r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter,
+ sizeof(filter));
+ if (r < 0)
+ return -errno;
+
+ /* RFC 3315, section 6.7, bullet point 2 may indicate that an
+ IPV6_PKTINFO socket option also applies for ICMPv6 multicast.
+ Empirical experiments indicates otherwise and therefore an
+ IPV6_MULTICAST_IF socket option is used here instead */
+ r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index,
+ sizeof(index));
+ if (r < 0)
+ return -errno;
+
+ r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &zero,
+ sizeof(zero));
+ if (r < 0)
+ return -errno;
+
+ r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops,
+ sizeof(hops));
+ if (r < 0)
+ return -errno;
+
+ r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq,
+ sizeof(mreq));
+ if (r < 0)
+ return -errno;
+
+ r = s;
+ s = -1;
+ return r;
+}
+
+int dhcp_network_icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr)
+{
+ struct sockaddr_in6 dst = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT,
+ };
+ struct {
+ struct nd_router_solicit rs;
+ struct nd_opt_hdr rs_opt;
+ struct ether_addr rs_opt_mac;
+ } _packed_ rs = {
+ .rs.nd_rs_type = ND_ROUTER_SOLICIT,
+ };
+ struct iovec iov[1] = {
+ { &rs, },
+ };
+ struct msghdr msg = {
+ .msg_name = &dst,
+ .msg_namelen = sizeof(dst),
+ .msg_iov = iov,
+ .msg_iovlen = 1,
+ };
+ int r;
+
+ if (ether_addr) {
+ memcpy(&rs.rs_opt_mac, ether_addr, ETH_ALEN);
+ rs.rs_opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR;
+ rs.rs_opt.nd_opt_len = 1;
+ iov[0].iov_len = sizeof(rs);
+ } else
+ iov[0].iov_len = sizeof(rs.rs);
+
+ r = sendmsg(s, &msg, 0);
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
diff --git a/src/libsystemd-network/icmp6-nd.c b/src/libsystemd-network/icmp6-nd.c
new file mode 100644
index 0000000..a842a70
--- /dev/null
+++ b/src/libsystemd-network/icmp6-nd.c
@@ -0,0 +1,317 @@
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <netinet/icmp6.h>
+#include <string.h>
+
+#include "refcnt.h"
+#include "async.h"
+
+#include "dhcp6-internal.h"
+#include "icmp6-nd.h"
+
+#define ICMP6_ROUTER_SOLICITATION_INTERVAL 4 * USEC_PER_SEC
+#define ICMP6_MAX_ROUTER_SOLICITATIONS 3
+
+enum icmp6_nd_state {
+ ICMP6_NEIGHBOR_DISCOVERY_IDLE = 0,
+ ICMP6_ROUTER_SOLICITATION_SENT = 10,
+ ICMP6_ROUTER_ADVERTISMENT_LISTEN = 11,
+};
+
+struct icmp6_nd {
+ RefCount n_ref;
+
+ enum icmp6_nd_state state;
+ sd_event *event;
+ int event_priority;
+ int index;
+ struct ether_addr mac_addr;
+ int fd;
+ sd_event_source *recv;
+ sd_event_source *timeout;
+ int nd_sent;
+ icmp6_nd_callback_t callback;
+ void *userdata;
+};
+
+static void icmp6_nd_notify(icmp6_nd *nd, int event)
+{
+ if (nd->callback)
+ nd->callback(nd, event, nd->userdata);
+}
+
+int icmp6_nd_set_callback(icmp6_nd *nd, icmp6_nd_callback_t callback,
+ void *userdata) {
+ assert(nd);
+
+ nd->callback = callback;
+ nd->userdata = userdata;
+
+ return 0;
+}
+
+int icmp6_nd_set_index(icmp6_nd *nd, int interface_index) {
+ assert(nd);
+ assert(interface_index >= -1);
+
+ nd->index = interface_index;
+
+ return 0;
+}
+
+int icmp6_nd_set_mac(icmp6_nd *nd, const struct ether_addr *mac_addr) {
+ assert(nd);
+
+ if (mac_addr)
+ memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr));
+ else
+ memset(&nd->mac_addr, 0x00, sizeof(nd->mac_addr));
+
+ return 0;
+
+}
+
+int icmp6_nd_attach_event(icmp6_nd *nd, sd_event *event, int priority) {
+ int r;
+
+ assert_return(nd, -EINVAL);
+ assert_return(!nd->event, -EBUSY);
+
+ if (event)
+ nd->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&nd->event);
+ if (r < 0)
+ return 0;
+ }
+
+ nd->event_priority = priority;
+
+ return 0;
+}
+
+int icmp6_nd_detach_event(icmp6_nd *nd) {
+ assert_return(nd, -EINVAL);
+
+ nd->event = sd_event_unref(nd->event);
+
+ return 0;
+}
+
+sd_event *icmp6_nd_get_event(icmp6_nd *nd) {
+ assert(nd);
+
+ return nd->event;
+}
+
+icmp6_nd *icmp6_nd_ref(icmp6_nd *nd) {
+ assert (nd);
+
+ assert_se(REFCNT_INC(nd->n_ref) >= 2);
+
+ return nd;
+}
+
+static int icmp6_nd_init(icmp6_nd *nd) {
+ assert(nd);
+
+ nd->recv = sd_event_source_unref(nd->recv);
+ nd->fd = asynchronous_close(nd->fd);
+ nd->timeout = sd_event_source_unref(nd->timeout);
+
+ return 0;
+}
+
+icmp6_nd *icmp6_nd_unref(icmp6_nd *nd) {
+ if (nd && REFCNT_DEC(nd->n_ref) <= 0) {
+
+ icmp6_nd_init(nd);
+ icmp6_nd_detach_event(nd);
+
+ free(nd);
+ }
+
+ return NULL;
+}
+
+int icmp6_nd_new(icmp6_nd **ret) {
+ _cleanup_icmp6_nd_free_ icmp6_nd *nd = NULL;
+
+ assert(ret);
+
+ nd = new0(icmp6_nd, 1);
+ if (!nd)
+ return -ENOMEM;
+
+ nd->n_ref = REFCNT_INIT;
+
+ nd->index = -1;
+
+ *ret = nd;
+ nd = NULL;
+
+ return 0;
+}
+
+static int icmp6_router_advertisment_recv(sd_event_source *s, int fd,
+ uint32_t revents, void *userdata)
+{
+ icmp6_nd *nd = userdata;
+ ssize_t len;
+ struct nd_router_advert ra;
+ 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))
+ return 0;
+
+ if (ra.nd_ra_type != ND_ROUTER_ADVERT)
+ return 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 )
+ event = ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER;
+
+ if (ra.nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
+ event = ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED;
+
+ log_icmp6_nd(nd, "Received Router Advertisment flags %s/%s",
+ "none",
+ "none");
+
+ icmp6_nd_notify(nd, event);
+
+ return 0;
+}
+
+static int icmp6_router_solicitation_timeout(sd_event_source *s, uint64_t usec,
+ void *userdata)
+{
+ icmp6_nd *nd = userdata;
+ uint64_t time_now, next_timeout;
+ struct ether_addr unset = { };
+ struct ether_addr *addr = NULL;
+ int r;
+
+ assert(s);
+ assert(nd);
+ assert(nd->event);
+
+ nd->timeout = sd_event_source_unref(nd->timeout);
+
+ if (nd->nd_sent >= ICMP6_MAX_ROUTER_SOLICITATIONS) {
+ icmp6_nd_notify(nd, ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT);
+ nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
+ } else {
+ if (memcmp(&nd->mac_addr, &unset, sizeof(struct ether_addr)))
+ addr = &nd->mac_addr;
+
+ r = dhcp_network_icmp6_send_router_solicitation(nd->fd, addr);
+ if (r < 0)
+ log_icmp6_nd(nd, "Error sending Router Solicitation");
+ else {
+ nd->state = ICMP6_ROUTER_SOLICITATION_SENT;
+ log_icmp6_nd(nd, "Sent Router Solicitation");
+ }
+
+ nd->nd_sent++;
+
+ r = sd_event_now(nd->event, CLOCK_MONOTONIC, &time_now);
+ if (r < 0) {
+ icmp6_nd_notify(nd, r);
+ return 0;
+ }
+
+ next_timeout = time_now + ICMP6_ROUTER_SOLICITATION_INTERVAL;
+
+ r = sd_event_add_time(nd->event, &nd->timeout, CLOCK_MONOTONIC,
+ next_timeout, 0,
+ icmp6_router_solicitation_timeout, nd);
+ if (r < 0) {
+ icmp6_nd_notify(nd, r);
+ return 0;
+ }
+
+ r = sd_event_source_set_priority(nd->timeout,
+ nd->event_priority);
+ if (r < 0) {
+ icmp6_nd_notify(nd, r);
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+int icmp6_router_solicitation_start(icmp6_nd *nd) {
+ int r;
+
+ assert(nd);
+ assert(nd->event);
+
+ if (nd->state != ICMP6_NEIGHBOR_DISCOVERY_IDLE)
+ return -EINVAL;
+
+ if (nd->index < 1)
+ return -EINVAL;
+
+ r = dhcp_network_icmp6_bind_router_solicitation(nd->index);
+ if (r < 0)
+ return r;
+
+ nd->fd = r;
+
+ r = sd_event_add_io(nd->event, &nd->recv, nd->fd, EPOLLIN,
+ icmp6_router_advertisment_recv, nd);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_source_set_priority(nd->recv, nd->event_priority);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_add_time(nd->event, &nd->timeout, CLOCK_MONOTONIC,
+ 0, 0, icmp6_router_solicitation_timeout, nd);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
+
+ if (r < 0)
+ icmp6_nd_init(nd);
+ else
+ log_dhcp6_client(client, "Start Router Solicitation");
+
+ return r;
+}
diff --git a/src/libsystemd-network/icmp6-nd.h b/src/libsystemd-network/icmp6-nd.h
new file mode 100644
index 0000000..a4e3124
--- /dev/null
+++ b/src/libsystemd-network/icmp6-nd.h
@@ -0,0 +1,59 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <netinet/in.h>
+#include <net/ethernet.h>
+
+#include "socket-util.h"
+#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,
+};
+
+#define log_icmp6_nd(p, fmt, ...) log_meta(LOG_DEBUG, __FILE__, __LINE__, __func__, "ICMPv6 CLIENT: " fmt, ##__VA_ARGS__)
+
+typedef struct icmp6_nd icmp6_nd;
+
+typedef void(*icmp6_nd_callback_t)(icmp6_nd *nd, int event, void *userdata);
+
+int icmp6_nd_set_callback(icmp6_nd *nd, icmp6_nd_callback_t cb, void *userdata);
+int icmp6_nd_set_index(icmp6_nd *nd, int interface_index);
+int icmp6_nd_set_mac(icmp6_nd *nd, const struct ether_addr *mac_addr);
+
+int icmp6_nd_attach_event(icmp6_nd *nd, sd_event *event, int priority);
+int icmp6_nd_detach_event(icmp6_nd *nd);
+sd_event *icmp6_nd_get_event(icmp6_nd *nd);
+
+icmp6_nd *icmp6_nd_ref(icmp6_nd *nd);
+icmp6_nd *icmp6_nd_unref(icmp6_nd *nd);
+int icmp6_nd_new(icmp6_nd **ret);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(icmp6_nd*, icmp6_nd_unref);
+#define _cleanup_icmp6_nd_free_ _cleanup_(icmp6_nd_unrefp)
+
+int icmp6_router_solicitation_start(icmp6_nd *nd);
Patrik Flykt
2014-06-16 09:39:41 UTC
Permalink
Hi,
Post by Dan Williams
1) usage of struct ether_addr may prevent correct operation on
non-ethernet links, like Infiniband or PPP or GRE. They don't have
6-byte MAC addresses, so anywhere that currently uses a MAC address I'd
suggest passing "u8*, u8 len" instead, to allow for non-ethernet links.
See ndisc_fill_addr_option() in the kernel...
Currently the MAC address is used as a fallback method for IAID creation
if the client runs in a container and can't access udev. If set, the MAC
address is also optionally sent in the router solicitation link address
option. Is the link address option mandatory for the links you listed
above?

The router solicitation code uses an IPPROTO_ICMPV6 socket bound to the
interface so that it avoids link-local IPv6 and MAC addresses
altogether.
Post by Dan Williams
2) as I replied to Tom, could we keep RS/RA code together and not tie it
with DHCP stuff, since they aren't really related? DHCP is the consumer
of the M/O bits, but if DHCP isn't requested at all by the router via
the M/O bits, there's no reason for DHCP to ever be involved in the
process. I think it would be better to keep them fully separate.
That I can do. Besides, such an API can then be extended to supply
networkd with DNS server information from the router advertisments. Once
someone has the time to write such code, that is.


Cheers,

Patrik
Patrik Flykt
2014-06-18 12:26:16 UTC
Permalink
Post by Patrik Flykt
Post by Dan Williams
1) usage of struct ether_addr may prevent correct operation on
non-ethernet links, like Infiniband or PPP or GRE. They don't have
6-byte MAC addresses, so anywhere that currently uses a MAC address
I'd suggest passing "u8*, u8 len" instead, to allow for non-ethernet
links. See ndisc_fill_addr_option() in the kernel...
This one I have left for a bit later as no harm is done if the MAC
address is not set.
Post by Patrik Flykt
Post by Dan Williams
2) as I replied to Tom, could we keep RS/RA code together and not
tie it with DHCP stuff, since they aren't really related? DHCP is
the consumer of the M/O bits, but if DHCP isn't requested at all by
the router via the M/O bits, there's no reason for DHCP to ever be
involved in the process. I think it would be better to keep them
fully separate.
That I can do.
This is now done and the interface looks like the following:

$ cat src/systemd/sd-icmp6-nd.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,
};

typedef struct sd_icmp6_nd sd_icmp6_nd;

typedef void(*sd_icmp6_nd_callback_t)(sd_icmp6_nd *nd, int event,
void *userdata);

int sd_icmp6_nd_set_callback(sd_icmp6_nd *nd, sd_icmp6_nd_callback_t cb,
void *userdata);
int sd_icmp6_nd_set_index(sd_icmp6_nd *nd, int interface_index);
int sd_icmp6_nd_set_mac(sd_icmp6_nd *nd, const struct ether_addr *mac_addr);

int sd_icmp6_nd_attach_event(sd_icmp6_nd *nd, sd_event *event, int priority);
int sd_icmp6_nd_detach_event(sd_icmp6_nd *nd);
sd_event *sd_icmp6_nd_get_event(sd_icmp6_nd *nd);

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_router_solicitation_start(sd_icmp6_nd *nd);


Did anyone have further comments on this initial patch set? Shall I send
the updated version to the mailing list or how do we want to proceed?

Cheers,

Patrik
Tom Gundersen
2014-06-18 13:24:31 UTC
Permalink
On Wed, Jun 18, 2014 at 2:26 PM, Patrik Flykt
Post by Patrik Flykt
Post by Patrik Flykt
Post by Dan Williams
1) usage of struct ether_addr may prevent correct operation on
non-ethernet links, like Infiniband or PPP or GRE. They don't have
6-byte MAC addresses, so anywhere that currently uses a MAC address
I'd suggest passing "u8*, u8 len" instead, to allow for non-ethernet
links. See ndisc_fill_addr_option() in the kernel...
This one I have left for a bit later as no harm is done if the MAC
address is not set.
Makes sense to keep this on the TODO and sort it out later. We
probably should do the same for the other libraries (where
applicable).
Post by Patrik Flykt
Post by Patrik Flykt
Post by Dan Williams
2) as I replied to Tom, could we keep RS/RA code together and not
tie it with DHCP stuff, since they aren't really related? DHCP is
the consumer of the M/O bits, but if DHCP isn't requested at all by
the router via the M/O bits, there's no reason for DHCP to ever be
involved in the process. I think it would be better to keep them
fully separate.
That I can do.
$ cat src/systemd/sd-icmp6-nd.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,
};
typedef struct sd_icmp6_nd sd_icmp6_nd;
typedef void(*sd_icmp6_nd_callback_t)(sd_icmp6_nd *nd, int event,
void *userdata);
int sd_icmp6_nd_set_callback(sd_icmp6_nd *nd, sd_icmp6_nd_callback_t cb,
void *userdata);
int sd_icmp6_nd_set_index(sd_icmp6_nd *nd, int interface_index);
int sd_icmp6_nd_set_mac(sd_icmp6_nd *nd, const struct ether_addr *mac_addr);
int sd_icmp6_nd_attach_event(sd_icmp6_nd *nd, sd_event *event, int priority);
int sd_icmp6_nd_detach_event(sd_icmp6_nd *nd);
sd_event *sd_icmp6_nd_get_event(sd_icmp6_nd *nd);
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_router_solicitation_start(sd_icmp6_nd *nd);
Did anyone have further comments on this initial patch set? Shall I send
the updated version to the mailing list or how do we want to proceed?
Go ahead and push. It is probably simpler to make further
suggestions/comments in terms of patches anyway.

Cheers,

Tom
Zbigniew Jędrzejewski-Szmek
2014-06-18 14:27:27 UTC
Permalink
Post by Tom Gundersen
Post by Patrik Flykt
Did anyone have further comments on this initial patch set? Shall I send
the updated version to the mailing list or how do we want to proceed?
Go ahead and push. It is probably simpler to make further
suggestions/comments in terms of patches anyway.
Agreed. I made some small comments, but the patch set looks great.

Zbyszek
Patrik Flykt
2014-06-19 12:05:29 UTC
Permalink
Post by Zbigniew Jędrzejewski-Szmek
Post by Tom Gundersen
Go ahead and push. It is probably simpler to make further
suggestions/comments in terms of patches anyway.
Agreed. I made some small comments, but the patch set looks great.
Patch set fixed according to your comments except that I skipped the
parentheses in 10/24, as they were gone anyway with 15/24. And I'll make
the test cases more verbose next week or so.

Cheers,

Patrik
Patrik Flykt
2014-06-19 12:51:59 UTC
Permalink
Post by Zbigniew Jędrzejewski-Szmek
Post by Tom Gundersen
Go ahead and push. It is probably simpler to make further
suggestions/comments in terms of patches anyway.
Agreed. I made some small comments, but the patch set looks great.
Polished a bit more and pushed, enjoy!

Cheers,

Patrik
Patrik Flykt
2014-06-13 13:44:55 UTC
Permalink
From: Tom Gundersen <***@jklm.no>

Initialize DHCP Unique Identifier when creating the client. The
DUID is generated based on the machine-id, which satisfies all the
requirements of what an DUID should be. The DUID type is DUID-EN.

Based on patch by Patrik Flykt.
---
src/libsystemd-network/sd-dhcp6-client.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)

diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index fbf3211..a785fa6 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -22,6 +22,7 @@
#include <errno.h>
#include <string.h>

+#include "siphash24.h"
#include "util.h"
#include "refcnt.h"

@@ -30,6 +31,9 @@
#include "dhcp6-internal.h"
#include "icmp6-nd.h"

+#define SYSTEMD_PEN 43793
+#define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
+
struct sd_dhcp6_client {
RefCount n_ref;

@@ -41,6 +45,12 @@ struct sd_dhcp6_client {
icmp6_nd *ra;
sd_dhcp6_client_cb_t cb;
void *userdata;
+
+ struct duid_en {
+ uint16_t type; /* DHCP6_DUID_EN */
+ uint32_t pen;
+ uint8_t id[8];
+ } _packed_ duid;
};

int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
@@ -251,6 +261,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
int sd_dhcp6_client_new(sd_dhcp6_client **ret)
{
_cleanup_dhcp6_client_free_ sd_dhcp6_client *client = NULL;
+ sd_id128_t machine_id;
+ int r;

assert_return(ret, -EINVAL);

@@ -262,6 +274,19 @@ int sd_dhcp6_client_new(sd_dhcp6_client **ret)

client->index = -1;

+ /* initialize DUID */
+ client->duid.type = htobe16(DHCP6_DUID_EN);
+ client->duid.pen = htobe32(SYSTEMD_PEN);
+
+ r = sd_id128_get_machine(&machine_id);
+ if (r < 0)
+ return r;
+
+ /* a bit of snake-oil perhaps, but no need to expose the machine-id
+ directly */
+ siphash24(client->duid.id, &machine_id, sizeof(machine_id),
+ HASH_KEY.bytes);
+
*ret = client;
client = NULL;
--
1.9.1
Patrik Flykt
2014-06-13 13:44:59 UTC
Permalink
Add the core of DHCPv6 client message retransmission and upper bound
timer and message count handling according to RFC 3315 Secions 7.1.2
and 14. Omit the DHCPv6 initial delay; for now it is assumed that
systemd-networkd will provide decent startup randomization that will
desynchronize the clients.

When reinitializing the client, clear all timers.
---
src/libsystemd-network/dhcp6-protocol.h | 5 ++
src/libsystemd-network/sd-dhcp6-client.c | 136 +++++++++++++++++++++++++++++++
src/systemd/sd-dhcp6-client.h | 2 +
3 files changed, 143 insertions(+)

diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h
index afe1413..442418d 100644
--- a/src/libsystemd-network/dhcp6-protocol.h
+++ b/src/libsystemd-network/dhcp6-protocol.h
@@ -41,6 +41,10 @@ enum {
DHCP6_PORT_CLIENT = 546,
};

+#define DHCP6_SOL_MAX_DELAY 1 * USEC_PER_SEC
+#define DHCP6_SOL_TIMEOUT 1 * USEC_PER_SEC
+#define DHCP6_SOL_MAX_RT 120 * USEC_PER_SEC
+
enum {
DHCP6_DUID_LLT = 1,
DHCP6_DUID_EN = 2,
@@ -51,6 +55,7 @@ enum {
enum DHCP6State {
DHCP6_STATE_STOPPED = 0,
DHCP6_STATE_RS = 1,
+ DHCP6_STATE_SOLICITATION = 2,
};

enum {
diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index f61442b..048b4ea 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -48,6 +48,10 @@ struct sd_dhcp6_client {
struct ether_addr mac_addr;
icmp6_nd *ra;
DHCP6IA ia_na;
+ usec_t retransmit_time;
+ uint8_t retransmit_count;
+ sd_event_source *timeout_resend;
+ sd_event_source *timeout_resend_expire;
sd_dhcp6_client_cb_t cb;
void *userdata;

@@ -111,6 +115,12 @@ static int client_initialize(sd_dhcp6_client *client)
client->ia_na.timeout_t2 =
sd_event_source_unref(client->ia_na.timeout_t2);

+ client->retransmit_time = 0;
+ client->retransmit_count = 0;
+ client->timeout_resend = sd_event_source_unref(client->timeout_resend);
+ client->timeout_resend_expire =
+ sd_event_source_unref(client->timeout_resend_expire);
+
client->state = DHCP6_STATE_STOPPED;

return 0;
@@ -126,6 +136,119 @@ static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) {
return client;
}

+static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
+ void *userdata) {
+ sd_dhcp6_client *client = userdata;
+
+ assert(s);
+ assert(client);
+ assert(client->event);
+
+ client_stop(client, DHCP6_EVENT_RESEND_EXPIRE);
+
+ return 0;
+}
+
+static usec_t client_timeout_compute_random(usec_t val) {
+ return val - val / 10 +
+ (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
+}
+
+static int client_timeout_resend(sd_event_source *s, uint64_t usec,
+ void *userdata) {
+ int r = 0;
+ sd_dhcp6_client *client = userdata;
+ usec_t time_now, init_retransmit_time, max_retransmit_time;
+ usec_t max_retransmit_duration;
+ uint8_t max_retransmit_count;
+ char time_string[FORMAT_TIMESPAN_MAX];
+
+ assert(s);
+ assert(client);
+ assert(client->event);
+
+ client->timeout_resend = sd_event_source_unref(client->timeout_resend);
+
+ switch (client->state) {
+ case DHCP6_STATE_SOLICITATION:
+ init_retransmit_time = DHCP6_SOL_TIMEOUT;
+ max_retransmit_time = DHCP6_SOL_MAX_RT;
+ max_retransmit_count = 0;
+ max_retransmit_duration = 0;
+
+ break;
+
+ case DHCP6_STATE_STOPPED:
+ case DHCP6_STATE_RS:
+ return 0;
+ }
+
+ if (max_retransmit_count &&
+ client->retransmit_count >= max_retransmit_count) {
+ client_stop(client, DHCP6_EVENT_RETRANS_MAX);
+ return 0;
+ }
+
+ r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
+ if (r < 0)
+ goto error;
+
+ if (!client->retransmit_time) {
+ client->retransmit_time =
+ client_timeout_compute_random(init_retransmit_time);
+ } else {
+ if (max_retransmit_time &&
+ client->retransmit_time > max_retransmit_time / 2)
+ client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
+ else
+ client->retransmit_time = client_timeout_compute_random(client->retransmit_time);
+ }
+
+ log_dhcp6_client(client, "Next retransmission in %s",
+ format_timespan(time_string, FORMAT_TIMESPAN_MAX,
+ client->retransmit_time, 0));
+
+ r = sd_event_add_time(client->event, &client->timeout_resend,
+ CLOCK_MONOTONIC,
+ time_now + client->retransmit_time,
+ 10 * USEC_PER_MSEC, client_timeout_resend,
+ client);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_source_set_priority(client->timeout_resend,
+ client->event_priority);
+ if (r < 0)
+ goto error;
+
+ if (max_retransmit_duration && !client->timeout_resend_expire) {
+
+ log_dhcp6_client(client, "Max retransmission duration "
+ "%"PRIu64" secs",
+ max_retransmit_duration / USEC_PER_SEC);
+
+ r = sd_event_add_time(client->event,
+ &client->timeout_resend_expire,
+ CLOCK_MONOTONIC,
+ time_now + max_retransmit_duration,
+ USEC_PER_SEC,
+ client_timeout_resend_expire, client);
+ if (r < 0)
+ goto error;
+
+ r = sd_event_source_set_priority(client->timeout_resend_expire,
+ client->event_priority);
+ if (r < 0)
+ goto error;
+ }
+
+error:
+ if (r < 0)
+ client_stop(client, r);
+
+ return 0;
+}
+
static int client_ensure_iaid(sd_dhcp6_client *client) {
const char *name = NULL;
uint64_t id;
@@ -182,6 +305,19 @@ static int client_start(sd_dhcp6_client *client)
if (r < 0)
return r;

+ client->state = DHCP6_STATE_SOLICITATION;
+
+ r = sd_event_add_time(client->event, &client->timeout_resend,
+ CLOCK_MONOTONIC, 0, 0, client_timeout_resend,
+ client);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(client->timeout_resend,
+ client->event_priority);
+ if (r < 0)
+ return r;
+
return 0;
}

diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h
index c64ad16..3aa1af9 100644
--- a/src/systemd/sd-dhcp6-client.h
+++ b/src/systemd/sd-dhcp6-client.h
@@ -29,6 +29,8 @@
enum {
DHCP6_EVENT_STOP = 0,
DHCP6_EVENT_NO_STATEFUL_CONFIGURATION = 10,
+ DHCP6_EVENT_RESEND_EXPIRE = 11,
+ DHCP6_EVENT_RETRANS_MAX = 12,
};

typedef struct sd_dhcp6_client sd_dhcp6_client;
--
1.9.1
Zbigniew Jędrzejewski-Szmek
2014-06-18 13:50:24 UTC
Permalink
Post by Patrik Flykt
Add the core of DHCPv6 client message retransmission and upper bound
timer and message count handling according to RFC 3315 Secions 7.1.2
^ 17.1.2 ?
Post by Patrik Flykt
and 14. Omit the DHCPv6 initial delay; for now it is assumed that
systemd-networkd will provide decent startup randomization that will
desynchronize the clients.
When reinitializing the client, clear all timers.
+ if (!client->retransmit_time) {
+ client->retransmit_time =
+ client_timeout_compute_random(init_retransmit_time);
+ } else {
+ if (max_retransmit_time &&
+ client->retransmit_time > max_retransmit_time / 2)
+ client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
+ else
+ client->retransmit_time = client_timeout_compute_random(client->retransmit_time);
+ }
Hm, I don't understand why the " / 2" is here. It seems that the
retransmit time suddenly jumps from max_retransmit_time/2 to
max_retransmit_time.

Zbyszek
Patrik Flykt
2014-06-19 07:55:25 UTC
Permalink
Hi,
Post by Zbigniew Jędrzejewski-Szmek
Post by Patrik Flykt
+ if (!client->retransmit_time) {
+ client->retransmit_time =
+ client_timeout_compute_random(init_retransmit_time);
+ } else {
+ if (max_retransmit_time &&
+ client->retransmit_time > max_retransmit_time / 2)
+ client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
+ else
+ client->retransmit_time = client_timeout_compute_random(client->retransmit_time);
There is a bug in here, this latter one should be += so that the value
actually doubles, thanks for noticing!
Post by Zbigniew Jędrzejewski-Szmek
Post by Patrik Flykt
+ }
Hm, I don't understand why the " / 2" is here. It seems that the
retransmit time suddenly jumps from max_retransmit_time/2 to
max_retransmit_time.
The existing client->retransmit_time contains the value calculated for
the current timeout. With the above bug fixed, this value is going to be
doubled with some randomness added. Instead of wasting expensive(?) CPU
cycles on computing the new, properly doubled value with randomness
added and see it thrown away immediately if found to be greater than the
given max_retransmit_time, I tried to be smart and avoid the computation
by checking it against half the value up front. After that the code only
needs to do the proper computation once.

It may have been clearer to multiply the retransmit_time by two instead.
Here I also rely on the compiler being smart enough to replace
multiplications or divisions by two with a bit shift to get it done
quickly enough.


Cheers,

Patrik
Patrik Flykt
2014-06-13 13:44:58 UTC
Permalink
Add test cases for basic DHCPv6 client handling, e.g. setting
interface index, mac address and attaching event loop.
---
Makefile.am | 13 ++++-
src/libsystemd-network/test-dhcp6-client.c | 76 ++++++++++++++++++++++++++++++
2 files changed, 88 insertions(+), 1 deletion(-)
create mode 100644 src/libsystemd-network/test-dhcp6-client.c

diff --git a/Makefile.am b/Makefile.am
index 96ef9ce..d50c23b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2567,6 +2567,7 @@ test_ipv4ll_LDADD = \
libsystemd-shared.la

test_dhcp6_rs_SOURCES = \
+ src/systemd/sd-dhcp6-client.h \
src/libsystemd-network/dhcp6-icmp6.h \
src/libsystemd-network/dhcp6-internal.h \
src/libsystemd-network/test-dhcp6-rs.c
@@ -2576,11 +2577,21 @@ test_dhcp6_rs_LDADD = \
libsystemd-internal.la \
libsystemd-shared.la

+test_dhcp6_client_SOURCES = \
+ src/systemd/sd-dhcp6-client.h \
+ src/libsystemd-network/test-dhcp6-client.c
+
+test_dhcp6_client_LDADD = \
+ libsystemd-network.la \
+ libsystemd-internal.la \
+ libsystemd-shared.la
+
tests += \
test-dhcp-option \
test-dhcp-client \
test-ipv4ll \
- test-dhcp6-rs
+ test-dhcp6-rs \
+ test-dhcp6-client

# ------------------------------------------------------------------------------
if ENABLE_GTK_DOC
diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c
new file mode 100644
index 0000000..b2dc8ba
--- /dev/null
+++ b/src/libsystemd-network/test-dhcp6-client.c
@@ -0,0 +1,76 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "macro.h"
+#include "sd-event.h"
+#include "event-util.h"
+
+#include "sd-dhcp6-client.h"
+#include "dhcp6-protocol.h"
+
+static struct ether_addr mac_addr = {
+ .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
+};
+
+static bool verbose = false;
+
+static int test_client_basic(sd_event *e) {
+ sd_dhcp6_client *client;
+
+ if (verbose)
+ printf("* %s\n", __FUNCTION__);
+
+ assert_se(sd_dhcp6_client_new(&client) >= 0);
+ assert_se(client);
+
+ assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0);
+
+ assert_se(sd_dhcp6_client_set_index(client, 15) == 0);
+ assert_se(sd_dhcp6_client_set_index(client, -42) == -EINVAL);
+ assert_se(sd_dhcp6_client_set_index(client, -1) == 0);
+ assert_se(sd_dhcp6_client_set_index(client, 42) >= 0);
+
+ assert_se(sd_dhcp6_client_set_mac(client, &mac_addr) >= 0);
+
+ assert_se(sd_dhcp6_client_set_callback(client, NULL, NULL) >= 0);
+
+ assert_se(sd_dhcp6_client_detach_event(client) >= 0);
+ assert_se(!sd_dhcp6_client_unref(client));
+
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_event_unref_ sd_event *e;
+
+ assert_se(sd_event_new(&e) >= 0);
+
+ log_set_max_level(LOG_DEBUG);
+ log_parse_environment();
+ log_open();
+
+ test_client_basic(e);
+
+ return 0;
+}
--
1.9.1
Patrik Flykt
2014-06-13 13:45:03 UTC
Permalink
Copy Router Advertisement functions and data from the previous
test case and verify the created Solicit message. The test will
take some non-trivial time to run due to the DHCPv6 initial delay.
---
Makefile.am | 1 +
src/libsystemd-network/test-dhcp6-client.c | 181 +++++++++++++++++++++++++++++
2 files changed, 182 insertions(+)

diff --git a/Makefile.am b/Makefile.am
index f6340dc..87ac728 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2580,6 +2580,7 @@ test_dhcp6_rs_LDADD = \

test_dhcp6_client_SOURCES = \
src/systemd/sd-dhcp6-client.h \
+ src/libsystemd-network/dhcp6-icmp6.h \
src/libsystemd-network/dhcp6-internal.h \
src/libsystemd-network/test-dhcp6-client.c

diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c
index b52f407..e8cc07d 100644
--- a/src/libsystemd-network/test-dhcp6-client.c
+++ b/src/libsystemd-network/test-dhcp6-client.c
@@ -21,7 +21,12 @@

#include <stdbool.h>
#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netinet/icmp6.h>

+#include "socket-util.h"
#include "macro.h"
#include "sd-event.h"
#include "event-util.h"
@@ -36,6 +41,12 @@ static struct ether_addr mac_addr = {

static bool verbose = false;

+static sd_event_source *hangcheck;
+static int test_rs_fd[2];
+static int test_dhcp_fd[2];
+static int test_index = 42;
+static sd_event *e_solicit;
+
static int test_client_basic(sd_event *e) {
sd_dhcp6_client *client;

@@ -128,6 +139,175 @@ static int test_option(sd_event *e) {
return 0;
}

+static int test_hangcheck(sd_event_source *s, uint64_t usec, void *userdata) {
+ assert_not_reached("Test case should have completed in 2 seconds");
+
+ return 0;
+}
+
+int dhcp_network_icmp6_bind_router_solicitation(int index) {
+ assert(index == 42);
+
+ if (socketpair(AF_UNIX, SOCK_DGRAM, 0, test_rs_fd) < 0)
+ return -errno;
+
+ return test_rs_fd[0];
+}
+
+static int send_ra(uint8_t flags) {
+ uint8_t advertisement[] = {
+ 0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x04, 0x40, 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,
+ 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,
+ };
+
+ advertisement[5] = flags;
+
+ assert(write(test_rs_fd[1], advertisement, sizeof(advertisement)) ==
+ sizeof(advertisement));
+
+ if (verbose)
+ printf(" sent RA with flag 0x%02x\n", flags);
+
+ return 0;
+}
+
+int dhcp_network_icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
+ return send_ra(ND_RA_FLAG_MANAGED);
+}
+
+int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) {
+ assert_se(index == test_index);
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, test_dhcp_fd) < 0)
+ return -errno;
+
+ return test_dhcp_fd[0];
+}
+
+static int verify_solicit(DHCP6Message *solicit, uint8_t *option, size_t len) {
+ uint8_t *optval;
+ uint16_t optcode;
+ size_t optlen;
+ bool found_clientid = false, found_iana = false;
+ int r;
+
+ assert_se(solicit->type == DHCP6_SOLICIT);
+
+ while ((r = dhcp6_option_parse(&option, &len,
+ &optcode, &optlen, &optval)) >= 0) {
+ switch(optcode) {
+ case DHCP6_OPTION_CLIENTID:
+ assert_se(!found_clientid);
+ found_clientid = true;
+
+ assert_se(optlen == 14);
+
+ break;
+
+ case DHCP6_OPTION_IA_NA:
+ assert_se(!found_iana);
+ found_iana = true;
+
+ assert_se(optlen == 12);
+
+ break;
+ }
+ }
+
+ assert_se(r == -ENOMSG);
+ assert_se(found_clientid && found_iana);
+
+ sd_event_exit(e_solicit, 0);
+
+ return 0;
+}
+
+int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address,
+ const void *packet, size_t len) {
+ struct in6_addr mcast =
+ IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
+ DHCP6Message *message;
+ uint8_t *option;
+
+ assert_se(s == test_dhcp_fd[0]);
+ assert_se(server_address);
+ assert_se(packet);
+ assert_se(len > sizeof(DHCP6Message) + 4);
+
+ assert_se(IN6_ARE_ADDR_EQUAL(server_address, &mcast));
+
+ message = (DHCP6Message *)packet;
+ option = (uint8_t *)(message + 1);
+ len -= sizeof(DHCP6Message);
+
+ assert_se(message->transaction_id & 0x00ffffff);
+
+ verify_solicit(message, option, len);
+
+ return len;
+}
+
+static void test_client_solicit_cb(sd_dhcp6_client *client, int event,
+ void *userdata) {
+ sd_event *e = userdata;
+
+ assert_se(e);
+
+ if (verbose)
+ printf(" got DHCPv6 event %d\n", event);
+
+ sd_event_exit(e, 0);
+}
+
+static int test_client_solicit(sd_event *e) {
+ sd_dhcp6_client *client;
+ usec_t time_now = now(CLOCK_MONOTONIC);
+
+ if (verbose)
+ printf("* %s\n", __FUNCTION__);
+
+ assert_se(sd_dhcp6_client_new(&client) >= 0);
+ assert_se(client);
+
+ assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0);
+
+ assert_se(sd_dhcp6_client_set_index(client, test_index) == 0);
+ assert_se(sd_dhcp6_client_set_mac(client, &mac_addr) >= 0);
+
+ assert_se(sd_dhcp6_client_set_callback(client,
+ test_client_solicit_cb, e) >= 0);
+
+ assert_se(sd_event_add_time(e, &hangcheck, CLOCK_MONOTONIC,
+ time_now + 2 * USEC_PER_SEC, 0,
+ test_hangcheck, NULL) >= 0);
+
+ e_solicit = e;
+
+ assert_se(sd_dhcp6_client_start(client) >= 0);
+
+ sd_event_loop(e);
+
+ hangcheck = sd_event_source_unref(hangcheck);
+
+ assert_se(!sd_dhcp6_client_unref(client));
+
+ test_rs_fd[1] = safe_close(test_rs_fd[1]);
+ test_dhcp_fd[1] = safe_close(test_dhcp_fd[1]);
+
+ return 0;
+}
+
int main(int argc, char *argv[]) {
_cleanup_event_unref_ sd_event *e;

@@ -139,6 +319,7 @@ int main(int argc, char *argv[]) {

test_client_basic(e);
test_option(e);
+ test_client_solicit(e);

assert_se(!sd_event_unref(e));
--
1.9.1
Zbigniew Jędrzejewski-Szmek
2014-06-18 14:00:40 UTC
Permalink
Post by Patrik Flykt
Copy Router Advertisement functions and data from the previous
test case and verify the created Solicit message. The test will
take some non-trivial time to run due to the DHCPv6 initial delay.
---
Makefile.am | 1 +
src/libsystemd-network/test-dhcp6-client.c | 181 +++++++++++++++++++++++++++++
2 files changed, 182 insertions(+)
diff --git a/Makefile.am b/Makefile.am
index f6340dc..87ac728 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2580,6 +2580,7 @@ test_dhcp6_rs_LDADD = \
test_dhcp6_client_SOURCES = \
src/systemd/sd-dhcp6-client.h \
+ src/libsystemd-network/dhcp6-icmp6.h \
src/libsystemd-network/dhcp6-internal.h \
src/libsystemd-network/test-dhcp6-client.c
diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c
index b52f407..e8cc07d 100644
--- a/src/libsystemd-network/test-dhcp6-client.c
+++ b/src/libsystemd-network/test-dhcp6-client.c
@@ -21,7 +21,12 @@
#include <stdbool.h>
#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netinet/icmp6.h>
+#include "socket-util.h"
#include "macro.h"
#include "sd-event.h"
#include "event-util.h"
@@ -36,6 +41,12 @@ static struct ether_addr mac_addr = {
static bool verbose = false;
Why not always run in verbose mode (in this test and other too)?
Normally the test output is redirected to a file, and most tests
binaries spit out debug messages freely.

Zbyszek
Patrik Flykt
2014-06-13 13:45:02 UTC
Permalink
Implement the initial functionality used for creating a DHCPv6 Solicit
message containing the needed options and send it to the DHCPv6
broadcast address. Increase the sent message count and ensure that
the Solicit Initial Retransmission Time is strictly greater than
the Solicitation IRT as described in RFC 3315, section 17.1.2.
---
src/libsystemd-network/dhcp6-internal.h | 4 ++
src/libsystemd-network/sd-dhcp6-client.c | 111 +++++++++++++++++++++++++++++++
2 files changed, 115 insertions(+)

diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
index 7a491fb..31f5bd2 100644
--- a/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -27,6 +27,7 @@
#include "sparse-endian.h"
#include "sd-event.h"
#include "list.h"
+#include "macro.h"

typedef struct DHCP6Address DHCP6Address;

@@ -69,3 +70,6 @@ int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
int dhcp6_network_bind_udp_socket(int index, struct in6_addr *address);
int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,
const void *packet, size_t len);
+
+const char *dhcp6_message_type_to_string(int s) _const_;
+int dhcp6_message_type_from_string(const char *s) _pure_;
diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index 048b4ea..bdd9177 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -48,6 +48,9 @@ struct sd_dhcp6_client {
struct ether_addr mac_addr;
icmp6_nd *ra;
DHCP6IA ia_na;
+ be32_t transaction_id;
+ int fd;
+ sd_event_source *receive_message;
usec_t retransmit_time;
uint8_t retransmit_count;
sd_event_source *timeout_resend;
@@ -62,6 +65,24 @@ struct sd_dhcp6_client {
} _packed_ duid;
};

+const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = {
+ [DHCP6_SOLICIT] = "SOLICIT",
+ [DHCP6_ADVERTISE] = "ADVERTISE",
+ [DHCP6_REQUEST] = "REQUEST",
+ [DHCP6_CONFIRM] = "CONFIRM",
+ [DHCP6_RENEW] = "RENEW",
+ [DHCP6_REBIND] = "REBIND",
+ [DHCP6_REPLY] = "REPLY",
+ [DHCP6_RELEASE] = "RELEASE",
+ [DHCP6_DECLINE] = "DECLINE",
+ [DHCP6_RECONFIGURE] = "RECONFIGURE",
+ [DHCP6_INFORMATION_REQUEST] = "INFORMATION-REQUEST",
+ [DHCP6_RELAY_FORW] = "RELAY-FORW",
+ [DHCP6_RELAY_REPL] = "RELAY-REPL",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);
+
int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
sd_dhcp6_client_cb_t cb, void *userdata)
{
@@ -110,6 +131,15 @@ static int client_initialize(sd_dhcp6_client *client)
{
assert_return(client, -EINVAL);

+ client->receive_message =
+ sd_event_source_unref(client->receive_message);
+
+ if (client->fd > 0)
+ safe_close(client->fd);
+ client->fd = -1;
+
+ client->transaction_id = random_u32() & 0x00ffffff;
+
client->ia_na.timeout_t1 =
sd_event_source_unref(client->ia_na.timeout_t1);
client->ia_na.timeout_t2 =
@@ -136,6 +166,55 @@ static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) {
return client;
}

+static int client_send_message(sd_dhcp6_client *client) {
+ _cleanup_free_ DHCP6Message *message = NULL;
+ struct in6_addr all_servers =
+ IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
+ size_t len, optlen = 512;
+ uint8_t *opt;
+ int r;
+
+ len = sizeof(DHCP6Message) + optlen;
+
+ message = malloc0(len);
+ if (!message)
+ return -ENOMEM;
+
+ opt = (uint8_t *)(message + 1);
+
+ message->transaction_id = client->transaction_id;
+
+ switch(client->state) {
+ case DHCP6_STATE_SOLICITATION:
+ message->type = DHCP6_SOLICIT;
+
+ r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID,
+ sizeof(client->duid), &client->duid);
+ if (r < 0)
+ return r;
+
+ r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case DHCP6_STATE_STOPPED:
+ case DHCP6_STATE_RS:
+ return -EINVAL;
+ }
+
+ r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
+ len - optlen);
+ if (r < 0)
+ return r;
+
+ log_dhcp6_client(client, "Sent %s",
+ dhcp6_message_type_to_string(message->type));
+
+ return 0;
+}
+
static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
void *userdata) {
sd_dhcp6_client *client = userdata;
@@ -189,6 +268,11 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
return 0;
}

+ r = client_send_message(client);
+ if (r >= 0)
+ client->retransmit_count++;
+
+
r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
if (r < 0)
goto error;
@@ -196,6 +280,10 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,
if (!client->retransmit_time) {
client->retransmit_time =
client_timeout_compute_random(init_retransmit_time);
+
+ if (client->state == DHCP6_STATE_SOLICITATION)
+ client->retransmit_time += init_retransmit_time / 10;
+
} else {
if (max_retransmit_time &&
client->retransmit_time > max_retransmit_time / 2)
@@ -293,6 +381,12 @@ static int client_ensure_iaid(sd_dhcp6_client *client) {
return 0;
}

+static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
+ void *userdata)
+{
+ return 0;
+}
+
static int client_start(sd_dhcp6_client *client)
{
int r;
@@ -305,6 +399,23 @@ static int client_start(sd_dhcp6_client *client)
if (r < 0)
return r;

+ r = dhcp6_network_bind_udp_socket(client->index, NULL);
+ if (r < 0)
+ return r;
+
+ client->fd = r;
+
+ r = sd_event_add_io(client->event, &client->receive_message,
+ client->fd, EPOLLIN, client_receive_message,
+ client);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(client->receive_message,
+ client->event_priority);
+ if (r < 0)
+ return r;
+
client->state = DHCP6_STATE_SOLICITATION;

r = sd_event_add_time(client->event, &client->timeout_resend,
--
1.9.1
Zbigniew Jędrzejewski-Szmek
2014-06-18 13:58:09 UTC
Permalink
Post by Patrik Flykt
Implement the initial functionality used for creating a DHCPv6 Solicit
message containing the needed options and send it to the DHCPv6
broadcast address. Increase the sent message count and ensure that
the Solicit Initial Retransmission Time is strictly greater than
the Solicitation IRT as described in RFC 3315, section 17.1.2.
---
src/libsystemd-network/dhcp6-internal.h | 4 ++
src/libsystemd-network/sd-dhcp6-client.c | 111 +++++++++++++++++++++++++++++++
2 files changed, 115 insertions(+)
diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
index 7a491fb..31f5bd2 100644
--- a/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -27,6 +27,7 @@
#include "sparse-endian.h"
#include "sd-event.h"
#include "list.h"
+#include "macro.h"
typedef struct DHCP6Address DHCP6Address;
@@ -69,3 +70,6 @@ int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
int dhcp6_network_bind_udp_socket(int index, struct in6_addr *address);
int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,
const void *packet, size_t len);
+
+const char *dhcp6_message_type_to_string(int s) _const_;
+int dhcp6_message_type_from_string(const char *s) _pure_;
diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index 048b4ea..bdd9177 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -48,6 +48,9 @@ struct sd_dhcp6_client {
struct ether_addr mac_addr;
icmp6_nd *ra;
DHCP6IA ia_na;
+ be32_t transaction_id;
+ int fd;
+ sd_event_source *receive_message;
usec_t retransmit_time;
uint8_t retransmit_count;
sd_event_source *timeout_resend;
@@ -62,6 +65,24 @@ struct sd_dhcp6_client {
} _packed_ duid;
};
+const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = {
+ [DHCP6_SOLICIT] = "SOLICIT",
+ [DHCP6_ADVERTISE] = "ADVERTISE",
+ [DHCP6_REQUEST] = "REQUEST",
+ [DHCP6_CONFIRM] = "CONFIRM",
+ [DHCP6_RENEW] = "RENEW",
+ [DHCP6_REBIND] = "REBIND",
+ [DHCP6_REPLY] = "REPLY",
+ [DHCP6_RELEASE] = "RELEASE",
+ [DHCP6_DECLINE] = "DECLINE",
+ [DHCP6_RECONFIGURE] = "RECONFIGURE",
+ [DHCP6_INFORMATION_REQUEST] = "INFORMATION-REQUEST",
+ [DHCP6_RELAY_FORW] = "RELAY-FORW",
+ [DHCP6_RELAY_REPL] = "RELAY-REPL",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);
+
int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
sd_dhcp6_client_cb_t cb, void *userdata)
{
@@ -110,6 +131,15 @@ static int client_initialize(sd_dhcp6_client *client)
{
assert_return(client, -EINVAL);
+ client->receive_message =
+ sd_event_source_unref(client->receive_message);
+
+ if (client->fd > 0)
+ safe_close(client->fd);
+ client->fd = -1;
client->fd = safe_close(client->fd);

That's what safe_close is for :)

Zbyszek
Filipe Brandenburger
2014-06-18 14:05:35 UTC
Permalink
On Wed, Jun 18, 2014 at 6:58 AM, Zbigniew Jędrzejewski-Szmek
Post by Zbigniew Jędrzejewski-Szmek
Post by Patrik Flykt
+ if (client->fd > 0)
+ safe_close(client->fd);
+ client->fd = -1;
client->fd = safe_close(client->fd);
That's what safe_close is for :)
And shouldn't the check be for client->fd >= 0? Zero is a valid file descriptor.

Cheers,
Filipe
Zbigniew Jędrzejewski-Szmek
2014-06-18 14:25:21 UTC
Permalink
Post by Filipe Brandenburger
On Wed, Jun 18, 2014 at 6:58 AM, Zbigniew Jędrzejewski-Szmek
Post by Zbigniew Jędrzejewski-Szmek
Post by Patrik Flykt
+ if (client->fd > 0)
+ safe_close(client->fd);
+ client->fd = -1;
client->fd = safe_close(client->fd);
That's what safe_close is for :)
And shouldn't the check be for client->fd >= 0? Zero is a valid file descriptor.
Yeah... but note that safe_close already has the fd >= 0 check, so the
replacement line replaces the if too.

Zbyszek
Patrik Flykt
2014-06-19 11:52:06 UTC
Permalink
Post by Zbigniew Jędrzejewski-Szmek
Post by Filipe Brandenburger
On Wed, Jun 18, 2014 at 6:58 AM, Zbigniew Jędrzejewski-Szmek
Post by Zbigniew Jędrzejewski-Szmek
Post by Patrik Flykt
+ if (client->fd > 0)
+ safe_close(client->fd);
+ client->fd = -1;
client->fd = safe_close(client->fd);
That's what safe_close is for :)
And shouldn't the check be for client->fd >= 0? Zero is a valid file descriptor.
Yeah... but note that safe_close already has the fd >= 0 check, so the
replacement line replaces the if too.
If I omit the client->fd > 0 check I get the following in the test case:

Assertion 'close_nointr(fd) != -EBADF' failed at src/shared/util.c:205,
function safe_close(). Aborting.


Cheers,

Patrik
Zbigniew Jędrzejewski-Szmek
2014-06-19 12:35:57 UTC
Permalink
Post by Patrik Flykt
Post by Zbigniew Jędrzejewski-Szmek
Post by Filipe Brandenburger
On Wed, Jun 18, 2014 at 6:58 AM, Zbigniew Jędrzejewski-Szmek
Post by Zbigniew Jędrzejewski-Szmek
Post by Patrik Flykt
+ if (client->fd > 0)
+ safe_close(client->fd);
+ client->fd = -1;
client->fd = safe_close(client->fd);
That's what safe_close is for :)
And shouldn't the check be for client->fd >= 0? Zero is a valid file descriptor.
Yeah... but note that safe_close already has the fd >= 0 check, so the
replacement line replaces the if too.
Assertion 'close_nointr(fd) != -EBADF' failed at src/shared/util.c:205,
function safe_close(). Aborting.
Hm, is client->fd == 0? Most likely this means that it needs to be initialized
to -1.

Zbyszek
Lennart Poettering
2014-06-20 17:45:39 UTC
Permalink
Post by Patrik Flykt
Post by Zbigniew Jędrzejewski-Szmek
Post by Filipe Brandenburger
On Wed, Jun 18, 2014 at 6:58 AM, Zbigniew Jędrzejewski-Szmek
Post by Zbigniew Jędrzejewski-Szmek
Post by Patrik Flykt
+ if (client->fd > 0)
+ safe_close(client->fd);
+ client->fd = -1;
client->fd = safe_close(client->fd);
That's what safe_close is for :)
And shouldn't the check be for client->fd >= 0? Zero is a valid file descriptor.
Yeah... but note that safe_close already has the fd >= 0 check, so the
replacement line replaces the if too.
Assertion 'close_nointr(fd) != -EBADF' failed at src/shared/util.c:205,
function safe_close(). Aborting.
safe_close() is a call that will ignore all kinds of errors except one:
EBADF. THe rationale for that is that the kernel has these weird
semantics that close() might fail with errors like EIO or whatever else
if something could't be written to disk or so, but the fd will still be
closed. Hence, you can invoke safe_close() in all those cases where you
just want to get rid of an fd, and don't care about anything else. Now,
it will only trip up on one specific problem which always indicates a
programming error: when close() returns EBADF, since that is the error
that is returned when you invoke close() on an fd that doesn't exist.

Putting this all together: safe_close() is basically your one stop
solution to getting rid of fds, and even updating your variable you
store it in:

fd = safe_close(fd);

safe_close() always returns -1, always gets rid of the fd, will be a NOP
if the fd is < 0. Will never fail. However, it if you invoke it the only
way that is a real programming error which is with an already-closed fd
or a never-opened fd, then it will assert() and die.

Hope this makes sense.

Or long words short: if the code tripped up like yours above, this is no
indication that safe_close() wasn't right. Instead it's an indication
that you are passing it a rubbish fd.

Lennart
--
Lennart Poettering, Red Hat
Zbigniew Jędrzejewski-Szmek
2014-06-20 20:00:36 UTC
Permalink
Post by Lennart Poettering
Post by Patrik Flykt
Post by Zbigniew Jędrzejewski-Szmek
Post by Filipe Brandenburger
On Wed, Jun 18, 2014 at 6:58 AM, Zbigniew Jędrzejewski-Szmek
Post by Zbigniew Jędrzejewski-Szmek
Post by Patrik Flykt
+ if (client->fd > 0)
+ safe_close(client->fd);
+ client->fd = -1;
client->fd = safe_close(client->fd);
That's what safe_close is for :)
And shouldn't the check be for client->fd >= 0? Zero is a valid file descriptor.
Yeah... but note that safe_close already has the fd >= 0 check, so the
replacement line replaces the if too.
Assertion 'close_nointr(fd) != -EBADF' failed at src/shared/util.c:205,
function safe_close(). Aborting.
EBADF. THe rationale for that is that the kernel has these weird
semantics that close() might fail with errors like EIO or whatever else
if something could't be written to disk or so, but the fd will still be
closed. Hence, you can invoke safe_close() in all those cases where you
just want to get rid of an fd, and don't care about anything else. Now,
it will only trip up on one specific problem which always indicates a
programming error: when close() returns EBADF, since that is the error
that is returned when you invoke close() on an fd that doesn't exist.
Putting this all together: safe_close() is basically your one stop
solution to getting rid of fds, and even updating your variable you
fd = safe_close(fd);
safe_close() always returns -1, always gets rid of the fd, will be a NOP
if the fd is < 0. Will never fail. However, it if you invoke it the only
way that is a real programming error which is with an already-closed fd
or a never-opened fd, then it will assert() and die.
Hope this makes sense.
Or long words short: if the code tripped up like yours above, this is no
indication that safe_close() wasn't right. Instead it's an indication
that you are passing it a rubbish fd.
Yeah, fixed in c806ffb.

Zbyszek

Patrik Flykt
2014-06-13 13:45:10 UTC
Permalink
As described in RFC 3315, Section 17.1.2, a client has to wait until the
first timeout has elapsed before it is allowed to request IPv6 addresses
from the DHCPv6 server. This is indicated by a non-NULL lease and a
non-zero resend count. Should the Advertisement contain a preference
value of 255 or be received after the first timeout, IPv6 address
requesting is started immediately.

In response to these events, create a Request message and set up proper
resend timers to send the message to the server.
---
src/libsystemd-network/dhcp6-protocol.h | 4 +++
src/libsystemd-network/sd-dhcp6-client.c | 47 ++++++++++++++++++++++++++++++--
2 files changed, 48 insertions(+), 3 deletions(-)

diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h
index e8df509..a3a8be1 100644
--- a/src/libsystemd-network/dhcp6-protocol.h
+++ b/src/libsystemd-network/dhcp6-protocol.h
@@ -54,6 +54,9 @@ enum {
#define DHCP6_SOL_MAX_DELAY 1 * USEC_PER_SEC
#define DHCP6_SOL_TIMEOUT 1 * USEC_PER_SEC
#define DHCP6_SOL_MAX_RT 120 * USEC_PER_SEC
+#define DHCP6_REQ_TIMEOUT 1 * USEC_PER_SEC
+#define DHCP6_REQ_MAX_RT 120 * USEC_PER_SEC
+#define DHCP6_REQ_MAX_RC 10

enum {
DHCP6_DUID_LLT = 1,
@@ -66,6 +69,7 @@ enum DHCP6State {
DHCP6_STATE_STOPPED = 0,
DHCP6_STATE_RS = 1,
DHCP6_STATE_SOLICITATION = 2,
+ DHCP6_STATE_REQUEST = 3,
};

enum {
diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index 98835f7..312c84c 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -216,12 +216,22 @@ static int client_send_message(sd_dhcp6_client *client) {
case DHCP6_STATE_SOLICITATION:
message->type = DHCP6_SOLICIT;

- r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID,
- sizeof(client->duid), &client->duid);
+ r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
if (r < 0)
return r;

- r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
+ break;
+
+ case DHCP6_STATE_REQUEST:
+ message->type = DHCP6_REQUEST;
+
+ r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_SERVERID,
+ client->lease->serverid_len,
+ client->lease->serverid);
+ if (r < 0)
+ return r;
+
+ r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
if (r < 0)
return r;

@@ -232,6 +242,11 @@ static int client_send_message(sd_dhcp6_client *client) {
return -EINVAL;
}

+ r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID,
+ sizeof(client->duid), &client->duid);
+ if (r < 0)
+ return r;
+
r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
len - optlen);
if (r < 0)
@@ -278,6 +293,12 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,

switch (client->state) {
case DHCP6_STATE_SOLICITATION:
+
+ if (client->retransmit_count && client->lease) {
+ client_start(client, DHCP6_STATE_REQUEST);
+ return 0;
+ }
+
init_retransmit_time = DHCP6_SOL_TIMEOUT;
max_retransmit_time = DHCP6_SOL_MAX_RT;
max_retransmit_count = 0;
@@ -285,6 +306,14 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,

break;

+ case DHCP6_STATE_REQUEST:
+ init_retransmit_time = DHCP6_REQ_TIMEOUT;
+ max_retransmit_time = DHCP6_REQ_MAX_RT;
+ max_retransmit_count = DHCP6_REQ_MAX_RC;
+ max_retransmit_duration = 0;
+
+ break;
+
case DHCP6_STATE_STOPPED:
case DHCP6_STATE_RS:
return 0;
@@ -541,6 +570,9 @@ static int client_receive_advertise(sd_dhcp6_client *client,
r = 0;
}

+ if (pref_advertise == 255 || client->retransmit_count > 1)
+ r = DHCP6_STATE_REQUEST;
+
return r;
}

@@ -601,8 +633,12 @@ static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
case DHCP6_STATE_SOLICITATION:
r = client_receive_advertise(client, message, len);

+ if (r == DHCP6_STATE_REQUEST)
+ client_start(client, r);
+
break;

+ case DHCP6_STATE_REQUEST:
case DHCP6_STATE_STOPPED:
case DHCP6_STATE_RS:
return 0;
@@ -660,6 +696,11 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
client->state = DHCP6_STATE_SOLICITATION;

break;
+
+ case DHCP6_STATE_REQUEST:
+ client->state = state;
+
+ break;
}

client->transaction_id = random_u32() & htobe32(0x00ffffff);
--
1.9.1
Patrik Flykt
2014-06-13 13:44:54 UTC
Permalink
Try DHCPv6 if router solicitation times out as a link without routers
may use DHCPv6 according to RFC 4862 section 5.5.2.
---
src/libsystemd-network/sd-dhcp6-client.c | 70 +++++++++++++++++++++++++++++++-
src/systemd/sd-dhcp6-client.h | 1 +
2 files changed, 69 insertions(+), 2 deletions(-)

diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index 5063d4a..fbf3211 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -27,6 +27,8 @@

#include "sd-dhcp6-client.h"
#include "dhcp6-protocol.h"
+#include "dhcp6-internal.h"
+#include "icmp6-nd.h"

struct sd_dhcp6_client {
RefCount n_ref;
@@ -36,6 +38,7 @@ struct sd_dhcp6_client {
int event_priority;
int index;
struct ether_addr mac_addr;
+ icmp6_nd *ra;
sd_dhcp6_client_cb_t cb;
void *userdata;
};
@@ -103,6 +106,30 @@ static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) {
return client;
}

+static void dhcp6_receive_router_advertisment(icmp6_nd *nd, int event,
+ void *userdata)
+{
+ sd_dhcp6_client *client = userdata;
+
+ if (event < 0) {
+ log_dhcp6_client(client, "Router Advertisment failed with "
+ "%d %s", event, strerror(-event));
+ client_notify(client, event);
+ return;
+ }
+
+ switch (event) {
+ case ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE:
+ case ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER:
+ client_stop(client, DHCP6_EVENT_NO_STATEFUL_CONFIGURATION);
+ return;
+
+ case ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT:
+ case ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED:
+ break;
+ }
+}
+
int sd_dhcp6_client_stop(sd_dhcp6_client *client)
{
client_stop(client, DHCP6_EVENT_STOP);
@@ -112,13 +139,50 @@ int sd_dhcp6_client_stop(sd_dhcp6_client *client)

int sd_dhcp6_client_start(sd_dhcp6_client *client)
{
- int r = 0;
+ _cleanup_icmp6_nd_free_ icmp6_nd *nd = NULL;
+ int r;
+ struct ether_addr *mac_addr = NULL;
+ struct ether_addr unset = { };

assert_return(client, -EINVAL);
assert_return(client->event, -EINVAL);
assert_return(client->index > 0, -EINVAL);

- return r;
+ r = icmp6_nd_new(&nd);
+ if (r < 0)
+ return r;
+
+ r = icmp6_nd_attach_event(nd, client->event, client->event_priority);
+ if (r < 0)
+ return r;
+
+ r = icmp6_nd_set_index(nd, client->index);
+ if (r < 0)
+ return r;
+
+ if (memcmp(&client->mac_addr, &unset, sizeof(unset)))
+ mac_addr = &client->mac_addr;
+
+ r = icmp6_nd_set_mac(nd, mac_addr);
+ if (r < 0)
+ return r;
+
+ r = icmp6_nd_set_callback(nd, dhcp6_receive_router_advertisment,
+ client);
+ if (r < 0)
+ return r;
+
+ r = icmp6_router_solicitation_start(nd);
+ if (r < 0)
+ return r;
+
+ client->ra = icmp6_nd_unref(client->ra);
+ client->ra = nd;
+ nd = NULL;
+
+ client->state = DHCP6_STATE_RS;
+
+ return 0;
}

int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
@@ -171,6 +235,8 @@ sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {

sd_dhcp6_client_detach_event(client);

+ client->ra = icmp6_nd_unref(client->ra);
+
free(client);

return NULL;
diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h
index 4965011..c64ad16 100644
--- a/src/systemd/sd-dhcp6-client.h
+++ b/src/systemd/sd-dhcp6-client.h
@@ -28,6 +28,7 @@

enum {
DHCP6_EVENT_STOP = 0,
+ DHCP6_EVENT_NO_STATEFUL_CONFIGURATION = 10,
};

typedef struct sd_dhcp6_client sd_dhcp6_client;
--
1.9.1
Patrik Flykt
2014-06-13 13:45:08 UTC
Permalink
Add a basic test case excersising once more option parsing function
in addition to lease handling. Check that the address iteration
functions return the correct IPv6 address and lifetimes and that
only one address is returned. Also verify that the server ID and
preference values are read correctly.
---
src/libsystemd-network/test-dhcp6-client.c | 138 +++++++++++++++++++++++++++++
1 file changed, 138 insertions(+)

diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c
index e8cc07d..afc19b6 100644
--- a/src/libsystemd-network/test-dhcp6-client.c
+++ b/src/libsystemd-network/test-dhcp6-client.c
@@ -34,6 +34,7 @@
#include "sd-dhcp6-client.h"
#include "dhcp6-protocol.h"
#include "dhcp6-internal.h"
+#include "dhcp6-lease-internal.h"

static struct ether_addr mac_addr = {
.ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
@@ -139,6 +140,142 @@ static int test_option(sd_event *e) {
return 0;
}

+static uint8_t msg_advertise[198] = {
+ 0x02, 0x0f, 0xb4, 0xe5, 0x00, 0x01, 0x00, 0x0e,
+ 0x00, 0x01, 0x00, 0x01, 0x1a, 0x6b, 0xf3, 0x30,
+ 0x3c, 0x97, 0x0e, 0xcf, 0xa3, 0x7d, 0x00, 0x03,
+ 0x00, 0x5e, 0x0e, 0xcf, 0xa3, 0x7d, 0x00, 0x00,
+ 0x00, 0x50, 0x00, 0x00, 0x00, 0x78, 0x00, 0x05,
+ 0x00, 0x18, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad,
+ 0xbe, 0xef, 0x78, 0xee, 0x1c, 0xf3, 0x09, 0x3c,
+ 0x55, 0xad, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00,
+ 0x00, 0xb4, 0x00, 0x0d, 0x00, 0x32, 0x00, 0x00,
+ 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x28,
+ 0x65, 0x73, 0x29, 0x20, 0x72, 0x65, 0x6e, 0x65,
+ 0x77, 0x65, 0x64, 0x2e, 0x20, 0x47, 0x72, 0x65,
+ 0x65, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x20, 0x66,
+ 0x72, 0x6f, 0x6d, 0x20, 0x70, 0x6c, 0x61, 0x6e,
+ 0x65, 0x74, 0x20, 0x45, 0x61, 0x72, 0x74, 0x68,
+ 0x00, 0x17, 0x00, 0x10, 0x20, 0x01, 0x0d, 0xb8,
+ 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x0b,
+ 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74,
+ 0x72, 0x61, 0x00, 0x00, 0x1f, 0x00, 0x10, 0x20,
+ 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x02, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x19,
+ 0x40, 0x5c, 0x53, 0x78, 0x2b, 0xcb, 0xb3, 0x6d,
+ 0x53, 0x00, 0x07, 0x00, 0x01, 0x00
+};
+
+static int test_advertise_option(sd_event *e) {
+ _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
+ DHCP6Message *advertise = (DHCP6Message *)msg_advertise;
+ uint8_t *optval, *opt = &msg_advertise[sizeof(DHCP6Message)];
+ uint16_t optcode;
+ size_t optlen, len = sizeof(msg_advertise);
+ be32_t val;
+ uint8_t preference = 255;
+ struct in6_addr addr;
+ uint32_t lt_pref, lt_valid;
+ int r;
+ bool opt_clientid = false;
+
+ if (verbose)
+ printf("* %s\n", __FUNCTION__);
+
+ assert_se(dhcp6_lease_new(&lease) >= 0);
+
+ assert_se(advertise->type == DHCP6_ADVERTISE);
+ assert_se((be32toh(advertise->transaction_id) & 0x00ffffff) ==
+ 0x0fb4e5);
+
+ while ((r = dhcp6_option_parse(&opt, &len, &optcode, &optlen,
+ &optval)) >= 0) {
+
+ switch(optcode) {
+ case DHCP6_OPTION_CLIENTID:
+ assert_se(optlen == 14);
+
+ opt_clientid = true;
+ break;
+
+ case DHCP6_OPTION_IA_NA:
+ assert_se(optlen == 94);
+ assert_se(!memcmp(optval, &msg_advertise[26], optlen));
+
+ val = htobe32(0x0ecfa37d);
+ assert_se(!memcmp(optval, &val, sizeof(val)));
+
+ val = htobe32(80);
+ assert_se(!memcmp(optval + 4, &val, sizeof(val)));
+
+ val = htobe32(120);
+ assert_se(!memcmp(optval + 8, &val, sizeof(val)));
+
+ assert_se(dhcp6_option_parse_ia(&optval, &optlen,
+ optcode,
+ &lease->ia) >= 0);
+
+ break;
+
+ case DHCP6_OPTION_SERVERID:
+ assert_se(optlen == 14);
+ assert_se(!memcmp(optval, &msg_advertise[179], optlen));
+
+ assert_se(dhcp6_lease_set_serverid(lease, optval,
+ optlen) >= 0);
+ break;
+
+ case DHCP6_OPTION_PREFERENCE:
+ assert_se(optlen == 1);
+ assert_se(!*optval);
+
+ assert_se(dhcp6_lease_set_preference(lease,
+ *optval) >= 0);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+
+ assert_se(r == -ENOMSG);
+
+ assert_se(opt_clientid);
+
+ assert_se(sd_dhcp6_lease_get_first_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_first_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(!memcmp(&addr, &msg_advertise[42], sizeof(addr)));
+ assert_se(sd_dhcp6_lease_get_next_address(lease, &addr, &lt_pref,
+ &lt_valid) == -ENOMSG);
+
+ assert_se(dhcp6_lease_get_serverid(lease, &opt, &len) >= 0);
+ assert_se(len == 14);
+ assert_se(!memcmp(opt, &msg_advertise[179], len));
+
+ assert_se(dhcp6_lease_get_preference(lease, &preference) >= 0);
+ assert_se(preference == 0);
+
+ return 0;
+}
+
static int test_hangcheck(sd_event_source *s, uint64_t usec, void *userdata) {
assert_not_reached("Test case should have completed in 2 seconds");

@@ -319,6 +456,7 @@ int main(int argc, char *argv[]) {

test_client_basic(e);
test_option(e);
+ test_advertise_option(e);
test_client_solicit(e);

assert_se(!sd_event_unref(e));
--
1.9.1
Patrik Flykt
2014-06-13 13:45:05 UTC
Permalink
Add functionality to parse DHCPv6 Identity Association for
Non-temporary (IA_NA) and Temporary Addresses (IA_TA) options.
Both of them contain one or more IA Address (IAADDR) options
and optinally a status code option. Only the IA_NA option
contains lease lifetimes. See RFC 3315, sections 22.4., 22.5.,
22.6., 22.13. and appendix B. for details. If the lease
timeouts are not set, use the ones recommended for servers in
section 22.4.

Factor out common code in the form of an option header parsing
helper function.
---
src/libsystemd-network/dhcp6-internal.h | 2 +
src/libsystemd-network/dhcp6-option.c | 182 ++++++++++++++++++++++++++++++--
2 files changed, 175 insertions(+), 9 deletions(-)

diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
index 31f5bd2..ec1d82a 100644
--- a/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -66,6 +66,8 @@ int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia);
int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
size_t *optlen, uint8_t **optvalue);
+int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
+ DHCP6IA *ia);

int dhcp6_network_bind_udp_socket(int index, struct in6_addr *address);
int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,
diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c
index cc4d261..55fd872 100644
--- a/src/libsystemd-network/dhcp6-option.c
+++ b/src/libsystemd-network/dhcp6-option.c
@@ -129,22 +129,186 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
return 0;
}

+
+static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *opt,
+ size_t *optlen) {
+ uint16_t len;
+
+ assert_return(buf, -EINVAL);
+ assert_return(opt, -EINVAL);
+ assert_return(optlen, -EINVAL);
+
+ if (*buflen < 4)
+ return -ENOMSG;
+
+ len = (*buf)[2] << 8 | (*buf)[3];
+
+ if (len > *buflen)
+ return -ENOMSG;
+
+ *opt = (*buf)[0] << 8 | (*buf)[1];
+ *optlen = len;
+
+ (*buf) += 4;
+ (*buflen) -= 4;
+
+ return 0;
+}
+
int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
size_t *optlen, uint8_t **optvalue) {
- assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
+ int r;

- if (*buflen == 0)
- return -ENOMSG;
+ assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);

- *optcode = (*buf)[0] << 8 | (*buf)[1];
- *optlen = (*buf)[2] << 8 | (*buf)[3];
+ r = option_parse_hdr(buf, buflen, optcode, optlen);
+ if (r < 0)
+ return r;

- if (*optlen > *buflen - 4)
+ if (*optlen > *buflen)
return -ENOBUFS;

- *optvalue = &(*buf)[4];
- *buflen -= (*optlen + 4);
- (*buf) += (*optlen + 4);
+ *optvalue = *buf;
+ *buflen -= *optlen;
+ (*buf) += *optlen;

return 0;
}
+
+int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
+ DHCP6IA *ia) {
+ int r;
+ uint16_t opt, status;
+ size_t optlen;
+ size_t iaaddr_offset;
+ DHCP6Address *addr;
+ uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0;
+
+ assert_return(ia, -EINVAL);
+ assert_return(!ia->addresses, -EINVAL);
+
+ switch (iatype) {
+ case DHCP6_OPTION_IA_NA:
+
+ if (*buflen < DHCP6_OPTION_IA_NA_LEN + DHCP6_OPTION_HDR_LEN +
+ DHCP6_OPTION_IAADDR_LEN) {
+ r = -ENOBUFS;
+ goto error;
+ }
+
+ iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
+ memcpy(&ia->id, *buf, iaaddr_offset);
+
+ lt_t1 = be32toh(ia->lifetime_t1);
+ lt_t2 = be32toh(ia->lifetime_t2);
+
+ if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
+ log_dhcp6_client(client, "IA T1 %ds > T2 %ds",
+ lt_t1, lt_t2);
+ r = -EINVAL;
+ goto error;
+ }
+
+ break;
+
+ case DHCP6_OPTION_IA_TA:
+ if (*buflen < DHCP6_OPTION_IA_TA_LEN + DHCP6_OPTION_HDR_LEN +
+ DHCP6_OPTION_IAADDR_LEN) {
+ r = -ENOBUFS;
+ goto error;
+ }
+
+ iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
+ memcpy(&ia->id, *buf, iaaddr_offset);
+
+ ia->lifetime_t1 = 0;
+ ia->lifetime_t2 = 0;
+
+ break;
+
+ default:
+ r = -ENOMSG;
+ goto error;
+ }
+
+ ia->type = iatype;
+
+ *buflen -= iaaddr_offset;
+ *buf += iaaddr_offset;
+
+ while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) {
+
+ switch (opt) {
+ case DHCP6_OPTION_IAADDR:
+
+ addr = new0(DHCP6Address, 1);
+ if (!addr) {
+ r = -ENOMEM;
+ goto error;
+ }
+
+ LIST_INIT(addresses, addr);
+
+ memcpy(&addr->address, *buf, DHCP6_OPTION_IAADDR_LEN);
+
+ lt_valid = be32toh(addr->lifetime_valid);
+ lt_pref = be32toh(addr->lifetime_valid);
+
+ if (!lt_valid || lt_pref > lt_valid) {
+ log_dhcp6_client(client, "IA preferred %ds > "
+ "valid %ds", lt_pref,
+ lt_valid);
+ free(addr);
+ } else {
+ LIST_PREPEND(addresses, ia->addresses, addr);
+ if (lt_valid < lt_min)
+ lt_min = lt_valid;
+ }
+
+ break;
+
+ case DHCP6_OPTION_STATUS_CODE:
+ if (optlen < sizeof(status))
+ break;
+
+ status = (*buf)[0] << 8 | (*buf)[1];
+ if (status) {
+ log_dhcp6_client(client, "IA status %d",
+ status);
+ r = -EINVAL;
+ goto error;
+ }
+
+ break;
+
+ default:
+ log_dhcp6_client(client, "Unknown IA option %d", opt);
+ break;
+ }
+
+ *buflen -= optlen;
+ *buf += optlen;
+ }
+
+ if (r == -ENOMSG)
+ r = 0;
+
+ if (!ia->lifetime_t1 && !ia->lifetime_t2) {
+ lt_t1 = lt_min / 2;
+ lt_t2 = lt_min / 10 * 8;
+ ia->lifetime_t1 = htobe32(lt_t1);
+ ia->lifetime_t2 = htobe32(lt_t2);
+
+ log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as "
+ "both were zero", lt_t1, lt_t2);
+ }
+
+ if (*buflen)
+ r = -ENOMSG;
+
+error:
+ *buf += *buflen;
+ *buflen = 0;
+
+ return r;
+}
--
1.9.1
Zbigniew Jędrzejewski-Szmek
2014-06-18 14:08:08 UTC
Permalink
Post by Patrik Flykt
Add functionality to parse DHCPv6 Identity Association for
Non-temporary (IA_NA) and Temporary Addresses (IA_TA) options.
Both of them contain one or more IA Address (IAADDR) options
and optinally a status code option. Only the IA_NA option
contains lease lifetimes. See RFC 3315, sections 22.4., 22.5.,
22.6., 22.13. and appendix B. for details. If the lease
timeouts are not set, use the ones recommended for servers in
section 22.4.
Factor out common code in the form of an option header parsing
helper function.
---
src/libsystemd-network/dhcp6-internal.h | 2 +
src/libsystemd-network/dhcp6-option.c | 182 ++++++++++++++++++++++++++++++--
2 files changed, 175 insertions(+), 9 deletions(-)
diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
index 31f5bd2..ec1d82a 100644
--- a/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -66,6 +66,8 @@ int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia);
int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
size_t *optlen, uint8_t **optvalue);
+int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
+ DHCP6IA *ia);
int dhcp6_network_bind_udp_socket(int index, struct in6_addr *address);
int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,
diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c
index cc4d261..55fd872 100644
--- a/src/libsystemd-network/dhcp6-option.c
+++ b/src/libsystemd-network/dhcp6-option.c
@@ -129,22 +129,186 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
return 0;
}
+
+static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *opt,
+ size_t *optlen) {
+ uint16_t len;
+
+ assert_return(buf, -EINVAL);
+ assert_return(opt, -EINVAL);
+ assert_return(optlen, -EINVAL);
+
+ if (*buflen < 4)
+ return -ENOMSG;
+
+ len = (*buf)[2] << 8 | (*buf)[3];
+
+ if (len > *buflen)
+ return -ENOMSG;
+
+ *opt = (*buf)[0] << 8 | (*buf)[1];
+ *optlen = len;
+
+ (*buf) += 4;
+ (*buflen) -= 4;
Unnecessary parens?
Post by Patrik Flykt
- *optvalue = &(*buf)[4];
- *buflen -= (*optlen + 4);
- (*buf) += (*optlen + 4);
+ *optvalue = *buf;
+ *buflen -= *optlen;
+ (*buf) += *optlen;
Zbyszek
Patrik Flykt
2014-06-13 13:45:11 UTC
Permalink
Enhance the test case by replying with an Advertise message to the
client. Copy the transaction id, IAID and DUID from the Solicit
message. Verify the Request message created by the DHCPv6 client
implementation and move the main loop exit to the end of the Request
message verification.

Provide local variants for detect_vm(), detect_container() and
detect_virtualization() defined in virt.h. This makes the DHCPv6
library believe it is run in a container and does not try to request
interface information from udev for the non-existing interface index
used by the test case code.
---
src/libsystemd-network/test-dhcp6-client.c | 134 +++++++++++++++++++++++++++--
1 file changed, 129 insertions(+), 5 deletions(-)

diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c
index afc19b6..1acbf95 100644
--- a/src/libsystemd-network/test-dhcp6-client.c
+++ b/src/libsystemd-network/test-dhcp6-client.c
@@ -30,6 +30,7 @@
#include "macro.h"
#include "sd-event.h"
#include "event-util.h"
+#include "virt.h"

#include "sd-dhcp6-client.h"
#include "dhcp6-protocol.h"
@@ -46,6 +47,9 @@ static sd_event_source *hangcheck;
static int test_rs_fd[2];
static int test_dhcp_fd[2];
static int test_index = 42;
+static int test_client_message_num;
+static be32_t test_iaid = 0;
+static uint8_t test_duid[14] = { };
static sd_event *e_solicit;

static int test_client_basic(sd_event *e) {
@@ -282,6 +286,18 @@ static int test_hangcheck(sd_event_source *s, uint64_t usec, void *userdata) {
return 0;
}

+int detect_vm(const char **id) {
+ return 1;
+}
+
+int detect_container(const char **id) {
+ return 1;
+}
+
+int detect_virtualization(const char **id) {
+ return 1;
+}
+
int dhcp_network_icmp6_bind_router_solicitation(int index) {
assert(index == 42);

@@ -332,7 +348,106 @@ int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) {
return test_dhcp_fd[0];
}

-static int verify_solicit(DHCP6Message *solicit, uint8_t *option, size_t len) {
+static int test_client_send_reply(DHCP6Message *request) {
+ return 0;
+}
+
+static int test_client_verify_request(DHCP6Message *request, uint8_t *option,
+ size_t len) {
+ _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
+ uint8_t *optval;
+ uint16_t optcode;
+ size_t optlen;
+ bool found_clientid = false, found_iana = false, found_serverid = false;
+ int r;
+ struct in6_addr addr;
+ be32_t val;
+ uint32_t lt_pref, lt_valid;
+
+ assert_se(request->type == DHCP6_REQUEST);
+
+ assert_se(dhcp6_lease_new(&lease) >= 0);
+
+ while ((r = dhcp6_option_parse(&option, &len,
+ &optcode, &optlen, &optval)) >= 0) {
+ switch(optcode) {
+ case DHCP6_OPTION_CLIENTID:
+ assert_se(!found_clientid);
+ found_clientid = true;
+
+ assert_se(!memcmp(optval, &test_duid,
+ sizeof(test_duid)));
+
+ break;
+
+ case DHCP6_OPTION_IA_NA:
+ assert_se(!found_iana);
+ found_iana = true;
+
+
+ assert_se(optlen == 40);
+ assert_se(!memcmp(optval, &test_iaid, sizeof(test_iaid)));
+
+ val = htobe32(80);
+ assert_se(!memcmp(optval + 4, &val, sizeof(val)));
+
+ val = htobe32(120);
+ assert_se(!memcmp(optval + 8, &val, sizeof(val)));
+
+ assert_se(!dhcp6_option_parse_ia(&optval, &optlen,
+ optcode, &lease->ia));
+
+ break;
+
+ case DHCP6_OPTION_SERVERID:
+ assert_se(!found_serverid);
+ found_serverid = true;
+
+ assert_se(optlen == 14);
+ assert_se(!memcmp(&msg_advertise[179], optval, optlen));
+
+ break;
+ }
+ }
+
+ assert_se(r == -ENOMSG);
+ assert_se(found_clientid && found_iana && found_serverid);
+
+ assert_se(sd_dhcp6_lease_get_first_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);
+
+ sd_event_exit(e_solicit, 0);
+
+ return 0;
+}
+
+static int test_client_send_advertise(DHCP6Message *solicit)
+{
+ DHCP6Message advertise;
+
+ advertise.transaction_id = solicit->transaction_id;
+ advertise.type = DHCP6_ADVERTISE;
+
+ memcpy(msg_advertise, &advertise.transaction_id, 4);
+
+ memcpy(&msg_advertise[8], test_duid, sizeof(test_duid));
+
+ memcpy(&msg_advertise[26], &test_iaid, sizeof(test_iaid));
+
+ assert_se(write(test_dhcp_fd[1], msg_advertise, sizeof(msg_advertise))
+ == sizeof(msg_advertise));
+
+ return 0;
+}
+
+static int test_client_verify_solicit(DHCP6Message *solicit, uint8_t *option,
+ size_t len) {
uint8_t *optval;
uint16_t optcode;
size_t optlen;
@@ -348,7 +463,8 @@ static int verify_solicit(DHCP6Message *solicit, uint8_t *option, size_t len) {
assert_se(!found_clientid);
found_clientid = true;

- assert_se(optlen == 14);
+ assert_se(optlen == sizeof(test_duid));
+ memcpy(&test_duid, optval, sizeof(test_duid));

break;

@@ -358,6 +474,8 @@ static int verify_solicit(DHCP6Message *solicit, uint8_t *option, size_t len) {

assert_se(optlen == 12);

+ memcpy(&test_iaid, optval, sizeof(test_iaid));
+
break;
}
}
@@ -365,8 +483,6 @@ static int verify_solicit(DHCP6Message *solicit, uint8_t *option, size_t len) {
assert_se(r == -ENOMSG);
assert_se(found_clientid && found_iana);

- sd_event_exit(e_solicit, 0);
-
return 0;
}

@@ -390,7 +506,15 @@ int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address,

assert_se(message->transaction_id & 0x00ffffff);

- verify_solicit(message, option, len);
+ if (test_client_message_num == 0) {
+ test_client_verify_solicit(message, option, len);
+ test_client_send_advertise(message);
+ test_client_message_num++;
+ } else if (test_client_message_num == 1) {
+ test_client_verify_request(message, option, len);
+ test_client_send_reply(message);
+ test_client_message_num++;
+ }

return len;
}
--
1.9.1
Patrik Flykt
2014-06-13 13:44:51 UTC
Permalink
Add initial structure definition and functions for setting index, MAC
address, callback and event loop. Define protocol values and states.
---
src/libsystemd-network/dhcp6-protocol.h | 104 ++++++++++++++++
src/libsystemd-network/sd-dhcp6-client.c | 203 +++++++++++++++++++++++++++++++
src/systemd/sd-dhcp6-client.h | 54 ++++++++
3 files changed, 361 insertions(+)
create mode 100644 src/libsystemd-network/dhcp6-protocol.h
create mode 100644 src/libsystemd-network/sd-dhcp6-client.c
create mode 100644 src/systemd/sd-dhcp6-client.h

diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h
new file mode 100644
index 0000000..afe1413
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-protocol.h
@@ -0,0 +1,104 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2013 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "macro.h"
+#include "sparse-endian.h"
+
+struct DHCP6Message {
+ union {
+ struct {
+ uint8_t type;
+ uint8_t _pad[3];
+ } _packed_;
+ be32_t transaction_id;
+ };
+} _packed_;
+
+typedef struct DHCP6Message DHCP6Message;
+
+enum {
+ DHCP6_PORT_SERVER = 547,
+ DHCP6_PORT_CLIENT = 546,
+};
+
+enum {
+ DHCP6_DUID_LLT = 1,
+ DHCP6_DUID_EN = 2,
+ DHCP6_DUID_LL = 3,
+ DHCP6_DUID_UUID = 4,
+};
+
+enum DHCP6State {
+ DHCP6_STATE_STOPPED = 0,
+ DHCP6_STATE_RS = 1,
+};
+
+enum {
+ DHCP6_SOLICIT = 1,
+ DHCP6_ADVERTISE = 2,
+ DHCP6_REQUEST = 3,
+ DHCP6_CONFIRM = 4,
+ DHCP6_RENEW = 5,
+ DHCP6_REBIND = 6,
+ DHCP6_REPLY = 7,
+ DHCP6_RELEASE = 8,
+ DHCP6_DECLINE = 9,
+ DHCP6_RECONFIGURE = 10,
+ DHCP6_INFORMATION_REQUEST = 11,
+ DHCP6_RELAY_FORW = 12,
+ DHCP6_RELAY_REPL = 13,
+ _DHCP6_MESSAGE_MAX = 14,
+};
+
+enum {
+ DHCP6_OPTION_CLIENTID = 1,
+ DHCP6_OPTION_SERVERID = 2,
+ DHCP6_OPTION_IA_NA = 3,
+ DHCP6_OPTION_IA_TA = 4,
+ DHCP6_OPTION_IAADDR = 5,
+ DHCP6_OPTION_ORO = 6,
+ DHCP6_OPTION_PREFERENCE = 7,
+ DHCP6_OPTION_ELAPSED_TIME = 8,
+ DHCP6_OPTION_RELAY_MSG = 9,
+ /* option code 10 is unassigned */
+ DHCP6_OPTION_AUTH = 11,
+ DHCP6_OPTION_UNICAST = 12,
+ DHCP6_OPTION_STATUS_CODE = 13,
+ DHCP6_OPTION_RAPID_COMMIT = 14,
+ DHCP6_OPTION_USER_CLASS = 15,
+ DHCP6_OPTION_VENDOR_CLASS = 16,
+ DHCP6_OPTION_VENDOR_OPTS = 17,
+ DHCP6_OPTION_INTERFACE_ID = 18,
+ DHCP6_OPTION_RECONF_MSG = 19,
+ DHCP6_OPTION_RECONF_ACCEPT = 20,
+};
+
+enum {
+ DHCP6_STATUS_SUCCESS = 0,
+ DHCP6_STATUS_UNSPEC_FAIL = 1,
+ DHCP6_STATUS_NO_ADDRS_AVAIL = 2,
+ DHCP6_STATUS_NO_BINDING = 3,
+ DHCP6_STATUS_NOT_ON_LINK = 4,
+ DHCP6_STATUS_USE_MULTICAST = 5,
+ _DHCP6_STATUS_MAX = 6,
+};
diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
new file mode 100644
index 0000000..5063d4a
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -0,0 +1,203 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+
+#include "util.h"
+#include "refcnt.h"
+
+#include "sd-dhcp6-client.h"
+#include "dhcp6-protocol.h"
+
+struct sd_dhcp6_client {
+ RefCount n_ref;
+
+ enum DHCP6State state;
+ sd_event *event;
+ int event_priority;
+ int index;
+ struct ether_addr mac_addr;
+ sd_dhcp6_client_cb_t cb;
+ void *userdata;
+};
+
+int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
+ sd_dhcp6_client_cb_t cb, void *userdata)
+{
+ assert_return(client, -EINVAL);
+
+ client->cb = cb;
+ client->userdata = userdata;
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index)
+{
+ assert_return(client, -EINVAL);
+ assert_return(interface_index >= -1, -EINVAL);
+
+ client->index = interface_index;
+
+ return 0;
+}
+
+int sd_dhcp6_client_set_mac(sd_dhcp6_client *client,
+ const struct ether_addr *mac_addr)
+{
+ assert_return(client, -EINVAL);
+
+ if (mac_addr)
+ memcpy(&client->mac_addr, mac_addr, sizeof(client->mac_addr));
+ else
+ memset(&client->mac_addr, 0x00, sizeof(client->mac_addr));
+
+ return 0;
+}
+
+static sd_dhcp6_client *client_notify(sd_dhcp6_client *client, int event) {
+ if (client->cb) {
+ client = sd_dhcp6_client_ref(client);
+ client->cb(client, event, client->userdata);
+ client = sd_dhcp6_client_unref(client);
+ }
+
+ return client;
+}
+
+static int client_initialize(sd_dhcp6_client *client)
+{
+ assert_return(client, -EINVAL);
+
+ client->state = DHCP6_STATE_STOPPED;
+
+ return 0;
+}
+
+static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) {
+ assert_return(client, NULL);
+
+ client = client_notify(client, error);
+ if (client)
+ client_initialize(client);
+
+ return client;
+}
+
+int sd_dhcp6_client_stop(sd_dhcp6_client *client)
+{
+ client_stop(client, DHCP6_EVENT_STOP);
+
+ return 0;
+}
+
+int sd_dhcp6_client_start(sd_dhcp6_client *client)
+{
+ int r = 0;
+
+ assert_return(client, -EINVAL);
+ assert_return(client->event, -EINVAL);
+ assert_return(client->index > 0, -EINVAL);
+
+ return r;
+}
+
+int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
+ int priority)
+{
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(!client->event, -EBUSY);
+
+ if (event)
+ client->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&client->event);
+ if (r < 0)
+ return 0;
+ }
+
+ client->event_priority = priority;
+
+ return 0;
+}
+
+int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
+ assert_return(client, -EINVAL);
+
+ client->event = sd_event_unref(client->event);
+
+ return 0;
+}
+
+sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
+ if (!client)
+ return NULL;
+
+ return client->event;
+}
+
+sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
+ if (client)
+ assert_se(REFCNT_INC(client->n_ref) >= 2);
+
+ return client;
+}
+
+sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
+ if (client && REFCNT_DEC(client->n_ref) <= 0) {
+
+ client_initialize(client);
+
+ sd_dhcp6_client_detach_event(client);
+
+ free(client);
+
+ return NULL;
+ }
+
+ return client;
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
+#define _cleanup_dhcp6_client_free_ _cleanup_(sd_dhcp6_client_unrefp)
+
+int sd_dhcp6_client_new(sd_dhcp6_client **ret)
+{
+ _cleanup_dhcp6_client_free_ sd_dhcp6_client *client = NULL;
+
+ assert_return(ret, -EINVAL);
+
+ client = new0(sd_dhcp6_client, 1);
+ if (!client)
+ return -ENOMEM;
+
+ client->n_ref = REFCNT_INIT;
+
+ client->index = -1;
+
+ *ret = client;
+ client = NULL;
+
+ return 0;
+}
diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h
new file mode 100644
index 0000000..4965011
--- /dev/null
+++ b/src/systemd/sd-dhcp6-client.h
@@ -0,0 +1,54 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosddhcp6clienthfoo
+#define foosddhcp6clienthfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <net/ethernet.h>
+
+#include "sd-event.h"
+
+enum {
+ DHCP6_EVENT_STOP = 0,
+};
+
+typedef struct sd_dhcp6_client sd_dhcp6_client;
+
+typedef void (*sd_dhcp6_client_cb_t)(sd_dhcp6_client *client, int event,
+ void *userdata);
+int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
+ sd_dhcp6_client_cb_t cb, void *userdata);
+
+int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index);
+int sd_dhcp6_client_set_mac(sd_dhcp6_client *client,
+ const struct ether_addr *mac_addr);
+
+int sd_dhcp6_client_stop(sd_dhcp6_client *client);
+int sd_dhcp6_client_start(sd_dhcp6_client *client);
+int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
+ int priority);
+int sd_dhcp6_client_detach_event(sd_dhcp6_client *client);
+sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client);
+sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client);
+sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client);
+int sd_dhcp6_client_new(sd_dhcp6_client **ret);
+
+#endif
--
1.9.1
Patrik Flykt
2014-06-13 13:45:06 UTC
Permalink
When receiving DHCPv6 messages, discard the ones that are not meant
for DHCPv6 clients and verify the transaction id. Once that is done,
process the Advertise message and select the Advertise with the
highest preference.

Create a separate function for lease information parsing so that it
can be reused in other parts of the protocol. Verify both DUID and
IAID in the received message and store other necessary information
with the lease structure.
---
src/libsystemd-network/dhcp6-internal.h | 2 +
src/libsystemd-network/dhcp6-protocol.h | 6 +
src/libsystemd-network/sd-dhcp6-client.c | 219 ++++++++++++++++++++++++++++++-
3 files changed, 225 insertions(+), 2 deletions(-)

diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
index ec1d82a..94e3a5d 100644
--- a/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -75,3 +75,5 @@ int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,

const char *dhcp6_message_type_to_string(int s) _const_;
int dhcp6_message_type_from_string(const char *s) _pure_;
+const char *dhcp6_message_status_to_string(int s) _const_;
+int dhcp6_message_status_from_string(const char *s) _pure_;
diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h
index de100d7..e8df509 100644
--- a/src/libsystemd-network/dhcp6-protocol.h
+++ b/src/libsystemd-network/dhcp6-protocol.h
@@ -21,6 +21,9 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/

+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+
#include "macro.h"
#include "sparse-endian.h"

@@ -36,6 +39,9 @@ struct DHCP6Message {

typedef struct DHCP6Message DHCP6Message;

+#define DHCP6_MIN_OPTIONS_SIZE \
+ 1280 - sizeof(struct ip6_hdr) - sizeof(struct udphdr)
+
#define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT \
{ { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 } } }
diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index bdd9177..a18698c 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -21,6 +21,7 @@

#include <errno.h>
#include <string.h>
+#include <sys/ioctl.h>

#include "udev.h"
#include "udev-util.h"
@@ -34,6 +35,7 @@
#include "dhcp6-protocol.h"
#include "dhcp6-internal.h"
#include "icmp6-nd.h"
+#include "dhcp6-lease-internal.h"

#define SYSTEMD_PEN 43793
#define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
@@ -49,6 +51,7 @@ struct sd_dhcp6_client {
icmp6_nd *ra;
DHCP6IA ia_na;
be32_t transaction_id;
+ struct sd_dhcp6_lease *lease;
int fd;
sd_event_source *receive_message;
usec_t retransmit_time;
@@ -83,6 +86,17 @@ const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = {

DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);

+const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {
+ [DHCP6_STATUS_SUCCESS] = "Success",
+ [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure",
+ [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available",
+ [DHCP6_STATUS_NO_BINDING] = "Binding unavailable",
+ [DHCP6_STATUS_NOT_ON_LINK] = "Not on link",
+ [DHCP6_STATUS_USE_MULTICAST] = "Use multicast",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int);
+
int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
sd_dhcp6_client_cb_t cb, void *userdata)
{
@@ -381,9 +395,210 @@ static int client_ensure_iaid(sd_dhcp6_client *client) {
return 0;
}

+static int client_parse_message(sd_dhcp6_client *client,
+ DHCP6Message *message, size_t len,
+ sd_dhcp6_lease *lease) {
+ int r;
+ uint8_t *optval, *option = (uint8_t *)(message + 1), *id = NULL;
+ uint16_t optcode, status;
+ size_t optlen, id_len;
+ bool clientid = false;
+ be32_t iaid_lease;
+
+ while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen,
+ &optval)) >= 0) {
+ switch (optcode) {
+ case DHCP6_OPTION_CLIENTID:
+ if (clientid) {
+ log_dhcp6_client(client, "%s contains multiple clientids",
+ dhcp6_message_type_to_string(message->type));
+ return -EINVAL;
+ }
+
+ if (optlen != sizeof(client->duid) ||
+ memcmp(&client->duid, optval, optlen) != 0) {
+ log_dhcp6_client(client, "%s DUID does not match",
+ dhcp6_message_type_to_string(message->type));
+
+ return -EINVAL;
+ }
+ clientid = true;
+
+ break;
+
+ case DHCP6_OPTION_SERVERID:
+ r = dhcp6_lease_get_serverid(lease, &id, &id_len);
+ if (r >= 0 && id) {
+ log_dhcp6_client(client, "%s contains multiple serverids",
+ dhcp6_message_type_to_string(message->type));
+ return -EINVAL;
+ }
+
+ r = dhcp6_lease_set_serverid(lease, optval, optlen);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case DHCP6_OPTION_PREFERENCE:
+ if (optlen != 1)
+ return -EINVAL;
+
+ r = dhcp6_lease_set_preference(lease, *optval);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case DHCP6_OPTION_STATUS_CODE:
+ if (optlen < 2)
+ return -EINVAL;
+
+ status = optval[0] << 8 | optval[1];
+ if (status) {
+ log_dhcp6_client(client, "%s Status %s",
+ dhcp6_message_type_to_string(message->type),
+ dhcp6_message_status_to_string(status));
+ return -EINVAL;
+ }
+
+ break;
+
+ case DHCP6_OPTION_IA_NA:
+ r = dhcp6_option_parse_ia(&optval, &optlen, optcode,
+ &lease->ia);
+ if (r < 0 && r != -ENOMSG)
+ return r;
+
+ r = dhcp6_lease_get_iaid(lease, &iaid_lease);
+ if (r < 0)
+ return r;
+
+ if (client->ia_na.id != iaid_lease) {
+ log_dhcp6_client(client, "%s has wrong IAID",
+ dhcp6_message_type_to_string(message->type));
+ return -EINVAL;
+ }
+
+ break;
+ }
+ }
+
+ if ((r < 0 && r != -ENOMSG) || !clientid) {
+ log_dhcp6_client(client, "%s has incomplete options",
+ dhcp6_message_type_to_string(message->type));
+ return -EINVAL;
+ }
+
+ r = dhcp6_lease_get_serverid(lease, &id, &id_len);
+ if (r < 0)
+ log_dhcp6_client(client, "%s has no server id",
+ dhcp6_message_type_to_string(message->type));
+
+return r;
+}
+
+static int client_receive_advertise(sd_dhcp6_client *client,
+ DHCP6Message *advertise, size_t len) {
+ int r;
+ _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
+ uint8_t pref_advertise = 0, pref_lease = 0;
+
+ if (advertise->type != DHCP6_ADVERTISE)
+ return -EINVAL;
+
+ r = dhcp6_lease_new(&lease);
+ if (r < 0)
+ return r;
+
+ r = client_parse_message(client, advertise, len, lease);
+ if (r < 0)
+ return r;
+
+ r = dhcp6_lease_get_preference(lease, &pref_advertise);
+ if (r < 0)
+ return r;
+
+ r = dhcp6_lease_get_preference(client->lease, &pref_lease);
+ if (!client->lease || r < 0 || pref_advertise > pref_lease) {
+ sd_dhcp6_lease_unref(client->lease);
+ client->lease = lease;
+ lease = NULL;
+ r = 0;
+ }
+
+ return r;
+}
+
static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
- void *userdata)
-{
+ void *userdata) {
+ sd_dhcp6_client *client = userdata;
+ _cleanup_free_ DHCP6Message *message;
+ int r, buflen, len;
+
+ assert(s);
+ assert(client);
+ assert(client->event);
+
+ r = ioctl(fd, FIONREAD, &buflen);
+ if (r < 0 || buflen <= 0)
+ buflen = DHCP6_MIN_OPTIONS_SIZE;
+
+ message = malloc0(buflen);
+ if (!message)
+ return -ENOMEM;
+
+ len = read(fd, message, buflen);
+ if ((size_t)len < sizeof(DHCP6Message)) {
+ log_dhcp6_client(client, "could not receive message from UDP "
+ "socket: %s", strerror(errno));
+ return 0;
+ }
+
+ switch(message->type) {
+ case DHCP6_SOLICIT:
+ case DHCP6_REQUEST:
+ case DHCP6_CONFIRM:
+ case DHCP6_RENEW:
+ case DHCP6_REBIND:
+ case DHCP6_RELEASE:
+ case DHCP6_DECLINE:
+ case DHCP6_INFORMATION_REQUEST:
+ case DHCP6_RELAY_FORW:
+ case DHCP6_RELAY_REPL:
+ return 0;
+
+ case DHCP6_ADVERTISE:
+ case DHCP6_REPLY:
+ case DHCP6_RECONFIGURE:
+ break;
+
+ default:
+ log_dhcp6_client(client, "unknown message type %d",
+ message->type);
+ return 0;
+ }
+
+ if (client->transaction_id != (message->transaction_id &
+ htobe32(0x00ffffff)))
+ return 0;
+
+ switch (client->state) {
+ case DHCP6_STATE_SOLICITATION:
+ r = client_receive_advertise(client, message, len);
+
+ break;
+
+ case DHCP6_STATE_STOPPED:
+ case DHCP6_STATE_RS:
+ return 0;
+ }
+
+ if (r >= 0) {
+ log_dhcp6_client(client, "Recv %s",
+ dhcp6_message_type_to_string(message->type));
+ }
+
return 0;
}
--
1.9.1
Zbigniew Jędrzejewski-Szmek
2014-06-18 14:11:59 UTC
Permalink
Post by Patrik Flykt
When receiving DHCPv6 messages, discard the ones that are not meant
for DHCPv6 clients and verify the transaction id. Once that is done,
process the Advertise message and select the Advertise with the
highest preference.
Create a separate function for lease information parsing so that it
can be reused in other parts of the protocol. Verify both DUID and
IAID in the received message and store other necessary information
with the lease structure.
---
src/libsystemd-network/dhcp6-internal.h | 2 +
src/libsystemd-network/dhcp6-protocol.h | 6 +
src/libsystemd-network/sd-dhcp6-client.c | 219 ++++++++++++++++++++++++++++++-
3 files changed, 225 insertions(+), 2 deletions(-)
diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
index ec1d82a..94e3a5d 100644
--- a/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -75,3 +75,5 @@ int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,
const char *dhcp6_message_type_to_string(int s) _const_;
int dhcp6_message_type_from_string(const char *s) _pure_;
+const char *dhcp6_message_status_to_string(int s) _const_;
+int dhcp6_message_status_from_string(const char *s) _pure_;
diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h
index de100d7..e8df509 100644
--- a/src/libsystemd-network/dhcp6-protocol.h
+++ b/src/libsystemd-network/dhcp6-protocol.h
@@ -21,6 +21,9 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+
#include "macro.h"
#include "sparse-endian.h"
@@ -36,6 +39,9 @@ struct DHCP6Message {
typedef struct DHCP6Message DHCP6Message;
+#define DHCP6_MIN_OPTIONS_SIZE \
+ 1280 - sizeof(struct ip6_hdr) - sizeof(struct udphdr)
+
#define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT \
{ { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 } } }
diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index bdd9177..a18698c 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -21,6 +21,7 @@
#include <errno.h>
#include <string.h>
+#include <sys/ioctl.h>
#include "udev.h"
#include "udev-util.h"
@@ -34,6 +35,7 @@
#include "dhcp6-protocol.h"
#include "dhcp6-internal.h"
#include "icmp6-nd.h"
+#include "dhcp6-lease-internal.h"
#define SYSTEMD_PEN 43793
#define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
@@ -49,6 +51,7 @@ struct sd_dhcp6_client {
icmp6_nd *ra;
DHCP6IA ia_na;
be32_t transaction_id;
+ struct sd_dhcp6_lease *lease;
int fd;
sd_event_source *receive_message;
usec_t retransmit_time;
@@ -83,6 +86,17 @@ const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);
+const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {
+ [DHCP6_STATUS_SUCCESS] = "Success",
+ [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure",
+ [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available",
+ [DHCP6_STATUS_NO_BINDING] = "Binding unavailable",
+ [DHCP6_STATUS_NOT_ON_LINK] = "Not on link",
+ [DHCP6_STATUS_USE_MULTICAST] = "Use multicast",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int);
+
int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
sd_dhcp6_client_cb_t cb, void *userdata)
{
@@ -381,9 +395,210 @@ static int client_ensure_iaid(sd_dhcp6_client *client) {
return 0;
}
+static int client_parse_message(sd_dhcp6_client *client,
+ DHCP6Message *message, size_t len,
+ sd_dhcp6_lease *lease) {
+ int r;
+ uint8_t *optval, *option = (uint8_t *)(message + 1), *id = NULL;
+ uint16_t optcode, status;
+ size_t optlen, id_len;
+ bool clientid = false;
+ be32_t iaid_lease;
+
+ while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen,
+ &optval)) >= 0) {
+ switch (optcode) {
+ if (clientid) {
+ log_dhcp6_client(client, "%s contains multiple clientids",
+ dhcp6_message_type_to_string(message->type));
+ return -EINVAL;
+ }
+
+ if (optlen != sizeof(client->duid) ||
+ memcmp(&client->duid, optval, optlen) != 0) {
+ log_dhcp6_client(client, "%s DUID does not match",
+ dhcp6_message_type_to_string(message->type));
+
+ return -EINVAL;
+ }
+ clientid = true;
+
+ break;
+
+ r = dhcp6_lease_get_serverid(lease, &id, &id_len);
+ if (r >= 0 && id) {
+ log_dhcp6_client(client, "%s contains multiple serverids",
+ dhcp6_message_type_to_string(message->type));
+ return -EINVAL;
+ }
+
+ r = dhcp6_lease_set_serverid(lease, optval, optlen);
+ if (r < 0)
+ return r;
+
+ break;
+
+ if (optlen != 1)
+ return -EINVAL;
+
+ r = dhcp6_lease_set_preference(lease, *optval);
+ if (r < 0)
+ return r;
+
+ break;
+
+ if (optlen < 2)
+ return -EINVAL;
+
+ status = optval[0] << 8 | optval[1];
+ if (status) {
+ log_dhcp6_client(client, "%s Status %s",
+ dhcp6_message_type_to_string(message->type),
+ dhcp6_message_status_to_string(status));
+ return -EINVAL;
+ }
+
+ break;
+
+ r = dhcp6_option_parse_ia(&optval, &optlen, optcode,
+ &lease->ia);
+ if (r < 0 && r != -ENOMSG)
+ return r;
+
+ r = dhcp6_lease_get_iaid(lease, &iaid_lease);
+ if (r < 0)
+ return r;
+
+ if (client->ia_na.id != iaid_lease) {
+ log_dhcp6_client(client, "%s has wrong IAID",
+ dhcp6_message_type_to_string(message->type));
+ return -EINVAL;
+ }
+
+ break;
+ }
+ }
+
+ if ((r < 0 && r != -ENOMSG) || !clientid) {
+ log_dhcp6_client(client, "%s has incomplete options",
+ dhcp6_message_type_to_string(message->type));
+ return -EINVAL;
+ }
+
+ r = dhcp6_lease_get_serverid(lease, &id, &id_len);
+ if (r < 0)
+ log_dhcp6_client(client, "%s has no server id",
+ dhcp6_message_type_to_string(message->type));
+
+return r;
Indentation.
Post by Patrik Flykt
+}
+
+static int client_receive_advertise(sd_dhcp6_client *client,
+ DHCP6Message *advertise, size_t len) {
+ int r;
Zbyszek
Patrik Flykt
2014-06-13 13:44:57 UTC
Permalink
Create structures describing Identity Association IDentifiers and
IPv6 lease addresses.

[tomegun: initialize the IAID when client is started. Base this off of the
predictable udev names, if available, as these satisfy the requirement of
the IAID, and base it off the mac addres otherwise, as that is the best we
have.]
---
src/libsystemd-network/dhcp6-internal.h | 32 +++++++++++++
src/libsystemd-network/sd-dhcp6-client.c | 80 ++++++++++++++++++++++++++++++++
2 files changed, 112 insertions(+)

diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
index 52283d7..1cdb912 100644
--- a/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -22,6 +22,38 @@
***/

#include <net/ethernet.h>
+#include <netinet/in.h>
+
+#include "sparse-endian.h"
+#include "sd-event.h"
+#include "list.h"
+
+typedef struct DHCP6Address DHCP6Address;
+
+struct DHCP6Address {
+ LIST_FIELDS(DHCP6Address, addresses);
+
+ struct {
+ struct in6_addr address;
+ be32_t lifetime_preferred;
+ be32_t lifetime_valid;
+ } _packed_;
+};
+
+struct DHCP6IA {
+ uint16_t type;
+ struct {
+ be32_t id;
+ be32_t lifetime_t1;
+ be32_t lifetime_t2;
+ } _packed_;
+ sd_event_source *timeout_t1;
+ sd_event_source *timeout_t2;
+
+ LIST_HEAD(DHCP6Address, addresses);
+};
+
+typedef struct DHCP6IA DHCP6IA;

#define log_dhcp6_client(p, fmt, ...) log_meta(LOG_DEBUG, __FILE__, __LINE__, __func__, "DHCPv6 CLIENT: " fmt, ##__VA_ARGS__)

diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index a785fa6..f61442b 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -22,10 +22,14 @@
#include <errno.h>
#include <string.h>

+#include "udev.h"
+#include "udev-util.h"
+#include "virt.h"
#include "siphash24.h"
#include "util.h"
#include "refcnt.h"

+#include "network-internal.h"
#include "sd-dhcp6-client.h"
#include "dhcp6-protocol.h"
#include "dhcp6-internal.h"
@@ -43,6 +47,7 @@ struct sd_dhcp6_client {
int index;
struct ether_addr mac_addr;
icmp6_nd *ra;
+ DHCP6IA ia_na;
sd_dhcp6_client_cb_t cb;
void *userdata;

@@ -101,6 +106,11 @@ static int client_initialize(sd_dhcp6_client *client)
{
assert_return(client, -EINVAL);

+ client->ia_na.timeout_t1 =
+ sd_event_source_unref(client->ia_na.timeout_t1);
+ client->ia_na.timeout_t2 =
+ sd_event_source_unref(client->ia_na.timeout_t2);
+
client->state = DHCP6_STATE_STOPPED;

return 0;
@@ -116,10 +126,70 @@ static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) {
return client;
}

+static int client_ensure_iaid(sd_dhcp6_client *client) {
+ const char *name = NULL;
+ uint64_t id;
+
+ assert(client);
+
+ if (client->ia_na.id)
+ return 0;
+
+ if (detect_container(NULL) <= 0) {
+ /* not in a container, udev will be around */
+ _cleanup_udev_unref_ struct udev *udev;
+ _cleanup_udev_device_unref_ struct udev_device *device;
+ char ifindex_str[2 + DECIMAL_STR_MAX(int)];
+
+ udev = udev_new();
+ if (!udev)
+ return -ENOMEM;
+
+ sprintf(ifindex_str, "n%d", client->index);
+ device = udev_device_new_from_device_id(udev, ifindex_str);
+ if (!device)
+ return -errno;
+
+ if (udev_device_get_is_initialized(device) <= 0)
+ /* not yet ready */
+ return -EBUSY;
+
+ name = net_get_name(device);
+ }
+
+ if (name)
+ siphash24((uint8_t*)&id, name, strlen(name), HASH_KEY.bytes);
+ else
+ /* fall back to mac address if no predictable name available */
+ siphash24((uint8_t*)&id, &client->mac_addr, ETH_ALEN,
+ HASH_KEY.bytes);
+
+ /* fold into 32 bits */
+ client->ia_na.id = (id & 0xffffffff) ^ (id >> 32);
+
+ return 0;
+}
+
+static int client_start(sd_dhcp6_client *client)
+{
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(client->event, -EINVAL);
+ assert_return(client->index > 0, -EINVAL);
+
+ r = client_ensure_iaid(client);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
static void dhcp6_receive_router_advertisment(icmp6_nd *nd, int event,
void *userdata)
{
sd_dhcp6_client *client = userdata;
+ int r;

if (event < 0) {
log_dhcp6_client(client, "Router Advertisment failed with "
@@ -136,6 +206,9 @@ static void dhcp6_receive_router_advertisment(icmp6_nd *nd, int event,

case ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT:
case ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED:
+ r = client_start(client);
+ if (r < 0)
+ client_stop(client, r);
break;
}
}
@@ -158,6 +231,10 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client)
assert_return(client->event, -EINVAL);
assert_return(client->index > 0, -EINVAL);

+ r = client_initialize(client);
+ if (r < 0)
+ return r;
+
r = icmp6_nd_new(&nd);
if (r < 0)
return r;
@@ -240,6 +317,7 @@ sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {

sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
if (client && REFCNT_DEC(client->n_ref) <= 0) {
+ log_dhcp6_client(client, "UNREF");

client_initialize(client);

@@ -272,6 +350,8 @@ int sd_dhcp6_client_new(sd_dhcp6_client **ret)

client->n_ref = REFCNT_INIT;

+ client->ia_na.type = DHCP6_OPTION_IA_NA;
+
client->index = -1;

/* initialize DUID */
--
1.9.1
Patrik Flykt
2014-06-13 13:45:04 UTC
Permalink
Create a structure describing a DHCPv6 lease. Add internal functions
for creating a new lease and accessing the server ID, preference and
IAID. Provide functions for clearing addresses and associated timers.

External users are initially given only the capabilities of
referencing and unreferencing the lease structure.
---
Makefile.am | 5 +-
src/libsystemd-network/dhcp6-lease-internal.h | 55 ++++++++++
src/libsystemd-network/sd-dhcp6-lease.c | 139 ++++++++++++++++++++++++++
src/systemd/sd-dhcp6-lease.h | 31 ++++++
4 files changed, 229 insertions(+), 1 deletion(-)
create mode 100644 src/libsystemd-network/dhcp6-lease-internal.h
create mode 100644 src/libsystemd-network/sd-dhcp6-lease.c
create mode 100644 src/systemd/sd-dhcp6-lease.h

diff --git a/Makefile.am b/Makefile.am
index 87ac728..3c2d73a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2520,12 +2520,15 @@ libsystemd_network_la_SOURCES = \
src/libsystemd-network/network-internal.c \
src/libsystemd-network/network-internal.h \
src/systemd/sd-dhcp6-client.h \
+ src/systemd/sd-dhcp6-lease.h \
src/libsystemd-network/sd-dhcp6-client.c \
src/libsystemd-network/icmp6-nd.h \
src/libsystemd-network/icmp6-nd.c \
src/libsystemd-network/dhcp6-internal.h \
src/libsystemd-network/dhcp6-network.c \
- src/libsystemd-network/dhcp6-option.c
+ src/libsystemd-network/dhcp6-option.c \
+ src/libsystemd-network/dhcp6-lease-internal.h \
+ src/libsystemd-network/sd-dhcp6-lease.c

libsystemd_network_la_LIBADD = \
libudev-internal.la \
diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h
new file mode 100644
index 0000000..f4f1488
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-lease-internal.h
@@ -0,0 +1,55 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdint.h>
+
+#include "refcnt.h"
+
+#include "sd-dhcp6-lease.h"
+#include "dhcp6-internal.h"
+
+struct sd_dhcp6_lease {
+ RefCount n_ref;
+
+ uint8_t *serverid;
+ size_t serverid_len;
+ uint8_t preference;
+
+ DHCP6IA ia;
+};
+
+int dhcp6_lease_clear_timers(DHCP6IA *ia);
+DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia);
+
+int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id,
+ size_t len);
+int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **id, size_t *len);
+int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference);
+int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference);
+int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid);
+
+int dhcp6_lease_new(sd_dhcp6_lease **ret);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_lease*, sd_dhcp6_lease_unref);
+#define _cleanup_dhcp6_lease_free_ _cleanup_(sd_dhcp6_lease_unrefp)
diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c
new file mode 100644
index 0000000..41d6a5a
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp6-lease.c
@@ -0,0 +1,139 @@
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "util.h"
+
+#include "dhcp6-lease-internal.h"
+
+int dhcp6_lease_clear_timers(DHCP6IA *ia) {
+ assert_return(ia, -EINVAL);
+
+ ia->timeout_t1 = sd_event_source_unref(ia->timeout_t1);
+ ia->timeout_t2 = sd_event_source_unref(ia->timeout_t2);
+
+ return 0;
+}
+
+DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia) {
+ DHCP6Address *address;
+
+ if (!ia)
+ return NULL;
+
+ dhcp6_lease_clear_timers(ia);
+
+ while (ia->addresses) {
+ address = ia->addresses;
+
+ LIST_REMOVE(addresses, ia->addresses, address);
+
+ free(address);
+ }
+
+ return NULL;
+}
+
+int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id,
+ size_t len) {
+ assert_return(lease, -EINVAL);
+ assert_return(id, -EINVAL);
+
+ free(lease->serverid);
+
+ lease->serverid = memdup(id, len);
+ if (!lease->serverid)
+ return -EINVAL;
+
+ lease->serverid_len = len;
+
+ return 0;
+}
+
+int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **id, size_t *len) {
+ assert_return(lease, -EINVAL);
+ assert_return(id, -EINVAL);
+ assert_return(len, -EINVAL);
+
+ *id = lease->serverid;
+ *len = lease->serverid_len;
+
+ return 0;
+}
+
+int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference) {
+ assert_return(lease, -EINVAL);
+
+ lease->preference = preference;
+
+ return 0;
+}
+
+int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference) {
+ assert_return(lease, -EINVAL);
+ assert_return(preference, -EINVAL);
+
+ *preference = lease->preference;
+
+ return 0;
+}
+
+int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid) {
+ assert_return(lease, -EINVAL);
+ assert_return(iaid, -EINVAL);
+
+ *iaid = lease->ia.id;
+
+ return 0;
+}
+
+sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease) {
+ if (lease)
+ assert_se(REFCNT_INC(lease->n_ref) >= 2);
+
+ return lease;
+}
+
+sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease) {
+ if (lease && REFCNT_DEC(lease->n_ref) <= 0) {
+ free(lease->serverid);
+ dhcp6_lease_free_ia(&lease->ia);
+
+ free(lease);
+ }
+
+ return NULL;
+}
+
+int dhcp6_lease_new(sd_dhcp6_lease **ret) {
+ sd_dhcp6_lease *lease;
+
+ lease = new0(sd_dhcp6_lease, 1);
+ if (!lease)
+ return -ENOMEM;
+
+ lease->n_ref = REFCNT_INIT;
+
+ LIST_HEAD_INIT(lease->ia.addresses);
+
+ *ret = lease;
+ return 0;
+}
diff --git a/src/systemd/sd-dhcp6-lease.h b/src/systemd/sd-dhcp6-lease.h
new file mode 100644
index 0000000..0b2765c
--- /dev/null
+++ b/src/systemd/sd-dhcp6-lease.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosddhcp6leasehfoo
+#define foosddhcp6leasehfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct sd_dhcp6_lease sd_dhcp6_lease;
+
+sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease);
+sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease);
+
+#endif
--
1.9.1
Patrik Flykt
2014-06-13 13:45:14 UTC
Permalink
Enable DHCPv6 support by creating a DHCPv6 boolean in the Network
section. Add necessary dhcp6 structures and initial function calls.
---
src/network/networkd-link.c | 75 ++++++++++++++++++++++++++++++++
src/network/networkd-network-gperf.gperf | 1 +
src/network/networkd.h | 3 ++
3 files changed, 79 insertions(+)

diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 3653426..507e82d 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -120,6 +120,8 @@ static void link_free(Link *link) {

sd_ipv4ll_unref(link->ipv4ll);

+ sd_dhcp6_client_unref(link->dhcp6_client);
+
hashmap_remove(link->manager->links, &link->ifindex);

free(link->ifname);
@@ -231,6 +233,17 @@ static int link_stop_clients(Link *link) {
}
}

+ if (link->network->dhcp6) {
+ assert(link->dhcp6_client);
+
+ k = sd_dhcp6_client_stop(link->dhcp6_client);
+ if (k < 0) {
+ log_warning_link(link, "Could not stop DHCPv6 "
+ "client: %s", strerror(-r));
+ r = k;
+ }
+ }
+
return r;
}

@@ -1183,6 +1196,19 @@ static void ipv4ll_handler(sd_ipv4ll *ll, int event, void *userdata){
}
}

+static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) {
+ Link *link = userdata;
+
+ assert(link);
+ assert(link->network);
+ assert(link->manager);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return;
+
+ log_debug_link(link, "DHCPv6 event %d", event);
+}
+
static int link_acquire_conf(Link *link) {
int r;

@@ -1217,6 +1243,19 @@ static int link_acquire_conf(Link *link) {
}
}

+ if (link->network->dhcp6) {
+ assert(link->dhcp6_client);
+
+ log_debug_link(link, "acquiring DHCPv6 lease");
+
+ r = sd_dhcp6_client_start(link->dhcp6_client);
+ if (r < 0) {
+ log_warning_link(link, "could not acquire DHCPv6 "
+ "lease");
+ return r;
+ }
+ }
+
return 0;
}

@@ -1667,6 +1706,30 @@ static int link_configure(Link *link) {
}
}

+ if (link->network->dhcp6) {
+ r = sd_dhcp6_client_new(&link->dhcp6_client);
+ if (r < 0)
+ return r;
+
+ r = sd_dhcp6_client_attach_event(link->dhcp6_client, NULL, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_dhcp6_client_set_mac(link->dhcp6_client, &link->mac);
+ if (r < 0)
+ return r;
+
+ r = sd_dhcp6_client_set_index(link->dhcp6_client,
+ link->ifindex);
+ if (r < 0)
+ return r;
+
+ r = sd_dhcp6_client_set_callback(link->dhcp6_client,
+ dhcp6_handler, link);
+ if (r < 0)
+ return r;
+ }
+
if (link_has_carrier(link->flags, link->kernel_operstate)) {
r = link_acquire_conf(link);
if (r < 0)
@@ -1974,6 +2037,18 @@ int link_update(Link *link, sd_rtnl_message *m) {
return r;
}
}
+
+ if (link->dhcp6_client) {
+ r = sd_dhcp6_client_set_mac(link->dhcp6_client,
+ &link->mac);
+ if (r < 0) {
+ log_warning_link(link, "Could not "
+ "update MAC address "
+ "in DHCPv6 client: %s",
+ strerror(-r));
+ return r;
+ }
+ }
}
}

diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 5038cb5..9e3993d 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -31,6 +31,7 @@ Network.VLAN, config_parse_netdev, 0,
Network.MACVLAN, config_parse_netdev, 0, offsetof(Network, macvlans)
Network.DHCP, config_parse_bool, 0, offsetof(Network, dhcp)
Network.IPv4LL, config_parse_bool, 0, offsetof(Network, ipv4ll)
+Network.DHCPv6, config_parse_bool, 0, offsetof(Network, dhcp6)
Network.Address, config_parse_address, 0, 0
Network.Gateway, config_parse_gateway, 0, 0
Network.DNS, config_parse_dns, 0, offsetof(Network, dns)
diff --git a/src/network/networkd.h b/src/network/networkd.h
index 6f77c77..c943512 100644
--- a/src/network/networkd.h
+++ b/src/network/networkd.h
@@ -28,6 +28,7 @@
#include "sd-bus.h"
#include "sd-dhcp-client.h"
#include "sd-ipv4ll.h"
+#include "sd-dhcp6-client.h"
#include "udev.h"

#include "rtnl-util.h"
@@ -148,6 +149,7 @@ struct Network {
bool dhcp_domainname;
bool dhcp_critical;
bool ipv4ll;
+ bool dhcp6;

LIST_HEAD(Address, static_addresses);
LIST_HEAD(Route, static_routes);
@@ -256,6 +258,7 @@ struct Link {
char *lease_file;
uint16_t original_mtu;
sd_ipv4ll *ipv4ll;
+ sd_dhcp6_client *dhcp6_client;
};

struct Manager {
--
1.9.1
Zbigniew Jędrzejewski-Szmek
2014-06-18 14:23:02 UTC
Permalink
Post by Patrik Flykt
Enable DHCPv6 support by creating a DHCPv6 boolean in the Network
section. Add necessary dhcp6 structures and initial function calls.
---
src/network/networkd-link.c | 75 ++++++++++++++++++++++++++++++++
src/network/networkd-network-gperf.gperf | 1 +
src/network/networkd.h | 3 ++
3 files changed, 79 insertions(+)
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 3653426..507e82d 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -120,6 +120,8 @@ static void link_free(Link *link) {
sd_ipv4ll_unref(link->ipv4ll);
+ sd_dhcp6_client_unref(link->dhcp6_client);
+
hashmap_remove(link->manager->links, &link->ifindex);
free(link->ifname);
@@ -231,6 +233,17 @@ static int link_stop_clients(Link *link) {
}
}
+ if (link->network->dhcp6) {
+ assert(link->dhcp6_client);
+
+ k = sd_dhcp6_client_stop(link->dhcp6_client);
+ if (k < 0) {
+ log_warning_link(link, "Could not stop DHCPv6 "
+ "client: %s", strerror(-r));
+ log_warning_link(link, "could not acquire DHCPv6 "
+ "lease");
+ log_warning_link(link, "Could not "
+ "update MAC address "
+ "in DHCPv6 client: %s",
+ strerror(-r));
Those multi-line strings are both hard to read, and hard to grep for,
when looking for the source of an error messsage. Normally the line
length restrictions are better ignored for "single line" message
strings.

Zbyszek
Patrik Flykt
2014-06-13 13:44:56 UTC
Permalink
From: Tom Gundersen <***@jklm.no>

---
src/libsystemd-network/network-internal.c | 26 +++++++++++++++++++-------
src/libsystemd-network/network-internal.h | 1 +
2 files changed, 20 insertions(+), 7 deletions(-)

diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c
index e9146d0..68d7f1b 100644
--- a/src/libsystemd-network/network-internal.c
+++ b/src/libsystemd-network/network-internal.c
@@ -34,21 +34,33 @@
#include "conf-parser.h"
#include "condition.h"

-#define HASH_KEY SD_ID128_MAKE(d3,1e,48,fa,90,fe,4b,4c,9d,af,d5,d7,a1,b1,2e,8a)
-
-int net_get_unique_predictable_data(struct udev_device *device, uint8_t result[8]) {
- size_t l, sz = 0;
+const char *net_get_name(struct udev_device *device) {
const char *name = NULL, *field = NULL;
- int r;
- uint8_t *v;
+
+ assert(device);

/* fetch some persistent data unique (on this machine) to this device */
- FOREACH_STRING(field, "ID_NET_NAME_ONBOARD", "ID_NET_NAME_SLOT", "ID_NET_NAME_PATH", "ID_NET_NAME_MAC") {
+ FOREACH_STRING(field, "ID_NET_NAME_ONBOARD", "ID_NET_NAME_SLOT",
+ "ID_NET_NAME_PATH", "ID_NET_NAME_MAC") {
name = udev_device_get_property_value(device, field);
if (name)
break;
}

+ return name;
+}
+
+#define HASH_KEY SD_ID128_MAKE(d3,1e,48,fa,90,fe,4b,4c,9d,af,d5,d7,a1,b1,2e,8a)
+
+int net_get_unique_predictable_data(struct udev_device *device, uint8_t result[8]) {
+ size_t l, sz = 0;
+ const char *name = NULL;
+ int r;
+ uint8_t *v;
+
+ assert(device);
+
+ name = net_get_name(device);
if (!name)
return -ENOENT;

diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h
index 2aeecf0..db48c2c 100644
--- a/src/libsystemd-network/network-internal.h
+++ b/src/libsystemd-network/network-internal.h
@@ -65,6 +65,7 @@ int config_parse_ifalias(const char *unit, const char *filename, unsigned line,
int net_parse_inaddr(const char *address, unsigned char *family, void *dst);

int net_get_unique_predictable_data(struct udev_device *device, uint8_t result[8]);
+const char *net_get_name(struct udev_device *device);

void serialize_in_addrs(FILE *f, const char *key, struct in_addr *addresses, size_t size);
int deserialize_in_addrs(struct in_addr **addresses, size_t *size, const char *string);
--
1.9.1
Patrik Flykt
2014-06-13 13:45:00 UTC
Permalink
Add option appending and parsing. DHCPv6 options are not aligned, thus
the option handling code must be able to handle options starting at
any byte boundary.

Add a test case for the basic option handling.
---
Makefile.am | 4 +-
src/libsystemd-network/dhcp6-internal.h | 6 ++
src/libsystemd-network/dhcp6-option.c | 150 +++++++++++++++++++++++++++++
src/libsystemd-network/test-dhcp6-client.c | 70 ++++++++++++++
4 files changed, 229 insertions(+), 1 deletion(-)
create mode 100644 src/libsystemd-network/dhcp6-option.c

diff --git a/Makefile.am b/Makefile.am
index d50c23b..f6340dc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2524,7 +2524,8 @@ libsystemd_network_la_SOURCES = \
src/libsystemd-network/icmp6-nd.h \
src/libsystemd-network/icmp6-nd.c \
src/libsystemd-network/dhcp6-internal.h \
- src/libsystemd-network/dhcp6-network.c
+ src/libsystemd-network/dhcp6-network.c \
+ src/libsystemd-network/dhcp6-option.c

libsystemd_network_la_LIBADD = \
libudev-internal.la \
@@ -2579,6 +2580,7 @@ test_dhcp6_rs_LDADD = \

test_dhcp6_client_SOURCES = \
src/systemd/sd-dhcp6-client.h \
+ src/libsystemd-network/dhcp6-internal.h \
src/libsystemd-network/test-dhcp6-client.c

test_dhcp6_client_LDADD = \
diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
index 1cdb912..30b624d 100644
--- a/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -59,3 +59,9 @@ typedef struct DHCP6IA DHCP6IA;

int dhcp_network_icmp6_bind_router_solicitation(int index);
int dhcp_network_icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr);
+
+int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
+ size_t optlen, const void *optval);
+int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia);
+int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
+ size_t *optlen, uint8_t **optvalue);
diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c
new file mode 100644
index 0000000..cc4d261
--- /dev/null
+++ b/src/libsystemd-network/dhcp6-option.c
@@ -0,0 +1,150 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <netinet/in.h>
+#include <errno.h>
+#include <string.h>
+
+#include "sparse-endian.h"
+#include "util.h"
+
+#include "dhcp6-internal.h"
+#include "dhcp6-protocol.h"
+
+#define DHCP6_OPTION_HDR_LEN 4
+#define DHCP6_OPTION_IA_NA_LEN 12
+#define DHCP6_OPTION_IA_TA_LEN 4
+#define DHCP6_OPTION_IAADDR_LEN 24
+
+static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
+ size_t optlen) {
+ assert_return(buf, -EINVAL);
+ assert_return(*buf, -EINVAL);
+ assert_return(buflen, -EINVAL);
+
+ if (optlen > 0xffff || *buflen < optlen + DHCP6_OPTION_HDR_LEN)
+ return -ENOBUFS;
+
+ (*buf)[0] = optcode >> 8;
+ (*buf)[1] = optcode & 0xff;
+ (*buf)[2] = optlen >> 8;
+ (*buf)[3] = optlen & 0xff;
+
+ *buf += DHCP6_OPTION_HDR_LEN;
+ *buflen -= DHCP6_OPTION_HDR_LEN;
+
+ return 0;
+}
+
+int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
+ size_t optlen, const void *optval) {
+ int r;
+
+ assert_return(optval, -EINVAL);
+
+ r = option_append_hdr(buf, buflen, code, optlen);
+ if (r < 0)
+ return r;
+
+ memcpy(*buf, optval, optlen);
+
+ *buf += optlen;
+ *buflen -= optlen;
+
+ return 0;
+}
+
+int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
+ uint16_t len;
+ uint8_t *ia_hdr;
+ size_t ia_buflen, ia_addrlen = 0;
+ DHCP6Address *addr;
+ int r;
+
+ assert_return(buf && *buf && buflen && ia, -EINVAL);
+
+ switch (ia->type) {
+ case DHCP6_OPTION_IA_NA:
+ len = DHCP6_OPTION_IA_NA_LEN;
+ break;
+
+ case DHCP6_OPTION_IA_TA:
+ len = DHCP6_OPTION_IA_TA_LEN;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (*buflen < len)
+ return -ENOBUFS;
+
+ ia_hdr = *buf;
+ ia_buflen = *buflen;
+
+ *buf += DHCP6_OPTION_HDR_LEN;
+ *buflen -= DHCP6_OPTION_HDR_LEN;
+
+ memcpy(*buf, &ia->id, len);
+
+ *buf += len;
+ *buflen -= len;
+
+ LIST_FOREACH(addresses, addr, ia->addresses) {
+ r = option_append_hdr(buf, buflen, DHCP6_OPTION_IAADDR,
+ DHCP6_OPTION_IAADDR_LEN);
+ if (r < 0)
+ return r;
+
+ memcpy(*buf, &addr->address, DHCP6_OPTION_IAADDR_LEN);
+
+ *buf += DHCP6_OPTION_IAADDR_LEN;
+ *buflen -= DHCP6_OPTION_IAADDR_LEN;
+
+ ia_addrlen += DHCP6_OPTION_HDR_LEN + DHCP6_OPTION_IAADDR_LEN;
+ }
+
+ r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
+ size_t *optlen, uint8_t **optvalue) {
+ assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
+
+ if (*buflen == 0)
+ return -ENOMSG;
+
+ *optcode = (*buf)[0] << 8 | (*buf)[1];
+ *optlen = (*buf)[2] << 8 | (*buf)[3];
+
+ if (*optlen > *buflen - 4)
+ return -ENOBUFS;
+
+ *optvalue = &(*buf)[4];
+ *buflen -= (*optlen + 4);
+ (*buf) += (*optlen + 4);
+
+ return 0;
+}
diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c
index b2dc8ba..b52f407 100644
--- a/src/libsystemd-network/test-dhcp6-client.c
+++ b/src/libsystemd-network/test-dhcp6-client.c
@@ -28,6 +28,7 @@

#include "sd-dhcp6-client.h"
#include "dhcp6-protocol.h"
+#include "dhcp6-internal.h"

static struct ether_addr mac_addr = {
.ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
@@ -61,6 +62,72 @@ static int test_client_basic(sd_event *e) {
return 0;
}

+static int test_option(sd_event *e) {
+ uint8_t packet[] = {
+ 'F', 'O', 'O',
+ 0x00, DHCP6_OPTION_ORO, 0x00, 0x07,
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G',
+ 0x00, DHCP6_OPTION_VENDOR_CLASS, 0x00, 0x09,
+ '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'B', 'A', 'R',
+ };
+ uint8_t result[] = {
+ 'F', 'O', 'O',
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 'B', 'A', 'R',
+ };
+ uint16_t optcode;
+ size_t optlen;
+ uint8_t *optval, *buf, *out;
+ size_t zero = 0, pos = 3;
+ size_t buflen = sizeof(packet), outlen = sizeof(result);
+
+ if (verbose)
+ printf("* %s\n", __FUNCTION__);
+
+ assert_se(buflen == outlen);
+
+ assert_se(dhcp6_option_parse(&buf, &zero, &optcode, &optlen,
+ &optval) == -ENOMSG);
+
+ buflen -= 3;
+ buf = &packet[3];
+ outlen -= 3;
+ out = &result[3];
+
+ assert_se(dhcp6_option_parse(&buf, &buflen, &optcode, &optlen,
+ &optval) >= 0);
+ pos += 4 + optlen;
+ assert_se(buf == &packet[pos]);
+ assert_se(optcode == DHCP6_OPTION_ORO);
+ assert_se(optlen == 7);
+ assert_se(buflen + pos == sizeof(packet));
+
+ assert_se(dhcp6_option_append(&out, &outlen, optcode, optlen,
+ optval) >= 0);
+ assert_se(out == &result[pos]);
+ assert_se(*out == 0x00);
+
+ assert_se(dhcp6_option_parse(&buf, &buflen, &optcode, &optlen,
+ &optval) >= 0);
+ pos += 4 + optlen;
+ assert_se(buf == &packet[pos]);
+ assert_se(optcode == DHCP6_OPTION_VENDOR_CLASS);
+ assert_se(optlen == 9);
+ assert_se(buflen + pos == sizeof(packet));
+
+ assert_se(dhcp6_option_append(&out, &outlen, optcode, optlen,
+ optval) >= 0);
+ assert_se(out == &result[pos]);
+ assert_se(*out == 'B');
+
+ assert_se(memcmp(packet, result, sizeof(packet)) == 0);
+
+ return 0;
+}
+
int main(int argc, char *argv[]) {
_cleanup_event_unref_ sd_event *e;

@@ -71,6 +138,9 @@ int main(int argc, char *argv[]) {
log_open();

test_client_basic(e);
+ test_option(e);
+
+ assert_se(!sd_event_unref(e));

return 0;
}
--
1.9.1
Zbigniew Jędrzejewski-Szmek
2014-06-18 13:53:20 UTC
Permalink
Post by Patrik Flykt
+ *optvalue = &(*buf)[4];
+ *buflen -= (*optlen + 4);
+ (*buf) += (*optlen + 4);
The parens on the right and left seem strange.

Zbyszek
Patrik Flykt
2014-06-13 13:45:01 UTC
Permalink
Add a function that creates a UDP socket bound to the given interface
and optionally to an IPv6 address. Add another function that will
send the DHCPv6 UDP packet to its destination.

Using IPV6_PKTINFO in setsockopt to bind the IPv6 socket to an
interface is documented in section 4. of RFC 3542, "Advanced Sockets
Application Program Interface (API) for IPv6"

Add a define for DHCPv6 Relay Agents and Servers multicast address as
its not available elsewhere.
---
src/libsystemd-network/dhcp6-internal.h | 4 +++
src/libsystemd-network/dhcp6-network.c | 63 +++++++++++++++++++++++++++++++++
src/libsystemd-network/dhcp6-protocol.h | 4 +++
3 files changed, 71 insertions(+)

diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
index 30b624d..7a491fb 100644
--- a/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -65,3 +65,7 @@ int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia);
int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
size_t *optlen, uint8_t **optvalue);
+
+int dhcp6_network_bind_udp_socket(int index, struct in6_addr *address);
+int dhcp6_network_send_udp_socket(int s, struct in6_addr *address,
+ const void *packet, size_t len);
diff --git a/src/libsystemd-network/dhcp6-network.c b/src/libsystemd-network/dhcp6-network.c
index 53ce23d..fe56c10 100644
--- a/src/libsystemd-network/dhcp6-network.c
+++ b/src/libsystemd-network/dhcp6-network.c
@@ -31,6 +31,7 @@
#include "socket-util.h"

#include "dhcp6-internal.h"
+#include "dhcp6-protocol.h"

#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \
{ { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
@@ -129,3 +130,65 @@ int dhcp_network_icmp6_send_router_solicitation(int s, const struct ether_addr *

return 0;
}
+
+int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) {
+ struct in6_pktinfo pktinfo = {
+ .ipi6_ifindex = index,
+ };
+ union sockaddr_union src = {
+ .in6.sin6_family = AF_INET6,
+ .in6.sin6_port = htobe16(DHCP6_PORT_CLIENT),
+ .in6.sin6_addr = IN6ADDR_ANY_INIT,
+ };
+ _cleanup_close_ int s = -1;
+ int r, off = 0, on = 1;
+
+ if (local_address)
+ memcpy(&src.in6.sin6_addr, local_address,
+ sizeof(src.in6.sin6_addr));
+
+ s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ IPPROTO_UDP);
+ if (s < 0)
+ return -errno;
+
+ r = setsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, &pktinfo,
+ sizeof(pktinfo));
+ if (r < 0)
+ return -errno;
+
+ r = setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
+ if (r < 0)
+ return -errno;
+
+ r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &off, sizeof(off));
+ if (r < 0)
+ return -errno;
+
+ r = bind(s, &src.sa, sizeof(src.in6));
+ if (r < 0)
+ return -errno;
+
+ r = s;
+ s = -1;
+ return r;
+}
+
+int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address,
+ const void *packet, size_t len) {
+ union sockaddr_union dest = {
+ .in6.sin6_family = AF_INET6,
+ .in6.sin6_port = htobe16(DHCP6_PORT_SERVER),
+ };
+ int r;
+
+ assert(server_address);
+
+ memcpy(&dest.in6.sin6_addr, server_address, sizeof(dest.in6.sin6_addr));
+
+ r = sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in6));
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h
index 442418d..de100d7 100644
--- a/src/libsystemd-network/dhcp6-protocol.h
+++ b/src/libsystemd-network/dhcp6-protocol.h
@@ -36,6 +36,10 @@ struct DHCP6Message {

typedef struct DHCP6Message DHCP6Message;

+#define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT \
+ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 } } }
+
enum {
DHCP6_PORT_SERVER = 547,
DHCP6_PORT_CLIENT = 546,
--
1.9.1
Patrik Flykt
2014-06-13 13:45:09 UTC
Permalink
Updated the start function so that the client state can be conveniently
changed with the previous message resend timers cleared. On initial
startup also create and bind to the UDP socket.
---
src/libsystemd-network/sd-dhcp6-client.c | 62 +++++++++++++++++++++-----------
1 file changed, 42 insertions(+), 20 deletions(-)

diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index 52f4e10..98835f7 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -97,6 +97,8 @@ const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {

DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int);

+static int client_start(sd_dhcp6_client *client, enum DHCP6State state);
+
int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
sd_dhcp6_client_cb_t cb, void *userdata)
{
@@ -164,7 +166,7 @@ static int client_initialize(sd_dhcp6_client *client)
safe_close(client->fd);
client->fd = -1;

- client->transaction_id = random_u32() & 0x00ffffff;
+ client->transaction_id = 0;

client->ia_na.timeout_t1 =
sd_event_source_unref(client->ia_na.timeout_t1);
@@ -614,36 +616,53 @@ static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
return 0;
}

-static int client_start(sd_dhcp6_client *client)
+static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
{
int r;

assert_return(client, -EINVAL);
assert_return(client->event, -EINVAL);
assert_return(client->index > 0, -EINVAL);
+ assert_return(client->state != state, -EINVAL);

- r = client_ensure_iaid(client);
- if (r < 0)
- return r;
+ client->timeout_resend_expire =
+ sd_event_source_unref(client->timeout_resend_expire);
+ client->timeout_resend = sd_event_source_unref(client->timeout_resend);
+ client->retransmit_time = 0;
+ client->retransmit_count = 0;

- r = dhcp6_network_bind_udp_socket(client->index, NULL);
- if (r < 0)
- return r;
+ switch (state) {
+ case DHCP6_STATE_STOPPED:
+ case DHCP6_STATE_RS:
+ case DHCP6_STATE_SOLICITATION:

- client->fd = r;
+ r = client_ensure_iaid(client);
+ if (r < 0)
+ return r;

- r = sd_event_add_io(client->event, &client->receive_message,
- client->fd, EPOLLIN, client_receive_message,
- client);
- if (r < 0)
- return r;
+ r = dhcp6_network_bind_udp_socket(client->index, NULL);
+ if (r < 0)
+ return r;

- r = sd_event_source_set_priority(client->receive_message,
- client->event_priority);
- if (r < 0)
- return r;
+ client->fd = r;

- client->state = DHCP6_STATE_SOLICITATION;
+ r = sd_event_add_io(client->event, &client->receive_message,
+ client->fd, EPOLLIN, client_receive_message,
+ client);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(client->receive_message,
+ client->event_priority);
+ if (r < 0)
+ return r;
+
+ client->state = DHCP6_STATE_SOLICITATION;
+
+ break;
+ }
+
+ client->transaction_id = random_u32() & htobe32(0x00ffffff);

r = sd_event_add_time(client->event, &client->timeout_resend,
CLOCK_MONOTONIC, 0, 0, client_timeout_resend,
@@ -680,7 +699,10 @@ static void dhcp6_receive_router_advertisment(icmp6_nd *nd, int event,

case ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT:
case ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED:
- r = client_start(client);
+ if (!IN_SET(client->state, DHCP6_STATE_STOPPED, DHCP6_STATE_RS))
+ break;
+
+ r = client_start(client, DHCP6_STATE_SOLICITATION);
if (r < 0)
client_stop(client, r);
break;
--
1.9.1
Patrik Flykt
2014-06-13 13:45:07 UTC
Permalink
Add support functions for accessing the current client lease as well
as iterating over the addresses and get their preferred and valid
lifetimes.
---
src/libsystemd-network/dhcp6-lease-internal.h | 2 ++
src/libsystemd-network/sd-dhcp6-client.c | 12 +++++++++
src/libsystemd-network/sd-dhcp6-lease.c | 39 +++++++++++++++++++++++++++
src/systemd/sd-dhcp6-client.h | 4 +++
src/systemd/sd-dhcp6-lease.h | 11 ++++++++
5 files changed, 68 insertions(+)

diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h
index f4f1488..295c223 100644
--- a/src/libsystemd-network/dhcp6-lease-internal.h
+++ b/src/libsystemd-network/dhcp6-lease-internal.h
@@ -37,6 +37,8 @@ struct sd_dhcp6_lease {
uint8_t preference;

DHCP6IA ia;
+
+ DHCP6Address *addr_iter;
};

int dhcp6_lease_clear_timers(DHCP6IA *ia);
diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index a18698c..52f4e10 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -131,6 +131,18 @@ int sd_dhcp6_client_set_mac(sd_dhcp6_client *client,
return 0;
}

+int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
+ assert_return(client, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!client->lease)
+ return -ENOMSG;
+
+ *ret = sd_dhcp6_lease_ref(client->lease);
+
+ return 0;
+}
+
static sd_dhcp6_client *client_notify(sd_dhcp6_client *client, int event) {
if (client->cb) {
client = sd_dhcp6_client_ref(client);
diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c
index 41d6a5a..cbda7d8 100644
--- a/src/libsystemd-network/sd-dhcp6-lease.c
+++ b/src/libsystemd-network/sd-dhcp6-lease.c
@@ -105,6 +105,45 @@ 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) {
+ assert_return(lease, -EINVAL);
+ assert_return(addr, -EINVAL);
+ assert_return(lifetime_preferred, -EINVAL);
+ assert_return(lifetime_valid, -EINVAL);
+
+ if (!lease->addr_iter)
+ return -ENOMSG;
+
+ memcpy(addr, &lease->addr_iter->address, sizeof(struct in6_addr));
+ *lifetime_preferred = be32toh(lease->addr_iter->lifetime_preferred);
+ *lifetime_valid = be32toh(lease->addr_iter->lifetime_valid);
+
+ lease->addr_iter = lease->addr_iter->addresses_next;
+
+ 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);
+}
+
sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease) {
if (lease)
assert_se(REFCNT_INC(lease->n_ref) >= 2);
diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h
index 3aa1af9..aadb622 100644
--- a/src/systemd/sd-dhcp6-client.h
+++ b/src/systemd/sd-dhcp6-client.h
@@ -26,6 +26,8 @@

#include "sd-event.h"

+#include "sd-dhcp6-lease.h"
+
enum {
DHCP6_EVENT_STOP = 0,
DHCP6_EVENT_NO_STATEFUL_CONFIGURATION = 10,
@@ -44,6 +46,8 @@ int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index);
int sd_dhcp6_client_set_mac(sd_dhcp6_client *client,
const struct ether_addr *mac_addr);

+int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret);
+
int sd_dhcp6_client_stop(sd_dhcp6_client *client);
int sd_dhcp6_client_start(sd_dhcp6_client *client);
int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
diff --git a/src/systemd/sd-dhcp6-lease.h b/src/systemd/sd-dhcp6-lease.h
index 0b2765c..1126f1a 100644
--- a/src/systemd/sd-dhcp6-lease.h
+++ b/src/systemd/sd-dhcp6-lease.h
@@ -23,8 +23,19 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/

+#include <netinet/in.h>
+
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);
+
sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease);
sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease);
--
1.9.1
Patrik Flykt
2014-06-13 13:45:12 UTC
Permalink
Receive and parse a Reply from the server. Set up T1 and T2 timers and
notify the library user of an acquired DHCPv6 lease.
---
src/libsystemd-network/dhcp6-protocol.h | 1 +
src/libsystemd-network/sd-dhcp6-client.c | 145 +++++++++++++++++++++++++++++++
src/systemd/sd-dhcp6-client.h | 1 +
3 files changed, 147 insertions(+)

diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h
index a3a8be1..95d37f7 100644
--- a/src/libsystemd-network/dhcp6-protocol.h
+++ b/src/libsystemd-network/dhcp6-protocol.h
@@ -70,6 +70,7 @@ enum DHCP6State {
DHCP6_STATE_RS = 1,
DHCP6_STATE_SOLICITATION = 2,
DHCP6_STATE_REQUEST = 3,
+ DHCP6_STATE_BOUND = 4,
};

enum {
diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index 312c84c..15e5ea0 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -239,6 +239,7 @@ static int client_send_message(sd_dhcp6_client *client) {

case DHCP6_STATE_STOPPED:
case DHCP6_STATE_RS:
+ case DHCP6_STATE_BOUND:
return -EINVAL;
}

@@ -258,6 +259,38 @@ static int client_send_message(sd_dhcp6_client *client) {
return 0;
}

+static int client_timeout_t2(sd_event_source *s, uint64_t usec,
+ void *userdata) {
+ sd_dhcp6_client *client = userdata;
+
+ assert_return(s, -EINVAL);
+ assert_return(client, -EINVAL);
+ assert_return(client->lease, -EINVAL);
+
+ client->lease->ia.timeout_t2 =
+ sd_event_source_unref(client->lease->ia.timeout_t2);
+
+ log_dhcp6_client(client, "Timeout T2");
+
+ return 0;
+}
+
+static int client_timeout_t1(sd_event_source *s, uint64_t usec,
+ void *userdata) {
+ sd_dhcp6_client *client = userdata;
+
+ assert_return(s, -EINVAL);
+ assert_return(client, -EINVAL);
+ assert_return(client->lease, -EINVAL);
+
+ client->lease->ia.timeout_t1 =
+ sd_event_source_unref(client->lease->ia.timeout_t1);
+
+ log_dhcp6_client(client, "Timeout T1");
+
+ return 0;
+}
+
static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
void *userdata) {
sd_dhcp6_client *client = userdata;
@@ -316,6 +349,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec,

case DHCP6_STATE_STOPPED:
case DHCP6_STATE_RS:
+ case DHCP6_STATE_BOUND:
return 0;
}

@@ -541,6 +575,32 @@ static int client_parse_message(sd_dhcp6_client *client,
return r;
}

+static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply,
+ size_t len)
+{
+ int r;
+ _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
+
+ if (reply->type != DHCP6_REPLY)
+ return -EINVAL;
+
+ r = dhcp6_lease_new(&lease);
+ if (r < 0)
+ return -ENOMEM;
+
+ r = client_parse_message(client, reply, len, lease);
+ if (r < 0)
+ return r;
+
+ dhcp6_lease_clear_timers(&client->lease->ia);
+
+ client->lease = sd_dhcp6_lease_unref(client->lease);
+ client->lease = lease;
+ lease = NULL;
+
+ return DHCP6_STATE_BOUND;
+}
+
static int client_receive_advertise(sd_dhcp6_client *client,
DHCP6Message *advertise, size_t len) {
int r;
@@ -639,6 +699,29 @@ static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
break;

case DHCP6_STATE_REQUEST:
+ r = client_receive_reply(client, message, len);
+ if (r < 0)
+ return 0;
+
+ if (r == DHCP6_STATE_BOUND) {
+
+ r = client_start(client, DHCP6_STATE_BOUND);
+ if (r < 0) {
+ client_stop(client, r);
+ return 0;
+ }
+
+ client = client_notify(client, DHCP6_EVENT_IP_ACQUIRE);
+ if (!client)
+ return 0;
+ }
+
+ break;
+
+ case DHCP6_STATE_BOUND:
+
+ break;
+
case DHCP6_STATE_STOPPED:
case DHCP6_STATE_RS:
return 0;
@@ -655,6 +738,8 @@ static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
{
int r;
+ usec_t timeout, time_now;
+ char time_string[FORMAT_TIMESPAN_MAX];

assert_return(client, -EINVAL);
assert_return(client->event, -EINVAL);
@@ -698,9 +783,69 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
break;

case DHCP6_STATE_REQUEST:
+
client->state = state;

break;
+
+ case DHCP6_STATE_BOUND:
+
+ r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
+ if (r < 0)
+ return r;
+
+ if (client->lease->ia.lifetime_t1 == 0xffffffff ||
+ client->lease->ia.lifetime_t2 == 0xffffffff) {
+
+ log_dhcp6_client(client, "infinite T1 0x%08x or T2 "
+ "0x%08x",
+ be32toh(client->lease->ia.lifetime_t1),
+ be32toh(client->lease->ia.lifetime_t2));
+
+ return 0;
+ }
+
+ timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t1) * USEC_PER_SEC);
+
+ log_dhcp6_client(client, "T1 expires in %s",
+ format_timespan(time_string,
+ FORMAT_TIMESPAN_MAX,
+ timeout, 0));
+
+ r = sd_event_add_time(client->event,
+ &client->lease->ia.timeout_t1,
+ CLOCK_MONOTONIC, time_now + timeout,
+ 10 * USEC_PER_SEC, client_timeout_t1,
+ client);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(client->lease->ia.timeout_t1,
+ client->event_priority);
+ if (r < 0)
+ return r;
+
+ timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t2) * USEC_PER_SEC);
+
+ log_dhcp6_client(client, "T2 expires in %s",
+ format_timespan(time_string,
+ FORMAT_TIMESPAN_MAX,
+ timeout, 0));
+
+ r = sd_event_add_time(client->event,
+ &client->lease->ia.timeout_t2,
+ CLOCK_MONOTONIC, time_now + timeout,
+ 10 * USEC_PER_SEC, client_timeout_t2,
+ client);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(client->lease->ia.timeout_t2,
+ client->event_priority);
+ if (r < 0)
+ return r;
+
+ return 0;
}

client->transaction_id = random_u32() & htobe32(0x00ffffff);
diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h
index aadb622..81e26fe 100644
--- a/src/systemd/sd-dhcp6-client.h
+++ b/src/systemd/sd-dhcp6-client.h
@@ -33,6 +33,7 @@ enum {
DHCP6_EVENT_NO_STATEFUL_CONFIGURATION = 10,
DHCP6_EVENT_RESEND_EXPIRE = 11,
DHCP6_EVENT_RETRANS_MAX = 12,
+ DHCP6_EVENT_IP_ACQUIRE = 13,
};

typedef struct sd_dhcp6_client sd_dhcp6_client;
--
1.9.1
Patrik Flykt
2014-06-13 13:45:13 UTC
Permalink
Enhance the test case by generating a Reply. With a properly formed
Reply the callback function will be called and the additional
earlier event loop exit can now be removed.
---
src/libsystemd-network/test-dhcp6-client.c | 45 ++++++++++++++++++++++++++----
1 file changed, 40 insertions(+), 5 deletions(-)

diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c
index 1acbf95..d1d147a 100644
--- a/src/libsystemd-network/test-dhcp6-client.c
+++ b/src/libsystemd-network/test-dhcp6-client.c
@@ -50,7 +50,6 @@ static int test_index = 42;
static int test_client_message_num;
static be32_t test_iaid = 0;
static uint8_t test_duid[14] = { };
-static sd_event *e_solicit;

static int test_client_basic(sd_event *e) {
sd_dhcp6_client *client;
@@ -172,6 +171,31 @@ static uint8_t msg_advertise[198] = {
0x53, 0x00, 0x07, 0x00, 0x01, 0x00
};

+static uint8_t msg_reply[173] = {
+ 0x07, 0xf7, 0x4e, 0x57, 0x00, 0x02, 0x00, 0x0e,
+ 0x00, 0x01, 0x00, 0x01, 0x19, 0x40, 0x5c, 0x53,
+ 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53, 0x00, 0x01,
+ 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1a, 0x6b,
+ 0xf3, 0x30, 0x3c, 0x97, 0x0e, 0xcf, 0xa3, 0x7d,
+ 0x00, 0x03, 0x00, 0x4a, 0x0e, 0xcf, 0xa3, 0x7d,
+ 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x78,
+ 0x00, 0x05, 0x00, 0x18, 0x20, 0x01, 0x0d, 0xb8,
+ 0xde, 0xad, 0xbe, 0xef, 0x78, 0xee, 0x1c, 0xf3,
+ 0x09, 0x3c, 0x55, 0xad, 0x00, 0x00, 0x00, 0x96,
+ 0x00, 0x00, 0x00, 0xb4, 0x00, 0x0d, 0x00, 0x1e,
+ 0x00, 0x00, 0x41, 0x6c, 0x6c, 0x20, 0x61, 0x64,
+ 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x20,
+ 0x77, 0x65, 0x72, 0x65, 0x20, 0x61, 0x73, 0x73,
+ 0x69, 0x67, 0x6e, 0x65, 0x64, 0x2e, 0x00, 0x17,
+ 0x00, 0x10, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad,
+ 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x18, 0x00, 0x0b, 0x03, 0x6c,
+ 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, 0x72, 0x61,
+ 0x00, 0x00, 0x1f, 0x00, 0x10, 0x20, 0x01, 0x0d,
+ 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01
+};
+
static int test_advertise_option(sd_event *e) {
_cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
DHCP6Message *advertise = (DHCP6Message *)msg_advertise;
@@ -349,6 +373,20 @@ int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) {
}

static int test_client_send_reply(DHCP6Message *request) {
+ DHCP6Message reply;
+
+ reply.transaction_id = request->transaction_id;
+ reply.type = DHCP6_REPLY;
+
+ memcpy(msg_reply, &reply.transaction_id, 4);
+
+ memcpy(&msg_reply[26], test_duid, sizeof(test_duid));
+
+ memcpy(&msg_reply[44], &test_iaid, sizeof(test_iaid));
+
+ assert_se(write(test_dhcp_fd[1], msg_reply, sizeof(msg_reply))
+ == sizeof(msg_reply));
+
return 0;
}

@@ -422,8 +460,6 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option,
assert_se(sd_dhcp6_lease_get_next_address(lease, &addr, &lt_pref,
&lt_valid) == -ENOMSG);

- sd_event_exit(e_solicit, 0);
-
return 0;
}

@@ -524,6 +560,7 @@ static void test_client_solicit_cb(sd_dhcp6_client *client, int event,
sd_event *e = userdata;

assert_se(e);
+ assert_se(event == DHCP6_EVENT_IP_ACQUIRE);

if (verbose)
printf(" got DHCPv6 event %d\n", event);
@@ -553,8 +590,6 @@ static int test_client_solicit(sd_event *e) {
time_now + 2 * USEC_PER_SEC, 0,
test_hangcheck, NULL) >= 0);

- e_solicit = e;
-
assert_se(sd_dhcp6_client_start(client) >= 0);

sd_event_loop(e);
--
1.9.1
Dan Williams
2014-06-13 16:26:10 UTC
Permalink
Hi,
This patch set provides initial DHCPv6 client support for
systemd-networkd. It adds support for ICMPv6 Router Solicitation sending
and Router Advertisment receiving and the very basic Solicit, Advertise,
Request and Reply message transactions. Networkd is able to start DHCPv6
negotiation for a link, but doesn't yet do anything with the result.
As only the initial DHCPv6 client side support is implemented, this patch
- Rapid Commit in the Solicit message
- Information Request, should the router only announce the availability
of "Other" information
- Reacquiring of an address, i.e. Renew, Rebind, etc.
- Temporary adresses, option requesting, elapsed time options
- DHCPv6 authentication
...and a few more that I for sure have forgotten from this list. The
intent is to have the remaining parts essential for the protocol
functionality to be done as quickly as possible after this patch set.
On startup, the DHCPv6 client implementation sends Router Soliciations
in order to receive Router Advertisments. Only the Managed/Other flags
in the Router Advertisment are currently used, any prefix information
is currently ignored. ICMPv6 and a basic test case is handled by patches
02-04 with the basic DHCPv6 library and test case provided in patches 01
and 08.
For future consumers of the library, can the RA/RS behavior be
selectively disabled?

Dan
Machine-specific DUID and IAID identifiers are created in patches 05-07.
The DUID is used to uniquely identify the machine with the IAID
identifying a particular interface. The DUID is based on the machine ID
and the IAID on interface name from udev or, if run in a container, the
interface MAC address. Credits for implementing this part of the code
goes to Tom.
With identifiers created, the DHCPv6 message (re)transmission is provided
by patch 09 and enhanced in turn with patches 12 and 22 where Solicit
and Reply message handling is implemented, respectively. Support for DHCPv6
option appending and parsing is provided in patches 10 and 15, separated
due to implementation complexity of the IA option and need of DHCPv6 lease
handling code provided by patch 14.
The remaining patches add DHCPv6 message processing one message at a time
with the required state changes and retransmission restarting. A client
test program is provided, it should hopefully prevent possible bugs with
later commits.
As with the DHCPv4 code, also with DHCPv6 it again became evident that
borrowing code 1:1 from ConnMan wasn't really an option. The main loop
syntax and systemd coding style is different enough to prevent any direct
copying. On the positive side the ConnMan implementation exists and works
very well making it possible to thoroughly dissect the DHCPv6 messaging
going back and forth between the client and the server.
Have fun while reviewing,
Patrik
sd-dhcp6-client: Add initial DHCPv6 client files
sd-dhcp6-client: Add Router Solicitation and Advertisement support
sd-dhcp6-client: Add initial Router Advertisement test case
sd-dhcp6-client: Initialize Router Solicitation on startup
sd-dhcp6-client: Add DHCPv6 IAID functionality
sd-dhcp6-client: Add basic DHCPv6 test cases
sd-dhcp6-client: Add DHCPv6 client Solicitation timeout handling
sd-dhcp6-client: Add basic DHCPv6 option handling
sd-dhcp6-client: Add functions to bind to DHCPv6 UDP socket
sd-dhcp6-client: Add DHCPv6 Solicit message creation and sending
sd-dhcp6-client: Add RA and DHCPv6 Solicit test case
sd-dhcp6-lease: Add DHCPv6 lease handling
sd-dhcp6-client: Add IA Address option parsing
sd-dhcp6-client: Receive and parse Advertise messages
sd-dhcp6-lease: Add functions for accessing lease and addresses
sd-dhcp6-client: Add test case for Advertise message parsing
sd-dhcp6-client: Update start function to take a state
sd-dhcp6-client: Add Request message sending
sd-dhcp6-client: Add Advertise sending for test cases
sd-dhcp6-client: Receive and parse a reply and set T1 and T2 timers
sd-dhcp6-client: Add reply sending for test
networkd: Add initial DHCPv6 support
sd-dhcp6-client: Initialize DUID
network-internal: split out net_get_name()
Makefile.am | 38 +-
src/libsystemd-network/dhcp6-internal.h | 79 ++
src/libsystemd-network/dhcp6-lease-internal.h | 57 ++
src/libsystemd-network/dhcp6-network.c | 194 +++++
src/libsystemd-network/dhcp6-option.c | 314 ++++++++
src/libsystemd-network/dhcp6-protocol.h | 124 +++
src/libsystemd-network/icmp6-nd.c | 317 ++++++++
src/libsystemd-network/icmp6-nd.h | 59 ++
src/libsystemd-network/network-internal.c | 26 +-
src/libsystemd-network/network-internal.h | 1 +
src/libsystemd-network/sd-dhcp6-client.c | 1056 +++++++++++++++++++++++++
src/libsystemd-network/sd-dhcp6-lease.c | 178 +++++
src/libsystemd-network/test-dhcp6-client.c | 624 +++++++++++++++
src/libsystemd-network/test-dhcp6-rs.c | 155 ++++
src/network/networkd-link.c | 75 ++
src/network/networkd-network-gperf.gperf | 1 +
src/network/networkd.h | 3 +
src/systemd/sd-dhcp6-client.h | 62 ++
src/systemd/sd-dhcp6-lease.h | 42 +
19 files changed, 3396 insertions(+), 9 deletions(-)
create mode 100644 src/libsystemd-network/dhcp6-internal.h
create mode 100644 src/libsystemd-network/dhcp6-lease-internal.h
create mode 100644 src/libsystemd-network/dhcp6-network.c
create mode 100644 src/libsystemd-network/dhcp6-option.c
create mode 100644 src/libsystemd-network/dhcp6-protocol.h
create mode 100644 src/libsystemd-network/icmp6-nd.c
create mode 100644 src/libsystemd-network/icmp6-nd.h
create mode 100644 src/libsystemd-network/sd-dhcp6-client.c
create mode 100644 src/libsystemd-network/sd-dhcp6-lease.c
create mode 100644 src/libsystemd-network/test-dhcp6-client.c
create mode 100644 src/libsystemd-network/test-dhcp6-rs.c
create mode 100644 src/systemd/sd-dhcp6-client.h
create mode 100644 src/systemd/sd-dhcp6-lease.h
Tom Gundersen
2014-06-13 16:38:33 UTC
Permalink
Post by Dan Williams
Hi,
This patch set provides initial DHCPv6 client support for
systemd-networkd. It adds support for ICMPv6 Router Solicitation sending
and Router Advertisment receiving and the very basic Solicit, Advertise,
Request and Reply message transactions. Networkd is able to start DHCPv6
negotiation for a link, but doesn't yet do anything with the result.
As only the initial DHCPv6 client side support is implemented, this patch
- Rapid Commit in the Solicit message
- Information Request, should the router only announce the availability
of "Other" information
- Reacquiring of an address, i.e. Renew, Rebind, etc.
- Temporary adresses, option requesting, elapsed time options
- DHCPv6 authentication
...and a few more that I for sure have forgotten from this list. The
intent is to have the remaining parts essential for the protocol
functionality to be done as quickly as possible after this patch set.
On startup, the DHCPv6 client implementation sends Router Soliciations
in order to receive Router Advertisments. Only the Managed/Other flags
in the Router Advertisment are currently used, any prefix information
is currently ignored. ICMPv6 and a basic test case is handled by patches
02-04 with the basic DHCPv6 library and test case provided in patches 01
and 08.
For future consumers of the library, can the RA/RS behavior be
selectively disabled?
No objections from me, but out of interest, what is your desired
behaviour here? Will this option be exposed to the user, or do you
have some other way to determine whether or not to do RA/RS, or do you
simply do it in a different way?

Cheers,

Tom
Dan Williams
2014-06-13 17:10:20 UTC
Permalink
Post by Tom Gundersen
Post by Dan Williams
Hi,
This patch set provides initial DHCPv6 client support for
systemd-networkd. It adds support for ICMPv6 Router Solicitation sending
and Router Advertisment receiving and the very basic Solicit, Advertise,
Request and Reply message transactions. Networkd is able to start DHCPv6
negotiation for a link, but doesn't yet do anything with the result.
As only the initial DHCPv6 client side support is implemented, this patch
- Rapid Commit in the Solicit message
- Information Request, should the router only announce the availability
of "Other" information
- Reacquiring of an address, i.e. Renew, Rebind, etc.
- Temporary adresses, option requesting, elapsed time options
- DHCPv6 authentication
...and a few more that I for sure have forgotten from this list. The
intent is to have the remaining parts essential for the protocol
functionality to be done as quickly as possible after this patch set.
On startup, the DHCPv6 client implementation sends Router Soliciations
in order to receive Router Advertisments. Only the Managed/Other flags
in the Router Advertisment are currently used, any prefix information
is currently ignored. ICMPv6 and a basic test case is handled by patches
02-04 with the basic DHCPv6 library and test case provided in patches 01
and 08.
For future consumers of the library, can the RA/RS behavior be
selectively disabled?
No objections from me, but out of interest, what is your desired
behaviour here? Will this option be exposed to the user, or do you
have some other way to determine whether or not to do RA/RS, or do you
simply do it in a different way?
Two reasons:

1) We're already using libndp to do RS/RA in NM, and I'm sure we'd be
open to switching over in the future, but more importantly:

2) IPv6 RA/RS has no relationship with DHCP per-se, it's a provider of
some information that DHCP might want to use (the M/O bit) but that's
all. As such it makes no real sense to tie the two together, because
RS/RA is used in many more circumstances than DHCP is used. I'm talking
about dhcp_network_icmp6_send_router_solicitation() and
dhcp_network_icmp6_bind_router_solicitation() mostly.

So best would be to keep the RS/RA bits completely separate from the
DHCP bits, but then consume the RS/RA stuff from the DHCP code. That's
not going to adversely affect time-to-address if properly architected.

I know you probably don't care much about other DHCP clients, but we're
fighting this same battle with dhcpcd (on Debian mostly) which tries
really, really hard to be everything-and-the-kitchen-sink, and also
unconditionally does RS/RA when DHCPv6 is requested.

Dan
Tom Gundersen
2014-06-13 17:13:21 UTC
Permalink
Post by Dan Williams
Post by Tom Gundersen
Post by Dan Williams
Hi,
This patch set provides initial DHCPv6 client support for
systemd-networkd. It adds support for ICMPv6 Router Solicitation sending
and Router Advertisment receiving and the very basic Solicit, Advertise,
Request and Reply message transactions. Networkd is able to start DHCPv6
negotiation for a link, but doesn't yet do anything with the result.
As only the initial DHCPv6 client side support is implemented, this patch
- Rapid Commit in the Solicit message
- Information Request, should the router only announce the availability
of "Other" information
- Reacquiring of an address, i.e. Renew, Rebind, etc.
- Temporary adresses, option requesting, elapsed time options
- DHCPv6 authentication
...and a few more that I for sure have forgotten from this list. The
intent is to have the remaining parts essential for the protocol
functionality to be done as quickly as possible after this patch set.
On startup, the DHCPv6 client implementation sends Router Soliciations
in order to receive Router Advertisments. Only the Managed/Other flags
in the Router Advertisment are currently used, any prefix information
is currently ignored. ICMPv6 and a basic test case is handled by patches
02-04 with the basic DHCPv6 library and test case provided in patches 01
and 08.
For future consumers of the library, can the RA/RS behavior be
selectively disabled?
No objections from me, but out of interest, what is your desired
behaviour here? Will this option be exposed to the user, or do you
have some other way to determine whether or not to do RA/RS, or do you
simply do it in a different way?
1) We're already using libndp to do RS/RA in NM, and I'm sure we'd be
2) IPv6 RA/RS has no relationship with DHCP per-se, it's a provider of
some information that DHCP might want to use (the M/O bit) but that's
all. As such it makes no real sense to tie the two together, because
RS/RA is used in many more circumstances than DHCP is used. I'm talking
about dhcp_network_icmp6_send_router_solicitation() and
dhcp_network_icmp6_bind_router_solicitation() mostly.
Right. Splitting this up makes sense to me.

Cheers,

Tom
Post by Dan Williams
So best would be to keep the RS/RA bits completely separate from the
DHCP bits, but then consume the RS/RA stuff from the DHCP code. That's
not going to adversely affect time-to-address if properly architected.
I know you probably don't care much about other DHCP clients, but we're
fighting this same battle with dhcpcd (on Debian mostly) which tries
really, really hard to be everything-and-the-kitchen-sink, and also
unconditionally does RS/RA when DHCPv6 is requested.
Dan
Loading...