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); _______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/systemd-devel