/* $OpenBSD: parse.y,v 1.408 2020/05/10 13:38:46 deraadt Exp $ */ /* * Copyright (c) 2002, 2003, 2004 Henning Brauer * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. * Copyright (c) 2001 Theo de Raadt. All rights reserved. * Copyright (c) 2016, 2017 Job Snijders * Copyright (c) 2016 Peter Hessler * Copyright (c) 2017, 2018 Sebastian Benoit * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ %{ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bgpd.h" #include "session.h" #include "rde.h" #include "log.h" TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); static struct file { TAILQ_ENTRY(file) entry; FILE *stream; char *name; size_t ungetpos; size_t ungetsize; u_char *ungetbuf; int eof_reached; int lineno; int errors; } *file, *topfile; struct file *pushfile(const char *, int); int popfile(void); int check_file_secrecy(int, const char *); int yyparse(void); int yylex(void); int yyerror(const char *, ...) __attribute__((__format__ (printf, 1, 2))) __attribute__((__nonnull__ (1))); int kw_cmp(const void *, const void *); int lookup(char *); int igetc(void); int lgetc(int); void lungetc(int); int findeol(void); TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); struct sym { TAILQ_ENTRY(sym) entry; int used; int persist; char *nam; char *val; }; int symset(const char *, const char *, int); char *symget(const char *); static struct bgpd_config *conf; static struct network_head *netconf; static struct peer_head *new_peers, *cur_peers; static struct peer *curpeer; static struct peer *curgroup; static struct rde_rib *currib; static struct l3vpn *curvpn; static struct prefixset *curpset, *curoset; static struct prefixset_tree *curpsitree; static struct filter_head *filter_l; static struct filter_head *peerfilter_l; static struct filter_head *groupfilter_l; static struct filter_rule *curpeer_filter[2]; static struct filter_rule *curgroup_filter[2]; struct filter_rib_l { struct filter_rib_l *next; char name[PEER_DESCR_LEN]; }; struct filter_peers_l { struct filter_peers_l *next; struct filter_peers p; }; struct filter_prefix_l { struct filter_prefix_l *next; struct filter_prefix p; }; struct filter_prefixlen { enum comp_ops op; int len_min; int len_max; }; struct filter_as_l { struct filter_as_l *next; struct filter_as a; }; struct filter_match_l { struct filter_match m; struct filter_prefix_l *prefix_l; struct filter_as_l *as_l; struct filter_prefixset *prefixset; } fmopts; struct peer *alloc_peer(void); struct peer *new_peer(void); struct peer *new_group(void); int add_mrtconfig(enum mrt_type, char *, int, struct peer *, char *); struct rde_rib *add_rib(char *); struct rde_rib *find_rib(char *); int rib_add_fib(struct rde_rib *, u_int); int get_id(struct peer *); int merge_prefixspec(struct filter_prefix *, struct filter_prefixlen *); int expand_rule(struct filter_rule *, struct filter_rib_l *, struct filter_peers_l *, struct filter_match_l *, struct filter_set_head *); int str2key(char *, char *, size_t); int neighbor_consistent(struct peer *); int merge_filterset(struct filter_set_head *, struct filter_set *); void optimize_filters(struct filter_head *); struct filter_rule *get_rule(enum action_types); int parsecommunity(struct community *, int, char *); int parseextcommunity(struct community *, char *, char *); static int new_as_set(char *); static void add_as_set(u_int32_t); static void done_as_set(void); static struct prefixset *new_prefix_set(char *, int); static void add_roa_set(struct prefixset_item *, u_int32_t, u_int8_t); typedef struct { union { long long number; char *string; struct bgpd_addr addr; u_int8_t u8; struct filter_rib_l *filter_rib; struct filter_peers_l *filter_peers; struct filter_match_l filter_match; struct filter_prefixset *filter_prefixset; struct filter_prefix_l *filter_prefix; struct filter_as_l *filter_as; struct filter_set *filter_set; struct filter_set_head *filter_set_head; struct { struct bgpd_addr prefix; u_int8_t len; } prefix; struct filter_prefixlen prefixlen; struct prefixset_item *prefixset_item; struct { u_int8_t enc_alg; char enc_key[IPSEC_ENC_KEY_LEN]; u_int8_t enc_key_len; } encspec; } v; int lineno; } YYSTYPE; %} %token AS ROUTERID HOLDTIME YMIN LISTEN ON FIBUPDATE FIBPRIORITY RTABLE %token NONE UNICAST VPN RD EXPORT EXPORTTRGT IMPORTTRGT DEFAULTROUTE %token RDE RIB EVALUATE IGNORE COMPARE %token GROUP NEIGHBOR NETWORK %token EBGP IBGP %token LOCALAS REMOTEAS DESCR LOCALADDR MULTIHOP PASSIVE MAXPREFIX RESTART %token ANNOUNCE CAPABILITIES REFRESH AS4BYTE CONNECTRETRY %token DEMOTE ENFORCE NEIGHBORAS ASOVERRIDE REFLECTOR DEPEND DOWN %token DUMP IN OUT SOCKET RESTRICTED %token LOG TRANSPARENT %token TCP MD5SIG PASSWORD KEY TTLSECURITY %token ALLOW DENY MATCH %token QUICK %token FROM TO ANY %token CONNECTED STATIC %token COMMUNITY EXTCOMMUNITY LARGECOMMUNITY DELETE %token PREFIX PREFIXLEN PREFIXSET %token ROASET ORIGINSET OVS %token ASSET SOURCEAS TRANSITAS PEERAS MAXASLEN MAXASSEQ %token SET LOCALPREF MED METRIC NEXTHOP REJECT BLACKHOLE NOMODIFY SELF %token PREPEND_SELF PREPEND_PEER PFTABLE WEIGHT RTLABEL ORIGIN PRIORITY %token ERROR INCLUDE %token IPSEC ESP AH SPI IKE %token IPV4 IPV6 %token QUALIFY VIA %token NE LE GE XRANGE LONGER MAXLEN %token STRING %token NUMBER %type asnumber as4number as4number_any optnumber %type espah family safi restart origincode nettype %type yesno inout restricted validity %type string %type address %type prefix addrspec %type prefixset_item %type action quick direction delete community %type filter_rib_h filter_rib_l filter_rib %type filter_peer filter_peer_l filter_peer_h %type filter_match filter_elm filter_match_h %type filter_as filter_as_l filter_as_h %type filter_as_t filter_as_t_l filter_as_l_h %type prefixlenop %type filter_set_opt %type filter_set filter_set_l %type filter_prefix filter_prefix_l filter_prefix_h %type filter_prefix_m %type unaryop equalityop binaryop filter_as_type %type encspec %% grammar : /* empty */ | grammar '\n' | grammar varset '\n' | grammar include '\n' | grammar as_set '\n' | grammar prefixset '\n' | grammar roa_set '\n' | grammar origin_set '\n' | grammar rib '\n' | grammar conf_main '\n' | grammar l3vpn '\n' | grammar neighbor '\n' | grammar group '\n' | grammar filterrule '\n' | grammar error '\n' { file->errors++; } ; asnumber : NUMBER { /* * According to iana 65535 and 4294967295 are reserved * but enforcing this is not duty of the parser. */ if ($1 < 0 || $1 > UINT_MAX) { yyerror("AS too big: max %u", UINT_MAX); YYERROR; } } as4number : STRING { const char *errstr; char *dot; u_int32_t uvalh = 0, uval; if ((dot = strchr($1,'.')) != NULL) { *dot++ = '\0'; uvalh = strtonum($1, 0, USHRT_MAX, &errstr); if (errstr) { yyerror("number %s is %s", $1, errstr); free($1); YYERROR; } uval = strtonum(dot, 0, USHRT_MAX, &errstr); if (errstr) { yyerror("number %s is %s", dot, errstr); free($1); YYERROR; } free($1); } else { yyerror("AS %s is bad", $1); free($1); YYERROR; } if (uvalh == 0 && (uval == AS_TRANS || uval == 0)) { yyerror("AS %u is reserved and may not be used", uval); YYERROR; } $$ = uval | (uvalh << 16); } | asnumber { if ($1 == AS_TRANS || $1 == 0) { yyerror("AS %u is reserved and may not be used", (u_int32_t)$1); YYERROR; } $$ = $1; } ; as4number_any : STRING { const char *errstr; char *dot; u_int32_t uvalh = 0, uval; if ((dot = strchr($1,'.')) != NULL) { *dot++ = '\0'; uvalh = strtonum($1, 0, USHRT_MAX, &errstr); if (errstr) { yyerror("number %s is %s", $1, errstr); free($1); YYERROR; } uval = strtonum(dot, 0, USHRT_MAX, &errstr); if (errstr) { yyerror("number %s is %s", dot, errstr); free($1); YYERROR; } free($1); } else { yyerror("AS %s is bad", $1); free($1); YYERROR; } $$ = uval | (uvalh << 16); } | asnumber { $$ = $1; } ; string : string STRING { if (asprintf(&$$, "%s %s", $1, $2) == -1) fatal("string: asprintf"); free($1); free($2); } | STRING ; yesno : STRING { if (!strcmp($1, "yes")) $$ = 1; else if (!strcmp($1, "no")) $$ = 0; else { yyerror("syntax error, " "either yes or no expected"); free($1); YYERROR; } free($1); } ; varset : STRING '=' string { char *s = $1; if (cmd_opts & BGPD_OPT_VERBOSE) printf("%s = \"%s\"\n", $1, $3); while (*s++) { if (isspace((unsigned char)*s)) { yyerror("macro name cannot contain " "whitespace"); free($1); free($3); YYERROR; } } if (symset($1, $3, 0) == -1) fatal("cannot store variable"); free($1); free($3); } ; include : INCLUDE STRING { struct file *nfile; if ((nfile = pushfile($2, 1)) == NULL) { yyerror("failed to include file %s", $2); free($2); YYERROR; } free($2); file = nfile; lungetc('\n'); } ; as_set : ASSET STRING '{' optnl { if (strlen($2) >= SET_NAME_LEN) { yyerror("as-set name %s too long", $2); free($2); YYERROR; } if (new_as_set($2) != 0) { free($2); YYERROR; } free($2); } as_set_l optnl '}' { done_as_set(); } | ASSET STRING '{' optnl '}' { if (new_as_set($2) != 0) { free($2); YYERROR; } free($2); } as_set_l : as4number_any { add_as_set($1); } | as_set_l comma as4number_any { add_as_set($3); } prefixset : PREFIXSET STRING '{' optnl { if ((curpset = new_prefix_set($2, 0)) == NULL) { free($2); YYERROR; } free($2); } prefixset_l optnl '}' { SIMPLEQ_INSERT_TAIL(&conf->prefixsets, curpset, entry); curpset = NULL; } | PREFIXSET STRING '{' optnl '}' { if ((curpset = new_prefix_set($2, 0)) == NULL) { free($2); YYERROR; } free($2); SIMPLEQ_INSERT_TAIL(&conf->prefixsets, curpset, entry); curpset = NULL; } prefixset_l : prefixset_item { struct prefixset_item *psi; if ($1->p.op != OP_NONE) curpset->sflags |= PREFIXSET_FLAG_OPS; psi = RB_INSERT(prefixset_tree, &curpset->psitems, $1); if (psi != NULL) { if (cmd_opts & BGPD_OPT_VERBOSE2) log_warnx("warning: duplicate entry in " "prefixset \"%s\" for %s/%u", curpset->name, log_addr(&$1->p.addr), $1->p.len); free($1); } } | prefixset_l comma prefixset_item { struct prefixset_item *psi; if ($3->p.op != OP_NONE) curpset->sflags |= PREFIXSET_FLAG_OPS; psi = RB_INSERT(prefixset_tree, &curpset->psitems, $3); if (psi != NULL) { if (cmd_opts & BGPD_OPT_VERBOSE2) log_warnx("warning: duplicate entry in " "prefixset \"%s\" for %s/%u", curpset->name, log_addr(&$3->p.addr), $3->p.len); free($3); } } ; prefixset_item : prefix prefixlenop { if ($2.op != OP_NONE && $2.op != OP_RANGE) { yyerror("unsupported prefixlen operation in " "prefix-set"); YYERROR; } if (($$ = calloc(1, sizeof(*$$))) == NULL) fatal(NULL); memcpy(&$$->p.addr, &$1.prefix, sizeof($$->p.addr)); $$->p.len = $1.len; if (merge_prefixspec(&$$->p, &$2) == -1) { free($$); YYERROR; } } ; roa_set : ROASET '{' optnl { curpsitree = &conf->roa; } roa_set_l optnl '}' { curpsitree = NULL; } | ROASET '{' optnl '}' /* nothing */ ; origin_set : ORIGINSET STRING '{' optnl { if ((curoset = new_prefix_set($2, 1)) == NULL) { free($2); YYERROR; } curpsitree = &curoset->psitems; free($2); } roa_set_l optnl '}' { SIMPLEQ_INSERT_TAIL(&conf->originsets, curoset, entry); curoset = NULL; curpsitree = NULL; } | ORIGINSET STRING '{' optnl '}' { if ((curoset = new_prefix_set($2, 1)) == NULL) { free($2); YYERROR; } free($2); SIMPLEQ_INSERT_TAIL(&conf->originsets, curoset, entry); curoset = NULL; curpsitree = NULL; } ; roa_set_l : prefixset_item SOURCEAS as4number_any { if ($1->p.len_min != $1->p.len) { yyerror("unsupported prefixlen operation in " "roa-set"); free($1); YYERROR; } add_roa_set($1, $3, $1->p.len_max); } | roa_set_l comma prefixset_item SOURCEAS as4number_any { if ($3->p.len_min != $3->p.len) { yyerror("unsupported prefixlen operation in " "roa-set"); free($3); YYERROR; } add_roa_set($3, $5, $3->p.len_max); } ; conf_main : AS as4number { conf->as = $2; if ($2 > USHRT_MAX) conf->short_as = AS_TRANS; else conf->short_as = $2; } | AS as4number asnumber { conf->as = $2; conf->short_as = $3; } | ROUTERID address { if ($2.aid != AID_INET) { yyerror("router-id must be an IPv4 address"); YYERROR; } conf->bgpid = $2.v4.s_addr; } | HOLDTIME NUMBER { if ($2 < MIN_HOLDTIME || $2 > USHRT_MAX) { yyerror("holdtime must be between %u and %u", MIN_HOLDTIME, USHRT_MAX); YYERROR; } conf->holdtime = $2; } | HOLDTIME YMIN NUMBER { if ($3 < MIN_HOLDTIME || $3 > USHRT_MAX) { yyerror("holdtime must be between %u and %u", MIN_HOLDTIME, USHRT_MAX); YYERROR; } conf->min_holdtime = $3; } | LISTEN ON address { struct listen_addr *la; struct sockaddr *sa; if ((la = calloc(1, sizeof(struct listen_addr))) == NULL) fatal("parse conf_main listen on calloc"); la->fd = -1; la->reconf = RECONF_REINIT; sa = addr2sa(&$3, BGP_PORT, &la->sa_len); memcpy(&la->sa, sa, la->sa_len); TAILQ_INSERT_TAIL(conf->listen_addrs, la, entry); } | FIBPRIORITY NUMBER { if ($2 <= RTP_NONE || $2 > RTP_MAX) { yyerror("invalid fib-priority"); YYERROR; } conf->fib_priority = $2; } | FIBUPDATE yesno { struct rde_rib *rr; rr = find_rib("Loc-RIB"); if (rr == NULL) fatalx("RTABLE can not find the main RIB!"); if ($2 == 0) rr->flags |= F_RIB_NOFIBSYNC; else rr->flags &= ~F_RIB_NOFIBSYNC; } | TRANSPARENT yesno { if ($2 == 1) conf->flags |= BGPD_FLAG_DECISION_TRANS_AS; else conf->flags &= ~BGPD_FLAG_DECISION_TRANS_AS; } | LOG STRING { if (!strcmp($2, "updates")) conf->log |= BGPD_LOG_UPDATES; else { free($2); YYERROR; } free($2); } | network | DUMP STRING STRING optnumber { int action; if ($4 < 0 || $4 > INT_MAX) { yyerror("bad timeout"); free($2); free($3); YYERROR; } if (!strcmp($2, "table")) action = MRT_TABLE_DUMP; else if (!strcmp($2, "table-mp")) action = MRT_TABLE_DUMP_MP; else if (!strcmp($2, "table-v2")) action = MRT_TABLE_DUMP_V2; else { yyerror("unknown mrt dump type"); free($2); free($3); YYERROR; } free($2); if (add_mrtconfig(action, $3, $4, NULL, NULL) == -1) { free($3); YYERROR; } free($3); } | DUMP RIB STRING STRING STRING optnumber { int action; if ($6 < 0 || $6 > INT_MAX) { yyerror("bad timeout"); free($3); free($4); free($5); YYERROR; } if (!strcmp($4, "table")) action = MRT_TABLE_DUMP; else if (!strcmp($4, "table-mp")) action = MRT_TABLE_DUMP_MP; else if (!strcmp($4, "table-v2")) action = MRT_TABLE_DUMP_V2; else { yyerror("unknown mrt dump type"); free($3); free($4); free($5); YYERROR; } free($4); if (add_mrtconfig(action, $5, $6, NULL, $3) == -1) { free($3); free($5); YYERROR; } free($3); free($5); } | mrtdump | RDE STRING EVALUATE { if (!strcmp($2, "route-age")) conf->flags |= BGPD_FLAG_DECISION_ROUTEAGE; else { yyerror("unknown route decision type"); free($2); YYERROR; } free($2); } | RDE STRING IGNORE { if (!strcmp($2, "route-age")) conf->flags &= ~BGPD_FLAG_DECISION_ROUTEAGE; else { yyerror("unknown route decision type"); free($2); YYERROR; } free($2); } | RDE MED COMPARE STRING { if (!strcmp($4, "always")) conf->flags |= BGPD_FLAG_DECISION_MED_ALWAYS; else if (!strcmp($4, "strict")) conf->flags &= ~BGPD_FLAG_DECISION_MED_ALWAYS; else { yyerror("rde med compare: " "unknown setting \"%s\"", $4); free($4); YYERROR; } free($4); } | NEXTHOP QUALIFY VIA STRING { if (!strcmp($4, "bgp")) conf->flags |= BGPD_FLAG_NEXTHOP_BGP; else if (!strcmp($4, "default")) conf->flags |= BGPD_FLAG_NEXTHOP_DEFAULT; else { yyerror("nexthop depend on: " "unknown setting \"%s\"", $4); free($4); YYERROR; } free($4); } | RTABLE NUMBER { struct rde_rib *rr; if ($2 > RT_TABLEID_MAX) { yyerror("rtable %llu too big: max %u", $2, RT_TABLEID_MAX); YYERROR; } if (ktable_exists($2, NULL) != 1) { yyerror("rtable id %lld does not exist", $2); YYERROR; } rr = find_rib("Loc-RIB"); if (rr == NULL) fatalx("RTABLE can not find the main RIB!"); rr->rtableid = $2; } | CONNECTRETRY NUMBER { if ($2 > USHRT_MAX || $2 < 1) { yyerror("invalid connect-retry"); YYERROR; } conf->connectretry = $2; } | SOCKET STRING restricted { if (strlen($2) >= sizeof(((struct sockaddr_un *)0)->sun_path)) { yyerror("socket path too long"); YYERROR; } if ($3) { free(conf->rcsock); conf->rcsock = $2; } else { free(conf->csock); conf->csock = $2; } } ; rib : RDE RIB STRING { if ((currib = add_rib($3)) == NULL) { free($3); YYERROR; } free($3); } ribopts { currib = NULL; } ribopts : fibupdate | RTABLE NUMBER fibupdate { if ($2 > RT_TABLEID_MAX) { yyerror("rtable %llu too big: max %u", $2, RT_TABLEID_MAX); YYERROR; } if (rib_add_fib(currib, $2) == -1) YYERROR; } | yesno EVALUATE { if ($1) { yyerror("bad rde rib definition"); YYERROR; } currib->flags |= F_RIB_NOEVALUATE; } ; fibupdate : /* empty */ | FIBUPDATE yesno { if ($2 == 0) currib->flags |= F_RIB_NOFIBSYNC; else currib->flags &= ~F_RIB_NOFIBSYNC; } ; mrtdump : DUMP STRING inout STRING optnumber { int action; if ($5 < 0 || $5 > INT_MAX) { yyerror("bad timeout"); free($2); free($4); YYERROR; } if (!strcmp($2, "all")) action = $3 ? MRT_ALL_IN : MRT_ALL_OUT; else if (!strcmp($2, "updates")) action = $3 ? MRT_UPDATE_IN : MRT_UPDATE_OUT; else { yyerror("unknown mrt msg dump type"); free($2); free($4); YYERROR; } if (add_mrtconfig(action, $4, $5, curpeer, NULL) == -1) { free($2); free($4); YYERROR; } free($2); free($4); } ; network : NETWORK prefix filter_set { struct network *n, *m; if ((n = calloc(1, sizeof(struct network))) == NULL) fatal("new_network"); memcpy(&n->net.prefix, &$2.prefix, sizeof(n->net.prefix)); n->net.prefixlen = $2.len; filterset_move($3, &n->net.attrset); free($3); TAILQ_FOREACH(m, netconf, entry) { if (n->net.type == m->net.type && n->net.prefixlen == m->net.prefixlen && prefix_compare(&n->net.prefix, &m->net.prefix, n->net.prefixlen) == 0) yyerror("duplicate prefix " "in network statement"); } TAILQ_INSERT_TAIL(netconf, n, entry); } | NETWORK PREFIXSET STRING filter_set { struct prefixset *ps; struct network *n; if ((ps = find_prefixset($3, &conf->prefixsets)) == NULL) { yyerror("prefix-set '%s' not defined", $3); free($3); filterset_free($4); free($4); YYERROR; } if (ps->sflags & PREFIXSET_FLAG_OPS) { yyerror("prefix-set %s has prefixlen operators " "and cannot be used in network statements.", ps->name); free($3); filterset_free($4); free($4); YYERROR; } if ((n = calloc(1, sizeof(struct network))) == NULL) fatal("new_network"); strlcpy(n->net.psname, ps->name, sizeof(n->net.psname)); filterset_move($4, &n->net.attrset); n->net.type = NETWORK_PREFIXSET; TAILQ_INSERT_TAIL(netconf, n, entry); free($3); free($4); } | NETWORK family RTLABEL STRING filter_set { struct network *n; if ((n = calloc(1, sizeof(struct network))) == NULL) fatal("new_network"); if (afi2aid($2, SAFI_UNICAST, &n->net.prefix.aid) == -1) { yyerror("unknown family"); filterset_free($5); free($5); YYERROR; } n->net.type = NETWORK_RTLABEL; n->net.rtlabel = rtlabel_name2id($4); filterset_move($5, &n->net.attrset); free($5); TAILQ_INSERT_TAIL(netconf, n, entry); } | NETWORK family PRIORITY NUMBER filter_set { struct network *n; if ($4 < RTP_LOCAL && $4 > RTP_MAX) { yyerror("priority %lld > max %d or < min %d", $4, RTP_MAX, RTP_LOCAL); YYERROR; } if ((n = calloc(1, sizeof(struct network))) == NULL) fatal("new_network"); if (afi2aid($2, SAFI_UNICAST, &n->net.prefix.aid) == -1) { yyerror("unknown family"); filterset_free($5); free($5); YYERROR; } n->net.type = NETWORK_PRIORITY; n->net.priority = $4; filterset_move($5, &n->net.attrset); free($5); TAILQ_INSERT_TAIL(netconf, n, entry); } | NETWORK family nettype filter_set { struct network *n; if ((n = calloc(1, sizeof(struct network))) == NULL) fatal("new_network"); if (afi2aid($2, SAFI_UNICAST, &n->net.prefix.aid) == -1) { yyerror("unknown family"); filterset_free($4); free($4); YYERROR; } n->net.type = $3 ? NETWORK_STATIC : NETWORK_CONNECTED; filterset_move($4, &n->net.attrset); free($4); TAILQ_INSERT_TAIL(netconf, n, entry); } ; inout : IN { $$ = 1; } | OUT { $$ = 0; } ; restricted : RESTRICTED { $$ = 1; } | /* nothing */ { $$ = 0; } ; address : STRING { u_int8_t len; if (!host($1, &$$, &len)) { yyerror("could not parse address spec \"%s\"", $1); free($1); YYERROR; } free($1); if (($$.aid == AID_INET && len != 32) || ($$.aid == AID_INET6 && len != 128)) { /* unreachable */ yyerror("got prefixlen %u, expected %u", len, $$.aid == AID_INET ? 32 : 128); YYERROR; } } ; prefix : STRING '/' NUMBER { char *s; if ($3 < 0 || $3 > 128) { yyerror("bad prefixlen %lld", $3); free($1); YYERROR; } if (asprintf(&s, "%s/%lld", $1, $3) == -1) fatal(NULL); free($1); if (!host(s, &$$.prefix, &$$.len)) { yyerror("could not parse address \"%s\"", s); free(s); YYERROR; } free(s); } | NUMBER '/' NUMBER { char *s; /* does not match IPv6 */ if ($1 < 0 || $1 > 255 || $3 < 0 || $3 > 32) { yyerror("bad prefix %lld/%lld", $1, $3); YYERROR; } if (asprintf(&s, "%lld/%lld", $1, $3) == -1) fatal(NULL); if (!host(s, &$$.prefix, &$$.len)) { yyerror("could not parse address \"%s\"", s); free(s); YYERROR; } free(s); } ; addrspec : address { memcpy(&$$.prefix, &$1, sizeof(struct bgpd_addr)); if ($$.prefix.aid == AID_INET) $$.len = 32; else $$.len = 128; } | prefix ; optnumber : /* empty */ { $$ = 0; } | NUMBER ; l3vpn : VPN STRING ON STRING { u_int rdomain, label; if (get_mpe_config($4, &rdomain, &label) == -1) { if ((cmd_opts & BGPD_OPT_NOACTION) == 0) { yyerror("troubles getting config of %s", $4); free($4); free($2); YYERROR; } } if (!(curvpn = calloc(1, sizeof(struct l3vpn)))) fatal(NULL); strlcpy(curvpn->ifmpe, $4, IFNAMSIZ); if (strlcpy(curvpn->descr, $2, sizeof(curvpn->descr)) >= sizeof(curvpn->descr)) { yyerror("descr \"%s\" too long: max %zu", $2, sizeof(curvpn->descr) - 1); free($2); free($4); free(curvpn); curvpn = NULL; YYERROR; } free($2); free($4); TAILQ_INIT(&curvpn->import); TAILQ_INIT(&curvpn->export); TAILQ_INIT(&curvpn->net_l); curvpn->label = label; curvpn->rtableid = rdomain; netconf = &curvpn->net_l; } '{' l3vpnopts_l '}' { /* insert into list */ SIMPLEQ_INSERT_TAIL(&conf->l3vpns, curvpn, entry); curvpn = NULL; netconf = &conf->networks; } ; l3vpnopts_l : /* empty */ | l3vpnopts_l '\n' | l3vpnopts_l l3vpnopts '\n' | l3vpnopts_l error '\n' ; l3vpnopts : RD STRING { struct community ext; memset(&ext, 0, sizeof(ext)); if (parseextcommunity(&ext, "rt", $2) == -1) { free($2); YYERROR; } free($2); /* * RD is almost encoded like an ext-community, * but only almost so convert here. */ if (community_to_rd(&ext, &curvpn->rd) == -1) { yyerror("bad encoding of rd"); YYERROR; } } | EXPORTTRGT STRING STRING { struct filter_set *set; if ((set = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); set->type = ACTION_SET_COMMUNITY; if (parseextcommunity(&set->action.community, $2, $3) == -1) { free($3); free($2); free(set); YYERROR; } free($3); free($2); TAILQ_INSERT_TAIL(&curvpn->export, set, entry); } | IMPORTTRGT STRING STRING { struct filter_set *set; if ((set = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); set->type = ACTION_SET_COMMUNITY; if (parseextcommunity(&set->action.community, $2, $3) == -1) { free($3); free($2); free(set); YYERROR; } free($3); free($2); TAILQ_INSERT_TAIL(&curvpn->import, set, entry); } | FIBUPDATE yesno { if ($2 == 0) curvpn->flags |= F_RIB_NOFIBSYNC; else curvpn->flags &= ~F_RIB_NOFIBSYNC; } | network ; neighbor : { curpeer = new_peer(); } NEIGHBOR addrspec { memcpy(&curpeer->conf.remote_addr, &$3.prefix, sizeof(curpeer->conf.remote_addr)); curpeer->conf.remote_masklen = $3.len; if (($3.prefix.aid == AID_INET && $3.len != 32) || ($3.prefix.aid == AID_INET6 && $3.len != 128)) curpeer->conf.template = 1; curpeer->conf.capabilities.mp[ curpeer->conf.remote_addr.aid] = 1; if (get_id(curpeer)) { yyerror("get_id failed"); YYERROR; } } peeropts_h { if (curpeer_filter[0] != NULL) TAILQ_INSERT_TAIL(peerfilter_l, curpeer_filter[0], entry); if (curpeer_filter[1] != NULL) TAILQ_INSERT_TAIL(peerfilter_l, curpeer_filter[1], entry); curpeer_filter[0] = NULL; curpeer_filter[1] = NULL; if (neighbor_consistent(curpeer) == -1) YYERROR; if (RB_INSERT(peer_head, new_peers, curpeer) != NULL) fatalx("%s: peer tree is corrupt", __func__); curpeer = curgroup; } ; group : GROUP string { curgroup = curpeer = new_group(); if (strlcpy(curgroup->conf.group, $2, sizeof(curgroup->conf.group)) >= sizeof(curgroup->conf.group)) { yyerror("group name \"%s\" too long: max %zu", $2, sizeof(curgroup->conf.group) - 1); free($2); YYERROR; } free($2); if (get_id(curgroup)) { yyerror("get_id failed"); YYERROR; } } '{' groupopts_l '}' { if (curgroup_filter[0] != NULL) TAILQ_INSERT_TAIL(groupfilter_l, curgroup_filter[0], entry); if (curgroup_filter[1] != NULL) TAILQ_INSERT_TAIL(groupfilter_l, curgroup_filter[1], entry); curgroup_filter[0] = NULL; curgroup_filter[1] = NULL; free(curgroup); curgroup = NULL; } ; groupopts_l : /* empty */ | groupopts_l '\n' | groupopts_l peeropts '\n' | groupopts_l neighbor '\n' | groupopts_l error '\n' ; peeropts_h : '{' '\n' peeropts_l '}' | '{' peeropts '}' | /* empty */ ; peeropts_l : /* empty */ | peeropts_l '\n' | peeropts_l peeropts '\n' | peeropts_l error '\n' ; peeropts : REMOTEAS as4number { curpeer->conf.remote_as = $2; } | LOCALAS as4number { curpeer->conf.local_as = $2; if ($2 > USHRT_MAX) curpeer->conf.local_short_as = AS_TRANS; else curpeer->conf.local_short_as = $2; } | LOCALAS as4number asnumber { curpeer->conf.local_as = $2; curpeer->conf.local_short_as = $3; } | DESCR string { if (strlcpy(curpeer->conf.descr, $2, sizeof(curpeer->conf.descr)) >= sizeof(curpeer->conf.descr)) { yyerror("descr \"%s\" too long: max %zu", $2, sizeof(curpeer->conf.descr) - 1); free($2); YYERROR; } free($2); } | LOCALADDR address { if ($2.aid == AID_INET) memcpy(&curpeer->conf.local_addr_v4, &$2, sizeof(curpeer->conf.local_addr_v4)); else if ($2.aid == AID_INET6) memcpy(&curpeer->conf.local_addr_v6, &$2, sizeof(curpeer->conf.local_addr_v6)); else { yyerror("Unsupported address family %s for " "local-addr", aid2str($2.aid)); YYERROR; } } | yesno LOCALADDR { if ($1) { yyerror("bad local-address definition"); YYERROR; } memset(&curpeer->conf.local_addr_v4, 0, sizeof(curpeer->conf.local_addr_v4)); memset(&curpeer->conf.local_addr_v6, 0, sizeof(curpeer->conf.local_addr_v6)); } | MULTIHOP NUMBER { if ($2 < 2 || $2 > 255) { yyerror("invalid multihop distance %lld", $2); YYERROR; } curpeer->conf.distance = $2; } | PASSIVE { curpeer->conf.passive = 1; } | DOWN { curpeer->conf.down = 1; } | DOWN STRING { curpeer->conf.down = 1; if (strlcpy(curpeer->conf.reason, $2, sizeof(curpeer->conf.reason)) >= sizeof(curpeer->conf.reason)) { yyerror("shutdown reason too long"); free($2); YYERROR; } free($2); } | RIB STRING { if (!find_rib($2)) { yyerror("rib \"%s\" does not exist.", $2); free($2); YYERROR; } if (strlcpy(curpeer->conf.rib, $2, sizeof(curpeer->conf.rib)) >= sizeof(curpeer->conf.rib)) { yyerror("rib name \"%s\" too long: max %zu", $2, sizeof(curpeer->conf.rib) - 1); free($2); YYERROR; } free($2); } | HOLDTIME NUMBER { if ($2 < MIN_HOLDTIME || $2 > USHRT_MAX) { yyerror("holdtime must be between %u and %u", MIN_HOLDTIME, USHRT_MAX); YYERROR; } curpeer->conf.holdtime = $2; } | HOLDTIME YMIN NUMBER { if ($3 < MIN_HOLDTIME || $3 > USHRT_MAX) { yyerror("holdtime must be between %u and %u", MIN_HOLDTIME, USHRT_MAX); YYERROR; } curpeer->conf.min_holdtime = $3; } | ANNOUNCE family safi { u_int8_t aid, safi; u_int16_t afi; if ($3 == SAFI_NONE) { for (aid = 0; aid < AID_MAX; aid++) { if (aid2afi(aid, &afi, &safi) == -1 || afi != $2) continue; curpeer->conf.capabilities.mp[aid] = 0; } } else { if (afi2aid($2, $3, &aid) == -1) { yyerror("unknown AFI/SAFI pair"); YYERROR; } curpeer->conf.capabilities.mp[aid] = 1; } } | ANNOUNCE CAPABILITIES yesno { curpeer->conf.announce_capa = $3; } | ANNOUNCE REFRESH yesno { curpeer->conf.capabilities.refresh = $3; } | ANNOUNCE RESTART yesno { curpeer->conf.capabilities.grestart.restart = $3; } | ANNOUNCE AS4BYTE yesno { curpeer->conf.capabilities.as4byte = $3; } | EXPORT NONE { curpeer->conf.export_type = EXPORT_NONE; } | EXPORT DEFAULTROUTE { curpeer->conf.export_type = EXPORT_DEFAULT_ROUTE; } | ENFORCE NEIGHBORAS yesno { if ($3) curpeer->conf.enforce_as = ENFORCE_AS_ON; else curpeer->conf.enforce_as = ENFORCE_AS_OFF; } | ENFORCE LOCALAS yesno { if ($3) curpeer->conf.enforce_local_as = ENFORCE_AS_ON; else curpeer->conf.enforce_local_as = ENFORCE_AS_OFF; } | ASOVERRIDE yesno { if ($2) { struct filter_rule *r; struct filter_set *s; if ((s = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); s->type = ACTION_SET_AS_OVERRIDE; r = get_rule(s->type); if (merge_filterset(&r->set, s) == -1) YYERROR; } } | MAXPREFIX NUMBER restart { if ($2 < 0 || $2 > UINT_MAX) { yyerror("bad maximum number of prefixes"); YYERROR; } curpeer->conf.max_prefix = $2; curpeer->conf.max_prefix_restart = $3; } | MAXPREFIX NUMBER OUT restart { if ($2 < 0 || $2 > UINT_MAX) { yyerror("bad maximum number of prefixes"); YYERROR; } curpeer->conf.max_out_prefix = $2; curpeer->conf.max_out_prefix_restart = $4; } | TCP MD5SIG PASSWORD string { if (curpeer->conf.auth.method) { yyerror("auth method cannot be redefined"); free($4); YYERROR; } if (strlcpy(curpeer->conf.auth.md5key, $4, sizeof(curpeer->conf.auth.md5key)) >= sizeof(curpeer->conf.auth.md5key)) { yyerror("tcp md5sig password too long: max %zu", sizeof(curpeer->conf.auth.md5key) - 1); free($4); YYERROR; } curpeer->conf.auth.method = AUTH_MD5SIG; curpeer->conf.auth.md5key_len = strlen($4); free($4); } | TCP MD5SIG KEY string { if (curpeer->conf.auth.method) { yyerror("auth method cannot be redefined"); free($4); YYERROR; } if (str2key($4, curpeer->conf.auth.md5key, sizeof(curpeer->conf.auth.md5key)) == -1) { free($4); YYERROR; } curpeer->conf.auth.method = AUTH_MD5SIG; curpeer->conf.auth.md5key_len = strlen($4) / 2; free($4); } | IPSEC espah IKE { if (curpeer->conf.auth.method) { yyerror("auth method cannot be redefined"); YYERROR; } if ($2) curpeer->conf.auth.method = AUTH_IPSEC_IKE_ESP; else curpeer->conf.auth.method = AUTH_IPSEC_IKE_AH; } | IPSEC espah inout SPI NUMBER STRING STRING encspec { u_int32_t auth_alg; u_int8_t keylen; if (curpeer->conf.auth.method && (((curpeer->conf.auth.spi_in && $3 == 1) || (curpeer->conf.auth.spi_out && $3 == 0)) || ($2 == 1 && curpeer->conf.auth.method != AUTH_IPSEC_MANUAL_ESP) || ($2 == 0 && curpeer->conf.auth.method != AUTH_IPSEC_MANUAL_AH))) { yyerror("auth method cannot be redefined"); free($6); free($7); YYERROR; } if (!strcmp($6, "sha1")) { auth_alg = SADB_AALG_SHA1HMAC; keylen = 20; } else if (!strcmp($6, "md5")) { auth_alg = SADB_AALG_MD5HMAC; keylen = 16; } else { yyerror("unknown auth algorithm \"%s\"", $6); free($6); free($7); YYERROR; } free($6); if (strlen($7) / 2 != keylen) { yyerror("auth key len: must be %u bytes, " "is %zu bytes", keylen, strlen($7) / 2); free($7); YYERROR; } if ($2) curpeer->conf.auth.method = AUTH_IPSEC_MANUAL_ESP; else { if ($8.enc_alg) { yyerror("\"ipsec ah\" doesn't take " "encryption keys"); free($7); YYERROR; } curpeer->conf.auth.method = AUTH_IPSEC_MANUAL_AH; } if ($5 <= SPI_RESERVED_MAX || $5 > UINT_MAX) { yyerror("bad spi number %lld", $5); free($7); YYERROR; } if ($3 == 1) { if (str2key($7, curpeer->conf.auth.auth_key_in, sizeof(curpeer->conf.auth.auth_key_in)) == -1) { free($7); YYERROR; } curpeer->conf.auth.spi_in = $5; curpeer->conf.auth.auth_alg_in = auth_alg; curpeer->conf.auth.enc_alg_in = $8.enc_alg; memcpy(&curpeer->conf.auth.enc_key_in, &$8.enc_key, sizeof(curpeer->conf.auth.enc_key_in)); curpeer->conf.auth.enc_keylen_in = $8.enc_key_len; curpeer->conf.auth.auth_keylen_in = keylen; } else { if (str2key($7, curpeer->conf.auth.auth_key_out, sizeof(curpeer->conf.auth.auth_key_out)) == -1) { free($7); YYERROR; } curpeer->conf.auth.spi_out = $5; curpeer->conf.auth.auth_alg_out = auth_alg; curpeer->conf.auth.enc_alg_out = $8.enc_alg; memcpy(&curpeer->conf.auth.enc_key_out, &$8.enc_key, sizeof(curpeer->conf.auth.enc_key_out)); curpeer->conf.auth.enc_keylen_out = $8.enc_key_len; curpeer->conf.auth.auth_keylen_out = keylen; } free($7); } | TTLSECURITY yesno { curpeer->conf.ttlsec = $2; } | SET filter_set_opt { struct filter_rule *r; r = get_rule($2->type); if (merge_filterset(&r->set, $2) == -1) YYERROR; } | SET '{' optnl filter_set_l optnl '}' { struct filter_rule *r; struct filter_set *s; while ((s = TAILQ_FIRST($4)) != NULL) { TAILQ_REMOVE($4, s, entry); r = get_rule(s->type); if (merge_filterset(&r->set, s) == -1) YYERROR; } free($4); } | mrtdump | REFLECTOR { if ((conf->flags & BGPD_FLAG_REFLECTOR) && conf->clusterid != 0) { yyerror("only one route reflector " "cluster allowed"); YYERROR; } conf->flags |= BGPD_FLAG_REFLECTOR; curpeer->conf.reflector_client = 1; } | REFLECTOR address { if ($2.aid != AID_INET) { yyerror("route reflector cluster-id must be " "an IPv4 address"); YYERROR; } if ((conf->flags & BGPD_FLAG_REFLECTOR) && conf->clusterid != $2.v4.s_addr) { yyerror("only one route reflector " "cluster allowed"); YYERROR; } conf->flags |= BGPD_FLAG_REFLECTOR; curpeer->conf.reflector_client = 1; conf->clusterid = $2.v4.s_addr; } | DEPEND ON STRING { if (strlcpy(curpeer->conf.if_depend, $3, sizeof(curpeer->conf.if_depend)) >= sizeof(curpeer->conf.if_depend)) { yyerror("interface name \"%s\" too long: " "max %zu", $3, sizeof(curpeer->conf.if_depend) - 1); free($3); YYERROR; } free($3); } | DEMOTE STRING { if (strlcpy(curpeer->conf.demote_group, $2, sizeof(curpeer->conf.demote_group)) >= sizeof(curpeer->conf.demote_group)) { yyerror("demote group name \"%s\" too long: " "max %zu", $2, sizeof(curpeer->conf.demote_group) - 1); free($2); YYERROR; } free($2); if (carp_demote_init(curpeer->conf.demote_group, cmd_opts & BGPD_OPT_FORCE_DEMOTE) == -1) { yyerror("error initializing group \"%s\"", curpeer->conf.demote_group); YYERROR; } } | TRANSPARENT yesno { if ($2 == 1) curpeer->conf.flags |= PEERFLAG_TRANS_AS; else curpeer->conf.flags &= ~PEERFLAG_TRANS_AS; } | LOG STRING { if (!strcmp($2, "updates")) curpeer->conf.flags |= PEERFLAG_LOG_UPDATES; else if (!strcmp($2, "no")) curpeer->conf.flags &= ~PEERFLAG_LOG_UPDATES; else { free($2); YYERROR; } free($2); } ; restart : /* nada */ { $$ = 0; } | RESTART NUMBER { if ($2 < 1 || $2 > USHRT_MAX) { yyerror("restart out of range. 1 to %u minutes", USHRT_MAX); YYERROR; } $$ = $2; } ; family : IPV4 { $$ = AFI_IPv4; } | IPV6 { $$ = AFI_IPv6; } ; safi : NONE { $$ = SAFI_NONE; } | UNICAST { $$ = SAFI_UNICAST; } | VPN { $$ = SAFI_MPLSVPN; } ; nettype : STATIC { $$ = 1; } | CONNECTED { $$ = 0; } ; espah : ESP { $$ = 1; } | AH { $$ = 0; } ; encspec : /* nada */ { bzero(&$$, sizeof($$)); } | STRING STRING { bzero(&$$, sizeof($$)); if (!strcmp($1, "3des") || !strcmp($1, "3des-cbc")) { $$.enc_alg = SADB_EALG_3DESCBC; $$.enc_key_len = 21; /* XXX verify */ } else if (!strcmp($1, "aes") || !strcmp($1, "aes-128-cbc")) { $$.enc_alg = SADB_X_EALG_AES; $$.enc_key_len = 16; } else { yyerror("unknown enc algorithm \"%s\"", $1); free($1); free($2); YYERROR; } free($1); if (strlen($2) / 2 != $$.enc_key_len) { yyerror("enc key length wrong: should be %u " "bytes, is %zu bytes", $$.enc_key_len * 2, strlen($2)); free($2); YYERROR; } if (str2key($2, $$.enc_key, sizeof($$.enc_key)) == -1) { free($2); YYERROR; } free($2); } ; filterrule : action quick filter_rib_h direction filter_peer_h filter_match_h filter_set { struct filter_rule r; struct filter_rib_l *rb, *rbnext; bzero(&r, sizeof(r)); r.action = $1; r.quick = $2; r.dir = $4; if ($3) { if (r.dir != DIR_IN) { yyerror("rib only allowed on \"from\" " "rules."); for (rb = $3; rb != NULL; rb = rbnext) { rbnext = rb->next; free(rb); } YYERROR; } } if (expand_rule(&r, $3, $5, &$6, $7) == -1) YYERROR; } ; action : ALLOW { $$ = ACTION_ALLOW; } | DENY { $$ = ACTION_DENY; } | MATCH { $$ = ACTION_NONE; } ; quick : /* empty */ { $$ = 0; } | QUICK { $$ = 1; } ; direction : FROM { $$ = DIR_IN; } | TO { $$ = DIR_OUT; } ; filter_rib_h : /* empty */ { $$ = NULL; } | RIB filter_rib { $$ = $2; } | RIB '{' optnl filter_rib_l optnl '}' { $$ = $4; } filter_rib_l : filter_rib { $$ = $1; } | filter_rib_l comma filter_rib { $3->next = $1; $$ = $3; } ; filter_rib : STRING { if (!find_rib($1)) { yyerror("rib \"%s\" does not exist.", $1); free($1); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_rib_l))) == NULL) fatal(NULL); $$->next = NULL; if (strlcpy($$->name, $1, sizeof($$->name)) >= sizeof($$->name)) { yyerror("rib name \"%s\" too long: " "max %zu", $1, sizeof($$->name) - 1); free($1); free($$); YYERROR; } free($1); } ; filter_peer_h : filter_peer | '{' optnl filter_peer_l optnl '}' { $$ = $3; } ; filter_peer_l : filter_peer { $$ = $1; } | filter_peer_l comma filter_peer { $3->next = $1; $$ = $3; } ; filter_peer : ANY { if (($$ = calloc(1, sizeof(struct filter_peers_l))) == NULL) fatal(NULL); $$->p.peerid = $$->p.groupid = 0; $$->next = NULL; } | address { struct peer *p; if (($$ = calloc(1, sizeof(struct filter_peers_l))) == NULL) fatal(NULL); $$->p.remote_as = $$->p.groupid = $$->p.peerid = 0; $$->next = NULL; RB_FOREACH(p, peer_head, new_peers) if (!memcmp(&p->conf.remote_addr, &$1, sizeof(p->conf.remote_addr))) { $$->p.peerid = p->conf.id; break; } if ($$->p.peerid == 0) { yyerror("no such peer: %s", log_addr(&$1)); free($$); YYERROR; } } | AS as4number { if (($$ = calloc(1, sizeof(struct filter_peers_l))) == NULL) fatal(NULL); $$->p.groupid = $$->p.peerid = 0; $$->p.remote_as = $2; } | GROUP STRING { struct peer *p; if (($$ = calloc(1, sizeof(struct filter_peers_l))) == NULL) fatal(NULL); $$->p.remote_as = $$->p.peerid = 0; $$->next = NULL; RB_FOREACH(p, peer_head, new_peers) if (!strcmp(p->conf.group, $2)) { $$->p.groupid = p->conf.groupid; break; } if ($$->p.groupid == 0) { yyerror("no such group: \"%s\"", $2); free($2); free($$); YYERROR; } free($2); } | EBGP { if (($$ = calloc(1, sizeof(struct filter_peers_l))) == NULL) fatal(NULL); $$->p.ebgp = 1; } | IBGP { if (($$ = calloc(1, sizeof(struct filter_peers_l))) == NULL) fatal(NULL); $$->p.ibgp = 1; } ; filter_prefix_h : IPV4 prefixlenop { if ($2.op == OP_NONE) { $2.op = OP_RANGE; $2.len_min = 0; $2.len_max = -1; } if (($$ = calloc(1, sizeof(struct filter_prefix_l))) == NULL) fatal(NULL); $$->p.addr.aid = AID_INET; if (merge_prefixspec(&$$->p, &$2) == -1) { free($$); YYERROR; } } | IPV6 prefixlenop { if ($2.op == OP_NONE) { $2.op = OP_RANGE; $2.len_min = 0; $2.len_max = -1; } if (($$ = calloc(1, sizeof(struct filter_prefix_l))) == NULL) fatal(NULL); $$->p.addr.aid = AID_INET6; if (merge_prefixspec(&$$->p, &$2) == -1) { free($$); YYERROR; } } | PREFIX filter_prefix { $$ = $2; } | PREFIX '{' filter_prefix_m '}' { $$ = $3; } ; filter_prefix_m : filter_prefix_l | '{' filter_prefix_l '}' { $$ = $2; } | '{' filter_prefix_l '}' filter_prefix_m { struct filter_prefix_l *p; /* merge, both can be lists */ for (p = $2; p != NULL && p->next != NULL; p = p->next) ; /* nothing */ if (p != NULL) p->next = $4; $$ = $2; } filter_prefix_l : filter_prefix { $$ = $1; } | filter_prefix_l comma filter_prefix { $3->next = $1; $$ = $3; } ; filter_prefix : prefix prefixlenop { if (($$ = calloc(1, sizeof(struct filter_prefix_l))) == NULL) fatal(NULL); memcpy(&$$->p.addr, &$1.prefix, sizeof($$->p.addr)); $$->p.len = $1.len; if (merge_prefixspec(&$$->p, &$2) == -1) { free($$); YYERROR; } } ; filter_as_h : filter_as_t | '{' filter_as_t_l '}' { $$ = $2; } ; filter_as_t_l : filter_as_t | filter_as_t_l comma filter_as_t { struct filter_as_l *a; /* merge, both can be lists */ for (a = $1; a != NULL && a->next != NULL; a = a->next) ; /* nothing */ if (a != NULL) a->next = $3; $$ = $1; } ; filter_as_t : filter_as_type filter_as { $$ = $2; $$->a.type = $1; } | filter_as_type '{' filter_as_l_h '}' { struct filter_as_l *a; $$ = $3; for (a = $$; a != NULL; a = a->next) a->a.type = $1; } | filter_as_type ASSET STRING { if (as_sets_lookup(&conf->as_sets, $3) == NULL) { yyerror("as-set \"%s\" not defined", $3); free($3); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_as_l))) == NULL) fatal(NULL); $$->a.type = $1; $$->a.flags = AS_FLAG_AS_SET_NAME; if (strlcpy($$->a.name, $3, sizeof($$->a.name)) >= sizeof($$->a.name)) { yyerror("as-set name \"%s\" too long: " "max %zu", $3, sizeof($$->a.name) - 1); free($3); free($$); YYERROR; } free($3); } ; filter_as_l_h : filter_as_l | '{' filter_as_l '}' { $$ = $2; } | '{' filter_as_l '}' filter_as_l_h { struct filter_as_l *a; /* merge, both can be lists */ for (a = $2; a != NULL && a->next != NULL; a = a->next) ; /* nothing */ if (a != NULL) a->next = $4; $$ = $2; } ; filter_as_l : filter_as | filter_as_l comma filter_as { $3->next = $1; $$ = $3; } ; filter_as : as4number_any { if (($$ = calloc(1, sizeof(struct filter_as_l))) == NULL) fatal(NULL); $$->a.as_min = $1; $$->a.as_max = $1; $$->a.op = OP_EQ; } | NEIGHBORAS { if (($$ = calloc(1, sizeof(struct filter_as_l))) == NULL) fatal(NULL); $$->a.flags = AS_FLAG_NEIGHBORAS; } | equalityop as4number_any { if (($$ = calloc(1, sizeof(struct filter_as_l))) == NULL) fatal(NULL); $$->a.op = $1; $$->a.as_min = $2; $$->a.as_max = $2; } | as4number_any binaryop as4number_any { if (($$ = calloc(1, sizeof(struct filter_as_l))) == NULL) fatal(NULL); if ($1 >= $3) { yyerror("start AS is bigger than end"); YYERROR; } $$->a.op = $2; $$->a.as_min = $1; $$->a.as_max = $3; } ; filter_match_h : /* empty */ { bzero(&$$, sizeof($$)); } | { bzero(&fmopts, sizeof(fmopts)); } filter_match { memcpy(&$$, &fmopts, sizeof($$)); } ; filter_match : filter_elm | filter_match filter_elm ; filter_elm : filter_prefix_h { if (fmopts.prefix_l != NULL) { yyerror("\"prefix\" already specified"); YYERROR; } if (fmopts.m.prefixset.name[0] != '\0') { yyerror("\"prefix-set\" already specified, " "cannot be used with \"prefix\" in the " "same filter rule"); YYERROR; } fmopts.prefix_l = $1; } | filter_as_h { if (fmopts.as_l != NULL) { yyerror("AS filters already specified"); YYERROR; } fmopts.as_l = $1; } | MAXASLEN NUMBER { if (fmopts.m.aslen.type != ASLEN_NONE) { yyerror("AS length filters already specified"); YYERROR; } if ($2 < 0 || $2 > UINT_MAX) { yyerror("bad max-as-len %lld", $2); YYERROR; } fmopts.m.aslen.type = ASLEN_MAX; fmopts.m.aslen.aslen = $2; } | MAXASSEQ NUMBER { if (fmopts.m.aslen.type != ASLEN_NONE) { yyerror("AS length filters already specified"); YYERROR; } if ($2 < 0 || $2 > UINT_MAX) { yyerror("bad max-as-seq %lld", $2); YYERROR; } fmopts.m.aslen.type = ASLEN_SEQ; fmopts.m.aslen.aslen = $2; } | community STRING { int i; for (i = 0; i < MAX_COMM_MATCH; i++) { if (fmopts.m.community[i].flags == 0) break; } if (i >= MAX_COMM_MATCH) { yyerror("too many \"community\" filters " "specified"); free($2); YYERROR; } if (parsecommunity(&fmopts.m.community[i], $1, $2) == -1) { free($2); YYERROR; } free($2); } | EXTCOMMUNITY STRING STRING { int i; for (i = 0; i < MAX_COMM_MATCH; i++) { if (fmopts.m.community[i].flags == 0) break; } if (i >= MAX_COMM_MATCH) { yyerror("too many \"community\" filters " "specified"); free($2); free($3); YYERROR; } if (parseextcommunity(&fmopts.m.community[i], $2, $3) == -1) { free($2); free($3); YYERROR; } free($2); free($3); } | EXTCOMMUNITY OVS STRING { int i; for (i = 0; i < MAX_COMM_MATCH; i++) { if (fmopts.m.community[i].flags == 0) break; } if (i >= MAX_COMM_MATCH) { yyerror("too many \"community\" filters " "specified"); free($3); YYERROR; } if (parseextcommunity(&fmopts.m.community[i], "ovs", $3) == -1) { free($3); YYERROR; } free($3); } | NEXTHOP address { if (fmopts.m.nexthop.flags) { yyerror("nexthop already specified"); YYERROR; } fmopts.m.nexthop.addr = $2; fmopts.m.nexthop.flags = FILTER_NEXTHOP_ADDR; } | NEXTHOP NEIGHBOR { if (fmopts.m.nexthop.flags) { yyerror("nexthop already specified"); YYERROR; } fmopts.m.nexthop.flags = FILTER_NEXTHOP_NEIGHBOR; } | PREFIXSET STRING prefixlenop { struct prefixset *ps; if (fmopts.prefix_l != NULL) { yyerror("\"prefix\" already specified, cannot " "be used with \"prefix-set\" in the same " "filter rule"); free($2); YYERROR; } if (fmopts.m.prefixset.name[0] != '\0') { yyerror("prefix-set filter already specified"); free($2); YYERROR; } if ((ps = find_prefixset($2, &conf->prefixsets)) == NULL) { yyerror("prefix-set '%s' not defined", $2); free($2); YYERROR; } if (strlcpy(fmopts.m.prefixset.name, $2, sizeof(fmopts.m.prefixset.name)) >= sizeof(fmopts.m.prefixset.name)) { yyerror("prefix-set name too long"); free($2); YYERROR; } if (!($3.op == OP_NONE || ($3.op == OP_RANGE && $3.len_min == -1 && $3.len_max == -1))) { yyerror("prefix-sets can only use option " "or-longer"); free($2); YYERROR; } if ($3.op == OP_RANGE && ps->sflags & PREFIXSET_FLAG_OPS) { yyerror("prefix-set %s contains prefixlen " "operators and cannot be used with an " "or-longer filter", $2); free($2); YYERROR; } if ($3.op == OP_RANGE && $3.len_min == -1 && $3.len_min == -1) fmopts.m.prefixset.flags |= PREFIXSET_FLAG_LONGER; fmopts.m.prefixset.flags |= PREFIXSET_FLAG_FILTER; free($2); } | ORIGINSET STRING { if (fmopts.m.originset.name[0] != '\0') { yyerror("origin-set filter already specified"); free($2); YYERROR; } if (find_prefixset($2, &conf->originsets) == NULL) { yyerror("origin-set '%s' not defined", $2); free($2); YYERROR; } if (strlcpy(fmopts.m.originset.name, $2, sizeof(fmopts.m.originset.name)) >= sizeof(fmopts.m.originset.name)) { yyerror("origin-set name too long"); free($2); YYERROR; } free($2); } | OVS validity { if (fmopts.m.ovs.is_set) { yyerror("ovs filter already specified"); YYERROR; } fmopts.m.ovs.validity = $2; fmopts.m.ovs.is_set = 1; } ; prefixlenop : /* empty */ { bzero(&$$, sizeof($$)); } | LONGER { bzero(&$$, sizeof($$)); $$.op = OP_RANGE; $$.len_min = -1; $$.len_max = -1; } | MAXLEN NUMBER { bzero(&$$, sizeof($$)); if ($2 < 0 || $2 > 128) { yyerror("prefixlen must be >= 0 and <= 128"); YYERROR; } $$.op = OP_RANGE; $$.len_min = -1; $$.len_max = $2; } | PREFIXLEN unaryop NUMBER { int min, max; bzero(&$$, sizeof($$)); if ($3 < 0 || $3 > 128) { yyerror("prefixlen must be >= 0 and <= 128"); YYERROR; } /* * convert the unary operation into the equivalent * range check */ $$.op = OP_RANGE; switch ($2) { case OP_NE: $$.op = $2; case OP_EQ: min = max = $3; break; case OP_LT: if ($3 == 0) { yyerror("prefixlen must be > 0"); YYERROR; } $3 -= 1; case OP_LE: min = -1; max = $3; break; case OP_GT: $3 += 1; case OP_GE: min = $3; max = -1; break; default: yyerror("unknown prefixlen operation"); YYERROR; } $$.len_min = min; $$.len_max = max; } | PREFIXLEN NUMBER binaryop NUMBER { bzero(&$$, sizeof($$)); if ($2 < 0 || $2 > 128 || $4 < 0 || $4 > 128) { yyerror("prefixlen must be < 128"); YYERROR; } if ($2 > $4) { yyerror("start prefixlen is bigger than end"); YYERROR; } $$.op = $3; $$.len_min = $2; $$.len_max = $4; } ; filter_as_type : AS { $$ = AS_ALL; } | SOURCEAS { $$ = AS_SOURCE; } | TRANSITAS { $$ = AS_TRANSIT; } | PEERAS { $$ = AS_PEER; } ; filter_set : /* empty */ { $$ = NULL; } | SET filter_set_opt { if (($$ = calloc(1, sizeof(struct filter_set_head))) == NULL) fatal(NULL); TAILQ_INIT($$); TAILQ_INSERT_TAIL($$, $2, entry); } | SET '{' optnl filter_set_l optnl '}' { $$ = $4; } ; filter_set_l : filter_set_l comma filter_set_opt { $$ = $1; if (merge_filterset($$, $3) == 1) YYERROR; } | filter_set_opt { if (($$ = calloc(1, sizeof(struct filter_set_head))) == NULL) fatal(NULL); TAILQ_INIT($$); TAILQ_INSERT_TAIL($$, $1, entry); } ; community : COMMUNITY { $$ = COMMUNITY_TYPE_BASIC; } | LARGECOMMUNITY { $$ = COMMUNITY_TYPE_LARGE; } ; delete : /* empty */ { $$ = 0; } | DELETE { $$ = 1; } ; filter_set_opt : LOCALPREF NUMBER { if ($2 < -INT_MAX || $2 > UINT_MAX) { yyerror("bad localpref %lld", $2); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); if ($2 >= 0) { $$->type = ACTION_SET_LOCALPREF; $$->action.metric = $2; } else { $$->type = ACTION_SET_RELATIVE_LOCALPREF; $$->action.relative = $2; } } | LOCALPREF '+' NUMBER { if ($3 < 0 || $3 > INT_MAX) { yyerror("bad localpref +%lld", $3); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_RELATIVE_LOCALPREF; $$->action.relative = $3; } | LOCALPREF '-' NUMBER { if ($3 < 0 || $3 > INT_MAX) { yyerror("bad localpref -%lld", $3); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_RELATIVE_LOCALPREF; $$->action.relative = -$3; } | MED NUMBER { if ($2 < -INT_MAX || $2 > UINT_MAX) { yyerror("bad metric %lld", $2); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); if ($2 >= 0) { $$->type = ACTION_SET_MED; $$->action.metric = $2; } else { $$->type = ACTION_SET_RELATIVE_MED; $$->action.relative = $2; } } | MED '+' NUMBER { if ($3 < 0 || $3 > INT_MAX) { yyerror("bad metric +%lld", $3); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_RELATIVE_MED; $$->action.relative = $3; } | MED '-' NUMBER { if ($3 < 0 || $3 > INT_MAX) { yyerror("bad metric -%lld", $3); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_RELATIVE_MED; $$->action.relative = -$3; } | METRIC NUMBER { /* alias for MED */ if ($2 < -INT_MAX || $2 > UINT_MAX) { yyerror("bad metric %lld", $2); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); if ($2 >= 0) { $$->type = ACTION_SET_MED; $$->action.metric = $2; } else { $$->type = ACTION_SET_RELATIVE_MED; $$->action.relative = $2; } } | METRIC '+' NUMBER { if ($3 < 0 || $3 > INT_MAX) { yyerror("bad metric +%lld", $3); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_RELATIVE_MED; $$->action.metric = $3; } | METRIC '-' NUMBER { if ($3 < 0 || $3 > INT_MAX) { yyerror("bad metric -%lld", $3); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_RELATIVE_MED; $$->action.relative = -$3; } | WEIGHT NUMBER { if ($2 < -INT_MAX || $2 > UINT_MAX) { yyerror("bad weight %lld", $2); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); if ($2 > 0) { $$->type = ACTION_SET_WEIGHT; $$->action.metric = $2; } else { $$->type = ACTION_SET_RELATIVE_WEIGHT; $$->action.relative = $2; } } | WEIGHT '+' NUMBER { if ($3 < 0 || $3 > INT_MAX) { yyerror("bad weight +%lld", $3); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_RELATIVE_WEIGHT; $$->action.relative = $3; } | WEIGHT '-' NUMBER { if ($3 < 0 || $3 > INT_MAX) { yyerror("bad weight -%lld", $3); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_RELATIVE_WEIGHT; $$->action.relative = -$3; } | NEXTHOP address { if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_NEXTHOP; memcpy(&$$->action.nexthop, &$2, sizeof($$->action.nexthop)); } | NEXTHOP BLACKHOLE { if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_NEXTHOP_BLACKHOLE; } | NEXTHOP REJECT { if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_NEXTHOP_REJECT; } | NEXTHOP NOMODIFY { if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_NEXTHOP_NOMODIFY; } | NEXTHOP SELF { if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_NEXTHOP_SELF; } | PREPEND_SELF NUMBER { if ($2 < 0 || $2 > 128) { yyerror("bad number of prepends"); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_PREPEND_SELF; $$->action.prepend = $2; } | PREPEND_PEER NUMBER { if ($2 < 0 || $2 > 128) { yyerror("bad number of prepends"); YYERROR; } if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_PREPEND_PEER; $$->action.prepend = $2; } | ASOVERRIDE { if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_AS_OVERRIDE; } | PFTABLE STRING { if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_PFTABLE; if (!(cmd_opts & BGPD_OPT_NOACTION) && pftable_exists($2) != 0) { yyerror("pftable name does not exist"); free($2); free($$); YYERROR; } if (strlcpy($$->action.pftable, $2, sizeof($$->action.pftable)) >= sizeof($$->action.pftable)) { yyerror("pftable name too long"); free($2); free($$); YYERROR; } if (pftable_add($2) != 0) { yyerror("Couldn't register table"); free($2); free($$); YYERROR; } free($2); } | RTLABEL STRING { if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_RTLABEL; if (strlcpy($$->action.rtlabel, $2, sizeof($$->action.rtlabel)) >= sizeof($$->action.rtlabel)) { yyerror("rtlabel name too long"); free($2); free($$); YYERROR; } free($2); } | community delete STRING { u_int8_t f1, f2, f3; if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); if ($2) $$->type = ACTION_DEL_COMMUNITY; else $$->type = ACTION_SET_COMMUNITY; if (parsecommunity(&$$->action.community, $1, $3) == -1) { free($3); free($$); YYERROR; } free($3); /* Don't allow setting of any match */ f1 = $$->action.community.flags >> 8; f2 = $$->action.community.flags >> 16; f3 = $$->action.community.flags >> 24; if (!$2 && (f1 == COMMUNITY_ANY || f2 == COMMUNITY_ANY || f3 == COMMUNITY_ANY)) { yyerror("'*' is not allowed in set community"); free($$); YYERROR; } } | EXTCOMMUNITY delete STRING STRING { if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); if ($2) $$->type = ACTION_DEL_COMMUNITY; else $$->type = ACTION_SET_COMMUNITY; if (parseextcommunity(&$$->action.community, $3, $4) == -1) { free($3); free($4); free($$); YYERROR; } free($3); free($4); } | EXTCOMMUNITY delete OVS STRING { if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); if ($2) $$->type = ACTION_DEL_COMMUNITY; else $$->type = ACTION_SET_COMMUNITY; if (parseextcommunity(&$$->action.community, "ovs", $4) == -1) { free($4); free($$); YYERROR; } free($4); } | ORIGIN origincode { if (($$ = calloc(1, sizeof(struct filter_set))) == NULL) fatal(NULL); $$->type = ACTION_SET_ORIGIN; $$->action.origin = $2; } ; origincode : STRING { if (!strcmp($1, "egp")) $$ = ORIGIN_EGP; else if (!strcmp($1, "igp")) $$ = ORIGIN_IGP; else if (!strcmp($1, "incomplete")) $$ = ORIGIN_INCOMPLETE; else { yyerror("unknown origin \"%s\"", $1); free($1); YYERROR; } free($1); }; validity : STRING { if (!strcmp($1, "not-found")) $$ = ROA_NOTFOUND; else if (!strcmp($1, "invalid")) $$ = ROA_INVALID; else if (!strcmp($1, "valid")) $$ = ROA_VALID; else { yyerror("unknown validity \"%s\"", $1); free($1); YYERROR; } free($1); }; optnl : /* empty */ | '\n' optnl ; comma : /* empty */ | ',' | '\n' optnl | ',' '\n' optnl ; unaryop : '=' { $$ = OP_EQ; } | NE { $$ = OP_NE; } | LE { $$ = OP_LE; } | '<' { $$ = OP_LT; } | GE { $$ = OP_GE; } | '>' { $$ = OP_GT; } ; equalityop : '=' { $$ = OP_EQ; } | NE { $$ = OP_NE; } ; binaryop : '-' { $$ = OP_RANGE; } | XRANGE { $$ = OP_XRANGE; } ; %% struct keywords { const char *k_name; int k_val; }; int yyerror(const char *fmt, ...) { va_list ap; char *msg; file->errors++; va_start(ap, fmt); if (vasprintf(&msg, fmt, ap) == -1) fatalx("yyerror vasprintf"); va_end(ap); logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); free(msg); return (0); } int kw_cmp(const void *k, const void *e) { return (strcmp(k, ((const struct keywords *)e)->k_name)); } int lookup(char *s) { /* this has to be sorted always */ static const struct keywords keywords[] = { { "AS", AS}, { "IPv4", IPV4}, { "IPv6", IPV6}, { "ah", AH}, { "allow", ALLOW}, { "announce", ANNOUNCE}, { "any", ANY}, { "as-4byte", AS4BYTE }, { "as-override", ASOVERRIDE}, { "as-set", ASSET }, { "blackhole", BLACKHOLE}, { "capabilities", CAPABILITIES}, { "community", COMMUNITY}, { "compare", COMPARE}, { "connect-retry", CONNECTRETRY}, { "connected", CONNECTED}, { "default-route", DEFAULTROUTE}, { "delete", DELETE}, { "demote", DEMOTE}, { "deny", DENY}, { "depend", DEPEND}, { "descr", DESCR}, { "down", DOWN}, { "dump", DUMP}, { "ebgp", EBGP}, { "enforce", ENFORCE}, { "esp", ESP}, { "evaluate", EVALUATE}, { "export", EXPORT}, { "export-target", EXPORTTRGT}, { "ext-community", EXTCOMMUNITY}, { "fib-priority", FIBPRIORITY}, { "fib-update", FIBUPDATE}, { "from", FROM}, { "group", GROUP}, { "holdtime", HOLDTIME}, { "ibgp", IBGP}, { "ignore", IGNORE}, { "ike", IKE}, { "import-target", IMPORTTRGT}, { "in", IN}, { "include", INCLUDE}, { "inet", IPV4}, { "inet6", IPV6}, { "ipsec", IPSEC}, { "key", KEY}, { "large-community", LARGECOMMUNITY}, { "listen", LISTEN}, { "local-address", LOCALADDR}, { "local-as", LOCALAS}, { "localpref", LOCALPREF}, { "log", LOG}, { "match", MATCH}, { "max-as-len", MAXASLEN}, { "max-as-seq", MAXASSEQ}, { "max-prefix", MAXPREFIX}, { "maxlen", MAXLEN}, { "md5sig", MD5SIG}, { "med", MED}, { "metric", METRIC}, { "min", YMIN}, { "multihop", MULTIHOP}, { "neighbor", NEIGHBOR}, { "neighbor-as", NEIGHBORAS}, { "network", NETWORK}, { "nexthop", NEXTHOP}, { "no-modify", NOMODIFY}, { "none", NONE}, { "on", ON}, { "or-longer", LONGER}, { "origin", ORIGIN}, { "origin-set", ORIGINSET}, { "out", OUT}, { "ovs", OVS}, { "passive", PASSIVE}, { "password", PASSWORD}, { "peer-as", PEERAS}, { "pftable", PFTABLE}, { "prefix", PREFIX}, { "prefix-set", PREFIXSET}, { "prefixlen", PREFIXLEN}, { "prepend-neighbor", PREPEND_PEER}, { "prepend-self", PREPEND_SELF}, { "priority", PRIORITY}, { "qualify", QUALIFY}, { "quick", QUICK}, { "rd", RD}, { "rde", RDE}, { "refresh", REFRESH }, { "reject", REJECT}, { "remote-as", REMOTEAS}, { "restart", RESTART}, { "restricted", RESTRICTED}, { "rib", RIB}, { "roa-set", ROASET }, { "route-reflector", REFLECTOR}, { "router-id", ROUTERID}, { "rtable", RTABLE}, { "rtlabel", RTLABEL}, { "self", SELF}, { "set", SET}, { "socket", SOCKET }, { "source-as", SOURCEAS}, { "spi", SPI}, { "static", STATIC}, { "tcp", TCP}, { "to", TO}, { "transit-as", TRANSITAS}, { "transparent-as", TRANSPARENT}, { "ttl-security", TTLSECURITY}, { "unicast", UNICAST}, { "via", VIA}, { "vpn", VPN}, { "weight", WEIGHT} }; const struct keywords *p; p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), sizeof(keywords[0]), kw_cmp); if (p) return (p->k_val); else return (STRING); } #define START_EXPAND 1 #define DONE_EXPAND 2 static int expanding; int igetc(void) { int c; while (1) { if (file->ungetpos > 0) c = file->ungetbuf[--file->ungetpos]; else c = getc(file->stream); if (c == START_EXPAND) expanding = 1; else if (c == DONE_EXPAND) expanding = 0; else break; } return (c); } int lgetc(int quotec) { int c, next; if (quotec) { if ((c = igetc()) == EOF) { yyerror("reached end of file while parsing " "quoted string"); if (file == topfile || popfile() == EOF) return (EOF); return (quotec); } return (c); } while ((c = igetc()) == '\\') { next = igetc(); if (next != '\n') { c = next; break; } yylval.lineno = file->lineno; file->lineno++; } if (c == EOF) { /* * Fake EOL when hit EOF for the first time. This gets line * count right if last line in included file is syntactically * invalid and has no newline. */ if (file->eof_reached == 0) { file->eof_reached = 1; return ('\n'); } while (c == EOF) { if (file == topfile || popfile() == EOF) return (EOF); c = igetc(); } } return (c); } void lungetc(int c) { if (c == EOF) return; if (file->ungetpos >= file->ungetsize) { void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); if (p == NULL) err(1, "lungetc"); file->ungetbuf = p; file->ungetsize *= 2; } file->ungetbuf[file->ungetpos++] = c; } int findeol(void) { int c; /* skip to either EOF or the first real EOL */ while (1) { c = lgetc(0); if (c == '\n') { file->lineno++; break; } if (c == EOF) break; } return (ERROR); } int yylex(void) { u_char buf[8096]; u_char *p, *val; int quotec, next, c; int token; top: p = buf; while ((c = lgetc(0)) == ' ' || c == '\t') ; /* nothing */ yylval.lineno = file->lineno; if (c == '#') while ((c = lgetc(0)) != '\n' && c != EOF) ; /* nothing */ if (c == '$' && !expanding) { while (1) { if ((c = lgetc(0)) == EOF) return (0); if (p + 1 >= buf + sizeof(buf) - 1) { yyerror("string too long"); return (findeol()); } if (isalnum(c) || c == '_') { *p++ = c; continue; } *p = '\0'; lungetc(c); break; } val = symget(buf); if (val == NULL) { yyerror("macro '%s' not defined", buf); return (findeol()); } p = val + strlen(val) - 1; lungetc(DONE_EXPAND); while (p >= val) { lungetc(*p); p--; } lungetc(START_EXPAND); goto top; } switch (c) { case '\'': case '"': quotec = c; while (1) { if ((c = lgetc(quotec)) == EOF) return (0); if (c == '\n') { file->lineno++; continue; } else if (c == '\\') { if ((next = lgetc(quotec)) == EOF) return (0); if (next == quotec || next == ' ' || next == '\t') c = next; else if (next == '\n') { file->lineno++; continue; } else lungetc(next); } else if (c == quotec) { *p = '\0'; break; } else if (c == '\0') { yyerror("syntax error: unterminated quote"); return (findeol()); } if (p + 1 >= buf + sizeof(buf) - 1) { yyerror("string too long"); return (findeol()); } *p++ = c; } yylval.v.string = strdup(buf); if (yylval.v.string == NULL) fatal("yylex: strdup"); return (STRING); case '!': next = lgetc(0); if (next == '=') return (NE); lungetc(next); break; case '<': next = lgetc(0); if (next == '=') return (LE); lungetc(next); break; case '>': next = lgetc(0); if (next == '<') return (XRANGE); else if (next == '=') return (GE); lungetc(next); break; } #define allowed_to_end_number(x) \ (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') if (c == '-' || isdigit(c)) { do { *p++ = c; if ((size_t)(p-buf) >= sizeof(buf)) { yyerror("string too long"); return (findeol()); } } while ((c = lgetc(0)) != EOF && isdigit(c)); lungetc(c); if (p == buf + 1 && buf[0] == '-') goto nodigits; if (c == EOF || allowed_to_end_number(c)) { const char *errstr = NULL; *p = '\0'; yylval.v.number = strtonum(buf, LLONG_MIN, LLONG_MAX, &errstr); if (errstr) { yyerror("\"%s\" invalid number: %s", buf, errstr); return (findeol()); } return (NUMBER); } else { nodigits: while (p > buf + 1) lungetc(*--p); c = *--p; if (c == '-') return (c); } } #define allowed_in_string(x) \ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ x != '{' && x != '}' && x != '<' && x != '>' && \ x != '!' && x != '=' && x != '/' && x != '#' && \ x != ',')) if (isalnum(c) || c == ':' || c == '_' || c == '*') { do { *p++ = c; if ((size_t)(p-buf) >= sizeof(buf)) { yyerror("string too long"); return (findeol()); } } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); lungetc(c); *p = '\0'; if ((token = lookup(buf)) == STRING) if ((yylval.v.string = strdup(buf)) == NULL) fatal("yylex: strdup"); return (token); } if (c == '\n') { yylval.lineno = file->lineno; file->lineno++; } if (c == EOF) return (0); return (c); } int check_file_secrecy(int fd, const char *fname) { struct stat st; if (fstat(fd, &st)) { log_warn("cannot stat %s", fname); return (-1); } return (0); } struct file * pushfile(const char *name, int secret) { struct file *nfile; if ((nfile = calloc(1, sizeof(struct file))) == NULL) { log_warn("%s", __func__); return (NULL); } if ((nfile->name = strdup(name)) == NULL) { log_warn("%s", __func__); free(nfile); return (NULL); } if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { log_warn("%s: %s", __func__, nfile->name); free(nfile->name); free(nfile); return (NULL); } if (secret && check_file_secrecy(fileno(nfile->stream), nfile->name)) { fclose(nfile->stream); free(nfile->name); free(nfile); return (NULL); } nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; nfile->ungetsize = 16; nfile->ungetbuf = malloc(nfile->ungetsize); if (nfile->ungetbuf == NULL) { log_warn("%s", __func__); fclose(nfile->stream); free(nfile->name); free(nfile); return (NULL); } TAILQ_INSERT_TAIL(&files, nfile, entry); return (nfile); } int popfile(void) { struct file *prev; if ((prev = TAILQ_PREV(file, files, entry)) != NULL) prev->errors += file->errors; TAILQ_REMOVE(&files, file, entry); fclose(file->stream); free(file->name); free(file->ungetbuf); free(file); file = prev; return (file ? 0 : EOF); } static void init_config(struct bgpd_config *c) { u_int rdomid; c->min_holdtime = MIN_HOLDTIME; c->holdtime = INTERVAL_HOLD; c->connectretry = INTERVAL_CONNECTRETRY; c->bgpid = get_bgpid(); c->fib_priority = RTP_BGP; c->default_tableid = getrtable(); ktable_exists(c->default_tableid, &rdomid); if (rdomid != c->default_tableid) fatalx("current routing table %u is not a routing domain", c->default_tableid); if (asprintf(&c->csock, "%s.%d", SOCKET_NAME, c->default_tableid) == -1) fatal(NULL); } struct bgpd_config * parse_config(char *filename, struct peer_head *ph) { struct sym *sym, *next; struct rde_rib *rr; struct network *n; int errors = 0; conf = new_config(); init_config(conf); if ((filter_l = calloc(1, sizeof(struct filter_head))) == NULL) fatal(NULL); if ((peerfilter_l = calloc(1, sizeof(struct filter_head))) == NULL) fatal(NULL); if ((groupfilter_l = calloc(1, sizeof(struct filter_head))) == NULL) fatal(NULL); TAILQ_INIT(filter_l); TAILQ_INIT(peerfilter_l); TAILQ_INIT(groupfilter_l); curpeer = NULL; curgroup = NULL; cur_peers = ph; new_peers = &conf->peers; netconf = &conf->networks; if ((rr = add_rib("Adj-RIB-In")) == NULL) fatal("add_rib failed"); rr->flags = F_RIB_NOFIB | F_RIB_NOEVALUATE; if ((rr = add_rib("Loc-RIB")) == NULL) fatal("add_rib failed"); rib_add_fib(rr, conf->default_tableid); rr->flags = F_RIB_LOCAL; if ((file = pushfile(filename, 1)) == NULL) goto errors; topfile = file; yyparse(); errors = file->errors; popfile(); /* check that we dont try to announce our own routes */ TAILQ_FOREACH(n, netconf, entry) if (n->net.priority == conf->fib_priority) { errors++; logit(LOG_CRIT, "network priority %d == fib-priority " "%d is not allowed.", n->net.priority, conf->fib_priority); } /* Free macros and check which have not been used. */ TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { if ((cmd_opts & BGPD_OPT_VERBOSE2) && !sym->used) fprintf(stderr, "warning: macro \"%s\" not " "used\n", sym->nam); if (!sym->persist) { free(sym->nam); free(sym->val); TAILQ_REMOVE(&symhead, sym, entry); free(sym); } } if (!conf->as) { log_warnx("configuration error: AS not given"); errors++; } /* clear the globals */ curpeer = NULL; curgroup = NULL; cur_peers = NULL; new_peers = NULL; netconf = NULL; if (errors) { errors: while ((rr = SIMPLEQ_FIRST(&ribnames)) != NULL) { SIMPLEQ_REMOVE_HEAD(&ribnames, entry); free(rr); } filterlist_free(filter_l); filterlist_free(peerfilter_l); filterlist_free(groupfilter_l); free_config(conf); return (NULL); } else { /* update clusterid in case it was not set explicitly */ if ((conf->flags & BGPD_FLAG_REFLECTOR) && conf->clusterid == 0) conf->clusterid = conf->bgpid; /* * Concatenate filter list and static group and peer filtersets * together. Static group sets come first then peer sets * last normal filter rules. */ TAILQ_CONCAT(conf->filters, groupfilter_l, entry); TAILQ_CONCAT(conf->filters, peerfilter_l, entry); TAILQ_CONCAT(conf->filters, filter_l, entry); optimize_filters(conf->filters); free(filter_l); free(peerfilter_l); free(groupfilter_l); return (conf); } } int symset(const char *nam, const char *val, int persist) { struct sym *sym; TAILQ_FOREACH(sym, &symhead, entry) { if (strcmp(nam, sym->nam) == 0) break; } if (sym != NULL) { if (sym->persist == 1) return (0); else { free(sym->nam); free(sym->val); TAILQ_REMOVE(&symhead, sym, entry); free(sym); } } if ((sym = calloc(1, sizeof(*sym))) == NULL) return (-1); sym->nam = strdup(nam); if (sym->nam == NULL) { free(sym); return (-1); } sym->val = strdup(val); if (sym->val == NULL) { free(sym->nam); free(sym); return (-1); } sym->used = 0; sym->persist = persist; TAILQ_INSERT_TAIL(&symhead, sym, entry); return (0); } int cmdline_symset(char *s) { char *sym, *val; int ret; if ((val = strrchr(s, '=')) == NULL) return (-1); sym = strndup(s, val - s); if (sym == NULL) fatal("%s: strndup", __func__); ret = symset(sym, val + 1, 1); free(sym); return (ret); } char * symget(const char *nam) { struct sym *sym; TAILQ_FOREACH(sym, &symhead, entry) { if (strcmp(nam, sym->nam) == 0) { sym->used = 1; return (sym->val); } } return (NULL); } static int getcommunity(char *s, int large, u_int32_t *val, u_int32_t *flag) { long long max = USHRT_MAX; const char *errstr; *flag = 0; *val = 0; if (strcmp(s, "*") == 0) { *flag = COMMUNITY_ANY; return 0; } else if (strcmp(s, "neighbor-as") == 0) { *flag = COMMUNITY_NEIGHBOR_AS; return 0; } else if (strcmp(s, "local-as") == 0) { *flag = COMMUNITY_LOCAL_AS; return 0; } if (large) max = UINT_MAX; *val = strtonum(s, 0, max, &errstr); if (errstr) { yyerror("Community %s is %s (max: %lld)", s, errstr, max); return -1; } return 0; } static void setcommunity(struct community *c, u_int32_t as, u_int32_t data, u_int32_t asflag, u_int32_t dataflag) { c->flags = COMMUNITY_TYPE_BASIC; c->flags |= asflag << 8; c->flags |= dataflag << 16; c->data1 = as; c->data2 = data; c->data3 = 0; } static int parselargecommunity(struct community *c, char *s) { char *p, *q; u_int32_t dflag1, dflag2, dflag3; if ((p = strchr(s, ':')) == NULL) { yyerror("Bad community syntax"); return (-1); } *p++ = 0; if ((q = strchr(p, ':')) == NULL) { yyerror("Bad community syntax"); return (-1); } *q++ = 0; if (getcommunity(s, 1, &c->data1, &dflag1) == -1 || getcommunity(p, 1, &c->data2, &dflag2) == -1 || getcommunity(q, 1, &c->data3, &dflag3) == -1) return (-1); c->flags = COMMUNITY_TYPE_LARGE; c->flags |= dflag1 << 8;; c->flags |= dflag2 << 16;; c->flags |= dflag3 << 24;; return (0); } int parsecommunity(struct community *c, int type, char *s) { char *p; u_int32_t as, data, asflag, dataflag; if (type == COMMUNITY_TYPE_LARGE) return parselargecommunity(c, s); /* Well-known communities */ if (strcasecmp(s, "GRACEFUL_SHUTDOWN") == 0) { setcommunity(c, COMMUNITY_WELLKNOWN, COMMUNITY_GRACEFUL_SHUTDOWN, 0, 0); return (0); } else if (strcasecmp(s, "NO_EXPORT") == 0) { setcommunity(c, COMMUNITY_WELLKNOWN, COMMUNITY_NO_EXPORT, 0, 0); return (0); } else if (strcasecmp(s, "NO_ADVERTISE") == 0) { setcommunity(c, COMMUNITY_WELLKNOWN, COMMUNITY_NO_ADVERTISE, 0, 0); return (0); } else if (strcasecmp(s, "NO_EXPORT_SUBCONFED") == 0) { setcommunity(c, COMMUNITY_WELLKNOWN, COMMUNITY_NO_EXPSUBCONFED, 0, 0); return (0); } else if (strcasecmp(s, "NO_PEER") == 0) { setcommunity(c, COMMUNITY_WELLKNOWN, COMMUNITY_NO_PEER, 0, 0); return (0); } else if (strcasecmp(s, "BLACKHOLE") == 0) { setcommunity(c, COMMUNITY_WELLKNOWN, COMMUNITY_BLACKHOLE, 0, 0); return (0); } if ((p = strchr(s, ':')) == NULL) { yyerror("Bad community syntax"); return (-1); } *p++ = 0; if (getcommunity(s, 0, &as, &asflag) == -1 || getcommunity(p, 0, &data, &dataflag) == -1) return (-1); setcommunity(c, as, data, asflag, dataflag); return (0); } static int parsesubtype(char *name, int *type, int *subtype) { const struct ext_comm_pairs *cp; int found = 0; for (cp = iana_ext_comms; cp->subname != NULL; cp++) { if (strcmp(name, cp->subname) == 0) { if (found == 0) { *type = cp->type; *subtype = cp->subtype; } found++; } } if (found > 1) *type = -1; return (found); } static int parseextvalue(int type, char *s, u_int32_t *v, u_int32_t *flag) { const char *errstr; char *p; struct in_addr ip; u_int32_t uvalh, uval; if (type != -1) { /* nothing */ } else if (strcmp(s, "neighbor-as") == 0) { *flag = COMMUNITY_NEIGHBOR_AS; *v = 0; return EXT_COMMUNITY_TRANS_FOUR_AS; } else if (strcmp(s, "local-as") == 0) { *flag = COMMUNITY_LOCAL_AS; *v = 0; return EXT_COMMUNITY_TRANS_FOUR_AS; } else if ((p = strchr(s, '.')) == NULL) { /* AS_PLAIN number (4 or 2 byte) */ strtonum(s, 0, USHRT_MAX, &errstr); if (errstr == NULL) type = EXT_COMMUNITY_TRANS_TWO_AS; else type = EXT_COMMUNITY_TRANS_FOUR_AS; } else if (strchr(p + 1, '.') == NULL) { /* AS_DOT number (4-byte) */ type = EXT_COMMUNITY_TRANS_FOUR_AS; } else { /* more than one dot -> IP address */ type = EXT_COMMUNITY_TRANS_IPV4; } switch (type) { case EXT_COMMUNITY_TRANS_TWO_AS: uval = strtonum(s, 0, USHRT_MAX, &errstr); if (errstr) { yyerror("Bad ext-community %s is %s", s, errstr); return (-1); } *v = uval; break; case EXT_COMMUNITY_TRANS_FOUR_AS: if ((p = strchr(s, '.')) == NULL) { uval = strtonum(s, 0, UINT_MAX, &errstr); if (errstr) { yyerror("Bad ext-community %s is %s", s, errstr); return (-1); } *v = uval; break; } *p++ = '\0'; uvalh = strtonum(s, 0, USHRT_MAX, &errstr); if (errstr) { yyerror("Bad ext-community %s is %s", s, errstr); return (-1); } uval = strtonum(p, 0, USHRT_MAX, &errstr); if (errstr) { yyerror("Bad ext-community %s is %s", p, errstr); return (-1); } *v = uval | (uvalh << 16); break; case EXT_COMMUNITY_TRANS_IPV4: if (inet_aton(s, &ip) == 0) { yyerror("Bad ext-community %s not parseable", s); return (-1); } *v = ntohl(ip.s_addr); break; default: fatalx("%s: unexpected type %d", __func__, type); } return (type); } int parseextcommunity(struct community *c, char *t, char *s) { const struct ext_comm_pairs *cp; char *p, *ep; u_int64_t ullval; u_int32_t uval, uval2, dflag1 = 0, dflag2 = 0; int type = 0, subtype = 0; if (strcmp(t, "*") == 0 && strcmp(s, "*") == 0) { c->flags = COMMUNITY_TYPE_EXT; c->flags |= COMMUNITY_ANY << 24; return (0); } if (parsesubtype(t, &type, &subtype) == 0) { yyerror("Bad ext-community unknown type"); return (-1); } switch (type) { case EXT_COMMUNITY_TRANS_TWO_AS: case EXT_COMMUNITY_TRANS_FOUR_AS: case EXT_COMMUNITY_TRANS_IPV4: case -1: if (strcmp(s, "*") == 0) { dflag1 = COMMUNITY_ANY; break; } if ((p = strchr(s, ':')) == NULL) { yyerror("Bad ext-community %s", s); return (-1); } *p++ = '\0'; if ((type = parseextvalue(type, s, &uval, &dflag1)) == -1) return (-1); switch (type) { case EXT_COMMUNITY_TRANS_TWO_AS: if (getcommunity(p, 1, &uval2, &dflag2) == -1) return (-1); break; case EXT_COMMUNITY_TRANS_IPV4: case EXT_COMMUNITY_TRANS_FOUR_AS: if (getcommunity(p, 0, &uval2, &dflag2) == -1) return (-1); break; default: fatalx("parseextcommunity: unexpected result"); } c->data1 = uval; c->data2 = uval2; break; case EXT_COMMUNITY_TRANS_OPAQUE: case EXT_COMMUNITY_TRANS_EVPN: if (strcmp(s, "*") == 0) { dflag1 = COMMUNITY_ANY; break; } errno = 0; ullval = strtoull(s, &ep, 0); if (s[0] == '\0' || *ep != '\0') { yyerror("Bad ext-community bad value"); return (-1); } if (errno == ERANGE && ullval > EXT_COMMUNITY_OPAQUE_MAX) { yyerror("Bad ext-community value too big"); return (-1); } c->data1 = ullval >> 32; c->data2 = ullval; break; case EXT_COMMUNITY_NON_TRANS_OPAQUE: if (subtype == EXT_COMMUNITY_SUBTYPE_OVS) { if (strcmp(s, "valid") == 0) { c->data2 = EXT_COMMUNITY_OVS_VALID; break; } else if (strcmp(s, "invalid") == 0) { c->data2 = EXT_COMMUNITY_OVS_INVALID; break; } else if (strcmp(s, "not-found") == 0) { c->data2 = EXT_COMMUNITY_OVS_NOTFOUND; break; } else if (strcmp(s, "*") == 0) { dflag1 = COMMUNITY_ANY; break; } } yyerror("Bad ext-community %s", s); return (-1); } c->data3 = type << 8 | subtype; /* special handling of ext-community rt * since type is not known */ if (dflag1 == COMMUNITY_ANY && type == -1) { c->flags = COMMUNITY_TYPE_EXT; c->flags |= dflag1 << 8; return (0); } /* verify type/subtype combo */ for (cp = iana_ext_comms; cp->subname != NULL; cp++) { if (cp->type == type && cp->subtype == subtype) { c->flags = COMMUNITY_TYPE_EXT; c->flags |= dflag1 << 8; c->flags |= dflag2 << 16; return (0); } } yyerror("Bad ext-community bad format for type"); return (-1); } struct peer * alloc_peer(void) { struct peer *p; u_int8_t i; if ((p = calloc(1, sizeof(struct peer))) == NULL) fatal("new_peer"); /* some sane defaults */ p->state = STATE_NONE; p->reconf_action = RECONF_REINIT; p->conf.distance = 1; p->conf.export_type = EXPORT_UNSET; p->conf.announce_capa = 1; for (i = 0; i < AID_MAX; i++) p->conf.capabilities.mp[i] = 0; p->conf.capabilities.refresh = 1; p->conf.capabilities.grestart.restart = 1; p->conf.capabilities.as4byte = 1; p->conf.local_as = conf->as; p->conf.local_short_as = conf->short_as; if (conf->flags & BGPD_FLAG_DECISION_TRANS_AS) p->conf.flags |= PEERFLAG_TRANS_AS; return (p); } struct peer * new_peer(void) { struct peer *p; p = alloc_peer(); if (curgroup != NULL) { memcpy(p, curgroup, sizeof(struct peer)); if (strlcpy(p->conf.group, curgroup->conf.group, sizeof(p->conf.group)) >= sizeof(p->conf.group)) fatalx("new_peer group strlcpy"); if (strlcpy(p->conf.descr, curgroup->conf.descr, sizeof(p->conf.descr)) >= sizeof(p->conf.descr)) fatalx("new_peer descr strlcpy"); p->conf.groupid = curgroup->conf.id; p->conf.local_as = curgroup->conf.local_as; p->conf.local_short_as = curgroup->conf.local_short_as; } return (p); } struct peer * new_group(void) { return (alloc_peer()); } int add_mrtconfig(enum mrt_type type, char *name, int timeout, struct peer *p, char *rib) { struct mrt *m, *n; LIST_FOREACH(m, conf->mrt, entry) { if ((rib && strcmp(rib, m->rib)) || (!rib && *m->rib)) continue; if (p == NULL) { if (m->peer_id != 0 || m->group_id != 0) continue; } else { if (m->peer_id != p->conf.id || m->group_id != p->conf.groupid) continue; } if (m->type == type) { yyerror("only one mrtdump per type allowed."); return (-1); } } if ((n = calloc(1, sizeof(struct mrt_config))) == NULL) fatal("add_mrtconfig"); n->type = type; if (strlcpy(MRT2MC(n)->name, name, sizeof(MRT2MC(n)->name)) >= sizeof(MRT2MC(n)->name)) { yyerror("filename \"%s\" too long: max %zu", name, sizeof(MRT2MC(n)->name) - 1); free(n); return (-1); } MRT2MC(n)->ReopenTimerInterval = timeout; if (p != NULL) { if (curgroup == p) { n->peer_id = 0; n->group_id = p->conf.id; } else { n->peer_id = p->conf.id; n->group_id = 0; } } if (rib) { if (!find_rib(rib)) { yyerror("rib \"%s\" does not exist.", rib); free(n); return (-1); } if (strlcpy(n->rib, rib, sizeof(n->rib)) >= sizeof(n->rib)) { yyerror("rib name \"%s\" too long: max %zu", name, sizeof(n->rib) - 1); free(n); return (-1); } } LIST_INSERT_HEAD(conf->mrt, n, entry); return (0); } struct rde_rib * add_rib(char *name) { struct rde_rib *rr; if ((rr = find_rib(name)) == NULL) { if ((rr = calloc(1, sizeof(*rr))) == NULL) { log_warn("add_rib"); return (NULL); } if (strlcpy(rr->name, name, sizeof(rr->name)) >= sizeof(rr->name)) { yyerror("rib name \"%s\" too long: max %zu", name, sizeof(rr->name) - 1); free(rr); return (NULL); } rr->flags = F_RIB_NOFIB; SIMPLEQ_INSERT_TAIL(&ribnames, rr, entry); } return (rr); } struct rde_rib * find_rib(char *name) { struct rde_rib *rr; SIMPLEQ_FOREACH(rr, &ribnames, entry) { if (!strcmp(rr->name, name)) return (rr); } return (NULL); } int rib_add_fib(struct rde_rib *rr, u_int rtableid) { u_int rdom; if (ktable_exists(rtableid, &rdom) != 1) { yyerror("rtable id %u does not exist", rtableid); return (-1); } /* * conf->default_tableid is also a rdomain because that is checked * in init_config() */ if (rdom != conf->default_tableid) { log_warnx("rtable %u does not belong to rdomain %u", rtableid, conf->default_tableid); return (-1); } rr->rtableid = rtableid; rr->flags &= ~F_RIB_NOFIB; return (0); } struct prefixset * find_prefixset(char *name, struct prefixset_head *p) { struct prefixset *ps; SIMPLEQ_FOREACH(ps, p, entry) { if (!strcmp(ps->name, name)) return (ps); } return (NULL); } int get_id(struct peer *newpeer) { static u_int32_t id = PEER_ID_STATIC_MIN; struct peer *p = NULL; /* check if the peer already existed before */ if (newpeer->conf.remote_addr.aid) { /* neighbor */ if (cur_peers) RB_FOREACH(p, peer_head, cur_peers) if (memcmp(&p->conf.remote_addr, &newpeer->conf.remote_addr, sizeof(p->conf.remote_addr)) == 0) break; if (p) { newpeer->conf.id = p->conf.id; return (0); } } else { /* group */ if (cur_peers) RB_FOREACH(p, peer_head, cur_peers) if (strcmp(p->conf.group, newpeer->conf.group) == 0) break; if (p) { newpeer->conf.id = p->conf.groupid; return (0); } } /* else new one */ if (id < PEER_ID_STATIC_MAX) { newpeer->conf.id = id++; return (0); } return (-1); } int merge_prefixspec(struct filter_prefix *p, struct filter_prefixlen *pl) { u_int8_t max_len = 0; switch (p->addr.aid) { case AID_INET: case AID_VPN_IPv4: max_len = 32; break; case AID_INET6: case AID_VPN_IPv6: max_len = 128; break; } if (pl->op == OP_NONE) { p->len_min = p->len_max = p->len; return (0); } if (pl->len_min == -1) pl->len_min = p->len; if (pl->len_max == -1) pl->len_max = max_len; if (pl->len_max > max_len) { yyerror("prefixlen %d too big, limit %d", pl->len_max, max_len); return (-1); } if (pl->len_min > pl->len_max) { yyerror("prefixlen %d too big, limit %d", pl->len_min, pl->len_max); return (-1); } if (pl->len_min < p->len) { yyerror("prefixlen %d smaller than prefix, limit %d", pl->len_min, p->len); return (-1); } p->op = pl->op; p->len_min = pl->len_min; p->len_max = pl->len_max; return (0); } int expand_rule(struct filter_rule *rule, struct filter_rib_l *rib, struct filter_peers_l *peer, struct filter_match_l *match, struct filter_set_head *set) { struct filter_rule *r; struct filter_rib_l *rb, *rbnext; struct filter_peers_l *p, *pnext; struct filter_prefix_l *prefix, *prefix_next; struct filter_as_l *a, *anext; struct filter_set *s; rb = rib; do { p = peer; do { a = match->as_l; do { prefix = match->prefix_l; do { if ((r = calloc(1, sizeof(struct filter_rule))) == NULL) { log_warn("expand_rule"); return (-1); } memcpy(r, rule, sizeof(struct filter_rule)); memcpy(&r->match, match, sizeof(struct filter_match)); filterset_copy(set, &r->set); if (rb != NULL) strlcpy(r->rib, rb->name, sizeof(r->rib)); if (p != NULL) memcpy(&r->peer, &p->p, sizeof(struct filter_peers)); if (prefix != NULL) memcpy(&r->match.prefix, &prefix->p, sizeof(r->match.prefix)); if (a != NULL) memcpy(&r->match.as, &a->a, sizeof(struct filter_as)); TAILQ_INSERT_TAIL(filter_l, r, entry); if (prefix != NULL) prefix = prefix->next; } while (prefix != NULL); if (a != NULL) a = a->next; } while (a != NULL); if (p != NULL) p = p->next; } while (p != NULL); if (rb != NULL) rb = rb->next; } while (rb != NULL); for (rb = rib; rb != NULL; rb = rbnext) { rbnext = rb->next; free(rb); } for (p = peer; p != NULL; p = pnext) { pnext = p->next; free(p); } for (a = match->as_l; a != NULL; a = anext) { anext = a->next; free(a); } for (prefix = match->prefix_l; prefix != NULL; prefix = prefix_next) { prefix_next = prefix->next; free(prefix); } if (set != NULL) { while ((s = TAILQ_FIRST(set)) != NULL) { TAILQ_REMOVE(set, s, entry); free(s); } free(set); } return (0); } int str2key(char *s, char *dest, size_t max_len) { unsigned i; char t[3]; if (strlen(s) / 2 > max_len) { yyerror("key too long"); return (-1); } if (strlen(s) % 2) { yyerror("key must be of even length"); return (-1); } for (i = 0; i < strlen(s) / 2; i++) { t[0] = s[2*i]; t[1] = s[2*i + 1]; t[2] = 0; if (!isxdigit(t[0]) || !isxdigit(t[1])) { yyerror("key must be specified in hex"); return (-1); } dest[i] = strtoul(t, NULL, 16); } return (0); } int neighbor_consistent(struct peer *p) { struct bgpd_addr *local_addr; switch (p->conf.remote_addr.aid) { case AID_INET: local_addr = &p->conf.local_addr_v4; break; case AID_INET6: local_addr = &p->conf.local_addr_v6; break; default: yyerror("Bad address family for remote-addr"); return (-1); } /* with any form of ipsec local-address is required */ if ((p->conf.auth.method == AUTH_IPSEC_IKE_ESP || p->conf.auth.method == AUTH_IPSEC_IKE_AH || p->conf.auth.method == AUTH_IPSEC_MANUAL_ESP || p->conf.auth.method == AUTH_IPSEC_MANUAL_AH) && local_addr->aid == AID_UNSPEC) { yyerror("neighbors with any form of IPsec configured " "need local-address to be specified"); return (-1); } /* with static keying we need both directions */ if ((p->conf.auth.method == AUTH_IPSEC_MANUAL_ESP || p->conf.auth.method == AUTH_IPSEC_MANUAL_AH) && (!p->conf.auth.spi_in || !p->conf.auth.spi_out)) { yyerror("with manual keyed IPsec, SPIs and keys " "for both directions are required"); return (-1); } if (!conf->as) { yyerror("AS needs to be given before neighbor definitions"); return (-1); } /* set default values if they where undefined */ p->conf.ebgp = (p->conf.remote_as != conf->as); if (p->conf.enforce_as == ENFORCE_AS_UNDEF) p->conf.enforce_as = p->conf.ebgp ? ENFORCE_AS_ON : ENFORCE_AS_OFF; if (p->conf.enforce_local_as == ENFORCE_AS_UNDEF) p->conf.enforce_local_as = ENFORCE_AS_ON; if (p->conf.remote_as == 0 && !p->conf.template) { yyerror("peer AS may not be zero"); return (-1); } /* EBGP neighbors are not allowed in route reflector clusters */ if (p->conf.reflector_client && p->conf.ebgp) { yyerror("EBGP neighbors are not allowed in route " "reflector clusters"); return (-1); } return (0); } static void filterset_add(struct filter_set_head *sh, struct filter_set *s) { struct filter_set *t; TAILQ_FOREACH(t, sh, entry) { if (s->type < t->type) { TAILQ_INSERT_BEFORE(t, s, entry); return; } if (s->type == t->type) { switch (s->type) { case ACTION_SET_COMMUNITY: case ACTION_DEL_COMMUNITY: if (memcmp(&s->action.community, &t->action.community, sizeof(s->action.community)) < 0) { TAILQ_INSERT_BEFORE(t, s, entry); return; } else if (memcmp(&s->action.community, &t->action.community, sizeof(s->action.community)) == 0) break; continue; case ACTION_SET_NEXTHOP: /* only last nexthop per AF matters */ if (s->action.nexthop.aid < t->action.nexthop.aid) { TAILQ_INSERT_BEFORE(t, s, entry); return; } else if (s->action.nexthop.aid == t->action.nexthop.aid) { t->action.nexthop = s->action.nexthop; break; } continue; case ACTION_SET_NEXTHOP_BLACKHOLE: case ACTION_SET_NEXTHOP_REJECT: case ACTION_SET_NEXTHOP_NOMODIFY: case ACTION_SET_NEXTHOP_SELF: /* set it only once */ break; case ACTION_SET_LOCALPREF: case ACTION_SET_MED: case ACTION_SET_WEIGHT: /* only last set matters */ t->action.metric = s->action.metric; break; case ACTION_SET_RELATIVE_LOCALPREF: case ACTION_SET_RELATIVE_MED: case ACTION_SET_RELATIVE_WEIGHT: /* sum all relative numbers */ t->action.relative += s->action.relative; break; case ACTION_SET_ORIGIN: /* only last set matters */ t->action.origin = s->action.origin; break; case ACTION_PFTABLE: /* only last set matters */ strlcpy(t->action.pftable, s->action.pftable, sizeof(t->action.pftable)); break; case ACTION_RTLABEL: /* only last set matters */ strlcpy(t->action.rtlabel, s->action.rtlabel, sizeof(t->action.rtlabel)); break; default: break; } free(s); return; } } TAILQ_INSERT_TAIL(sh, s, entry); } int merge_filterset(struct filter_set_head *sh, struct filter_set *s) { struct filter_set *t; TAILQ_FOREACH(t, sh, entry) { /* * need to cycle across the full list because even * if types are not equal filterset_cmp() may return 0. */ if (filterset_cmp(s, t) == 0) { if (s->type == ACTION_SET_COMMUNITY) yyerror("community is already set"); else if (s->type == ACTION_DEL_COMMUNITY) yyerror("community will already be deleted"); else yyerror("redefining set parameter %s", filterset_name(s->type)); return (-1); } } filterset_add(sh, s); return (0); } static int filter_equal(struct filter_rule *fa, struct filter_rule *fb) { if (fa == NULL || fb == NULL) return 0; if (fa->action != fb->action || fa->quick != fb->quick || fa->dir != fb->dir) return 0; if (memcmp(&fa->peer, &fb->peer, sizeof(fa->peer))) return 0; if (memcmp(&fa->match, &fb->match, sizeof(fa->match))) return 0; return 1; } /* do a basic optimization by folding equal rules together */ void optimize_filters(struct filter_head *fh) { struct filter_rule *r, *nr; TAILQ_FOREACH_SAFE(r, fh, entry, nr) { while (filter_equal(r, nr)) { struct filter_set *t; while ((t = TAILQ_FIRST(&nr->set)) != NULL) { TAILQ_REMOVE(&nr->set, t, entry); filterset_add(&r->set, t); } TAILQ_REMOVE(fh, nr, entry); free(nr); nr = TAILQ_NEXT(r, entry); } } } struct filter_rule * get_rule(enum action_types type) { struct filter_rule *r; int out; switch (type) { case ACTION_SET_PREPEND_SELF: case ACTION_SET_NEXTHOP_NOMODIFY: case ACTION_SET_NEXTHOP_SELF: out = 1; break; default: out = 0; break; } r = (curpeer == curgroup) ? curgroup_filter[out] : curpeer_filter[out]; if (r == NULL) { if ((r = calloc(1, sizeof(struct filter_rule))) == NULL) fatal(NULL); r->quick = 0; r->dir = out ? DIR_OUT : DIR_IN; r->action = ACTION_NONE; TAILQ_INIT(&r->set); if (curpeer == curgroup) { /* group */ r->peer.groupid = curgroup->conf.id; curgroup_filter[out] = r; } else { /* peer */ r->peer.peerid = curpeer->conf.id; curpeer_filter[out] = r; } } return (r); } struct set_table *curset; static int new_as_set(char *name) { struct as_set *aset; if (as_sets_lookup(&conf->as_sets, name) != NULL) { yyerror("as-set \"%s\" already exists", name); return -1; } aset = as_sets_new(&conf->as_sets, name, 0, sizeof(u_int32_t)); if (aset == NULL) fatal(NULL); curset = aset->set; return 0; } static void add_as_set(u_int32_t as) { if (curset == NULL) fatalx("%s: bad mojo jojo", __func__); if (set_add(curset, &as, 1) != 0) fatal(NULL); } static void done_as_set(void) { curset = NULL; } static struct prefixset * new_prefix_set(char *name, int is_roa) { const char *type = "prefix-set"; struct prefixset_head *sets = &conf->prefixsets; struct prefixset *pset; if (is_roa) { type = "roa-set"; sets = &conf->originsets; } if (find_prefixset(name, sets) != NULL) { yyerror("%s \"%s\" already exists", type, name); return NULL; } if ((pset = calloc(1, sizeof(*pset))) == NULL) fatal("prefixset"); if (strlcpy(pset->name, name, sizeof(pset->name)) >= sizeof(pset->name)) { yyerror("%s \"%s\" too long: max %zu", type, name, sizeof(pset->name) - 1); free(pset); return NULL; } RB_INIT(&pset->psitems); return pset; } static void add_roa_set(struct prefixset_item *npsi, u_int32_t as, u_int8_t max) { struct prefixset_item *psi; struct roa_set rs, *rsp; /* no prefixlen option in this tree */ npsi->p.op = OP_NONE; npsi->p.len_max = npsi->p.len_min = npsi->p.len; psi = RB_INSERT(prefixset_tree, curpsitree, npsi); if (psi == NULL) psi = npsi; if (psi->set == NULL) if ((psi->set = set_new(1, sizeof(rs))) == NULL) fatal("set_new"); /* merge sets with same key, longer maxlen wins */ if ((rsp = set_match(psi->set, as)) != NULL) { if (rsp->maxlen < max) rsp->maxlen = max; } else { rs.as = as; rs.maxlen = max; if (set_add(psi->set, &rs, 1) != 0) fatal("as_set_new"); /* prep data so that set_match works */ set_prep(psi->set); } }