Merge branch 'master' of ssh://git.nic.cz/bird

This commit is contained in:
Ondrej Filip 2012-04-26 17:03:53 +02:00
commit 1f85226ecb
11 changed files with 132 additions and 72 deletions

View file

@ -431,13 +431,19 @@ to zero to disable it. An empty <cf><m/switch/</cf> is equivalent to <cf/on/
<tag>export <m/filter/</tag> This is similar to the <cf>import</cf> keyword, except that it <tag>export <m/filter/</tag> This is similar to the <cf>import</cf> keyword, except that it
works in the direction from the routing table to the protocol. Default: <cf/none/. works in the direction from the routing table to the protocol. Default: <cf/none/.
<tag>import limit <m/number/ exceed warn | block | restart | disable</tag> <tag>import limit <m/number/ [exceed warn | block | restart | disable]</tag>
Specify an import route limit and the action to be taken when Specify an import route limit (a maximum number of routes
the limit is hit. Warn action just prints warning log imported from the protocol) and optionally the action to be
message. Block action ignores new routes (and converts route taken when the limit is hit. Warn action just prints warning
updates to withdraws) coming from the protocol. Restart and log message. Block action ignores new routes coming from the
disable actions shut the protocol down like appropriate protocol. Restart and disable actions shut the protocol down
commands. Default: <cf/none/. like appropriate commands. Disable is the default action if an
action is not explicitly specified. Default: <cf/none/.
<tag>export limit <m/number/ [exceed warn | block | restart | disable]</tag>
Specify an export route limit, works similarly to
the <cf>import limit</cf> option, but for the routes exported
to the protocol. Default: <cf/none/.
<tag>description "<m/text/"</tag> This is an optional <tag>description "<m/text/"</tag> This is an optional
description of the protocol. It is displayed as a part of the description of the protocol. It is displayed as a part of the

View file

@ -179,6 +179,7 @@ proto_item:
| IMPORT imexport { this_proto->in_filter = $2; } | IMPORT imexport { this_proto->in_filter = $2; }
| EXPORT imexport { this_proto->out_filter = $2; } | EXPORT imexport { this_proto->out_filter = $2; }
| IMPORT LIMIT limit_spec { this_proto->in_limit = $3; } | IMPORT LIMIT limit_spec { this_proto->in_limit = $3; }
| EXPORT LIMIT limit_spec { this_proto->out_limit = $3; }
| TABLE rtable { this_proto->table = $2; } | TABLE rtable { this_proto->table = $2; }
| ROUTER ID idval { this_proto->router_id = $3; } | ROUTER ID idval { this_proto->router_id = $3; }
| DESCRIPTION TEXT { this_proto->dsc = $2; } | DESCRIPTION TEXT { this_proto->dsc = $2; }
@ -192,17 +193,18 @@ imexport:
; ;
limit_action: limit_action:
WARN { $$ = PLA_WARN; } /* default */ { $$ = PLA_DISABLE; }
| BLOCK { $$ = PLA_BLOCK; } | EXCEED WARN { $$ = PLA_WARN; }
| RESTART { $$ = PLA_RESTART; } | EXCEED BLOCK { $$ = PLA_BLOCK; }
| DISABLE { $$ = PLA_DISABLE; } | EXCEED RESTART { $$ = PLA_RESTART; }
| EXCEED DISABLE { $$ = PLA_DISABLE; }
; ;
limit_spec: limit_spec:
expr EXCEED limit_action { expr limit_action {
struct proto_limit *l = cfg_allocz(sizeof(struct proto_limit)); struct proto_limit *l = cfg_allocz(sizeof(struct proto_limit));
l->limit = $1; l->limit = $1;
l->action = $3; l->action = $2;
$$ = l; $$ = l;
} }
; ;

View file

@ -406,13 +406,14 @@ proto_reconfigure(struct proto *p, struct proto_config *oc, struct proto_config
if (p->proto->multitable) if (p->proto->multitable)
return 1; return 1;
/* Update filters in the main announce hook */ /* Update filters and limits in the main announce hook
Note that this also resets limit state */
if (p->main_ahook) if (p->main_ahook)
{ {
p->main_ahook->in_filter = nc->in_filter; p->main_ahook->in_filter = nc->in_filter;
p->main_ahook->out_filter = nc->out_filter; p->main_ahook->out_filter = nc->out_filter;
p->main_ahook->in_limit = nc->in_limit; p->main_ahook->in_limit = nc->in_limit;
// p->main_ahook->out_limit = nc->out_limit; p->main_ahook->out_limit = nc->out_limit;
} }
/* Update routes when filters changed. If the protocol in not UP, /* Update routes when filters changed. If the protocol in not UP,
@ -774,9 +775,16 @@ proto_schedule_feed(struct proto *p, int initial)
p->core_state = FS_FEEDING; p->core_state = FS_FEEDING;
p->refeeding = !initial; p->refeeding = !initial;
/* Hack: reset exp_routes during refeed, and do not decrease it later */ /* FIXME: This should be changed for better support of multitable protos */
if (!initial) if (!initial)
{
struct announce_hook *ah;
for (ah = p->ahooks; ah; ah = ah->next)
proto_reset_limit(ah->out_limit);
/* Hack: reset exp_routes during refeed, and do not decrease it later */
p->stats.exp_routes = 0; p->stats.exp_routes = 0;
}
/* Connect protocol to routing table */ /* Connect protocol to routing table */
if (initial && !p->proto->multitable) if (initial && !p->proto->multitable)
@ -785,7 +793,9 @@ proto_schedule_feed(struct proto *p, int initial)
p->main_ahook->in_filter = p->cf->in_filter; p->main_ahook->in_filter = p->cf->in_filter;
p->main_ahook->out_filter = p->cf->out_filter; p->main_ahook->out_filter = p->cf->out_filter;
p->main_ahook->in_limit = p->cf->in_limit; p->main_ahook->in_limit = p->cf->in_limit;
// p->main_ahook->out_limit = p->cf->out_limit; p->main_ahook->out_limit = p->cf->out_limit;
proto_reset_limit(p->main_ahook->in_limit);
proto_reset_limit(p->main_ahook->out_limit);
} }
proto_relink(p); proto_relink(p);
@ -870,6 +880,8 @@ proto_schedule_flush(struct proto *p)
proto_schedule_flush_loop(); proto_schedule_flush_loop();
} }
/* Temporary hack to propagate restart to BGP */
int proto_restart;
static void static void
proto_shutdown_loop(struct timer *t UNUSED) proto_shutdown_loop(struct timer *t UNUSED)
@ -879,11 +891,11 @@ proto_shutdown_loop(struct timer *t UNUSED)
WALK_LIST_DELSAFE(p, p_next, active_proto_list) WALK_LIST_DELSAFE(p, p_next, active_proto_list)
if (p->down_sched) if (p->down_sched)
{ {
int restart = (p->down_sched == PDS_RESTART); proto_restart = (p->down_sched == PDS_RESTART);
p->disabled = 1; p->disabled = 1;
proto_rethink_goal(p); proto_rethink_goal(p);
if (restart) if (proto_restart)
{ {
p->disabled = 0; p->disabled = 0;
proto_rethink_goal(p); proto_rethink_goal(p);
@ -953,43 +965,43 @@ proto_limit_name(struct proto_limit *l)
* proto_notify_limit: notify about limit hit and take appropriate action * proto_notify_limit: notify about limit hit and take appropriate action
* @ah: announce hook * @ah: announce hook
* @l: limit being hit * @l: limit being hit
* @rt_count: the number of routes
* *
* The function is called by the route processing core when limit @l * The function is called by the route processing core when limit @l
* is breached. It activates the limit and tooks appropriate action * is breached. It activates the limit and tooks appropriate action
* according to @l->action. It also says what should be done with the * according to @l->action.
* route that breached the limit.
*
* Returns 1 if the route should be freed, 0 otherwise.
*/ */
int void
proto_notify_limit(struct announce_hook *ah, struct proto_limit *l) proto_notify_limit(struct announce_hook *ah, struct proto_limit *l, u32 rt_count)
{ {
struct proto *p = ah->proto; struct proto *p = ah->proto;
int dir = (ah->in_limit == l); int dir = (ah->in_limit == l);
if (l->active) if (l->state == PLS_BLOCKED)
return (l->action != PLA_WARN); return;
l->active = 1; /* For warning action, we want the log message every time we hit the limit */
if (!l->state || ((l->action == PLA_WARN) && (rt_count == l->limit)))
log(L_WARN "Protocol %s hits route %s limit (%d), action: %s", log(L_WARN "Protocol %s hits route %s limit (%d), action: %s",
p->name, dir ? "import" : "export", l->limit, proto_limit_name(l)); p->name, dir ? "import" : "export", l->limit, proto_limit_name(l));
switch (l->action) switch (l->action)
{ {
case PLA_WARN: case PLA_WARN:
return 0; l->state = PLS_ACTIVE;
break;
case PLA_BLOCK: case PLA_BLOCK:
return 1; l->state = PLS_BLOCKED;
break;
case PLA_RESTART: case PLA_RESTART:
case PLA_DISABLE: case PLA_DISABLE:
l->state = PLS_BLOCKED;
proto_schedule_down(p, l->action == PLA_RESTART, proto_schedule_down(p, l->action == PLA_RESTART,
dir ? PDC_IN_LIMIT_HIT : PDC_OUT_LIMIT_HIT); dir ? PDC_IN_LIMIT_HIT : PDC_OUT_LIMIT_HIT);
return 1; break;
} }
return 0;
} }
/** /**
@ -1101,9 +1113,11 @@ proto_show_stats(struct proto_stats *s)
void void
proto_show_limit(struct proto_limit *l, const char *dsc) proto_show_limit(struct proto_limit *l, const char *dsc)
{ {
if (l) if (!l)
cli_msg(-1006, " %16s%d, action: %s%s", dsc, l->limit, return;
proto_limit_name(l), l->active ? " [HIT]" : "");
cli_msg(-1006, " %-16s%d%s", dsc, l->limit, l->state ? " [HIT]" : "");
cli_msg(-1006, " Action: %s", proto_limit_name(l));
} }
void void
@ -1115,6 +1129,7 @@ proto_show_basic_info(struct proto *p)
cli_msg(-1006, " Output filter: %s", filter_name(p->cf->out_filter)); cli_msg(-1006, " Output filter: %s", filter_name(p->cf->out_filter));
proto_show_limit(p->cf->in_limit, "Import limit:"); proto_show_limit(p->cf->in_limit, "Import limit:");
proto_show_limit(p->cf->out_limit, "Export limit:");
if (p->proto_state != PS_DOWN) if (p->proto_state != PS_DOWN)
proto_show_stats(&p->stats); proto_show_stats(&p->stats);
@ -1233,8 +1248,8 @@ proto_cmd_reload(struct proto *p, unsigned int dir, int cnt UNUSED)
* Should be done before reload_routes() hook? * Should be done before reload_routes() hook?
* Perhaps, but these hooks work asynchronously. * Perhaps, but these hooks work asynchronously.
*/ */
if (!p->proto->multitable && p->main_ahook->in_limit) if (!p->proto->multitable)
p->main_ahook->in_limit->active = 0; proto_reset_limit(p->main_ahook->in_limit);
} }
/* re-exporting routes */ /* re-exporting routes */

View file

@ -95,7 +95,7 @@ struct proto_config {
struct rtable_config *table; /* Table we're attached to */ struct rtable_config *table; /* Table we're attached to */
struct filter *in_filter, *out_filter; /* Attached filters */ struct filter *in_filter, *out_filter; /* Attached filters */
struct proto_limit *in_limit; /* Limit for importing routes from protocol */ struct proto_limit *in_limit; /* Limit for importing routes from protocol */
// struct proto_limit *out_limit; /* Limit for exporting routes to protocol */ struct proto_limit *out_limit; /* Limit for exporting routes to protocol */
/* Check proto_reconfigure() and proto_copy_config() after changing struct proto_config */ /* Check proto_reconfigure() and proto_copy_config() after changing struct proto_config */
@ -375,13 +375,24 @@ extern struct proto_config *cf_dev_proto;
#define PLA_RESTART 4 /* Force protocol restart */ #define PLA_RESTART 4 /* Force protocol restart */
#define PLA_DISABLE 5 /* Shutdown and disable protocol */ #define PLA_DISABLE 5 /* Shutdown and disable protocol */
#define PLS_INITIAL 0 /* Initial limit state after protocol start */
#define PLS_ACTIVE 1 /* Limit was hit */
#define PLS_BLOCKED 2 /* Limit is active and blocking new routes */
struct proto_limit { struct proto_limit {
u32 limit; /* Maximum number of prefixes */ u32 limit; /* Maximum number of prefixes */
byte action; /* Action to take (PLA_*) */ byte action; /* Action to take (PLA_*) */
byte active; /* Limit is active */ byte state; /* State of limit (PLS_*) */
}; };
int proto_notify_limit(struct announce_hook *ah, struct proto_limit *l); void proto_notify_limit(struct announce_hook *ah, struct proto_limit *l, u32 rt_count);
static inline void proto_reset_limit(struct proto_limit *l)
{
if (l)
l->state = PLS_INITIAL;
}
/* /*
* Route Announcement Hook * Route Announcement Hook
@ -394,7 +405,7 @@ struct announce_hook {
struct filter *in_filter; /* Input filter */ struct filter *in_filter; /* Input filter */
struct filter *out_filter; /* Output filter */ struct filter *out_filter; /* Output filter */
struct proto_limit *in_limit; /* Input limit */ struct proto_limit *in_limit; /* Input limit */
// struct proto_limit *out_limit; /* Output limit */ struct proto_limit *out_limit; /* Output limit */
struct proto_stats *stats; /* Per-table protocol statistics */ struct proto_stats *stats; /* Per-table protocol statistics */
struct announce_hook *next; /* Next hook for the same protocol */ struct announce_hook *next; /* Next hook for the same protocol */
}; };

View file

@ -273,6 +273,23 @@ do_rte_announce(struct announce_hook *ah, int type UNUSED, net *net, rte *new, r
if (!new && !old) if (!new && !old)
return; return;
struct proto_limit *l = ah->out_limit;
if (l && new && (!old || refeed))
{
if (stats->exp_routes >= l->limit)
proto_notify_limit(ah, l, stats->exp_routes);
if (l->state == PLS_BLOCKED)
{
/* Exported route counter ignores whether the route was
blocked by limit, to be consistent when limits change */
stats->exp_routes++;
stats->exp_updates_rejected++;
rte_trace_out(D_FILTERS, p, new, "rejected [limit]");
goto done;
}
}
if (new) if (new)
stats->exp_updates_accepted++; stats->exp_updates_accepted++;
else else
@ -307,6 +324,8 @@ do_rte_announce(struct announce_hook *ah, int type UNUSED, net *net, rte *new, r
} }
else else
p->rt_notify(p, ah->table, net, new, old, new->attrs->eattrs); p->rt_notify(p, ah->table, net, new, old, new->attrs->eattrs);
done:
if (new && new != new0) /* Discard temporary rte's */ if (new && new != new0) /* Discard temporary rte's */
rte_free(new); rte_free(new);
if (old && old != old0) if (old && old != old0)
@ -485,13 +504,19 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str
} }
struct proto_limit *l = ah->in_limit; struct proto_limit *l = ah->in_limit;
if (l && !old && new && (stats->imp_routes >= l->limit) && proto_notify_limit(ah, l)) if (l && !old && new)
{
if (stats->imp_routes >= l->limit)
proto_notify_limit(ah, l, stats->imp_routes);
if (l->state == PLS_BLOCKED)
{ {
stats->imp_updates_ignored++; stats->imp_updates_ignored++;
rte_trace_in(D_FILTERS, p, new, "ignored [limit]"); rte_trace_in(D_FILTERS, p, new, "ignored [limit]");
rte_free_quick(new); rte_free_quick(new);
return; return;
} }
}
if (new) if (new)
stats->imp_updates_accepted++; stats->imp_updates_accepted++;

View file

@ -848,6 +848,8 @@ bgp_start(struct proto *P)
return PS_START; return PS_START;
} }
extern int proto_restart;
static int static int
bgp_shutdown(struct proto *P) bgp_shutdown(struct proto *P)
{ {
@ -877,10 +879,16 @@ bgp_shutdown(struct proto *P)
case PDC_IN_LIMIT_HIT: case PDC_IN_LIMIT_HIT:
subcode = 1; // Errcode 6, 1 - max number of prefixes reached subcode = 1; // Errcode 6, 1 - max number of prefixes reached
/* log message for compatibility */
log(L_WARN "%s: Route limit exceeded, shutting down", p->p.name); log(L_WARN "%s: Route limit exceeded, shutting down", p->p.name);
goto limit;
case PDC_OUT_LIMIT_HIT:
subcode = proto_restart ? 4 : 2; // Administrative reset or shutdown
limit:
bgp_store_error(p, NULL, BE_AUTO_DOWN, BEA_ROUTE_LIMIT_EXCEEDED); bgp_store_error(p, NULL, BE_AUTO_DOWN, BEA_ROUTE_LIMIT_EXCEEDED);
if (P->cf->in_limit->action == PLA_RESTART) if (proto_restart)
bgp_update_startup_delay(p); bgp_update_startup_delay(p);
else else
p->startup_delay = 0; p->startup_delay = 0;
@ -1165,9 +1173,9 @@ bgp_show_proto_info(struct proto *P)
p->rs_client ? " route-server" : "", p->rs_client ? " route-server" : "",
p->as4_session ? " AS4" : ""); p->as4_session ? " AS4" : "");
cli_msg(-1006, " Source address: %I", p->source_addr); cli_msg(-1006, " Source address: %I", p->source_addr);
if (p->cf->route_limit) if (P->cf->in_limit)
cli_msg(-1006, " Route limit: %d/%d", cli_msg(-1006, " Route limit: %d/%d",
p->p.stats.imp_routes, p->cf->route_limit); p->p.stats.imp_routes, P->cf->in_limit->limit);
cli_msg(-1006, " Hold timer: %d/%d", cli_msg(-1006, " Hold timer: %d/%d",
tm_remains(c->hold_timer), c->hold_time); tm_remains(c->hold_timer), c->hold_time);
cli_msg(-1006, " Keepalive timer: %d/%d", cli_msg(-1006, " Keepalive timer: %d/%d",

View file

@ -40,7 +40,6 @@ struct bgp_config {
int rr_client; /* Whether neighbor is RR client of me */ int rr_client; /* Whether neighbor is RR client of me */
int rs_client; /* Whether neighbor is RS client of me */ int rs_client; /* Whether neighbor is RS client of me */
int advertise_ipv4; /* Whether we should add IPv4 capability advertisement to OPEN message */ int advertise_ipv4; /* Whether we should add IPv4 capability advertisement to OPEN message */
u32 route_limit; /* Number of routes that may be imported, 0 means disable limit */
int passive; /* Do not initiate outgoing connection */ int passive; /* Do not initiate outgoing connection */
int interpret_communities; /* Hardwired handling of well-known communities */ int interpret_communities; /* Hardwired handling of well-known communities */
unsigned connect_retry_time; unsigned connect_retry_time;

View file

@ -36,7 +36,6 @@ pipe_proto:
cf_error("Routing table name expected"); cf_error("Routing table name expected");
PIPE_CFG->peer = $4->def; PIPE_CFG->peer = $4->def;
} }
| pipe_proto EXPORT LIMIT limit_spec ';' { PIPE_CFG->out_limit = $4; }
| pipe_proto MODE OPAQUE ';' { PIPE_CFG->mode = PIPE_OPAQUE; } | pipe_proto MODE OPAQUE ';' { PIPE_CFG->mode = PIPE_OPAQUE; }
| pipe_proto MODE TRANSPARENT ';' { PIPE_CFG->mode = PIPE_TRANSPARENT; } | pipe_proto MODE TRANSPARENT ';' { PIPE_CFG->mode = PIPE_TRANSPARENT; }
; ;

View file

@ -127,10 +127,8 @@ pipe_reload_routes(struct proto *P)
*/ */
proto_request_feeding(P); proto_request_feeding(P);
if (P->main_ahook->in_limit) proto_reset_limit(P->main_ahook->in_limit);
P->main_ahook->in_limit->active = 0; proto_reset_limit(p->peer_ahook->in_limit);
if (p->peer_ahook->in_limit)
p->peer_ahook->in_limit->active = 0;
return 1; return 1;
} }
@ -168,10 +166,12 @@ pipe_start(struct proto *P)
P->main_ahook = proto_add_announce_hook(P, P->table, &P->stats); P->main_ahook = proto_add_announce_hook(P, P->table, &P->stats);
P->main_ahook->out_filter = cf->c.out_filter; P->main_ahook->out_filter = cf->c.out_filter;
P->main_ahook->in_limit = cf->c.in_limit; P->main_ahook->in_limit = cf->c.in_limit;
proto_reset_limit(P->main_ahook->in_limit);
p->peer_ahook = proto_add_announce_hook(P, p->peer_table, &p->peer_stats); p->peer_ahook = proto_add_announce_hook(P, p->peer_table, &p->peer_stats);
p->peer_ahook->out_filter = cf->c.in_filter; p->peer_ahook->out_filter = cf->c.in_filter;
p->peer_ahook->in_limit = cf->out_limit; p->peer_ahook->in_limit = cf->c.out_limit;
proto_reset_limit(p->peer_ahook->in_limit);
return PS_UP; return PS_UP;
} }
@ -225,7 +225,7 @@ pipe_reconfigure(struct proto *P, struct proto_config *new)
if (p->peer_ahook) if (p->peer_ahook)
{ {
p->peer_ahook->out_filter = new->in_filter; p->peer_ahook->out_filter = new->in_filter;
p->peer_ahook->in_limit = nc->out_limit; p->peer_ahook->in_limit = new->out_limit;
} }
if ((P->proto_state != PS_UP) || (proto_reconfig_type == RECONFIG_SOFT)) if ((P->proto_state != PS_UP) || (proto_reconfig_type == RECONFIG_SOFT))
@ -311,7 +311,7 @@ pipe_show_proto_info(struct proto *P)
cli_msg(-1006, " Output filter: %s", filter_name(cf->c.out_filter)); cli_msg(-1006, " Output filter: %s", filter_name(cf->c.out_filter));
proto_show_limit(cf->c.in_limit, "Import limit:"); proto_show_limit(cf->c.in_limit, "Import limit:");
proto_show_limit(cf->out_limit, "Export limit:"); proto_show_limit(cf->c.out_limit, "Export limit:");
if (P->proto_state != PS_DOWN) if (P->proto_state != PS_DOWN)
pipe_show_stats(p); pipe_show_stats(p);

View file

@ -15,7 +15,6 @@
struct pipe_config { struct pipe_config {
struct proto_config c; struct proto_config c;
struct rtable_config *peer; /* Table we're connected to */ struct rtable_config *peer; /* Table we're connected to */
struct proto_limit *out_limit; /* Export route limit */
int mode; /* PIPE_OPAQUE or PIPE_TRANSPARENT */ int mode; /* PIPE_OPAQUE or PIPE_TRANSPARENT */
}; };

View file

@ -588,9 +588,9 @@ krt_capable(rte *e)
switch (a->dest) switch (a->dest)
{ {
case RTD_ROUTER: case RTD_ROUTER:
if (ipa_has_link_scope(a->gw) && (a->iface == NULL))
return 0;
case RTD_DEVICE: case RTD_DEVICE:
if (a->iface == NULL)
return 0;
case RTD_BLACKHOLE: case RTD_BLACKHOLE:
case RTD_UNREACHABLE: case RTD_UNREACHABLE:
case RTD_PROHIBIT: case RTD_PROHIBIT:
@ -653,20 +653,16 @@ nl_send_route(struct krt_proto *p, rte *e, struct ea_list *eattrs, int new)
if (ea = ea_find(eattrs, EA_KRT_REALM)) if (ea = ea_find(eattrs, EA_KRT_REALM))
nl_add_attr_u32(&r.h, sizeof(r), RTA_FLOW, ea->u.data); nl_add_attr_u32(&r.h, sizeof(r), RTA_FLOW, ea->u.data);
/* a->iface != NULL checked in krt_capable() for router and device routes */
switch (a->dest) switch (a->dest)
{ {
case RTD_ROUTER: case RTD_ROUTER:
r.r.rtm_type = RTN_UNICAST; r.r.rtm_type = RTN_UNICAST;
nl_add_attr_ipa(&r.h, sizeof(r), RTA_GATEWAY, a->gw);
/* a->iface != NULL checked in krt_capable() */
if (ipa_has_link_scope(a->gw))
nl_add_attr_u32(&r.h, sizeof(r), RTA_OIF, a->iface->index); nl_add_attr_u32(&r.h, sizeof(r), RTA_OIF, a->iface->index);
nl_add_attr_ipa(&r.h, sizeof(r), RTA_GATEWAY, a->gw);
break; break;
case RTD_DEVICE: case RTD_DEVICE:
if (!a->iface)
return -1;
r.r.rtm_type = RTN_UNICAST; r.r.rtm_type = RTN_UNICAST;
nl_add_attr_u32(&r.h, sizeof(r), RTA_OIF, a->iface->index); nl_add_attr_u32(&r.h, sizeof(r), RTA_OIF, a->iface->index);
break; break;