[systemd-devel] [PATCH 02/24] sd-dhcp6-client: Add Router Solicitation and Advertisement support
Dan Williams
dcbw at redhat.com
Fri Jun 13 10:17:44 PDT 2014
On Fri, 2014-06-13 at 16:44 +0300, Patrik Flykt wrote:
> 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
> ---
> 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);
More information about the systemd-devel
mailing list