Skip to content

Commit 88d5a3d

Browse files
committed
sd-radv: Receive Router Solicitations
Receive Router Solicitations and send a unicast Router Advertisment in response. Refactor ICMPv6 packet handling code so that the common ICMPv6 validation parts are reused between the existing router discovery and the new functionality adding reception of Router Solicitation messages.
1 parent 77baf5a commit 88d5a3d

File tree

6 files changed

+179
-75
lines changed

6 files changed

+179
-75
lines changed

src/libsystemd-network/icmp6-util.c

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "fd-util.h"
3333
#include "icmp6-util.h"
3434
#include "socket-util.h"
35+
#include "in-addr-util.h"
3536

3637
#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \
3738
{ { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
@@ -164,3 +165,74 @@ int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
164165

165166
return 0;
166167
}
168+
169+
int icmp6_receive(int fd, void *buffer, size_t size, struct in6_addr *dst,
170+
triple_timestamp *timestamp) {
171+
union {
172+
struct cmsghdr cmsghdr;
173+
uint8_t buf[CMSG_SPACE(sizeof(int)) + /* ttl */
174+
CMSG_SPACE(sizeof(struct timeval))];
175+
} control = {};
176+
struct iovec iov = {};
177+
union sockaddr_union sa = {};
178+
struct msghdr msg = {
179+
.msg_name = &sa.sa,
180+
.msg_namelen = sizeof(sa),
181+
.msg_iov = &iov,
182+
.msg_iovlen = 1,
183+
.msg_control = &control,
184+
.msg_controllen = sizeof(control),
185+
};
186+
struct cmsghdr *cmsg;
187+
ssize_t len;
188+
189+
iov.iov_base = buffer;
190+
iov.iov_len = size;
191+
192+
len = recvmsg(fd, &msg, MSG_DONTWAIT);
193+
if (len < 0) {
194+
if (errno == EAGAIN || errno == EINTR)
195+
return 0;
196+
197+
return -errno;
198+
}
199+
200+
if ((size_t) len != size)
201+
return -EINVAL;
202+
203+
if (msg.msg_namelen == sizeof(struct sockaddr_in6) &&
204+
sa.in6.sin6_family == AF_INET6) {
205+
206+
*dst = sa.in6.sin6_addr;
207+
if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) dst) <= 0)
208+
return -EADDRNOTAVAIL;
209+
210+
} else if (msg.msg_namelen > 0)
211+
return -EPFNOSUPPORT;
212+
213+
/* namelen == 0 only happens when running the test-suite over a socketpair */
214+
215+
assert(!(msg.msg_flags & MSG_CTRUNC));
216+
assert(!(msg.msg_flags & MSG_TRUNC));
217+
218+
CMSG_FOREACH(cmsg, &msg) {
219+
if (cmsg->cmsg_level == SOL_IPV6 &&
220+
cmsg->cmsg_type == IPV6_HOPLIMIT &&
221+
cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
222+
int hops = *(int*) CMSG_DATA(cmsg);
223+
224+
if (hops != 255)
225+
return -EMULTIHOP;
226+
}
227+
228+
if (cmsg->cmsg_level == SOL_SOCKET &&
229+
cmsg->cmsg_type == SO_TIMESTAMP &&
230+
cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
231+
triple_timestamp_from_realtime(timestamp, timeval_load((struct timeval*) CMSG_DATA(cmsg)));
232+
}
233+
234+
if (!triple_timestamp_is_set(timestamp))
235+
triple_timestamp_get(timestamp);
236+
237+
return 0;
238+
}

src/libsystemd-network/icmp6-util.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
#include <net/ethernet.h>
2323

24+
#include "time-util.h"
25+
2426
#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \
2527
{ { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
2628
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } }
@@ -32,3 +34,5 @@
3234
int icmp6_bind_router_solicitation(int index);
3335
int icmp6_bind_router_advertisement(int index);
3436
int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr);
37+
int icmp6_receive(int fd, void *buffer, size_t size, struct in6_addr *dst,
38+
triple_timestamp *timestamp);

src/libsystemd-network/radv-internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ struct sd_radv {
5858

5959
int fd;
6060
unsigned ra_sent;
61+
sd_event_source *recv_event_source;
6162
sd_event_source *timeout_event_source;
6263

6364
unsigned n_prefixes;

src/libsystemd-network/sd-ndisc.c

Lines changed: 20 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -222,23 +222,9 @@ static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
222222
static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
223223
_cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
224224
sd_ndisc *nd = userdata;
225-
union {
226-
struct cmsghdr cmsghdr;
227-
uint8_t buf[CMSG_SPACE(sizeof(int)) + /* ttl */
228-
CMSG_SPACE(sizeof(struct timeval))];
229-
} control = {};
230-
struct iovec iov = {};
231-
union sockaddr_union sa = {};
232-
struct msghdr msg = {
233-
.msg_name = &sa.sa,
234-
.msg_namelen = sizeof(sa),
235-
.msg_iov = &iov,
236-
.msg_iovlen = 1,
237-
.msg_control = &control,
238-
.msg_controllen = sizeof(control),
239-
};
240-
struct cmsghdr *cmsg;
241-
ssize_t len, buflen;
225+
ssize_t buflen;
226+
int r;
227+
_cleanup_free_ char *addr = NULL;
242228

243229
assert(s);
244230
assert(nd);
@@ -252,66 +238,27 @@ static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userda
252238
if (!rt)
253239
return -ENOMEM;
254240

255-
iov.iov_base = NDISC_ROUTER_RAW(rt);
256-
iov.iov_len = rt->raw_size;
257-
258-
len = recvmsg(fd, &msg, MSG_DONTWAIT);
259-
if (len < 0) {
260-
if (errno == EAGAIN || errno == EINTR)
261-
return 0;
262-
263-
return log_ndisc_errno(errno, "Could not receive message from ICMPv6 socket: %m");
264-
}
265-
266-
if ((size_t) len != rt->raw_size) {
267-
log_ndisc("Packet size mismatch.");
268-
return -EINVAL;
269-
}
270-
271-
if (msg.msg_namelen == sizeof(struct sockaddr_in6) &&
272-
sa.in6.sin6_family == AF_INET6) {
273-
274-
if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr) <= 0) {
275-
_cleanup_free_ char *addr = NULL;
276-
277-
(void) in_addr_to_string(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr, &addr);
278-
log_ndisc("Received RA from non-link-local address %s. Ignoring.", strna(addr));
279-
return 0;
280-
}
281-
282-
rt->address = sa.in6.sin6_addr;
283-
284-
} else if (msg.msg_namelen > 0) {
285-
log_ndisc("Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t) msg.msg_namelen);
286-
return -EINVAL;
287-
}
288-
289-
/* namelen == 0 only happens when running the test-suite over a socketpair */
290-
291-
assert(!(msg.msg_flags & MSG_CTRUNC));
292-
assert(!(msg.msg_flags & MSG_TRUNC));
293-
294-
CMSG_FOREACH(cmsg, &msg) {
295-
if (cmsg->cmsg_level == SOL_IPV6 &&
296-
cmsg->cmsg_type == IPV6_HOPLIMIT &&
297-
cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
298-
int hops = *(int*) CMSG_DATA(cmsg);
299-
300-
if (hops != 255) {
301-
log_ndisc("Received RA with invalid hop limit %d. Ignoring.", hops);
302-
return 0;
303-
}
241+
r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address,
242+
&rt->timestamp);
243+
if (r < 0) {
244+
switch (r) {
245+
case -EADDRNOTAVAIL:
246+
(void) in_addr_to_string(AF_INET6, (union in_addr_union*) &rt->address, &addr);
247+
log_ndisc("Received RA from non-link-local address %s. Ignoring", addr);
248+
break;
249+
250+
case -EMULTIHOP:
251+
log_ndisc("Received RA with invalid hop limit. Ignoring.");
252+
break;
253+
254+
case -EPFNOSUPPORT:
255+
log_ndisc("Received invalid source address from ICMPv6 socket.");
256+
break;
304257
}
305258

306-
if (cmsg->cmsg_level == SOL_SOCKET &&
307-
cmsg->cmsg_type == SO_TIMESTAMP &&
308-
cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
309-
triple_timestamp_from_realtime(&rt->timestamp, timeval_load((struct timeval*) CMSG_DATA(cmsg)));
259+
return 0;
310260
}
311261

312-
if (!triple_timestamp_is_set(&rt->timestamp))
313-
triple_timestamp_get(&rt->timestamp);
314-
315262
nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
316263

317264
return ndisc_handle_datagram(nd, rt);

src/libsystemd-network/sd-radv.c

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ static void radv_reset(sd_radv *ra) {
9393
ra->timeout_event_source =
9494
sd_event_source_unref(ra->timeout_event_source);
9595

96+
ra->recv_event_source =
97+
sd_event_source_unref(ra->recv_event_source);
98+
9699
ra->ra_sent = 0;
97100
}
98101

@@ -160,8 +163,9 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst,
160163
.msg_iov = iov,
161164
};
162165

163-
if (dst)
166+
if (dst && !in_addr_is_null(AF_INET6, (union in_addr_union*) dst))
164167
dst_addr.sin6_addr = *dst;
168+
165169
adv.nd_ra_type = ND_ROUTER_ADVERT;
166170
adv.nd_ra_curhoplimit = ra->hop_limit;
167171
adv.nd_ra_flags_reserved = ra->flags;
@@ -198,6 +202,63 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst,
198202
return 0;
199203
}
200204

205+
static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
206+
sd_radv *ra = userdata;
207+
_cleanup_free_ char *addr = NULL;
208+
struct in6_addr src;
209+
triple_timestamp timestamp;
210+
int r;
211+
ssize_t buflen;
212+
_cleanup_free_ char *buf = NULL;
213+
214+
assert(s);
215+
assert(ra);
216+
assert(ra->event);
217+
218+
buflen = next_datagram_size_fd(fd);
219+
220+
if ((unsigned) buflen < sizeof(struct nd_router_solicit))
221+
return log_radv("Too short packet received");
222+
223+
buf = new0(char, buflen);
224+
if (!buf)
225+
return 0;
226+
227+
r = icmp6_receive(fd, buf, buflen, &src, &timestamp);
228+
if (r < 0) {
229+
switch (r) {
230+
case -EADDRNOTAVAIL:
231+
(void) in_addr_to_string(AF_INET6, (union in_addr_union*) &src, &addr);
232+
log_radv("Received RS from non-link-local address %s. Ignoring", addr);
233+
break;
234+
235+
case -EMULTIHOP:
236+
log_radv("Received RS with invalid hop limit. Ignoring.");
237+
break;
238+
239+
case -EPFNOSUPPORT:
240+
log_radv("Received invalid source address from ICMPv6 socket. Ignoring.");
241+
break;
242+
243+
default:
244+
log_radv_warning_errno(r, "Error receiving from ICMPv6 socket: %m");
245+
break;
246+
}
247+
248+
return 0;
249+
}
250+
251+
(void) in_addr_to_string(AF_INET6, (union in_addr_union*) &src, &addr);
252+
253+
r = radv_send(ra, &src, ra->lifetime);
254+
if (r < 0)
255+
log_radv_warning_errno(r, "Unable to send solicited Router Advertisment to %s: %m", addr);
256+
else
257+
log_radv("Sent solicited Router Advertisement to %s", addr);
258+
259+
return 0;
260+
}
261+
201262
static usec_t radv_compute_timeout(usec_t min, usec_t max) {
202263
assert_return(min <= max, SD_RADV_DEFAULT_MIN_TIMEOUT_USEC);
203264

@@ -313,7 +374,16 @@ _public_ int sd_radv_start(sd_radv *ra) {
313374
goto fail;
314375

315376
ra->fd = r;
316-
r = 0;
377+
378+
r = sd_event_add_io(ra->event, &ra->recv_event_source, ra->fd, EPOLLIN, radv_recv, ra);
379+
if (r < 0)
380+
goto fail;
381+
382+
r = sd_event_source_set_priority(ra->recv_event_source, ra->event_priority);
383+
if (r < 0)
384+
goto fail;
385+
386+
(void) sd_event_source_set_description(ra->recv_event_source, "radv-receive-message");
317387

318388
ra->state = SD_RADV_STATE_ADVERTISING;
319389

src/libsystemd-network/test-ndisc-rs.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,16 @@ int icmp6_bind_router_advertisement(int index) {
198198
return -ENOSYS;
199199
}
200200

201+
int icmp6_receive(int fd, void *iov_base, size_t iov_len,
202+
struct in6_addr *dst, triple_timestamp *timestamp) {
203+
assert (read (fd, iov_base, iov_len) == (ssize_t)iov_len);
204+
205+
if (timestamp)
206+
triple_timestamp_get(timestamp);
207+
208+
return 0;
209+
}
210+
201211
static int send_ra(uint8_t flags) {
202212
uint8_t advertisement[] = {
203213
0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4,

0 commit comments

Comments
 (0)