Download raw body.
add source link-layer address option to rad(8)
On Fri, May 10, 2024 at 07:36:28PM +0000, Florian Obser wrote:
> Hi,
>
> On 2024-05-07 16:15 -07, Ryan Vogt <rvogt.ca@gmail.com> wrote:
> > Hi tech@,
> >
> > This is my first time sending a proposed patch, so hello everyone.
> > This patch adds support to rad(8) for the source link-layer address
> > option in router advertisement messages (RFC 4861, section 4.2).
>
> awesome, we certainly need more people who are willing to roll around in
> IPv6.
Thank you so much for taking the time to write such a detailed message
back to me, and taking the time to give so much feedback.
I've inlined some more information below, and attached a new revision
of the proposed patch where I've tried to best incorporate your
feedback (with a little asterisk on one detail where there were two
different designs to choose from).
> > Motivation: if an RA message includes a DNS option (RDNSS, DNSSL, or
> > both), but the RA message doesn't include the source link-layer
> > address option, Apple clients (macOS, iOS, etc.) in an IPv6-only
> > environment will misbehave. I'm more than happy to share the details
> > of how they misbehave if anyone would like, but I'll leave it at
> > "misbehave" here for brevity.
>
> I'm interested. Might also be worthwhile to have it in the
> archives.
Sure thing! I'll give a Readers' Digest version here -- hopefully
enough for someone to find with a search of the archvies, and enough
for someone to reproduce the issue. But again, I'm more than happy to
expand on the details.
The misbehaviour can happen in IPv4-IPv6 mixed environments (it's very
easy to get an iPhone to misbehave there), but it's easier to get at
the underlying issue in an IPv6-only environment in macOS. I was
actually oversimplifying things to say the issue is IPv6-only (so I
didn't go off on a tangent in my first email), but since Zack (from
another reply) mentioned the iPhone issue, I'll include those details,
too.
I'm going to interject with one detail that may or may not be
important to the reproduction: in between my OpenBSD gateway running
rad(8) and my macOS / iOS cients, I have a wireless base station
acting in bridge mode. Definitely no router advertisements, etc.,
coming from that wireless bridge. But, I don't have the physical
equipment to test if the problem occurs without that wireless bridge
(I don't have a macOS machine with an ethernet port).
Anyhow, dead simple reproduction on an iPhone, and I think Zack might
have run into this issue (?). Have dhcpd(8) serving IPv4 addresses to
your internal network and have rad(8) in charge of the IPv6 side of
things. Use an rad.conf that looks something like:
# I used a private nameserver here, but you could also use
# Google's or a similar public nameserver with the same result
# Feel free to omit the DNSSL and leave only the RDNSS.
dns {
nameserver fdaa:aaaa:aaaa:1::1
search myhomedomain.internal
}
interface em0
Reboot your iPhone (getting it on a fresh boot is the easiest way to
trigger the problem). Your phone will show the little concentric
semicircles to say, "I'm connected to your WiFi". But, aside from a
few packets after boot, the phone never actually sends anything else
over WiFi, and you're left with a non-functional connection. You have
to manually disconnect the iPhone from WiFi (e.g., airplane mode, or
flip WiFi off) then reconnect to WiFi (airplane mode off / WiFi back
on) after a phone reboot to get a working connection.
If there are no DNS options in rad.conf(5), there's no problem (DNS
happens seemlessly over IPv4, and the device uses it's IPv6 address
from rad(8)). But, add the DNS options, and the problem appears.
But now, add the source link-layer address option into rad(8)'s RA
messages in addition to the DNS option, and the problem is solved. The
iOS device behaves properly (actually connects to WiFi and uses it on
a reboot, and uses the DNS instructions provided by rad(8)).
On macOS, to see more of what's happening (probably (?) shining some
light into what's happening on the phone), set your macOS TCP/IP
settings to use IPv6 only (completely disable IPv4). Then, reboot --
that's important, because once IPv4 traffic gets flowing in macOS, the
problem generally seems to vanish. So, we want a fresh boot where no
IPv4 traffic has happened.
To recap our situation, we have a fresh-boot of macOS with IPv4
completely disabled, rad.conf(5) including DNS options, but
rad(8) not sending the source link-level address option.
Now, despite having a perfectly good IPv6 router and a functional IPv6
nameserver, a similar situation occurs in macOS as to what happens in
iOS: a few packets go over the WiFi connection, then nothing. The
culprit seems to be that macOS' deault route is in interface scope:
$ netstat -rn
[...snip...]
Internet 6:
Destination Gateway Flags Netif
default fe80::...%en0 UGcIg en0
[...snip...]
That "I" flag is what I'm looking at. With the default route being in
interface scope, you can do things like:
$ ping6 fdaa:...:1%en0
but not:
$ ping6 fdaa:...:1
(Again, this could be a public IPv6 address like Google's IPv6 DNS
server address; same result.)
Understandably, with this interface-scope restriction in place,
nothing actually works on your computer (no applications actually have
a functional connection).
But, if rad(8)'s RA messages include the source link-level address
option, the default route doesn't go into interface scope, and the
computer works as expected in an IPv6-only environment (uses the DNS
server provided by rad(8), connects nicely to IPv6-only websites,
etc.)
> > Quick notes about the patch:
> >
> > 1. I avoided abbreviating "link-layer address" as "lladrr",
> > "linkaddr", etc., where possible, to avoid any confusion with link-
> > local addresses. So, while "linklayer_addr" is more verbose, I hope
> > it minimizes confusion.
>
> Please have a look at sbin/slaacd/frontend.c how it's handled there.
> We fetch the mac address in update_iface() and use it in
> send_solicitation().
>
> - please use similar variable names / structs
> - you can assume that all the world is ethernet and that link-layer
> addresses are available. Otherwise something is really wrong and
> rad(8) is the least of our problems.
Assuming that all the world is ethernet changed the nature of the
solution significantly. Thank you very much for that feedback, because
I had my head lost in the clouds reading the RFC (which allows for
arbitrary-length hardware addresses), instead of having my head
grounded in reality.
Thank you also for pointing me to slaacd/frontend.c. It was really
helpful to look at the precise details there.
But, I think there's a little hiccup in consistency between
rad/frontend.c and slaacd/frontend.c here. In rad/frontend.c, all of
the structs for the different RA options contain both the header (the
type and the length) and the body. For example:
struct nd_opt_route_info (from <netinet/icmp6.h>)
which is used in rad/frontend.c, contains header fields
nd_opt_rti_type and nd_opt_rti_len, then the body of the option
(prefix len, etc.).
struct nd_opt_route_info {
/* Header */
u_int8_t nd_opt_rti_type;
u_int8_t nd_opt_rti_len;
/* Body */
u_int8_t nd_opt_rti_prefixlen;
u_int8_t nd_opt_rti_flags;
u_int32_t nd_opt_rti_lifetime;
};
Similarly,
struct nd_opt_pref64 (from rad/frontend.c)
also contains the header (nd_opt_pref64_type and nd_opt_pref64_len)
and the body (nd_opt_p ref64_sltime_plc and nd_opt_pref64).
struct nd_opt_pref64 {
/* Header */
u_int8_t nd_opt_pref64_type;
u_int8_t nd_opt_pref64_len;
/* Body */
u_int16_t nd_opt_pref64_sltime_plc;
u_int8_t nd_opt_pref64[12];
};
I'll call that the rad/frontend.c style -- structs named nd_opt_[...]
which contain both the header and the body/data for each option.
On the other hand, in slaacd/frontend.c, the header and the data are
two different structs. There's a struct nd_opt_hdr, but also a
struct ether_addr named nd_opt_source_link_addr.
nd_opt_hdr nd_opt_hdr;
struct ether_addr nd_opt_source_link_addr;
Here in slaacd/frontend.c, it's the variable name for the body alone
that follows the naming convention of nd_opt_[...]. I'll call that the
slaacd/frontend.c style -- two separate structs, this time with the
body variable named nd_opt_[...].
The question is: which style should the proposed patch follow?
Knowing now that all the world is ethernet (no more arbitrary-length
body), I could define a struct in the rad/frontend.c style that holds
both the header and the body, just like the other structs in
rad/frontend.c. Call this Option 1:
struct nd_opt_source_link_addr {
/* Header */
u_int8_t nd_opt_source_link_addr_type;
u_int8_t nd_opt_source_link_addr_len;
/* Body */
struct ether_addr nd_opt_source_link_addr_hw_addr;
};
Then, in rad/frontend.c's build_packet(), I have a:
struct nd_opt_source_link_addr *ndopt_source_link_addr;
alongside the:
struct nd_opt_mtu *ndopt_mtu;
struct nd_opt_prefix_info *ndopt_prefix_info;
That uses the same name for the source link-layer address option as
slaacd/frontend.c (specifically, "nd_opt_source_link_addr"), but
follows the rad/frontend.c convention of putting the entirety of the
option (header and body) into a single struct.
In the alternative (call this Option 2), I could have used a header
and body that mimic slaacd/frontend.c exactly:
struct nd_opt_hdr *nd_opt_hdr;
struct ether_addr *nd_opt_source_link_addr;
struct nd_opt_mtu *ndopt_mtu;
struct nd_opt_prefix_info *ndopt_prefix_info;
In the end, I chose Option 1 -- making my proposed patch follow the
conventions of rad/frontend.c, despite the difference between that
and following slaacd/frontend.c exactly as in Option 2. That's how it
appears in the new version of the proposed patch below, but I'm more
than happy to flip it around to the other style (of making it match
slaacd/frontend.c exactly with Option 2).
> > 2. RFC 4861 section 4.2 states that the source link-layer address
> > option "MAY" be omitted, which might be read as meaning that it
> > should be included by default? That said, I didn't want to change
> > the default and current behaviour of rad(8) -- that is, not
> > including the option -- because excluding it does serve a purpose
> > (inbound load sharing, see RFC 4861). So, I made the default in
> > rad.conf(5) to exclude the source link-layer address option.
>
> I don't think you can get OpenBSD to support inbound load sharing like
> this. The default should be yes.
Changed :)
> This was an oversight by me to not implement it when rewriting
> rtadvd(8). I suspect it did not support this option either.
Just for the purposes of people searching the archives for the
equivalent configuration option between rtadvd.conf(5) and
rad.conf(5), the option in OpenBSD 6.3's rtadvd(8) was:
nolladdr
The proposed patch would make the equivalent in rad.conf(5):
source link-layer address no
My motivation for making the patch, to fix the macOS / iOS issue, was
to have the ability to do:
source link-layer address yes
(That's now the default in the proposed patch.)
> > 3. I used a "struct nd_opt_hdr" to hold the header for the source
> > link-layer address option, instead of making a new struct (e.g.,
> > "struct nd_opt_src_linklayer_addr"). I did that because there's
> > nothing in the source link-layer address option except the type,
> > length, and arbitrary-sized extra data (the arbitrarily sized link-
> > layer address), just how a "struct nd_opt_hdr" is defined in
> > <netinet/icmp6.h>. I could define a new struct instead if it would
> > help readability or consistency.
>
> yeah, that's fine, just a bit overcomplicated, see my response to 1.
>
> >
> > I'd be grateful for any feedback.
> >
>
> some more comments inline
I've attached a new version of the proposed patch underneath the
previous version.
A few quick notes:
1. In build_packet(), I've placed the struct pointers
nd_opt_source_link_addr, nd_opt_mtu, and nd_opt_prefix_info in the
order they're presented in RFC 4861 section 4.2.
2. I'm uncertain why we're doing a strcmp() against the name of the
interface while iterating over ifap, instead of comparing against
the interface index. But, since that's how it's done in
slaacd/frontend.c, where both the name and the index are also
available, I'm assuming there's a reason we use the name instead of
the index that I just don't know about. So, I switched to using a
strcmp() against the interface name instead of comparing against
the index.
3. Despite the assumption that all the world is ethernet, it feels
wrong to me to do the memcpy() into the struct ether_addr without
verifying that the address I'm copying is actually an ethernet
address of length ETHER_ADDR_LEN. If the check is extraneous and
unnecessary, I can easily remove it.
> > diff --git usr.sbin/rad/frontend.c usr.sbin/rad/frontend.c
> > index 6748e43732d..b8db63e02b6 100644
> > --- usr.sbin/rad/frontend.c
> > +++ usr.sbin/rad/frontend.c
> > @@ -56,6 +56,7 @@
> > #include <sys/uio.h>
> >
> > #include <net/if.h>
> > +#include <net/if_dl.h>
> > #include <net/if_types.h>
> > #include <net/route.h>
> >
> > @@ -128,6 +129,7 @@ void frontend_startup(void);
> > void icmp6_receive(int, short, void *);
> > void join_all_routers_mcast_group(struct ra_iface *);
> > void leave_all_routers_mcast_group(struct ra_iface *);
> > +struct sockaddr_dl *find_linklayer_sockaddr(struct ifaddrs *, int);
> > int get_link_state(char *);
> > int get_ifrdomain(char *);
> > void merge_ra_interface(char *, char *);
> > @@ -729,6 +731,23 @@ find_ra_iface_conf(struct ra_iface_conf_head *head, char *if_name)
> > return (NULL);
> > }
> >
> > +struct sockaddr_dl*
> > +find_linklayer_sockaddr(struct ifaddrs *ifap, int if_index)
> > +{
> > + struct ifaddrs *ifa;
> > +
> > + for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
> > + if (ifa->ifa_addr == NULL ||
> > + ifa->ifa_addr->sa_family != AF_LINK)
> > + continue;
> > + if (((struct sockaddr_dl *)ifa->ifa_addr)->sdl_index ==
> > + if_index)
> > + return (struct sockaddr_dl *)ifa->ifa_addr;
> > + }
> > + log_warn("find_linklayer_sockaddr");
>
> As said, you can assume that a link-layer address always exists and that
> it's ethernet. Please inline this into build packet and and copy the
> address into a struct ether_addr. then you don't need to hold on to ifap
> for such a long time.
>
> > + return (NULL);
> > +}
> > +
> > int
> > get_link_state(char *if_name)
> > {
> > @@ -1110,6 +1129,9 @@ build_packet(struct ra_iface *ra_iface)
> > struct ra_rdnss_conf *ra_rdnss;
> > struct ra_dnssl_conf *ra_dnssl;
> > struct ra_pref64_conf *pref64;
> > + struct nd_opt_hdr *ndopt_srclinklayeraddr;
> > + struct ifaddrs *ifap;
> > + struct sockaddr_dl *sa_linklayer;
> > size_t len, label_len;
> > uint8_t *p, buf[RA_MAX_SIZE];
> > char *label_start, *label_end;
> > @@ -1136,6 +1158,20 @@ build_packet(struct ra_iface *ra_iface)
> > entry)
> > len += sizeof(struct nd_opt_pref64);
> >
> > + ifap = NULL;
> > + sa_linklayer = NULL;
> > + if (ra_iface_conf->ra_options.src_linklayer_addr) {
> > + if (getifaddrs(&ifap) != 0)
> > + log_warn("getifaddrs");
> > + else
> > + sa_linklayer = find_linklayer_sockaddr(ifap,
> > + ra_iface->if_index);
> > + if (sa_linklayer != NULL)
> > + /* round up to 8 byte boundary */
> > + len += (sizeof(struct nd_opt_hdr) +
> > + sa_linklayer->sdl_alen + 7) & ~7;
> > + }
> > +
> > if (len > sizeof(ra_iface->data))
> > fatalx("%s: packet too big", __func__); /* XXX send multiple */
> >
> > @@ -1283,6 +1319,22 @@ build_packet(struct ra_iface *ra_iface)
> > p += sizeof(struct nd_opt_pref64);
> > }
> >
> > + if (sa_linklayer != NULL) {
> > + ndopt_srclinklayeraddr = (struct nd_opt_hdr *)p;
> > + ndopt_srclinklayeraddr->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
> > + /* length expressed in units of 8 octets */
> > + ndopt_srclinklayeraddr->nd_opt_len =
> > + ((sizeof(struct nd_opt_hdr) + sa_linklayer->sdl_alen + 7) &
> > + ~7) / 8;
> > + p += sizeof(struct nd_opt_hdr);
> > + memcpy(p, LLADDR(sa_linklayer), sa_linklayer->sdl_alen);
> > + p += sa_linklayer->sdl_alen;
> > + while (((uintptr_t)p) % 8 != 0)
> > + *p++ = '\0';
>
> memset(3) ;) but this goes away once you use struct ether_addr
>
> > + }
> > + if (ifap != NULL)
> > + freeifaddrs(ifap);
> > +
> > if (len != ra_iface->datalen || memcmp(buf, ra_iface->data, len)
> > != 0) {
> > memcpy(ra_iface->data, buf, len);
> > diff --git usr.sbin/rad/parse.y usr.sbin/rad/parse.y
> > index 66cc0c0ab64..fb9f4628854 100644
> > --- usr.sbin/rad/parse.y
> > +++ usr.sbin/rad/parse.y
> > @@ -122,6 +122,7 @@ typedef struct {
> > %token CONFIGURATION OTHER LIFETIME REACHABLE TIME RETRANS TIMER
> > %token AUTO PREFIX VALID PREFERENCE PREFERRED LIFETIME ONLINK AUTONOMOUS
> > %token ADDRESS_CONFIGURATION DNS NAMESERVER SEARCH MTU NAT64 HIGH MEDIUM LOW
> > +%token SOURCE LINK_LAYER
> >
> > %token <v.string> STRING
> > %token <v.number> NUMBER
> > @@ -226,6 +227,9 @@ ra_opt_block : DEFAULT ROUTER yesno {
> > | RETRANS TIMER NUMBER {
> > ra_options->retrans_timer = $3;
> > }
> > + | SOURCE LINK_LAYER ADDRESS yesno {
> > + ra_options->src_linklayer_addr = $4;
> > + }
> > | MTU NUMBER {
> > ra_options->mtu = $2;
> > }
> > @@ -523,6 +527,7 @@ lookup(char *s)
> > {"interface", RA_IFACE},
> > {"lifetime", LIFETIME},
> > {"limit", LIMIT},
> > + {"link-layer", LINK_LAYER},
> > {"low", LOW},
> > {"managed", MANAGED},
> > {"medium", MEDIUM},
> > @@ -539,6 +544,7 @@ lookup(char *s)
> > {"retrans", RETRANS},
> > {"router", ROUTER},
> > {"search", SEARCH},
> > + {"source", SOURCE},
> > {"time", TIME},
> > {"timer", TIMER},
> > {"valid", VALID},
> > diff --git usr.sbin/rad/printconf.c usr.sbin/rad/printconf.c
> > index 184a5df2dc1..4cf64fabd93 100644
> > --- usr.sbin/rad/printconf.c
> > +++ usr.sbin/rad/printconf.c
> > @@ -78,6 +78,8 @@ print_ra_options(const char *indent, const struct ra_options_conf *ra_options)
> > printf("%srouter lifetime %d\n", indent, ra_options->router_lifetime);
> > printf("%sreachable time %u\n", indent, ra_options->reachable_time);
> > printf("%sretrans timer %u\n", indent, ra_options->retrans_timer);
> > + printf("%ssource link-layer address %s\n", indent,
> > + yesno(ra_options->src_linklayer_addr));
> > if (ra_options->mtu > 0)
> > printf("%smtu %u\n", indent, ra_options->mtu);
> >
> > diff --git usr.sbin/rad/rad.conf.5 usr.sbin/rad/rad.conf.5
> > index f82ccfd216e..11545c1c23f 100644
> > --- usr.sbin/rad/rad.conf.5
> > +++ usr.sbin/rad/rad.conf.5
> > @@ -127,6 +127,10 @@ The default is medium.
> > .\" XXX
> > .\" .It Ic retrans timer Ar number
> > .\" XXX
> > +.It Ic source link-layer address Pq Ic yes Ns | Ns Ic no
> > +Add a source link-layer address option to router advertisement messages, to
> > +communicate the link-layer address of the sending interface.
> > +The default is no.
> > .El
> > .Sh INTERFACES
> > A list of interfaces or interface groups to send advertisements on:
> > diff --git usr.sbin/rad/rad.h usr.sbin/rad/rad.h
> > index 787e78c7c4a..509f91b208a 100644
> > --- usr.sbin/rad/rad.h
> > +++ usr.sbin/rad/rad.h
> > @@ -97,6 +97,7 @@ struct ra_options_conf {
> > int router_lifetime; /* default router lifetime */
> > uint32_t reachable_time;
> > uint32_t retrans_timer;
> > + int src_linklayer_addr;
> > uint32_t mtu;
> > uint32_t rdns_lifetime;
> > SIMPLEQ_HEAD(, ra_rdnss_conf) ra_rdnss_list;
> >
>
> --
> In my defence, I have been left unsupervised.
Here's a new version of the proposed patch:
diff --git frontend.c frontend.c
index 6748e43732d..7610fc8d9b7 100644
--- frontend.c
+++ frontend.c
@@ -56,6 +56,7 @@
#include <sys/uio.h>
#include <net/if.h>
+#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/route.h>
@@ -120,6 +121,12 @@ struct nd_opt_pref64 {
u_int8_t nd_opt_pref64[12];
};
+struct nd_opt_source_link_addr {
+ u_int8_t nd_opt_source_link_addr_type;
+ u_int8_t nd_opt_source_link_addr_len;
+ struct ether_addr nd_opt_source_link_addr_hw_addr;
+};
+
TAILQ_HEAD(, ra_iface) ra_interfaces;
__dead void frontend_shutdown(void);
@@ -1099,6 +1106,7 @@ void
build_packet(struct ra_iface *ra_iface)
{
struct nd_router_advert *ra;
+ struct nd_opt_source_link_addr *ndopt_source_link_addr;
struct nd_opt_mtu *ndopt_mtu;
struct nd_opt_prefix_info *ndopt_pi;
struct ra_iface_conf *ra_iface_conf;
@@ -1110,6 +1118,8 @@ build_packet(struct ra_iface *ra_iface)
struct ra_rdnss_conf *ra_rdnss;
struct ra_dnssl_conf *ra_dnssl;
struct ra_pref64_conf *pref64;
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr_dl *sdl;
size_t len, label_len;
uint8_t *p, buf[RA_MAX_SIZE];
char *label_start, *label_end;
@@ -1119,6 +1129,8 @@ build_packet(struct ra_iface *ra_iface)
ra_options_conf = &ra_iface_conf->ra_options;
len = sizeof(*ra);
+ if (ra_iface_conf->ra_options.source_link_addr)
+ len += sizeof(*ndopt_source_link_addr);
if (ra_options_conf->mtu > 0)
len += sizeof(*ndopt_mtu);
len += sizeof(*ndopt_pi) * ra_iface->prefix_count;
@@ -1170,6 +1182,35 @@ build_packet(struct ra_iface *ra_iface)
ra->nd_ra_retransmit = htonl(ra_options_conf->retrans_timer);
p += sizeof(*ra);
+ if (ra_iface_conf->ra_options.source_link_addr) {
+ ndopt_source_link_addr = (struct nd_opt_source_link_addr *)p;
+ ndopt_source_link_addr->nd_opt_source_link_addr_type =
+ ND_OPT_SOURCE_LINKADDR;
+ ndopt_source_link_addr->nd_opt_source_link_addr_len = 1;
+ if (getifaddrs(&ifap) != 0) {
+ ifap = NULL;
+ log_warn("getifaddrs");
+ }
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == NULL ||
+ ifa->ifa_addr->sa_family != AF_LINK)
+ continue;
+ if (strcmp(ra_iface->name, ifa->ifa_name) != 0)
+ continue;
+ sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+ if (sdl->sdl_type != IFT_ETHER ||
+ sdl->sdl_alen != ETHER_ADDR_LEN)
+ continue;
+ memcpy(&ndopt_source_link_addr->
+ nd_opt_source_link_addr_hw_addr,
+ LLADDR(sdl), ETHER_ADDR_LEN);
+ break;
+ }
+ if (ifap != NULL)
+ freeifaddrs(ifap);
+ p += sizeof(*ndopt_source_link_addr);
+ }
+
if (ra_options_conf->mtu > 0) {
ndopt_mtu = (struct nd_opt_mtu *)p;
ndopt_mtu->nd_opt_mtu_type = ND_OPT_MTU;
diff --git parse.y parse.y
index 66cc0c0ab64..b9d369bde17 100644
--- parse.y
+++ parse.y
@@ -122,6 +122,7 @@ typedef struct {
%token CONFIGURATION OTHER LIFETIME REACHABLE TIME RETRANS TIMER
%token AUTO PREFIX VALID PREFERENCE PREFERRED LIFETIME ONLINK AUTONOMOUS
%token ADDRESS_CONFIGURATION DNS NAMESERVER SEARCH MTU NAT64 HIGH MEDIUM LOW
+%token SOURCE LINK_LAYER
%token <v.string> STRING
%token <v.number> NUMBER
@@ -226,6 +227,9 @@ ra_opt_block : DEFAULT ROUTER yesno {
| RETRANS TIMER NUMBER {
ra_options->retrans_timer = $3;
}
+ | SOURCE LINK_LAYER ADDRESS yesno {
+ ra_options->source_link_addr = $4;
+ }
| MTU NUMBER {
ra_options->mtu = $2;
}
@@ -523,6 +527,7 @@ lookup(char *s)
{"interface", RA_IFACE},
{"lifetime", LIFETIME},
{"limit", LIMIT},
+ {"link-layer", LINK_LAYER},
{"low", LOW},
{"managed", MANAGED},
{"medium", MEDIUM},
@@ -539,6 +544,7 @@ lookup(char *s)
{"retrans", RETRANS},
{"router", ROUTER},
{"search", SEARCH},
+ {"source", SOURCE},
{"time", TIME},
{"timer", TIMER},
{"valid", VALID},
diff --git printconf.c printconf.c
index 184a5df2dc1..2edfb06edd4 100644
--- printconf.c
+++ printconf.c
@@ -78,6 +78,8 @@ print_ra_options(const char *indent, const struct ra_options_conf *ra_options)
printf("%srouter lifetime %d\n", indent, ra_options->router_lifetime);
printf("%sreachable time %u\n", indent, ra_options->reachable_time);
printf("%sretrans timer %u\n", indent, ra_options->retrans_timer);
+ printf("%ssource link-layer address %s\n", indent,
+ yesno(ra_options->source_link_addr));
if (ra_options->mtu > 0)
printf("%smtu %u\n", indent, ra_options->mtu);
diff --git rad.c rad.c
index b283e58fbf1..9093932c563 100644
--- rad.c
+++ rad.c
@@ -757,6 +757,7 @@ config_new_empty(void)
xconf->ra_options.router_lifetime = ADV_DEFAULT_LIFETIME;
xconf->ra_options.reachable_time = 0;
xconf->ra_options.retrans_timer = 0;
+ xconf->ra_options.source_link_addr = 1;
xconf->ra_options.mtu = 0;
xconf->ra_options.rdns_lifetime = DEFAULT_RDNS_LIFETIME;
SIMPLEQ_INIT(&xconf->ra_options.ra_rdnss_list);
diff --git rad.conf.5 rad.conf.5
index f82ccfd216e..a2589cda88c 100644
--- rad.conf.5
+++ rad.conf.5
@@ -127,6 +127,10 @@ The default is medium.
.\" XXX
.\" .It Ic retrans timer Ar number
.\" XXX
+.It Ic source link-layer address Pq Ic yes Ns | Ns Ic no
+Add a source link-layer address option to router advertisement messages, to
+communicate the link-layer address of the sending interface.
+The default is yes.
.El
.Sh INTERFACES
A list of interfaces or interface groups to send advertisements on:
diff --git rad.h rad.h
index 787e78c7c4a..61187dba273 100644
--- rad.h
+++ rad.h
@@ -97,6 +97,7 @@ struct ra_options_conf {
int router_lifetime; /* default router lifetime */
uint32_t reachable_time;
uint32_t retrans_timer;
+ int source_link_addr; /* source link-layer address */
uint32_t mtu;
uint32_t rdns_lifetime;
SIMPLEQ_HEAD(, ra_rdnss_conf) ra_rdnss_list;
add source link-layer address option to rad(8)