* [PATCH 3/7] icmp6: Switch socket from AF_INET6 to AF_PACKET
2022-09-19 13:30 [PATCH 1/7] icmp6: Save SLAAC prefixes from RAs Andrew Zaborowski
2022-09-19 13:31 ` [PATCH 2/7] icmp6: Parse RDNSS and DNSSL options Andrew Zaborowski
@ 2022-09-19 13:31 ` Andrew Zaborowski
2022-09-19 13:31 ` [PATCH 4/7] rtnl: Add l_rtnl_address_get_in_addr Andrew Zaborowski
` (3 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Andrew Zaborowski @ 2022-09-19 13:31 UTC (permalink / raw)
To: ell
In order to be able to send our Router Solicitations before the
interface has a confirmed link-local address we have to be able to
supply our own IPv6 header and skip source address selection and routing
in the kernel. It is at least tricky to do with an AF_INET6 raw socket
as some socket features, like SO_DONTROUTE/MSG_DONTROUTE, are not
supported and others don't suppress the source address selection/checks.
Additionally since we need to use the IPv6 unspecified address, which is
represented as all 0 bytes, some syscalls interpret it as no value
supplied.
Build/parse the IPv6 header on our own and send/receive using an
AF_PACKET socket.
Once we have the link-local address, we switch to sending from that
address. Add l_icmp6_client_set_link_local_address for the user to give
it to us as they're likely to need it for DHCPv6.
---
ell/dhcp6.c | 3 +
ell/ell.sym | 1 +
ell/icmp6.c | 324 ++++++++++++++++++++++++++++++------------------
ell/icmp6.h | 2 +
ell/netconfig.c | 1 +
5 files changed, 213 insertions(+), 118 deletions(-)
diff --git a/ell/dhcp6.c b/ell/dhcp6.c
index 01d7eb3..15b451d 100644
--- a/ell/dhcp6.c
+++ b/ell/dhcp6.c
@@ -1619,6 +1619,9 @@ LIB_EXPORT bool l_dhcp6_client_set_link_local_address(
if (inet_pton(AF_INET6, ll, &client->ll_address) != 1)
return false;
+ if (!client->nora)
+ l_icmp6_client_set_link_local_address(client->icmp6, ll);
+
return true;
}
diff --git a/ell/ell.sym b/ell/ell.sym
index ed66352..a28eb55 100644
--- a/ell/ell.sym
+++ b/ell/ell.sym
@@ -703,6 +703,7 @@ global:
l_icmp6_client_set_nodelay;
l_icmp6_client_set_rtnl;
l_icmp6_client_set_route_priority;
+ l_icmp6_client_set_link_local_address;
l_icmp6_router_get_address;
l_icmp6_router_get_managed;
l_icmp6_router_get_other;
diff --git a/ell/icmp6.c b/ell/icmp6.c
index c38df17..a2765ea 100644
--- a/ell/icmp6.c
+++ b/ell/icmp6.c
@@ -24,6 +24,7 @@
#include <config.h>
#endif
+#define _GNU_SOURCE
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
@@ -31,12 +32,16 @@
#include <netinet/icmp6.h>
#include <linux/ipv6.h>
#include <linux/rtnetlink.h>
+#include <linux/if_packet.h>
+#include <linux/filter.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <net/if.h>
+#include <net/ethernet.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
+#include <stddef.h>
#include "private.h"
#include "useful.h"
@@ -75,135 +80,186 @@
{ { { 0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } }
#define IN6ADDR_LINKLOCAL_ALLROUTERS_INIT \
{ { { 0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2 } } }
+#define LLADDR_LINKLOCAL_ALLNODES_INIT \
+ { 0x33,0x33,0,0,0,1 }
+#define LLADDR_LINKLOCAL_ALLROUTERS_INIT \
+ { 0x33,0x33,0,0,0,2 }
-static const struct in6_addr in6addr_linklocal_allnodes_init =
- IN6ADDR_LINKLOCAL_ALLNODES_INIT;
-
-static int add_mreq(int s, int ifindex, const struct in6_addr *mc_addr)
-{
- struct ipv6_mreq mreq = {
- .ipv6mr_interface = ifindex,
- .ipv6mr_multiaddr = *mc_addr,
- };
-
- return setsockopt(s, IPPROTO_IPV6,
- IPV6_JOIN_GROUP, &mreq, sizeof(mreq));
-}
-
-static int icmp6_open_router_common(const struct icmp6_filter *filter,
- int ifindex)
+static int icmp6_open_router_solicitation(int ifindex)
{
int s;
- int r;
- int yes = 1;
- int no = 0;
- int nhops = 255;
+ struct sockaddr_ll addr;
+ struct sock_filter filter[] = {
+ /* A <- packet length */
+ BPF_STMT(BPF_LD | BPF_W | BPF_LEN, 0),
+ /* A >= sizeof(nd_router_advert) ? */
+ BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, sizeof(struct ip6_hdr) +
+ sizeof(struct nd_router_advert), 1, 0),
+ /* ignore */
+ BPF_STMT(BPF_RET | BPF_K, 0),
+ /* A <- IP version + Traffic class */
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, 0),
+ /* A <- A & 0xf0 (Mask off version) */
+ BPF_STMT(BPF_ALU | BPF_AND | BPF_K, 0xf0),
+ /* A == IPv6 ? */
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 6 << 4, 1, 0),
+ /* ignore */
+ BPF_STMT(BPF_RET | BPF_K, 0),
+ /* A <- Next Header */
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS,
+ offsetof(struct ip6_hdr, ip6_nxt)),
+ /* A == ICMPv6 ? */
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 1, 0),
+ /* ignore */
+ BPF_STMT(BPF_RET | BPF_K, 0),
+ /* A <- ICMPv6 Type */
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, sizeof(struct ip6_hdr) +
+ offsetof(struct icmp6_hdr, icmp6_type)),
+ /* A == Router Advertisement ? */
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 1, 0),
+ /* ignore */
+ BPF_STMT(BPF_RET | BPF_K, 0),
+ /* A <- Payload Length */
+ BPF_STMT(BPF_LD | BPF_H | BPF_ABS,
+ offsetof(struct ip6_hdr, ip6_plen)),
+ /* A >= sizeof(nd_router_advert) ? */
+ BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K,
+ sizeof(struct nd_router_advert), 1, 0),
+ /* ignore */
+ BPF_STMT(BPF_RET | BPF_K, 0),
+ /* return all */
+ BPF_STMT(BPF_RET | BPF_K, 65535),
+ };
+ const struct sock_fprog fprog = {
+ .len = L_ARRAY_SIZE(filter),
+ .filter = filter
+ };
+ int one = 1;
- s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMPV6);
+ s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IPV6));
if (s < 0)
return -errno;
- r = setsockopt(s, IPPROTO_ICMPV6,
- ICMP6_FILTER, filter, sizeof(struct icmp6_filter));
- if (r < 0)
- goto fail;
-
- r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &no, sizeof(no));
- if (r < 0)
- goto fail;
-
- r = setsockopt(s, IPPROTO_IPV6,
- IPV6_MULTICAST_IF, &ifindex, sizeof(ifindex));
- if (r < 0)
- goto fail;
+ if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER,
+ &fprog, sizeof(fprog)) < 0)
+ goto error;
- r = setsockopt(s, IPPROTO_IPV6,
- IPV6_RECVHOPLIMIT, &yes, sizeof(yes));
- if (r < 0)
- goto fail;
+ if (setsockopt(s, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0)
+ goto error;
- r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
- &nhops, sizeof(nhops));
- if (r < 0)
- goto fail;
+ memset(&addr, 0, sizeof(addr));
+ addr.sll_family = AF_PACKET;
+ addr.sll_protocol = htons(ETH_P_IPV6);
+ addr.sll_ifindex = ifindex;
- r = setsockopt(s, SOL_SOCKET, SO_BINDTOIFINDEX,
- &ifindex, sizeof(ifindex));
- if (r < 0 && errno == ENOPROTOOPT) {
- struct ifreq ifr = {
- .ifr_ifindex = ifindex,
- };
-
- r = ioctl(s, SIOCGIFNAME, &ifr);
- if (r < 0)
- goto fail;
-
- r = setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
- ifr.ifr_name, strlen(ifr.ifr_name) + 1);
- }
-
- if (r < 0)
- goto fail;
-
- r = setsockopt(s, SOL_SOCKET, SO_TIMESTAMP, &yes, sizeof(yes));
- if (r < 0)
- goto fail;
+ if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0)
+ goto error;
return s;
-fail:
- close(s);
+error:
+ L_TFR(close(s));
return -errno;
}
-static int icmp6_open_router_solicitation(int ifindex)
+static uint16_t icmp6_checksum(const struct iovec *iov, unsigned int iov_len)
{
- struct icmp6_filter filter;
- int s;
- int r;
+ const struct ip6_hdr *ip_hdr = iov[0].iov_base;
+ uint32_t sum = 0;
+ const uint16_t *ptr;
+ const uint16_t *buf_end;
+ /* Skip the real IPv6 header */
+ unsigned int buf_offset = sizeof(struct ip6_hdr);
- ICMP6_FILTER_SETBLOCKALL(&filter);
- ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
+ /*
+ * ICMPv6 checksum according to RFC 4443 Section 2.3, this includes
+ * the IPv6 payload + the IPv6 pseudo-header according to RFC 2460
+ * Section 8.1, i.e. the two IPv6 addresses + the payload length +
+ * the header type. The caller must ensure that the IPv6 header is
+ * all in one buffer and that all buffer starts and lengths are
+ * 16-bit-aligned.
+ *
+ * We can skip all zero words such as the upper 16 bits of the
+ * payload length. No need to byteswap as the carry bits from
+ * either byte (high or low) accumulate in the other byte in
+ * exactly the same way.
+ */
+ buf_end = (void *) &ip_hdr->ip6_src + 32;
+ for (ptr = (void *) &ip_hdr->ip6_src; ptr < buf_end; )
+ sum += *ptr++;
- s = icmp6_open_router_common(&filter, ifindex);
- if (s < 0)
- return s;
+ sum += ip_hdr->ip6_plen + htons(ip_hdr->ip6_nxt);
- r = add_mreq(s, ifindex, &in6addr_linklocal_allnodes_init);
- if (r < 0) {
- close(s);
- return -errno;
+ for (; iov_len; iov++, iov_len--) {
+ buf_end = iov->iov_base + iov->iov_len;
+ for (ptr = iov->iov_base + buf_offset; ptr < buf_end; )
+ sum += *ptr++;
+
+ buf_offset = 0;
}
- return s;
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+
+ return ~sum;
}
-static int icmp6_send_router_solicitation(int s, const uint8_t mac[static 6])
+static int icmp6_send_router_solicitation(int s, int ifindex,
+ const uint8_t src_mac[static 6],
+ const struct in6_addr *src_ip)
{
- struct sockaddr_in6 dst = {
- .sin6_family = AF_INET6,
- .sin6_addr = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT,
- };
struct nd_router_solicit rs = {
.nd_rs_type = ND_ROUTER_SOLICIT,
+ .nd_rs_code = 0,
};
- struct nd_opt_hdr rs_opt = {
+ struct nd_opt_hdr rs_sllao = {
.nd_opt_type = ND_OPT_SOURCE_LINKADDR,
.nd_opt_len = 1,
};
- struct iovec iov[3] = {
+ const size_t rs_sllao_size = sizeof(rs_sllao) + 6;
+ struct ip6_hdr ip_hdr = {
+ .ip6_flow = htonl(6 << 28),
+ .ip6_hops = 255,
+ .ip6_nxt = IPPROTO_ICMPV6,
+ .ip6_plen = htons(sizeof(rs) + rs_sllao_size),
+ .ip6_dst = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT,
+ };
+ struct iovec iov[4] = {
+ { .iov_base = &ip_hdr, .iov_len = sizeof(ip_hdr) },
{ .iov_base = &rs, .iov_len = sizeof(rs) },
- { .iov_base = &rs_opt, .iov_len = sizeof(rs_opt) },
- { .iov_base = (void *) mac, .iov_len = 6 } };
-
+ { .iov_base = &rs_sllao, .iov_len = sizeof(rs_sllao) },
+ { .iov_base = (void *) src_mac, .iov_len = 6 } };
+
+ struct sockaddr_ll dst = {
+ .sll_family = AF_PACKET,
+ .sll_protocol = htons(ETH_P_IPV6),
+ .sll_ifindex = ifindex,
+ .sll_addr = LLADDR_LINKLOCAL_ALLROUTERS_INIT,
+ .sll_halen = 6,
+ };
struct msghdr msg = {
.msg_name = &dst,
.msg_namelen = sizeof(dst),
.msg_iov = iov,
- .msg_iovlen = 3,
+ .msg_iovlen = L_ARRAY_SIZE(iov),
};
int r;
+ memcpy(&ip_hdr.ip6_src, src_ip, 16);
+
+ if (l_memeqzero(src_ip, 16)) {
+ /*
+ * radvd will discard and warn about RSs from the unspecified
+ * address with the SLLAO, omit that option by dropping the
+ * last two iov buffers.
+ */
+ msg.msg_iovlen -= 2;
+ ip_hdr.ip6_plen = htons(ntohs(ip_hdr.ip6_plen) - rs_sllao_size);
+ }
+
+ /* Don't byteswap the checksum */
+ rs.nd_rs_cksum = icmp6_checksum(msg.msg_iov, msg.msg_iovlen);
+
r = sendmsg(s, &msg, 0);
if (r < 0)
return -errno;
@@ -211,22 +267,23 @@ static int icmp6_send_router_solicitation(int s, const uint8_t mac[static 6])
return 0;
}
-static int icmp6_receive(int s, void *buf, size_t buf_len, struct in6_addr *src,
- uint64_t *out_timestamp)
+static int icmp6_receive(int s, void *buf, ssize_t *buf_len,
+ struct in6_addr *src, uint64_t *out_timestamp)
{
char c_msg_buf[CMSG_SPACE(sizeof(int)) +
CMSG_SPACE(sizeof(struct timeval))];
- struct iovec iov = {
- .iov_base = buf,
- .iov_len = buf_len,
+ struct ip6_hdr ip_hdr;
+ struct iovec iov[2] = {
+ { .iov_base = &ip_hdr, .iov_len = sizeof(ip_hdr) },
+ { .iov_base = buf, .iov_len = *buf_len - sizeof(ip_hdr) },
};
- struct sockaddr_in6 saddr;
+ struct sockaddr_ll saddr;
struct msghdr msg = {
.msg_name = (void *)&saddr,
- .msg_namelen = sizeof(struct sockaddr_in6),
+ .msg_namelen = sizeof(struct sockaddr_ll),
.msg_flags = 0,
- .msg_iov = &iov,
- .msg_iovlen = 1,
+ .msg_iov = iov,
+ .msg_iovlen = L_ARRAY_SIZE(iov),
.msg_control = c_msg_buf,
.msg_controllen = sizeof(c_msg_buf),
};
@@ -238,22 +295,30 @@ static int icmp6_receive(int s, void *buf, size_t buf_len, struct in6_addr *src,
if (l < 0)
return -errno;
- if ((size_t) l != buf_len)
+ if (l != *buf_len)
return -EINVAL;
- if (msg.msg_namelen != sizeof(struct sockaddr_in6) ||
- saddr.sin6_family != AF_INET6)
- return -EPFNOSUPPORT;
+ if (ntohs(ip_hdr.ip6_plen) > iov[1].iov_len)
+ return -EMSGSIZE;
+
+ iov[1].iov_len = ntohs(ip_hdr.ip6_plen);
+
+ /*
+ * Unlikely but align length for icmp6_checksum(). We know we have
+ * at least sizeof(struct ip6_hdr) extra bytes in buf so we can
+ * append this 0 byte no problem.
+ */
+ if (iov[1].iov_len & 1)
+ ((uint8_t *) buf)[iov[1].iov_len++] = 0x00;
+
+ if (icmp6_checksum(iov, L_ARRAY_SIZE(iov)))
+ return -EBADMSG;
+
+ if (ip_hdr.ip6_hops != 255)
+ return -EMULTIHOP;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
- if (cmsg->cmsg_level == SOL_IPV6 &&
- cmsg->cmsg_type == IPV6_HOPLIMIT &&
- cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
- int hops = l_get_u32(CMSG_DATA(cmsg));
-
- if (hops != 255)
- return -EMULTIHOP;
- } else if (cmsg->cmsg_level == SOL_SOCKET &&
+ if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_TIMESTAMP &&
cmsg->cmsg_len ==
CMSG_LEN(sizeof(struct timeval))) {
@@ -263,7 +328,8 @@ static int icmp6_receive(int s, void *buf, size_t buf_len, struct in6_addr *src,
}
}
- memcpy(src, &saddr.sin6_addr, sizeof(saddr.sin6_addr));
+ *buf_len = ntohs(ip_hdr.ip6_plen);
+ memcpy(src, &ip_hdr.ip6_src, 16);
*out_timestamp = timestamp ?: l_time_now();
return 0;
}
@@ -280,6 +346,7 @@ struct l_icmp6_client {
struct l_timeout *timeout_send;
uint64_t retransmit_time;
struct l_io *io;
+ struct in6_addr src_ip;
struct l_icmp6_router *ra;
struct l_netlink *rtnl;
@@ -432,12 +499,16 @@ static bool icmp6_client_read_handler(struct l_io *io, void *userdata)
return false;
}
- ra = l_malloc(l);
- if (icmp6_receive(s, ra, l, &src, ×tamp) < 0)
- goto done;
+ if ((size_t) l < sizeof(struct ip6_hdr) +
+ sizeof(struct nd_router_advert)) {
+ CLIENT_DEBUG("Message too small - ignore");
+ return true;
+ }
- if ((size_t) l < sizeof(struct nd_router_advert)) {
- CLIENT_DEBUG("Message to small - ignore");
+ ra = l_malloc(l);
+ r = icmp6_receive(s, ra, &l, &src, ×tamp);
+ if (r < 0) {
+ CLIENT_DEBUG("icmp6_receive(): %s (%i)", strerror(-r), -r);
goto done;
}
@@ -475,7 +546,8 @@ static void icmp6_client_timeout_send(struct l_timeout *timeout,
SOLICITATION_INTERVAL);
r = icmp6_send_router_solicitation(l_io_get_fd(client->io),
- client->mac);
+ client->ifindex, client->mac,
+ &client->src_ip);
if (r < 0) {
CLIENT_DEBUG("Error sending Router Solicitation: %s",
strerror(-r));
@@ -692,6 +764,22 @@ LIB_EXPORT bool l_icmp6_client_set_route_priority(
return true;
}
+LIB_EXPORT bool l_icmp6_client_set_link_local_address(
+ struct l_icmp6_client *client,
+ const char *ll)
+{
+ if (unlikely(!client))
+ return false;
+
+ /*
+ * client->src_ip is all 0s initially which results in our Router
+ * Solicitations being sent from the IPv6 Unspecified Address, which
+ * is fine. Once we have a confirmed link-local address we use that
+ * as the source address.
+ */
+ return inet_pton(AF_INET6, ll, &client->src_ip) == 1;
+}
+
struct l_icmp6_router *_icmp6_router_new()
{
struct l_icmp6_router *r = l_new(struct l_icmp6_router, 1);
diff --git a/ell/icmp6.h b/ell/icmp6.h
index 615dba4..ffbb8a8 100644
--- a/ell/icmp6.h
+++ b/ell/icmp6.h
@@ -65,6 +65,8 @@ bool l_icmp6_client_set_rtnl(struct l_icmp6_client *client,
struct l_netlink *rtnl);
bool l_icmp6_client_set_route_priority(struct l_icmp6_client *client,
uint32_t priority);
+bool l_icmp6_client_set_link_local_address(struct l_icmp6_client *client,
+ const char *ll);
char *l_icmp6_router_get_address(const struct l_icmp6_router *r);
bool l_icmp6_router_get_managed(const struct l_icmp6_router *r);
diff --git a/ell/netconfig.c b/ell/netconfig.c
index bbe1dec..72c67a9 100644
--- a/ell/netconfig.c
+++ b/ell/netconfig.c
@@ -1532,6 +1532,7 @@ static void netconfig_ifaddr_ipv6_added(struct l_netconfig *nc,
netconfig_addr_wait_unregister(nc, true);
l_dhcp6_client_set_link_local_address(nc->dhcp6_client, ip);
+ l_icmp6_client_set_link_local_address(nc->icmp6_client, ip);
/*
* Only now that we have a link-local address start actual DHCPv6
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 5/7] netconfig: Decouple icmp6 start from dhcp6 start
2022-09-19 13:30 [PATCH 1/7] icmp6: Save SLAAC prefixes from RAs Andrew Zaborowski
` (2 preceding siblings ...)
2022-09-19 13:31 ` [PATCH 4/7] rtnl: Add l_rtnl_address_get_in_addr Andrew Zaborowski
@ 2022-09-19 13:31 ` Andrew Zaborowski
2022-09-19 13:31 ` [PATCH 6/7] netconfig: Create SLAAC address Andrew Zaborowski
2022-09-19 13:31 ` [PATCH 7/7] netconfig: Control optimistic DAD Andrew Zaborowski
5 siblings, 0 replies; 9+ messages in thread
From: Andrew Zaborowski @ 2022-09-19 13:31 UTC (permalink / raw)
To: ell
In preparation for supporting generating SLAAC addresses, start the
l_icmp6_client directly from the netconfig state machine.
l_dhcp6_client still takes care of creating and destroying
l_icmp6_client because it needs it for its own public API, but netconfig
starts/stops/handles events locally. This also allows a slight
optimization described in RFC4862 where if we're lucky, we will
send the Router Solicitation simultaneously with the Neighbor
Solicitation for the link-local addresses so as to avoid waiting the sum
of the RA response time + DAD timeout and instead wait the longer of the
two periods.
---
ell/dhcp6.c | 3 ++
ell/netconfig.c | 102 ++++++++++++++++++++++++++++++++++++++++++------
2 files changed, 93 insertions(+), 12 deletions(-)
diff --git a/ell/dhcp6.c b/ell/dhcp6.c
index 15b451d..e234eb0 100644
--- a/ell/dhcp6.c
+++ b/ell/dhcp6.c
@@ -1473,6 +1473,9 @@ static void dhcp6_client_icmp6_event(struct l_icmp6_client *icmp6,
{
struct l_dhcp6_client *client = user_data;
+ if (client->nora)
+ return;
+
switch (event) {
case L_ICMP6_CLIENT_EVENT_ROUTER_FOUND:
{
diff --git a/ell/netconfig.c b/ell/netconfig.c
index 72c67a9..3ac0319 100644
--- a/ell/netconfig.c
+++ b/ell/netconfig.c
@@ -24,12 +24,13 @@
#include <config.h>
#endif
+#include <net/if.h>
#include <linux/types.h>
#include <linux/if_ether.h>
+#include <linux/if_arp.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <netinet/icmp6.h>
-#include <net/if.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -56,6 +57,7 @@
#include "net.h"
#include "net-private.h"
#include "acd.h"
+#include "timeout.h"
#include "netconfig.h"
struct l_netconfig {
@@ -87,6 +89,8 @@ struct l_netconfig {
struct l_queue *icmp_route_data;
struct l_acd *acd;
unsigned int orig_disable_ipv6;
+ uint8_t mac[ETH_ALEN];
+ struct l_timeout *ra_timeout;
/* These objects, if not NULL, are owned by @addresses and @routes */
struct l_rtnl_address *v4_address;
@@ -665,6 +669,35 @@ static void netconfig_dhcp6_event_handler(struct l_dhcp6_client *client,
}
}
+static bool netconfig_match(const void *a, const void *b)
+{
+ return a == b;
+}
+
+static bool netconfig_check_start_dhcp6(struct l_netconfig *nc)
+{
+ /* Don't start DHCPv6 until we get an RA with the managed bit set */
+ if (nc->ra_timeout)
+ return true;
+
+ /* Don't start DHCPv6 while waiting for the link-local address */
+ if (l_queue_find(addr_wait_list, netconfig_match, nc))
+ return true;
+
+ return l_dhcp6_client_start(nc->dhcp6_client);
+}
+
+static void netconfig_ra_timeout_cb(struct l_timeout *timeout, void *user_data)
+{
+ struct l_netconfig *nc = user_data;
+
+ l_timeout_remove(l_steal_ptr(nc->ra_timeout));
+
+ /* No Router Advertisements received, assume no DHCPv6 or SLAAC */
+ l_icmp6_client_stop(nc->icmp6_client);
+ netconfig_emit_event(nc, AF_INET6, L_NETCONFIG_EVENT_FAILED);
+}
+
static uint64_t now;
static bool netconfig_check_route_expired(void *data, void *user_data)
@@ -883,23 +916,23 @@ static void netconfig_icmp6_event_handler(struct l_icmp6_client *client,
const struct l_icmp6_router *r;
struct netconfig_route_data *default_rd;
unsigned int i;
+ bool first_ra = false;
if (event != L_ICMP6_CLIENT_EVENT_ROUTER_FOUND)
return;
r = event_data;
- /*
- * Note: If this is the first RA received, the l_dhcp6_client
- * will have received the event before us and will be acting
- * on it by now.
- */
-
- if (nc->v6_gateway_override)
- return;
+ if (nc->ra_timeout) {
+ first_ra = true;
+ l_timeout_remove(l_steal_ptr(nc->ra_timeout));
+ }
netconfig_expire_routes(nc);
+ if (nc->v6_gateway_override)
+ goto process_nondefault_routes;
+
/* Process the default gateway information */
default_rd = netconfig_find_icmp6_route(nc, r->address, NULL);
@@ -922,6 +955,7 @@ static void netconfig_icmp6_event_handler(struct l_icmp6_client *client,
else if (default_rd && !r->lifetime)
netconfig_remove_icmp6_route(nc, default_rd);
+process_nondefault_routes:
/*
* Process the onlink and offlink routes, from the Router
* Advertisement's Prefix Information options and Route
@@ -950,6 +984,16 @@ static void netconfig_icmp6_event_handler(struct l_icmp6_client *client,
netconfig_remove_icmp6_route(nc, rd);
}
+ /* See if we should start DHCPv6 now */
+ if (first_ra) {
+ if (!l_icmp6_router_get_managed(r) ||
+ !netconfig_check_start_dhcp6(nc)) {
+ netconfig_emit_event(nc, AF_INET6,
+ L_NETCONFIG_EVENT_FAILED);
+ return;
+ }
+ }
+
/*
* Note: we may be emitting this before L_NETCONFIG_EVENT_CONFIGURE.
* We should probably instead save the affected routes in separate
@@ -1046,6 +1090,7 @@ LIB_EXPORT struct l_netconfig *l_netconfig_new(uint32_t ifindex)
nc, NULL);
nc->dhcp6_client = l_dhcp6_client_new(ifindex);
+ l_dhcp6_client_set_nora(nc->dhcp6_client, true);
l_dhcp6_client_set_event_handler(nc->dhcp6_client,
netconfig_dhcp6_event_handler,
nc, NULL);
@@ -1535,10 +1580,10 @@ static void netconfig_ifaddr_ipv6_added(struct l_netconfig *nc,
l_icmp6_client_set_link_local_address(nc->icmp6_client, ip);
/*
- * Only now that we have a link-local address start actual DHCPv6
- * setup.
+ * Only now that we have a link-local address see if we can start
+ * actual DHCPv6 setup.
*/
- if (l_dhcp6_client_start(nc->dhcp6_client))
+ if (netconfig_check_start_dhcp6(nc))
return;
netconfig_emit_event(nc, AF_INET6, L_NETCONFIG_EVENT_FAILED);
@@ -1687,6 +1732,35 @@ configure_ipv6:
l_queue_push_tail(addr_wait_list, netconfig);
+ if (!l_net_get_mac_address(netconfig->ifindex, netconfig->mac))
+ goto unregister;
+
+ l_dhcp6_client_set_address(netconfig->dhcp6_client, ARPHRD_ETHER,
+ netconfig->mac, ETH_ALEN);
+ l_icmp6_client_set_address(netconfig->icmp6_client, netconfig->mac);
+
+ /*
+ * RFC4862 Section 4: "To speed the autoconfiguration process, a host
+ * may generate its link-local address (and verify its uniqueness) in
+ * parallel with waiting for a Router Advertisement. Because a router
+ * may delay responding to a Router Solicitation for a few seconds,
+ * the total time needed to complete autoconfiguration can be
+ * significantly longer if the two steps are done serially."
+ *
+ * We don't know whether we have the LL address yet. The interface
+ * may have been just brought up and DAD may still running or the LL
+ * address may have been deleted and won't be added until
+ * netconfig_ifaddr_ipv6_dump_done_cb() writes the /proc settings.
+ * In any case the Router Solicitation doesn't depend on having the
+ * LL address so send it now. We won't start DHCPv6 however until we
+ * have both the LL address and the Router Advertisement.
+ */
+ if (!l_icmp6_client_start(netconfig->icmp6_client))
+ goto unregister;
+
+ netconfig->ra_timeout = l_timeout_create(10, netconfig_ra_timeout_cb,
+ netconfig, NULL);
+
done:
netconfig->started = true;
return true;
@@ -1717,6 +1791,9 @@ LIB_EXPORT void l_netconfig_stop(struct l_netconfig *netconfig)
if (netconfig->signal_expired_work)
l_idle_remove(l_steal_ptr(netconfig->signal_expired_work));
+ if (netconfig->ra_timeout)
+ l_timeout_remove(l_steal_ptr(netconfig->ra_timeout));
+
netconfig_addr_wait_unregister(netconfig, false);
netconfig_update_cleanup(netconfig);
@@ -1734,6 +1811,7 @@ LIB_EXPORT void l_netconfig_stop(struct l_netconfig *netconfig)
l_dhcp_client_stop(netconfig->dhcp_client);
l_dhcp6_client_stop(netconfig->dhcp6_client);
+ l_icmp6_client_stop(netconfig->icmp6_client);
l_acd_destroy(l_steal_ptr(netconfig->acd));
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 6/7] netconfig: Create SLAAC address
2022-09-19 13:30 [PATCH 1/7] icmp6: Save SLAAC prefixes from RAs Andrew Zaborowski
` (3 preceding siblings ...)
2022-09-19 13:31 ` [PATCH 5/7] netconfig: Decouple icmp6 start from dhcp6 start Andrew Zaborowski
@ 2022-09-19 13:31 ` Andrew Zaborowski
2022-09-19 13:31 ` [PATCH 7/7] netconfig: Control optimistic DAD Andrew Zaborowski
5 siblings, 0 replies; 9+ messages in thread
From: Andrew Zaborowski @ 2022-09-19 13:31 UTC (permalink / raw)
To: ell
If the Router Advertisement doesn't indicate DHCPv6 is available and
includes prefixes for address auto-configuration, fall back to using
those to generate a single IPv6 address. Other settings such as DNS
are not supported in this mode yet and there's no renewal timer for when
the address lifetime is finite.
---
ell/netconfig.c | 184 +++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 174 insertions(+), 10 deletions(-)
diff --git a/ell/netconfig.c b/ell/netconfig.c
index 3ac0319..3336b30 100644
--- a/ell/netconfig.c
+++ b/ell/netconfig.c
@@ -91,6 +91,11 @@ struct l_netconfig {
unsigned int orig_disable_ipv6;
uint8_t mac[ETH_ALEN];
struct l_timeout *ra_timeout;
+ enum {
+ NETCONFIG_V6_METHOD_UNSET,
+ NETCONFIG_V6_METHOD_DHCP,
+ NETCONFIG_V6_METHOD_SLAAC,
+ } v6_auto_method;
/* These objects, if not NULL, are owned by @addresses and @routes */
struct l_rtnl_address *v4_address;
@@ -677,7 +682,7 @@ static bool netconfig_match(const void *a, const void *b)
static bool netconfig_check_start_dhcp6(struct l_netconfig *nc)
{
/* Don't start DHCPv6 until we get an RA with the managed bit set */
- if (nc->ra_timeout)
+ if (nc->ra_timeout || nc->v6_auto_method != NETCONFIG_V6_METHOD_DHCP)
return true;
/* Don't start DHCPv6 while waiting for the link-local address */
@@ -698,6 +703,109 @@ static void netconfig_ra_timeout_cb(struct l_timeout *timeout, void *user_data)
netconfig_emit_event(nc, AF_INET6, L_NETCONFIG_EVENT_FAILED);
}
+static void netconfig_add_slaac_address(struct l_netconfig *nc,
+ const struct l_icmp6_router *r)
+{
+ unsigned int i;
+ const struct autoconf_prefix_info *longest = &r->ac_prefixes[0];
+ uint8_t addr[16];
+ char addr_str[INET6_ADDRSTRLEN];
+ uint32_t p, v;
+
+ /* Find the autoconfiguration prefix that offers the longest lifetime */
+ for (i = 1; i < r->n_ac_prefixes; i++)
+ if (r->ac_prefixes[i].preferred_lifetime >
+ longest->preferred_lifetime)
+ longest = &r->ac_prefixes[i];
+
+ memcpy(addr, longest->prefix, 8);
+ /* EUI-64-based Interface Identifier (RFC2464 Section 4) */
+ addr[ 8] = nc->mac[0] ^ 0x02;
+ addr[ 9] = nc->mac[1];
+ addr[10] = nc->mac[2];
+ addr[11] = 0xff;
+ addr[12] = 0xfe;
+ addr[13] = nc->mac[3];
+ addr[14] = nc->mac[4];
+ addr[15] = nc->mac[5];
+ inet_ntop(AF_INET6, addr, addr_str, sizeof(addr_str));
+ p = longest->preferred_lifetime;
+ v = longest->valid_lifetime;
+
+ nc->v6_address = l_rtnl_address_new(addr_str, 128);
+ l_rtnl_address_set_noprefixroute(nc->v6_address, true);
+
+ if (p != 0xffffffff || v != 0xffffffff) {
+ l_rtnl_address_set_lifetimes(nc->v6_address,
+ p != 0xffffffff ? p : 0,
+ v != 0xffffffff ? v : 0);
+ l_rtnl_address_set_expiry(nc->v6_address,
+ p != 0xffffffff ?
+ r->start_time + p * L_USEC_PER_SEC : 0,
+ v != 0xffffffff ?
+ r->start_time + v * L_USEC_PER_SEC : 0);
+ }
+
+ l_queue_push_tail(nc->addresses.current, nc->v6_address);
+ l_queue_push_tail(nc->addresses.added, nc->v6_address);
+ netconfig_emit_event(nc, AF_INET6, L_NETCONFIG_EVENT_CONFIGURE);
+
+ /* TODO: set a renew timeout */
+}
+
+static void netconfig_set_slaac_address_lifetimes(struct l_netconfig *nc,
+ const struct l_icmp6_router *r)
+{
+ const uint8_t *addr = l_rtnl_address_get_in_addr(nc->v6_address);
+ bool updated = false;
+ uint64_t p_expiry;
+ uint64_t v_expiry;
+ uint32_t remaining = 0xffffffff;
+ unsigned int i;
+
+ if (L_WARN_ON(!addr))
+ return;
+
+ l_rtnl_address_get_expiry(nc->v6_address, &p_expiry, &v_expiry);
+
+ if (v_expiry)
+ remaining = (v_expiry - r->start_time) / L_USEC_PER_SEC;
+
+ for (i = 0; i < r->n_ac_prefixes; i++) {
+ const struct autoconf_prefix_info *prefix = &r->ac_prefixes[i];
+ uint32_t p = prefix->preferred_lifetime;
+ uint32_t v = prefix->valid_lifetime;
+
+ if (memcmp(prefix->prefix, addr, 8))
+ continue;
+
+ /* RFC4862 Section 5.5.3 e) */
+ if (v < 120 * 60 && v < remaining)
+ v = 120 * 60; /* 2 hours */
+
+ l_rtnl_address_set_lifetimes(nc->v6_address,
+ p != 0xffffffff ? p : 0,
+ v != 0xffffffff ? v : 0);
+ p_expiry = p != 0xffffffff ? r->start_time + p * L_USEC_PER_SEC : 0;
+ v_expiry = v != 0xffffffff ? r->start_time + v * L_USEC_PER_SEC : 0;
+ l_rtnl_address_set_expiry(nc->v6_address, p_expiry, v_expiry);
+ updated = true;
+
+ /*
+ * TODO: modify the renew timeout.
+ *
+ * Also we probably want to apply a mechanism similar to that
+ * in netconfig_check_route_need_update() to avoid generating
+ * and UPDATED event for every RA that covers this prefix
+ * with constant lifetime values.
+ */
+ }
+
+ if (updated && !l_queue_find(nc->addresses.added, netconfig_match,
+ nc->v6_address))
+ l_queue_push_tail(nc->addresses.updated, nc->v6_address);
+}
+
static uint64_t now;
static bool netconfig_check_route_expired(void *data, void *user_data)
@@ -916,17 +1024,14 @@ static void netconfig_icmp6_event_handler(struct l_icmp6_client *client,
const struct l_icmp6_router *r;
struct netconfig_route_data *default_rd;
unsigned int i;
- bool first_ra = false;
if (event != L_ICMP6_CLIENT_EVENT_ROUTER_FOUND)
return;
r = event_data;
- if (nc->ra_timeout) {
- first_ra = true;
+ if (nc->ra_timeout)
l_timeout_remove(l_steal_ptr(nc->ra_timeout));
- }
netconfig_expire_routes(nc);
@@ -984,16 +1089,72 @@ process_nondefault_routes:
netconfig_remove_icmp6_route(nc, rd);
}
- /* See if we should start DHCPv6 now */
- if (first_ra) {
- if (!l_icmp6_router_get_managed(r) ||
- !netconfig_check_start_dhcp6(nc)) {
+ /*
+ * For lack of a better policy, select between DHCPv6 and SLAAC based
+ * on the first RA received. Prefer DHCPv6.
+ *
+ * Just like we currently only request one address in l_dhcp6_client,
+ * we only set up one address using SLAAC regardless of how many
+ * prefixes are available. Generate the address in the prefix that
+ * offers the longest preferred_lifetime.
+ */
+ if (nc->v6_auto_method == NETCONFIG_V6_METHOD_UNSET &&
+ l_icmp6_router_get_managed(r)) {
+ nc->v6_auto_method = NETCONFIG_V6_METHOD_DHCP;
+
+ if (!netconfig_check_start_dhcp6(nc)) {
netconfig_emit_event(nc, AF_INET6,
L_NETCONFIG_EVENT_FAILED);
return;
}
+
+ goto emit_event;
+ }
+
+ /*
+ * DHCP not available according to this router, check if any of the
+ * prefixes allow SLAAC.
+ */
+ if (nc->v6_auto_method == NETCONFIG_V6_METHOD_UNSET &&
+ r->n_ac_prefixes) {
+ nc->v6_auto_method = NETCONFIG_V6_METHOD_SLAAC;
+
+ /*
+ * The DAD for the link-local address may be still running
+ * but again we can generate the global address already and
+ * commit it to start in-kernel DAD for it.
+ *
+ * The global address alone should work for most uses. On
+ * the other hand since both the link-local address and the
+ * global address are based on the same MAC, there's some
+ * correlation between one failing DAD and the other
+ * failing DAD due to another host using the same address.
+ * As RFC4862 Section 5.4 notes we can't rely on that to
+ * skip DAD for one of the addresses.
+ */
+
+ netconfig_add_slaac_address(nc, r);
+ return;
}
+ /* Neither method seems available, fail */
+ if (nc->v6_auto_method == NETCONFIG_V6_METHOD_UNSET) {
+ netconfig_emit_event(nc, AF_INET6, L_NETCONFIG_EVENT_FAILED);
+ return;
+ }
+
+ /* DHCP already started or waiting for the LL address, nothing to do */
+ if (nc->v6_auto_method == NETCONFIG_V6_METHOD_DHCP)
+ goto emit_event;
+
+ /*
+ * Otherwise we already have a SLAAC address, just check if any of the
+ * auto-configuration prefixes in this RA covers our existing address
+ * and allows us to extend its lifetime.
+ */
+ netconfig_set_slaac_address_lifetimes(nc, r);
+
+emit_event:
/*
* Note: we may be emitting this before L_NETCONFIG_EVENT_CONFIGURE.
* We should probably instead save the affected routes in separate
@@ -1003,7 +1164,8 @@ process_nondefault_routes:
if (!l_queue_isempty(nc->routes.added) ||
!l_queue_isempty(nc->routes.updated) ||
!l_queue_isempty(nc->routes.removed) ||
- !l_queue_isempty(nc->routes.expired))
+ !l_queue_isempty(nc->routes.expired) ||
+ !l_queue_isempty(nc->addresses.updated))
netconfig_emit_event(nc, AF_INET6, L_NETCONFIG_EVENT_UPDATE);
}
@@ -1706,6 +1868,8 @@ configure_ipv6:
goto done;
}
+ netconfig->v6_auto_method = NETCONFIG_V6_METHOD_UNSET;
+
/*
* We only care about being on addr_wait_list if we're waiting for
* the link-local address for DHCP6. Add ourself to the list here
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread