src/usr.sbin/bgpd/parse.y

6018 lines
133 KiB
Plaintext

/* $OpenBSD: parse.y,v 1.462 2024/04/24 10:41:34 claudio Exp $ */
/*
* Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
* 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 <job@openbsd.org>
* Copyright (c) 2016 Peter Hessler <phessler@openbsd.org>
* Copyright (c) 2017, 2018 Sebastian Benoit <benno@openbsd.org>
*
* 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 <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip_ipsp.h>
#include <netinet/icmp6.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <endian.h>
#include <err.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <netdb.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include "bgpd.h"
#include "session.h"
#include "rde.h"
#include "log.h"
#ifndef nitems
#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
#endif
#define MACRO_NAME_LEN 128
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);
int expand_macro(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 *);
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 aspa_tas_l {
struct aspa_tas_l *next;
uint32_t as;
uint32_t num;
};
struct flowspec_context {
uint8_t *components[FLOWSPEC_TYPE_MAX];
uint16_t complen[FLOWSPEC_TYPE_MAX];
uint8_t aid;
uint8_t type;
uint8_t addr_type;
};
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(uint32_t);
static void done_as_set(void);
static struct prefixset *new_prefix_set(char *, int);
static void add_roa_set(struct prefixset_item *, uint32_t, uint8_t,
time_t);
static struct rtr_config *get_rtr(struct bgpd_addr *);
static int insert_rtr(struct rtr_config *);
static int merge_aspa_set(uint32_t, struct aspa_tas_l *, time_t);
static int map_tos(char *, int *);
static int getservice(char *);
static int parse_flags(char *);
static struct flowspec_config *flow_to_flowspec(struct flowspec_context *);
static void flow_free(struct flowspec_context *);
static int push_prefix(struct bgpd_addr *, uint8_t);
static int push_binop(uint8_t, long long);
static int push_unary_numop(enum comp_ops, long long);
static int push_binary_numop(enum comp_ops, long long, long long);
static int geticmptypebyname(char *, uint8_t);
static int geticmpcodebyname(u_long, char *, uint8_t);
static struct bgpd_config *conf;
static struct network_head *netconf;
static struct peer_head *new_peers, *cur_peers;
static struct rtr_config_head *cur_rtrs;
static struct peer *curpeer;
static struct peer *curgroup;
static struct rde_rib *currib;
static struct l3vpn *curvpn;
static struct prefixset *curpset, *curoset;
static struct roa_tree *curroatree;
static struct rtr_config *currtr;
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];
static struct flowspec_context *curflow;
static int noexpires;
typedef struct {
union {
long long number;
char *string;
struct bgpd_addr addr;
uint8_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 aspa_tas_l *aspa_elm;
struct {
struct bgpd_addr prefix;
uint8_t len;
} prefix;
struct filter_prefixlen prefixlen;
struct prefixset_item *prefixset_item;
struct {
enum auth_enc_alg enc_alg;
uint8_t enc_key_len;
char enc_key[IPSEC_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 RTR PORT
%token GROUP NEIGHBOR NETWORK
%token EBGP IBGP
%token FLOWSPEC PROTO FLAGS FRAGMENT TOS LENGTH ICMPTYPE CODE
%token LOCALAS REMOTEAS DESCR LOCALADDR MULTIHOP PASSIVE MAXPREFIX RESTART
%token ANNOUNCE REFRESH AS4BYTE CONNECTRETRY ENHANCED ADDPATH
%token SEND RECV PLUS POLICY ROLE
%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 MAXCOMMUNITIES MAXEXTCOMMUNITIES MAXLARGECOMMUNITIES
%token PREFIX PREFIXLEN PREFIXSET
%token ASPASET ROASET ORIGINSET OVS AVS EXPIRES
%token ASSET SOURCEAS TRANSITAS PEERAS PROVIDERAS CUSTOMERAS 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 MAX
%token <v.string> STRING
%token <v.number> NUMBER
%type <v.number> asnumber as4number as4number_any optnumber
%type <v.number> espah af safi restart origincode nettype
%type <v.number> yesno inout restricted expires
%type <v.number> yesnoenforce enforce
%type <v.number> validity aspa_validity
%type <v.number> addpathextra addpathmax
%type <v.number> port proto_item tos length flag icmptype
%type <v.string> string
%type <v.addr> address
%type <v.prefix> prefix addrspec
%type <v.prefixset_item> prefixset_item
%type <v.u8> action quick direction delete community
%type <v.filter_rib> filter_rib_h filter_rib_l filter_rib
%type <v.filter_peers> filter_peer filter_peer_l filter_peer_h
%type <v.filter_match> filter_match filter_elm filter_match_h
%type <v.filter_as> filter_as filter_as_l filter_as_h
%type <v.filter_as> filter_as_t filter_as_t_l filter_as_l_h
%type <v.prefixlen> prefixlenop
%type <v.filter_set> filter_set_opt
%type <v.filter_set_head> filter_set filter_set_l
%type <v.filter_prefix> filter_prefix filter_prefix_l filter_prefix_h
%type <v.filter_prefix> filter_prefix_m
%type <v.u8> unaryop equalityop binaryop filter_as_type
%type <v.encspec> encspec
%type <v.aspa_elm> aspa_tas aspa_tas_l
%%
grammar : /* empty */
| grammar '\n'
| grammar varset '\n'
| grammar include '\n'
| grammar as_set '\n'
| grammar prefixset '\n'
| grammar roa_set '\n'
| grammar aspa_set '\n'
| grammar origin_set '\n'
| grammar rtr '\n'
| grammar rib '\n'
| grammar network '\n'
| grammar flowspec '\n'
| grammar mrtdump '\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;
uint32_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",
(uint32_t)$1);
YYERROR;
}
$$ = $1;
}
;
as4number_any : STRING {
const char *errstr;
char *dot;
uint32_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 (strlen($1) >= MACRO_NAME_LEN) {
yyerror("macro name to long, max %d characters",
MACRO_NAME_LEN - 1);
free($1);
free($3);
YYERROR;
}
do {
if (isalnum((unsigned char)*s) || *s == '_')
continue;
yyerror("macro name can only contain "
"alphanumerics and '_'");
free($1);
free($3);
YYERROR;
} while (*++s);
if (cmd_opts & BGPD_OPT_VERBOSE)
printf("%s = \"%s\"\n", $1, $3);
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 {
curroatree = &conf->roa;
} roa_set_l optnl '}' {
curroatree = NULL;
}
| ROASET '{' optnl '}' /* nothing */
;
origin_set : ORIGINSET STRING '{' optnl {
if ((curoset = new_prefix_set($2, 1)) == NULL) {
free($2);
YYERROR;
}
curroatree = &curoset->roaitems;
noexpires = 1;
free($2);
} roa_set_l optnl '}' {
SIMPLEQ_INSERT_TAIL(&conf->originsets, curoset, entry);
curoset = NULL;
curroatree = NULL;
noexpires = 0;
}
| 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;
curroatree = NULL;
}
;
expires : /* empty */ {
$$ = 0;
}
| EXPIRES NUMBER {
if (noexpires) {
yyerror("syntax error, expires not allowed");
YYERROR;
}
$$ = $2;
}
roa_set_l : prefixset_item SOURCEAS as4number_any expires {
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, $4);
free($1);
}
| roa_set_l comma prefixset_item SOURCEAS as4number_any expires {
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, $6);
free($3);
}
;
aspa_set : ASPASET '{' optnl aspa_set_l optnl '}'
| ASPASET '{' optnl '}'
;
aspa_set_l : aspa_elm
| aspa_set_l comma aspa_elm
;
aspa_elm : CUSTOMERAS as4number expires PROVIDERAS '{' optnl
aspa_tas_l optnl '}' {
int rv;
struct aspa_tas_l *a, *n;
rv = merge_aspa_set($2, $7, $3);
for (a = $7; a != NULL; a = n) {
n = a->next;
free(a);
}
if (rv == -1)
YYERROR;
}
;
aspa_tas_l : aspa_tas { $$ = $1; }
| aspa_tas_l comma aspa_tas {
$3->next = $1;
$3->num = $1->num + 1;
$$ = $3;
}
;
aspa_tas : as4number_any {
if (($$ = calloc(1, sizeof(*$$))) == NULL)
fatal(NULL);
$$->as = $1;
$$->num = 1;
}
| as4number_any af {
if (($$ = calloc(1, sizeof(*$$))) == NULL)
fatal(NULL);
$$->as = $1;
$$->num = 1;
}
;
rtr : RTR address {
currtr = get_rtr(&$2);
currtr->remote_port = RTR_PORT;
if (insert_rtr(currtr) == -1) {
free(currtr);
YYERROR;
}
currtr = NULL;
}
| RTR address {
currtr = get_rtr(&$2);
currtr->remote_port = RTR_PORT;
} '{' optnl rtropt_l optnl '}' {
if (insert_rtr(currtr) == -1) {
free(currtr);
YYERROR;
}
currtr = NULL;
}
;
rtropt_l : rtropt
| rtropt_l optnl rtropt
;
rtropt : DESCR STRING {
if (strlcpy(currtr->descr, $2,
sizeof(currtr->descr)) >=
sizeof(currtr->descr)) {
yyerror("descr \"%s\" too long: max %zu",
$2, sizeof(currtr->descr) - 1);
free($2);
YYERROR;
}
free($2);
}
| LOCALADDR address {
if ($2.aid != currtr->remote_addr.aid) {
yyerror("Bad address family %s for "
"local-addr", aid2str($2.aid));
YYERROR;
}
currtr->local_addr = $2;
}
| PORT port {
currtr->remote_port = $2;
}
;
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);
}
| LISTEN ON address PORT port {
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, $5, &la->sa_len);
memcpy(&la->sa, sa, la->sa_len);
TAILQ_INSERT_TAIL(conf->listen_addrs, la, entry);
}
| FIBPRIORITY NUMBER {
if (!kr_check_prio($2)) {
yyerror("fib-priority %lld out of range", $2);
YYERROR;
}
conf->fib_priority = $2;
}
| FIBUPDATE yesno {
struct rde_rib *rr;
rr = find_rib("Loc-RIB");
if (rr == NULL)
fatalx("RTABLE cannot 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;
}
| REJECT ASSET yesno {
if ($3 == 1)
conf->flags |= BGPD_FLAG_NO_AS_SET;
else
conf->flags &= ~BGPD_FLAG_NO_AS_SET;
}
| LOG STRING {
if (!strcmp($2, "updates"))
conf->log |= BGPD_LOG_UPDATES;
else {
free($2);
YYERROR;
}
free($2);
}
| 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);
}
| 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);
}
| RDE EVALUATE STRING {
if (!strcmp($3, "all"))
conf->flags |= BGPD_FLAG_DECISION_ALL_PATHS;
else if (!strcmp($3, "default"))
conf->flags &= ~BGPD_FLAG_DECISION_ALL_PATHS;
else {
yyerror("rde evaluate: "
"unknown setting \"%s\"", $3);
free($3);
YYERROR;
}
free($3);
}
| 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)) {
yyerror("rtable id %lld does not exist", $2);
YYERROR;
}
rr = find_rib("Loc-RIB");
if (rr == NULL)
fatalx("RTABLE cannot 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 af 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 address 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 af PRIORITY NUMBER filter_set {
struct network *n;
if (!kr_check_prio($4)) {
yyerror("priority %lld out of range", $4);
YYERROR;
}
if ((n = calloc(1, sizeof(struct network))) == NULL)
fatal("new_network");
if (afi2aid($2, SAFI_UNICAST, &n->net.prefix.aid) ==
-1) {
yyerror("unknown address 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 af 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 address 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);
}
;
flowspec : FLOWSPEC af {
if ((curflow = calloc(1, sizeof(*curflow))) == NULL)
fatal("new_flowspec");
curflow->aid = $2;
} flow_rules filter_set {
struct flowspec_config *f;
f = flow_to_flowspec(curflow);
if (f == NULL) {
yyerror("out of memory");
free($5);
flow_free(curflow);
curflow = NULL;
YYERROR;
}
filterset_move($5, &f->attrset);
free($5);
flow_free(curflow);
curflow = NULL;
if (RB_INSERT(flowspec_tree, &conf->flowspecs, f) !=
NULL) {
yyerror("duplicate flowspec definition");
flowspec_free(f);
YYERROR;
}
}
;
proto : PROTO proto_item
| PROTO '{' optnl proto_list optnl '}'
;
proto_list : proto_item {
curflow->type = FLOWSPEC_TYPE_PROTO;
if (push_unary_numop(OP_EQ, $1) == -1)
YYERROR;
}
| proto_list comma proto_item {
curflow->type = FLOWSPEC_TYPE_PROTO;
if (push_unary_numop(OP_EQ, $3) == -1)
YYERROR;
}
;
proto_item : STRING {
struct protoent *p;
p = getprotobyname($1);
if (p == NULL) {
yyerror("unknown protocol %s", $1);
free($1);
YYERROR;
}
$$ = p->p_proto;
free($1);
}
| NUMBER {
if ($1 < 0 || $1 > 255) {
yyerror("protocol outside range");
YYERROR;
}
$$ = $1;
}
;
from : FROM {
curflow->type = FLOWSPEC_TYPE_SRC_PORT;
curflow->addr_type = FLOWSPEC_TYPE_SOURCE;
} ipportspec
;
to : TO {
curflow->type = FLOWSPEC_TYPE_DST_PORT;
curflow->addr_type = FLOWSPEC_TYPE_DEST;
} ipportspec
;
ipportspec : ipspec
| ipspec PORT portspec
| PORT portspec
;
ipspec : ANY
| prefix {
if (push_prefix(&$1.prefix, $1.len) == -1)
YYERROR;
}
;
portspec : port_item
| '{' optnl port_list optnl '}'
;
port_list : port_item
| port_list comma port_item
;
port_item : port {
if (push_unary_numop(OP_EQ, $1) == -1)
YYERROR;
}
| unaryop port {
if (push_unary_numop($1, $2) == -1)
YYERROR;
}
| port binaryop port {
if (push_binary_numop($2, $1, $3))
YYERROR;
}
;
port : NUMBER {
if ($1 < 1 || $1 > USHRT_MAX) {
yyerror("port must be between %u and %u",
1, USHRT_MAX);
YYERROR;
}
$$ = $1;
}
| STRING {
if (($$ = getservice($1)) == -1) {
yyerror("unknown port '%s'", $1);
free($1);
YYERROR;
}
free($1);
}
;
flow_rules : /* empty */
| flow_rules_l
;
flow_rules_l : flowrule
| flow_rules_l flowrule
;
flowrule : from
| to
| FLAGS {
curflow->type = FLOWSPEC_TYPE_TCP_FLAGS;
} flags
| FRAGMENT {
curflow->type = FLOWSPEC_TYPE_FRAG;
} flags;
| icmpspec
| LENGTH lengthspec {
curflow->type = FLOWSPEC_TYPE_PKT_LEN;
}
| proto
| TOS tos {
curflow->type = FLOWSPEC_TYPE_DSCP;
if (push_unary_numop(OP_EQ, $2 >> 2) == -1)
YYERROR;
}
;
flags : flag '/' flag {
if (($1 & $3) != $1) {
yyerror("bad flag combination, "
"check bit not in mask");
YYERROR;
}
if (push_binop(FLOWSPEC_OP_BIT_MATCH, $1) == -1)
YYERROR;
/* check if extra mask op is needed */
if ($3 & ~$1) {
if (push_binop(FLOWSPEC_OP_BIT_NOT |
FLOWSPEC_OP_AND, $3 & ~$1) == -1)
YYERROR;
}
}
| '/' flag {
if (push_binop(FLOWSPEC_OP_BIT_NOT, $2) == -1)
YYERROR;
}
| flag {
if (push_binop(0, $1) == -1)
YYERROR;
}
| ANY /* nothing */
;
flag : STRING {
if (($$ = parse_flags($1)) < 0) {
yyerror("bad flags %s", $1);
free($1);
YYERROR;
}
free($1);
}
;
icmpspec : ICMPTYPE icmp_item
| ICMPTYPE '{' optnl icmp_list optnl '}'
;
icmp_list : icmp_item
| icmp_list comma icmp_item
;
icmp_item : icmptype {
curflow->type = FLOWSPEC_TYPE_ICMP_TYPE;
if (push_unary_numop(OP_EQ, $1) == -1)
YYERROR;
}
| icmptype CODE STRING {
int code;
if ((code = geticmpcodebyname($1, $3, curflow->aid)) ==
-1) {
yyerror("unknown icmp-code %s", $3);
free($3);
YYERROR;
}
free($3);
curflow->type = FLOWSPEC_TYPE_ICMP_TYPE;
if (push_unary_numop(OP_EQ, $1) == -1)
YYERROR;
curflow->type = FLOWSPEC_TYPE_ICMP_CODE;
if (push_unary_numop(OP_EQ, code) == -1)
YYERROR;
}
| icmptype CODE NUMBER {
if ($3 < 0 || $3 > 255) {
yyerror("illegal icmp-code %lld", $3);
YYERROR;
}
curflow->type = FLOWSPEC_TYPE_ICMP_TYPE;
if (push_unary_numop(OP_EQ, $1) == -1)
YYERROR;
curflow->type = FLOWSPEC_TYPE_ICMP_CODE;
if (push_unary_numop(OP_EQ, $3) == -1)
YYERROR;
}
;
icmptype : STRING {
int type;
if ((type = geticmptypebyname($1, curflow->aid)) ==
-1) {
yyerror("unknown icmp-type %s", $1);
free($1);
YYERROR;
}
$$ = type;
free($1);
}
| NUMBER {
if ($1 < 0 || $1 > 255) {
yyerror("illegal icmp-type %lld", $1);
YYERROR;
}
$$ = $1;
}
;
tos : STRING {
int val;
char *end;
if (map_tos($1, &val))
$$ = val;
else if ($1[0] == '0' && $1[1] == 'x') {
errno = 0;
$$ = strtoul($1, &end, 16);
if (errno || *end != '\0')
$$ = 256;
} else
$$ = 256;
if ($$ < 0 || $$ > 255) {
yyerror("illegal tos value %s", $1);
free($1);
YYERROR;
}
free($1);
}
| NUMBER {
if ($$ < 0 || $$ > 255) {
yyerror("illegal tos value %lld", $1);
YYERROR;
}
$$ = $1;
}
;
lengthspec : length_item
| '{' optnl length_list optnl '}'
;
length_list : length_item
| length_list comma length_item
;
length_item : length {
if (push_unary_numop(OP_EQ, $1) == -1)
YYERROR;
}
| unaryop length {
if (push_unary_numop($1, $2) == -1)
YYERROR;
}
| length binaryop length {
if (push_binary_numop($2, $1, $3) == -1)
YYERROR;
}
;
length : NUMBER {
if ($$ < 0 || $$ > USHRT_MAX) {
yyerror("illegal ptk length value %lld", $1);
YYERROR;
}
$$ = $1;
}
inout : IN { $$ = 1; }
| OUT { $$ = 0; }
;
restricted : /* empty */ { $$ = 0; }
| RESTRICTED { $$ = 1; }
;
address : STRING {
uint8_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) {
free(curpeer);
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);
free(curgroup);
YYERROR;
}
free($2);
if (get_id(curgroup)) {
yyerror("get_id failed");
free(curgroup);
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'
;
addpathextra : /* empty */ { $$ = 0; }
| PLUS NUMBER {
if ($2 < 1 || $2 > USHRT_MAX) {
yyerror("additional paths must be between "
"%u and %u", 1, USHRT_MAX);
YYERROR;
}
$$ = $2;
}
;
addpathmax : /* empty */ { $$ = 0; }
| MAX NUMBER {
if ($2 < 1 || $2 > USHRT_MAX) {
yyerror("maximum additional paths must be "
"between %u and %u", 1, USHRT_MAX);
YYERROR;
}
$$ = $2;
}
;
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 af safi enforce {
uint8_t aid, safi;
uint16_t afi;
if ($3 == SAFI_NONE) {
for (aid = AID_MIN; 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;
}
if ($4)
curpeer->conf.capabilities.mp[aid] = 2;
else
curpeer->conf.capabilities.mp[aid] = 1;
}
}
| ANNOUNCE REFRESH yesnoenforce {
curpeer->conf.capabilities.refresh = $3;
}
| ANNOUNCE ENHANCED REFRESH yesnoenforce {
curpeer->conf.capabilities.enhanced_rr = $4;
}
| ANNOUNCE RESTART yesnoenforce {
curpeer->conf.capabilities.grestart.restart = $3;
}
| ANNOUNCE AS4BYTE yesnoenforce {
curpeer->conf.capabilities.as4byte = $3;
}
| ANNOUNCE ADDPATH RECV yesnoenforce {
int8_t *ap = curpeer->conf.capabilities.add_path;
uint8_t i;
for (i = AID_MIN; i < AID_MAX; i++) {
if ($4) {
if ($4 == 2)
ap[i] |= CAPA_AP_RECV_ENFORCE;
ap[i] |= CAPA_AP_RECV;
} else
ap[i] &= ~CAPA_AP_RECV;
}
}
| ANNOUNCE ADDPATH SEND STRING addpathextra addpathmax enforce {
int8_t *ap = curpeer->conf.capabilities.add_path;
enum addpath_mode mode;
u_int8_t i;
if (!strcmp($4, "no")) {
free($4);
if ($5 != 0 || $6 != 0 || $7 != 0) {
yyerror("no additional option allowed "
"for 'add-path send no'");
YYERROR;
}
mode = ADDPATH_EVAL_NONE;
} else if (!strcmp($4, "all")) {
free($4);
if ($5 != 0 || $6 != 0) {
yyerror("no additional option allowed "
"for 'add-path send all'");
YYERROR;
}
mode = ADDPATH_EVAL_ALL;
} else if (!strcmp($4, "best")) {
free($4);
mode = ADDPATH_EVAL_BEST;
} else if (!strcmp($4, "ecmp")) {
free($4);
mode = ADDPATH_EVAL_ECMP;
} else if (!strcmp($4, "as-wide-best")) {
free($4);
mode = ADDPATH_EVAL_AS_WIDE;
} else {
yyerror("announce add-path send: "
"unknown mode \"%s\"", $4);
free($4);
YYERROR;
}
for (i = AID_MIN; i < AID_MAX; i++) {
if (mode != ADDPATH_EVAL_NONE) {
if ($7)
ap[i] |= CAPA_AP_SEND_ENFORCE;
ap[i] |= CAPA_AP_SEND;
} else
ap[i] &= ~CAPA_AP_SEND;
}
curpeer->conf.eval.mode = mode;
curpeer->conf.eval.extrapaths = $5;
curpeer->conf.eval.maxpaths = $6;
}
| ANNOUNCE POLICY yesnoenforce {
curpeer->conf.capabilities.policy = $3;
}
| ROLE STRING {
if (strcmp($2, "provider") == 0) {
curpeer->conf.role = ROLE_PROVIDER;
} else if (strcmp($2, "rs") == 0) {
curpeer->conf.role = ROLE_RS;
} else if (strcmp($2, "rs-client") == 0) {
curpeer->conf.role = ROLE_RS_CLIENT;
} else if (strcmp($2, "customer") == 0) {
curpeer->conf.role = ROLE_CUSTOMER;
} else if (strcmp($2, "peer") == 0) {
curpeer->conf.role = ROLE_PEER;
} else {
yyerror("syntax error, one of none, provider, "
"rs, rs-client, customer, peer expected");
free($2);
YYERROR;
}
free($2);
}
| ROLE NONE {
curpeer->conf.role = ROLE_NONE;
}
| 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 {
enum auth_alg auth_alg;
uint8_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 = AUTH_AALG_SHA1HMAC;
keylen = 20;
} else if (!strcmp($6, "md5")) {
auth_alg = AUTH_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);
}
| REJECT ASSET yesno {
if ($3 == 1)
curpeer->conf.flags |= PEERFLAG_NO_AS_SET;
else
curpeer->conf.flags &= ~PEERFLAG_NO_AS_SET;
}
| PORT port {
curpeer->conf.remote_port = $2;
}
| RDE EVALUATE STRING {
if (!strcmp($3, "all"))
curpeer->conf.flags |= PEERFLAG_EVALUATE_ALL;
else if (!strcmp($3, "default"))
curpeer->conf.flags &= ~PEERFLAG_EVALUATE_ALL;
else {
yyerror("rde evaluate: "
"unknown setting \"%s\"", $3);
free($3);
YYERROR;
}
free($3);
}
;
restart : /* nada */ { $$ = 0; }
| RESTART NUMBER {
if ($2 < 1 || $2 > USHRT_MAX) {
yyerror("restart out of range. 1 to %u minutes",
USHRT_MAX);
YYERROR;
}
$$ = $2;
}
;
af : IPV4 { $$ = AFI_IPv4; }
| IPV6 { $$ = AFI_IPv6; }
;
safi : NONE { $$ = SAFI_NONE; }
| UNICAST { $$ = SAFI_UNICAST; }
| VPN { $$ = SAFI_MPLSVPN; }
| FLOWSPEC { $$ = SAFI_FLOWSPEC; }
;
nettype : STATIC { $$ = 1; }
| CONNECTED { $$ = 0; }
;
espah : ESP { $$ = 1; }
| AH { $$ = 0; }
;
encspec : /* nada */ {
memset(&$$, 0, sizeof($$));
}
| STRING STRING {
memset(&$$, 0, sizeof($$));
if (!strcmp($1, "3des") || !strcmp($1, "3des-cbc")) {
$$.enc_alg = AUTH_EALG_3DESCBC;
$$.enc_key_len = 21; /* XXX verify */
} else if (!strcmp($1, "aes") ||
!strcmp($1, "aes-128-cbc")) {
$$.enc_alg = AUTH_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;
memset(&r, 0, 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 */ {
memset(&$$, 0, sizeof($$));
}
| {
memset(&fmopts, 0, 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);
}
| MAXCOMMUNITIES NUMBER {
if ($2 < 0 || $2 > INT16_MAX) {
yyerror("bad max-comunities %lld", $2);
YYERROR;
}
if (fmopts.m.maxcomm != 0) {
yyerror("%s already specified",
"max-communities");
YYERROR;
}
/*
* Offset by 1 since 0 means not used.
* The match function then uses >= to compensate.
*/
fmopts.m.maxcomm = $2 + 1;
}
| MAXEXTCOMMUNITIES NUMBER {
if ($2 < 0 || $2 > INT16_MAX) {
yyerror("bad max-ext-communities %lld", $2);
YYERROR;
}
if (fmopts.m.maxextcomm != 0) {
yyerror("%s already specified",
"max-ext-communities");
YYERROR;
}
fmopts.m.maxextcomm = $2 + 1;
}
| MAXLARGECOMMUNITIES NUMBER {
if ($2 < 0 || $2 > INT16_MAX) {
yyerror("bad max-large-communities %lld", $2);
YYERROR;
}
if (fmopts.m.maxlargecomm != 0) {
yyerror("%s already specified",
"max-large-communities");
YYERROR;
}
fmopts.m.maxlargecomm = $2 + 1;
}
| 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;
}
| AVS aspa_validity {
if (fmopts.m.avs.is_set) {
yyerror("avs filter already specified");
YYERROR;
}
fmopts.m.avs.validity = $2;
fmopts.m.avs.is_set = 1;
}
;
prefixlenop : /* empty */ { memset(&$$, 0, sizeof($$)); }
| LONGER {
memset(&$$, 0, sizeof($$));
$$.op = OP_RANGE;
$$.len_min = -1;
$$.len_max = -1;
}
| MAXLEN NUMBER {
memset(&$$, 0, 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;
memset(&$$, 0, 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 {
memset(&$$, 0, 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; }
;
enforce : /* empty */ { $$ = 0; }
| ENFORCE { $$ = 2; }
;
yesnoenforce : yesno { $$ = $1; }
| ENFORCE { $$ = 2; }
;
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 {
uint8_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 roa validity \"%s\"", $1);
free($1);
YYERROR;
}
free($1);
};
aspa_validity : STRING {
if (!strcmp($1, "unknown"))
$$ = ASPA_UNKNOWN;
else if (!strcmp($1, "invalid"))
$$ = ASPA_INVALID;
else if (!strcmp($1, "valid"))
$$ = ASPA_VALID;
else {
yyerror("unknown aspa 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},
{ "add-path", ADDPATH},
{ "ah", AH},
{ "allow", ALLOW},
{ "announce", ANNOUNCE},
{ "any", ANY},
{ "as-4byte", AS4BYTE },
{ "as-override", ASOVERRIDE},
{ "as-set", ASSET },
{ "aspa-set", ASPASET},
{ "avs", AVS},
{ "blackhole", BLACKHOLE},
{ "community", COMMUNITY},
{ "compare", COMPARE},
{ "connect-retry", CONNECTRETRY},
{ "connected", CONNECTED},
{ "customer-as", CUSTOMERAS},
{ "default-route", DEFAULTROUTE},
{ "delete", DELETE},
{ "demote", DEMOTE},
{ "deny", DENY},
{ "depend", DEPEND},
{ "descr", DESCR},
{ "down", DOWN},
{ "dump", DUMP},
{ "ebgp", EBGP},
{ "enforce", ENFORCE},
{ "enhanced", ENHANCED },
{ "esp", ESP},
{ "evaluate", EVALUATE},
{ "expires", EXPIRES},
{ "export", EXPORT},
{ "export-target", EXPORTTRGT},
{ "ext-community", EXTCOMMUNITY},
{ "fib-priority", FIBPRIORITY},
{ "fib-update", FIBUPDATE},
{ "flags", FLAGS},
{ "flowspec", FLOWSPEC},
{ "fragment", FRAGMENT},
{ "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", MAX},
{ "max-as-len", MAXASLEN},
{ "max-as-seq", MAXASSEQ},
{ "max-communities", MAXCOMMUNITIES},
{ "max-ext-communities", MAXEXTCOMMUNITIES},
{ "max-large-communities", MAXLARGECOMMUNITIES},
{ "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},
{ "plus", PLUS},
{ "policy", POLICY},
{ "port", PORT},
{ "prefix", PREFIX},
{ "prefix-set", PREFIXSET},
{ "prefixlen", PREFIXLEN},
{ "prepend-neighbor", PREPEND_PEER},
{ "prepend-self", PREPEND_SELF},
{ "priority", PRIORITY},
{ "proto", PROTO},
{ "provider-as", PROVIDERAS},
{ "qualify", QUALIFY},
{ "quick", QUICK},
{ "rd", RD},
{ "rde", RDE},
{ "recv", RECV},
{ "refresh", REFRESH },
{ "reject", REJECT},
{ "remote-as", REMOTEAS},
{ "restart", RESTART},
{ "restricted", RESTRICTED},
{ "rib", RIB},
{ "roa-set", ROASET },
{ "role", ROLE},
{ "route-reflector", REFLECTOR},
{ "router-id", ROUTERID},
{ "rtable", RTABLE},
{ "rtlabel", RTLABEL},
{ "rtr", RTR},
{ "self", SELF},
{ "send", SEND},
{ "set", SET},
{ "socket", SOCKET },
{ "source-as", SOURCEAS},
{ "spi", SPI},
{ "static", STATIC},
{ "tcp", TCP},
{ "to", TO},
{ "tos", TOS},
{ "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, nitems(keywords), 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
expand_macro(void)
{
char buf[MACRO_NAME_LEN];
char *p, *val;
int c;
p = buf;
while (1) {
if ((c = lgetc('$')) == EOF)
return (ERROR);
if (p + 1 >= buf + sizeof(buf) - 1) {
yyerror("macro name too long");
return (ERROR);
}
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 (ERROR);
}
p = val + strlen(val) - 1;
lungetc(DONE_EXPAND);
while (p >= val) {
lungetc((unsigned char)*p);
p--;
}
lungetc(START_EXPAND);
return (0);
}
int
yylex(void)
{
char buf[8096];
char *p;
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) {
c = expand_macro();
if (c != 0)
return (c);
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((unsigned char)*--p);
c = (unsigned char)*--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 {
if (c == '$' && !expanding) {
c = expand_macro();
if (c != 0)
return (c);
} else
*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 = kr_default_prio();
c->default_tableid = getrtable();
if (!ktable_exists(c->default_tableid, &rdomid))
fatalx("current routing table %u does not exist",
c->default_tableid);
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 rtr_config_head *rh)
{
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;
cur_rtrs = rh;
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;
curflow = 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);
}
/* Create default listeners if none where specified. */
if (TAILQ_EMPTY(conf->listen_addrs)) {
struct listen_addr *la;
if ((la = calloc(1, sizeof(struct listen_addr))) == NULL)
fatal("setup_listeners calloc");
la->fd = -1;
la->flags = DEFAULT_LISTENER;
la->reconf = RECONF_REINIT;
la->sa_len = sizeof(struct sockaddr_in);
((struct sockaddr_in *)&la->sa)->sin_family = AF_INET;
((struct sockaddr_in *)&la->sa)->sin_addr.s_addr =
htonl(INADDR_ANY);
((struct sockaddr_in *)&la->sa)->sin_port = htons(BGP_PORT);
TAILQ_INSERT_TAIL(conf->listen_addrs, la, entry);
if ((la = calloc(1, sizeof(struct listen_addr))) == NULL)
fatal("setup_listeners calloc");
la->fd = -1;
la->flags = DEFAULT_LISTENER;
la->reconf = RECONF_REINIT;
la->sa_len = sizeof(struct sockaddr_in6);
((struct sockaddr_in6 *)&la->sa)->sin6_family = AF_INET6;
((struct sockaddr_in6 *)&la->sa)->sin6_port = htons(BGP_PORT);
TAILQ_INSERT_TAIL(conf->listen_addrs, la, entry);
}
/* 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
cmpcommunity(struct community *a, struct community *b)
{
if (a->flags > b->flags)
return 1;
if (a->flags < b->flags)
return -1;
if (a->data1 > b->data1)
return 1;
if (a->data1 < b->data1)
return -1;
if (a->data2 > b->data2)
return 1;
if (a->data2 < b->data2)
return -1;
if (a->data3 > b->data3)
return 1;
if (a->data3 < b->data3)
return -1;
return 0;
}
static int
getcommunity(char *s, int large, uint32_t *val, uint32_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, uint32_t as, uint32_t data,
uint32_t asflag, uint32_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;
uint32_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;
uint32_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, uint32_t *v, uint32_t *flag)
{
const char *errstr;
char *p;
struct in_addr ip;
uint32_t uvalh, uval;
if (type != -1) {
/* nothing */
} else if (strcmp(s, "neighbor-as") == 0) {
*flag = COMMUNITY_NEIGHBOR_AS;
*v = 0;
return EXT_COMMUNITY_TRANS_TWO_AS;
} else if (strcmp(s, "local-as") == 0) {
*flag = COMMUNITY_LOCAL_AS;
*v = 0;
return EXT_COMMUNITY_TRANS_TWO_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 & EXT_COMMUNITY_VALUE) {
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;
uint64_t ullval;
uint32_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 EXT_COMMUNITY_GEN_TWO_AS:
case EXT_COMMUNITY_GEN_FOUR_AS:
case EXT_COMMUNITY_GEN_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:
case EXT_COMMUNITY_GEN_TWO_AS:
if (getcommunity(p, 1, &uval2, &dflag2) == -1)
return (-1);
break;
case EXT_COMMUNITY_TRANS_IPV4:
case EXT_COMMUNITY_TRANS_FOUR_AS:
case EXT_COMMUNITY_GEN_IPV4:
case EXT_COMMUNITY_GEN_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;
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.capabilities.refresh = 1;
p->conf.capabilities.grestart.restart = 1;
p->conf.capabilities.as4byte = 1;
p->conf.capabilities.policy = 1;
p->conf.local_as = conf->as;
p->conf.local_short_as = conf->short_as;
p->conf.remote_port = BGP_PORT;
if (conf->flags & BGPD_FLAG_DECISION_TRANS_AS)
p->conf.flags |= PEERFLAG_TRANS_AS;
if (conf->flags & BGPD_FLAG_DECISION_ALL_PATHS)
p->conf.flags |= PEERFLAG_EVALUATE_ALL;
if (conf->flags & BGPD_FLAG_NO_AS_SET)
p->conf.flags |= PEERFLAG_NO_AS_SET;
return (p);
}
struct peer *
new_peer(void)
{
struct peer *p;
p = alloc_peer();
if (curgroup != NULL) {
memcpy(p, curgroup, sizeof(struct peer));
p->conf.groupid = curgroup->conf.id;
}
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;
n->state = MRT_STATE_OPEN;
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 = p->conf.groupid;
}
}
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)) {
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 uint32_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 (p->conf.remote_masklen ==
newpeer->conf.remote_masklen &&
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)
{
uint8_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);
}
static int
h2i(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
else if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
else if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
else
return -1;
}
int
str2key(char *s, char *dest, size_t max_len)
{
size_t i;
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++) {
int hi, lo;
hi = h2i(s[2 * i]);
lo = h2i(s[2 * i + 1]);
if (hi == -1 || lo == -1) {
yyerror("key must be specified in hex");
return (-1);
}
dest[i] = (hi << 4) | lo;
}
return (0);
}
int
neighbor_consistent(struct peer *p)
{
struct bgpd_addr *local_addr;
struct peer *xp;
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);
}
/* BGP role and RFC 9234 role are only valid for EBGP neighbors */
if (!p->conf.ebgp) {
p->conf.role = ROLE_NONE;
p->conf.capabilities.policy = 0;
} else if (p->conf.role == ROLE_NONE) {
/* no role, no policy capability */
p->conf.capabilities.policy = 0;
}
/* check for duplicate peer definitions */
RB_FOREACH(xp, peer_head, new_peers)
if (xp->conf.remote_masklen ==
p->conf.remote_masklen &&
memcmp(&xp->conf.remote_addr,
&p->conf.remote_addr,
sizeof(p->conf.remote_addr)) == 0)
break;
if (xp != NULL) {
char *descr = log_fmt_peer(&p->conf);
yyerror("duplicate %s", descr);
free(descr);
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:
switch (cmpcommunity(&s->action.community,
&t->action.community)) {
case -1:
TAILQ_INSERT_BEFORE(t, s, entry);
return;
case 0:
break;
case 1:
continue;
}
break;
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(uint32_t));
if (aset == NULL)
fatal(NULL);
curset = aset->set;
return 0;
}
static void
add_as_set(uint32_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 = "origin-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);
RB_INIT(&pset->roaitems);
return pset;
}
static void
add_roa_set(struct prefixset_item *npsi, uint32_t as, uint8_t max,
time_t expires)
{
struct roa *roa, *r;
if ((roa = calloc(1, sizeof(*roa))) == NULL)
fatal("add_roa_set");
roa->aid = npsi->p.addr.aid;
roa->prefixlen = npsi->p.len;
roa->maxlen = max;
roa->asnum = as;
roa->expires = expires;
switch (roa->aid) {
case AID_INET:
roa->prefix.inet = npsi->p.addr.v4;
break;
case AID_INET6:
roa->prefix.inet6 = npsi->p.addr.v6;
break;
default:
fatalx("Bad address family for roa_set address");
}
r = RB_INSERT(roa_tree, curroatree, roa);
if (r != NULL) {
/* just ignore duplicates */
if (r->expires != 0 && expires != 0 && expires > r->expires)
r->expires = expires;
free(roa);
}
}
static struct rtr_config *
get_rtr(struct bgpd_addr *addr)
{
struct rtr_config *n;
n = calloc(1, sizeof(*n));
if (n == NULL) {
yyerror("out of memory");
return NULL;
}
n->remote_addr = *addr;
strlcpy(n->descr, log_addr(addr), sizeof(currtr->descr));
return n;
}
static int
insert_rtr(struct rtr_config *new)
{
static uint32_t id;
struct rtr_config *r;
if (id == UINT32_MAX) {
yyerror("out of rtr session IDs");
return -1;
}
SIMPLEQ_FOREACH(r, &conf->rtrs, entry)
if (memcmp(&r->remote_addr, &new->remote_addr,
sizeof(r->remote_addr)) == 0 &&
r->remote_port == new->remote_port) {
yyerror("duplicate rtr session to %s:%u",
log_addr(&new->remote_addr), new->remote_port);
return -1;
}
if (cur_rtrs)
SIMPLEQ_FOREACH(r, cur_rtrs, entry)
if (memcmp(&r->remote_addr, &new->remote_addr,
sizeof(r->remote_addr)) == 0 &&
r->remote_port == new->remote_port) {
new->id = r->id;
break;
}
if (new->id == 0)
new->id = ++id;
SIMPLEQ_INSERT_TAIL(&conf->rtrs, currtr, entry);
return 0;
}
static int
merge_aspa_set(uint32_t as, struct aspa_tas_l *tas, time_t expires)
{
struct aspa_set *aspa, needle = { .as = as };
uint32_t i, num, *newtas;
aspa = RB_FIND(aspa_tree, &conf->aspa, &needle);
if (aspa == NULL) {
if ((aspa = calloc(1, sizeof(*aspa))) == NULL) {
yyerror("out of memory");
return -1;
}
aspa->as = as;
aspa->expires = expires;
RB_INSERT(aspa_tree, &conf->aspa, aspa);
}
if (MAX_ASPA_SPAS_COUNT - aspa->num <= tas->num) {
yyerror("too many providers for customer-as %u", as);
return -1;
}
num = aspa->num + tas->num;
newtas = recallocarray(aspa->tas, aspa->num, num, sizeof(uint32_t));
if (newtas == NULL) {
yyerror("out of memory");
return -1;
}
/* fill starting at the end since the tas list is reversed */
if (num > 0) {
for (i = num - 1; tas; tas = tas->next, i--)
newtas[i] = tas->as;
}
aspa->num = num;
aspa->tas = newtas;
/* take the longest expiry time, same logic as for ROA entries */
if (aspa->expires != 0 && expires != 0 && expires > aspa->expires)
aspa->expires = expires;
return 0;
}
static int
kw_casecmp(const void *k, const void *e)
{
return (strcasecmp(k, ((const struct keywords *)e)->k_name));
}
static int
map_tos(char *s, int *val)
{
/* DiffServ Codepoints and other TOS mappings */
const struct keywords toswords[] = {
{ "af11", IPTOS_DSCP_AF11 },
{ "af12", IPTOS_DSCP_AF12 },
{ "af13", IPTOS_DSCP_AF13 },
{ "af21", IPTOS_DSCP_AF21 },
{ "af22", IPTOS_DSCP_AF22 },
{ "af23", IPTOS_DSCP_AF23 },
{ "af31", IPTOS_DSCP_AF31 },
{ "af32", IPTOS_DSCP_AF32 },
{ "af33", IPTOS_DSCP_AF33 },
{ "af41", IPTOS_DSCP_AF41 },
{ "af42", IPTOS_DSCP_AF42 },
{ "af43", IPTOS_DSCP_AF43 },
{ "critical", IPTOS_PREC_CRITIC_ECP },
{ "cs0", IPTOS_DSCP_CS0 },
{ "cs1", IPTOS_DSCP_CS1 },
{ "cs2", IPTOS_DSCP_CS2 },
{ "cs3", IPTOS_DSCP_CS3 },
{ "cs4", IPTOS_DSCP_CS4 },
{ "cs5", IPTOS_DSCP_CS5 },
{ "cs6", IPTOS_DSCP_CS6 },
{ "cs7", IPTOS_DSCP_CS7 },
{ "ef", IPTOS_DSCP_EF },
{ "inetcontrol", IPTOS_PREC_INTERNETCONTROL },
{ "lowdelay", IPTOS_LOWDELAY },
{ "netcontrol", IPTOS_PREC_NETCONTROL },
{ "reliability", IPTOS_RELIABILITY },
{ "throughput", IPTOS_THROUGHPUT }
};
const struct keywords *p;
p = bsearch(s, toswords, nitems(toswords), sizeof(toswords[0]),
kw_casecmp);
if (p) {
*val = p->k_val;
return (1);
}
return (0);
}
static int
getservice(char *n)
{
struct servent *s;
s = getservbyname(n, "tcp");
if (s == NULL)
s = getservbyname(n, "udp");
if (s == NULL)
return -1;
return s->s_port;
}
static int
parse_flags(char *s)
{
const char *flags = FLOWSPEC_TCP_FLAG_STRING;
char *p, *q;
uint8_t f = 0;
if (curflow->type == FLOWSPEC_TYPE_FRAG) {
if (curflow->aid == AID_INET)
flags = FLOWSPEC_FRAG_STRING4;
else
flags = FLOWSPEC_FRAG_STRING6;
}
for (p = s; *p; p++) {
if ((q = strchr(flags, *p)) == NULL)
return -1;
f |= 1 << (q - flags);
}
return (f ? f : 0xff);
}
static void
component_finish(int type, uint8_t *data, int len)
{
uint8_t *last;
int i;
switch (type) {
case FLOWSPEC_TYPE_DEST:
case FLOWSPEC_TYPE_SOURCE:
/* nothing to do */
return;
default:
break;
}
i = 0;
do {
last = data + i;
i += FLOWSPEC_OP_LEN(*last) + 1;
} while (i < len);
*last |= FLOWSPEC_OP_EOL;
}
static struct flowspec_config *
flow_to_flowspec(struct flowspec_context *ctx)
{
struct flowspec_config *f;
int i, len = 0;
uint8_t aid;
switch (ctx->aid) {
case AID_INET:
aid = AID_FLOWSPECv4;
break;
case AID_INET6:
aid = AID_FLOWSPECv6;
break;
default:
return NULL;
}
for (i = FLOWSPEC_TYPE_MIN; i < FLOWSPEC_TYPE_MAX; i++)
if (ctx->components[i] != NULL)
len += ctx->complen[i] + 1;
f = flowspec_alloc(aid, len);
if (f == NULL)
return NULL;
len = 0;
for (i = FLOWSPEC_TYPE_MIN; i < FLOWSPEC_TYPE_MAX; i++)
if (ctx->components[i] != NULL) {
f->flow->data[len++] = i;
component_finish(i, ctx->components[i],
ctx->complen[i]);
memcpy(f->flow->data + len, ctx->components[i],
ctx->complen[i]);
len += ctx->complen[i];
}
return f;
}
static void
flow_free(struct flowspec_context *ctx)
{
int i;
for (i = 0; i < FLOWSPEC_TYPE_MAX; i++)
free(ctx->components[i]);
free(ctx);
}
static int
push_prefix(struct bgpd_addr *addr, uint8_t len)
{
void *data;
uint8_t *comp;
int complen, l;
if (curflow->components[curflow->addr_type] != NULL) {
yyerror("flowspec address already set");
return -1;
}
if (curflow->aid != addr->aid) {
yyerror("wrong address family for flowspec address");
return -1;
}
switch (curflow->aid) {
case AID_INET:
complen = PREFIX_SIZE(len);
data = &addr->v4;
break;
case AID_INET6:
/* IPv6 includes an offset byte */
complen = PREFIX_SIZE(len) + 1;
data = &addr->v6;
break;
default:
yyerror("unsupported address family for flowspec address");
return -1;
}
comp = malloc(complen);
if (comp == NULL) {
yyerror("out of memory");
return -1;
}
l = 0;
comp[l++] = len;
if (curflow->aid == AID_INET6)
comp[l++] = 0;
memcpy(comp + l, data, complen - l);
curflow->complen[curflow->addr_type] = complen;
curflow->components[curflow->addr_type] = comp;
return 0;
}
static int
push_binop(uint8_t binop, long long val)
{
uint8_t *comp;
int complen;
uint8_t u8;
if (val < 0 || val > 0xff) {
yyerror("unsupported value for flowspec bin_op");
return -1;
}
u8 = val;
complen = curflow->complen[curflow->type];
comp = realloc(curflow->components[curflow->type],
complen + 2);
if (comp == NULL) {
yyerror("out of memory");
return -1;
}
comp[complen++] = binop;
comp[complen++] = u8;
curflow->complen[curflow->type] = complen;
curflow->components[curflow->type] = comp;
return 0;
}
static uint8_t
component_numop(enum comp_ops op, int and, int len)
{
uint8_t flag = 0;
switch (op) {
case OP_EQ:
flag |= FLOWSPEC_OP_NUM_EQ;
break;
case OP_NE:
flag |= FLOWSPEC_OP_NUM_NOT;
break;
case OP_LE:
flag |= FLOWSPEC_OP_NUM_LE;
break;
case OP_LT:
flag |= FLOWSPEC_OP_NUM_LT;
break;
case OP_GE:
flag |= FLOWSPEC_OP_NUM_GE;
break;
case OP_GT:
flag |= FLOWSPEC_OP_NUM_GT;
break;
default:
fatalx("unsupported op");
}
switch (len) {
case 2:
flag |= 1 << FLOWSPEC_OP_LEN_SHIFT;
break;
case 4:
flag |= 2 << FLOWSPEC_OP_LEN_SHIFT;
break;
case 8:
flag |= 3 << FLOWSPEC_OP_LEN_SHIFT;
break;
}
if (and)
flag |= FLOWSPEC_OP_AND;
return flag;
}
static int
push_numop(enum comp_ops op, int and, long long val)
{
uint8_t *comp;
void *data;
uint32_t u32;
uint16_t u16;
uint8_t u8;
int len, complen;
if (val < 0 || val > 0xffffffff) {
yyerror("unsupported value for flowspec num_op");
return -1;
} else if (val <= 255) {
len = 1;
u8 = val;
data = &u8;
} else if (val <= 0xffff) {
len = 2;
u16 = htons(val);
data = &u16;
} else {
len = 4;
u32 = htonl(val);
data = &u32;
}
complen = curflow->complen[curflow->type];
comp = realloc(curflow->components[curflow->type],
complen + len + 1);
if (comp == NULL) {
yyerror("out of memory");
return -1;
}
comp[complen++] = component_numop(op, and, len);
memcpy(comp + complen, data, len);
complen += len;
curflow->complen[curflow->type] = complen;
curflow->components[curflow->type] = comp;
return 0;
}
static int
push_unary_numop(enum comp_ops op, long long val)
{
return push_numop(op, 0, val);
}
static int
push_binary_numop(enum comp_ops op, long long min, long long max)
{
switch (op) {
case OP_RANGE:
if (push_numop(OP_GE, 0, min) == -1)
return -1;
return push_numop(OP_LE, 1, max);
case OP_XRANGE:
if (push_numop(OP_LT, 0, min) == -1)
return -1;
return push_numop(OP_GT, 0, max);
default:
yyerror("unsupported binary flowspec num_op");
return -1;
}
}
struct icmptypeent {
const char *name;
u_int8_t type;
};
struct icmpcodeent {
const char *name;
u_int8_t type;
u_int8_t code;
};
static const struct icmptypeent icmp_type[] = {
{ "echoreq", ICMP_ECHO },
{ "echorep", ICMP_ECHOREPLY },
{ "unreach", ICMP_UNREACH },
{ "squench", ICMP_SOURCEQUENCH },
{ "redir", ICMP_REDIRECT },
{ "althost", ICMP_ALTHOSTADDR },
{ "routeradv", ICMP_ROUTERADVERT },
{ "routersol", ICMP_ROUTERSOLICIT },
{ "timex", ICMP_TIMXCEED },
{ "paramprob", ICMP_PARAMPROB },
{ "timereq", ICMP_TSTAMP },
{ "timerep", ICMP_TSTAMPREPLY },
{ "inforeq", ICMP_IREQ },
{ "inforep", ICMP_IREQREPLY },
{ "maskreq", ICMP_MASKREQ },
{ "maskrep", ICMP_MASKREPLY },
{ "trace", ICMP_TRACEROUTE },
{ "dataconv", ICMP_DATACONVERR },
{ "mobredir", ICMP_MOBILE_REDIRECT },
{ "ipv6-where", ICMP_IPV6_WHEREAREYOU },
{ "ipv6-here", ICMP_IPV6_IAMHERE },
{ "mobregreq", ICMP_MOBILE_REGREQUEST },
{ "mobregrep", ICMP_MOBILE_REGREPLY },
{ "skip", ICMP_SKIP },
{ "photuris", ICMP_PHOTURIS }
};
static const struct icmptypeent icmp6_type[] = {
{ "unreach", ICMP6_DST_UNREACH },
{ "toobig", ICMP6_PACKET_TOO_BIG },
{ "timex", ICMP6_TIME_EXCEEDED },
{ "paramprob", ICMP6_PARAM_PROB },
{ "echoreq", ICMP6_ECHO_REQUEST },
{ "echorep", ICMP6_ECHO_REPLY },
{ "groupqry", ICMP6_MEMBERSHIP_QUERY },
{ "listqry", MLD_LISTENER_QUERY },
{ "grouprep", ICMP6_MEMBERSHIP_REPORT },
{ "listenrep", MLD_LISTENER_REPORT },
{ "groupterm", ICMP6_MEMBERSHIP_REDUCTION },
{ "listendone", MLD_LISTENER_DONE },
{ "routersol", ND_ROUTER_SOLICIT },
{ "routeradv", ND_ROUTER_ADVERT },
{ "neighbrsol", ND_NEIGHBOR_SOLICIT },
{ "neighbradv", ND_NEIGHBOR_ADVERT },
{ "redir", ND_REDIRECT },
{ "routrrenum", ICMP6_ROUTER_RENUMBERING },
{ "wrureq", ICMP6_WRUREQUEST },
{ "wrurep", ICMP6_WRUREPLY },
{ "fqdnreq", ICMP6_FQDN_QUERY },
{ "fqdnrep", ICMP6_FQDN_REPLY },
{ "niqry", ICMP6_NI_QUERY },
{ "nirep", ICMP6_NI_REPLY },
{ "mtraceresp", MLD_MTRACE_RESP },
{ "mtrace", MLD_MTRACE },
{ "listenrepv2", MLDV2_LISTENER_REPORT },
};
static const struct icmpcodeent icmp_code[] = {
{ "net-unr", ICMP_UNREACH, ICMP_UNREACH_NET },
{ "host-unr", ICMP_UNREACH, ICMP_UNREACH_HOST },
{ "proto-unr", ICMP_UNREACH, ICMP_UNREACH_PROTOCOL },
{ "port-unr", ICMP_UNREACH, ICMP_UNREACH_PORT },
{ "needfrag", ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG },
{ "srcfail", ICMP_UNREACH, ICMP_UNREACH_SRCFAIL },
{ "net-unk", ICMP_UNREACH, ICMP_UNREACH_NET_UNKNOWN },
{ "host-unk", ICMP_UNREACH, ICMP_UNREACH_HOST_UNKNOWN },
{ "isolate", ICMP_UNREACH, ICMP_UNREACH_ISOLATED },
{ "net-prohib", ICMP_UNREACH, ICMP_UNREACH_NET_PROHIB },
{ "host-prohib", ICMP_UNREACH, ICMP_UNREACH_HOST_PROHIB },
{ "net-tos", ICMP_UNREACH, ICMP_UNREACH_TOSNET },
{ "host-tos", ICMP_UNREACH, ICMP_UNREACH_TOSHOST },
{ "filter-prohib", ICMP_UNREACH, ICMP_UNREACH_FILTER_PROHIB },
{ "host-preced", ICMP_UNREACH, ICMP_UNREACH_HOST_PRECEDENCE },
{ "cutoff-preced", ICMP_UNREACH, ICMP_UNREACH_PRECEDENCE_CUTOFF },
{ "redir-net", ICMP_REDIRECT, ICMP_REDIRECT_NET },
{ "redir-host", ICMP_REDIRECT, ICMP_REDIRECT_HOST },
{ "redir-tos-net", ICMP_REDIRECT, ICMP_REDIRECT_TOSNET },
{ "redir-tos-host", ICMP_REDIRECT, ICMP_REDIRECT_TOSHOST },
{ "normal-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NORMAL },
{ "common-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NOROUTE_COMMON },
{ "transit", ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS },
{ "reassemb", ICMP_TIMXCEED, ICMP_TIMXCEED_REASS },
{ "badhead", ICMP_PARAMPROB, ICMP_PARAMPROB_ERRATPTR },
{ "optmiss", ICMP_PARAMPROB, ICMP_PARAMPROB_OPTABSENT },
{ "badlen", ICMP_PARAMPROB, ICMP_PARAMPROB_LENGTH },
{ "unknown-ind", ICMP_PHOTURIS, ICMP_PHOTURIS_UNKNOWN_INDEX },
{ "auth-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_AUTH_FAILED },
{ "decrypt-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_DECRYPT_FAILED }
};
static const struct icmpcodeent icmp6_code[] = {
{ "admin-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN },
{ "noroute-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE },
{ "beyond-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_BEYONDSCOPE },
{ "addr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR },
{ "port-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOPORT },
{ "transit", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT },
{ "reassemb", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_REASSEMBLY },
{ "badhead", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER },
{ "nxthdr", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_NEXTHEADER },
{ "redironlink", ND_REDIRECT, ND_REDIRECT_ONLINK },
{ "redirrouter", ND_REDIRECT, ND_REDIRECT_ROUTER }
};
static int
geticmptypebyname(char *w, uint8_t aid)
{
size_t i;
switch (aid) {
case AID_INET:
for (i = 0; i < nitems(icmp_type); i++) {
if (!strcmp(w, icmp_type[i].name))
return (icmp_type[i].type);
}
break;
case AID_INET6:
for (i = 0; i < nitems(icmp6_type); i++) {
if (!strcmp(w, icmp6_type[i].name))
return (icmp6_type[i].type);
}
break;
}
return -1;
}
static int
geticmpcodebyname(u_long type, char *w, uint8_t aid)
{
size_t i;
switch (aid) {
case AID_INET:
for (i = 0; i < nitems(icmp_code); i++) {
if (type == icmp_code[i].type &&
!strcmp(w, icmp_code[i].name))
return (icmp_code[i].code);
}
break;
case AID_INET6:
for (i = 0; i < nitems(icmp6_code); i++) {
if (type == icmp6_code[i].type &&
!strcmp(w, icmp6_code[i].name))
return (icmp6_code[i].code);
}
break;
}
return -1;
}