From: claudio Date: Tue, 18 Apr 2023 12:11:27 +0000 (+0000) Subject: Implement the parser bits to process flowspec rules. Heavily inspired by X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=44f7a62c2a207c643aa322422e8d4722ec4a01e9;p=openbsd Implement the parser bits to process flowspec rules. Heavily inspired by pfctl, in bgpd flowspec rules are written like pf rules (with a few exceptions / extensions). As a result not all flowspec features are available but that is OK. OK tb@ --- diff --git a/usr.sbin/bgpd/bgpd.h b/usr.sbin/bgpd/bgpd.h index 43bc6afbcf2..eb59cb3c088 100644 --- a/usr.sbin/bgpd/bgpd.h +++ b/usr.sbin/bgpd/bgpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: bgpd.h,v 1.471 2023/04/17 08:02:21 claudio Exp $ */ +/* $OpenBSD: bgpd.h,v 1.472 2023/04/18 12:11:27 claudio Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -231,6 +231,9 @@ SIMPLEQ_HEAD(l3vpn_head, l3vpn); struct network; TAILQ_HEAD(network_head, network); +struct flowspec_config; +RB_HEAD(flowspec_tree, flowspec_config); + struct prefixset; SIMPLEQ_HEAD(prefixset_head, prefixset); struct prefixset_item; @@ -271,6 +274,7 @@ struct roa { }; RB_HEAD(roa_tree, roa); +struct aspa_set; RB_HEAD(aspa_tree, aspa_set); struct set_table; @@ -287,6 +291,7 @@ struct bgpd_config { struct peer_head peers; struct l3vpn_head l3vpns; struct network_head networks; + struct flowspec_tree flowspecs; struct filter_head *filters; struct listen_addrs *listen_addrs; struct mrt_head *mrt; @@ -514,8 +519,23 @@ struct network_config { }; struct network { - struct network_config net; - TAILQ_ENTRY(network) entry; + struct network_config net; + TAILQ_ENTRY(network) entry; +}; + +struct flowspec { + uint16_t len; + uint8_t aid; + uint8_t pad; + uint8_t data[1]; +}; +#define FLOWSPEC_SIZE (offsetof(struct flowspec, data)) + +struct flowspec_config { + RB_ENTRY(flowspec_config) entry; + struct filter_set_head attrset; + struct flowspec *flow; + enum reconf_action reconf_action; }; enum rtr_error { @@ -1121,6 +1141,10 @@ extern const struct ext_comm_pairs iana_ext_comms[]; #define FLOWSPEC_TYPE_FLOW 13 #define FLOWSPEC_TYPE_MAX 14 +#define FLOWSPEC_TCP_FLAG_STRING "FSRPAUEW" +#define FLOWSPEC_FRAG_STRING4 "DIFL" +#define FLOWSPEC_FRAG_STRING6 " IFL" + struct filter_prefix { struct bgpd_addr addr; uint8_t op; @@ -1366,6 +1390,8 @@ int control_imsg_relay(struct imsg *, struct peer *); struct bgpd_config *new_config(void); void copy_config(struct bgpd_config *, struct bgpd_config *); void network_free(struct network *); +struct flowspec_config *flowspec_alloc(uint8_t, int); +void flowspec_free(struct flowspec_config *); void free_l3vpns(struct l3vpn_head *); void free_config(struct bgpd_config *); void free_prefixsets(struct prefixset_head *); @@ -1382,6 +1408,7 @@ void expand_networks(struct bgpd_config *, struct network_head *); RB_PROTOTYPE(prefixset_tree, prefixset_item, entry, prefixset_cmp); RB_PROTOTYPE(roa_tree, roa, entry, roa_cmp); RB_PROTOTYPE(aspa_tree, aspa_set, entry, aspa_cmp); +RB_PROTOTYPE(flowspec_tree, flowspec_config, entry, flowspec_config_cmp); /* kroute.c */ int kr_init(int *, uint8_t); diff --git a/usr.sbin/bgpd/config.c b/usr.sbin/bgpd/config.c index 025be0f0aa5..00f2379df73 100644 --- a/usr.sbin/bgpd/config.c +++ b/usr.sbin/bgpd/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.106 2022/12/28 21:30:15 jmc Exp $ */ +/* $OpenBSD: config.c,v 1.107 2023/04/18 12:11:27 claudio Exp $ */ /* * Copyright (c) 2003, 2004, 2005 Henning Brauer @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,7 @@ int host_ip(const char *, struct bgpd_addr *, uint8_t *); void free_networks(struct network_head *); +void free_flowspecs(struct flowspec_tree *); struct bgpd_config * new_config(void) @@ -53,6 +55,7 @@ new_config(void) /* init the various list for later */ RB_INIT(&conf->peers); TAILQ_INIT(&conf->networks); + RB_INIT(&conf->flowspecs); SIMPLEQ_INIT(&conf->l3vpns); SIMPLEQ_INIT(&conf->prefixsets); SIMPLEQ_INIT(&conf->originsets); @@ -105,6 +108,50 @@ free_networks(struct network_head *networks) } } +struct flowspec_config * +flowspec_alloc(uint8_t aid, int len) +{ + struct flowspec_config *conf; + struct flowspec *flow; + + flow = malloc(FLOWSPEC_SIZE + len); + if (flow == NULL) + return NULL; + memset(flow, 0, FLOWSPEC_SIZE); + + conf = calloc(1, sizeof(*conf)); + if (conf == NULL) { + free(flow); + return NULL; + } + + conf->flow = flow; + TAILQ_INIT(&conf->attrset); + flow->len = len; + flow->aid = aid; + + return conf; +} + +void +flowspec_free(struct flowspec_config *f) +{ + filterset_free(&f->attrset); + free(f->flow); + free(f); +} + +void +free_flowspecs(struct flowspec_tree *flowspecs) +{ + struct flowspec_config *f, *nf; + + RB_FOREACH_SAFE(f, flowspec_tree, flowspecs, nf) { + RB_REMOVE(flowspec_tree, flowspecs, f); + flowspec_free(f); + } +} + void free_l3vpns(struct l3vpn_head *l3vpns) { @@ -213,6 +260,7 @@ free_config(struct bgpd_config *conf) free_l3vpns(&conf->l3vpns); free_networks(&conf->networks); + free_flowspecs(&conf->flowspecs); filterlist_free(conf->filters); free_prefixsets(&conf->prefixsets); free_prefixsets(&conf->originsets); @@ -251,6 +299,7 @@ merge_config(struct bgpd_config *xconf, struct bgpd_config *conf) { struct listen_addr *nla, *ola, *next; struct peer *p, *np, *nextp; + struct flowspec_config *f, *nextf, *xf; /* * merge the freshly parsed conf into the running xconf @@ -316,6 +365,26 @@ merge_config(struct bgpd_config *xconf, struct bgpd_config *conf) free_networks(&xconf->networks); TAILQ_CONCAT(&xconf->networks, &conf->networks, entry); + /* + * Merge the flowspec statements. Mark the old ones for deletion + * which happens when the flowspec is sent to the RDE. + */ + RB_FOREACH(f, flowspec_tree, &xconf->flowspecs) + f->reconf_action = RECONF_DELETE; + + RB_FOREACH_SAFE(f, flowspec_tree, &conf->flowspecs, nextf) { + RB_REMOVE(flowspec_tree, &conf->flowspecs, f); + + xf = RB_INSERT(flowspec_tree, &xconf->flowspecs, f); + if (xf != NULL) { + filterset_free(&xf->attrset); + filterset_move(&f->attrset, &xf->attrset); + flowspec_free(f); + xf->reconf_action = RECONF_KEEP; + } else + f->reconf_action = RECONF_KEEP; + } + /* switch the l3vpn configs, first remove the old ones */ free_l3vpns(&xconf->l3vpns); SIMPLEQ_CONCAT(&xconf->l3vpns, &conf->l3vpns); @@ -668,3 +737,17 @@ aspa_cmp(struct aspa_set *a, struct aspa_set *b) } RB_GENERATE(aspa_tree, aspa_set, entry, aspa_cmp); + +static inline int +flowspec_config_cmp(struct flowspec_config *a, struct flowspec_config *b) +{ + if (a->flow->aid < b->flow->aid) + return -1; + if (a->flow->aid > b->flow->aid) + return 1; + + return flowspec_cmp(a->flow->data, a->flow->len, + b->flow->data, b->flow->len, a->flow->aid == AID_FLOWSPECv6); +} + +RB_GENERATE(flowspec_tree, flowspec_config, entry, flowspec_config_cmp); diff --git a/usr.sbin/bgpd/parse.y b/usr.sbin/bgpd/parse.y index cd3ae88857c..5696adca947 100644 --- a/usr.sbin/bgpd/parse.y +++ b/usr.sbin/bgpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.446 2023/04/05 08:37:21 claudio Exp $ */ +/* $OpenBSD: parse.y,v 1.447 2023/04/18 12:11:27 claudio Exp $ */ /* * Copyright (c) 2002, 2003, 2004 Henning Brauer @@ -28,7 +28,10 @@ #include #include #include +#include +#include #include +#include #include #include @@ -130,6 +133,14 @@ struct aspa_tas_l { uint8_t aid; }; +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); @@ -162,7 +173,15 @@ static void add_roa_set(struct prefixset_item *, uint32_t, uint8_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 struct bgpd_config *conf; static struct network_head *netconf; @@ -180,6 +199,7 @@ 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 { @@ -215,10 +235,11 @@ typedef struct { %} %token AS ROUTERID HOLDTIME YMIN LISTEN ON FIBUPDATE FIBPRIORITY RTABLE -%token NONE UNICAST VPN FLOWSPEC RD EXPORT EXPORTTRGT IMPORTTRGT DEFAULTROUTE +%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 CAPABILITIES REFRESH AS4BYTE CONNECTRETRY ENHANCED ADDPATH %token SEND RECV PLUS POLICY ROLE @@ -249,7 +270,7 @@ typedef struct { %type yesno inout restricted expires enforce %type validity aspa_validity %type addpathextra addpathmax -%type port +%type port proto_item tos length flag icmptype %type string %type address %type prefix addrspec @@ -282,6 +303,7 @@ grammar : /* empty */ | grammar rtr '\n' | grammar rib '\n' | grammar network '\n' + | grammar flowspec '\n' | grammar mrtdump '\n' | grammar conf_main '\n' | grammar l3vpn '\n' @@ -1139,6 +1161,118 @@ network : NETWORK prefix filter_set { } ; +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", @@ -1157,6 +1291,195 @@ port : NUMBER { } ; +flow_rules : /* empty */ + | flow_rules_l + ; + +flow_rules_l : flowrule + | flow_rules_l flowrule + ; + +flowrule : from + | to + | proto + | FLAGS { + curflow->type = FLOWSPEC_TYPE_TCP_FLAGS; + } flags + | icmpspec + | TOS tos { + curflow->type = FLOWSPEC_TYPE_DSCP; + if (push_unary_numop(OP_EQ, $2 >> 2) == -1) + YYERROR; + } + | LENGTH lengthspec { + curflow->type = FLOWSPEC_TYPE_PKT_LEN; + } + | FRAGMENT { + curflow->type = FLOWSPEC_TYPE_FRAG; + } flags; + ; + +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; } ; @@ -3206,7 +3529,9 @@ lookup(char *s) { "ext-community", EXTCOMMUNITY}, { "fib-priority", FIBPRIORITY}, { "fib-update", FIBUPDATE}, + { "flags", FLAGS}, { "flowspec", FLOWSPEC}, + { "fragment", FRAGMENT}, { "from", FROM}, { "group", GROUP}, { "holdtime", HOLDTIME}, @@ -3265,6 +3590,7 @@ lookup(char *s) { "prepend-neighbor", PREPEND_PEER}, { "prepend-self", PREPEND_SELF}, { "priority", PRIORITY}, + { "proto", PROTO}, { "provider-as", PROVIDERAS}, { "qualify", QUALIFY}, { "quick", QUICK}, @@ -3293,6 +3619,7 @@ lookup(char *s) { "static", STATIC}, { "tcp", TCP}, { "to", TO}, + { "tos", TOS}, { "transit-as", TRANSITAS}, { "transparent-as", TRANSPARENT}, { "ttl-security", TTLSECURITY}, @@ -3781,6 +4108,7 @@ parse_config(char *filename, struct peer_head *ph, struct rtr_config_head *rh) cur_peers = NULL; new_peers = NULL; netconf = NULL; + curflow = NULL; if (errors) { errors: @@ -5158,6 +5486,57 @@ merge_aspa_set(uint32_t as, struct aspa_tas_l *tas, time_t 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, sizeof(toswords)/sizeof(toswords[0]), + sizeof(toswords[0]), kw_casecmp); + + if (p) { + *val = p->k_val; + return (1); + } + return (0); +} + static int getservice(char *n) { @@ -5170,3 +5549,451 @@ getservice(char *n) 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 = 0; + + switch (type) { + case FLOWSPEC_TYPE_DEST: + case FLOWSPEC_TYPE_SOURCE: + /* nothing to do */ + return; + default: + break; + } + + 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 = 0; + + 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; + } + comp = malloc(complen); + if (comp == NULL) { + yyerror("out of memory"); + return -1; + } + + comp[l++] = len; + if (curflow->aid == AID_INET6) + comp[l++] = 0; + memcpy(comp + l, data, PREFIX_SIZE(len) - 1); + + 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) +{ + unsigned int i; + + switch (aid) { + case AID_INET: + for (i = 0; i < (sizeof(icmp_type) / sizeof(icmp_type[0])); + i++) { + if (!strcmp(w, icmp_type[i].name)) + return (icmp_type[i].type); + } + break; + case AID_INET6: + for (i = 0; i < (sizeof(icmp6_type) / sizeof(icmp6_type[0])); + 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) +{ + unsigned int i; + + switch (aid) { + case AID_INET: + for (i = 0; i < (sizeof(icmp_code) / sizeof(icmp_code[0])); + 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 < (sizeof(icmp6_code) / sizeof(icmp6_code[0])); + i++) { + if (type == icmp6_code[i].type && + !strcmp(w, icmp6_code[i].name)) + return (icmp6_code[i].code); + } + break; + } + return -1; +}