bird/proto/radv/radv.c

780 lines
18 KiB
C
Raw Normal View History

/*
* BIRD -- Router Advertisement
*
* (c) 2011--2019 Ondrej Zajicek <santiago@crfreenet.org>
* (c) 2011--2019 CZ.NIC z.s.p.o.
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#include <stdlib.h>
#include "radv.h"
/**
* DOC: Router Advertisements
*
* The RAdv protocol is implemented in two files: |radv.c| containing the
* interface with BIRD core and the protocol logic and |packets.c| handling low
* level protocol stuff (RX, TX and packet formats). The protocol does not
* export any routes.
*
* The RAdv is structured in the usual way - for each handled interface there is
* a structure &radv_iface that contains a state related to that interface
* together with its resources (a socket, a timer). There is also a prepared RA
* stored in a TX buffer of the socket associated with an iface. These iface
* structures are created and removed according to iface events from BIRD core
* handled by radv_if_notify() callback.
*
* The main logic of RAdv consists of two functions: radv_iface_notify(), which
* processes asynchronous events (specified by RA_EV_* codes), and radv_timer(),
* which triggers sending RAs and computes the next timeout.
*
Terminology cleanup: The import_control hook is now called preexport. Once upon a time, far far away, there were the old Bird developers discussing what direction of route flow shall be called import and export. They decided to say "import to protocol" and "export to table" when speaking about a protocol. When speaking about a table, they spoke about "importing to table" and "exporting to protocol". The latter terminology was adopted in configuration, then also the bird CLI in commit ea2ae6dd0 started to use it (in year 2009). Now it's 2018 and the terminology is the latter. Import is from protocol to table, export is from table to protocol. Anyway, there was still an import_control hook which executed right before route export. One thing is funny. There are two commits in April 1999 with just two minutes between them. The older announces the final settlement on config terminology, the newer uses the other definition. Let's see their commit messages as the git-log tool shows them (the newer first): commit 9e0e485e50ea74c4f1c5cb65bdfe6ce819c2cee2 Author: Martin Mares <mj@ucw.cz> Date: Mon Apr 5 20:17:59 1999 +0000 Added some new protocol hooks (look at the comments for better explanation): make_tmp_attrs Convert inline attributes to ea_list store_tmp_attrs Convert ea_list to inline attributes import_control Pre-import decisions commit 5056c559c4eb253a4eee10cf35b694faec5265eb Author: Martin Mares <mj@ucw.cz> Date: Mon Apr 5 20:15:31 1999 +0000 Changed syntax of attaching filters to protocols to hopefully the final version: EXPORT <filter-spec> for outbound routes (i.e., those announced by BIRD to the rest of the world). IMPORT <filter-spec> for inbound routes (i.e., those imported by BIRD from the rest of the world). where <filter-spec> is one of: ALL pass all routes NONE drop all routes FILTER <name> use named filter FILTER { <filter> } use explicitly defined filter For all protocols, the default is IMPORT ALL, EXPORT NONE. This includes the kernel protocol, so that you need to add EXPORT ALL to get the previous configuration of kernel syncer (as usually, see doc/bird.conf.example for a bird.conf example :)). Let's say RIP to this almost 19-years-old inconsistency. For now, if you import a route, it is always from protocol to table. If you export a route, it is always from table to protocol. And they lived happily ever after.
2018-02-14 20:42:53 +08:00
* The RAdv protocol could receive routes (through radv_preexport() and
* radv_rt_notify()), but only the configured trigger route is tracked (in
* &active var). When a radv protocol is reconfigured, the connected routing
* table is examined (in radv_check_active()) to have proper &active value in
* case of the specified trigger prefix was changed.
*
* Supported standards:
* RFC 4861 - main RA standard
* RFC 4191 - Default Router Preferences and More-Specific Routes
* RFC 6106 - DNS extensions (RDDNS, DNSSL)
*/
static void radv_prune_prefixes(struct radv_iface *ifa);
static void radv_prune_routes(struct radv_proto *p);
static void
radv_timer(timer *tm)
{
struct radv_iface *ifa = tm->data;
struct radv_proto *p = ifa->ra;
btime now = current_time();
RADV_TRACE(D_EVENTS, "Timer fired on %s", ifa->iface->name);
if (ifa->prune_time <= now)
radv_prune_prefixes(ifa);
if (p->prune_time <= now)
radv_prune_routes(p);
radv_send_ra(ifa, IPA_NONE);
/* Update timer */
ifa->last = now;
btime t = ifa->cf->min_ra_int S;
btime r = (ifa->cf->max_ra_int - ifa->cf->min_ra_int) S;
2017-06-14 23:02:11 +08:00
t += random() % (r + 1);
if (ifa->initial)
2017-06-14 23:02:11 +08:00
{
t = MIN(t, MAX_INITIAL_RTR_ADVERT_INTERVAL);
ifa->initial--;
2017-06-14 23:02:11 +08:00
}
tm_start(ifa->timer, t);
}
static struct radv_prefix_config default_prefix = {
.onlink = 1,
.autonomous = 1,
.valid_lifetime = DEFAULT_VALID_LIFETIME,
.preferred_lifetime = DEFAULT_PREFERRED_LIFETIME
};
static struct radv_prefix_config dead_prefix = {
};
/* Find a corresponding config for the given prefix */
static struct radv_prefix_config *
radv_prefix_match(struct radv_iface *ifa, net_addr_ip6 *px)
{
struct radv_proto *p = ifa->ra;
struct radv_config *cf = (struct radv_config *) (p->p.cf);
struct radv_prefix_config *pc;
WALK_LIST(pc, ifa->cf->pref_list)
if (net_in_net_ip6(px, &pc->prefix))
return pc;
WALK_LIST(pc, cf->pref_list)
if (net_in_net_ip6(px, &pc->prefix))
return pc;
return &default_prefix;
}
/*
* Go through the list of prefixes, compare them with configs and decide if we
2017-08-30 01:17:35 +08:00
* want them or not.
*/
static void
2017-08-30 01:17:35 +08:00
radv_prepare_prefixes(struct radv_iface *ifa)
{
struct radv_proto *p = ifa->ra;
struct radv_prefix *pfx, *next;
btime now = current_time();
2017-08-30 01:17:35 +08:00
/* First mark all the prefixes as unused */
WALK_LIST(pfx, ifa->prefixes)
pfx->mark = 0;
2017-08-30 01:17:35 +08:00
/* Find all the prefixes we want to use and make sure they are in the list. */
struct ifa *addr;
WALK_LIST(addr, ifa->iface->addrs)
{
if ((addr->prefix.type != NET_IP6) ||
(addr->scope <= SCOPE_LINK))
continue;
net_addr_ip6 *prefix = (void *) &addr->prefix;
struct radv_prefix_config *pc = radv_prefix_match(ifa, prefix);
if (!pc || pc->skip)
continue;
/* Do we have it already? */
struct radv_prefix *existing = NULL;
WALK_LIST(pfx, ifa->prefixes)
if (net_equal_ip6(&pfx->prefix, prefix))
{
existing = pfx;
break;
}
if (!existing)
{
RADV_TRACE(D_EVENTS, "Adding new prefix %N on %s",
prefix, ifa->iface->name);
2017-08-30 01:17:35 +08:00
existing = mb_allocz(ifa->pool, sizeof *existing);
net_copy_ip6(&existing->prefix, prefix);
add_tail(&ifa->prefixes, NODE existing);
}
2017-08-30 01:17:35 +08:00
/*
* Update the information (it may have changed, or even bring a prefix back
* to life).
*/
existing->valid = 1;
existing->changed = now;
existing->mark = 1;
2017-08-30 01:17:35 +08:00
existing->cf = pc;
}
2017-08-30 01:17:35 +08:00
WALK_LIST_DELSAFE(pfx, next, ifa->prefixes)
{
if (pfx->valid && !pfx->mark)
{
RADV_TRACE(D_EVENTS, "Invalidating prefix %N on %s",
&pfx->prefix, ifa->iface->name);
2017-08-30 01:17:35 +08:00
pfx->valid = 0;
pfx->changed = now;
2017-08-30 01:17:35 +08:00
pfx->cf = &dead_prefix;
}
}
}
static void
radv_prune_prefixes(struct radv_iface *ifa)
{
struct radv_proto *p = ifa->ra;
btime now = current_time();
btime next = TIME_INFINITY;
btime expires = 0;
2017-08-30 01:17:35 +08:00
struct radv_prefix *px, *pxn;
WALK_LIST_DELSAFE(px, pxn, ifa->prefixes)
{
if (!px->valid)
2017-08-30 01:17:35 +08:00
{
expires = px->changed + ifa->cf->prefix_linger_time S;
if (expires <= now)
{
RADV_TRACE(D_EVENTS, "Removing prefix %N on %s",
&px->prefix, ifa->iface->name);
2017-08-30 01:17:35 +08:00
rem_node(NODE px);
mb_free(px);
}
else
next = MIN(next, expires);
2017-08-30 01:17:35 +08:00
}
}
2017-08-30 01:17:35 +08:00
ifa->prune_time = next;
}
static char* ev_name[] = { NULL, "Init", "Change", "RS" };
void
radv_iface_notify(struct radv_iface *ifa, int event)
{
struct radv_proto *p = ifa->ra;
if (!ifa->sk)
return;
RADV_TRACE(D_EVENTS, "Event %s on %s", ev_name[event], ifa->iface->name);
switch (event)
{
case RA_EV_CHANGE:
radv_invalidate(ifa);
/* fallthrough */
case RA_EV_INIT:
ifa->initial = MAX_INITIAL_RTR_ADVERTISEMENTS;
radv_prepare_prefixes(ifa);
radv_prune_prefixes(ifa);
break;
case RA_EV_RS:
break;
}
/* Update timer */
btime t = ifa->last + ifa->cf->min_delay S - current_time();
tm_start(ifa->timer, t);
}
static void
radv_iface_notify_all(struct radv_proto *p, int event)
{
struct radv_iface *ifa;
WALK_LIST(ifa, p->iface_list)
radv_iface_notify(ifa, event);
}
static struct radv_iface *
radv_iface_find(struct radv_proto *p, struct iface *what)
{
struct radv_iface *ifa;
WALK_LIST(ifa, p->iface_list)
if (ifa->iface == what)
return ifa;
return NULL;
}
static void
radv_iface_add(struct object_lock *lock)
{
struct radv_iface *ifa = lock->data;
struct radv_proto *p = ifa->ra;
if (! radv_sk_open(ifa))
{
log(L_ERR "%s: Socket open failed on interface %s", p->p.name, ifa->iface->name);
return;
}
radv_iface_notify(ifa, RA_EV_INIT);
}
static void
radv_iface_new(struct radv_proto *p, struct iface *iface, struct radv_iface_config *cf)
{
struct radv_iface *ifa;
RADV_TRACE(D_EVENTS, "Adding interface %s", iface->name);
pool *pool = rp_new(p->p.pool, iface->name);
ifa = mb_allocz(pool, sizeof(struct radv_iface));
ifa->pool = pool;
ifa->ra = p;
ifa->cf = cf;
ifa->iface = iface;
ifa->addr = iface->llv6;
init_list(&ifa->prefixes);
ifa->prune_time = TIME_INFINITY;
add_tail(&p->iface_list, NODE ifa);
ifa->timer = tm_new_init(pool, radv_timer, ifa, 0, 0);
struct object_lock *lock = olock_new(pool);
lock->type = OBJLOCK_IP;
lock->port = ICMPV6_PROTO;
lock->iface = iface;
lock->data = ifa;
lock->hook = radv_iface_add;
ifa->lock = lock;
olock_acquire(lock);
}
static void
radv_iface_remove(struct radv_iface *ifa)
{
struct radv_proto *p = ifa->ra;
RADV_TRACE(D_EVENTS, "Removing interface %s", ifa->iface->name);
rem_node(NODE ifa);
rfree(ifa->pool);
}
static void
radv_if_notify(struct proto *P, unsigned flags, struct iface *iface)
{
struct radv_proto *p = (struct radv_proto *) P;
struct radv_config *cf = (struct radv_config *) (P->cf);
struct radv_iface *ifa = radv_iface_find(p, iface);
if (iface->flags & IF_IGNORE)
return;
/* Add, remove or restart interface */
if (flags & (IF_CHANGE_UPDOWN | IF_CHANGE_LLV6))
{
if (ifa)
radv_iface_remove(ifa);
if (!(iface->flags & IF_UP))
return;
/* Ignore non-multicast ifaces */
if (!(iface->flags & IF_MULTICAST))
return;
/* Ignore ifaces without link-local address */
if (!iface->llv6)
return;
struct radv_iface_config *ic = (void *) iface_patt_find(&cf->patt_list, iface, NULL);
if (ic)
radv_iface_new(p, iface, ic);
return;
}
if (!ifa)
return;
if ((flags & IF_CHANGE_LINK) && (iface->flags & IF_LINK_UP))
radv_iface_notify(ifa, RA_EV_INIT);
}
static void
radv_ifa_notify(struct proto *P, unsigned flags UNUSED, struct ifa *a)
{
struct radv_proto *p = (struct radv_proto *) P;
if (a->flags & IA_SECONDARY)
return;
if (a->scope <= SCOPE_LINK)
return;
struct radv_iface *ifa = radv_iface_find(p, a->iface);
if (ifa)
radv_iface_notify(ifa, RA_EV_CHANGE);
}
static inline int
radv_trigger_valid(struct radv_config *cf)
{
return cf->trigger.type != 0;
}
static inline int
radv_net_match_trigger(struct radv_config *cf, net *n)
{
return radv_trigger_valid(cf) && net_equal(n->n.addr, &cf->trigger);
}
int
2022-07-11 16:41:17 +08:00
radv_preexport(struct channel *C, rte *new)
{
// struct radv_proto *p = (struct radv_proto *) P;
struct radv_config *cf = (struct radv_config *) (C->proto->cf);
if (radv_net_match_trigger(cf, new->net))
return RIC_PROCESS;
if (cf->propagate_routes)
return RIC_PROCESS;
else
return RIC_DROP;
}
static void
radv_rt_notify(struct proto *P, struct channel *ch UNUSED, net *n, rte *new, rte *old UNUSED)
{
struct radv_proto *p = (struct radv_proto *) P;
struct radv_config *cf = (struct radv_config *) (P->cf);
struct radv_route *rt;
eattr *ea;
if (radv_net_match_trigger(cf, n))
{
u8 old_active = p->active;
p->active = !!new;
if (p->active == old_active)
return;
if (p->active)
RADV_TRACE(D_EVENTS, "Triggered");
else
RADV_TRACE(D_EVENTS, "Suppressed");
radv_iface_notify_all(p, RA_EV_CHANGE);
return;
}
if (!cf->propagate_routes)
return;
/*
* Some other route we want to send (or stop sending). Update the cache,
* with marking a removed one as dead or creating a new one as needed.
*
* And yes, we exclude the trigger route on purpose.
*/
if (new)
{
/* Update */
ea = ea_find(new->attrs->eattrs, EA_RA_PREFERENCE);
uint preference = ea ? ea->u.data : RA_PREF_MEDIUM;
uint preference_set = !!ea;
ea = ea_find(new->attrs->eattrs, EA_RA_LIFETIME);
uint lifetime = ea ? ea->u.data : 0;
uint lifetime_set = !!ea;
if ((preference != RA_PREF_LOW) &&
(preference != RA_PREF_MEDIUM) &&
(preference != RA_PREF_HIGH))
{
log(L_WARN "%s: Invalid ra_preference value %u on route %N",
p->p.name, preference, n->n.addr);
preference = RA_PREF_MEDIUM;
preference_set = 1;
lifetime = 0;
lifetime_set = 1;
}
rt = fib_get(&p->routes, n->n.addr);
/* Ignore update if nothing changed */
if (rt->valid &&
(rt->preference == preference) &&
(rt->preference_set == preference_set) &&
(rt->lifetime == lifetime) &&
(rt->lifetime_set == lifetime_set))
return;
if (p->routes.entries == 18)
log(L_WARN "%s: More than 17 routes exported to RAdv", p->p.name);
rt->valid = 1;
rt->changed = current_time();
rt->preference = preference;
rt->preference_set = preference_set;
rt->lifetime = lifetime;
rt->lifetime_set = lifetime_set;
}
else
{
/* Withdraw */
rt = fib_find(&p->routes, n->n.addr);
if (!rt || !rt->valid)
return;
/* Invalidate the route */
rt->valid = 0;
rt->changed = current_time();
/* Invalidated route will be pruned eventually */
btime expires = rt->changed + cf->max_linger_time S;
p->prune_time = MIN(p->prune_time, expires);
}
radv_iface_notify_all(p, RA_EV_CHANGE);
}
/*
* Cleans up all the dead routes that expired and schedules itself to be run
* again if there are more routes waiting for expiration.
*/
static void
radv_prune_routes(struct radv_proto *p)
{
struct radv_config *cf = (struct radv_config *) (p->p.cf);
btime now = current_time();
btime next = TIME_INFINITY;
btime expires = 0;
/* Should not happen */
if (!p->fib_up)
return;
struct fib_iterator fit;
FIB_ITERATE_INIT(&fit, &p->routes);
again:
FIB_ITERATE_START(&p->routes, &fit, struct radv_route, rt)
{
if (!rt->valid)
{
expires = rt->changed + cf->max_linger_time S;
/* Delete expired nodes */
if (expires <= now)
{
FIB_ITERATE_PUT(&fit);
fib_delete(&p->routes, rt);
goto again;
}
else
next = MIN(next, expires);
}
}
FIB_ITERATE_END;
p->prune_time = next;
}
static int
radv_check_active(struct radv_proto *p)
{
struct radv_config *cf = (struct radv_config *) (p->p.cf);
if (!radv_trigger_valid(cf))
return 1;
struct channel *c = p->p.main_channel;
return rt_examine(c->table, &cf->trigger, c, c->out_filter);
}
static void
radv_postconfig(struct proto_config *CF)
{
// struct radv_config *cf = (void *) CF;
/* Define default channel */
if (! proto_cf_main_channel(CF))
channel_config_new(NULL, net_label[NET_IP6], NET_IP6, CF);
}
static struct proto *
radv_init(struct proto_config *CF)
{
struct proto *P = proto_new(CF);
P->main_channel = proto_add_channel(P, proto_cf_main_channel(CF));
Terminology cleanup: The import_control hook is now called preexport. Once upon a time, far far away, there were the old Bird developers discussing what direction of route flow shall be called import and export. They decided to say "import to protocol" and "export to table" when speaking about a protocol. When speaking about a table, they spoke about "importing to table" and "exporting to protocol". The latter terminology was adopted in configuration, then also the bird CLI in commit ea2ae6dd0 started to use it (in year 2009). Now it's 2018 and the terminology is the latter. Import is from protocol to table, export is from table to protocol. Anyway, there was still an import_control hook which executed right before route export. One thing is funny. There are two commits in April 1999 with just two minutes between them. The older announces the final settlement on config terminology, the newer uses the other definition. Let's see their commit messages as the git-log tool shows them (the newer first): commit 9e0e485e50ea74c4f1c5cb65bdfe6ce819c2cee2 Author: Martin Mares <mj@ucw.cz> Date: Mon Apr 5 20:17:59 1999 +0000 Added some new protocol hooks (look at the comments for better explanation): make_tmp_attrs Convert inline attributes to ea_list store_tmp_attrs Convert ea_list to inline attributes import_control Pre-import decisions commit 5056c559c4eb253a4eee10cf35b694faec5265eb Author: Martin Mares <mj@ucw.cz> Date: Mon Apr 5 20:15:31 1999 +0000 Changed syntax of attaching filters to protocols to hopefully the final version: EXPORT <filter-spec> for outbound routes (i.e., those announced by BIRD to the rest of the world). IMPORT <filter-spec> for inbound routes (i.e., those imported by BIRD from the rest of the world). where <filter-spec> is one of: ALL pass all routes NONE drop all routes FILTER <name> use named filter FILTER { <filter> } use explicitly defined filter For all protocols, the default is IMPORT ALL, EXPORT NONE. This includes the kernel protocol, so that you need to add EXPORT ALL to get the previous configuration of kernel syncer (as usually, see doc/bird.conf.example for a bird.conf example :)). Let's say RIP to this almost 19-years-old inconsistency. For now, if you import a route, it is always from protocol to table. If you export a route, it is always from table to protocol. And they lived happily ever after.
2018-02-14 20:42:53 +08:00
P->preexport = radv_preexport;
P->rt_notify = radv_rt_notify;
P->if_notify = radv_if_notify;
P->ifa_notify = radv_ifa_notify;
return P;
}
static void
radv_set_fib(struct radv_proto *p, int up)
{
if (up == p->fib_up)
return;
if (up)
fib_init(&p->routes, p->p.pool, NET_IP6, sizeof(struct radv_route),
OFFSETOF(struct radv_route, n), 4, NULL);
else
fib_free(&p->routes);
p->fib_up = up;
p->prune_time = TIME_INFINITY;
}
static int
radv_start(struct proto *P)
{
struct radv_proto *p = (struct radv_proto *) P;
struct radv_config *cf = (struct radv_config *) (P->cf);
init_list(&(p->iface_list));
p->valid = 1;
p->active = !radv_trigger_valid(cf);
p->fib_up = 0;
radv_set_fib(p, cf->propagate_routes);
p->prune_time = TIME_INFINITY;
return PS_UP;
}
static inline void
radv_iface_shutdown(struct radv_iface *ifa)
{
if (ifa->sk)
{
radv_invalidate(ifa);
radv_send_ra(ifa, IPA_NONE);
}
}
static int
radv_shutdown(struct proto *P)
{
struct radv_proto *p = (struct radv_proto *) P;
p->valid = 0;
struct radv_iface *ifa;
WALK_LIST(ifa, p->iface_list)
radv_iface_shutdown(ifa);
return PS_DOWN;
}
static int
radv_reconfigure(struct proto *P, struct proto_config *CF)
{
struct radv_proto *p = (struct radv_proto *) P;
struct radv_config *old = (struct radv_config *) (P->cf);
struct radv_config *new = (struct radv_config *) CF;
if (!proto_configure_channel(P, &P->main_channel, proto_cf_main_channel(CF)))
return 0;
2017-08-09 18:46:27 +08:00
P->cf = CF; /* radv_check_active() requires proper P->cf */
p->active = radv_check_active(p);
/* Allocate or free FIB */
radv_set_fib(p, new->propagate_routes);
/* We started to accept routes so we need to refeed them */
if (!old->propagate_routes && new->propagate_routes)
channel_request_feeding(p->p.main_channel);
struct iface *iface;
WALK_LIST(iface, iface_list)
{
if (!(iface->flags & IF_UP))
continue;
/* Ignore non-multicast ifaces */
if (!(iface->flags & IF_MULTICAST))
continue;
/* Ignore ifaces without link-local address */
if (!iface->llv6)
continue;
struct radv_iface *ifa = radv_iface_find(p, iface);
struct radv_iface_config *ic = (struct radv_iface_config *)
iface_patt_find(&new->patt_list, iface, NULL);
if (ifa && ic)
{
ifa->cf = ic;
/* We cheat here - always notify the change even if there isn't
any. That would leads just to a few unnecessary RAs. */
radv_iface_notify(ifa, RA_EV_CHANGE);
}
if (ifa && !ic)
{
radv_iface_shutdown(ifa);
radv_iface_remove(ifa);
}
if (!ifa && ic)
radv_iface_new(p, iface, ic);
}
return 1;
}
static void
radv_copy_config(struct proto_config *dest, struct proto_config *src)
{
struct radv_config *d = (struct radv_config *) dest;
struct radv_config *s = (struct radv_config *) src;
/* We clean up patt_list, ifaces are non-sharable */
init_list(&d->patt_list);
/* We copy pref_list, shallow copy suffices */
cfg_copy_list(&d->pref_list, &s->pref_list, sizeof(struct radv_prefix_config));
}
static void
radv_get_status(struct proto *P, byte *buf)
{
struct radv_proto *p = (struct radv_proto *) P;
if (!p->active)
strcpy(buf, "Suppressed");
}
static const char *
radv_pref_str(u32 pref)
{
switch (pref)
{
case RA_PREF_LOW:
return "low";
case RA_PREF_MEDIUM:
return "medium";
case RA_PREF_HIGH:
return "high";
default:
return "??";
}
}
/* The buffer has some minimal size */
static int
radv_get_attr(const eattr *a, byte *buf, int buflen UNUSED)
{
switch (a->id)
{
case EA_RA_PREFERENCE:
bsprintf(buf, "preference: %s", radv_pref_str(a->u.data));
return GA_FULL;
case EA_RA_LIFETIME:
bsprintf(buf, "lifetime");
return GA_NAME;
default:
return GA_UNKNOWN;
}
}
struct protocol proto_radv = {
.name = "RAdv",
.template = "radv%d",
.class = PROTOCOL_RADV,
.channel_mask = NB_IP6,
.proto_size = sizeof(struct radv_proto),
.config_size = sizeof(struct radv_config),
.postconfig = radv_postconfig,
.init = radv_init,
.start = radv_start,
.shutdown = radv_shutdown,
.reconfigure = radv_reconfigure,
.copy_config = radv_copy_config,
.get_status = radv_get_status,
.get_attr = radv_get_attr
};
void
radv_build(void)
{
proto_build(&proto_radv);
}