2
* ndisc.c - ICMPv6 neighbour discovery command line tool
3
* $Id: ndisc.c 355 2006-10-07 15:02:56Z remi $
6
/***********************************************************************
7
* Copyright (C) 2004-2006 Rémi Denis-Courmont. *
8
* This program is free software; you can redistribute and/or modify *
9
* it under the terms of the GNU General Public License as published *
10
* by the Free Software Foundation; version 2 of the license. *
12
* This program is distributed in the hope that it will be useful, *
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
15
* See the GNU General Public License for more details. *
17
* You should have received a copy of the GNU General Public License *
18
* along with this program; if not, you can get it from: *
19
* http://www.gnu.org/copyleft/gpl.html *
20
***********************************************************************/
28
#include <stdlib.h> /* div() */
30
#include <limits.h> /* UINT_MAX */
33
#include <errno.h> /* EMFILE */
34
#include <sys/types.h>
35
#include <unistd.h> /* close() */
36
#include <time.h> /* clock_gettime() */
37
#include <poll.h> /* poll() */
38
#include <sys/socket.h>
48
#include <netdb.h> /* getaddrinfo() */
49
#include <arpa/inet.h> /* inet_ntop() */
50
#include <net/if.h> /* if_nametoindex() */
52
#include <netinet/in.h>
53
#include <netinet/icmp6.h>
55
#ifndef IPV6_RECVHOPLIMIT
56
/* Using obsolete RFC 2292 instead of RFC 3542 */
57
# define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT
66
# define ND_TYPE_ADVERT ND_NEIGHBOR_ADVERT
67
# define TYPE_NAME "Neighbor"
68
# define NDISC_DEFAULT (NDISC_VERBOSE1 | NDISC_SINGLE)
70
# include <sys/ioctl.h>
74
# define ND_TYPE_ADVERT ND_ROUTER_ADVERT
75
# define TYPE_NAME "Router"
76
# define NDISC_DEFAULT NDISC_VERBOSE1
92
getipv6byname (const char *name, const char *ifname, int numeric,
93
struct sockaddr_in6 *addr)
95
struct addrinfo hints, *res;
96
memset (&hints, 0, sizeof (hints));
97
hints.ai_family = PF_INET6;
98
hints.ai_socktype = SOCK_DGRAM; /* dummy */
99
hints.ai_flags = numeric ? AI_NUMERICHOST : 0;
101
int val = getaddrinfo (name, NULL, &hints, &res);
104
fprintf (stderr, _("%s: %s\n"), name, gai_strerror (val));
108
memcpy (addr, res->ai_addr, sizeof (struct sockaddr_in6));
111
val = if_nametoindex (ifname);
117
addr->sin6_scope_id = val;
124
setmcasthoplimit (int fd, int value)
126
return setsockopt (fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
127
&value, sizeof (value));
132
printmacaddress (const uint8_t *ptr, size_t len)
136
printf ("%02X:", *ptr);
142
printf ("%02X\n", *ptr);
148
getmacaddress (const char *ifname, uint8_t *addr)
150
# ifdef SIOCGIFHWADDR
152
memset (&req, 0, sizeof (req));
154
if (((unsigned)strlen (ifname)) >= (unsigned)IFNAMSIZ)
155
return -1; /* buffer overflow = local root */
156
strcpy (req.ifr_name, ifname);
158
int fd = socket (AF_INET6, SOCK_DGRAM, 0);
162
if (ioctl (fd, SIOCGIFHWADDR, &req))
170
memcpy (addr, req.ifr_hwaddr.sa_data, 6);
181
struct nd_neighbor_solicit hdr;
182
struct nd_opt_hdr opt;
187
buildsol (solicit_packet *ns, struct sockaddr_in6 *tgt, const char *ifname)
189
/* builds ICMPv6 Neighbor Solicitation packet */
190
ns->hdr.nd_ns_type = ND_NEIGHBOR_SOLICIT;
191
ns->hdr.nd_ns_code = 0;
192
ns->hdr.nd_ns_cksum = 0; /* computed by the kernel */
193
ns->hdr.nd_ns_reserved = 0;
194
memcpy (&ns->hdr.nd_ns_target, &tgt->sin6_addr, 16);
196
/* determines actual multicast destination address */
197
memcpy (&tgt->sin6_addr.s6_addr, "\xff\x02\x00\x00\x00\x00\x00\x00"
198
"\x00\x00\x00\x01\xff", 13);
200
/* gets our own interface's link-layer address (MAC) */
201
if (getmacaddress (ifname, ns->hw_addr))
202
return sizeof (ns->hdr);
204
ns->opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR;
205
ns->opt.nd_opt_len = 1; /* 8 bytes */
211
parseadv (const uint8_t *buf, size_t len, const struct sockaddr_in6 *tgt,
214
const struct nd_neighbor_advert *na =
215
(const struct nd_neighbor_advert *)buf;
218
/* checks if the packet is a Neighbor Advertisement, and
219
* if the target IPv6 address is the right one */
220
if ((len < sizeof (struct nd_neighbor_advert))
221
|| (na->nd_na_type != ND_NEIGHBOR_ADVERT)
222
|| (na->nd_na_code != 0)
223
|| memcmp (&na->nd_na_target, &tgt->sin6_addr, 16))
226
len -= sizeof (struct nd_neighbor_advert);
228
/* looks for Target Link-layer address option */
229
ptr = buf + sizeof (struct nd_neighbor_advert);
235
optlen = ((uint16_t)(ptr[1])) << 3;
237
break; /* invalid length */
239
if (len < optlen) /* length > remaining bytes */
244
/* skips unrecognized option */
245
if (ptr[0] != ND_OPT_TARGET_LINKADDR)
251
/* Found! displays link-layer address */
255
fputs (_("Target link-layer address: "), stdout);
257
printmacaddress (ptr, optlen);
264
typedef struct nd_router_solicit solicit_packet;
267
buildsol (solicit_packet *rs)
269
/* builds ICMPv6 Router Solicitation packet */
270
rs->nd_rs_type = ND_ROUTER_SOLICIT;
272
rs->nd_rs_cksum = 0; /* computed by the kernel */
273
rs->nd_rs_reserved = 0;
279
parseprefix (const struct nd_opt_prefix_info *pi, size_t optlen, int verbose)
281
char str[INET6_ADDRSTRLEN];
283
if (optlen < sizeof (*pi))
286
/* displays prefix informations */
287
if (inet_ntop (AF_INET6, &pi->nd_opt_pi_prefix, str,
288
sizeof (str)) == NULL)
292
fputs (_(" Prefix : "), stdout);
293
printf ("%s/%u\n", str, pi->nd_opt_pi_prefix_len);
297
/* INET6_ADDRSTRLEN > 13 */
300
fputs (_(" Valid time : "), stdout);
301
v = ntohl (pi->nd_opt_pi_valid_time);
303
fputs (_(" infinite (0xffffffff)\n"), stdout);
305
printf (_("%12u (0x%08x) %s\n"),
306
v, v, ngettext ("second", "seconds", v));
308
fputs (_(" Pref. time : "), stdout);
309
v = ntohl (pi->nd_opt_pi_preferred_time);
311
fputs (_(" infinite (0xffffffff)\n"), stdout);
313
printf (_("%12u (0x%08x) %s\n"),
314
v, v, ngettext ("second", "seconds", v));
321
parsemtu (const struct nd_opt_mtu *m)
323
unsigned mtu = ntohl (m->nd_opt_mtu_mtu);
325
fputs (_(" MTU : "), stdout);
326
printf (" %5u %s (%s)\n", mtu,
327
ngettext ("byte", "bytes", mtu),
328
gettext((mtu >= 1280) ? N_("valid") : N_("invalid")));
333
parseadv (const uint8_t *buf, size_t len, int verbose)
335
const struct nd_router_advert *ra =
336
(const struct nd_router_advert *)buf;
339
/* checks if the packet is a Router Advertisement */
340
if ((len < sizeof (struct nd_router_advert))
341
|| (ra->nd_ra_type != ND_ROUTER_ADVERT)
342
|| (ra->nd_ra_code != 0))
351
"Hop limit : "), stdout);
352
v = ra->nd_ra_curhoplimit;
354
printf (_(" %3u"), v);
356
fputs (_("undefined"), stdout);
357
printf (_(" ( 0x%02x)\n"), v);
359
/* Router lifetime */
360
fputs (_("Router lifetime : "), stdout);
361
v = ntohs (ra->nd_ra_router_lifetime);
362
printf (_("%12u (0x%08x) %s\n"), v, v,
363
ngettext ("millisecond", "milliseconds", v));
365
/* ND Reachable time */
366
fputs (_("Reachable time : "), stdout);
367
v = ntohl (ra->nd_ra_reachable);
369
printf (_("%12u (0x%08x) %s\n"), v, v,
370
ngettext ("millisecond", "milliseconds", v));
372
fputs (_(" unspecified (0x00000000)\n"), stdout);
374
/* ND Retransmit time */
375
fputs (_("Retransmit time : "), stdout);
376
v = ntohl (ra->nd_ra_retransmit);
378
printf (_("%12u (0x%08x) %s\n"), v, v,
379
ngettext ("millisecond", "milliseconds", v));
381
fputs (_(" unspecified (0x00000000)\n"), stdout);
383
len -= sizeof (struct nd_router_advert);
386
ptr = buf + sizeof (struct nd_router_advert);
392
optlen = ((uint16_t)(ptr[1])) << 3;
393
if ((optlen == 0) /* invalid length */
394
|| (len < optlen) /* length > remaining bytes */)
399
/* skips unrecognized option */
402
case ND_OPT_SOURCE_LINKADDR:
405
fputs (" Source link-layer address: ", stdout);
406
printmacaddress (ptr + 2, optlen - 2);
410
case ND_OPT_TARGET_LINKADDR:
413
case ND_OPT_PREFIX_INFORMATION:
414
if (parseprefix ((const struct nd_opt_prefix_info *)ptr,
418
case ND_OPT_REDIRECTED_HEADER:
423
parsemtu ((const struct nd_opt_mtu *)ptr);
433
# define buildsol( a, b, c ) buildsol (a)
434
# define parseadv( a, b, c, d ) parseadv (a, b, d)
439
recvfromLL (int fd, void *buf, size_t len, int flags,
440
struct sockaddr_in6 *addr)
442
char cbuf[CMSG_SPACE (sizeof (int))];
451
.msg_namelen = sizeof (*addr),
455
.msg_controllen = sizeof (cbuf)
458
ssize_t val = recvmsg (fd, &hdr, flags);
462
/* ensures the hop limit is 255 */
463
for (struct cmsghdr *cmsg = CMSG_FIRSTHDR (&hdr);
465
cmsg = CMSG_NXTHDR (&hdr, cmsg))
467
if ((cmsg->cmsg_level == IPPROTO_IPV6)
468
&& (cmsg->cmsg_type == IPV6_HOPLIMIT))
470
if (255 != *(int *)CMSG_DATA (cmsg))
472
// pretend to be a spurious wake-up
484
recvadv (int fd, const struct sockaddr_in6 *tgt, unsigned wait_ms,
487
struct timespec now, end;
488
unsigned responses = 0;
490
/* computes deadline time */
495
d = div (wait_ms, 1000);
496
end.tv_sec = now.tv_sec + d.quot;
497
end.tv_nsec = now.tv_nsec + (d.rem * 1000000);
503
/* waits for reply until deadline */
505
if (end.tv_sec >= now.tv_sec)
507
val = (end.tv_sec - now.tv_sec) * 1000
508
+ (int)((end.tv_nsec - now.tv_nsec) / 1000000);
513
val = poll (&(struct pollfd){ .fd = fd, .events = POLLIN }, 1, val);
520
/* receives an ICMPv6 packet */
521
// TODO: use interface MTU as buffer size
523
struct sockaddr_in6 addr;
525
val = recvfromLL (fd, buf, sizeof (buf), MSG_DONTWAIT, &addr);
529
perror (_("Receiving ICMPv6 packet"));
533
/* ensures the response came through the right interface */
534
if (addr.sin6_scope_id
535
&& (addr.sin6_scope_id != tgt->sin6_scope_id))
538
if (parseadv (buf, val, tgt, flags & NDISC_VERBOSE) == 0)
540
if (flags & NDISC_VERBOSE)
542
char str[INET6_ADDRSTRLEN];
544
if (inet_ntop (AF_INET6, &addr.sin6_addr, str,
545
sizeof (str)) != NULL)
546
printf (_(" from %s\n"), str);
549
if (responses < INT_MAX)
552
if (flags & NDISC_SINGLE)
553
return 1 /* = responses */;
558
return -1; /* error */
565
ndisc (const char *name, const char *ifname, unsigned flags, unsigned retry,
568
struct sockaddr_in6 tgt;
572
perror (_("Raw IPv6 socket"));
576
fcntl (fd, F_SETFD, FD_CLOEXEC);
578
/* set ICMPv6 filter */
580
struct icmp6_filter f;
582
ICMP6_FILTER_SETBLOCKALL (&f);
583
ICMP6_FILTER_SETPASS (ND_TYPE_ADVERT, &f);
584
setsockopt (fd, IPPROTO_ICMPV6, ICMP6_FILTER, &f, sizeof (f));
587
setsockopt (fd, SOL_SOCKET, SO_DONTROUTE, &(int){ 1 }, sizeof (int));
589
/* sets Hop-by-hop limit to 255 */
590
setmcasthoplimit (fd, 255);
591
setsockopt (fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
592
&(int){ 1 }, sizeof (int));
594
/* resolves target's IPv6 address */
595
if (getipv6byname (name, ifname, (flags & NDISC_NUMERIC) ? 1 : 0, &tgt))
599
char s[INET6_ADDRSTRLEN];
601
inet_ntop (AF_INET6, &tgt.sin6_addr, s, sizeof (s));
602
if (flags & NDISC_VERBOSE)
603
printf (_("Soliciting %s (%s) on %s...\n"), name, s, ifname);
607
solicit_packet packet;
608
struct sockaddr_in6 dst;
611
memcpy (&dst, &tgt, sizeof (dst));
612
plen = buildsol (&packet, &dst, ifname);
618
/* sends a Solitication */
619
if (sendto (fd, &packet, plen, 0,
620
(const struct sockaddr *)&dst,
621
sizeof (dst)) != plen)
623
perror (_("Sending ICMPv6 packet"));
628
/* receives an Advertisement */
629
ssize_t val = recvadv (fd, &tgt, wait_ms, flags);
638
if (flags & NDISC_VERBOSE)
639
puts (_("Timed out."));
647
if (flags & NDISC_VERBOSE)
648
puts (_("No response."));
658
quick_usage (const char *path)
660
fprintf (stderr, _("Try \"%s -h\" for more information.\n"), path);
666
usage (const char *path)
670
_("Usage: %s [options] <IPv6 address> <interface>\n"
671
"Looks up an on-link IPv6 node link-layer address (Neighbor Discovery)\n")
673
_("Usage: %s [options] [IPv6 address] <interface>\n"
674
"Solicits on-link IPv6 routers (Router Discovery)\n")
679
" -1, --single display first response and exit\n"
680
" -h, --help display this help and exit\n"
681
" -m, --multiple wait and display all responses\n"
682
" -n, --numeric don't resolve host names\n"
683
" -q, --quiet only print the %s (mainly for scripts)\n"
684
" -r, --retry maximum number of attempts (default: 3)\n"
685
" -V, --version display program version and exit\n"
686
" -v, --verbose verbose display (this is the default)\n"
687
" -w, --wait how long to wait for a response [ms] (default: 1000)\n"
690
_("link-layer address")
692
_("advertised prefixes")
704
NAME"6: IPv6 "TYPE_NAME" Discovery userland tool %s ($Rev: 355 $)\n"
705
" built %s on %s\n"), VERSION, __DATE__, PACKAGE_BUILD_HOSTNAME);
706
printf (_("Configured with: %s\n"), PACKAGE_CONFIGURE_INVOCATION);
707
puts (_("Written by Remi Denis-Courmont\n"));
709
printf (_("Copyright (C) %u-%u Remi Denis-Courmont\n"
710
"This is free software; see the source for copying conditions.\n"
711
"There is NO warranty; not even for MERCHANTABILITY or\n"
712
"FITNESS FOR A PARTICULAR PURPOSE.\n"), 2004, 2006);
718
static const struct option opts[] =
720
{ "single", no_argument, NULL, '1' },
721
{ "help", no_argument, NULL, 'h' },
722
{ "multiple", required_argument, NULL, 'm' },
723
{ "numeric", no_argument, NULL, 'n' },
724
{ "quiet", no_argument, NULL, 'q' },
725
{ "retry", required_argument, NULL, 'r' },
726
{ "version", no_argument, NULL, 'V' },
727
{ "verbose", no_argument, NULL, 'v' },
728
{ "wait", required_argument, NULL, 'w' },
734
main (int argc, char *argv[])
736
fd = socket (PF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
739
/* Drops root privileges (if setuid not run by root).
740
* Also make sure the socket is not STDIN/STDOUT/STDERR. */
741
if (setuid (getuid ()) || ((fd >= 0) && (fd <= 2)))
744
setlocale (LC_CTYPE, "");
747
unsigned retry = 3, flags = NDISC_DEFAULT, wait_ms = 1000;
748
const char *hostname, *ifname;
750
while ((val = getopt_long (argc, argv, "1hmnqr:Vvw:", opts, NULL)) != EOF)
755
flags |= NDISC_SINGLE;
759
return usage (argv[0]);
762
flags &= ~NDISC_SINGLE;
766
flags |= NDISC_NUMERIC;
770
flags &= ~NDISC_VERBOSE;
778
l = strtoul (optarg, &end, 0);
779
if (*end || l > UINT_MAX)
780
return quick_usage (argv[0]);
789
/* NOTE: assume NDISC_VERBOSE occupies low-order bits */
790
if ((flags & NDISC_VERBOSE) < NDISC_VERBOSE)
799
l = strtoul (optarg, &end, 0);
800
if (*end || l > UINT_MAX)
801
return quick_usage (argv[0]);
808
return quick_usage (argv[0]);
814
hostname = argv[optind++];
817
ifname = argv[optind++];
822
return quick_usage (argv[0]);
828
hostname = "ff02::2";
832
if ((optind != argc) || (ifname == NULL))
833
return quick_usage (argv[0]);
835
errno = errval; /* restore socket() error value */
836
return -ndisc (hostname, ifname, flags, retry, wait_ms);