diff options
Diffstat (limited to 'proto/radv/radv.c')
-rw-r--r-- | proto/radv/radv.c | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/proto/radv/radv.c b/proto/radv/radv.c new file mode 100644 index 00000000..01cb6899 --- /dev/null +++ b/proto/radv/radv.c @@ -0,0 +1,329 @@ +/* + * BIRD -- Router Advertisement + * + * + * 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 import or 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. + */ + +static void +radv_timer(timer *tm) +{ + struct radv_iface *ifa = tm->data; + struct proto_radv *ra = ifa->ra; + + RADV_TRACE(D_EVENTS, "Timer fired on %s", ifa->iface->name); + + radv_send_ra(ifa, 0); + + /* Update timer */ + ifa->last = now; + unsigned after = ifa->cf->min_ra_int; + after += random() % (ifa->cf->max_ra_int - ifa->cf->min_ra_int + 1); + + if (ifa->initial) + ifa->initial--; + + if (ifa->initial) + after = MIN(after, MAX_INITIAL_RTR_ADVERT_INTERVAL); + + tm_start(ifa->timer, after); +} + +static char* ev_name[] = { NULL, "Init", "Change", "RS" }; + +void +radv_iface_notify(struct radv_iface *ifa, int event) +{ + struct proto_radv *ra = 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: + ifa->plen = 0; + case RA_EV_INIT: + ifa->initial = MAX_INITIAL_RTR_ADVERTISEMENTS; + break; + + case RA_EV_RS: + break; + } + + /* Update timer */ + unsigned delta = now - ifa->last; + unsigned after = 0; + + if (delta < ifa->cf->min_delay) + after = ifa->cf->min_delay - delta; + + tm_start(ifa->timer, after); +} + +static struct radv_iface * +radv_iface_find(struct proto_radv *ra, struct iface *what) +{ + struct radv_iface *ifa; + + WALK_LIST(ifa, ra->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 proto_radv *ra = ifa->ra; + + if (! radv_sk_open(ifa)) + { + log(L_ERR "%s: Socket open failed on interface %s", ra->p.name, ifa->iface->name); + return; + } + + radv_iface_notify(ifa, RA_EV_INIT); +} + +static inline struct ifa * +find_lladdr(struct iface *iface) +{ + struct ifa *a; + WALK_LIST(a, iface->addrs) + if (a->scope == SCOPE_LINK) + return a; + + return NULL; +} + +static void +radv_iface_new(struct proto_radv *ra, struct iface *iface, struct radv_iface_config *cf) +{ + pool *pool = ra->p.pool; + struct radv_iface *ifa; + + RADV_TRACE(D_EVENTS, "Adding interface %s", iface->name); + + ifa = mb_allocz(pool, sizeof(struct radv_iface)); + ifa->ra = ra; + ifa->cf = cf; + ifa->iface = iface; + + add_tail(&ra->iface_list, NODE ifa); + + ifa->addr = find_lladdr(iface); + if (!ifa->addr) + { + log(L_ERR "%s: Cannot find link-locad addr on interface %s", ra->p.name, iface->name); + return; + } + + timer *tm = tm_new(pool); + tm->hook = radv_timer; + tm->data = ifa; + tm->randomize = 0; + tm->recurrent = 0; + ifa->timer = tm; + + struct object_lock *lock = olock_new(pool); + lock->addr = IPA_NONE; + 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 proto_radv *ra = ifa->ra; + RADV_TRACE(D_EVENTS, "Removing interface %s", ifa->iface->name); + + rem_node(NODE ifa); + + rfree(ifa->sk); + rfree(ifa->timer); + rfree(ifa->lock); + + mb_free(ifa); +} + +static void +radv_if_notify(struct proto *p, unsigned flags, struct iface *iface) +{ + struct proto_radv *ra = (struct proto_radv *) p; + struct radv_config *cf = (struct radv_config *) (p->cf); + + if (iface->flags & IF_IGNORE) + return; + + if (flags & IF_CHANGE_UP) + { + struct radv_iface_config *ic = (struct radv_iface_config *) + iface_patt_find(&cf->patt_list, iface, NULL); + + if (ic) + radv_iface_new(ra, iface, ic); + + return; + } + + struct radv_iface *ifa = radv_iface_find(ra, iface); + if (!ifa) + return; + + if (flags & IF_CHANGE_DOWN) + { + radv_iface_remove(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, struct ifa *a) +{ + struct proto_radv *ra = (struct proto_radv *) p; + + if (a->flags & IA_SECONDARY) + return; + + if (a->scope <= SCOPE_LINK) + return; + + struct radv_iface *ifa = radv_iface_find(ra, a->iface); + + if (ifa) + radv_iface_notify(ifa, RA_EV_CHANGE); +} + +static struct proto * +radv_init(struct proto_config *c) +{ + struct proto *p = proto_new(c, sizeof(struct proto_radv)); + + p->if_notify = radv_if_notify; + p->ifa_notify = radv_ifa_notify; + return p; +} + +static int +radv_start(struct proto *p) +{ + struct proto_radv *ra = (struct proto_radv *) p; + // struct radv_config *cf = (struct radv_config *) (p->cf); + + init_list(&(ra->iface_list)); + + return PS_UP; +} + +static inline void +radv_iface_shutdown(struct radv_iface *ifa) +{ + if (ifa->sk) + radv_send_ra(ifa, 1); +} + +static int +radv_shutdown(struct proto *p) +{ + struct proto_radv *ra = (struct proto_radv *) p; + + struct radv_iface *ifa; + WALK_LIST(ifa, ra->iface_list) + radv_iface_shutdown(ifa); + + return PS_DOWN; +} + +static int +radv_reconfigure(struct proto *p, struct proto_config *c) +{ + struct proto_radv *ra = (struct proto_radv *) p; + // struct radv_config *old = (struct radv_config *) (p->cf); + struct radv_config *new = (struct radv_config *) c; + + /* + * The question is why there is a reconfigure function for RAdv if + * it has almost none internal state so restarting the protocol + * would probably suffice. One small reason is that restarting the + * protocol would lead to sending a RA with Router Lifetime 0 + * causing nodes to temporary remove their default routes. + */ + + struct iface *iface; + WALK_LIST(iface, iface_list) + { + struct radv_iface *ifa = radv_iface_find(ra, 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(ra, iface, ic); + } + + return 1; +} + + +struct protocol proto_radv = { + .name = "RAdv", + .template = "radv%d", + .init = radv_init, + .start = radv_start, + .shutdown = radv_shutdown, + .reconfigure = radv_reconfigure +}; |