mirror of
https://gitlab.nic.cz/labs/bird.git
synced 2024-12-23 18:21:54 +00:00
8d1215dba6
If the protocol supports route refresh on export, we keep the stop-start method of route refeed. This applies for BGP with ERR or with export table on, for OSPF, Babel, RIP or Pipe. For BGP without ERR or for future selective ROA reloads, we're adding an auxiliary export request, doing the refeed while the main export request is running, somehow resembling the original method of BIRD 2 refeed. There is also a refeed request queue to keep track of different refeed requests.
2659 lines
70 KiB
C
2659 lines
70 KiB
C
/*
|
|
* BIRD -- The Babel protocol
|
|
*
|
|
* Copyright (c) 2015--2016 Toke Hoiland-Jorgensen
|
|
* (c) 2016--2017 Ondrej Zajicek <santiago@crfreenet.org>
|
|
* (c) 2016--2017 CZ.NIC z.s.p.o.
|
|
*
|
|
* Can be freely distributed and used under the terms of the GNU GPL.
|
|
*
|
|
* This file contains the main routines for handling and sending TLVs, as
|
|
* well as timers and interaction with the nest.
|
|
*/
|
|
|
|
/**
|
|
* DOC: The Babel protocol
|
|
*
|
|
* The Babel is a loop-avoiding distance-vector routing protocol that is robust
|
|
* and efficient both in ordinary wired networks and in wireless mesh networks.
|
|
*
|
|
* The Babel protocol keeps state for each neighbour in a &babel_neighbor
|
|
* struct, tracking received Hello and I Heard You (IHU) messages. A
|
|
* &babel_interface struct keeps hello and update times for each interface, and
|
|
* a separate hello seqno is maintained for each interface.
|
|
*
|
|
* For each prefix, Babel keeps track of both the possible routes (with next hop
|
|
* and router IDs), as well as the feasibility distance for each prefix and
|
|
* router id. The prefix itself is tracked in a &babel_entry struct, while the
|
|
* possible routes for the prefix are tracked as &babel_route entries and the
|
|
* feasibility distance is maintained through &babel_source structures.
|
|
*
|
|
* The main route selection is done in babel_select_route(). This is called when
|
|
* an entry is updated by receiving updates from the network or when modified by
|
|
* internal timers. The function selects from feasible and reachable routes the
|
|
* one with the lowest metric to be announced to the core.
|
|
*
|
|
* Supported standards:
|
|
* RFC 8966 - The Babel Routing Protocol
|
|
* RFC 8967 - MAC Authentication for Babel
|
|
* RFC 9079 - Source Specific Routing for Babel
|
|
* RFC 9229 - IPv4 Routes with IPv6 Next Hop for Babel
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include "babel.h"
|
|
#include "lib/macro.h"
|
|
|
|
#define LOG_PKT_AUTH(msg, args...) \
|
|
log_rl(&p->log_pkt_tbf, L_AUTH "%s: " msg, p->p.name, args)
|
|
|
|
/*
|
|
* Is one number greater or equal than another mod 2^16? This is based on the
|
|
* definition of serial number space in RFC 1982. Note that arguments are of
|
|
* uint type to avoid integer promotion to signed integer.
|
|
*/
|
|
static inline int ge_mod64k(uint a, uint b)
|
|
{ return (u16)(a - b) < 0x8000; }
|
|
|
|
/* Strict inequality version of the above */
|
|
static inline int gt_mod64k(uint a, uint b)
|
|
{ return ge_mod64k(a, b) && a != b; }
|
|
|
|
static void babel_expire_requests(struct babel_proto *p, struct babel_entry *e);
|
|
static void babel_select_route(struct babel_proto *p, struct babel_entry *e, struct babel_route *mod);
|
|
static inline void babel_announce_retraction(struct babel_proto *p, struct babel_entry *e);
|
|
static void babel_send_route_request(struct babel_proto *p, struct babel_entry *e, struct babel_neighbor *n);
|
|
static void babel_send_seqno_request(struct babel_proto *p, struct babel_entry *e, struct babel_seqno_request *sr, struct babel_neighbor *n);
|
|
static void babel_update_cost(struct babel_neighbor *n);
|
|
static inline void babel_kick_timer(struct babel_proto *p);
|
|
static inline void babel_iface_kick_timer(struct babel_iface *ifa);
|
|
|
|
static struct ea_class ea_babel_metric, ea_babel_router_id, ea_babel_seqno;
|
|
|
|
/*
|
|
* Functions to maintain data structures
|
|
*/
|
|
|
|
static void
|
|
babel_init_entry(struct fib *f UNUSED, void *E)
|
|
{
|
|
struct babel_entry *e = E;
|
|
|
|
e->updated = current_time();
|
|
init_list(&e->requests);
|
|
init_list(&e->sources);
|
|
init_list(&e->routes);
|
|
}
|
|
|
|
static inline struct babel_entry *
|
|
babel_find_entry(struct babel_proto *p, const net_addr *n)
|
|
{
|
|
struct fib *rtable = (n->type == NET_IP4) ? &p->ip4_rtable : &p->ip6_rtable;
|
|
return fib_find(rtable, n);
|
|
}
|
|
|
|
static struct babel_entry *
|
|
babel_get_entry(struct babel_proto *p, const net_addr *n)
|
|
{
|
|
struct fib *rtable = (n->type == NET_IP4) ? &p->ip4_rtable : &p->ip6_rtable;
|
|
struct babel_entry *e = fib_get(rtable, n);
|
|
return e;
|
|
}
|
|
|
|
static struct babel_source *
|
|
babel_find_source(struct babel_entry *e, u64 router_id)
|
|
{
|
|
struct babel_source *s;
|
|
|
|
WALK_LIST(s, e->sources)
|
|
if (s->router_id == router_id)
|
|
return s;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct babel_source *
|
|
babel_get_source(struct babel_proto *p, struct babel_entry *e, u64 router_id,
|
|
u16 initial_seqno)
|
|
{
|
|
struct babel_source *s = babel_find_source(e, router_id);
|
|
|
|
if (s)
|
|
return s;
|
|
|
|
s = sl_allocz(p->source_slab);
|
|
s->router_id = router_id;
|
|
s->expires = current_time() + BABEL_GARBAGE_INTERVAL;
|
|
s->seqno = initial_seqno;
|
|
s->metric = BABEL_INFINITY;
|
|
add_tail(&e->sources, NODE s);
|
|
|
|
return s;
|
|
}
|
|
|
|
static void
|
|
babel_expire_sources(struct babel_proto *p UNUSED, struct babel_entry *e)
|
|
{
|
|
struct babel_source *n, *nx;
|
|
btime now_ = current_time();
|
|
|
|
WALK_LIST_DELSAFE(n, nx, e->sources)
|
|
{
|
|
if (n->expires && n->expires <= now_)
|
|
{
|
|
rem_node(NODE n);
|
|
sl_free(n);
|
|
}
|
|
}
|
|
}
|
|
|
|
static struct babel_route *
|
|
babel_find_route(struct babel_entry *e, struct babel_neighbor *n)
|
|
{
|
|
struct babel_route *r;
|
|
|
|
WALK_LIST(r, e->routes)
|
|
if (r->neigh == n)
|
|
return r;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct babel_route *
|
|
babel_get_route(struct babel_proto *p, struct babel_entry *e, struct babel_neighbor *nbr)
|
|
{
|
|
struct babel_route *r = babel_find_route(e, nbr);
|
|
|
|
if (r)
|
|
return r;
|
|
|
|
r = sl_allocz(p->route_slab);
|
|
|
|
r->e = e;
|
|
r->neigh = nbr;
|
|
add_tail(&e->routes, NODE r);
|
|
add_tail(&nbr->routes, NODE &r->neigh_route);
|
|
|
|
return r;
|
|
}
|
|
|
|
static inline void
|
|
babel_retract_route(struct babel_proto *p, struct babel_route *r)
|
|
{
|
|
r->metric = r->advert_metric = BABEL_INFINITY;
|
|
|
|
if (r == r->e->selected)
|
|
babel_select_route(p, r->e, r);
|
|
}
|
|
|
|
static void
|
|
babel_flush_route(struct babel_proto *p UNUSED, struct babel_route *r)
|
|
{
|
|
DBG("Babel: Flush route %N router_id %lR neigh %I\n",
|
|
r->e->n.addr, r->router_id, r->neigh->addr);
|
|
|
|
rem_node(NODE r);
|
|
rem_node(&r->neigh_route);
|
|
|
|
if (r->e->selected == r)
|
|
r->e->selected = NULL;
|
|
|
|
sl_free(r);
|
|
}
|
|
|
|
static void
|
|
babel_expire_route(struct babel_proto *p, struct babel_route *r)
|
|
{
|
|
struct babel_config *cf = (void *) p->p.cf;
|
|
|
|
TRACE(D_EVENTS, "Route expiry timer for %N router-id %lR fired",
|
|
r->e->n.addr, r->router_id);
|
|
|
|
if (r->metric < BABEL_INFINITY)
|
|
{
|
|
r->metric = r->advert_metric = BABEL_INFINITY;
|
|
r->expires = current_time() + cf->hold_time;
|
|
}
|
|
else
|
|
{
|
|
babel_flush_route(p, r);
|
|
}
|
|
}
|
|
|
|
static void
|
|
babel_refresh_route(struct babel_proto *p, struct babel_route *r)
|
|
{
|
|
if (r == r->e->selected)
|
|
babel_send_route_request(p, r->e, r->neigh);
|
|
|
|
r->refresh_time = 0;
|
|
}
|
|
|
|
static void
|
|
babel_expire_routes_(struct babel_proto *p, struct fib *rtable)
|
|
{
|
|
struct babel_config *cf = (void *) p->p.cf;
|
|
struct babel_route *r, *rx;
|
|
struct fib_iterator fit;
|
|
btime now_ = current_time();
|
|
|
|
FIB_ITERATE_INIT(&fit, rtable);
|
|
|
|
loop:
|
|
FIB_ITERATE_START(rtable, &fit, struct babel_entry, e)
|
|
{
|
|
int changed = 0;
|
|
|
|
WALK_LIST_DELSAFE(r, rx, e->routes)
|
|
{
|
|
if (r->refresh_time && r->refresh_time <= now_)
|
|
babel_refresh_route(p, r);
|
|
|
|
if (r->expires && r->expires <= now_)
|
|
{
|
|
changed = changed || (r == e->selected);
|
|
babel_expire_route(p, r);
|
|
}
|
|
}
|
|
|
|
if (changed)
|
|
{
|
|
/*
|
|
* We have to restart the iteration because there may be a cascade of
|
|
* synchronous events babel_select_route() -> nest table change ->
|
|
* babel_rt_notify() -> rtable change, invalidating hidden variables.
|
|
*/
|
|
FIB_ITERATE_PUT(&fit);
|
|
babel_select_route(p, e, NULL);
|
|
goto loop;
|
|
}
|
|
|
|
/* Clean up stale entries */
|
|
if ((e->valid == BABEL_ENTRY_STALE) && ((e->updated + cf->hold_time) <= now_))
|
|
e->valid = BABEL_ENTRY_DUMMY;
|
|
|
|
/* Clean up unreachable route */
|
|
if (e->unreachable && (!e->valid || (e->router_id == p->router_id)))
|
|
{
|
|
FIB_ITERATE_PUT(&fit);
|
|
babel_announce_retraction(p, e);
|
|
goto loop;
|
|
}
|
|
|
|
babel_expire_sources(p, e);
|
|
babel_expire_requests(p, e);
|
|
|
|
/* Remove empty entries */
|
|
if (!e->valid && EMPTY_LIST(e->routes) && EMPTY_LIST(e->sources) && EMPTY_LIST(e->requests))
|
|
{
|
|
FIB_ITERATE_PUT(&fit);
|
|
fib_delete(rtable, e);
|
|
goto loop;
|
|
}
|
|
}
|
|
FIB_ITERATE_END;
|
|
}
|
|
|
|
static void
|
|
babel_expire_routes(struct babel_proto *p)
|
|
{
|
|
babel_expire_routes_(p, &p->ip4_rtable);
|
|
babel_expire_routes_(p, &p->ip6_rtable);
|
|
}
|
|
|
|
/*
|
|
* Add seqno request to the table of pending requests (RFC 8966 3.2.6) and send
|
|
* it to network. Do nothing if it is already in the table.
|
|
*/
|
|
|
|
static void
|
|
babel_add_seqno_request(struct babel_proto *p, struct babel_entry *e,
|
|
u64 router_id, u16 seqno, u8 hop_count,
|
|
struct babel_neighbor *target)
|
|
{
|
|
struct babel_seqno_request *sr;
|
|
btime now_ = current_time();
|
|
|
|
WALK_LIST(sr, e->requests)
|
|
if (sr->router_id == router_id)
|
|
{
|
|
/*
|
|
* To suppress duplicates, check if we already have a newer (higher seqno)
|
|
* outstanding request. If we do, suppress this request if the outstanding
|
|
* request is one we originated ourselves. If the outstanding request is
|
|
* forwarded, suppress only if this request is also one we're forwarding
|
|
* *and* we're within the duplicate suppression time of that request (see
|
|
* below).
|
|
*/
|
|
if (ge_mod64k(sr->seqno, seqno) &&
|
|
(!sr->forwarded || (target && now_ < sr->dup_suppress_time)))
|
|
return;
|
|
|
|
rem_node(NODE sr);
|
|
|
|
/* Allow upgrading from forwarded to non-forwarded */
|
|
if (!target)
|
|
sr->forwarded = 0;
|
|
|
|
goto found;
|
|
}
|
|
|
|
/* No entries found */
|
|
sr = sl_allocz(p->seqno_slab);
|
|
sr->forwarded = !!target;
|
|
|
|
found:
|
|
sr->router_id = router_id;
|
|
sr->seqno = seqno;
|
|
sr->hop_count = hop_count ?: BABEL_INITIAL_HOP_COUNT;
|
|
sr->count = 0;
|
|
|
|
if (sr->forwarded)
|
|
{
|
|
/*
|
|
* We want to keep the entry around for a reasonable period of time so it
|
|
* can be used to trigger an update (through babel_satisfy_seqno_request()).
|
|
* However, duplicate suppression should only trigger for a short period of
|
|
* time so it suppresses duplicates from multiple sources, but not
|
|
* retransmissions from the same source. Hence we keep two timers.
|
|
*/
|
|
sr->expires = now_ + BABEL_SEQNO_FORWARD_EXPIRY;
|
|
sr->dup_suppress_time = now_ + BABEL_SEQNO_DUP_SUPPRESS_TIME;
|
|
}
|
|
else
|
|
{
|
|
sr->expires = now_ + BABEL_SEQNO_REQUEST_EXPIRY;
|
|
}
|
|
|
|
add_tail(&e->requests, NODE sr);
|
|
babel_send_seqno_request(p, e, sr, target);
|
|
}
|
|
|
|
static void
|
|
babel_generate_seqno_request(struct babel_proto *p, struct babel_entry *e,
|
|
u64 router_id, u16 seqno, struct babel_neighbor *target)
|
|
{
|
|
struct babel_seqno_request req = {
|
|
.router_id = router_id,
|
|
.seqno = seqno,
|
|
.hop_count = BABEL_INITIAL_HOP_COUNT,
|
|
};
|
|
|
|
babel_send_seqno_request(p, e, &req, target);
|
|
}
|
|
|
|
static void
|
|
babel_remove_seqno_request(struct babel_proto *p UNUSED, struct babel_seqno_request *sr)
|
|
{
|
|
rem_node(NODE sr);
|
|
sl_free(sr);
|
|
}
|
|
|
|
static int
|
|
babel_satisfy_seqno_request(struct babel_proto *p, struct babel_entry *e,
|
|
u64 router_id, u16 seqno)
|
|
{
|
|
struct babel_seqno_request *sr;
|
|
|
|
WALK_LIST(sr, e->requests)
|
|
if ((sr->router_id == router_id) && ge_mod64k(seqno, sr->seqno))
|
|
{
|
|
/* Found the request, remove it */
|
|
babel_remove_seqno_request(p, sr);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
babel_expire_requests(struct babel_proto *p, struct babel_entry *e)
|
|
{
|
|
struct babel_seqno_request *sr, *srx;
|
|
btime now_ = current_time();
|
|
|
|
WALK_LIST_DELSAFE(sr, srx, e->requests)
|
|
{
|
|
/* Handle expired requests - resend or remove */
|
|
if (sr->expires && sr->expires <= now_)
|
|
{
|
|
if (!sr->forwarded && sr->count < BABEL_SEQNO_REQUEST_RETRY)
|
|
{
|
|
sr->count++;
|
|
sr->expires += (BABEL_SEQNO_REQUEST_EXPIRY << sr->count);
|
|
babel_send_seqno_request(p, e, sr, NULL);
|
|
}
|
|
else
|
|
{
|
|
TRACE(D_EVENTS, "Seqno request for %N router-id %lR expired",
|
|
e->n.addr, sr->router_id);
|
|
|
|
babel_remove_seqno_request(p, sr);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static struct babel_neighbor *
|
|
babel_find_neighbor(struct babel_iface *ifa, ip_addr addr)
|
|
{
|
|
struct babel_neighbor *nbr;
|
|
|
|
WALK_LIST(nbr, ifa->neigh_list)
|
|
if (ipa_equal(nbr->addr, addr))
|
|
return nbr;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct babel_neighbor *
|
|
babel_get_neighbor(struct babel_iface *ifa, ip_addr addr)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
struct babel_neighbor *nbr = babel_find_neighbor(ifa, addr);
|
|
|
|
if (nbr)
|
|
return nbr;
|
|
|
|
TRACE(D_EVENTS, "New neighbor %I on %s", addr, ifa->iface->name);
|
|
|
|
nbr = mb_allocz(ifa->pool, sizeof(struct babel_neighbor));
|
|
nbr->ifa = ifa;
|
|
nbr->addr = addr;
|
|
nbr->rxcost = BABEL_INFINITY;
|
|
nbr->txcost = BABEL_INFINITY;
|
|
nbr->cost = BABEL_INFINITY;
|
|
nbr->init_expiry = current_time() + BABEL_INITIAL_NEIGHBOR_TIMEOUT;
|
|
init_list(&nbr->routes);
|
|
add_tail(&ifa->neigh_list, NODE nbr);
|
|
|
|
return nbr;
|
|
}
|
|
|
|
static void
|
|
babel_flush_neighbor(struct babel_proto *p, struct babel_neighbor *nbr)
|
|
{
|
|
struct babel_route *r;
|
|
node *n;
|
|
|
|
TRACE(D_EVENTS, "Removing neighbor %I on %s", nbr->addr, nbr->ifa->iface->name);
|
|
|
|
WALK_LIST_FIRST(n, nbr->routes)
|
|
{
|
|
r = SKIP_BACK(struct babel_route, neigh_route, n);
|
|
babel_retract_route(p, r);
|
|
babel_flush_route(p, r);
|
|
}
|
|
|
|
nbr->ifa = NULL;
|
|
rem_node(NODE nbr);
|
|
mb_free(nbr);
|
|
}
|
|
|
|
static void
|
|
babel_expire_ihu(struct babel_proto *p, struct babel_neighbor *nbr)
|
|
{
|
|
TRACE(D_EVENTS, "IHU from nbr %I on %s expired", nbr->addr, nbr->ifa->iface->name);
|
|
|
|
nbr->txcost = BABEL_INFINITY;
|
|
nbr->ihu_expiry = 0;
|
|
babel_update_cost(nbr);
|
|
}
|
|
|
|
static void
|
|
babel_expire_hello(struct babel_proto *p, struct babel_neighbor *nbr, btime now_)
|
|
{
|
|
again:
|
|
nbr->hello_map <<= 1;
|
|
|
|
if (nbr->hello_cnt < 16)
|
|
nbr->hello_cnt++;
|
|
|
|
nbr->hello_expiry += nbr->last_hello_int;
|
|
|
|
/* We may expire multiple hellos if last_hello_int is too short */
|
|
if (nbr->hello_map && nbr->hello_expiry <= now_)
|
|
goto again;
|
|
|
|
TRACE(D_EVENTS, "Hello from nbr %I on %s expired, %d left",
|
|
nbr->addr, nbr->ifa->iface->name, u32_popcount(nbr->hello_map));
|
|
|
|
if (nbr->hello_map)
|
|
babel_update_cost(nbr);
|
|
else
|
|
babel_flush_neighbor(p, nbr);
|
|
}
|
|
|
|
static void
|
|
babel_expire_neighbors(struct babel_proto *p)
|
|
{
|
|
struct babel_iface *ifa;
|
|
struct babel_neighbor *nbr, *nbx;
|
|
btime now_ = current_time();
|
|
|
|
WALK_LIST(ifa, p->interfaces)
|
|
{
|
|
WALK_LIST_DELSAFE(nbr, nbx, ifa->neigh_list)
|
|
{
|
|
if (nbr->ihu_expiry && nbr->ihu_expiry <= now_)
|
|
babel_expire_ihu(p, nbr);
|
|
|
|
if (nbr->init_expiry && nbr->init_expiry <= now_)
|
|
{ babel_flush_neighbor(p, nbr); continue; }
|
|
|
|
if (nbr->hello_expiry && nbr->hello_expiry <= now_)
|
|
{ babel_expire_hello(p, nbr, now_); continue; }
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Best route selection
|
|
*/
|
|
|
|
/*
|
|
* From the RFC (section 3.5.1):
|
|
*
|
|
* a route advertisement carrying the quintuple (prefix, plen, router-id, seqno,
|
|
* metric) is feasible if one of the following conditions holds:
|
|
*
|
|
* - metric is infinite; or
|
|
*
|
|
* - no entry exists in the source table indexed by (id, prefix, plen); or
|
|
*
|
|
* - an entry (prefix, plen, router-id, seqno', metric') exists in the source
|
|
* table, and either
|
|
* - seqno' < seqno or
|
|
* - seqno = seqno' and metric < metric'.
|
|
*/
|
|
static inline int
|
|
babel_is_feasible(struct babel_source *s, u16 seqno, u16 metric)
|
|
{
|
|
return !s ||
|
|
(metric == BABEL_INFINITY) ||
|
|
gt_mod64k(seqno, s->seqno) ||
|
|
((seqno == s->seqno) && (metric < s->metric));
|
|
}
|
|
|
|
/* Simple additive metric - Appendix 3.1 in the RFC */
|
|
static inline u16
|
|
babel_compute_metric(struct babel_neighbor *n, uint metric)
|
|
{
|
|
return MIN(metric + n->cost, BABEL_INFINITY);
|
|
}
|
|
|
|
static void
|
|
babel_update_cost(struct babel_neighbor *nbr)
|
|
{
|
|
struct babel_proto *p = nbr->ifa->proto;
|
|
struct babel_iface_config *cf = nbr->ifa->cf;
|
|
uint rcv = u32_popcount(nbr->hello_map); // number of bits set
|
|
uint max = nbr->hello_cnt;
|
|
uint rxcost = BABEL_INFINITY; /* Cost to announce in IHU */
|
|
uint txcost = BABEL_INFINITY; /* Effective cost for route selection */
|
|
|
|
if (!rcv || !nbr->ifa->up)
|
|
goto done;
|
|
|
|
switch (cf->type)
|
|
{
|
|
case BABEL_IFACE_TYPE_WIRED:
|
|
/* k-out-of-j selection - Appendix 2.1 in the RFC. */
|
|
|
|
/* Link is bad if less than cf->limit/16 of expected hellos were received */
|
|
if (rcv * 16 < cf->limit * max)
|
|
break;
|
|
|
|
rxcost = cf->rxcost;
|
|
txcost = nbr->txcost;
|
|
break;
|
|
|
|
case BABEL_IFACE_TYPE_WIRELESS:
|
|
/*
|
|
* ETX - Appendix 2.2 in the RFC.
|
|
*
|
|
* alpha = prob. of successful transmission estimated by the neighbor
|
|
* beta = prob. of successful transmission estimated by the router
|
|
* rxcost = nominal rxcost of the router / beta
|
|
* txcost = nominal rxcost of the neighbor / (alpha * beta)
|
|
* = received txcost / beta
|
|
*
|
|
* Note that received txcost is just neighbor's rxcost. Beta is rcv/max,
|
|
* we use inverse values of beta (i.e. max/rcv) to stay in integers.
|
|
*/
|
|
rxcost = MIN( cf->rxcost * max / rcv, BABEL_INFINITY);
|
|
txcost = MIN(nbr->txcost * max / rcv, BABEL_INFINITY);
|
|
break;
|
|
}
|
|
|
|
done:
|
|
/* If RX cost changed, send IHU with next Hello */
|
|
if (rxcost != nbr->rxcost)
|
|
{
|
|
nbr->rxcost = rxcost;
|
|
nbr->ihu_cnt = 0;
|
|
}
|
|
|
|
/* If link cost changed, run route selection */
|
|
if (txcost != nbr->cost)
|
|
{
|
|
TRACE(D_EVENTS, "Cost of nbr %I on %s changed from %u to %u",
|
|
nbr->addr, nbr->ifa->iface->name, nbr->cost, txcost);
|
|
|
|
nbr->cost = txcost;
|
|
|
|
struct babel_route *r; node *n;
|
|
WALK_LIST2(r, n, nbr->routes, neigh_route)
|
|
{
|
|
r->metric = babel_compute_metric(nbr, r->advert_metric);
|
|
babel_select_route(p, r->e, r);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* babel_announce_rte - announce selected route to the core
|
|
* @p: Babel protocol instance
|
|
* @e: Babel route entry to announce
|
|
*
|
|
* This function announces a Babel entry to the core if it has a selected
|
|
* incoming path, and retracts it otherwise. If there is no selected route but
|
|
* the entry is valid and ours, the unreachable route is announced instead.
|
|
*/
|
|
static void
|
|
babel_announce_rte(struct babel_proto *p, struct babel_entry *e)
|
|
{
|
|
struct babel_route *r = e->selected;
|
|
struct channel *c = (e->n.addr->type == NET_IP4) ? p->ip4_channel : p->ip6_channel;
|
|
|
|
if (r)
|
|
{
|
|
struct nexthop_adata nhad = {
|
|
.nh = {
|
|
.gw = r->next_hop,
|
|
.iface = r->neigh->ifa->iface,
|
|
},
|
|
.ad = {
|
|
.length = sizeof nhad - sizeof nhad.ad,
|
|
},
|
|
};
|
|
|
|
/*
|
|
* If we cannot find a reachable neighbour, set the entry to be onlink. This
|
|
* makes it possible to, e.g., assign /32 addresses on a mesh interface and
|
|
* have routing work.
|
|
*/
|
|
if (!neigh_find(&p->p, r->next_hop, r->neigh->ifa->iface, 0))
|
|
nhad.nh.flags = RNF_ONLINK;
|
|
|
|
struct {
|
|
ea_list l;
|
|
eattr a[7];
|
|
} eattrs = {
|
|
.l.count = ARRAY_SIZE(eattrs.a),
|
|
.a = {
|
|
EA_LITERAL_EMBEDDED(&ea_gen_preference, 0, c->preference),
|
|
EA_LITERAL_STORE_ADATA(&ea_gen_from, 0, &r->neigh->addr, sizeof(r->neigh->addr)),
|
|
EA_LITERAL_EMBEDDED(&ea_gen_source, 0, RTS_BABEL),
|
|
EA_LITERAL_STORE_ADATA(&ea_gen_nexthop, 0, nhad.ad.data, nhad.ad.length),
|
|
EA_LITERAL_EMBEDDED(&ea_babel_metric, 0, r->metric),
|
|
EA_LITERAL_STORE_ADATA(&ea_babel_router_id, 0, &r->router_id, sizeof(r->router_id)),
|
|
EA_LITERAL_EMBEDDED(&ea_babel_seqno, 0, r->seqno),
|
|
}
|
|
};
|
|
|
|
rte e0 = {
|
|
.attrs = &eattrs.l,
|
|
.src = p->p.main_source,
|
|
};
|
|
|
|
e->unreachable = 0;
|
|
rte_update(c, e->n.addr, &e0, p->p.main_source);
|
|
}
|
|
else if (e->valid && (e->router_id != p->router_id))
|
|
{
|
|
/* Unreachable */
|
|
ea_list *ea = NULL;
|
|
|
|
ea_set_attr_u32(&ea, &ea_gen_preference, 0, 1);
|
|
ea_set_attr_u32(&ea, &ea_gen_source, 0, RTS_BABEL);
|
|
ea_set_dest(&ea, 0, RTD_UNREACHABLE);
|
|
|
|
rte e0 = {
|
|
.attrs = ea,
|
|
.src = p->p.main_source,
|
|
};
|
|
|
|
e->unreachable = 1;
|
|
rte_update(c, e->n.addr, &e0, p->p.main_source);
|
|
}
|
|
else
|
|
{
|
|
/* Retraction */
|
|
e->unreachable = 0;
|
|
rte_update(c, e->n.addr, NULL, p->p.main_source);
|
|
}
|
|
}
|
|
|
|
/* Special case of babel_announce_rte() just for retraction */
|
|
static inline void
|
|
babel_announce_retraction(struct babel_proto *p, struct babel_entry *e)
|
|
{
|
|
struct channel *c = (e->n.addr->type == NET_IP4) ? p->ip4_channel : p->ip6_channel;
|
|
e->unreachable = 0;
|
|
rte_update(c, e->n.addr, NULL, p->p.main_source);
|
|
}
|
|
|
|
|
|
/**
|
|
* babel_select_route - select best route for given route entry
|
|
* @p: Babel protocol instance
|
|
* @e: Babel entry to select the best route for
|
|
* @mod: Babel route that was modified or NULL if unspecified
|
|
*
|
|
* Select the best reachable and feasible route for a given prefix among the
|
|
* routes received from peers, and propagate it to the nest. This just selects
|
|
* the reachable and feasible route with the lowest metric, but keeps selected
|
|
* the old one in case of tie.
|
|
*
|
|
* If no feasible route is available for a prefix that previously had a route
|
|
* selected, a seqno request is sent to try to get a valid route. If the entry
|
|
* is valid and not owned by us, the unreachable route is announced to the nest
|
|
* (to blackhole packets going to it, as per section 2.8). It is later removed
|
|
* by babel_expire_routes(). Otherwise, the route is just removed from the nest.
|
|
*
|
|
* Argument @mod is used to optimize best route calculation. When specified, the
|
|
* function can assume that only the @mod route was modified to avoid full best
|
|
* route selection and announcement when non-best route was modified in minor
|
|
* way. The caller is advised to not call babel_select_route() when no change is
|
|
* done (e.g. periodic route updates) to avoid unnecessary announcements of the
|
|
* same best route. The caller is not required to call the function in case of a
|
|
* retraction of a non-best route.
|
|
*
|
|
* Note that the function does not active triggered updates. That is done by
|
|
* babel_rt_notify() when the change is propagated back to Babel.
|
|
*/
|
|
static void
|
|
babel_select_route(struct babel_proto *p, struct babel_entry *e, struct babel_route *mod)
|
|
{
|
|
struct babel_route *r, *best = e->selected;
|
|
|
|
/* Shortcut if only non-best was modified */
|
|
if (mod && (mod != best))
|
|
{
|
|
/* Either select modified route, or keep old best route */
|
|
if ((mod->metric < (best ? best->metric : BABEL_INFINITY)) && mod->feasible)
|
|
best = mod;
|
|
else
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
/* Selected route may be modified and no longer admissible */
|
|
if (!best || (best->metric == BABEL_INFINITY) || !best->feasible)
|
|
best = NULL;
|
|
|
|
/* Find the best feasible route from all routes */
|
|
WALK_LIST(r, e->routes)
|
|
if ((r->metric < (best ? best->metric : BABEL_INFINITY)) && r->feasible)
|
|
best = r;
|
|
}
|
|
|
|
if (best)
|
|
{
|
|
if (best != e->selected)
|
|
TRACE(D_EVENTS, "Picked new route for prefix %N: router-id %lR metric %d",
|
|
e->n.addr, best->router_id, best->metric);
|
|
}
|
|
else if (e->selected)
|
|
{
|
|
/*
|
|
* We have lost all feasible routes. We have to broadcast seqno request
|
|
* (Section 3.8.2.1) and keep unreachable route for a while (section 2.8).
|
|
* The later is done automatically by babel_announce_rte().
|
|
*/
|
|
|
|
TRACE(D_EVENTS, "Lost feasible route for prefix %N", e->n.addr);
|
|
if (e->valid && (e->selected->router_id == e->router_id))
|
|
babel_add_seqno_request(p, e, e->selected->router_id, e->selected->seqno + 1, 0, NULL);
|
|
}
|
|
else
|
|
return;
|
|
|
|
e->selected = best;
|
|
babel_announce_rte(p, e);
|
|
}
|
|
|
|
/*
|
|
* Functions to send replies
|
|
*/
|
|
|
|
static void
|
|
babel_send_ack(struct babel_iface *ifa, ip_addr dest, u16 nonce)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
union babel_msg msg = {};
|
|
|
|
TRACE(D_PACKETS, "Sending ACK to %I with nonce %d", dest, nonce);
|
|
|
|
msg.type = BABEL_TLV_ACK;
|
|
msg.ack.nonce = nonce;
|
|
|
|
babel_send_unicast(&msg, ifa, dest);
|
|
}
|
|
|
|
static void
|
|
babel_build_ihu(union babel_msg *msg, struct babel_iface *ifa, struct babel_neighbor *n)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
|
|
msg->type = BABEL_TLV_IHU;
|
|
msg->ihu.addr = n->addr;
|
|
msg->ihu.rxcost = n->rxcost;
|
|
msg->ihu.interval = ifa->cf->ihu_interval;
|
|
|
|
TRACE(D_PACKETS, "Sending IHU for %I with rxcost %d interval %t",
|
|
msg->ihu.addr, msg->ihu.rxcost, (btime) msg->ihu.interval);
|
|
}
|
|
|
|
static void
|
|
babel_send_ihu(struct babel_iface *ifa, struct babel_neighbor *n)
|
|
{
|
|
union babel_msg msg = {};
|
|
babel_build_ihu(&msg, ifa, n);
|
|
babel_send_unicast(&msg, ifa, n->addr);
|
|
n->ihu_cnt = BABEL_IHU_INTERVAL_FACTOR;
|
|
}
|
|
|
|
static void
|
|
babel_send_ihus(struct babel_iface *ifa)
|
|
{
|
|
struct babel_neighbor *n;
|
|
WALK_LIST(n, ifa->neigh_list)
|
|
{
|
|
if (n->hello_cnt && (--n->ihu_cnt <= 0))
|
|
{
|
|
union babel_msg msg = {};
|
|
babel_build_ihu(&msg, ifa, n);
|
|
babel_enqueue(&msg, ifa);
|
|
n->ihu_cnt = BABEL_IHU_INTERVAL_FACTOR;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
babel_send_hello(struct babel_iface *ifa, uint interval)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
union babel_msg msg = {};
|
|
|
|
msg.type = BABEL_TLV_HELLO;
|
|
msg.hello.seqno = ifa->hello_seqno++;
|
|
msg.hello.interval = interval ?: ifa->cf->hello_interval;
|
|
|
|
TRACE(D_PACKETS, "Sending hello on %s with seqno %d interval %t",
|
|
ifa->ifname, msg.hello.seqno, (btime) msg.hello.interval);
|
|
|
|
babel_enqueue(&msg, ifa);
|
|
|
|
babel_send_ihus(ifa);
|
|
}
|
|
|
|
static void
|
|
babel_send_route_request(struct babel_proto *p, struct babel_entry *e, struct babel_neighbor *n)
|
|
{
|
|
union babel_msg msg = {};
|
|
|
|
TRACE(D_PACKETS, "Sending route request for %N to %I", e->n.addr, n->addr);
|
|
|
|
msg.type = BABEL_TLV_ROUTE_REQUEST;
|
|
net_copy(&msg.route_request.net, e->n.addr);
|
|
|
|
babel_send_unicast(&msg, n->ifa, n->addr);
|
|
}
|
|
|
|
static void
|
|
babel_send_wildcard_request(struct babel_iface *ifa)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
union babel_msg msg = {};
|
|
|
|
TRACE(D_PACKETS, "Sending wildcard route request on %s", ifa->ifname);
|
|
|
|
msg.type = BABEL_TLV_ROUTE_REQUEST;
|
|
msg.route_request.full = 1;
|
|
|
|
babel_enqueue(&msg, ifa);
|
|
}
|
|
|
|
static void
|
|
babel_send_seqno_request(struct babel_proto *p, struct babel_entry *e,
|
|
struct babel_seqno_request *sr, struct babel_neighbor *n)
|
|
{
|
|
union babel_msg msg = {};
|
|
|
|
msg.type = BABEL_TLV_SEQNO_REQUEST;
|
|
msg.seqno_request.hop_count = sr->hop_count;
|
|
msg.seqno_request.seqno = sr->seqno;
|
|
msg.seqno_request.router_id = sr->router_id;
|
|
net_copy(&msg.seqno_request.net, e->n.addr);
|
|
|
|
if (n)
|
|
{
|
|
TRACE(D_PACKETS, "Sending seqno request for %N router-id %lR seqno %d to %I on %s",
|
|
e->n.addr, sr->router_id, sr->seqno, n->addr, n->ifa->ifname);
|
|
|
|
babel_send_unicast(&msg, n->ifa, n->addr);
|
|
}
|
|
else
|
|
{
|
|
TRACE(D_PACKETS, "Sending broadcast seqno request for %N router-id %lR seqno %d",
|
|
e->n.addr, sr->router_id, sr->seqno);
|
|
|
|
struct babel_iface *ifa;
|
|
WALK_LIST(ifa, p->interfaces)
|
|
babel_enqueue(&msg, ifa);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* babel_send_update - send route table updates
|
|
* @ifa: Interface to transmit on
|
|
* @changed: Only send entries changed since this time
|
|
*
|
|
* This function produces update TLVs for all entries changed since the time
|
|
* indicated by the &changed parameter and queues them for transmission on the
|
|
* selected interface. During the process, the feasibility distance for each
|
|
* transmitted entry is updated.
|
|
*/
|
|
static void
|
|
babel_send_update_(struct babel_iface *ifa, btime changed, struct fib *rtable)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
|
|
/* Update increase was requested */
|
|
if (p->update_seqno_inc)
|
|
{
|
|
p->update_seqno++;
|
|
p->update_seqno_inc = 0;
|
|
}
|
|
|
|
FIB_WALK(rtable, struct babel_entry, e)
|
|
{
|
|
if (!e->valid)
|
|
continue;
|
|
|
|
/* Our own seqno might have changed, in which case we update the routes we
|
|
originate. */
|
|
if ((e->router_id == p->router_id) && (e->seqno < p->update_seqno))
|
|
{
|
|
e->seqno = p->update_seqno;
|
|
e->updated = current_time();
|
|
}
|
|
|
|
/* Skip routes that weren't updated since 'changed' time */
|
|
if (e->updated < changed)
|
|
continue;
|
|
|
|
TRACE(D_PACKETS, "Sending update for %N router-id %lR seqno %d metric %d",
|
|
e->n.addr, e->router_id, e->seqno, e->metric);
|
|
|
|
union babel_msg msg = {};
|
|
msg.type = BABEL_TLV_UPDATE;
|
|
msg.update.interval = ifa->cf->update_interval;
|
|
msg.update.seqno = e->seqno;
|
|
msg.update.metric = e->metric;
|
|
msg.update.router_id = e->router_id;
|
|
net_copy(&msg.update.net, e->n.addr);
|
|
|
|
if (e->n.addr->type == NET_IP4)
|
|
{
|
|
/* Always prefer IPv4 nexthop if set */
|
|
if (ipa_nonzero(ifa->next_hop_ip4))
|
|
msg.update.next_hop = ifa->next_hop_ip4;
|
|
|
|
/* Only send IPv6 nexthop if enabled */
|
|
else if (ifa->cf->ext_next_hop)
|
|
msg.update.next_hop = ifa->next_hop_ip6;
|
|
}
|
|
else
|
|
msg.update.next_hop = ifa->next_hop_ip6;
|
|
|
|
/* Do not send route if next hop is unknown, e.g. no configured IPv4 address */
|
|
if (ipa_zero(msg.update.next_hop))
|
|
continue;
|
|
|
|
babel_enqueue(&msg, ifa);
|
|
|
|
/* RFC 8966 3.7.3 - update feasibility distance for redistributed routes */
|
|
if (e->router_id != p->router_id)
|
|
{
|
|
struct babel_source *s = babel_get_source(p, e, e->router_id, msg.update.seqno);
|
|
s->expires = current_time() + BABEL_GARBAGE_INTERVAL;
|
|
|
|
if (gt_mod64k(msg.update.seqno, s->seqno) ||
|
|
((msg.update.seqno == s->seqno) && (msg.update.metric < s->metric)))
|
|
{
|
|
s->seqno = msg.update.seqno;
|
|
s->metric = msg.update.metric;
|
|
}
|
|
}
|
|
}
|
|
FIB_WALK_END;
|
|
}
|
|
|
|
static void
|
|
babel_send_update(struct babel_iface *ifa, btime changed)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
|
|
babel_send_update_(ifa, changed, &p->ip4_rtable);
|
|
babel_send_update_(ifa, changed, &p->ip6_rtable);
|
|
}
|
|
|
|
static void
|
|
babel_trigger_iface_update(struct babel_iface *ifa)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
|
|
/* Interface not active or already scheduled */
|
|
if (!ifa->up || ifa->want_triggered)
|
|
return;
|
|
|
|
TRACE(D_EVENTS, "Scheduling triggered updates for %s seqno %d",
|
|
ifa->iface->name, p->update_seqno);
|
|
|
|
ifa->want_triggered = current_time();
|
|
babel_iface_kick_timer(ifa);
|
|
}
|
|
|
|
/* Sends and update on all interfaces. */
|
|
static void
|
|
babel_trigger_update(struct babel_proto *p)
|
|
{
|
|
if (p->triggered)
|
|
return;
|
|
|
|
struct babel_iface *ifa;
|
|
WALK_LIST(ifa, p->interfaces)
|
|
babel_trigger_iface_update(ifa);
|
|
|
|
p->triggered = 1;
|
|
}
|
|
|
|
/* A retraction is an update with an infinite metric */
|
|
static void
|
|
babel_send_retraction(struct babel_iface *ifa, net_addr *n)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
union babel_msg msg = {};
|
|
|
|
TRACE(D_PACKETS, "Sending retraction for %N seqno %d", n, p->update_seqno);
|
|
|
|
msg.type = BABEL_TLV_UPDATE;
|
|
msg.update.interval = ifa->cf->update_interval;
|
|
msg.update.seqno = p->update_seqno;
|
|
msg.update.metric = BABEL_INFINITY;
|
|
msg.update.net = *n;
|
|
|
|
babel_enqueue(&msg, ifa);
|
|
}
|
|
|
|
static void
|
|
babel_send_wildcard_retraction(struct babel_iface *ifa)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
union babel_msg msg = {};
|
|
|
|
TRACE(D_PACKETS, "Sending wildcard retraction on %s", ifa->ifname);
|
|
|
|
msg.type = BABEL_TLV_UPDATE;
|
|
msg.update.wildcard = 1;
|
|
msg.update.interval = ifa->cf->update_interval;
|
|
msg.update.seqno = p->update_seqno;
|
|
msg.update.metric = BABEL_INFINITY;
|
|
|
|
babel_enqueue(&msg, ifa);
|
|
}
|
|
|
|
|
|
/*
|
|
* TLV handler helpers
|
|
*/
|
|
|
|
/* Update hello history according to Appendix A1 of the RFC */
|
|
static void
|
|
babel_update_hello_history(struct babel_neighbor *n, u16 seqno, uint interval)
|
|
{
|
|
/*
|
|
* Compute the difference between expected and received seqno (modulo 2^16).
|
|
* If the expected and received seqnos are within 16 of each other, the modular
|
|
* difference is going to be less than 16 for one of the directions. Otherwise,
|
|
* the values differ too much, so just reset the state.
|
|
*/
|
|
|
|
u16 delta = ((uint) seqno - (uint) n->next_hello_seqno);
|
|
|
|
if ((delta == 0) || (n->hello_cnt == 0))
|
|
{
|
|
/* Do nothing */
|
|
}
|
|
else if (delta <= 16)
|
|
{
|
|
/* Sending node decreased interval; fast-forward */
|
|
n->hello_map <<= delta;
|
|
n->hello_cnt = MIN(n->hello_cnt + delta, 16);
|
|
}
|
|
else if (delta >= 0xfff0)
|
|
{
|
|
u8 diff = (0xffff - delta);
|
|
/* Sending node increased interval; undo history */
|
|
n->hello_map >>= diff;
|
|
n->hello_cnt = (diff < n->hello_cnt) ? n->hello_cnt - diff : 0;
|
|
}
|
|
else
|
|
{
|
|
/* Note state reset - flush entries */
|
|
n->hello_map = n->hello_cnt = 0;
|
|
}
|
|
|
|
/* Current entry */
|
|
n->hello_map = (n->hello_map << 1) | 1;
|
|
n->next_hello_seqno = seqno+1;
|
|
if (n->hello_cnt < 16) n->hello_cnt++;
|
|
|
|
/* Update expiration */
|
|
n->hello_expiry = current_time() + BABEL_HELLO_EXPIRY_FACTOR(interval);
|
|
n->last_hello_int = interval;
|
|
|
|
/* Disable initial timeout */
|
|
n->init_expiry = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* TLV handlers
|
|
*/
|
|
|
|
void
|
|
babel_handle_ack_req(union babel_msg *m, struct babel_iface *ifa)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
struct babel_msg_ack_req *msg = &m->ack_req;
|
|
|
|
TRACE(D_PACKETS, "Handling ACK request nonce %d interval %t",
|
|
msg->nonce, (btime) msg->interval);
|
|
|
|
babel_send_ack(ifa, msg->sender, msg->nonce);
|
|
}
|
|
|
|
void
|
|
babel_handle_hello(union babel_msg *m, struct babel_iface *ifa)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
struct babel_msg_hello *msg = &m->hello;
|
|
|
|
TRACE(D_PACKETS, "Handling hello seqno %d interval %t",
|
|
msg->seqno, (btime) msg->interval);
|
|
|
|
struct babel_neighbor *n = babel_get_neighbor(ifa, msg->sender);
|
|
int first_hello = !n->hello_cnt;
|
|
|
|
babel_update_hello_history(n, msg->seqno, msg->interval);
|
|
babel_update_cost(n);
|
|
|
|
/* Speed up session establishment by sending IHU immediately */
|
|
if (first_hello)
|
|
babel_send_ihu(ifa, n);
|
|
}
|
|
|
|
void
|
|
babel_handle_ihu(union babel_msg *m, struct babel_iface *ifa)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
struct babel_msg_ihu *msg = &m->ihu;
|
|
|
|
/* Ignore IHUs that are not about us */
|
|
if ((msg->ae != BABEL_AE_WILDCARD) && !ipa_equal(msg->addr, ifa->addr))
|
|
return;
|
|
|
|
TRACE(D_PACKETS, "Handling IHU rxcost %d interval %t",
|
|
msg->rxcost, (btime) msg->interval);
|
|
|
|
struct babel_neighbor *n = babel_get_neighbor(ifa, msg->sender);
|
|
n->txcost = msg->rxcost;
|
|
n->ihu_expiry = current_time() + BABEL_IHU_EXPIRY_FACTOR(msg->interval);
|
|
babel_update_cost(n);
|
|
}
|
|
|
|
/**
|
|
* babel_handle_update - handle incoming route updates
|
|
* @m: Incoming update TLV
|
|
* @ifa: Interface the update was received on
|
|
*
|
|
* This function is called as a handler for update TLVs and handles the updating
|
|
* and maintenance of route entries in Babel's internal routing cache. The
|
|
* handling follows the actions described in the Babel RFC, and at the end of
|
|
* each update handling, babel_select_route() is called on the affected entry to
|
|
* optionally update the selected routes and propagate them to the core.
|
|
*/
|
|
void
|
|
babel_handle_update(union babel_msg *m, struct babel_iface *ifa)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
struct babel_msg_update *msg = &m->update;
|
|
|
|
struct babel_neighbor *nbr;
|
|
struct babel_entry *e;
|
|
struct babel_source *s;
|
|
struct babel_route *r, *best;
|
|
node *n;
|
|
int feasible, metric;
|
|
|
|
if (msg->wildcard)
|
|
TRACE(D_PACKETS, "Handling wildcard retraction", msg->seqno);
|
|
else
|
|
TRACE(D_PACKETS, "Handling update for %N with seqno %d metric %d",
|
|
&msg->net, msg->seqno, msg->metric);
|
|
|
|
nbr = babel_find_neighbor(ifa, msg->sender);
|
|
if (!nbr)
|
|
{
|
|
DBG("Babel: Haven't heard from neighbor %I; ignoring update.\n", msg->sender);
|
|
return;
|
|
}
|
|
|
|
if (msg->router_id == p->router_id)
|
|
{
|
|
DBG("Babel: Ignoring update for our own router ID.\n");
|
|
return;
|
|
}
|
|
|
|
struct channel *c = (msg->net.type == NET_IP4) ? p->ip4_channel : p->ip6_channel;
|
|
if (!c || (c->channel_state != CS_UP))
|
|
{
|
|
DBG("Babel: Ignoring update for inactive address family.\n");
|
|
return;
|
|
}
|
|
|
|
/* Reject IPv4 via IPv6 routes if disabled */
|
|
if ((msg->net.type == NET_IP4) && ipa_is_ip6(msg->next_hop) && !ifa->cf->ext_next_hop)
|
|
{
|
|
DBG("Babel: Ignoring disabled IPv4 via IPv6 route.\n");
|
|
return;
|
|
}
|
|
|
|
/* Retraction */
|
|
if (msg->metric == BABEL_INFINITY)
|
|
{
|
|
if (msg->wildcard)
|
|
{
|
|
/*
|
|
* Special case: This is a retraction of all prefixes announced by this
|
|
* neighbour (see second-to-last paragraph of section 4.4.9 in the RFC).
|
|
*/
|
|
WALK_LIST(n, nbr->routes)
|
|
{
|
|
r = SKIP_BACK(struct babel_route, neigh_route, n);
|
|
babel_retract_route(p, r);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e = babel_find_entry(p, &msg->net);
|
|
|
|
if (!e)
|
|
return;
|
|
|
|
/* The route entry indexed by neighbour */
|
|
r = babel_find_route(e, nbr);
|
|
|
|
if (!r)
|
|
return;
|
|
|
|
/* Router-id, next-hop and seqno are ignored for retractions */
|
|
babel_retract_route(p, r);
|
|
}
|
|
|
|
/* Done with retractions */
|
|
return;
|
|
}
|
|
|
|
/* Regular update */
|
|
e = babel_get_entry(p, &msg->net);
|
|
r = babel_get_route(p, e, nbr); /* the route entry indexed by neighbour */
|
|
s = babel_find_source(e, msg->router_id); /* for feasibility */
|
|
feasible = babel_is_feasible(s, msg->seqno, msg->metric);
|
|
metric = babel_compute_metric(nbr, msg->metric);
|
|
best = e->selected;
|
|
|
|
/*
|
|
* RFC 8966 3.8.2.2 - dealing with unfeasible updates. Generate a one-off
|
|
* (not retransmitted) unicast seqno request to the originator of this update.
|
|
* Note: !feasible -> s exists, check for 's' is just for clarity / safety.
|
|
*/
|
|
if (!feasible && s && (metric != BABEL_INFINITY) &&
|
|
(!best || (r == best) || (metric < best->metric)))
|
|
babel_generate_seqno_request(p, e, s->router_id, s->seqno + 1, nbr);
|
|
|
|
/* Special case - ignore unfeasible update to best route */
|
|
if (r == best && !feasible && (msg->router_id == r->router_id))
|
|
return;
|
|
|
|
r->expires = current_time() + BABEL_ROUTE_EXPIRY_FACTOR(msg->interval);
|
|
r->refresh_time = current_time() + BABEL_ROUTE_REFRESH_FACTOR(msg->interval);
|
|
|
|
/* No further processing if there is no change */
|
|
if ((r->feasible == feasible) && (r->seqno == msg->seqno) &&
|
|
(r->metric == metric) && (r->advert_metric == msg->metric) &&
|
|
(r->router_id == msg->router_id) && ipa_equal(r->next_hop, msg->next_hop))
|
|
return;
|
|
|
|
/* Last paragraph above - update the entry */
|
|
r->feasible = feasible;
|
|
r->seqno = msg->seqno;
|
|
r->metric = metric;
|
|
r->advert_metric = msg->metric;
|
|
r->router_id = msg->router_id;
|
|
r->next_hop = msg->next_hop;
|
|
|
|
/* If received update satisfies seqno request, we send triggered updates */
|
|
if (babel_satisfy_seqno_request(p, e, msg->router_id, msg->seqno))
|
|
{
|
|
babel_trigger_update(p);
|
|
e->updated = current_time();
|
|
}
|
|
|
|
babel_select_route(p, e, r);
|
|
}
|
|
|
|
void
|
|
babel_handle_route_request(union babel_msg *m, struct babel_iface *ifa)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
struct babel_msg_route_request *msg = &m->route_request;
|
|
|
|
/* RFC 8966 3.8.1.1 */
|
|
|
|
/* Wildcard request - full update on the interface */
|
|
if (msg->full)
|
|
{
|
|
TRACE(D_PACKETS, "Handling wildcard route request");
|
|
ifa->want_triggered = 1;
|
|
return;
|
|
}
|
|
|
|
TRACE(D_PACKETS, "Handling route request for %N", &msg->net);
|
|
|
|
/* Non-wildcard request - see if we have an entry for the route.
|
|
If not, send a retraction, otherwise send an update. */
|
|
struct babel_entry *e = babel_find_entry(p, &msg->net);
|
|
if (!e)
|
|
{
|
|
babel_send_retraction(ifa, &msg->net);
|
|
}
|
|
else
|
|
{
|
|
babel_trigger_iface_update(ifa);
|
|
e->updated = current_time();
|
|
}
|
|
}
|
|
|
|
static struct babel_neighbor *
|
|
babel_find_seqno_request_target(struct babel_entry *e, struct babel_neighbor *skip)
|
|
{
|
|
struct babel_route *r, *best_feasible = NULL, *best_any = NULL;
|
|
|
|
WALK_LIST(r, e->routes)
|
|
{
|
|
if (r->neigh == skip)
|
|
continue;
|
|
|
|
if (r->feasible && (!best_feasible || r->metric < best_feasible->metric))
|
|
best_feasible = r;
|
|
|
|
if (!best_any || r->metric < best_any->metric)
|
|
best_any = r;
|
|
}
|
|
|
|
if (best_feasible)
|
|
return best_feasible->neigh;
|
|
|
|
if (best_any)
|
|
return best_any->neigh;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
babel_handle_seqno_request(union babel_msg *m, struct babel_iface *ifa)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
struct babel_msg_seqno_request *msg = &m->seqno_request;
|
|
|
|
/* RFC 8966 3.8.1.2 */
|
|
|
|
TRACE(D_PACKETS, "Handling seqno request for %N router-id %lR seqno %d hop count %d",
|
|
&msg->net, msg->router_id, msg->seqno, msg->hop_count);
|
|
|
|
/* Ignore if we have no such entry or entry has infinite metric */
|
|
struct babel_entry *e = babel_find_entry(p, &msg->net);
|
|
if (!e || !e->valid || (e->metric == BABEL_INFINITY))
|
|
return;
|
|
|
|
/* Trigger update on incoming interface if we have a selected route with
|
|
different router id or seqno no smaller than requested */
|
|
if ((e->router_id != msg->router_id) || ge_mod64k(e->seqno, msg->seqno))
|
|
{
|
|
babel_trigger_iface_update(ifa);
|
|
e->updated = current_time();
|
|
return;
|
|
}
|
|
|
|
/* Seqno is larger; check if we own the router id */
|
|
if (msg->router_id == p->router_id)
|
|
{
|
|
/* Ours; seqno increase and trigger global update */
|
|
p->update_seqno_inc = 1;
|
|
babel_trigger_update(p);
|
|
}
|
|
else if (msg->hop_count > 1)
|
|
{
|
|
/* Not ours; forward if TTL allows it */
|
|
|
|
struct babel_neighbor *nbr, *target;
|
|
|
|
nbr = babel_find_neighbor(ifa, msg->sender);
|
|
if (!nbr)
|
|
return;
|
|
|
|
target = babel_find_seqno_request_target(e, nbr);
|
|
if (!target)
|
|
{
|
|
TRACE(D_PACKETS, "No neighbor to forward seqno request for %N router-id %lR seqno %d to",
|
|
e->n.addr, msg->router_id, msg->seqno);
|
|
return;
|
|
}
|
|
|
|
babel_add_seqno_request(p, e, msg->router_id, msg->seqno, msg->hop_count-1, target);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Authentication functions
|
|
*/
|
|
|
|
/**
|
|
* babel_auth_reset_index - Reset authentication index on interface
|
|
* @ifa: Interface to reset
|
|
*
|
|
* This function resets the authentication index and packet counter for an
|
|
* interface, and should be called on interface configuration, or when the
|
|
* packet counter overflows.
|
|
*/
|
|
void
|
|
babel_auth_reset_index(struct babel_iface *ifa)
|
|
{
|
|
random_bytes(ifa->auth_index, BABEL_AUTH_INDEX_LEN);
|
|
ifa->auth_pc = 1;
|
|
}
|
|
|
|
static void
|
|
babel_auth_send_challenge_request(struct babel_iface *ifa, struct babel_neighbor *n)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
union babel_msg msg = {};
|
|
|
|
TRACE(D_PACKETS, "Sending challenge request to %I on %s",
|
|
n->addr, ifa->ifname);
|
|
|
|
random_bytes(n->auth_nonce, BABEL_AUTH_NONCE_LEN);
|
|
n->auth_nonce_expiry = current_time() + BABEL_AUTH_CHALLENGE_TIMEOUT;
|
|
n->auth_next_challenge = current_time() + BABEL_AUTH_CHALLENGE_INTERVAL;
|
|
|
|
msg.type = BABEL_TLV_CHALLENGE_REQUEST;
|
|
msg.challenge.nonce_len = BABEL_AUTH_NONCE_LEN;
|
|
msg.challenge.nonce = n->auth_nonce;
|
|
|
|
babel_send_unicast(&msg, ifa, n->addr);
|
|
}
|
|
|
|
static void
|
|
babel_auth_send_challenge_reply(struct babel_iface *ifa, struct babel_neighbor *n, struct babel_msg_auth *rcv)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
union babel_msg msg = {};
|
|
|
|
TRACE(D_PACKETS, "Sending challenge reply to %I on %s",
|
|
n->addr, ifa->ifname);
|
|
|
|
n->auth_next_challenge_reply = current_time() + BABEL_AUTH_CHALLENGE_INTERVAL;
|
|
|
|
msg.type = BABEL_TLV_CHALLENGE_REPLY;
|
|
msg.challenge.nonce_len = rcv->challenge_len;
|
|
msg.challenge.nonce = rcv->challenge;
|
|
|
|
babel_send_unicast(&msg, ifa, n->addr);
|
|
}
|
|
|
|
int
|
|
babel_auth_check_pc(struct babel_iface *ifa, struct babel_msg_auth *msg)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
struct babel_neighbor *n;
|
|
|
|
/*
|
|
* We create the neighbour entry at this point because it makes it easier to
|
|
* rate limit challenge replies; this is explicitly allowed by the spec (see
|
|
* Section 4.3).
|
|
*/
|
|
n = babel_get_neighbor(ifa, msg->sender);
|
|
|
|
/* (3b) Handle challenge request */
|
|
if (msg->challenge_seen && (n->auth_next_challenge_reply <= current_time()))
|
|
babel_auth_send_challenge_reply(ifa, n, msg);
|
|
|
|
/* (4a) If PC TLV is missing, drop the packet */
|
|
if (!msg->pc_seen)
|
|
{
|
|
LOG_PKT_AUTH("Authentication failed for %I on %s - missing or invalid PC",
|
|
msg->sender, ifa->ifname);
|
|
return 0;
|
|
}
|
|
|
|
/* (4b) On successful challenge, update PC and index to current values */
|
|
if (msg->challenge_reply_seen &&
|
|
(n->auth_nonce_expiry > current_time()) &&
|
|
!memcmp(msg->challenge_reply, n->auth_nonce, BABEL_AUTH_NONCE_LEN))
|
|
{
|
|
n->auth_index_len = msg->index_len;
|
|
memcpy(n->auth_index, msg->index, msg->index_len);
|
|
|
|
n->auth_pc_unicast = msg->pc;
|
|
n->auth_pc_multicast = msg->pc;
|
|
n->auth_passed = 1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* (5) If index differs, send challenge and drop the packet */
|
|
if ((n->auth_index_len != msg->index_len) ||
|
|
memcmp(n->auth_index, msg->index, msg->index_len))
|
|
{
|
|
TRACE(D_PACKETS, "Index mismatch for packet from %I via %s",
|
|
msg->sender, ifa->ifname);
|
|
|
|
if (n->auth_next_challenge <= current_time())
|
|
babel_auth_send_challenge_request(ifa, n);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* (6) Index matches; only accept if PC is greater than last. We keep separate
|
|
* counters for unicast and multicast because multicast packets can be delayed
|
|
* significantly on wireless networks (enough to be received out of order).
|
|
* Separate counters are safe because the packet destination address is part
|
|
* of the MAC pseudo-header (so unicast packets can't be replayed as multicast
|
|
* and vice versa).
|
|
*/
|
|
u32 auth_pc = msg->unicast ? n->auth_pc_unicast : n->auth_pc_multicast;
|
|
if (auth_pc >= msg->pc)
|
|
{
|
|
LOG_PKT_AUTH("Authentication failed for %I on %s - "
|
|
"lower %s packet counter (rcv %u, old %u)",
|
|
msg->sender, ifa->ifname,
|
|
msg->unicast ? "unicast" : "multicast",
|
|
msg->pc, auth_pc);
|
|
return 0;
|
|
}
|
|
|
|
if (msg->unicast)
|
|
n->auth_pc_unicast = msg->pc;
|
|
else
|
|
n->auth_pc_multicast = msg->pc;
|
|
|
|
n->auth_passed = 1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* Babel interfaces
|
|
*/
|
|
|
|
/**
|
|
* babel_iface_timer - Babel interface timer handler
|
|
* @t: Timer
|
|
*
|
|
* This function is called by the per-interface timer and triggers sending of
|
|
* periodic Hello's and both triggered and periodic updates. Periodic Hello's
|
|
* and updates are simply handled by setting the next_{hello,regular} variables
|
|
* on the interface, and triggering an update (and resetting the variable)
|
|
* whenever 'now' exceeds that value.
|
|
*
|
|
* For triggered updates, babel_trigger_iface_update() will set the
|
|
* want_triggered field on the interface to a timestamp value. If this is set
|
|
* (and the next_triggered time has passed; this is a rate limiting mechanism),
|
|
* babel_send_update() will be called with this timestamp as the second
|
|
* parameter. This causes updates to be send consisting of only the routes that
|
|
* have changed since the time saved in want_triggered.
|
|
*
|
|
* Mostly when an update is triggered, the route being modified will be set to
|
|
* the value of 'now' at the time of the trigger; the >= comparison for
|
|
* selecting which routes to send in the update will make sure this is included.
|
|
*/
|
|
static void
|
|
babel_iface_timer(timer *t)
|
|
{
|
|
struct babel_iface *ifa = t->data;
|
|
struct babel_proto *p = ifa->proto;
|
|
btime hello_period = ifa->cf->hello_interval;
|
|
btime update_period = ifa->cf->update_interval;
|
|
btime now_ = current_time();
|
|
|
|
if (now_ >= ifa->next_hello)
|
|
{
|
|
babel_send_hello(ifa, 0);
|
|
ifa->next_hello += hello_period * (1 + (now_ - ifa->next_hello) / hello_period);
|
|
}
|
|
|
|
if (now_ >= ifa->next_regular)
|
|
{
|
|
TRACE(D_EVENTS, "Sending regular updates on %s", ifa->ifname);
|
|
babel_send_update(ifa, 0);
|
|
ifa->next_regular += update_period * (1 + (now_ - ifa->next_regular) / update_period);
|
|
ifa->want_triggered = 0;
|
|
p->triggered = 0;
|
|
}
|
|
else if (ifa->want_triggered && (now_ >= ifa->next_triggered))
|
|
{
|
|
TRACE(D_EVENTS, "Sending triggered updates on %s", ifa->ifname);
|
|
babel_send_update(ifa, ifa->want_triggered);
|
|
ifa->next_triggered = now_ + MIN(1 S, update_period / 2);
|
|
ifa->want_triggered = 0;
|
|
p->triggered = 0;
|
|
}
|
|
|
|
btime next_event = MIN(ifa->next_hello, ifa->next_regular);
|
|
if (ifa->want_triggered) next_event = MIN(next_event, ifa->next_triggered);
|
|
tm_set(ifa->timer, next_event);
|
|
}
|
|
|
|
static inline void
|
|
babel_iface_kick_timer(struct babel_iface *ifa)
|
|
{
|
|
if (ifa->timer->expires > (current_time() + 100 MS))
|
|
tm_start(ifa->timer, 100 MS);
|
|
}
|
|
|
|
static void
|
|
babel_iface_start(struct babel_iface *ifa)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
|
|
TRACE(D_EVENTS, "Starting interface %s", ifa->ifname);
|
|
|
|
ifa->next_hello = current_time() + (random() % ifa->cf->hello_interval);
|
|
ifa->next_regular = current_time() + (random() % ifa->cf->update_interval);
|
|
ifa->next_triggered = current_time() + MIN(1 S, ifa->cf->update_interval / 2);
|
|
ifa->want_triggered = 0; /* We send an immediate update (below) */
|
|
tm_start(ifa->timer, 100 MS);
|
|
ifa->up = 1;
|
|
|
|
babel_send_hello(ifa, 0);
|
|
babel_send_wildcard_retraction(ifa);
|
|
babel_send_wildcard_request(ifa);
|
|
babel_send_update(ifa, 0); /* Full update */
|
|
}
|
|
|
|
static void
|
|
babel_iface_stop(struct babel_iface *ifa)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
struct babel_neighbor *nbr;
|
|
struct babel_route *r;
|
|
node *n;
|
|
|
|
TRACE(D_EVENTS, "Stopping interface %s", ifa->ifname);
|
|
|
|
/*
|
|
* Rather than just flushing the neighbours, we set the metric of their routes
|
|
* to infinity. This allows us to keep the neighbour hello state for when the
|
|
* interface comes back up. The routes will also be kept until they expire.
|
|
*/
|
|
WALK_LIST(nbr, ifa->neigh_list)
|
|
{
|
|
WALK_LIST(n, nbr->routes)
|
|
{
|
|
r = SKIP_BACK(struct babel_route, neigh_route, n);
|
|
babel_retract_route(p, r);
|
|
}
|
|
}
|
|
|
|
tm_stop(ifa->timer);
|
|
ifa->up = 0;
|
|
}
|
|
|
|
static inline int
|
|
babel_iface_link_up(struct babel_iface *ifa)
|
|
{
|
|
return !ifa->cf->check_link || (ifa->iface->flags & IF_LINK_UP);
|
|
}
|
|
|
|
static void
|
|
babel_iface_update_state(struct babel_iface *ifa)
|
|
{
|
|
int up = ifa->sk && babel_iface_link_up(ifa);
|
|
|
|
if (up == ifa->up)
|
|
return;
|
|
|
|
if (up)
|
|
babel_iface_start(ifa);
|
|
else
|
|
babel_iface_stop(ifa);
|
|
}
|
|
|
|
static void
|
|
babel_iface_update_addr4(struct babel_iface *ifa)
|
|
{
|
|
struct babel_proto *p = ifa->proto;
|
|
|
|
ip_addr addr4 = ifa->iface->addr4 ? ifa->iface->addr4->ip : IPA_NONE;
|
|
ifa->next_hop_ip4 = ipa_nonzero(ifa->cf->next_hop_ip4) ? ifa->cf->next_hop_ip4 : addr4;
|
|
|
|
if (ipa_zero(ifa->next_hop_ip4) && p->ip4_channel && !ifa->cf->ext_next_hop)
|
|
log(L_WARN "%s: Missing IPv4 next hop address for %s", p->p.name, ifa->ifname);
|
|
|
|
if (ifa->up)
|
|
babel_iface_kick_timer(ifa);
|
|
}
|
|
|
|
static void
|
|
babel_iface_update_buffers(struct babel_iface *ifa)
|
|
{
|
|
if (!ifa->sk)
|
|
return;
|
|
|
|
uint mtu = MAX(BABEL_MIN_MTU, ifa->iface->mtu);
|
|
uint rbsize = ifa->cf->rx_buffer ?: mtu;
|
|
uint tbsize = ifa->cf->tx_length ?: mtu;
|
|
rbsize = MAX(rbsize, tbsize);
|
|
|
|
sk_set_rbsize(ifa->sk, rbsize);
|
|
sk_set_tbsize(ifa->sk, tbsize);
|
|
|
|
ifa->tx_length = tbsize - BABEL_OVERHEAD;
|
|
|
|
babel_auth_set_tx_overhead(ifa);
|
|
}
|
|
|
|
static struct babel_iface*
|
|
babel_find_iface(struct babel_proto *p, struct iface *what)
|
|
{
|
|
struct babel_iface *ifa;
|
|
|
|
WALK_LIST (ifa, p->interfaces)
|
|
if (ifa->iface == what)
|
|
return ifa;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
babel_iface_locked(void *_ifa)
|
|
{
|
|
struct babel_iface *ifa = _ifa;
|
|
struct babel_proto *p = ifa->proto;
|
|
|
|
if (!babel_open_socket(ifa))
|
|
{
|
|
log(L_ERR "%s: Cannot open socket for %s", p->p.name, ifa->iface->name);
|
|
return;
|
|
}
|
|
|
|
babel_iface_update_buffers(ifa);
|
|
babel_iface_update_state(ifa);
|
|
}
|
|
|
|
static void
|
|
babel_add_iface(struct babel_proto *p, struct iface *new, struct babel_iface_config *ic)
|
|
{
|
|
struct babel_iface *ifa;
|
|
|
|
TRACE(D_EVENTS, "Adding interface %s", new->name);
|
|
|
|
pool *pool = rp_new(p->p.pool, proto_domain(&p->p), new->name);
|
|
|
|
ifa = mb_allocz(pool, sizeof(struct babel_iface));
|
|
ifa->proto = p;
|
|
ifa->iface = new;
|
|
ifa->cf = ic;
|
|
ifa->pool = pool;
|
|
ifa->ifname = new->name;
|
|
ifa->addr = new->llv6->ip;
|
|
|
|
add_tail(&p->interfaces, NODE ifa);
|
|
|
|
ip_addr addr4 = new->addr4 ? new->addr4->ip : IPA_NONE;
|
|
ifa->next_hop_ip4 = ipa_nonzero(ic->next_hop_ip4) ? ic->next_hop_ip4 : addr4;
|
|
ifa->next_hop_ip6 = ipa_nonzero(ic->next_hop_ip6) ? ic->next_hop_ip6 : ifa->addr;
|
|
|
|
if (ipa_zero(ifa->next_hop_ip4) && p->ip4_channel && !ic->ext_next_hop)
|
|
log(L_WARN "%s: Missing IPv4 next hop address for %s", p->p.name, ifa->ifname);
|
|
|
|
init_list(&ifa->neigh_list);
|
|
ifa->hello_seqno = 1;
|
|
|
|
if (ic->auth_type != BABEL_AUTH_NONE)
|
|
babel_auth_reset_index(ifa);
|
|
|
|
ifa->timer = tm_new_init(ifa->pool, babel_iface_timer, ifa, 0, 0);
|
|
|
|
init_list(&ifa->msg_queue);
|
|
ifa->send_event = ev_new_init(ifa->pool, babel_send_queue, ifa);
|
|
|
|
struct object_lock *lock = olock_new(ifa->pool);
|
|
lock->type = OBJLOCK_UDP;
|
|
lock->addr = IP6_BABEL_ROUTERS;
|
|
lock->port = ifa->cf->port;
|
|
lock->iface = ifa->iface;
|
|
lock->event = (event) {
|
|
.hook = babel_iface_locked,
|
|
.data = ifa,
|
|
};
|
|
lock->target = &global_event_list;
|
|
|
|
olock_acquire(lock);
|
|
}
|
|
|
|
static void
|
|
babel_remove_iface(struct babel_proto *p, struct babel_iface *ifa)
|
|
{
|
|
TRACE(D_EVENTS, "Removing interface %s", ifa->iface->name);
|
|
|
|
struct babel_neighbor *n;
|
|
WALK_LIST_FIRST(n, ifa->neigh_list)
|
|
babel_flush_neighbor(p, n);
|
|
|
|
rem_node(NODE ifa);
|
|
|
|
rp_free(ifa->pool); /* contains ifa itself, locks, socket, etc */
|
|
}
|
|
|
|
static int
|
|
iface_is_valid(struct babel_proto *p, struct iface *iface)
|
|
{
|
|
if (!(iface->flags & IF_MULTICAST))
|
|
{
|
|
log(L_ERR "%s: Interface %s does not support multicast",
|
|
p->p.name, iface->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
babel_if_notify(struct proto *P, unsigned flags, struct iface *iface)
|
|
{
|
|
struct babel_proto *p = (void *) P;
|
|
struct babel_config *cf = (void *) P->cf;
|
|
struct babel_iface *ifa = babel_find_iface(p, iface);
|
|
|
|
if (iface->flags & IF_IGNORE)
|
|
return;
|
|
|
|
/* Add, remove or restart interface */
|
|
if (flags & (IF_CHANGE_UPDOWN | IF_CHANGE_LLV6))
|
|
{
|
|
if (ifa)
|
|
babel_remove_iface(p, ifa);
|
|
|
|
if (!(iface->flags & IF_UP))
|
|
return;
|
|
|
|
/* Ignore ifaces without link-local address */
|
|
if (!iface->llv6)
|
|
return;
|
|
|
|
struct babel_iface_config *ic = (void *) iface_patt_find(&cf->iface_list, iface, NULL);
|
|
|
|
if (ic && iface_is_valid(p, iface))
|
|
babel_add_iface(p, iface, ic);
|
|
|
|
return;
|
|
}
|
|
|
|
if (!ifa)
|
|
return;
|
|
|
|
if (flags & IF_CHANGE_ADDR4)
|
|
babel_iface_update_addr4(ifa);
|
|
|
|
if (flags & IF_CHANGE_MTU)
|
|
babel_iface_update_buffers(ifa);
|
|
|
|
if (flags & IF_CHANGE_LINK)
|
|
babel_iface_update_state(ifa);
|
|
}
|
|
|
|
static int
|
|
babel_reconfigure_iface(struct babel_proto *p, struct babel_iface *ifa, struct babel_iface_config *new)
|
|
{
|
|
struct babel_iface_config *old = ifa->cf;
|
|
|
|
/* Change of these options would require to reset the iface socket */
|
|
if ((new->port != old->port) ||
|
|
(new->tx_tos != old->tx_tos) ||
|
|
(new->tx_priority != old->tx_priority))
|
|
return 0;
|
|
|
|
TRACE(D_EVENTS, "Reconfiguring interface %s", ifa->iface->name);
|
|
|
|
ifa->cf = new;
|
|
|
|
ip_addr addr4 = ifa->iface->addr4 ? ifa->iface->addr4->ip : IPA_NONE;
|
|
ifa->next_hop_ip4 = ipa_nonzero(new->next_hop_ip4) ? new->next_hop_ip4 : addr4;
|
|
ifa->next_hop_ip6 = ipa_nonzero(new->next_hop_ip6) ? new->next_hop_ip6 : ifa->addr;
|
|
|
|
babel_iface_update_buffers(ifa);
|
|
|
|
if ((new->auth_type != BABEL_AUTH_NONE) && (new->auth_type != old->auth_type))
|
|
babel_auth_reset_index(ifa);
|
|
|
|
if (ipa_zero(ifa->next_hop_ip4) && p->ip4_channel && !new->ext_next_hop)
|
|
log(L_WARN "%s: Missing IPv4 next hop address for %s", p->p.name, ifa->ifname);
|
|
|
|
if (ifa->next_hello > (current_time() + new->hello_interval))
|
|
ifa->next_hello = current_time() + (random() % new->hello_interval);
|
|
|
|
if (ifa->next_regular > (current_time() + new->update_interval))
|
|
ifa->next_regular = current_time() + (random() % new->update_interval);
|
|
|
|
if (new->check_link != old->check_link)
|
|
babel_iface_update_state(ifa);
|
|
|
|
if (ifa->up)
|
|
babel_iface_kick_timer(ifa);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
babel_reconfigure_ifaces(struct babel_proto *p, struct babel_config *cf)
|
|
{
|
|
IFACE_WALK(iface)
|
|
{
|
|
if (p->p.vrf && p->p.vrf != iface->master)
|
|
continue;
|
|
|
|
if (!(iface->flags & IF_UP))
|
|
continue;
|
|
|
|
/* Ignore ifaces without link-local address */
|
|
if (!iface->llv6)
|
|
continue;
|
|
|
|
struct babel_iface *ifa = babel_find_iface(p, iface);
|
|
struct babel_iface_config *ic = (void *) iface_patt_find(&cf->iface_list, iface, NULL);
|
|
|
|
if (ic && !iface_is_valid(p, iface))
|
|
ic = NULL;
|
|
|
|
if (ifa && ic)
|
|
{
|
|
if (babel_reconfigure_iface(p, ifa, ic))
|
|
continue;
|
|
|
|
/* Hard restart */
|
|
log(L_INFO "%s: Restarting interface %s", p->p.name, ifa->iface->name);
|
|
babel_remove_iface(p, ifa);
|
|
babel_add_iface(p, iface, ic);
|
|
}
|
|
|
|
if (ifa && !ic)
|
|
babel_remove_iface(p, ifa);
|
|
|
|
if (!ifa && ic)
|
|
babel_add_iface(p, iface, ic);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Debugging and info output functions
|
|
*/
|
|
|
|
static void
|
|
babel_dump_source(struct babel_source *s)
|
|
{
|
|
debug("Source router_id %lR seqno %d metric %d expires %t\n",
|
|
s->router_id, s->seqno, s->metric,
|
|
s->expires ? s->expires - current_time() : 0);
|
|
}
|
|
|
|
static void
|
|
babel_dump_route(struct babel_route *r)
|
|
{
|
|
debug("Route neigh %I if %s seqno %d metric %d/%d router_id %lR expires %t\n",
|
|
r->neigh->addr, r->neigh->ifa->ifname, r->seqno, r->advert_metric, r->metric,
|
|
r->router_id, r->expires ? r->expires - current_time() : 0);
|
|
}
|
|
|
|
static void
|
|
babel_dump_entry(struct babel_entry *e)
|
|
{
|
|
struct babel_source *s;
|
|
struct babel_route *r;
|
|
|
|
debug("Babel: Entry %N:\n", e->n.addr);
|
|
|
|
WALK_LIST(s,e->sources)
|
|
{ debug(" "); babel_dump_source(s); }
|
|
|
|
WALK_LIST(r,e->routes)
|
|
{
|
|
debug(" ");
|
|
if (r == e->selected) debug("*");
|
|
babel_dump_route(r);
|
|
}
|
|
}
|
|
|
|
static void
|
|
babel_dump_neighbor(struct babel_neighbor *n)
|
|
{
|
|
debug("Neighbor %I txcost %d hello_map %x next seqno %d expires %t/%t\n",
|
|
n->addr, n->txcost, n->hello_map, n->next_hello_seqno,
|
|
n->hello_expiry ? n->hello_expiry - current_time() : 0,
|
|
n->ihu_expiry ? n->ihu_expiry - current_time() : 0);
|
|
}
|
|
|
|
static void
|
|
babel_dump_iface(struct babel_iface *ifa)
|
|
{
|
|
struct babel_neighbor *n;
|
|
|
|
debug("Babel: Interface %s addr %I rxcost %d type %d hello seqno %d intervals %t %t",
|
|
ifa->ifname, ifa->addr, ifa->cf->rxcost, ifa->cf->type, ifa->hello_seqno,
|
|
ifa->cf->hello_interval, ifa->cf->update_interval);
|
|
debug(" next hop v4 %I next hop v6 %I\n", ifa->next_hop_ip4, ifa->next_hop_ip6);
|
|
|
|
WALK_LIST(n, ifa->neigh_list)
|
|
{ debug(" "); babel_dump_neighbor(n); }
|
|
}
|
|
|
|
static void
|
|
babel_dump(struct proto *P)
|
|
{
|
|
struct babel_proto *p = (struct babel_proto *) P;
|
|
struct babel_iface *ifa;
|
|
|
|
debug("Babel: router id %lR update seqno %d\n", p->router_id, p->update_seqno);
|
|
|
|
WALK_LIST(ifa, p->interfaces)
|
|
babel_dump_iface(ifa);
|
|
|
|
FIB_WALK(&p->ip4_rtable, struct babel_entry, e)
|
|
{
|
|
babel_dump_entry(e);
|
|
}
|
|
FIB_WALK_END;
|
|
FIB_WALK(&p->ip6_rtable, struct babel_entry, e)
|
|
{
|
|
babel_dump_entry(e);
|
|
}
|
|
FIB_WALK_END;
|
|
}
|
|
|
|
static void
|
|
babel_get_route_info(const rte *rte, byte *buf)
|
|
{
|
|
u64 rid = 0;
|
|
eattr *e = ea_find(rte->attrs, &ea_babel_router_id);
|
|
if (e)
|
|
memcpy(&rid, e->u.ptr->data, sizeof(u64));
|
|
|
|
buf += bsprintf(buf, " (%d/%d) [%lR]",
|
|
rt_get_preference(rte),
|
|
ea_get_int(rte->attrs, &ea_babel_metric, BABEL_INFINITY), rid);
|
|
}
|
|
|
|
static void
|
|
babel_router_id_format(const eattr *a, byte *buf, uint len)
|
|
{
|
|
u64 rid = 0;
|
|
memcpy(&rid, a->u.ptr->data, sizeof(u64));
|
|
bsnprintf(buf, len, "%lR", rid);
|
|
}
|
|
|
|
static struct ea_class ea_babel_metric = {
|
|
.name = "babel_metric",
|
|
.type = T_INT,
|
|
};
|
|
|
|
static struct ea_class ea_babel_router_id = {
|
|
.name = "babel_router_id",
|
|
.type = T_OPAQUE,
|
|
.readonly = 1,
|
|
.format = babel_router_id_format,
|
|
};
|
|
|
|
static struct ea_class ea_babel_seqno = {
|
|
.name = "babel_seqno",
|
|
.type = T_INT,
|
|
.readonly = 1,
|
|
.hidden = 1,
|
|
};
|
|
|
|
|
|
void
|
|
babel_show_interfaces(struct proto *P, const char *iff)
|
|
{
|
|
struct babel_proto *p = (void *) P;
|
|
struct babel_iface *ifa = NULL;
|
|
struct babel_neighbor *nbr = NULL;
|
|
|
|
if (p->p.proto_state != PS_UP)
|
|
{
|
|
cli_msg(-1023, "%s: is not up", p->p.name);
|
|
return;
|
|
}
|
|
|
|
cli_msg(-1023, "%s:", p->p.name);
|
|
cli_msg(-1023, "%-10s %-6s %-5s %7s %6s %7s %-15s %s",
|
|
"Interface", "State", "Auth", "RX cost", "Nbrs", "Timer",
|
|
"Next hop (v4)", "Next hop (v6)");
|
|
|
|
WALK_LIST(ifa, p->interfaces)
|
|
{
|
|
if (iff && !patmatch(iff, ifa->iface->name))
|
|
continue;
|
|
|
|
int nbrs = 0;
|
|
WALK_LIST(nbr, ifa->neigh_list)
|
|
nbrs++;
|
|
|
|
btime timer = MIN(ifa->next_regular, ifa->next_hello) - current_time();
|
|
cli_msg(-1023, "%-10s %-6s %-5s %7u %6u %7t %-15I %I",
|
|
ifa->iface->name, (ifa->up ? "Up" : "Down"),
|
|
(ifa->cf->auth_type == BABEL_AUTH_MAC ?
|
|
(ifa->cf->auth_permissive ? "Perm" : "Yes") : "No"),
|
|
ifa->cf->rxcost, nbrs, MAX(timer, 0),
|
|
ifa->next_hop_ip4, ifa->next_hop_ip6);
|
|
}
|
|
}
|
|
|
|
void
|
|
babel_show_neighbors(struct proto *P, const char *iff)
|
|
{
|
|
struct babel_proto *p = (void *) P;
|
|
struct babel_iface *ifa = NULL;
|
|
struct babel_neighbor *n = NULL;
|
|
struct babel_route *r = NULL;
|
|
|
|
if (p->p.proto_state != PS_UP)
|
|
{
|
|
cli_msg(-1024, "%s: is not up", p->p.name);
|
|
return;
|
|
}
|
|
|
|
cli_msg(-1024, "%s:", p->p.name);
|
|
cli_msg(-1024, "%-25s %-10s %6s %6s %6s %7s %4s",
|
|
"IP address", "Interface", "Metric", "Routes", "Hellos", "Expires", "Auth");
|
|
|
|
WALK_LIST(ifa, p->interfaces)
|
|
{
|
|
if (iff && !patmatch(iff, ifa->iface->name))
|
|
continue;
|
|
|
|
WALK_LIST(n, ifa->neigh_list)
|
|
{
|
|
int rts = 0;
|
|
WALK_LIST(r, n->routes)
|
|
rts++;
|
|
|
|
uint hellos = u32_popcount(n->hello_map);
|
|
btime timer = (n->hello_expiry ?: n->init_expiry) - current_time();
|
|
cli_msg(-1024, "%-25I %-10s %6u %6u %6u %7t %-4s",
|
|
n->addr, ifa->iface->name, n->cost, rts, hellos, MAX(timer, 0),
|
|
n->auth_passed ? "Yes" : "No");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
babel_show_entries_(struct babel_proto *p, struct fib *rtable)
|
|
{
|
|
int width = babel_sadr_enabled(p) ? -54 : -24;
|
|
|
|
FIB_WALK(rtable, struct babel_entry, e)
|
|
{
|
|
struct babel_route *r = NULL;
|
|
uint rts = 0, srcs = 0;
|
|
node *n;
|
|
|
|
WALK_LIST(n, e->routes)
|
|
rts++;
|
|
|
|
WALK_LIST(n, e->sources)
|
|
srcs++;
|
|
|
|
if (e->valid)
|
|
cli_msg(-1025, "%-*N %-23lR %6u %5u %7u %7u", width,
|
|
e->n.addr, e->router_id, e->metric, e->seqno, rts, srcs);
|
|
else if (r = e->selected)
|
|
cli_msg(-1025, "%-*N %-23lR %6u %5u %7u %7u", width,
|
|
e->n.addr, r->router_id, r->metric, r->seqno, rts, srcs);
|
|
else
|
|
cli_msg(-1025, "%-*N %-23s %6s %5s %7u %7u", width,
|
|
e->n.addr, "<none>", "-", "-", rts, srcs);
|
|
}
|
|
FIB_WALK_END;
|
|
}
|
|
|
|
void
|
|
babel_show_entries(struct proto *P)
|
|
{
|
|
struct babel_proto *p = (void *) P;
|
|
int width = babel_sadr_enabled(p) ? -54 : -24;
|
|
|
|
if (p->p.proto_state != PS_UP)
|
|
{
|
|
cli_msg(-1025, "%s: is not up", p->p.name);
|
|
return;
|
|
}
|
|
|
|
cli_msg(-1025, "%s:", p->p.name);
|
|
cli_msg(-1025, "%-*s %-23s %6s %5s %7s %7s", width,
|
|
"Prefix", "Router ID", "Metric", "Seqno", "Routes", "Sources");
|
|
|
|
babel_show_entries_(p, &p->ip4_rtable);
|
|
babel_show_entries_(p, &p->ip6_rtable);
|
|
}
|
|
|
|
static void
|
|
babel_show_routes_(struct babel_proto *p, struct fib *rtable)
|
|
{
|
|
int width = babel_sadr_enabled(p) ? -54 : -24;
|
|
|
|
FIB_WALK(rtable, struct babel_entry, e)
|
|
{
|
|
struct babel_route *r;
|
|
WALK_LIST(r, e->routes)
|
|
{
|
|
char c = (r == e->selected) ? '*' : (r->feasible ? '+' : ' ');
|
|
btime time = r->expires ? r->expires - current_time() : 0;
|
|
cli_msg(-1025, "%-*N %-25I %-10s %5u %c %5u %7t", width,
|
|
e->n.addr, r->next_hop, r->neigh->ifa->ifname,
|
|
r->metric, c, r->seqno, MAX(time, 0));
|
|
}
|
|
}
|
|
FIB_WALK_END;
|
|
}
|
|
|
|
void
|
|
babel_show_routes(struct proto *P)
|
|
{
|
|
struct babel_proto *p = (void *) P;
|
|
int width = babel_sadr_enabled(p) ? -54 : -24;
|
|
|
|
if (p->p.proto_state != PS_UP)
|
|
{
|
|
cli_msg(-1025, "%s: is not up", p->p.name);
|
|
return;
|
|
}
|
|
|
|
cli_msg(-1025, "%s:", p->p.name);
|
|
cli_msg(-1025, "%-*s %-25s %-9s %6s F %5s %7s", width,
|
|
"Prefix", "Nexthop", "Interface", "Metric", "Seqno", "Expires");
|
|
|
|
babel_show_routes_(p, &p->ip4_rtable);
|
|
babel_show_routes_(p, &p->ip6_rtable);
|
|
}
|
|
|
|
|
|
/*
|
|
* Babel protocol glue
|
|
*/
|
|
|
|
/**
|
|
* babel_timer - global timer hook
|
|
* @t: Timer
|
|
*
|
|
* This function is called by the global protocol instance timer and handles
|
|
* expiration of routes and neighbours as well as pruning of the seqno request
|
|
* cache.
|
|
*/
|
|
static void
|
|
babel_timer(timer *t)
|
|
{
|
|
struct babel_proto *p = t->data;
|
|
|
|
babel_expire_routes(p);
|
|
babel_expire_neighbors(p);
|
|
}
|
|
|
|
static inline void
|
|
babel_kick_timer(struct babel_proto *p)
|
|
{
|
|
if (p->timer->expires > (current_time() + 100 MS))
|
|
tm_start(p->timer, 100 MS);
|
|
}
|
|
|
|
|
|
static int
|
|
babel_preexport(struct channel *C, struct rte *new)
|
|
{
|
|
if (new->src->owner != &C->proto->sources)
|
|
return 0;
|
|
|
|
/* Reject our own unreachable routes */
|
|
eattr *ea = ea_find(new->attrs, &ea_gen_nexthop);
|
|
struct nexthop_adata *nhad = (void *) ea->u.ptr;
|
|
if (!NEXTHOP_IS_REACHABLE(nhad))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
babel_entry_invalidate(struct babel_entry *e)
|
|
{
|
|
e->valid = BABEL_ENTRY_STALE;
|
|
e->metric = BABEL_INFINITY;
|
|
e->updated = current_time();
|
|
}
|
|
|
|
|
|
/*
|
|
* babel_rt_notify - core tells us about new route (possibly our own),
|
|
* so store it into our data structures.
|
|
*/
|
|
static void
|
|
babel_rt_notify(struct proto *P, struct channel *c UNUSED, const net_addr *net,
|
|
struct rte *new, const struct rte *old UNUSED)
|
|
{
|
|
struct babel_proto *p = (void *) P;
|
|
struct babel_entry *e;
|
|
|
|
if (new)
|
|
{
|
|
/* Update */
|
|
uint rt_seqno;
|
|
uint rt_metric = ea_get_int(new->attrs, &ea_babel_metric, 0);
|
|
u64 rt_router_id = 0;
|
|
|
|
if (new->src->owner == &P->sources)
|
|
{
|
|
rt_seqno = ea_get_int(new->attrs, &ea_babel_seqno, 0);
|
|
eattr *e = ea_find(new->attrs, &ea_babel_router_id);
|
|
if (e)
|
|
memcpy(&rt_router_id, e->u.ptr->data, sizeof(u64));
|
|
}
|
|
else
|
|
{
|
|
rt_seqno = p->update_seqno;
|
|
rt_router_id = p->router_id;
|
|
}
|
|
|
|
if (rt_metric > BABEL_INFINITY)
|
|
{
|
|
log(L_WARN "%s: Invalid babel_metric value %u for route %N",
|
|
p->p.name, rt_metric, net);
|
|
rt_metric = BABEL_INFINITY;
|
|
}
|
|
|
|
e = babel_get_entry(p, net);
|
|
|
|
/* Activate triggered updates */
|
|
if (!(e->valid & BABEL_ENTRY_VALID) ||
|
|
(e->router_id != rt_router_id))
|
|
{
|
|
babel_trigger_update(p);
|
|
e->updated = current_time();
|
|
}
|
|
|
|
e->valid = BABEL_ENTRY_VALID;
|
|
e->seqno = rt_seqno;
|
|
e->metric = rt_metric;
|
|
e->router_id = rt_router_id;
|
|
}
|
|
else
|
|
{
|
|
/* Withdraw */
|
|
e = babel_find_entry(p, net);
|
|
|
|
if (!e || e->valid != BABEL_ENTRY_VALID)
|
|
return;
|
|
|
|
babel_entry_invalidate(e);
|
|
babel_trigger_update(p);
|
|
}
|
|
}
|
|
|
|
static void
|
|
babel_feed_begin(struct channel *C)
|
|
{
|
|
if (!C->refeeding || C->refeed_req.hook)
|
|
return;
|
|
|
|
struct babel_proto *p = (struct babel_proto *) C->proto;
|
|
struct fib *rtable = (C->net_type == NET_IP4) ? &p->ip4_rtable : &p->ip6_rtable;
|
|
|
|
FIB_WALK(rtable, struct babel_entry, e)
|
|
if (e->valid == BABEL_ENTRY_VALID)
|
|
e->valid = BABEL_ENTRY_REFEEDING;
|
|
FIB_WALK_END;
|
|
}
|
|
|
|
static void
|
|
babel_feed_end(struct channel *C)
|
|
{
|
|
if (!C->refeeding || C->refeed_req.hook)
|
|
return;
|
|
|
|
struct babel_proto *p = (struct babel_proto *) C->proto;
|
|
struct fib *rtable = (C->net_type == NET_IP4) ? &p->ip4_rtable : &p->ip6_rtable;
|
|
int changed = 0;
|
|
|
|
FIB_WALK(rtable, struct babel_entry, e)
|
|
if (e->valid == BABEL_ENTRY_REFEEDING)
|
|
{
|
|
babel_entry_invalidate(e);
|
|
changed++;
|
|
}
|
|
FIB_WALK_END;
|
|
|
|
if (changed)
|
|
babel_trigger_update(p);
|
|
}
|
|
|
|
|
|
static int
|
|
babel_rte_better(const rte *new, const rte *old)
|
|
{
|
|
uint new_metric = ea_get_int(new->attrs, &ea_babel_metric, BABEL_INFINITY);
|
|
uint old_metric = ea_get_int(old->attrs, &ea_babel_metric, BABEL_INFINITY);
|
|
|
|
return new_metric < old_metric;
|
|
}
|
|
|
|
static u32
|
|
babel_rte_igp_metric(const rte *rt)
|
|
{
|
|
return ea_get_int(rt->attrs, &ea_babel_metric, BABEL_INFINITY);
|
|
}
|
|
|
|
|
|
static void
|
|
babel_postconfig(struct proto_config *CF)
|
|
{
|
|
struct babel_config *cf = (void *) CF;
|
|
struct channel_config *ip4, *ip6, *ip6_sadr;
|
|
|
|
ip4 = proto_cf_find_channel(CF, NET_IP4);
|
|
ip6 = proto_cf_find_channel(CF, NET_IP6);
|
|
ip6_sadr = proto_cf_find_channel(CF, NET_IP6_SADR);
|
|
|
|
if (ip6 && ip6_sadr)
|
|
cf_error("Both ipv6 and ipv6-sadr channels");
|
|
|
|
cf->ip4_channel = ip4;
|
|
cf->ip6_channel = ip6 ?: ip6_sadr;
|
|
}
|
|
|
|
static struct rte_owner_class babel_rte_owner_class = {
|
|
.get_route_info = babel_get_route_info,
|
|
.rte_better = babel_rte_better,
|
|
.rte_igp_metric = babel_rte_igp_metric,
|
|
};
|
|
|
|
static struct proto *
|
|
babel_init(struct proto_config *CF)
|
|
{
|
|
struct proto *P = proto_new(CF);
|
|
struct babel_proto *p = (void *) P;
|
|
struct babel_config *cf = (void *) CF;
|
|
|
|
proto_configure_channel(P, &p->ip4_channel, cf->ip4_channel);
|
|
proto_configure_channel(P, &p->ip6_channel, cf->ip6_channel);
|
|
|
|
P->iface_sub.if_notify = babel_if_notify;
|
|
P->rt_notify = babel_rt_notify;
|
|
P->preexport = babel_preexport;
|
|
P->feed_begin = babel_feed_begin;
|
|
P->feed_end = babel_feed_end;
|
|
|
|
P->sources.class = &babel_rte_owner_class;
|
|
|
|
return P;
|
|
}
|
|
|
|
static inline void
|
|
babel_randomize_router_id(struct babel_proto *p)
|
|
{
|
|
p->router_id &= (u64) 0xffffffff;
|
|
p->router_id |= ((u64) random()) << 32;
|
|
TRACE(D_EVENTS, "Randomized router ID to %lR", p->router_id);
|
|
}
|
|
|
|
static int
|
|
babel_start(struct proto *P)
|
|
{
|
|
struct babel_proto *p = (void *) P;
|
|
struct babel_config *cf = (void *) P->cf;
|
|
u8 ip6_type = cf->ip6_channel ? cf->ip6_channel->net_type : NET_IP6;
|
|
|
|
fib_init(&p->ip4_rtable, P->pool, NET_IP4, sizeof(struct babel_entry),
|
|
OFFSETOF(struct babel_entry, n), 0, babel_init_entry);
|
|
fib_init(&p->ip6_rtable, P->pool, ip6_type, sizeof(struct babel_entry),
|
|
OFFSETOF(struct babel_entry, n), 0, babel_init_entry);
|
|
|
|
init_list(&p->interfaces);
|
|
p->timer = tm_new_init(P->pool, babel_timer, p, 1 S, 0);
|
|
tm_start(p->timer, 1 S);
|
|
p->update_seqno = 1;
|
|
p->router_id = proto_get_router_id(&cf->c);
|
|
|
|
if (cf->randomize_router_id)
|
|
babel_randomize_router_id(p);
|
|
|
|
p->route_slab = sl_new(P->pool, sizeof(struct babel_route));
|
|
p->source_slab = sl_new(P->pool, sizeof(struct babel_source));
|
|
p->msg_slab = sl_new(P->pool, sizeof(struct babel_msg_node));
|
|
p->seqno_slab = sl_new(P->pool, sizeof(struct babel_seqno_request));
|
|
|
|
p->log_pkt_tbf = (struct tbf){ .rate = 1, .burst = 5 };
|
|
|
|
return PS_UP;
|
|
}
|
|
|
|
static inline void
|
|
babel_iface_shutdown(struct babel_iface *ifa)
|
|
{
|
|
if (ifa->sk)
|
|
{
|
|
/*
|
|
* Retract all our routes and lower the hello interval so peers' neighbour
|
|
* state expires quickly
|
|
*/
|
|
babel_send_hello(ifa, BABEL_MIN_INTERVAL);
|
|
babel_send_wildcard_retraction(ifa);
|
|
babel_send_queue(ifa);
|
|
}
|
|
}
|
|
|
|
static int
|
|
babel_shutdown(struct proto *P)
|
|
{
|
|
struct babel_proto *p = (void *) P;
|
|
struct babel_iface *ifa;
|
|
|
|
TRACE(D_EVENTS, "Shutdown requested");
|
|
|
|
WALK_LIST_FIRST(ifa, p->interfaces)
|
|
{
|
|
babel_iface_shutdown(ifa);
|
|
babel_remove_iface(p, ifa);
|
|
}
|
|
|
|
return PS_DOWN;
|
|
}
|
|
|
|
static int
|
|
babel_reconfigure(struct proto *P, struct proto_config *CF)
|
|
{
|
|
struct babel_proto *p = (void *) P;
|
|
struct babel_config *new = (void *) CF;
|
|
u8 ip6_type = new->ip6_channel ? new->ip6_channel->net_type : NET_IP6;
|
|
|
|
TRACE(D_EVENTS, "Reconfiguring");
|
|
|
|
if (p->ip6_rtable.addr_type != ip6_type)
|
|
return 0;
|
|
|
|
if (!proto_configure_channel(P, &p->ip4_channel, new->ip4_channel) ||
|
|
!proto_configure_channel(P, &p->ip6_channel, new->ip6_channel))
|
|
return 0;
|
|
|
|
p->p.cf = CF;
|
|
babel_reconfigure_ifaces(p, new);
|
|
|
|
babel_trigger_update(p);
|
|
babel_kick_timer(p);
|
|
|
|
return 1;
|
|
}
|
|
|
|
struct protocol proto_babel = {
|
|
.name = "Babel",
|
|
.template = "babel%d",
|
|
.preference = DEF_PREF_BABEL,
|
|
.channel_mask = NB_IP | NB_IP6_SADR,
|
|
.proto_size = sizeof(struct babel_proto),
|
|
.config_size = sizeof(struct babel_config),
|
|
.postconfig = babel_postconfig,
|
|
.init = babel_init,
|
|
.dump = babel_dump,
|
|
.start = babel_start,
|
|
.shutdown = babel_shutdown,
|
|
.reconfigure = babel_reconfigure,
|
|
};
|
|
|
|
void
|
|
babel_build(void)
|
|
{
|
|
proto_build(&proto_babel);
|
|
|
|
EA_REGISTER_ALL(
|
|
&ea_babel_metric,
|
|
&ea_babel_router_id,
|
|
&ea_babel_seqno
|
|
);
|
|
}
|