[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