From cd1d99611e445c9fe2452d05627ccfc624f35c39 Mon Sep 17 00:00:00 2001 From: "Ondrej Zajicek (work)" Date: Tue, 19 Sep 2017 19:55:37 +0200 Subject: [PATCH 1/9] BGP: Shutdown communication (RFC 8203) The patch implements BGP Administrative Shutdown Communication (RFC 8203) allowing BGP operators to pass messages related to BGP session administrative shutdown/restart. It handles both transmit and receive of shutdown messages. Messages are logged and may be displayed by show protocol all command. Thanks to Job Snijders for the basic patch. --- nest/config.Y | 12 ++++----- nest/proto.c | 63 +++++++++++++++++++++++++++++++++++-------- nest/protocol.h | 18 +++++++------ proto/babel/packets.c | 2 -- proto/bgp/bgp.c | 48 +++++++++++++++++++++++---------- proto/bgp/bgp.h | 2 +- proto/bgp/packets.c | 58 ++++++++++++++++++++++++++++++--------- 7 files changed, 149 insertions(+), 54 deletions(-) diff --git a/nest/config.Y b/nest/config.Y index 025e8969..f51be6ea 100644 --- a/nest/config.Y +++ b/nest/config.Y @@ -696,12 +696,12 @@ echo_size: } ; -CF_CLI(DISABLE, proto_patt, | \"\" | all, [[Disable protocol]]) -{ proto_apply_cmd($2, proto_cmd_disable, 1, 0); } ; -CF_CLI(ENABLE, proto_patt, | \"\" | all, [[Enable protocol]]) -{ proto_apply_cmd($2, proto_cmd_enable, 1, 0); } ; -CF_CLI(RESTART, proto_patt, | \"\" | all, [[Restart protocol]]) -{ proto_apply_cmd($2, proto_cmd_restart, 1, 0); } ; +CF_CLI(DISABLE, proto_patt text_or_none, ( | \"\" | all) [message], [[Disable protocol]]) +{ proto_apply_cmd($2, proto_cmd_disable, 1, (uintptr_t) $3); } ; +CF_CLI(ENABLE, proto_patt text_or_none, ( | \"\" | all) [message], [[Enable protocol]]) +{ proto_apply_cmd($2, proto_cmd_enable, 1, (uintptr_t) $3); } ; +CF_CLI(RESTART, proto_patt text_or_none, ( | \"\" | all) [message], [[Restart protocol]]) +{ proto_apply_cmd($2, proto_cmd_restart, 1, (uintptr_t) $3); } ; CF_CLI(RELOAD, proto_patt, | \"\" | all, [[Reload protocol]]) { proto_apply_cmd($2, proto_cmd_reload, 1, CMD_RELOAD); } ; CF_CLI(RELOAD IN, proto_patt, | \"\" | all, [[Reload protocol (just imported routes)]]) diff --git a/nest/proto.c b/nest/proto.c index 64422ee3..552d53ae 100644 --- a/nest/proto.c +++ b/nest/proto.c @@ -610,6 +610,7 @@ proto_rethink_goal(struct proto *p) config_del_obstacle(p->cf->global); rem_node(&p->n); rem_node(&p->glob_node); + mb_free(p->message); mb_free(p); if (!nc) return; @@ -1096,6 +1097,39 @@ proto_schedule_down(struct proto *p, byte restart, byte code) tm_start_max(proto_shutdown_timer, restart ? 2 : 0); } +/** + * proto_set_message - set administrative message to protocol + * @p: protocol + * @msg: message + * @len: message length (-1 for NULL-terminated string) + * + * The function sets administrative message (string) related to protocol state + * change. It is called by the nest code for manual enable/disable/restart + * commands all routes to the protocol, and by protocol-specific code when the + * protocol state change is initiated by the protocol. Using NULL message clears + * the last message. The message string may be either NULL-terminated or with an + * explicit length. + */ +void +proto_set_message(struct proto *p, char *msg, int len) +{ + mb_free(p->message); + p->message = NULL; + + if (!msg || !len) + return; + + if (len < 0) + len = strlen(msg); + + if (!len) + return; + + p->message = mb_alloc(proto_pool, len + 1); + memcpy(p->message, msg, len); + p->message[len] = 0; +} + /** * proto_request_feeding - request feeding routes to the protocol @@ -1497,7 +1531,7 @@ proto_show_basic_info(struct proto *p) } void -proto_cmd_show(struct proto *p, uint verbose, int cnt) +proto_cmd_show(struct proto *p, uintptr_t verbose, int cnt) { byte buf[256], tbuf[TM_DATETIME_BUFFER_SIZE]; @@ -1520,6 +1554,10 @@ proto_cmd_show(struct proto *p, uint verbose, int cnt) { if (p->cf->dsc) cli_msg(-1006, " Description: %s", p->cf->dsc); + + if (p->message) + cli_msg(-1006, " Message: %s", p->message); + if (p->cf->router_id) cli_msg(-1006, " Router ID: %R", p->cf->router_id); @@ -1533,7 +1571,7 @@ proto_cmd_show(struct proto *p, uint verbose, int cnt) } void -proto_cmd_disable(struct proto *p, uint arg UNUSED, int cnt UNUSED) +proto_cmd_disable(struct proto *p, uintptr_t arg, int cnt UNUSED) { if (p->disabled) { @@ -1544,12 +1582,13 @@ proto_cmd_disable(struct proto *p, uint arg UNUSED, int cnt UNUSED) log(L_INFO "Disabling protocol %s", p->name); p->disabled = 1; p->down_code = PDC_CMD_DISABLE; + proto_set_message(p, (char *) arg, -1); proto_rethink_goal(p); cli_msg(-9, "%s: disabled", p->name); } void -proto_cmd_enable(struct proto *p, uint arg UNUSED, int cnt UNUSED) +proto_cmd_enable(struct proto *p, uintptr_t arg, int cnt UNUSED) { if (!p->disabled) { @@ -1559,12 +1598,13 @@ proto_cmd_enable(struct proto *p, uint arg UNUSED, int cnt UNUSED) log(L_INFO "Enabling protocol %s", p->name); p->disabled = 0; + proto_set_message(p, (char *) arg, -1); proto_rethink_goal(p); cli_msg(-11, "%s: enabled", p->name); } void -proto_cmd_restart(struct proto *p, uint arg UNUSED, int cnt UNUSED) +proto_cmd_restart(struct proto *p, uintptr_t arg, int cnt UNUSED) { if (p->disabled) { @@ -1575,6 +1615,7 @@ proto_cmd_restart(struct proto *p, uint arg UNUSED, int cnt UNUSED) log(L_INFO "Restarting protocol %s", p->name); p->disabled = 1; p->down_code = PDC_CMD_RESTART; + proto_set_message(p, (char *) arg, -1); proto_rethink_goal(p); p->disabled = 0; proto_rethink_goal(p); @@ -1582,7 +1623,7 @@ proto_cmd_restart(struct proto *p, uint arg UNUSED, int cnt UNUSED) } void -proto_cmd_reload(struct proto *p, uint dir, int cnt UNUSED) +proto_cmd_reload(struct proto *p, uintptr_t dir, int cnt UNUSED) { if (p->disabled) { @@ -1624,19 +1665,19 @@ proto_cmd_reload(struct proto *p, uint dir, int cnt UNUSED) } void -proto_cmd_debug(struct proto *p, uint mask, int cnt UNUSED) +proto_cmd_debug(struct proto *p, uintptr_t mask, int cnt UNUSED) { p->debug = mask; } void -proto_cmd_mrtdump(struct proto *p, uint mask, int cnt UNUSED) +proto_cmd_mrtdump(struct proto *p, uintptr_t mask, int cnt UNUSED) { p->mrtdump = mask; } static void -proto_apply_cmd_symbol(struct symbol *s, void (* cmd)(struct proto *, uint, int), uint arg) +proto_apply_cmd_symbol(struct symbol *s, void (* cmd)(struct proto *, uintptr_t, int), uintptr_t arg) { if (s->class != SYM_PROTO) { @@ -1649,7 +1690,7 @@ proto_apply_cmd_symbol(struct symbol *s, void (* cmd)(struct proto *, uint, int) } static void -proto_apply_cmd_patt(char *patt, void (* cmd)(struct proto *, uint, int), uint arg) +proto_apply_cmd_patt(char *patt, void (* cmd)(struct proto *, uintptr_t, int), uintptr_t arg) { int cnt = 0; @@ -1669,8 +1710,8 @@ proto_apply_cmd_patt(char *patt, void (* cmd)(struct proto *, uint, int), uint a } void -proto_apply_cmd(struct proto_spec ps, void (* cmd)(struct proto *, uint, int), - int restricted, uint arg) +proto_apply_cmd(struct proto_spec ps, void (* cmd)(struct proto *, uintptr_t, int), + int restricted, uintptr_t arg) { if (restricted && cli_access_restricted()) return; diff --git a/nest/protocol.h b/nest/protocol.h index 18dfbd6f..5aca9a4e 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -164,6 +164,7 @@ struct proto { u32 hash_key; /* Random key used for hashing of neighbors */ bird_clock_t last_state_change; /* Time of last state transition */ char *last_state_name_announced; /* Last state name we've announced to the user */ + char *message; /* State-change message, allocated from proto_pool */ struct proto_stats stats; /* Current protocol statistics */ /* @@ -250,6 +251,7 @@ struct proto_spec { void *proto_new(struct proto_config *, unsigned size); void *proto_config_new(struct protocol *, int class); void proto_copy_config(struct proto_config *dest, struct proto_config *src); +void proto_set_message(struct proto *p, char *msg, int len); void proto_request_feeding(struct proto *p); static inline void @@ -267,15 +269,15 @@ void proto_graceful_restart_unlock(struct proto *p); void proto_show_limit(struct proto_limit *l, const char *dsc); void proto_show_basic_info(struct proto *p); -void proto_cmd_show(struct proto *, uint, int); -void proto_cmd_disable(struct proto *, uint, int); -void proto_cmd_enable(struct proto *, uint, int); -void proto_cmd_restart(struct proto *, uint, int); -void proto_cmd_reload(struct proto *, uint, int); -void proto_cmd_debug(struct proto *, uint, int); -void proto_cmd_mrtdump(struct proto *, uint, int); +void proto_cmd_show(struct proto *, uintptr_t, int); +void proto_cmd_disable(struct proto *, uintptr_t, int); +void proto_cmd_enable(struct proto *, uintptr_t, int); +void proto_cmd_restart(struct proto *, uintptr_t, int); +void proto_cmd_reload(struct proto *, uintptr_t, int); +void proto_cmd_debug(struct proto *, uintptr_t, int); +void proto_cmd_mrtdump(struct proto *, uintptr_t, int); -void proto_apply_cmd(struct proto_spec ps, void (* cmd)(struct proto *, uint, int), int restricted, uint arg); +void proto_apply_cmd(struct proto_spec ps, void (* cmd)(struct proto *, uintptr_t, int), int restricted, uintptr_t arg); struct proto *proto_get_named(struct symbol *, struct protocol *); #define CMD_RELOAD 0 diff --git a/proto/babel/packets.c b/proto/babel/packets.c index 90421836..e9c6d51d 100644 --- a/proto/babel/packets.c +++ b/proto/babel/packets.c @@ -146,8 +146,6 @@ struct babel_write_state { #define TLV_HDR(tlv,t,l) ({ tlv->type = t; tlv->length = l - sizeof(struct babel_tlv); }) #define TLV_HDR0(tlv,t) TLV_HDR(tlv, t, tlv_data[t].min_length) -#define BYTES(n) ((((uint) n) + 7) / 8) - static inline u16 get_time16(const void *p) { diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index 8a6b2f02..b99672f5 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -290,7 +290,7 @@ bgp_update_startup_delay(struct bgp_proto *p) } static void -bgp_graceful_close_conn(struct bgp_conn *conn, unsigned subcode) +bgp_graceful_close_conn(struct bgp_conn *conn, uint subcode, byte *data, uint len) { switch (conn->state) { @@ -304,7 +304,7 @@ bgp_graceful_close_conn(struct bgp_conn *conn, unsigned subcode) case BS_OPENSENT: case BS_OPENCONFIRM: case BS_ESTABLISHED: - bgp_error(conn, 6, subcode, NULL, 0); + bgp_error(conn, 6, subcode, data, len); return; default: bug("bgp_graceful_close_conn: Unknown state %d", conn->state); @@ -340,11 +340,11 @@ bgp_decision(void *vp) } void -bgp_stop(struct bgp_proto *p, unsigned subcode) +bgp_stop(struct bgp_proto *p, uint subcode, byte *data, uint len) { proto_notify_state(&p->p, PS_STOP); - bgp_graceful_close_conn(&p->outgoing_conn, subcode); - bgp_graceful_close_conn(&p->incoming_conn, subcode); + bgp_graceful_close_conn(&p->outgoing_conn, subcode, data, len); + bgp_graceful_close_conn(&p->incoming_conn, subcode, data, len); ev_schedule(p->event); } @@ -420,7 +420,7 @@ bgp_conn_leave_established_state(struct bgp_proto *p) bgp_free_bucket_table(p); if (p->p.proto_state == PS_UP) - bgp_stop(p, 0); + bgp_stop(p, 0, NULL, 0); } void @@ -516,7 +516,7 @@ bgp_graceful_restart_timeout(timer *t) struct bgp_proto *p = t->data; BGP_TRACE(D_EVENTS, "Neighbor graceful restart timeout"); - bgp_stop(p, 0); + bgp_stop(p, 0, NULL, 0); } @@ -973,7 +973,7 @@ bgp_neigh_notify(neighbor *n) BGP_TRACE(D_EVENTS, "Neighbor lost"); bgp_store_error(p, NULL, BE_MISC, BEM_NEIGHBOR_LOST); /* Perhaps also run bgp_update_startup_delay(p)? */ - bgp_stop(p, 0); + bgp_stop(p, 0, NULL, 0); } } else if (p->cf->check_link && !(n->iface->flags & IF_LINK_UP)) @@ -984,7 +984,7 @@ bgp_neigh_notify(neighbor *n) bgp_store_error(p, NULL, BE_MISC, BEM_LINK_DOWN); if (ps == PS_UP) bgp_update_startup_delay(p); - bgp_stop(p, 0); + bgp_stop(p, 0, NULL, 0); } } else @@ -1009,7 +1009,7 @@ bgp_bfd_notify(struct bfd_request *req) bgp_store_error(p, NULL, BE_MISC, BEM_BFD_DOWN); if (ps == PS_UP) bgp_update_startup_delay(p); - bgp_stop(p, 0); + bgp_stop(p, 0, NULL, 0); } } @@ -1196,7 +1196,11 @@ static int bgp_shutdown(struct proto *P) { struct bgp_proto *p = (struct bgp_proto *) P; - unsigned subcode = 0; + uint subcode = 0; + + char *message = NULL; + byte *data = NULL; + uint len = 0; BGP_TRACE(D_EVENTS, "Shutdown requested"); @@ -1214,10 +1218,12 @@ bgp_shutdown(struct proto *P) case PDC_CMD_DISABLE: case PDC_CMD_SHUTDOWN: subcode = 2; // Errcode 6, 2 - administrative shutdown + message = P->message; break; case PDC_CMD_RESTART: subcode = 4; // Errcode 6, 4 - administrative reset + message = P->message; break; case PDC_RX_LIMIT_HIT: @@ -1242,8 +1248,22 @@ bgp_shutdown(struct proto *P) bgp_store_error(p, NULL, BE_MAN_DOWN, 0); p->startup_delay = 0; - done: - bgp_stop(p, subcode); + /* RFC 8203 - shutdown communication */ + if (message) + { + uint msg_len = strlen(message); + msg_len = MIN(msg_len, 128); + + /* Buffer will be freed automatically by protocol shutdown */ + data = mb_alloc(p->p.pool, msg_len + 1); + len = msg_len + 1; + + data[0] = msg_len; + memcpy(data+1, message, msg_len); + } + +done: + bgp_stop(p, subcode, data, len); return p->p.proto_state; } @@ -1433,7 +1453,7 @@ bgp_error(struct bgp_conn *c, unsigned code, unsigned subcode, byte *data, int l if (code != 6) { bgp_update_startup_delay(p); - bgp_stop(p, 0); + bgp_stop(p, 0, NULL, 0); } } diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h index e47a0eb1..22a150ab 100644 --- a/proto/bgp/bgp.h +++ b/proto/bgp/bgp.h @@ -212,7 +212,7 @@ void bgp_graceful_restart_done(struct bgp_proto *p); void bgp_refresh_begin(struct bgp_proto *p); void bgp_refresh_end(struct bgp_proto *p); void bgp_store_error(struct bgp_proto *p, struct bgp_conn *c, u8 class, u32 code); -void bgp_stop(struct bgp_proto *p, unsigned subcode); +void bgp_stop(struct bgp_proto *p, uint subcode, byte *data, uint len); struct rte_source *bgp_find_source(struct bgp_proto *p, u32 path_id); struct rte_source *bgp_get_source(struct bgp_proto *p, u32 path_id); diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c index ab87bdcc..af3b15b5 100644 --- a/proto/bgp/packets.c +++ b/proto/bgp/packets.c @@ -1494,38 +1494,72 @@ bgp_error_dsc(unsigned code, unsigned subcode) return buff; } +/* RFC 8203 - shutdown communication message */ +static int +bgp_handle_message(struct bgp_proto *p, byte *data, uint len, byte **bp) +{ + byte *msg = data + 1; + uint msg_len = data[0]; + uint i; + + /* Handle zero length message */ + if (msg_len == 0) + return 1; + + /* Handle proper message */ + if ((msg_len > 128) && (msg_len + 1 > len)) + return 0; + + /* Some elementary cleanup */ + for (i = 0; i < msg_len; i++) + if (msg[i] < ' ') + msg[i] = ' '; + + proto_set_message(&p->p, msg, msg_len); + *bp += bsprintf(*bp, ": \"%s\"", p->p.message); + return 1; +} + void bgp_log_error(struct bgp_proto *p, u8 class, char *msg, unsigned code, unsigned subcode, byte *data, unsigned len) { - const byte *name; - byte *t, argbuf[36]; + byte argbuf[256], *t = argbuf; unsigned i; /* Don't report Cease messages generated by myself */ if (code == 6 && class == BE_BGP_TX) return; - name = bgp_error_dsc(code, subcode); - t = argbuf; + /* Reset shutdown message */ + if ((code == 6) && ((subcode == 2) || (subcode == 4))) + proto_set_message(&p->p, NULL, 0); + if (len) { - *t++ = ':'; - *t++ = ' '; - + /* Bad peer AS - we would like to print the AS */ if ((code == 2) && (subcode == 2) && ((len == 2) || (len == 4))) { - /* Bad peer AS - we would like to print the AS */ - t += bsprintf(t, "%d", (len == 2) ? get_u16(data) : get_u32(data)); + t += bsprintf(t, ": %u", (len == 2) ? get_u16(data) : get_u32(data)); goto done; } + + /* RFC 8203 - shutdown communication */ + if (((code == 6) && ((subcode == 2) || (subcode == 4)))) + if (bgp_handle_message(p, data, len, &t)) + goto done; + + *t++ = ':'; + *t++ = ' '; if (len > 16) len = 16; for (i=0; ip.name, msg, name, argbuf); + const byte *dsc = bgp_error_dsc(code, subcode); + log(L_REMOTE "%s: %s: %s%s", p->p.name, msg, dsc, argbuf); } static void @@ -1571,7 +1605,7 @@ bgp_rx_notification(struct bgp_conn *conn, byte *pkt, uint len) if (err) { bgp_update_startup_delay(p); - bgp_stop(p, 0); + bgp_stop(p, 0, NULL, 0); } } From 5a8b1fb047d675badc17ab24175d0db06d7cc00c Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Tue, 5 Sep 2017 15:50:00 +0200 Subject: [PATCH 2/9] filter: Allow assigning enums into extended attributes They are internally ints, but they got refused as a wrong type. This fixes setting of the BGP origin and is also needed for RA. --- filter/filter.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/filter/filter.c b/filter/filter.c index f18970e0..1e8f7d5a 100644 --- a/filter/filter.c +++ b/filter/filter.c @@ -1088,7 +1088,8 @@ interpret(struct f_inst *what) switch (what->aux & EAF_TYPE_MASK) { case EAF_TYPE_INT: - if (v1.type != T_INT) + // Enums are also ints, so allow them in. + if (v1.type != T_INT && (v1.type < T_ENUM_LO || v1.type > T_ENUM_HI)) runtime( "Setting int attribute to non-int value" ); l->attrs[0].u.data = v1.val.i; break; From 2a95e63343a94243745e5d7000bb3e0cb61a4a0f Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Thu, 31 Aug 2017 15:40:23 +0200 Subject: [PATCH 3/9] RAdv: Support for more specific routes (RFC 4191) The patch implements Default Router Preferences and More-Specific Routes (RFC 4191) for RAdv protocol, allowing to announce router preference and more specific routes in router advertisements. Routes can be exported to RAdv like to regular routing protocols. Some cleanups, bugfixes and other changes done by Ondrej Zajicek. --- filter/filter.h | 1 + nest/route.h | 3 +- proto/radv/config.Y | 22 ++- proto/radv/packets.c | 72 ++++++++-- proto/radv/radv.c | 322 +++++++++++++++++++++++++++++++++---------- proto/radv/radv.h | 32 ++++- 6 files changed, 357 insertions(+), 95 deletions(-) diff --git a/filter/filter.h b/filter/filter.h index 049ceb76..72b37461 100644 --- a/filter/filter.h +++ b/filter/filter.h @@ -168,6 +168,7 @@ void val_format(struct f_val v, buffer *buf); #define T_ENUM_RTC 0x33 #define T_ENUM_RTD 0x34 #define T_ENUM_ROA 0x35 +#define T_ENUM_RA_PREFERENCE 0x36 /* new enums go here */ #define T_ENUM_EMPTY 0x3f /* Special hack for atomic_aggr */ diff --git a/nest/route.h b/nest/route.h index 2e6ae5bf..0834da45 100644 --- a/nest/route.h +++ b/nest/route.h @@ -430,7 +430,8 @@ typedef struct eattr { #define EAP_OSPF 3 /* OSPF */ #define EAP_KRT 4 /* Kernel route attributes */ #define EAP_BABEL 5 /* Babel attributes */ -#define EAP_MAX 6 +#define EAP_RADV 6 /* Router advertisment attributes */ +#define EAP_MAX 7 #define EA_CODE(proto,id) (((proto) << 8) | (id)) #define EA_PROTO(ea) ((ea) >> 8) diff --git a/proto/radv/config.Y b/proto/radv/config.Y index 0ff84aeb..2fa11ce2 100644 --- a/proto/radv/config.Y +++ b/proto/radv/config.Y @@ -30,7 +30,10 @@ CF_KEYWORDS(RADV, PREFIX, INTERFACE, MIN, MAX, RA, DELAY, INTERVAL, MANAGED, OTHER, CONFIG, LINGER, LINK, MTU, REACHABLE, TIME, RETRANS, TIMER, CURRENT, HOP, LIMIT, DEFAULT, VALID, PREFERRED, MULT, LIFETIME, SKIP, ONLINK, AUTONOMOUS, RDNSS, DNSSL, NS, DOMAIN, - LOCAL, TRIGGER, SENSITIVE, PREFERENCE, LOW, MEDIUM, HIGH) + LOCAL, TRIGGER, SENSITIVE, PREFERENCE, LOW, MEDIUM, HIGH, PROPAGATE, + ROUTE, ROUTES, RA_PREFERENCE, RA_LIFETIME) + +CF_ENUM(T_ENUM_RA_PREFERENCE, RA_PREF_, LOW, MEDIUM, HIGH) %type radv_mult radv_sensitive radv_preference @@ -45,6 +48,8 @@ radv_proto_start: proto_start RADV init_list(&RADV_CFG->pref_list); init_list(&RADV_CFG->rdnss_list); init_list(&RADV_CFG->dnssl_list); + RADV_CFG->route_lifetime = DEFAULT_VALID_LIFETIME; + RADV_CFG->route_linger_time = DEFAULT_LINGER_TIME; }; radv_proto_item: @@ -58,6 +63,16 @@ radv_proto_item: RADV_CFG->trigger_pxlen = $2.len; RADV_CFG->trigger_valid = 1; } + | PROPAGATE ROUTES bool { RADV_CFG->propagate_routes = $3; } + | ROUTE LIFETIME expr radv_sensitive { + RADV_CFG->route_lifetime = $3; + if ($4 != -1) RADV_CFG->route_lifetime_sensitive = $4; + } + | ROUTE LINGER TIME expr { + RADV_CFG->route_linger_time = $4; + if (($4 < 0) || ($4 > 3600)) + cf_error("Linger time must be in range 0-3600"); + } ; radv_proto_opts: @@ -168,12 +183,10 @@ radv_prefix_item: | AUTONOMOUS bool { RADV_PREFIX->autonomous = $2; } | VALID LIFETIME expr radv_sensitive { RADV_PREFIX->valid_lifetime = $3; - if ($3 < 0) cf_error("Valid lifetime must be 0 or positive"); if ($4 != -1) RADV_PREFIX->valid_lifetime_sensitive = $4; } | PREFERRED LIFETIME expr radv_sensitive { RADV_PREFIX->preferred_lifetime = $3; - if ($3 < 0) cf_error("Preferred lifetime must be 0 or positive"); if ($4 != -1) RADV_PREFIX->preferred_lifetime_sensitive = $4; } ; @@ -303,6 +316,9 @@ radv_sensitive: | SENSITIVE bool { $$ = $2; } ; +CF_ADDTO(dynamic_attr, RA_PREFERENCE { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_ENUM_RA_PREFERENCE, EA_RA_PREFERENCE); }) +CF_ADDTO(dynamic_attr, RA_LIFETIME { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_INT, EA_RA_LIFETIME); }) + CF_CODE CF_END diff --git a/proto/radv/packets.c b/proto/radv/packets.c index 8a301854..fbe02060 100644 --- a/proto/radv/packets.c +++ b/proto/radv/packets.c @@ -26,6 +26,7 @@ struct radv_ra_packet #define OPT_PREFIX 3 #define OPT_MTU 5 +#define OPT_ROUTE 24 #define OPT_RDNSS 25 #define OPT_DNSSL 31 @@ -52,6 +53,15 @@ struct radv_opt_mtu u32 mtu; }; +struct radv_opt_route { + u8 type; + u8 length; + u8 pxlen; + u8 flags; + u32 lifetime; + u8 prefix[]; +}; + struct radv_opt_rdnss { u8 type; @@ -70,6 +80,41 @@ struct radv_opt_dnssl char domain[]; }; +static int +radv_prepare_route(struct radv_iface *ifa, struct radv_route *rt, + char **buf, char *bufend) +{ + struct radv_proto *p = ifa->ra; + struct radv_config *cf = (void *) p->p.cf; + u8 px_blocks = (rt->n.pxlen + 63) / 64; + u8 opt_len = 8 * (1 + px_blocks); + + if (*buf + opt_len > bufend) + { + log(L_WARN, "%s: Too many RA options on interface %s", + p->p.name, ifa->iface->name); + return -1; + } + + struct radv_opt_route *opt = (void *) *buf; + *buf += opt_len; + opt->type = OPT_ROUTE; + opt->length = 1 + px_blocks; + opt->pxlen = rt->n.pxlen; + opt->flags = rt->preference; + + if (p->valid && (p->active || !cf->route_lifetime_sensitive) && rt->alive) + opt->lifetime = htonl(rt->lifetime_set ? rt->lifetime : cf->route_lifetime); + else + opt->lifetime = 0; + + /* Copy the relevant part of the prefix */ + ip6_addr px_addr = ip6_hton(rt->n.prefix); + memcpy(opt->prefix, &px_addr, 8 * px_blocks); + + return 0; +} + static int radv_prepare_rdnss(struct radv_iface *ifa, list *rdnss_list, char **buf, char *bufend) { @@ -252,7 +297,7 @@ radv_prepare_ra(struct radv_iface *ifa) pkt->code = 0; pkt->checksum = 0; pkt->current_hop_limit = ic->current_hop_limit; - pkt->router_lifetime = (p->active || !ic->default_lifetime_sensitive) ? + pkt->router_lifetime = (p->valid && (p->active || !ic->default_lifetime_sensitive)) ? htons(ic->default_lifetime) : 0; pkt->flags = (ic->managed ? OPT_RA_MANAGED : 0) | (ic->other_config ? OPT_RA_OTHER_CFG : 0) | @@ -292,13 +337,23 @@ radv_prepare_ra(struct radv_iface *ifa) if (radv_prepare_dnssl(ifa, &ic->dnssl_list, &buf, bufend) < 0) goto done; + if (p->fib_up) + { + FIB_WALK(&p->routes, rt) + { + if (radv_prepare_route(ifa, (struct radv_route *) rt, &buf, bufend) < 0) + goto done; + } + FIB_WALK_END; + } + done: ifa->plen = buf - bufstart; } void -radv_send_ra(struct radv_iface *ifa, int shutdown) +radv_send_ra(struct radv_iface *ifa) { struct radv_proto *p = ifa->ra; @@ -306,19 +361,6 @@ radv_send_ra(struct radv_iface *ifa, int shutdown) if (!ifa->plen) radv_prepare_ra(ifa); - if (shutdown) - { - /* - * Modify router lifetime to 0, it is not restored because we suppose that - * the iface will be removed. The preference value also has to be zeroed. - * (RFC 4191 2.2: If router lifetime is 0, the preference value must be 0.) - */ - - struct radv_ra_packet *pkt = (void *) ifa->sk->tbuf; - pkt->router_lifetime = 0; - pkt->flags &= ~RA_PREF_MASK; - } - RADV_TRACE(D_PACKETS, "Sending RA via %s", ifa->iface->name); sk_send_to(ifa->sk, ifa->plen, IP6_ALL_NODES, 0); } diff --git a/proto/radv/radv.c b/proto/radv/radv.c index c53a0a95..d799dab5 100644 --- a/proto/radv/radv.c +++ b/proto/radv/radv.c @@ -12,37 +12,41 @@ /** * DOC: Router Advertisements * - * The RAdv protocol is implemented in two files: |radv.c| containing - * the interface with BIRD core and the protocol logic and |packets.c| - * handling low level protocol stuff (RX, TX and packet formats). - * The protocol does not export any routes. + * The RAdv protocol is implemented in two files: |radv.c| containing the + * interface with BIRD core and the protocol logic and |packets.c| handling low + * level protocol stuff (RX, TX and packet formats). The protocol does not + * export any routes. * - * The RAdv is structured in the usual way - for each handled interface - * there is a structure &radv_iface that contains a state related to - * that interface together with its resources (a socket, a timer). - * There is also a prepared RA stored in a TX buffer of the socket - * associated with an iface. These iface structures are created - * and removed according to iface events from BIRD core handled by - * radv_if_notify() callback. + * The RAdv is structured in the usual way - for each handled interface there is + * a structure &radv_iface that contains a state related to that interface + * together with its resources (a socket, a timer). There is also a prepared RA + * stored in a TX buffer of the socket associated with an iface. These iface + * structures are created and removed according to iface events from BIRD core + * handled by radv_if_notify() callback. * - * The main logic of RAdv consists of two functions: - * radv_iface_notify(), which processes asynchronous events (specified - * by RA_EV_* codes), and radv_timer(), which triggers sending RAs and - * computes the next timeout. + * The main logic of RAdv consists of two functions: radv_iface_notify(), which + * processes asynchronous events (specified by RA_EV_* codes), and radv_timer(), + * which triggers sending RAs and computes the next timeout. * - * The RAdv protocol could receive routes (through - * radv_import_control() and radv_rt_notify()), but only the - * configured trigger route is tracked (in &active var). When a radv - * protocol is reconfigured, the connected routing table is examined - * (in radv_check_active()) to have proper &active value in case of - * the specified trigger prefix was changed. + * The RAdv protocol could receive routes (through radv_import_control() and + * radv_rt_notify()), but only the configured trigger route is tracked (in + * &active var). When a radv protocol is reconfigured, the connected routing + * table is examined (in radv_check_active()) to have proper &active value in + * case of the specified trigger prefix was changed. * * Supported standards: * - RFC 4861 - main RA standard + * - RFC 4191 - Default Router Preferences and More-Specific Routes * - RFC 6106 - DNS extensions (RDDNS, DNSSL) - * - RFC 4191 (partial) - Default Router Preference */ +static void radv_prune_prefixes(struct radv_iface *ifa); +static void radv_prune_routes(struct radv_proto *p); + +/* Invalidate cached RA packet */ +static inline void radv_invalidate(struct radv_iface *ifa) +{ ifa->plen = 0; } + static void radv_timer(timer *tm) { @@ -51,17 +55,13 @@ radv_timer(timer *tm) RADV_TRACE(D_EVENTS, "Timer fired on %s", ifa->iface->name); - /* - * If some dead prefixes expired, regenerate the prefix list and the packet. - * We do so by pretending there was a change on the interface. - * - * This sets the timer, but we replace it just at the end of this function - * (replacing a timer is fine). - */ - if (ifa->prefix_expires && (ifa->prefix_expires <= now)) - radv_iface_notify(ifa, RA_EV_GC); + if (ifa->prune_time <= now) + radv_prune_prefixes(ifa); - radv_send_ra(ifa, 0); + if (p->prune_time <= now) + radv_prune_routes(p); + + radv_send_ra(ifa); /* Update timer */ ifa->last = now; @@ -118,7 +118,7 @@ radv_prepare_prefixes(struct radv_iface *ifa) { struct radv_proto *p = ifa->ra; struct radv_iface_config *cf = ifa->cf; - struct radv_prefix *pfx; + struct radv_prefix *pfx, *next; /* First mark all the prefixes as unused */ WALK_LIST(pfx, ifa->prefixes) @@ -162,49 +162,53 @@ radv_prepare_prefixes(struct radv_iface *ifa) existing->cf = pc; } - /* - * Garbage-collect the prefixes. If something isn't used, it dies (but isn't - * dropped just yet). If something is dead and rots there for long enough, - * clean it up. - */ - bird_clock_t expires = now + cf->linger_time; - bird_clock_t expires_min = 0; - struct radv_prefix *next; WALK_LIST_DELSAFE(pfx, next, ifa->prefixes) { if (pfx->alive && !pfx->mark) { - RADV_TRACE(D_EVENTS, "Marking prefix %I/$d on %s as dead", + RADV_TRACE(D_EVENTS, "Invalidating prefix %I/$d on %s", pfx->prefix, pfx->len, ifa->iface->name); pfx->alive = 0; - pfx->expires = expires; + pfx->expires = now + cf->linger_time; pfx->cf = &dead_prefix; } + } +} - if (!pfx->alive) +static void +radv_prune_prefixes(struct radv_iface *ifa) +{ + struct radv_proto *p = ifa->ra; + bird_clock_t next = TIME_INFINITY; + int changed = 0; + + struct radv_prefix *px, *pxn; + WALK_LIST_DELSAFE(px, pxn, ifa->prefixes) + { + if (!px->alive) { - if (pfx->expires <= now) + if (px->expires <= now) { RADV_TRACE(D_EVENTS, "Removing prefix %I/%d on %s", - pfx->prefix, pfx->len, ifa->iface->name); + px->prefix, px->len, ifa->iface->name); - rem_node(NODE pfx); - mb_free(pfx); + rem_node(NODE px); + mb_free(px); + changed = 1; } else - { - /* Find minimum expiration time */ - if (!expires_min || (pfx->expires < expires_min)) - expires_min = pfx->expires; - } + next = MIN(next, px->expires); } } - ifa->prefix_expires = expires_min; + if (changed) + radv_invalidate(ifa); + + ifa->prune_time = next; } -static char* ev_name[] = { NULL, "Init", "Change", "RS", "Garbage collect" }; +static char* ev_name[] = { NULL, "Init", "Change", "RS" }; void radv_iface_notify(struct radv_iface *ifa, int event) @@ -219,18 +223,17 @@ radv_iface_notify(struct radv_iface *ifa, int event) switch (event) { case RA_EV_CHANGE: - case RA_EV_GC: - ifa->plen = 0; + radv_invalidate(ifa); case RA_EV_INIT: ifa->initial = MAX_INITIAL_RTR_ADVERTISEMENTS; + radv_prepare_prefixes(ifa); + radv_prune_prefixes(ifa); break; case RA_EV_RS: break; } - radv_prepare_prefixes(ifa); - /* Update timer */ unsigned delta = now - ifa->last; unsigned after = 0; @@ -250,7 +253,6 @@ radv_iface_notify_all(struct radv_proto *p, int event) radv_iface_notify(ifa, event); } - static struct radv_iface * radv_iface_find(struct radv_proto *p, struct iface *what) { @@ -303,6 +305,7 @@ radv_iface_new(struct radv_proto *p, struct iface *iface, struct radv_iface_conf ifa->cf = cf; ifa->iface = iface; init_list(&ifa->prefixes); + ifa->prune_time = TIME_INFINITY; add_tail(&p->iface_list, NODE ifa); @@ -409,14 +412,18 @@ radv_import_control(struct proto *P, rte **new, ea_list **attrs UNUSED, struct l if (radv_net_match_trigger(cf, (*new)->net)) return RIC_PROCESS; - return RIC_DROP; + if (cf->propagate_routes) + return RIC_PROCESS; + else + return RIC_DROP; } static void -radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old UNUSED, ea_list *attrs UNUSED) +radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old UNUSED, ea_list *attrs) { struct radv_proto *p = (struct radv_proto *) P; struct radv_config *cf = (struct radv_config *) (P->cf); + struct radv_route *rt; if (radv_net_match_trigger(cf, n)) { @@ -432,7 +439,117 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U RADV_TRACE(D_EVENTS, "Suppressed"); radv_iface_notify_all(p, RA_EV_CHANGE); + return; } + + if (!cf->propagate_routes) + return; + + /* + * Some other route we want to send (or stop sending). Update the cache, + * with marking a removed one as dead or creating a new one as needed. + * + * And yes, we exclude the trigger route on purpose. + */ + + if (new) + { + /* Update */ + uint preference = ea_get_int(attrs, EA_RA_PREFERENCE, RA_PREF_MEDIUM); + uint lifetime = ea_get_int(attrs, EA_RA_LIFETIME, 0); + uint lifetime_set = !!ea_find(attrs, EA_RA_LIFETIME); + + if ((preference != RA_PREF_LOW) && + (preference != RA_PREF_MEDIUM) && + (preference != RA_PREF_HIGH)) + { + log(L_WARN "%s: Invalid ra_preference value %u on route %I/%d", + p->p.name, preference, n->n.prefix, n->n.pxlen); + preference = RA_PREF_MEDIUM; + lifetime = 0; + lifetime_set = 1; + } + + rt = fib_get(&p->routes, &n->n.prefix, n->n.pxlen); + + /* Ignore update if nothing changed */ + if (rt->alive && + (rt->preference == preference) && + (rt->lifetime == lifetime) && + (rt->lifetime_set == lifetime_set)) + return; + + if (p->routes.entries == 18) + log(L_WARN "%s: More than 17 routes exported to RAdv", p->p.name); + + rt->alive = 1; + rt->preference = preference; + rt->lifetime = lifetime; + rt->lifetime_set = lifetime_set; + } + else + { + /* Withdraw */ + rt = fib_find(&p->routes, &n->n.prefix, n->n.pxlen); + + if (!rt || !rt->alive) + return; + + /* Invalidate the route */ + rt->alive = 0; + rt->expires = now + cf->route_linger_time; + p->prune_time = MIN(p->prune_time, rt->expires); + } + + radv_iface_notify_all(p, RA_EV_CHANGE); +} + +/* + * Cleans up all the dead routes that expired and schedules itself to be run + * again if there are more routes waiting for expiration. + */ +static void +radv_prune_routes(struct radv_proto *p) +{ + bird_clock_t next = TIME_INFINITY; + int changed = 0; + + /* Should not happen */ + if (!p->fib_up) + return; + + struct fib_iterator fit; + FIB_ITERATE_INIT(&fit, &p->routes); + +again: + FIB_ITERATE_START(&p->routes, &fit, node) + { + struct radv_route *rt = (void *) node; + + if (!rt->alive) + { + /* Delete expired nodes */ + if (rt->expires <= now) + { + FIB_ITERATE_PUT(&fit, node); + fib_delete(&p->routes, node); + changed = 1; + goto again; + } + else + next = MIN(next, rt->expires); + } + } + FIB_ITERATE_END(node); + + if (changed) + { + struct radv_iface *ifa; + WALK_LIST(ifa, p->iface_list) + radv_invalidate(ifa); + } + + p->prune_time = next; } static int @@ -461,6 +578,21 @@ radv_init(struct proto_config *c) return P; } +static void +radv_set_fib(struct radv_proto *p, int up) +{ + if (up == p->fib_up) + return; + + if (up) + fib_init(&p->routes, p->p.pool, sizeof(struct radv_route), 4, NULL); + else + fib_free(&p->routes); + + p->fib_up = up; + p->prune_time = TIME_INFINITY; +} + static int radv_start(struct proto *P) { @@ -468,8 +600,13 @@ radv_start(struct proto *P) struct radv_config *cf = (struct radv_config *) (P->cf); init_list(&(p->iface_list)); + p->valid = 1; p->active = !cf->trigger_valid; + p->fib_up = 0; + radv_set_fib(p, cf->propagate_routes); + p->prune_time = TIME_INFINITY; + return PS_UP; } @@ -477,7 +614,10 @@ static inline void radv_iface_shutdown(struct radv_iface *ifa) { if (ifa->sk) - radv_send_ra(ifa, 1); + { + radv_invalidate(ifa); + radv_send_ra(ifa); + } } static int @@ -485,6 +625,8 @@ radv_shutdown(struct proto *P) { struct radv_proto *p = (struct radv_proto *) P; + p->valid = 0; + struct radv_iface *ifa; WALK_LIST(ifa, p->iface_list) radv_iface_shutdown(ifa); @@ -496,20 +638,19 @@ static int radv_reconfigure(struct proto *P, struct proto_config *c) { struct radv_proto *p = (struct radv_proto *) P; - // struct radv_config *old = (struct radv_config *) (p->cf); + struct radv_config *old = (struct radv_config *) (P->cf); struct radv_config *new = (struct radv_config *) c; - /* - * The question is why there is a reconfigure function for RAdv if - * it has almost none internal state so restarting the protocol - * would probably suffice. One small reason is that restarting the - * protocol would lead to sending a RA with Router Lifetime 0 - * causing nodes to temporary remove their default routes. - */ - P->cf = c; /* radv_check_active() requires proper P->cf */ p->active = radv_check_active(p); + /* Allocate or free FIB */ + radv_set_fib(p, new->propagate_routes); + + /* We started to accept routes so we need to refeed them */ + if (!old->propagate_routes && new->propagate_routes) + proto_request_feeding(&p->p); + struct iface *iface; WALK_LIST(iface, iface_list) { @@ -561,14 +702,49 @@ radv_get_status(struct proto *P, byte *buf) strcpy(buf, "Suppressed"); } +static const char * +radv_pref_str(u32 pref) +{ + switch (pref) + { + case RA_PREF_LOW: + return "low"; + case RA_PREF_MEDIUM: + return "medium"; + case RA_PREF_HIGH: + return "high"; + default: + return "??"; + } +} + +/* The buffer has some minimal size */ +static int +radv_get_attr(eattr *a, byte *buf, int buflen UNUSED) +{ + switch (a->id) + { + case EA_RA_PREFERENCE: + bsprintf(buf, "preference: %s", radv_pref_str(a->u.data)); + return GA_FULL; + case EA_RA_LIFETIME: + bsprintf(buf, "lifetime"); + return GA_NAME; + default: + return GA_UNKNOWN; + } +} + struct protocol proto_radv = { .name = "RAdv", .template = "radv%d", + .attr_class = EAP_RADV, .config_size = sizeof(struct radv_config), .init = radv_init, .start = radv_start, .shutdown = radv_shutdown, .reconfigure = radv_reconfigure, .copy_config = radv_copy_config, - .get_status = radv_get_status + .get_status = radv_get_status, + .get_attr = radv_get_attr }; diff --git a/proto/radv/radv.h b/proto/radv/radv.h index 60b9980f..3bc18249 100644 --- a/proto/radv/radv.h +++ b/proto/radv/radv.h @@ -54,6 +54,10 @@ struct radv_config ip_addr trigger_prefix; /* Prefix of a trigger route, if defined */ u8 trigger_pxlen; /* Pxlen of a trigger route, if defined */ u8 trigger_valid; /* Whether a trigger route is defined */ + u8 propagate_routes; /* Do we propagate more specific routes (RFC 4191)? */ + u32 route_lifetime; /* Lifetime for the RFC 4191 routes */ + u32 route_lifetime_sensitive; /* Whether route_lifetime depends on trigger */ + u32 route_linger_time; /* For how long we advertise dead routes with lifetime = 0 */ }; struct radv_iface_config @@ -117,12 +121,32 @@ struct radv_dnssl_config char *domain; /* Domain for DNS search list, in processed form */ }; +/* + * One more specific route as per RFC 4191. + * + * Note that it does *not* contain the next hop field. The next hop is always + * the router sending the advertisment and the more specific route only allows + * overriding the preference of the route. + */ +struct radv_route +{ + struct fib_node n; + u32 lifetime; /* Lifetime from an attribute */ + u8 lifetime_set; /* Is the lifetime set by an attribute? */ + u8 preference; /* Preference of the route, RA_PREF_* */ + u8 alive; + bird_clock_t expires; /* Time to remove when !alive */ +}; struct radv_proto { struct proto p; list iface_list; /* List of active ifaces */ + u8 valid; /* Router is valid for forwarding, used for shutdown */ u8 active; /* Whether radv is active w.r.t. triggers */ + u8 fib_up; /* FIB table (routes) is initialized */ + struct fib routes; /* FIB table of specific routes (struct radv_route) */ + bird_clock_t prune_time; /* Next time of route table pruning */ }; struct radv_prefix /* One prefix we advertise */ @@ -147,7 +171,7 @@ struct radv_iface struct ifa *addr; /* Link-local address of iface */ struct pool *pool; /* A pool for interface-specific things */ list prefixes; /* The prefixes we advertise (struct radv_prefix) */ - bird_clock_t prefix_expires; /* When the soonest prefix expires (0 = none dead) */ + bird_clock_t prune_time; /* Next time of prefix list pruning */ timer *timer; struct object_lock *lock; @@ -161,7 +185,6 @@ struct radv_iface #define RA_EV_INIT 1 /* Switch to initial mode */ #define RA_EV_CHANGE 2 /* Change of options or prefixes */ #define RA_EV_RS 3 /* Received RS */ -#define RA_EV_GC 4 /* Internal garbage collection of prefixes */ /* Default Router Preferences (RFC 4191) */ #define RA_PREF_LOW 0x18 @@ -169,6 +192,9 @@ struct radv_iface #define RA_PREF_HIGH 0x08 #define RA_PREF_MASK 0x18 +/* Attributes */ +#define EA_RA_PREFERENCE EA_CODE(EAP_RADV, 0) +#define EA_RA_LIFETIME EA_CODE(EAP_RADV, 1) #ifdef LOCAL_DEBUG #define RADV_FORCE_DEBUG 1 @@ -184,7 +210,7 @@ void radv_iface_notify(struct radv_iface *ifa, int event); /* packets.c */ int radv_process_domain(struct radv_dnssl_config *cf); -void radv_send_ra(struct radv_iface *ifa, int shutdown); +void radv_send_ra(struct radv_iface *ifa); int radv_sk_open(struct radv_iface *ifa); From 7c0bab3a3987b42bc699c4417c7b2e838f189158 Mon Sep 17 00:00:00 2001 From: "Ondrej Zajicek (work)" Date: Fri, 6 Oct 2017 12:22:18 +0200 Subject: [PATCH 4/9] RAdv: Change specific route options to be per-interface And change default values of specific route options to be consistent with values of default router options. --- proto/radv/config.Y | 45 ++++++++++++++++++-------- proto/radv/packets.c | 44 ++++++++++++++++++------- proto/radv/radv.c | 77 +++++++++++++++++++++++++------------------- proto/radv/radv.h | 27 ++++++++-------- 4 files changed, 121 insertions(+), 72 deletions(-) diff --git a/proto/radv/config.Y b/proto/radv/config.Y index 2fa11ce2..84a2de0e 100644 --- a/proto/radv/config.Y +++ b/proto/radv/config.Y @@ -48,8 +48,6 @@ radv_proto_start: proto_start RADV init_list(&RADV_CFG->pref_list); init_list(&RADV_CFG->rdnss_list); init_list(&RADV_CFG->dnssl_list); - RADV_CFG->route_lifetime = DEFAULT_VALID_LIFETIME; - RADV_CFG->route_linger_time = DEFAULT_LINGER_TIME; }; radv_proto_item: @@ -64,15 +62,6 @@ radv_proto_item: RADV_CFG->trigger_valid = 1; } | PROPAGATE ROUTES bool { RADV_CFG->propagate_routes = $3; } - | ROUTE LIFETIME expr radv_sensitive { - RADV_CFG->route_lifetime = $3; - if ($4 != -1) RADV_CFG->route_lifetime_sensitive = $4; - } - | ROUTE LINGER TIME expr { - RADV_CFG->route_linger_time = $4; - if (($4 < 0) || ($4 > 3600)) - cf_error("Linger time must be in range 0-3600"); - } ; radv_proto_opts: @@ -96,11 +85,15 @@ radv_iface_start: RADV_IFACE->min_ra_int = -1; /* undefined */ RADV_IFACE->max_ra_int = DEFAULT_MAX_RA_INT; RADV_IFACE->min_delay = DEFAULT_MIN_DELAY; + RADV_IFACE->prefix_linger_time = -1; + RADV_IFACE->route_linger_time = -1; RADV_IFACE->current_hop_limit = DEFAULT_CURRENT_HOP_LIMIT; - RADV_IFACE->linger_time = DEFAULT_LINGER_TIME; RADV_IFACE->default_lifetime = -1; RADV_IFACE->default_lifetime_sensitive = 1; RADV_IFACE->default_preference = RA_PREF_MEDIUM; + RADV_IFACE->route_lifetime = -1; + RADV_IFACE->route_lifetime_sensitive = 0; + RADV_IFACE->route_preference = RA_PREF_MEDIUM; }; radv_iface_item: @@ -112,14 +105,20 @@ radv_iface_item: | LINK MTU expr { RADV_IFACE->link_mtu = $3; if ($3 < 0) cf_error("Link MTU must be 0 or positive"); } | REACHABLE TIME expr { RADV_IFACE->reachable_time = $3; if (($3 < 0) || ($3 > 3600000)) cf_error("Reachable time must be in range 0-3600000"); } | RETRANS TIMER expr { RADV_IFACE->retrans_timer = $3; if ($3 < 0) cf_error("Retrans timer must be 0 or positive"); } - | LINGER TIME expr { RADV_IFACE->linger_time = $3; if (($3 < 0) || ($3 > 3600)) cf_error("Linger time must be in range 0-3600"); } | CURRENT HOP LIMIT expr { RADV_IFACE->current_hop_limit = $4; if (($4 < 0) || ($4 > 255)) cf_error("Current hop limit must be in range 0-255"); } | DEFAULT LIFETIME expr radv_sensitive { RADV_IFACE->default_lifetime = $3; if (($3 < 0) || ($3 > 9000)) cf_error("Default lifetime must be in range 0-9000"); if ($4 != -1) RADV_IFACE->default_lifetime_sensitive = $4; } + | ROUTE LIFETIME expr radv_sensitive { + RADV_IFACE->route_lifetime = $3; + if ($4 != -1) RADV_IFACE->route_lifetime_sensitive = $4; + } | DEFAULT PREFERENCE radv_preference { RADV_IFACE->default_preference = $3; } + | ROUTE PREFERENCE radv_preference { RADV_IFACE->route_preference = $3; } + | PREFIX LINGER TIME expr { RADV_IFACE->prefix_linger_time = $4; } + | ROUTE LINGER TIME expr { RADV_IFACE->route_linger_time = $4; } | PREFIX radv_prefix { add_tail(&RADV_IFACE->pref_list, NODE this_radv_prefix); } | RDNSS { init_list(&radv_dns_list); } radv_rdnss { add_tail_list(&RADV_IFACE->rdnss_list, &radv_dns_list); } | DNSSL { init_list(&radv_dns_list); } radv_dnssl { add_tail_list(&RADV_IFACE->dnssl_list, &radv_dns_list); } @@ -142,12 +141,32 @@ radv_iface_finish: if (ic->default_lifetime == (u32) -1) ic->default_lifetime = 3 * ic->max_ra_int; + if (ic->route_lifetime == (u32) -1) + ic->route_lifetime = 3 * ic->max_ra_int; + + if (ic->prefix_linger_time == (u32) -1) + ic->prefix_linger_time = 3 * ic->max_ra_int; + + if (ic->route_linger_time == (u32) -1) + ic->route_linger_time = 3 * ic->max_ra_int; + if ((ic->min_ra_int > 3) && (ic->min_ra_int > (ic->max_ra_int * 3 / 4))) cf_error("Min RA interval must be at most 3/4 * Max RA interval %d %d", ic->min_ra_int, ic->max_ra_int); if ((ic->default_lifetime > 0) && (ic->default_lifetime < ic->max_ra_int)) cf_error("Default lifetime must be either 0 or at least Max RA interval"); + + if ((ic->route_lifetime > 0) && (ic->route_lifetime < ic->max_ra_int)) + cf_error("Route lifetime must be either 0 or at least Max RA interval"); + + if ((ic->prefix_linger_time > 0) && (ic->prefix_linger_time < ic->max_ra_int)) + cf_error("Prefix linger time must be either 0 or at least Max RA interval"); + + if ((ic->route_linger_time > 0) && (ic->route_linger_time < ic->max_ra_int)) + cf_error("Route linger time must be either 0 or at least Max RA interval"); + + RADV_CFG->max_linger_time = MAX_(RADV_CFG->max_linger_time, ic->route_linger_time); }; diff --git a/proto/radv/packets.c b/proto/radv/packets.c index fbe02060..7d54a827 100644 --- a/proto/radv/packets.c +++ b/proto/radv/packets.c @@ -85,7 +85,6 @@ radv_prepare_route(struct radv_iface *ifa, struct radv_route *rt, char **buf, char *bufend) { struct radv_proto *p = ifa->ra; - struct radv_config *cf = (void *) p->p.cf; u8 px_blocks = (rt->n.pxlen + 63) / 64; u8 opt_len = 8 * (1 + px_blocks); @@ -96,22 +95,26 @@ radv_prepare_route(struct radv_iface *ifa, struct radv_route *rt, return -1; } + uint preference = rt->preference_set ? rt->preference : ifa->cf->route_preference; + uint lifetime = rt->lifetime_set ? rt->lifetime : ifa->cf->route_lifetime; + uint valid = rt->valid && p->valid && (p->active || !ifa->cf->route_lifetime_sensitive); + struct radv_opt_route *opt = (void *) *buf; *buf += opt_len; opt->type = OPT_ROUTE; opt->length = 1 + px_blocks; opt->pxlen = rt->n.pxlen; - opt->flags = rt->preference; - - if (p->valid && (p->active || !cf->route_lifetime_sensitive) && rt->alive) - opt->lifetime = htonl(rt->lifetime_set ? rt->lifetime : cf->route_lifetime); - else - opt->lifetime = 0; + opt->flags = preference; + opt->lifetime = valid ? htonl(lifetime) : 0; /* Copy the relevant part of the prefix */ ip6_addr px_addr = ip6_hton(rt->n.prefix); memcpy(opt->prefix, &px_addr, 8 * px_blocks); + /* Keeping track of first linger timeout */ + if (!rt->valid) + ifa->valid_time = MIN(ifa->valid_time, rt->changed + ifa->cf->route_linger_time); + return 0; } @@ -278,6 +281,10 @@ radv_prepare_prefix(struct radv_iface *ifa, struct radv_prefix *prefix, ipa_hton(op->prefix); *buf += sizeof(*op); + /* Keeping track of first linger timeout */ + if (!prefix->valid) + ifa->valid_time = MIN(ifa->valid_time, prefix->changed + ifa->cf->prefix_linger_time); + return 0; } @@ -316,10 +323,17 @@ radv_prepare_ra(struct radv_iface *ifa) buf += sizeof (*om); } - struct radv_prefix *prefix; - WALK_LIST(prefix, ifa->prefixes) + /* Keeping track of first linger timeout */ + ifa->valid_time = TIME_INFINITY; + + struct radv_prefix *px; + WALK_LIST(px, ifa->prefixes) { - if (radv_prepare_prefix(ifa, prefix, &buf, bufend) < 0) + /* Skip invalid prefixes that are past linger timeout but still not pruned */ + if (!px->valid && (px->changed + ic->prefix_linger_time <= now)) + continue; + + if (radv_prepare_prefix(ifa, px, &buf, bufend) < 0) goto done; } @@ -339,9 +353,15 @@ radv_prepare_ra(struct radv_iface *ifa) if (p->fib_up) { - FIB_WALK(&p->routes, rt) + FIB_WALK(&p->routes, n) { - if (radv_prepare_route(ifa, (struct radv_route *) rt, &buf, bufend) < 0) + struct radv_route *rt = (void *) n; + + /* Skip invalid routes that are past linger timeout but still not pruned */ + if (!rt->valid && (rt->changed + ic->route_linger_time <= now)) + continue; + + if (radv_prepare_route(ifa, rt, &buf, bufend) < 0) goto done; } FIB_WALK_END; diff --git a/proto/radv/radv.c b/proto/radv/radv.c index d799dab5..d53b65e3 100644 --- a/proto/radv/radv.c +++ b/proto/radv/radv.c @@ -55,6 +55,9 @@ radv_timer(timer *tm) RADV_TRACE(D_EVENTS, "Timer fired on %s", ifa->iface->name); + if (ifa->valid_time <= now) + radv_invalidate(ifa); + if (ifa->prune_time <= now) radv_prune_prefixes(ifa); @@ -117,7 +120,6 @@ static void radv_prepare_prefixes(struct radv_iface *ifa) { struct radv_proto *p = ifa->ra; - struct radv_iface_config *cf = ifa->cf; struct radv_prefix *pfx, *next; /* First mark all the prefixes as unused */ @@ -157,20 +159,21 @@ radv_prepare_prefixes(struct radv_iface *ifa) * Update the information (it may have changed, or even bring a prefix back * to life). */ - existing->alive = 1; + existing->valid = 1; + existing->changed = now; existing->mark = 1; existing->cf = pc; } WALK_LIST_DELSAFE(pfx, next, ifa->prefixes) { - if (pfx->alive && !pfx->mark) + if (pfx->valid && !pfx->mark) { RADV_TRACE(D_EVENTS, "Invalidating prefix %I/$d on %s", pfx->prefix, pfx->len, ifa->iface->name); - pfx->alive = 0; - pfx->expires = now + cf->linger_time; + pfx->valid = 0; + pfx->changed = now; pfx->cf = &dead_prefix; } } @@ -181,30 +184,28 @@ radv_prune_prefixes(struct radv_iface *ifa) { struct radv_proto *p = ifa->ra; bird_clock_t next = TIME_INFINITY; - int changed = 0; + bird_clock_t expires = 0; struct radv_prefix *px, *pxn; WALK_LIST_DELSAFE(px, pxn, ifa->prefixes) { - if (!px->alive) + if (!px->valid) { - if (px->expires <= now) + expires = px->changed + ifa->cf->prefix_linger_time; + + if (expires <= now) { RADV_TRACE(D_EVENTS, "Removing prefix %I/%d on %s", px->prefix, px->len, ifa->iface->name); rem_node(NODE px); mb_free(px); - changed = 1; } else - next = MIN(next, px->expires); + next = MIN(next, expires); } } - if (changed) - radv_invalidate(ifa); - ifa->prune_time = next; } @@ -424,6 +425,7 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U struct radv_proto *p = (struct radv_proto *) P; struct radv_config *cf = (struct radv_config *) (P->cf); struct radv_route *rt; + eattr *ea; if (radv_net_match_trigger(cf, n)) { @@ -455,9 +457,14 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U if (new) { /* Update */ - uint preference = ea_get_int(attrs, EA_RA_PREFERENCE, RA_PREF_MEDIUM); - uint lifetime = ea_get_int(attrs, EA_RA_LIFETIME, 0); - uint lifetime_set = !!ea_find(attrs, EA_RA_LIFETIME); + + ea = ea_find(attrs, EA_RA_PREFERENCE); + uint preference = ea ? ea->u.data : RA_PREF_MEDIUM; + uint preference_set = !!ea; + + ea = ea_find(attrs, EA_RA_LIFETIME); + uint lifetime = ea ? ea->u.data : 0; + uint lifetime_set = !!ea; if ((preference != RA_PREF_LOW) && (preference != RA_PREF_MEDIUM) && @@ -466,6 +473,7 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U log(L_WARN "%s: Invalid ra_preference value %u on route %I/%d", p->p.name, preference, n->n.prefix, n->n.pxlen); preference = RA_PREF_MEDIUM; + preference_set = 1; lifetime = 0; lifetime_set = 1; } @@ -473,8 +481,9 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U rt = fib_get(&p->routes, &n->n.prefix, n->n.pxlen); /* Ignore update if nothing changed */ - if (rt->alive && + if (rt->valid && (rt->preference == preference) && + (rt->preference_set == preference_set) && (rt->lifetime == lifetime) && (rt->lifetime_set == lifetime_set)) return; @@ -482,8 +491,10 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U if (p->routes.entries == 18) log(L_WARN "%s: More than 17 routes exported to RAdv", p->p.name); - rt->alive = 1; + rt->valid = 1; + rt->changed = now; rt->preference = preference; + rt->preference_set = preference_set; rt->lifetime = lifetime; rt->lifetime_set = lifetime_set; } @@ -492,13 +503,16 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U /* Withdraw */ rt = fib_find(&p->routes, &n->n.prefix, n->n.pxlen); - if (!rt || !rt->alive) + if (!rt || !rt->valid) return; /* Invalidate the route */ - rt->alive = 0; - rt->expires = now + cf->route_linger_time; - p->prune_time = MIN(p->prune_time, rt->expires); + rt->valid = 0; + rt->changed = now; + + /* Invalidated route will be pruned eventually */ + bird_clock_t expires = rt->changed + cf->max_linger_time; + p->prune_time = MIN(p->prune_time, expires); } radv_iface_notify_all(p, RA_EV_CHANGE); @@ -511,8 +525,9 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U static void radv_prune_routes(struct radv_proto *p) { + struct radv_config *cf = (struct radv_config *) (p->p.cf); bird_clock_t next = TIME_INFINITY; - int changed = 0; + bird_clock_t expires = 0; /* Should not happen */ if (!p->fib_up) @@ -526,29 +541,23 @@ again: { struct radv_route *rt = (void *) node; - if (!rt->alive) + if (!rt->valid) { + expires = rt->changed + cf->max_linger_time; + /* Delete expired nodes */ - if (rt->expires <= now) + if (expires <= now) { FIB_ITERATE_PUT(&fit, node); fib_delete(&p->routes, node); - changed = 1; goto again; } else - next = MIN(next, rt->expires); + next = MIN(next, expires); } } FIB_ITERATE_END(node); - if (changed) - { - struct radv_iface *ifa; - WALK_LIST(ifa, p->iface_list) - radv_invalidate(ifa); - } - p->prune_time = next; } diff --git a/proto/radv/radv.h b/proto/radv/radv.h index 3bc18249..ab081397 100644 --- a/proto/radv/radv.h +++ b/proto/radv/radv.h @@ -35,7 +35,6 @@ #define DEFAULT_MAX_RA_INT 600 #define DEFAULT_MIN_DELAY 3 #define DEFAULT_CURRENT_HOP_LIMIT 64 -#define DEFAULT_LINGER_TIME 300 #define DEFAULT_VALID_LIFETIME 86400 #define DEFAULT_PREFERRED_LIFETIME 14400 @@ -55,9 +54,7 @@ struct radv_config u8 trigger_pxlen; /* Pxlen of a trigger route, if defined */ u8 trigger_valid; /* Whether a trigger route is defined */ u8 propagate_routes; /* Do we propagate more specific routes (RFC 4191)? */ - u32 route_lifetime; /* Lifetime for the RFC 4191 routes */ - u32 route_lifetime_sensitive; /* Whether route_lifetime depends on trigger */ - u32 route_linger_time; /* For how long we advertise dead routes with lifetime = 0 */ + u32 max_linger_time; /* Maximum of interface route_linger_time */ }; struct radv_iface_config @@ -71,8 +68,8 @@ struct radv_iface_config u32 max_ra_int; u32 min_delay; - u32 linger_time; /* How long a dead prefix should still be advertised with 0 - lifetime */ + u32 prefix_linger_time; /* How long we advertise dead prefixes with lifetime 0 */ + u32 route_linger_time; /* How long we advertise dead routes with lifetime 0 */ u8 rdnss_local; /* Global list is not used for RDNSS */ u8 dnssl_local; /* Global list is not used for DNSSL */ @@ -84,8 +81,11 @@ struct radv_iface_config u32 retrans_timer; u32 current_hop_limit; u32 default_lifetime; + u32 route_lifetime; /* Lifetime for the RFC 4191 routes */ u8 default_lifetime_sensitive; /* Whether default_lifetime depends on trigger */ - u8 default_preference; /* Default Router Preference (RFC 4191) */ + u8 route_lifetime_sensitive; /* Whether route_lifetime depends on trigger */ + u8 default_preference; /* Default Router Preference (RFC 4191) */ + u8 route_preference; /* Specific Route Preference (RFC 4191) */ }; struct radv_prefix_config @@ -132,10 +132,11 @@ struct radv_route { struct fib_node n; u32 lifetime; /* Lifetime from an attribute */ - u8 lifetime_set; /* Is the lifetime set by an attribute? */ + u8 lifetime_set; /* Whether lifetime is defined */ u8 preference; /* Preference of the route, RA_PREF_* */ - u8 alive; - bird_clock_t expires; /* Time to remove when !alive */ + u8 preference_set; /* Whether preference is defined */ + u8 valid; /* Whethe route is valid or withdrawn */ + bird_clock_t changed; /* Last time when the route changed */ }; struct radv_proto @@ -154,11 +155,10 @@ struct radv_prefix /* One prefix we advertise */ node n; ip_addr prefix; u8 len; - u8 alive; /* Is the prefix alive? If not, we advertise it + u8 valid; /* Is the prefix valid? If not, we advertise it with 0 lifetime, so clients stop using it */ u8 mark; /* A temporary mark for processing */ - bird_clock_t expires; /* The time when we drop this prefix from - advertising. It is valid only if !alive. */ + bird_clock_t changed; /* Last time when the prefix changed */ struct radv_prefix_config *cf; /* The config tied to this prefix */ }; @@ -172,6 +172,7 @@ struct radv_iface struct pool *pool; /* A pool for interface-specific things */ list prefixes; /* The prefixes we advertise (struct radv_prefix) */ bird_clock_t prune_time; /* Next time of prefix list pruning */ + bird_clock_t valid_time; /* Cached packet is valid until first linger timeout */ timer *timer; struct object_lock *lock; From 18352188ed7415ddb3aa7b0d7a2fc16d57a2d5d2 Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Tue, 19 Sep 2017 17:23:31 +0200 Subject: [PATCH 5/9] RAdv: Documentation for more specific routes --- doc/bird.sgml | 94 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 11 deletions(-) diff --git a/doc/bird.sgml b/doc/bird.sgml index c549e3c3..129e9c08 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -3336,7 +3336,8 @@ time intervals or as an answer to a request) advertisement packets to connected networks. These packets contain basic information about a local network (e.g. a list of network prefixes), which allows network hosts to autoconfigure network addresses and choose a default route. BIRD implements router behavior as defined -in and also the DNS extensions from . +in , router preferences and specific routes (), +and DNS extensions (). Configuration