babel: Add route metric smoothing

The Babel RTT extension employs metric smoothing to dampen route
oscillations in the face of varying RTT values between two peers[0].

This patch implements such dampening in Bird, roughly following the
implementation in babeld (i.e., using the same exponential function
definition). The main difference is that we calculate everything in the
native Bird microsecond time unit (and increase constants accordingly), and
that we split out the smoothed metric calculation in two function variants,
one that has no side effects and one that does.

  [0] https://arxiv.org/pdf/1403.3488.pdf

Signed-off-by: Toke Høiland-Jørgensen <toke@toke.dk>
This commit is contained in:
Toke Høiland-Jørgensen 2022-04-21 00:57:23 +02:00
parent 56841eecbc
commit 4c582913ec
4 changed files with 150 additions and 9 deletions

View file

@ -1865,6 +1865,7 @@ protocol babel [<name>] {
ipv4 { <channel config> }; ipv4 { <channel config> };
ipv6 [sadr] { <channel config> }; ipv6 [sadr] { <channel config> };
randomize router id <switch>; randomize router id <switch>;
metric decay <time>;
interface <interface pattern> { interface <interface pattern> {
type <wired|wireless|tunnel>; type <wired|wireless|tunnel>;
rxcost <number>; rxcost <number>;
@ -1914,6 +1915,17 @@ protocol babel [<name>] {
router ID every time it starts up, which avoids this problem at the cost router ID every time it starts up, which avoids this problem at the cost
of not having stable router IDs in the network. Default: no. of not having stable router IDs in the network. Default: no.
<tag><label id="babel-metric-decay">metric decay <m/time/ s</tag>
The metric smoothing decay time. When route metrics vary (because of
varying quality of a wireless link, or varying RTT when timestamps are
enabled), Babel applies an exponential smoothing procedure to the metric
to dampen route oscillations. This parameter specifies the half-life of
the convergence of the smoothed metric to the actual metric of the route.
I.e., the distance between the smoothed and the actual metric will be
halfed for each time period specified here (until they converge). Set to 0
to disable metric smoothing; if set, the value must be in the interval
1-180 s. Default: 4 s
<tag><label id="babel-type">type wired|wireless|tunnel </tag> <tag><label id="babel-type">type wired|wireless|tunnel </tag>
This option specifies the interface type: Wired, wireless or tunnel. On This option specifies the interface type: Wired, wireless or tunnel. On
wired interfaces a neighbor is considered unreachable after a small number wired interfaces a neighbor is considered unreachable after a small number

View file

@ -134,6 +134,103 @@ babel_expire_sources(struct babel_proto *p, struct babel_entry *e)
} }
} }
static u16
babel_calc_smoothed_metric(struct babel_proto *p, struct babel_route *r, u8 update)
{
struct babel_config *cf = (void *) p->p.cf;
uint metric = r->metric, smoothed_metric = r->smoothed_metric;
btime smoothed_time = r->smoothed_time, now = current_time();
if (!cf->metric_decay || metric == BABEL_INFINITY ||
metric == smoothed_metric || !smoothed_time)
{
smoothed_metric = metric;
smoothed_time = now;
goto out;
}
int diff = metric - smoothed_metric;
/*
* The decay defines the half-life of the metric convergence, so first iterate
* in halving steps
*/
while (smoothed_time < now - cf->metric_decay && diff) {
smoothed_metric += diff/2;
smoothed_time += cf->metric_decay;
diff = metric - smoothed_metric;
}
/*
* Then, update remainder in BABEL_SMOOTHING_STEP intervals using the
* exponential function (approximated via the pre-computed reciprocal).
*/
while (smoothed_time < now - BABEL_SMOOTHING_STEP && diff) {
smoothed_metric += (BABEL_SMOOTHING_STEP * diff *
(cf->smooth_recp - BABEL_SMOOTHING_UNIT) / BABEL_SMOOTHING_UNIT);
smoothed_time += BABEL_SMOOTHING_STEP;
diff = metric - smoothed_metric;
}
/* Consider the metric converged once we're close enough */
if (ABS(diff) < BABEL_SMOOTHING_MIN_DIFF)
smoothed_metric = metric;
out:
if (update) {
r->smoothed_metric = smoothed_metric;
r->smoothed_time = smoothed_time;
}
return smoothed_metric;
}
static u16
babel_update_smoothed_metric(struct babel_proto *p, struct babel_route *r)
{
if (!r->metric)
return 0;
if (!r->smoothed_metric) {
r->smoothed_metric = r->metric;
r->smoothed_time = current_time();
return r->smoothed_metric;
}
u16 smoothed = babel_calc_smoothed_metric(p, r, 1);
DBG("Updated smoothed metric for prefix %N: router-id %lR metric %d/%d\n",
r->e->n.addr, r->router_id, r->metric, smoothed);
return smoothed;
}
static u16
babel_smoothed_metric(struct babel_proto *p, struct babel_route *r)
{
return babel_calc_smoothed_metric(p, r, 0);
}
static void
babel_update_metric(struct babel_proto *p, struct babel_route *r, u16 metric)
{
babel_update_smoothed_metric(p, r);
r->metric = metric;
}
static inline u8
babel_route_better(struct babel_proto *p, struct babel_route *mod,
struct babel_route *best)
{
if (!mod->feasible)
return 0;
if (!best)
return mod->metric < BABEL_INFINITY;
return (mod->metric < best->metric &&
babel_smoothed_metric(p, mod) < babel_smoothed_metric(p, best));
}
static struct babel_route * static struct babel_route *
babel_find_route(struct babel_entry *e, struct babel_neighbor *n) babel_find_route(struct babel_entry *e, struct babel_neighbor *n)
{ {
@ -151,8 +248,10 @@ babel_get_route(struct babel_proto *p, struct babel_entry *e, struct babel_neigh
{ {
struct babel_route *r = babel_find_route(e, nbr); struct babel_route *r = babel_find_route(e, nbr);
if (r) if (r) {
babel_update_smoothed_metric(p, r);
return r; return r;
}
r = sl_allocz(p->route_slab); r = sl_allocz(p->route_slab);
@ -641,7 +740,7 @@ done:
struct babel_route *r; node *n; struct babel_route *r; node *n;
WALK_LIST2(r, n, nbr->routes, neigh_route) WALK_LIST2(r, n, nbr->routes, neigh_route)
{ {
r->metric = babel_compute_metric(nbr, r->advert_metric); babel_update_metric(p, r, babel_compute_metric(nbr, r->advert_metric));
babel_select_route(p, r->e, r); babel_select_route(p, r->e, r);
} }
} }
@ -765,8 +864,7 @@ babel_select_route(struct babel_proto *p, struct babel_entry *e, struct babel_ro
/* Shortcut if only non-best was modified */ /* Shortcut if only non-best was modified */
if (mod && (mod != best)) if (mod && (mod != best))
{ {
/* Either select modified route, or keep old best route */ if (babel_route_better(p, mod, best))
if ((mod->metric < (best ? best->metric : BABEL_INFINITY)) && mod->feasible)
best = mod; best = mod;
else else
return; return;
@ -777,17 +875,24 @@ babel_select_route(struct babel_proto *p, struct babel_entry *e, struct babel_ro
if (!best || (best->metric == BABEL_INFINITY) || !best->feasible) if (!best || (best->metric == BABEL_INFINITY) || !best->feasible)
best = NULL; best = NULL;
/* best will be compared to many routes below, make sure it's up-to-date */
if (best)
babel_update_smoothed_metric(p, best);
/* Find the best feasible route from all routes */ /* Find the best feasible route from all routes */
WALK_LIST(r, e->routes) WALK_LIST(r, e->routes)
if ((r->metric < (best ? best->metric : BABEL_INFINITY)) && r->feasible) if (babel_route_better(p, r, best))
best = r; best = r;
} }
if (best) if (best)
{ {
if (best != e->selected) 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); u16 smoothed = babel_update_smoothed_metric(p, best);
TRACE(D_EVENTS, "Picked new route for prefix %N: router-id %lR metric %d/%d",
e->n.addr, best->router_id, best->metric, smoothed);
}
} }
else if (e->selected) else if (e->selected)
{ {
@ -1366,9 +1471,9 @@ babel_handle_update(union babel_msg *m, struct babel_iface *ifa)
return; return;
/* Last paragraph above - update the entry */ /* Last paragraph above - update the entry */
babel_update_metric(p, r, metric);
r->feasible = feasible; r->feasible = feasible;
r->seqno = msg->seqno; r->seqno = msg->seqno;
r->metric = metric;
r->advert_metric = msg->metric; r->advert_metric = msg->metric;
r->router_id = msg->router_id; r->router_id = msg->router_id;
r->next_hop = msg->next_hop; r->next_hop = msg->next_hop;

View file

@ -60,6 +60,18 @@
#define BABEL_RTT_MAX (120 MS_) #define BABEL_RTT_MAX (120 MS_)
#define BABEL_RTT_DECAY 42 #define BABEL_RTT_DECAY 42
/*
* Constants for calculating metric smoothing. Chosen so that:
* log(2) = BABEL_SMOOTHING_CONSTANT / BABEL_SMOOTHING_UNIT, which means that
* log(2)/x can be calculated as BABEL_SMOOTHING_UNIT + BABEL_SMOOTHING_CONSTANT / x
*/
#define BABEL_SMOOTHING_UNIT 0x10000000
#define BABEL_SMOOTHING_CONSTANT 186065279
#define BABEL_SMOOTHING_STEP (1 S_) /* smoothing calculated in this step size */
#define BABEL_SMOOTHING_MIN_DIFF 4 /* metric diff beneath this is converged */
#define BABEL_SMOOTHING_DECAY (4 S_)
#define BABEL_SMOOTHING_DECAY_MAX (180 S_)
/* Max interval that will not overflow when carried as 16-bit centiseconds */ /* Max interval that will not overflow when carried as 16-bit centiseconds */
#define BABEL_TIME_UNITS 10000 /* On-wire times are counted in centiseconds */ #define BABEL_TIME_UNITS 10000 /* On-wire times are counted in centiseconds */
#define BABEL_MIN_INTERVAL (0x0001 * BABEL_TIME_UNITS) #define BABEL_MIN_INTERVAL (0x0001 * BABEL_TIME_UNITS)
@ -127,6 +139,8 @@ enum babel_ae_type {
struct babel_config { struct babel_config {
struct proto_config c; struct proto_config c;
list iface_list; /* List of iface configs (struct babel_iface_config) */ list iface_list; /* List of iface configs (struct babel_iface_config) */
btime metric_decay;
uint smooth_recp; /* Reciprocal for exponential metric smoothing */
uint hold_time; /* Time to hold stale entries and unreachable routes */ uint hold_time; /* Time to hold stale entries and unreachable routes */
u8 randomize_router_id; u8 randomize_router_id;
@ -280,9 +294,11 @@ struct babel_route {
u16 seqno; u16 seqno;
u16 metric; u16 metric;
u16 advert_metric; u16 advert_metric;
u16 smoothed_metric;
u64 router_id; u64 router_id;
ip_addr next_hop; ip_addr next_hop;
btime refresh_time; btime refresh_time;
btime smoothed_time;
btime expires; btime expires;
}; };

View file

@ -37,6 +37,13 @@ babel_proto_start: proto_start BABEL
this_proto = proto_config_new(&proto_babel, $1); this_proto = proto_config_new(&proto_babel, $1);
init_list(&BABEL_CFG->iface_list); init_list(&BABEL_CFG->iface_list);
BABEL_CFG->hold_time = 1 S_; BABEL_CFG->hold_time = 1 S_;
BABEL_CFG->metric_decay = BABEL_SMOOTHING_DECAY;
};
babel_proto_finish:
{
if (BABEL_CFG->metric_decay)
BABEL_CFG->smooth_recp = BABEL_SMOOTHING_UNIT + BABEL_SMOOTHING_CONSTANT / BABEL_CFG->metric_decay;
}; };
babel_proto_item: babel_proto_item:
@ -44,6 +51,7 @@ babel_proto_item:
| proto_channel | proto_channel
| INTERFACE babel_iface | INTERFACE babel_iface
| RANDOMIZE ROUTER ID bool { BABEL_CFG->randomize_router_id = $4; } | RANDOMIZE ROUTER ID bool { BABEL_CFG->randomize_router_id = $4; }
| METRIC DECAY expr_us { BABEL_CFG->metric_decay = $3; if ($3 && (($3 < BABEL_SMOOTHING_STEP) || ($3 > BABEL_SMOOTHING_DECAY_MAX))) cf_error("Metric decay must be 0, or between 1-180s"); }
; ;
babel_proto_opts: babel_proto_opts:
@ -52,7 +60,7 @@ babel_proto_opts:
; ;
babel_proto: babel_proto:
babel_proto_start proto_name '{' babel_proto_opts '}'; babel_proto_start proto_name '{' babel_proto_opts '}' babel_proto_finish;
babel_iface_start: babel_iface_start: