diff --git a/conf/cf-lex.l b/conf/cf-lex.l index 9bbb3660..63c76940 100644 --- a/conf/cf-lex.l +++ b/conf/cf-lex.l @@ -87,7 +87,7 @@ HASH_DEFINE_REHASH_FN(SYM, struct symbol) HASH(struct keyword) kw_hash; -static struct sym_scope *conf_this_scope; +struct sym_scope *conf_this_scope; linpool *cfg_mem; @@ -673,7 +673,8 @@ cf_lex_init(int is_cli, struct config *c) else BEGIN(INITIAL); - conf_this_scope = cfg_allocz(sizeof(struct sym_scope)); + c->root_scope = cfg_allocz(sizeof(struct sym_scope)); + conf_this_scope = c->root_scope; conf_this_scope->active = 1; } diff --git a/conf/conf.h b/conf/conf.h index 427569fd..354a4da8 100644 --- a/conf/conf.h +++ b/conf/conf.h @@ -52,6 +52,7 @@ struct config { int file_fd; /* File descriptor of main configuration file */ HASH(struct symbol) sym_hash; /* Lexer: symbol hash table */ struct config *fallback; /* Link to regular config for CLI parsing */ + struct sym_scope *root_scope; /* Scope for root symbols */ int obstacle_count; /* Number of items blocking freeing of this config */ int shutdown; /* This is a pseudo-config for daemon shutdown */ btime load_time; /* When we've got this configuration */ @@ -152,6 +153,8 @@ struct include_file_stack { extern struct include_file_stack *ifs; +extern struct sym_scope *conf_this_scope; + int cf_lex(void); void cf_lex_init(int is_cli, struct config *c); void cf_lex_unwind(void); diff --git a/nest/proto.c b/nest/proto.c index d4a333d0..415471c4 100644 --- a/nest/proto.c +++ b/nest/proto.c @@ -874,6 +874,28 @@ proto_copy_config(struct proto_config *dest, struct proto_config *src) dest->protocol->copy_config(dest, src); } +void +proto_clone_config(struct symbol *sym, struct proto_config *parent) +{ + struct proto_config *cf = proto_config_new(parent->protocol, SYM_PROTO); + proto_copy_config(cf, parent); + cf->name = sym->name; + cf->proto = NULL; + cf->parent = parent; + + sym->class = cf->class; + sym->def = cf; +} + +static void +proto_undef_clone(struct symbol *sym, struct proto_config *cf) +{ + rem_node(&cf->n); + + sym->class = SYM_VOID; + sym->def = NULL; +} + /** * protos_preconfig - pre-configuration processing * @c: new configuration @@ -973,6 +995,24 @@ protos_commit(struct config *new, struct config *old, int force_reconfig, int ty { p = oc->proto; sym = cf_find_symbol(new, oc->name); + + /* Handle dynamic protocols */ + if (!sym && oc->parent && !new->shutdown) + { + struct symbol *parsym = cf_find_symbol(new, oc->parent->name); + if (parsym && parsym->class == SYM_PROTO) + { + /* This is hack, we would like to share config, but we need to copy it now */ + new_config = new; + cfg_mem = new->mem; + conf_this_scope = new->root_scope; + sym = cf_get_symbol(oc->name); + proto_clone_config(sym, parsym->def); + new_config = NULL; + cfg_mem = NULL; + } + } + if (sym && sym->class == SYM_PROTO && !new->shutdown) { /* Found match, let's check if we can smoothly switch to new configuration */ @@ -984,6 +1024,12 @@ protos_commit(struct config *new, struct config *old, int force_reconfig, int ty if (! force_reconfig && proto_reconfigure(p, oc, nc, type)) continue; + if (nc->parent) + { + proto_undef_clone(sym, nc); + goto remove; + } + /* Unsuccessful, we will restart it */ if (!p->disabled && !nc->disabled) log(L_INFO "Restarting protocol %s", p->name); @@ -997,6 +1043,7 @@ protos_commit(struct config *new, struct config *old, int force_reconfig, int ty } else if (!new->shutdown) { + remove: log(L_INFO "Removing protocol %s", p->name); p->down_code = PDC_CF_REMOVE; p->cf_new = NULL; @@ -1105,6 +1152,15 @@ proto_rethink_goal(struct proto *p) } } +struct proto * +proto_spawn(struct proto_config *cf, uint disabled) +{ + struct proto *p = proto_init(cf, TAIL(proto_list)); + p->disabled = disabled; + proto_rethink_goal(p); + return p; +} + /** * DOC: Graceful restart recovery diff --git a/nest/protocol.h b/nest/protocol.h index 56d66ed5..b26d9cc1 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -89,6 +89,7 @@ void protos_build(void); void proto_build(struct protocol *); void protos_preconfig(struct config *); void protos_commit(struct config *new, struct config *old, int force_restart, int type); +struct proto * proto_spawn(struct proto_config *cf, uint disabled); void protos_dump_all(void); #define GA_UNKNOWN 0 /* Attribute not recognized */ @@ -113,6 +114,7 @@ struct proto_config { struct config *global; /* Global configuration data */ struct protocol *protocol; /* Protocol */ struct proto *proto; /* Instance we've created */ + struct proto_config *parent; /* Parent proto_config for dynamic protocols */ char *name; char *dsc; int class; /* SYM_PROTO or SYM_TEMPLATE */ @@ -263,6 +265,7 @@ struct proto_spec { void *proto_new(struct proto_config *); void *proto_config_new(struct protocol *, int class); void proto_copy_config(struct proto_config *dest, struct proto_config *src); +void proto_clone_config(struct symbol *sym, struct proto_config *parent); void proto_set_message(struct proto *p, char *msg, int len); void graceful_restart_recovery(void); diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index c332a836..a5c06783 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -129,6 +129,9 @@ static list bgp_sockets; /* Global list of listening sockets */ static void bgp_connect(struct bgp_proto *p); static void bgp_active(struct bgp_proto *p); +static void bgp_setup_conn(struct bgp_proto *p, struct bgp_conn *conn); +static void bgp_setup_sk(struct bgp_conn *conn, sock *s); +static void bgp_send_open(struct bgp_conn *conn); static void bgp_update_bfd(struct bgp_proto *p, int use_bfd); static int bgp_incoming_connection(sock *sk, uint dummy UNUSED); @@ -149,7 +152,7 @@ bgp_open(struct bgp_proto *p) struct bgp_socket *bs = NULL; struct iface *ifa = p->cf->strict_bind ? p->cf->iface : NULL; ip_addr addr = p->cf->strict_bind ? p->cf->local_ip : - (ipa_is_ip4(p->cf->remote_ip) ? IPA_NONE4 : IPA_NONE6); + (p->ipv4 ? IPA_NONE4 : IPA_NONE6); uint port = p->cf->local_port; /* FIXME: Add some global init? */ @@ -272,8 +275,17 @@ bgp_startup(struct bgp_proto *p) BGP_TRACE(D_EVENTS, "Started"); p->start_state = BSS_CONNECT; - if (!p->cf->passive) + if (!p->passive) bgp_active(p); + + if (p->postponed_sk) + { + /* Apply postponed incoming connection */ + bgp_setup_conn(p, &p->incoming_conn); + bgp_setup_sk(&p->incoming_conn, p->postponed_sk); + bgp_send_open(&p->incoming_conn); + p->postponed_sk = NULL; + } } static void @@ -456,7 +468,7 @@ bgp_decision(void *vp) if ((p->p.proto_state == PS_START) && (p->outgoing_conn.state == BS_IDLE) && (p->incoming_conn.state != BS_OPENCONFIRM) && - !p->cf->passive) + !p->passive) bgp_active(p); if ((p->p.proto_state == PS_STOP) && @@ -465,6 +477,29 @@ bgp_decision(void *vp) bgp_down(p); } +static struct bgp_proto * +bgp_spawn(struct bgp_proto *pp, ip_addr remote_ip) +{ + struct symbol *sym; + char fmt[SYM_MAX_LEN]; + + bsprintf(fmt, "%s%%0%dd", pp->cf->dynamic_name, pp->cf->dynamic_name_digits); + + /* This is hack, we would like to share config, but we need to copy it now */ + new_config = config; + cfg_mem = config->mem; + conf_this_scope = config->root_scope; + sym = cf_default_name(fmt, &(pp->dynamic_name_counter)); + proto_clone_config(sym, pp->p.cf); + new_config = NULL; + cfg_mem = NULL; + + /* Just pass remote_ip to bgp_init() */ + ((struct bgp_config *) sym->def)->remote_ip = remote_ip; + + return (void *) proto_spawn(sym->def, 0); +} + void bgp_stop(struct bgp_proto *p, uint subcode, byte *data, uint len) { @@ -1088,6 +1123,9 @@ err: return; } +static inline int bgp_is_dynamic(struct bgp_proto *p) +{ return ipa_zero(p->remote_ip); } + /** * bgp_find_proto - find existing proto for incoming connection * @sk: TCP socket @@ -1096,6 +1134,7 @@ err: static struct bgp_proto * bgp_find_proto(sock *sk) { + struct bgp_proto *best = NULL; struct bgp_proto *p; /* sk->iface is valid only if src or dst address is link-local */ @@ -1103,13 +1142,20 @@ bgp_find_proto(sock *sk) WALK_LIST(p, proto_list) if ((p->p.proto == &proto_bgp) && - (p->sock == sk->data) && - ipa_equal(p->cf->remote_ip, sk->daddr) && + (ipa_equal(p->remote_ip, sk->daddr) || bgp_is_dynamic(p)) && + (!p->cf->remote_range || ipa_in_netX(sk->daddr, p->cf->remote_range)) && + (p->p.vrf == sk->vrf) && + (p->cf->local_port == sk->sport) && (!link || (p->cf->iface == sk->iface)) && (ipa_zero(p->cf->local_ip) || ipa_equal(p->cf->local_ip, sk->saddr))) - return p; + { + best = p; - return NULL; + if (!bgp_is_dynamic(p)) + break; + } + + return best; } /** @@ -1188,6 +1234,16 @@ bgp_incoming_connection(sock *sk, uint dummy UNUSED) sk_reallocate(sk); } + /* For dynamic BGP, spawn new instance and postpone the socket */ + if (bgp_is_dynamic(p)) + { + p = bgp_spawn(p, sk->daddr); + p->postponed_sk = sk; + rmove(sk, p->p.pool); + return 0; + } + + rmove(sk, p->p.pool); bgp_setup_conn(p, &p->incoming_conn); bgp_setup_sk(&p->incoming_conn, sk); bgp_send_open(&p->incoming_conn); @@ -1306,7 +1362,7 @@ bgp_bfd_notify(struct bfd_request *req) static void bgp_update_bfd(struct bgp_proto *p, int use_bfd) { - if (use_bfd && !p->bfd_req) + if (use_bfd && !p->bfd_req && !bgp_is_dynamic(p)) p->bfd_req = bfd_request_session(p->p.pool, p->remote_ip, p->local_ip, p->cf->multihop ? NULL : p->neigh->iface, bgp_bfd_notify, p); @@ -1398,7 +1454,7 @@ bgp_start_locked(struct object_lock *lock) DBG("BGP: Got lock\n"); - if (cf->multihop) + if (cf->multihop || bgp_is_dynamic(p)) { /* Multi-hop sessions do not use neighbor entries */ bgp_initiate(p); @@ -1433,20 +1489,26 @@ bgp_start(struct proto *P) const struct bgp_config *cf = p->cf; p->local_ip = cf->local_ip; - p->remote_ip = cf->remote_ip; p->local_as = cf->local_as; p->remote_as = cf->remote_as; p->public_as = cf->local_as; + /* For dynamic BGP childs, remote_ip is already set */ + if (ipa_nonzero(cf->remote_ip)) + p->remote_ip = cf->remote_ip; + /* Confederation ID is used for truly external peers */ if (p->cf->confederation && !p->is_interior) p->public_as = cf->confederation; + p->passive = cf->passive || bgp_is_dynamic(p); + p->start_state = BSS_PREPARE; p->outgoing_conn.state = BS_IDLE; p->incoming_conn.state = BS_IDLE; p->neigh = NULL; p->bfd_req = NULL; + p->postponed_sk = NULL; p->gr_ready = 0; p->gr_active_num = 0; @@ -1588,6 +1650,17 @@ bgp_init(struct proto_config *CF) p->rs_client = cf->rs_client; p->rr_client = cf->rr_client; + p->ipv4 = ipa_nonzero(cf->remote_ip) ? + ipa_is_ip4(cf->remote_ip) : + (cf->remote_range && (cf->remote_range->type == NET_IP4)); + + p->remote_ip = cf->remote_ip; + p->remote_as = cf->remote_as; + + /* Hack: We use cf->remote_ip just to pass remote_ip from bgp_spawn() */ + if (cf->c.parent) + cf->remote_ip = IPA_NONE; + /* Add all channels */ struct bgp_channel_config *cc; WALK_LIST(cc, CF->channels) @@ -1788,9 +1861,12 @@ bgp_postconfig(struct proto_config *CF) if (!cf->local_as) cf_error("Local AS number must be set"); - if (ipa_zero(cf->remote_ip)) + if (ipa_zero(cf->remote_ip) && !cf->remote_range) cf_error("Neighbor must be configured"); + if (ipa_zero(cf->local_ip) && cf->strict_bind) + cf_error("Local address must be configured for strict bind"); + if (!cf->remote_as && !cf->peer_type) cf_error("Remote AS number (or peer type) must be set"); @@ -1921,7 +1997,10 @@ bgp_reconfigure(struct proto *P, struct proto_config *CF) // password item is last and must be checked separately OFFSETOF(struct bgp_config, password) - sizeof(struct proto_config)) && ((!old->password && !new->password) - || (old->password && new->password && !strcmp(old->password, new->password))); + || (old->password && new->password && !strcmp(old->password, new->password))) + && net_equal(old->remote_range, new->remote_range) + && !strcmp(old->dynamic_name, new->dynamic_name) + && (old->dynamic_name_digits == new->dynamic_name_digits); /* FIXME: Move channel reconfiguration to generic protocol code ? */ struct channel *C, *C2; @@ -1951,6 +2030,9 @@ bgp_reconfigure(struct proto *P, struct proto_config *CF) if (same) p->cf = new; + /* Reset name counter */ + p->dynamic_name_counter = 0; + return same; } @@ -2081,7 +2163,7 @@ bgp_state_dsc(struct bgp_proto *p) return "Down"; int state = MAX(p->incoming_conn.state, p->outgoing_conn.state); - if ((state == BS_IDLE) && (p->start_state >= BSS_CONNECT) && p->cf->passive) + if ((state == BS_IDLE) && (p->start_state >= BSS_CONNECT) && p->passive) return "Passive"; return bgp_state_names[state]; @@ -2257,8 +2339,13 @@ bgp_show_proto_info(struct proto *P) struct bgp_proto *p = (struct bgp_proto *) P; cli_msg(-1006, " BGP state: %s", bgp_state_dsc(p)); - cli_msg(-1006, " Neighbor address: %I%J", p->cf->remote_ip, p->cf->iface); - cli_msg(-1006, " Neighbor AS: %u", p->cf->remote_as ?: p->remote_as); + + if (bgp_is_dynamic(p) && p->cf->remote_range) + cli_msg(-1006, " Neighbor range: %N", p->cf->remote_range); + else + cli_msg(-1006, " Neighbor address: %I%J", p->remote_ip, p->cf->iface); + + cli_msg(-1006, " Neighbor AS: %u", p->remote_as); if (p->gr_active_num) cli_msg(-1006, " Neighbor graceful restart active"); diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h index 4d24e4fa..d8c9fe94 100644 --- a/proto/bgp/bgp.h +++ b/proto/bgp/bgp.h @@ -124,6 +124,9 @@ struct bgp_config { u32 disable_after_cease; /* Disable it when cease is received, bitfield */ char *password; /* Password used for MD5 authentication */ + net_addr *remote_range; /* Allowed neighbor range for dynamic BGP */ + char *dynamic_name; /* Name pattern for dynamic BGP */ + int dynamic_name_digits; /* Minimum number of digits for dynamic names */ int check_link; /* Use iface link state for liveness detection */ int bfd; /* Use BFD for liveness detection */ }; @@ -270,12 +273,14 @@ struct bgp_proto { u32 local_id; /* BGP identifier of this router */ u32 remote_id; /* BGP identifier of the neighbor */ u32 rr_cluster_id; /* Route reflector cluster ID */ - int start_state; /* Substates that partitions BS_START */ + u8 start_state; /* Substates that partitions BS_START */ u8 is_internal; /* Internal BGP session (local_as == remote_as) */ u8 is_interior; /* Internal or intra-confederation BGP session */ u8 as4_session; /* Session uses 4B AS numbers in AS_PATH (both sides support it) */ u8 rr_client; /* Whether neighbor is RR client of me */ u8 rs_client; /* Whether neighbor is RS client of me */ + u8 ipv4; /* Use IPv4 connection, i.e. remote_ip is IPv4 */ + u8 passive; /* Do not initiate outgoing connection */ u8 route_refresh; /* Route refresh allowed to send [RFC 2918] */ u8 enhanced_refresh; /* Enhanced refresh is negotiated [RFC 7313] */ u8 gr_ready; /* Neighbor could do graceful restart */ @@ -292,10 +297,12 @@ struct bgp_proto { struct neighbor *neigh; /* Neighbor entry corresponding to remote ip, NULL if multihop */ struct bgp_socket *sock; /* Shared listening socket */ struct bfd_request *bfd_req; /* BFD request, if BFD is used */ + struct birdsock *postponed_sk; /* Postponed incoming socket for dynamic BGP */ ip_addr link_addr; /* Link-local version of local_ip */ event *event; /* Event for respawning and shutting process */ timer *startup_timer; /* Timer used to delay protocol startup due to previous errors (startup_delay) */ timer *gr_timer; /* Timer waiting for reestablishment after graceful restart */ + int dynamic_name_counter; /* Counter for dynamic BGP names */ uint startup_delay; /* Delay (in seconds) of protocol startup due to previous errors */ btime last_proto_error; /* Time of last error that leads to protocol stop */ u8 last_error_class; /* Error class of last error */ diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y index c9a6af96..bbc7d9a4 100644 --- a/proto/bgp/config.Y +++ b/proto/bgp/config.Y @@ -29,7 +29,8 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE, SECURITY, DETERMINISTIC, SECONDARY, ALLOW, BFD, ADD, PATHS, RX, TX, GRACEFUL, RESTART, AWARE, CHECK, LINK, PORT, EXTENDED, MESSAGES, SETKEY, STRICT, BIND, CONFEDERATION, MEMBER, MULTICAST, FLOW4, FLOW6, LONG, - LIVED, STALE, IMPORT, IBGP, EBGP, MANDATORY, INTERNAL, EXTERNAL) + LIVED, STALE, IMPORT, IBGP, EBGP, MANDATORY, INTERNAL, EXTERNAL, + DYNAMIC, RANGE, NAME, DIGITS) %type bgp_nh %type bgp_afi @@ -68,6 +69,7 @@ bgp_proto_start: proto_start BGP { BGP_CFG->llgr_mode = -1; BGP_CFG->llgr_time = 3600; BGP_CFG->setkey = 1; + BGP_CFG->dynamic_name = "dynbgp"; BGP_CFG->check_link = -1; } ; @@ -120,11 +122,18 @@ bgp_proto: } | bgp_proto NEIGHBOR bgp_nbr_opts ';' | bgp_proto NEIGHBOR ipa ipa_scope bgp_nbr_opts ';' { - if (ipa_nonzero(BGP_CFG->remote_ip)) + if (ipa_nonzero(BGP_CFG->remote_ip) || BGP_CFG->remote_range) cf_error("Only one neighbor per BGP instance is allowed"); BGP_CFG->remote_ip = $3; if ($4) BGP_CFG->iface = $4; } + | bgp_proto NEIGHBOR RANGE net_ip bgp_nbr_opts ';' { + if (ipa_nonzero(BGP_CFG->remote_ip) || BGP_CFG->remote_range) + cf_error("Only one neighbor per BGP instance is allowed"); + net_addr *n = cfg_alloc($4.length); + net_copy(n, &($4)); + BGP_CFG->remote_range = n; + } | bgp_proto INTERFACE TEXT ';' { BGP_CFG->iface = if_get_by_name($3); } | bgp_proto RR CLUSTER ID idval ';' { BGP_CFG->rr_cluster_id = $5; } | bgp_proto RR CLIENT bool ';' { BGP_CFG->rr_client = $4; } @@ -136,6 +145,12 @@ bgp_proto: | bgp_proto DIRECT ';' { BGP_CFG->multihop = 0; } | bgp_proto MULTIHOP ';' { BGP_CFG->multihop = 64; } | bgp_proto MULTIHOP expr ';' { BGP_CFG->multihop = $3; if (($3<1) || ($3>255)) cf_error("Multihop must be in range 1-255"); } + | bgp_proto DYNAMIC NAME text ';' { + if (strchr($4, '%')) cf_error("Forbidden character '%%' in dynamic name"); + if (strlen($4) > (SYM_MAX_LEN - 16)) cf_error("Dynamic name too long"); + BGP_CFG->dynamic_name = $4; + } + | bgp_proto DYNAMIC NAME DIGITS expr ';' { BGP_CFG->dynamic_name_digits = $5; if ($5>10) cf_error("Dynamic name digits must be at most 10"); } | bgp_proto STRICT BIND bool ';' { BGP_CFG->strict_bind = $4; } | bgp_proto PATH METRIC bool ';' { BGP_CFG->compare_path_lengths = $4; } | bgp_proto MED METRIC bool ';' { BGP_CFG->med_metric = $4; } diff --git a/sysdep/unix/io.c b/sysdep/unix/io.c index d1d86e3b..3a1e22c5 100644 --- a/sysdep/unix/io.c +++ b/sysdep/unix/io.c @@ -1082,6 +1082,7 @@ sk_passive_connected(sock *s, int type) t->fd = fd; t->ttl = s->ttl; t->tos = s->tos; + t->vrf = s->vrf; t->rbsize = s->rbsize; t->tbsize = s->tbsize;