From ea8bf6d8fc44d18f17586171f7f792a3d52fc7ac Mon Sep 17 00:00:00 2001 From: Jan Moskyto Matejka Date: Tue, 4 Jul 2017 14:49:37 +0200 Subject: [PATCH] Flowspec: action formatting --- conf/confbase.Y | 1 + conf/gen_keywords.m4 | 5 +++ conf/gen_parser.m4 | 2 + lib/flowspec.c | 100 ++++++++++++++++++++++++++++++++++++++++++ lib/flowspec.h | 27 ++++++++++++ lib/printf.c | 11 +++++ nest/rt-show.c | 59 +++++++++++++++++++++++-- proto/static/config.Y | 66 +++++++++++++++++++++++++--- 8 files changed, 262 insertions(+), 9 deletions(-) diff --git a/conf/confbase.Y b/conf/confbase.Y index 901ca2b2..fc4945b8 100644 --- a/conf/confbase.Y +++ b/conf/confbase.Y @@ -39,6 +39,7 @@ CF_DECLS uint i; u32 i32; u64 i64; + float fl; ip_addr a; ip4_addr ip4; ip6_addr ip6; diff --git a/conf/gen_keywords.m4 b/conf/gen_keywords.m4 index cf3fb58e..3566338c 100644 --- a/conf/gen_keywords.m4 +++ b/conf/gen_keywords.m4 @@ -25,9 +25,14 @@ m4_define(CF_DEFINES, `m4_divert(-1)') # Keywords are translated to C initializers m4_define(CF_handle_kw, `m4_divert(1){ "m4_translit($1,[[A-Z]],[[a-z]])", $1, NULL }, m4_divert(-1)') +m4_define(CF_handle_kw_cs, `m4_divert(1){ "$1", $1, NULL }, +m4_divert(-1)') m4_define(CF_keywd, `m4_ifdef([[CF_tok_$1]],,[[m4_define([[CF_tok_$1]],1)CF_handle_kw($1)]])') +m4_define(CF_keywd_cs, `m4_ifdef([[CF_tok_$1]],,[[m4_define([[CF_tok_$1]],1)CF_handle_kw_cs($1)]])') m4_define(CF_KEYWORDS, `m4_define([[CF_toks]],[[]])CF_iterate([[CF_keywd]], [[$@]])m4_ifelse(CF_toks,,,%token[[]]CF_toks )DNL') +m4_define(CF_KEYWORDS_CS, `m4_define([[CF_toks]],[[]])CF_iterate([[CF_keywd_cs]], [[$@]])m4_ifelse(CF_toks,,,%token[[]]CF_toks +)DNL') # CLI commands generate keywords as well m4_define(CF_CLI, `CF_KEYWORDS(m4_translit($1, [[ ]], [[,]])) diff --git a/conf/gen_parser.m4 b/conf/gen_parser.m4 index 00b55023..80e6e63a 100644 --- a/conf/gen_parser.m4 +++ b/conf/gen_parser.m4 @@ -33,6 +33,8 @@ m4_define(CF_iterate, `m4_define([[CF_iter]], m4_defn([[$1]]))CF_itera($2)') m4_define(CF_keywd, `m4_ifdef([[CF_tok_$1]],,[[m4_define([[CF_tok_$1]],1)m4_define([[CF_toks]],CF_toks $1)]])') m4_define(CF_KEYWORDS, `m4_define([[CF_toks]],[[]])CF_iterate([[CF_keywd]], [[$@]])m4_ifelse(CF_toks,,,%token[[]]CF_toks )DNL') +m4_define(CF_KEYWORDS_CS, `m4_define([[CF_toks]],[[]])CF_iterate([[CF_keywd]], [[$@]])m4_ifelse(CF_toks,,,%token[[]]CF_toks +)DNL') # Dynamic syntax rules m4_define(CF_dyn_rules,) diff --git a/lib/flowspec.c b/lib/flowspec.c index dd6a2cba..cdf80ba3 100644 --- a/lib/flowspec.c +++ b/lib/flowspec.c @@ -80,6 +80,22 @@ flow_type_str(enum flow_type type, int ipv6) return ipv6 ? flow6_type_str[type] : flow4_type_str[type]; } +const char * +flow_action_str(uint action) +{ +#define C(c, s) case c: return s + switch(action) { + C(FLOW_ACTION_TRAFFIC_BYTERATE, "byterate"); + C(FLOW_ACTION_TRAFFIC_ACTION, "action"); + C(FLOW_ACTION_REDIRECT_AS2, "redirect"); + C(FLOW_ACTION_REDIRECT_AS4, "redirect"); + C(FLOW_ACTION_REDIRECT_IP4, "redirect"); + C(FLOW_ACTION_TRAFFIC_MARKING, "mark"); + } +#undef C + return NULL; +} + /* * Length */ @@ -1174,3 +1190,87 @@ flow6_net_format(char *buf, uint blen, const net_addr_flow6 *f, const char *sep) { return net_format_flow(buf, blen, f->data, f->length - sizeof(net_addr_flow6), 1, sep); } + +/* Action */ + +u64 +flow_action_encode_byterate(u16 asn, float rate) +{ + u32 urate; + memcpy(&urate, &rate, 4); + return flow_action_encode(FLOW_ACTION_TRAFFIC_BYTERATE, (((u64) asn) << 32) | ((u64) urate)); +} + +u64 +flow_action_encode_redirect(u64 asn, u64 val) +{ + if (asn < 0x10000) + return (((u64) FLOW_ACTION_REDIRECT_AS2) << 48) | (asn << 32) | (val & 0xffffffff); + else + return (((u64) FLOW_ACTION_REDIRECT_AS4) << 48) | (asn << 16) | (val & 0xffff); +} + + +/** + * Flow actions are encoded as BGP Extended Community. + */ +uint +flow_action_format_part(char *buf, uint blen, u64 ec) +{ + int type = (ec >> 48); + switch (type) { + case FLOW_ACTION_TRAFFIC_BYTERATE: + { + float rate; + u32 urate = (ec & 0xffffffff); + memcpy(&rate, &urate, sizeof(rate)); + int asn = (ec >> 32) & 0xffff; + + rate *= 8; /* Convert from byterate to bitrate */ + const char *rs; + if (rate < 2) { + rs = "mbps"; rate *= 1000; + } else if (rate < 2000) { + rs = "bps"; + } else if (rate < 2000000) { + rs = "kbps"; rate /= 1000; + } else if (rate < 2000000000) { + rs = "Mbps"; rate /= 1000000; + } else if (rate < 2000000000000) { + rs = "Gbps"; rate /= 1000000000; + } else { + rs = "Tbps"; rate /= 1000000000000; + } + + return bsnprintf(buf, blen, "rate %.3f %s asn %u;", rate, rs, asn); + } + case FLOW_ACTION_TRAFFIC_ACTION: + { + int total = 0; + if (ec & FLOW_ACTION_LAST) { + int cnt = bsnprintf(buf, blen, "last;"); + if (cnt < 0) + return -1; + ADVANCE(buf, blen, cnt); + total += cnt; + } + + if (ec & FLOW_ACTION_SAMPLE) { + int cnt = bsnprintf(buf, blen, "sample;"); + if (cnt < 0) + return -1; + ADVANCE(buf, blen, cnt); + total += cnt; + } + return total; + } + case FLOW_ACTION_REDIRECT_AS2: + return bsnprintf(buf, blen, "rt %d,%d;", ((ec >> 32) & 0xffff), (ec & 0xffffffff)); + case FLOW_ACTION_REDIRECT_AS4: + return bsnprintf(buf, blen, "rt %d,%d;", ((ec >> 16) & 0xffffffff), (ec & 0xffff)); + case FLOW_ACTION_TRAFFIC_MARKING: + return bsnprintf(buf, blen, "dscp %d;", (ec & 0x3f)); + default: + return 0; + } +} diff --git a/lib/flowspec.h b/lib/flowspec.h index 90438b5b..b8e52028 100644 --- a/lib/flowspec.h +++ b/lib/flowspec.h @@ -49,6 +49,33 @@ enum flow_type { const char *flow_type_str(enum flow_type type, int ipv6); +#define FLOW_ACTION_TRAFFIC_BYTERATE 0x8006 +#define FLOW_ACTION_TRAFFIC_ACTION 0x8007 +#define FLOW_ACTION_REDIRECT_AS2 0x8008 +#define FLOW_ACTION_REDIRECT_IP4 0x8108 /* Not supported yet */ +#define FLOW_ACTION_REDIRECT_AS4 0x8208 +#define FLOW_ACTION_TRAFFIC_MARKING 0x8009 + +#define FLOW_ACTION_LAST 0x000000000001ULL +#define FLOW_ACTION_SAMPLE 0x000000000002ULL + +const char *flow_action_str(uint action); + +static inline u64 flow_action_encode(u16 key, u64 value) +{ return value | (((u64) key) << 48); } +u64 flow_action_encode_byterate(u16 asn, float rate); +static inline u64 flow_action_encode_bitrate(u16 asn, float rate) +{ return flow_action_encode_byterate(asn, rate/8); } +static inline u64 flow_action_encode_sample(void) +{ return flow_action_encode(FLOW_ACTION_TRAFFIC_ACTION, FLOW_ACTION_SAMPLE); } +static inline u64 flow_action_encode_last(void) +{ return flow_action_encode(FLOW_ACTION_TRAFFIC_ACTION, FLOW_ACTION_LAST); } +u64 flow_action_encode_redirect(u64 asn, u64 val); +static inline u64 flow_action_encode_dscp(u64 dscp) +{ return (dscp & 0x3f) | (((u64) FLOW_ACTION_TRAFFIC_MARKING) << 48); } + + +uint flow_action_format_part(char *buf, uint blen, u64 ec); /* * Length diff --git a/lib/printf.c b/lib/printf.c index 8e3cbbcf..af795c21 100644 --- a/lib/printf.c +++ b/lib/printf.c @@ -10,6 +10,7 @@ #include "nest/bird.h" #include "string.h" +#include #include #include "nest/iface.h" @@ -143,6 +144,8 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args) const char *s; char ipbuf[NET_MAX_TEXT_LENGTH+1]; struct iface *iface; + const char *percent; + char fmtbuf[strlen(fmt)]; int flags; /* flags to number() */ @@ -159,6 +162,8 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args) continue; } + percent = fmt; + /* process flags */ flags = 0; repeat: @@ -360,6 +365,12 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args) s = ipbuf; goto str; + /* float number formats delegated to stdio */ + case 'f': + memcpy(fmtbuf, percent, (fmt - percent) + 1); + snprintf(ipbuf, sizeof(ipbuf), fmtbuf, va_arg(args, double)); + s = ipbuf; + goto str; /* integer number formats - set up the flags and "break" */ case 'o': base = 8; diff --git a/nest/rt-show.c b/nest/rt-show.c index 981f74e2..c62ff913 100644 --- a/nest/rt-show.c +++ b/nest/rt-show.c @@ -16,6 +16,7 @@ #include "nest/iface.h" #include "filter/filter.h" #include "lib/flowspec.h" +#include "proto/bgp/bgp.h" static void rt_show_table(struct cli *c, struct rt_show_data *d) @@ -29,6 +30,46 @@ rt_show_table(struct cli *c, struct rt_show_data *d) d->last_table = d->tab; } +static void +rt_flow_action_format(char *buf, uint blen, rte *e) +{ + int cnt; + cnt = bsnprintf(buf, blen, "action {\n\t"); + ADVANCE(buf, blen, cnt); + + eattr *ea = ea_find(e->attrs->eattrs, EA_CODE(EAP_BGP, BA_EXT_COMMUNITY)); + if (!ea) goto noaction; + + u32 *data = int_set_get_data(ea->u.ptr); + int max = int_set_get_size(ea->u.ptr); + int empty = 1; + for (int i=0; inet->n.addr->type) { + case NET_FLOW4: + case NET_FLOW6: + rt_flow_action_format(ai, sizeof(ai) - 1, e); + break; + } + tm_format_datetime(tm, &config->tf_route, e->lastmod); if (ipa_nonzero(a->from) && !ipa_equal(a->from, a->nh.gw)) bsprintf(from, " from %I", a->from); @@ -64,7 +113,7 @@ rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, ea_list *tm if (d->last_table != d->tab) rt_show_table(c, d); - cli_printf(c, -1007, "%-18s %s [%s %s%s]%s%s", ia, rta_dest_name(a->dest), + cli_printf(c, -1007, "%-18s%s %s [%s %s%s]%s%s", ia, ai, rta_dest_name(a->dest), a->src->proto->name, tm, from, primary ? (sync_error ? " !" : " *") : "", info); if (a->dest == RTD_UNICAST) @@ -103,8 +152,12 @@ rt_show_net(struct cli *c, net *n, struct rt_show_data *d, const uint addr_type) switch (addr_type) { case NET_FLOW4: - flow4_net_format(ia, sizeof(ia), (net_addr_flow4 *) n->n.addr, "\n\t"); - break; + { + uint cnt = flow4_net_format(ia, sizeof(ia), (net_addr_flow4 *) n->n.addr, "\n\t"); + ia[cnt-2] = '}'; + ia[cnt-1] = '\n'; + break; + } case NET_FLOW6: flow6_net_format(ia, sizeof(ia), (net_addr_flow6 *) n->n.addr, "\n\t"); break; diff --git a/proto/static/config.Y b/proto/static/config.Y index 66ae3c98..bccd0c4c 100644 --- a/proto/static/config.Y +++ b/proto/static/config.Y @@ -34,6 +34,17 @@ static_nexthop_new(void) return nh; }; +static void +static_route_start(net_addr *n) +{ + this_srt = cfg_allocz(sizeof(struct static_route)); + add_tail(&STATIC_CFG->routes, &this_srt->n); + this_srt->net = n; + this_srt_last_cmd = &(this_srt->cmds); + this_srt->mp_next = NULL; + this_snh = NULL; +} + static void static_route_finish(void) { @@ -41,10 +52,30 @@ static_route_finish(void) cf_error("Unexpected or missing nexthop/type"); } +static void +static_flow_action(u64 ec) +{ + NEW_F_VAL; + + val->type = T_EC; val->val.ec = ec; + struct f_inst *fic = f_new_inst(); + fic->code = 'C'; fic->a1.p = val; + *this_srt_last_cmd = f_generate_complex( + P('C','a'), 'a', + f_new_dynamic_attr(EAF_TYPE_EC_SET, T_ECLIST, EA_CODE(EAP_BGP, BA_EXT_COMMUNITY)), + fic + ); + this_srt_last_cmd = &((*this_srt_last_cmd)->next); +} + CF_DECLS +%type float_rate + CF_KEYWORDS(STATIC, ROUTE, VIA, DROP, REJECT, PROHIBIT, PREFERENCE, CHECK, LINK) CF_KEYWORDS(WEIGHT, RECURSIVE, IGP, TABLE, BLACKHOLE, UNREACHABLE, BFD, MPLS) +CF_KEYWORDS(RATE, SAMPLE, LAST, DSCP) +CF_KEYWORDS_CS(mBps, mbps, Bps, bps, kBps, kbps, MBps, Mbps, GBps, Gbps, TBps, Tbps) CF_GRAMMAR @@ -102,12 +133,9 @@ stat_nexthops: ; stat_route0: ROUTE net_any { - this_srt = cfg_allocz(sizeof(struct static_route)); - add_tail(&STATIC_CFG->routes, &this_srt->n); - this_srt->net = $2; - this_srt_last_cmd = &(this_srt->cmds); - this_srt->mp_next = NULL; - this_snh = NULL; + if (net_type_match($2, NB_FLOW)) + cf_error("Flowspec rules are not routes. Apologize!"); + static_route_start($2); } ; @@ -128,6 +156,7 @@ stat_route: | stat_route0 BLACKHOLE { this_srt->dest = RTD_BLACKHOLE; } | stat_route0 UNREACHABLE { this_srt->dest = RTD_UNREACHABLE; } | stat_route0 PROHIBIT { this_srt->dest = RTD_PROHIBIT; } + | net_flow_ { static_route_start($1); } flowspec_action ; stat_route_item: @@ -144,6 +173,31 @@ stat_route_opt_list: | '{' stat_route_opts '}' ; +flowspec_action: ACTION '{' flowspec_action0 '}' ; + +flowspec_action0: + /* empty */ + | flowspec_action0 RATE float_rate ';' { static_flow_action(flow_action_encode_byterate(0, $3)); } + | flowspec_action0 SAMPLE ';' { static_flow_action(flow_action_encode_sample()); } + | flowspec_action0 LAST ';' { static_flow_action(flow_action_encode_last()); } + | flowspec_action0 RT cnum ',' cnum ';' { static_flow_action(flow_action_encode_redirect($3, $5)); } + | flowspec_action0 DSCP NUM ';' { static_flow_action(flow_action_encode_dscp($3)); } +; + +float_rate: + NUM mBps { $$ = $1 / 1000.0; } + | NUM mbps { $$ = $1 / 8000.0; } + | NUM Bps { $$ = $1; } + | NUM bps { $$ = $1 / 8.0; } + | NUM kBps { $$ = 1000.0 * $1; } + | NUM kbps { $$ = 1000.0 * $1 / 8.0; } + | NUM MBps { $$ = 1000000.0 * $1; } + | NUM Mbps { $$ = 1000000.0 * $1 / 8.0; } + | NUM GBps { $$ = 1000000000.0 * $1; } + | NUM Gbps { $$ = 1000000000.0 * $1 / 8.0; } + | NUM TBps { $$ = 1000000000000.0 * $1; } + | NUM Tbps { $$ = 1000000000000.0 * $1 / 8.0; } +; CF_CLI(SHOW STATIC, optsym, [], [[Show details of static protocol]]) { static_show(proto_get_named($3, &proto_static)); } ;