6018 lines
133 KiB
Plaintext
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;
|
|
}
|