0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-12-23 02:01:55 +00:00
bird/proto/babel/babel.c
Ondrej Zajicek (work) 4324025f98 Babel: Add support for dual-stack IPv4/IPv6 operation
This adds support for dual-stack v4/v6 operation to the Babel protocol.
Routing messages will be exchanged over IPv6, but IPv4 routes can be
carried in the messages being exchanged. This matches how the reference
Babel implementation (babeld) works.

The nexthop address for v4 can be configured per interface, and will
default to the first available IPv4 address on the given interface. For
symmetry, a configuration option to configure the IPv6 nexthop address
is also added.

Thanks to Toke Høiland-Jørgensen <toke@toke.dk> for the patch.
2017-06-08 12:18:16 +02:00

2215 lines
56 KiB
C

/*
* BIRD -- The Babel protocol
*
* Copyright (c) 2015--2016 Toke Hoiland-Jorgensen
*
* 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
*
* Babel (RFC6126) 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. It performs feasibility checks on the available routes for
* the prefix and selects the one with the lowest metric to be announced to the
* core.
*/
#include <stdlib.h>
#include "babel.h"
#define OUR_ROUTE(r) (r->neigh == NULL)
/*
* 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; }
static void babel_dump_entry(struct babel_entry *e);
static void babel_dump_route(struct babel_route *r);
static void babel_select_route(struct babel_entry *e);
static void babel_send_route_request(struct babel_entry *e, struct babel_neighbor *n);
static void babel_send_wildcard_request(struct babel_iface *ifa);
static int babel_cache_seqno_request(struct babel_proto *p, net_addr *n, u64 router_id, u16 seqno);
static void babel_trigger_iface_update(struct babel_iface *ifa);
static void babel_trigger_update(struct babel_proto *p);
static void babel_send_seqno_request(struct babel_entry *e);
static inline void babel_kick_timer(struct babel_proto *p);
static inline void babel_iface_kick_timer(struct babel_iface *ifa);
/*
* Functions to maintain data structures
*/
static void
babel_init_entry(void *E)
{
struct babel_entry *e = E;
e->updated = now;
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);
e->proto = p;
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_entry *e, u64 router_id)
{
struct babel_proto *p = e->proto;
struct babel_source *s = babel_find_source(e, router_id);
if (s)
return s;
s = sl_alloc(p->source_slab);
s->router_id = router_id;
s->expires = now + BABEL_GARBAGE_INTERVAL;
s->seqno = 0;
s->metric = BABEL_INFINITY;
add_tail(&e->sources, NODE s);
return s;
}
static void
babel_expire_sources(struct babel_entry *e)
{
struct babel_proto *p = e->proto;
struct babel_source *n, *nx;
WALK_LIST_DELSAFE(n, nx, e->sources)
{
if (n->expires && n->expires <= now)
{
rem_node(NODE n);
sl_free(p->source_slab, 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_entry *e, struct babel_neighbor *nbr)
{
struct babel_proto *p = e->proto;
struct babel_route *r = babel_find_route(e, nbr);
if (r)
return r;
r = sl_alloc(p->route_slab);
memset(r, 0, sizeof(*r));
r->e = e;
add_tail(&e->routes, NODE r);
if (nbr)
{
r->neigh = nbr;
r->expires = now + BABEL_GARBAGE_INTERVAL;
add_tail(&nbr->routes, NODE &r->neigh_route);
}
return r;
}
static void
babel_flush_route(struct babel_route *r)
{
struct babel_proto *p = r->e->proto;
DBG("Babel: Flush route %N router_id %lR neigh %I\n",
r->e->n.addr, r->router_id, r->neigh ? r->neigh->addr : IPA_NONE);
rem_node(NODE r);
if (r->neigh)
rem_node(&r->neigh_route);
if (r->e->selected_in == r)
r->e->selected_in = NULL;
if (r->e->selected_out == r)
r->e->selected_out = NULL;
sl_free(p->route_slab, r);
}
static void
babel_expire_route(struct babel_route *r)
{
struct babel_proto *p = r->e->proto;
struct babel_entry *e = r->e;
TRACE(D_EVENTS, "Route expiry timer for %N router-id %lR fired",
e->n.addr, r->router_id);
if (r->metric < BABEL_INFINITY)
{
r->metric = BABEL_INFINITY;
r->expires = now + r->expiry_interval;
}
else
{
babel_flush_route(r);
}
}
static void
babel_refresh_route(struct babel_route *r)
{
if (!OUR_ROUTE(r) && (r == r->e->selected_in))
babel_send_route_request(r->e, r->neigh);
r->refresh_time = 0;
}
static void
babel_expire_routes_(struct babel_proto *p UNUSED, struct fib *rtable)
{
struct babel_route *r, *rx;
struct fib_iterator fit;
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(r);
if (r->expires && r->expires <= now)
{
babel_expire_route(r);
changed = 1;
}
}
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(e);
goto loop;
}
babel_expire_sources(e);
/* Remove empty entries */
if (EMPTY_LIST(e->sources) && EMPTY_LIST(e->routes))
{
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);
}
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_neighbor *nbr = babel_find_neighbor(ifa, addr);
if (nbr)
return nbr;
nbr = mb_allocz(ifa->pool, sizeof(struct babel_neighbor));
nbr->ifa = ifa;
nbr->addr = addr;
nbr->txcost = BABEL_INFINITY;
init_list(&nbr->routes);
add_tail(&ifa->neigh_list, NODE nbr);
return nbr;
}
static void
babel_flush_neighbor(struct babel_neighbor *nbr)
{
struct babel_proto *p = nbr->ifa->proto;
node *n;
TRACE(D_EVENTS, "Flushing neighbor %I", nbr->addr);
WALK_LIST_FIRST(n, nbr->routes)
{
struct babel_route *r = SKIP_BACK(struct babel_route, neigh_route, n);
struct babel_entry *e = r->e;
int selected = (r == e->selected_in);
babel_flush_route(r);
if (selected)
babel_select_route(e);
}
rem_node(NODE nbr);
mb_free(nbr);
}
static void
babel_expire_ihu(struct babel_neighbor *nbr)
{
nbr->txcost = BABEL_INFINITY;
}
static void
babel_expire_hello(struct babel_neighbor *nbr)
{
nbr->hello_map <<= 1;
if (nbr->hello_cnt < 16)
nbr->hello_cnt++;
if (!nbr->hello_map)
babel_flush_neighbor(nbr);
}
static void
babel_expire_neighbors(struct babel_proto *p)
{
struct babel_iface *ifa;
struct babel_neighbor *nbr, *nbx;
WALK_LIST(ifa, p->interfaces)
{
WALK_LIST_DELSAFE(nbr, nbx, ifa->neigh_list)
{
if (nbr->ihu_expiry && nbr->ihu_expiry <= now)
babel_expire_ihu(nbr);
if (nbr->hello_expiry && nbr->hello_expiry <= now)
babel_expire_hello(nbr);
}
}
}
/*
* 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) ||
(seqno > s->seqno) ||
((seqno == s->seqno) && (metric < s->metric));
}
static u16
babel_compute_rxcost(struct babel_neighbor *n)
{
struct babel_iface *ifa = n->ifa;
u8 cnt, missed;
u16 map=n->hello_map;
if (!map) return BABEL_INFINITY;
cnt = u32_popcount(map); // number of bits set
missed = n->hello_cnt-cnt;
if (ifa->cf->type == BABEL_IFACE_TYPE_WIRELESS)
{
/* ETX - Appendix 2.2 in the RFC.
beta = prob. of successful transmission.
rxcost = BABEL_RXCOST_WIRELESS/beta
Since: beta = 1-missed/n->hello_cnt = cnt/n->hello_cnt
Then: rxcost = BABEL_RXCOST_WIRELESS * n->hello_cnt / cnt
*/
if (!cnt) return BABEL_INFINITY;
return BABEL_RXCOST_WIRELESS * n->hello_cnt / cnt;
}
else
{
/* k-out-of-j selection - Appendix 2.1 in the RFC. */
DBG("Babel: Missed %d hellos from %I\n", missed, n->addr);
/* Link is bad if more than half the expected hellos were lost */
return (missed > n->hello_cnt/2) ? BABEL_INFINITY : ifa->cf->rxcost;
}
}
static u16
babel_compute_cost(struct babel_neighbor *n)
{
struct babel_iface *ifa = n->ifa;
u16 rxcost = babel_compute_rxcost(n);
if (rxcost == BABEL_INFINITY) return rxcost;
else if (ifa->cf->type == BABEL_IFACE_TYPE_WIRELESS)
{
/* ETX - Appendix 2.2 in the RFC */
return (MAX(n->txcost, BABEL_RXCOST_WIRELESS) * rxcost)/BABEL_RXCOST_WIRELESS;
}
else
{
/* k-out-of-j selection - Appendix 2.1 in the RFC. */
return n->txcost;
}
}
/* Simple additive metric - Appendix 3.1 in the RFC */
static u16
babel_compute_metric(struct babel_neighbor *n, uint metric)
{
metric += babel_compute_cost(n);
return MIN(metric, BABEL_INFINITY);
}
/**
* 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 the selected entry has infinite
* metric, the route is announced as unreachable.
*/
static void
babel_announce_rte(struct babel_proto *p, struct babel_entry *e)
{
struct babel_route *r = e->selected_in;
struct channel *c = (e->n.addr->type == NET_IP4) ? p->ip4_channel : p->ip6_channel;
if (r)
{
rta *ap0 = allocz(RTA_MAX_SIZE);
*ap0 = (rta) {
.src = p->p.main_source,
.source = RTS_BABEL,
.scope = SCOPE_UNIVERSE,
.dest = r->metric == BABEL_INFINITY ? RTD_UNREACHABLE : RTD_UNICAST,
.from = r->neigh->addr,
.nh.iface = r->neigh->ifa->iface,
};
if (r->metric < BABEL_INFINITY)
ap0->nh.gw = r->next_hop;
rta *a = rta_lookup(ap0);
rte *rte = rte_get_temp(a);
rte->u.babel.metric = r->metric;
rte->u.babel.router_id = r->router_id;
rte->pflags = 0;
rte_update2(c, e->n.addr, rte, p->p.main_source);
}
else
{
/* Retraction */
rte_update2(c, e->n.addr, NULL, p->p.main_source);
}
}
/**
* babel_select_route - select best route for given route entry
* @e: Babel entry to select the best route for
*
* Select the best feasible route for a given prefix among the routes received
* from peers, and propagate it to the nest. This just selects the feasible
* route with the lowest metric.
*
* 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. In the
* meantime, the route is marked as infeasible in the nest (to blackhole packets
* going to it, as per the RFC).
*
* If no feasible route is available, and no previous route is selected, the
* route is removed from the nest entirely.
*/
static void
babel_select_route(struct babel_entry *e)
{
struct babel_proto *p = e->proto;
struct babel_route *r, *cur = e->selected_in;
/* try to find the best feasible route */
WALK_LIST(r, e->routes)
if (!OUR_ROUTE(r) && /* prevent propagating our own routes back to core */
(!cur || r->metric < cur->metric) &&
babel_is_feasible(babel_find_source(e, r->router_id), r->seqno, r->advert_metric))
cur = r;
if (cur && !OUR_ROUTE(cur) &&
((!e->selected_in && cur->metric < BABEL_INFINITY) ||
(e->selected_in && cur->metric < e->selected_in->metric)))
{
TRACE(D_EVENTS, "Picked new route for prefix %N: router id %lR metric %d",
e->n.addr, cur->router_id, cur->metric);
e->selected_in = cur;
e->updated = now;
babel_announce_rte(p, e);
}
else if (!cur || cur->metric == BABEL_INFINITY)
{
/* Couldn't find a feasible route. If we have a selected route, that means
it just became infeasible; so set it's metric to infinite and install it
(as unreachable), then send a seqno request.
babel_build_rte() will set the unreachable flag if the metric is BABEL_INFINITY.*/
if (e->selected_in)
{
TRACE(D_EVENTS, "Lost feasible route for prefix %N",
e->n.addr);
e->selected_in->metric = BABEL_INFINITY;
e->updated = now;
babel_send_seqno_request(e);
babel_announce_rte(p, e);
/* Section 3.6 of the RFC forbids an infeasible from being selected. This
is cleared after announcing the route to the core to make sure an
unreachable route is propagated first. */
e->selected_in = NULL;
}
else
{
/* No route currently selected, and no new one selected; this means we
don't have a route to this destination anymore (and were probably
called from an expiry timer). Remove the route from the nest. */
TRACE(D_EVENTS, "Flushing route for prefix %N", e->n.addr);
e->selected_in = NULL;
e->updated = now;
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 = babel_compute_rxcost(n);
msg->ihu.interval = ifa->cf->ihu_interval;
TRACE(D_PACKETS, "Sending IHU for %I with rxcost %d interval %d",
msg->ihu.addr, msg->ihu.rxcost, 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);
}
static void
babel_send_ihus(struct babel_iface *ifa)
{
struct babel_neighbor *n;
WALK_LIST(n, ifa->neigh_list)
{
union babel_msg msg = {};
babel_build_ihu(&msg, ifa, n);
babel_enqueue(&msg, ifa);
}
}
static void
babel_send_hello(struct babel_iface *ifa, u8 send_ihu)
{
struct babel_proto *p = ifa->proto;
union babel_msg msg = {};
msg.type = BABEL_TLV_HELLO;
msg.hello.seqno = ifa->hello_seqno++;
msg.hello.interval = ifa->cf->hello_interval;
TRACE(D_PACKETS, "Sending hello on %s with seqno %d interval %d",
ifa->ifname, msg.hello.seqno, msg.hello.interval);
babel_enqueue(&msg, ifa);
if (send_ihu)
babel_send_ihus(ifa);
}
static void
babel_send_route_request(struct babel_entry *e, struct babel_neighbor *n)
{
struct babel_proto *p = e->proto;
struct babel_iface *ifa = n->ifa;
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, 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_entry *e)
{
struct babel_proto *p = e->proto;
struct babel_route *r = e->selected_in;
struct babel_iface *ifa = NULL;
struct babel_source *s = NULL;
union babel_msg msg = {};
s = babel_find_source(e, r->router_id);
if (!s || !babel_cache_seqno_request(p, e->n.addr, r->router_id, s->seqno + 1))
return;
TRACE(D_PACKETS, "Sending seqno request for %N router-id %lR seqno %d",
e->n.addr, r->router_id, s->seqno + 1);
msg.type = BABEL_TLV_SEQNO_REQUEST;
msg.seqno_request.hop_count = BABEL_INITIAL_HOP_COUNT;
msg.seqno_request.seqno = s->seqno + 1;
msg.seqno_request.router_id = r->router_id;
net_copy(&msg.seqno_request.net, e->n.addr);
WALK_LIST(ifa, p->interfaces)
babel_enqueue(&msg, ifa);
}
static void
babel_unicast_seqno_request(struct babel_route *r)
{
struct babel_entry *e = r->e;
struct babel_proto *p = e->proto;
struct babel_iface *ifa = r->neigh->ifa;
struct babel_source *s = NULL;
union babel_msg msg = {};
s = babel_find_source(e, r->router_id);
if (!s || !babel_cache_seqno_request(p, e->n.addr, r->router_id, s->seqno + 1))
return;
TRACE(D_PACKETS, "Sending seqno request for %N router-id %lR seqno %d",
e->n.addr, r->router_id, s->seqno + 1);
msg.type = BABEL_TLV_SEQNO_REQUEST;
msg.seqno_request.hop_count = BABEL_INITIAL_HOP_COUNT;
msg.seqno_request.seqno = s->seqno + 1;
msg.seqno_request.router_id = r->router_id;
net_copy(&msg.seqno_request.net, e->n.addr);
babel_send_unicast(&msg, ifa, r->neigh->addr);
}
/**
* 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, bird_clock_t changed, struct fib *rtable)
{
struct babel_proto *p = ifa->proto;
FIB_WALK(rtable, struct babel_entry, e)
{
struct babel_route *r = e->selected_out;
if (!r)
continue;
/* Our own seqno might have changed, in which case we update the routes we
originate. */
if ((r->router_id == p->router_id) && (r->seqno < p->update_seqno))
{
r->seqno = p->update_seqno;
e->updated = now;
}
/* 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, r->router_id, r->seqno, r->metric);
union babel_msg msg = {};
msg.type = BABEL_TLV_UPDATE;
msg.update.interval = ifa->cf->update_interval;
msg.update.seqno = r->seqno;
msg.update.metric = r->metric;
msg.update.router_id = r->router_id;
net_copy(&msg.update.net, e->n.addr);
msg.update.next_hop = ((e->n.addr->type == NET_IP4) ?
ifa->next_hop_ip4 : ifa->next_hop_ip6);
babel_enqueue(&msg, ifa);
/* Update feasibility distance for redistributed routes */
if (!OUR_ROUTE(r))
{
struct babel_source *s = babel_get_source(e, r->router_id);
s->expires = now + BABEL_GARBAGE_INTERVAL;
if ((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, bird_clock_t 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 = now;
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, u16 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)
{
/* 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++;
n->hello_expiry = now + BABEL_HELLO_EXPIRY_FACTOR(interval);
}
static void
babel_expire_seqno_requests(struct babel_proto *p)
{
struct babel_seqno_request *n, *nx;
WALK_LIST_DELSAFE(n, nx, p->seqno_cache)
{
if ((n->updated + BABEL_SEQNO_REQUEST_EXPIRY) <= now)
{
rem_node(NODE n);
sl_free(p->seqno_slab, n);
}
}
}
/*
* Checks the seqno request cache for a matching request and returns failure if
* found. Otherwise, a new entry is stored in the cache.
*/
static int
babel_cache_seqno_request(struct babel_proto *p, net_addr *n,
u64 router_id, u16 seqno)
{
struct babel_seqno_request *r;
WALK_LIST(r, p->seqno_cache)
{
if (net_equal(&r->net, n) && (r->router_id == router_id) && (r->seqno == seqno))
return 0;
}
/* no entries found */
r = sl_alloc(p->seqno_slab);
net_copy(&r->net, n);
r->router_id = router_id;
r->seqno = seqno;
r->updated = now;
add_tail(&p->seqno_cache, NODE r);
return 1;
}
static void
babel_forward_seqno_request(struct babel_entry *e,
struct babel_msg_seqno_request *in,
ip_addr sender)
{
struct babel_proto *p = e->proto;
struct babel_route *r;
TRACE(D_PACKETS, "Forwarding seqno request for %N router-id %lR seqno %d",
e->n.addr, in->router_id, in->seqno);
WALK_LIST(r, e->routes)
{
if ((r->router_id == in->router_id) &&
!OUR_ROUTE(r) &&
!ipa_equal(r->neigh->addr, sender))
{
if (!babel_cache_seqno_request(p, e->n.addr, in->router_id, in->seqno))
return;
union babel_msg msg = {};
msg.type = BABEL_TLV_SEQNO_REQUEST;
msg.seqno_request.hop_count = in->hop_count-1;
msg.seqno_request.seqno = in->seqno;
msg.seqno_request.router_id = in->router_id;
net_copy(&msg.seqno_request.net, e->n.addr);
babel_send_unicast(&msg, r->neigh->ifa, r->neigh->addr);
return;
}
}
}
/*
* 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 %d",
msg->nonce, 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 %d",
msg->seqno, msg->interval);
struct babel_neighbor *n = babel_get_neighbor(ifa, msg->sender);
babel_update_hello_history(n, msg->seqno, msg->interval);
if (ifa->cf->type == BABEL_IFACE_TYPE_WIRELESS)
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 %d",
msg->rxcost, msg->interval);
struct babel_neighbor *n = babel_get_neighbor(ifa, msg->sender);
n->txcost = msg->rxcost;
n->ihu_expiry = now + BABEL_IHU_EXPIRY_FACTOR(msg->interval);
}
/**
* 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;
node *n;
int feasible;
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;
}
/*
* RFC section 3.5.4:
*
* When a Babel node receives an update (id, prefix, seqno, metric) from a
* neighbour neigh with a link cost value equal to cost, it checks whether it
* already has a routing table entry indexed by (neigh, id, prefix).
*
* If no such entry exists:
*
* o if the update is unfeasible, it is ignored;
*
* o if the metric is infinite (the update is a retraction), the update is
* ignored;
*
* o otherwise, a new route table entry is created, indexed by (neigh, id,
* prefix), with seqno equal to seqno and an advertised metric equal to the
* metric carried by the update.
*
* If such an entry exists:
*
* o if the entry is currently installed and the update is unfeasible, then
* the behaviour depends on whether the router-ids of the two entries match.
* If the router-ids are different, the update is treated as though it were
* a retraction (i.e., as though the metric were FFFF hexadecimal). If the
* router-ids are equal, the update is ignored;
*
* o otherwise (i.e., if either the update is feasible or the entry is not
* currently installed), then the entry's sequence number, advertised
* metric, metric, and router-id are updated and, unless the advertised
* metric is infinite, the route's expiry timer is reset to a small multiple
* of the Interval value included in the update.
*/
/* 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);
r->metric = BABEL_INFINITY;
babel_select_route(r->e);
}
}
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;
r->metric = BABEL_INFINITY;
babel_select_route(e);
}
/* Done with retractions */
return;
}
e = babel_get_entry(p, &msg->net);
r = babel_find_route(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);
if (!r)
{
if (!feasible)
return;
r = babel_get_route(e, nbr);
r->advert_metric = msg->metric;
r->router_id = msg->router_id;
r->metric = babel_compute_metric(nbr, msg->metric);
r->next_hop = msg->next_hop;
r->seqno = msg->seqno;
}
else if (r == r->e->selected_in && !feasible)
{
/*
* Route is installed and update is infeasible - we may lose the route,
* so send a unicast seqno request (section 3.8.2.2 second paragraph).
*/
babel_unicast_seqno_request(r);
if (msg->router_id == r->router_id)
return;
/* Treat as retraction */
r->metric = BABEL_INFINITY;
}
else
{
/* Last paragraph above - update the entry */
r->advert_metric = msg->metric;
r->metric = babel_compute_metric(nbr, msg->metric);
r->next_hop = msg->next_hop;
r->router_id = msg->router_id;
r->seqno = msg->seqno;
r->expiry_interval = BABEL_ROUTE_EXPIRY_FACTOR(msg->interval);
r->expires = now + r->expiry_interval;
if (r->expiry_interval > BABEL_ROUTE_REFRESH_INTERVAL)
r->refresh_time = now + r->expiry_interval - BABEL_ROUTE_REFRESH_INTERVAL;
/* If the route is not feasible at this point, it means it is from another
neighbour than the one currently selected; so send a unicast seqno
request to try to get a better route (section 3.8.2.2 last paragraph). */
if (!feasible)
babel_unicast_seqno_request(r);
}
babel_select_route(e);
}
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 6126 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 = now;
}
}
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 6126 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->selected_out || (e->selected_out->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 */
struct babel_route *r = e->selected_out;
if ((r->router_id != msg->router_id) || ge_mod64k(r->seqno, msg->seqno))
{
babel_trigger_iface_update(ifa);
e->updated = now;
return;
}
/* Seqno is larger; check if we own the router id */
if (msg->router_id == p->router_id)
{
/* Ours; update seqno and trigger global update */
p->update_seqno++;
babel_trigger_update(p);
}
else
{
/* Not ours; forward if TTL allows it */
if (msg->hop_count > 1)
babel_forward_seqno_request(e, msg, msg->sender);
}
}
/*
* 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;
bird_clock_t hello_period = ifa->cf->hello_interval;
bird_clock_t update_period = ifa->cf->update_interval;
if (now >= ifa->next_hello)
{
babel_send_hello(ifa, (ifa->cf->type == BABEL_IFACE_TYPE_WIRELESS ||
ifa->hello_seqno % BABEL_IHU_INTERVAL_FACTOR == 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(5, update_period / 2 + 1);
ifa->want_triggered = 0;
p->triggered = 0;
}
bird_clock_t next_event = MIN(ifa->next_hello, ifa->next_regular);
tm_start(ifa->timer, ifa->want_triggered ? 1 : (next_event - now));
}
static inline void
babel_iface_kick_timer(struct babel_iface *ifa)
{
if (ifa->timer->expires > (now + 1))
tm_start(ifa->timer, 1);
}
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 = now + (random() % ifa->cf->hello_interval) + 1;
ifa->next_regular = now + (random() % ifa->cf->update_interval) + 1;
ifa->next_triggered = now + MIN(5, ifa->cf->update_interval / 2 + 1);
ifa->want_triggered = 0; /* We send an immediate update (below) */
tm_start(ifa->timer, 1);
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);
r->metric = BABEL_INFINITY;
r->expires = now + r->expiry_interval;
babel_select_route(r->e);
}
}
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_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;
}
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(struct object_lock *lock)
{
struct babel_iface *ifa = lock->data;
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, 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;
add_tail(&p->interfaces, NODE ifa);
ip_addr addr4 = IPA_NONE;
struct ifa *addr;
WALK_LIST(addr, new->addrs)
{
if (ipa_is_link_local(addr->ip))
ifa->addr = addr->ip;
if (ipa_zero(addr4) && ipa_is_ip4(addr->ip))
addr4 = addr->ip;
}
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->addr))
log(L_WARN "%s: Cannot find link-local addr on %s", p->p.name, new->name);
if (ipa_zero(ifa->next_hop_ip4) && p->ip4_channel)
log(L_WARN "%s: Cannot find IPv4 next hop addr on %s", p->p.name, new->name);
init_list(&ifa->neigh_list);
ifa->hello_seqno = 1;
ifa->timer = tm_new_set(ifa->pool, babel_iface_timer, ifa, 0, 0);
init_list(&ifa->msg_queue);
ifa->send_event = ev_new(ifa->pool);
ifa->send_event->hook = babel_send_queue;
ifa->send_event->data = 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->hook = babel_iface_locked;
lock->data = ifa;
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(n);
rem_node(NODE ifa);
rfree(ifa->pool); /* contains ifa itself, locks, socket, etc */
}
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;
if (iface->flags & IF_IGNORE)
return;
if (flags & IF_CHANGE_UP)
{
struct babel_iface_config *ic = (void *) iface_patt_find(&cf->iface_list, iface, iface->addr);
/* we only speak multicast */
if (!(iface->flags & IF_MULTICAST))
return;
if (ic)
babel_add_iface(p, iface, ic);
return;
}
struct babel_iface *ifa = babel_find_iface(p, iface);
if (!ifa)
return;
if (flags & IF_CHANGE_DOWN)
{
babel_remove_iface(p, ifa);
return;
}
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;
if (ipa_nonzero(new->next_hop_ip4))
ifa->next_hop_ip4 = new->next_hop_ip4;
else
{
ifa->next_hop_ip4 = IPA_NONE;
struct ifa *addr;
WALK_LIST(addr, ifa->iface->addrs)
if (ipa_is_ip4(addr->ip))
{
ifa->next_hop_ip4 = addr->ip;
break;
}
}
ifa->next_hop_ip6 = ipa_nonzero(new->next_hop_ip6) ? new->next_hop_ip6 : ifa->addr;
if (ipa_zero(ifa->next_hop_ip4) && p->ip4_channel)
log(L_WARN "%s: Cannot find IPv4 next hop addr on %s", p->p.name, ifa->ifname);
if (ifa->next_hello > (now + new->hello_interval))
ifa->next_hello = now + (random() % new->hello_interval) + 1;
if (ifa->next_regular > (now + new->update_interval))
ifa->next_regular = now + (random() % new->update_interval) + 1;
if ((new->tx_length != old->tx_length) || (new->rx_buffer != old->rx_buffer))
babel_iface_update_buffers(ifa);
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)
{
struct iface *iface;
WALK_LIST(iface, iface_list)
{
if (! (iface->flags & IF_UP))
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 (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 %d\n",
s->router_id, s->seqno, s->metric, s->expires ? s->expires-now : 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 %d\n",
r->neigh ? r->neigh->addr : IPA_NONE,
r->neigh ? r->neigh->ifa->ifname : "(none)",
r->seqno, r->advert_metric, r->metric,
r->router_id, r->expires ? r->expires-now : 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_out) debug("*");
if (r == e->selected_in) 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 %d/%d\n",
n->addr, n->txcost, n->hello_map, n->next_hello_seqno,
n->hello_expiry ? n->hello_expiry - now : 0,
n->ihu_expiry ? n->ihu_expiry - now : 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 %d %d",
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(rte *rte, byte *buf, ea_list *attrs UNUSED)
{
buf += bsprintf(buf, " (%d/%d) [%lR]", rte->pref, rte->u.babel.metric, rte->u.babel.router_id);
}
static int
babel_get_attr(eattr *a, byte *buf, int buflen UNUSED)
{
switch (a->id)
{
case EA_BABEL_METRIC:
bsprintf(buf, "metric: %d", a->u.data);
return GA_FULL;
case EA_BABEL_ROUTER_ID:
{
u64 rid = 0;
memcpy(&rid, a->u.ptr->data, sizeof(u64));
bsprintf(buf, "router_id: %lR", rid);
return GA_FULL;
}
default:
return GA_UNKNOWN;
}
}
void
babel_show_interfaces(struct proto *P, 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);
cli_msg(0, "");
return;
}
cli_msg(-1023, "%s:", p->p.name);
cli_msg(-1023, "%-10s %-6s %7s %6s %6s %-15s %s",
"Interface", "State", "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++;
int timer = MIN(ifa->next_regular, ifa->next_hello) - now;
cli_msg(-1023, "%-10s %-6s %7u %6u %6u %-15I %I",
ifa->iface->name, (ifa->up ? "Up" : "Down"),
ifa->cf->rxcost, nbrs, MAX(timer, 0),
ifa->next_hop_ip4, ifa->next_hop_ip6);
}
cli_msg(0, "");
}
void
babel_show_neighbors(struct proto *P, 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);
cli_msg(0, "");
return;
}
cli_msg(-1024, "%s:", p->p.name);
cli_msg(-1024, "%-25s %-10s %6s %6s %10s",
"IP address", "Interface", "Metric", "Routes", "Next hello");
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++;
int timer = n->hello_expiry - now;
cli_msg(-1024, "%-25I %-10s %6u %6u %10u",
n->addr, ifa->iface->name, n->txcost, rts, MAX(timer, 0));
}
}
cli_msg(0, "");
}
static void
babel_show_entries_(struct babel_proto *p, struct fib *rtable)
{
struct babel_source *s = NULL;
struct babel_route *r = NULL;
char ridbuf[ROUTER_ID_64_LENGTH+1];
FIB_WALK(rtable, struct babel_entry, e)
{
r = e->selected_in ? e->selected_in : e->selected_out;
int srcs = 0;
WALK_LIST(s, e->sources)
srcs++;
if (r)
{
if (r->router_id == p->router_id)
bsprintf(ridbuf, "%s", "<self>");
else
bsprintf(ridbuf, "%lR", r->router_id);
int time = r->expires ? r->expires - now : 0;
cli_msg(-1025, "%-29N %-23s %6u %5u %7u %7u",
e->n.addr, ridbuf, r->metric, r->seqno, MAX(time, 0), srcs);
}
else
{
cli_msg(-1025, "%-29N %-44s %7u", e->n.addr, "<pending>", srcs);
}
}
FIB_WALK_END;
}
void
babel_show_entries(struct proto *P)
{
struct babel_proto *p = (void *) P;
if (p->p.proto_state != PS_UP)
{
cli_msg(-1025, "%s: is not up", p->p.name);
cli_msg(0, "");
return;
}
cli_msg(-1025, "%s:", p->p.name);
cli_msg(-1025, "%-29s %-23s %6s %5s %7s %7s",
"Prefix", "Router ID", "Metric", "Seqno", "Expires", "Sources");
babel_show_entries_(p, &p->ip4_rtable);
babel_show_entries_(p, &p->ip6_rtable);
cli_msg(0, "");
}
/*
* 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_seqno_requests(p);
babel_expire_neighbors(p);
}
static inline void
babel_kick_timer(struct babel_proto *p)
{
if (p->timer->expires > (now + 1))
tm_start(p->timer, 1);
}
static struct ea_list *
babel_prepare_attrs(struct linpool *pool, ea_list *next, uint metric, u64 router_id)
{
struct ea_list *l = lp_alloc(pool, sizeof(struct ea_list) + 2*sizeof(eattr));
struct adata *rid = lp_alloc(pool, sizeof(struct adata) + sizeof(u64));
rid->length = sizeof(u64);
memcpy(&rid->data, &router_id, sizeof(u64));
l->next = next;
l->flags = EALF_SORTED;
l->count = 2;
l->attrs[0].id = EA_BABEL_METRIC;
l->attrs[0].flags = 0;
l->attrs[0].type = EAF_TYPE_INT | EAF_TEMP;
l->attrs[0].u.data = metric;
l->attrs[1].id = EA_BABEL_ROUTER_ID;
l->attrs[1].flags = 0;
l->attrs[1].type = EAF_TYPE_OPAQUE | EAF_TEMP;
l->attrs[1].u.ptr = rid;
return l;
}
static int
babel_import_control(struct proto *P, struct rte **rt, struct ea_list **attrs, struct linpool *pool)
{
struct babel_proto *p = (void *) P;
/* Prepare attributes with initial values */
if ((*rt)->attrs->source != RTS_BABEL)
*attrs = babel_prepare_attrs(pool, NULL, 0, p->router_id);
return 0;
}
static struct ea_list *
babel_make_tmp_attrs(struct rte *rt, struct linpool *pool)
{
return babel_prepare_attrs(pool, NULL, rt->u.babel.metric, rt->u.babel.router_id);
}
static void
babel_store_tmp_attrs(struct rte *rt, struct ea_list *attrs)
{
rt->u.babel.metric = ea_get_int(attrs, EA_BABEL_METRIC, 0);
}
/*
* 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, struct network *net,
struct rte *new, struct rte *old UNUSED, struct ea_list *attrs UNUSED)
{
struct babel_proto *p = (void *) P;
struct babel_entry *e;
struct babel_route *r;
if (new)
{
/* Update */
e = babel_get_entry(p, net->n.addr);
if (new->attrs->src->proto != P)
{
r = babel_get_route(e, NULL);
r->seqno = p->update_seqno;
r->router_id = p->router_id;
r->metric = 0; /* FIXME: should be selectable */
}
else
r = e->selected_in;
if (r != e->selected_out)
{
e->selected_out = r;
e->updated = now;
babel_trigger_update(p);
}
}
else
{
/* Withdraw */
e = babel_find_entry(p, net->n.addr);
if (!e || !e->selected_out)
return;
if (OUR_ROUTE(e->selected_out))
{
/*
* We originate this route, so set its metric to infinity and set an
* expiry time. This causes a retraction to be sent, and later the route
* to be flushed once the hold time has passed.
*/
e->selected_out->metric = BABEL_INFINITY;
e->selected_out->expires = now + BABEL_HOLD_TIME;
e->updated = now;
babel_trigger_update(p);
}
else
{
/*
* This is a route originating from someone else that was lost; presumably
* because an export filter was updated to filter it. This means we can't
* set the metric to infinity (it would be overridden on subsequent
* updates from the peer originating the route), so just clear the
* exported route.
*
* This causes peers to expire the route after a while (like if we just
* shut down), but it's the best we can do in these circumstances; and
* since export filters presumably aren't updated that often this is
* acceptable.
*/
e->selected_out = NULL;
}
}
}
static int
babel_rte_better(struct rte *new, struct rte *old)
{
return new->u.babel.metric < old->u.babel.metric;
}
static int
babel_rte_same(struct rte *new, struct rte *old)
{
return ((new->u.babel.router_id == old->u.babel.router_id) &&
(new->u.babel.metric == old->u.babel.metric));
}
static struct proto *
babel_init(struct proto_config *CF)
{
struct proto *P = proto_new(CF);
struct babel_proto *p = (void *) P;
proto_configure_channel(P, &p->ip4_channel, proto_cf_find_channel(CF, NET_IP4));
proto_configure_channel(P, &p->ip6_channel, proto_cf_find_channel(CF, NET_IP6));
P->if_notify = babel_if_notify;
P->rt_notify = babel_rt_notify;
P->import_control = babel_import_control;
P->make_tmp_attrs = babel_make_tmp_attrs;
P->store_tmp_attrs = babel_store_tmp_attrs;
P->rte_better = babel_rte_better;
P->rte_same = babel_rte_same;
return P;
}
static int
babel_start(struct proto *P)
{
struct babel_proto *p = (void *) P;
struct babel_config *cf = (void *) P->cf;
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, NET_IP6, sizeof(struct babel_entry),
OFFSETOF(struct babel_entry, n), 0, babel_init_entry);
init_list(&p->interfaces);
p->timer = tm_new_set(P->pool, babel_timer, p, 0, 1);
tm_start(p->timer, 2);
p->update_seqno = 1;
p->router_id = proto_get_router_id(&cf->c);
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));
init_list(&p->seqno_cache);
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)
{
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(ifa, p->interfaces)
babel_iface_shutdown(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;
TRACE(D_EVENTS, "Reconfiguring");
if (!proto_configure_channel(P, &p->ip4_channel, proto_cf_find_channel(CF, NET_IP4)) ||
!proto_configure_channel(P, &p->ip6_channel, proto_cf_find_channel(CF, NET_IP6)))
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",
.attr_class = EAP_BABEL,
.preference = DEF_PREF_BABEL,
.channel_mask = NB_IP,
.proto_size = sizeof(struct babel_proto),
.config_size = sizeof(struct babel_config),
.init = babel_init,
.dump = babel_dump,
.start = babel_start,
.shutdown = babel_shutdown,
.reconfigure = babel_reconfigure,
.get_route_info = babel_get_route_info,
.get_attr = babel_get_attr
};