From eddc0ffdab239c61cc0e064b6ebd33dfadcef3cd Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Thu, 24 Aug 2023 03:04:58 +0200 Subject: [PATCH 1/2] Lib: Add functions for reading and writing of bytestrings Based on patch from Alexander Zubkov, thanks! --- conf/cf-lex.l | 42 +++++++------------- lib/string.h | 3 ++ lib/strtoul.c | 104 ++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 99 insertions(+), 50 deletions(-) diff --git a/conf/cf-lex.l b/conf/cf-lex.l index 9025a84d..965e1e3f 100644 --- a/conf/cf-lex.l +++ b/conf/cf-lex.l @@ -256,38 +256,22 @@ WHITE [ \t] } ({XIGIT}{2}){16,}|{XIGIT}{2}(:{XIGIT}{2}){15,}|hex:({XIGIT}{2}(:?{XIGIT}{2})*)? { - char *s, *sb = yytext; - size_t len = 0, i; - struct bytestring *bytes; - byte *b; + char *s = yytext; + struct bytestring *bs; - /* skip 'hex:' prefix */ - if (sb[0] == 'h' && sb[1] == 'e' && sb[2] == 'x' && sb[3] == ':') - sb += 4; + /* Skip 'hex:' prefix */ + if (s[0] == 'h' && s[1] == 'e' && s[2] == 'x' && s[3] == ':') + s += 4; - s = sb; - while (*s) { - len++; - s += 2; - if (*s == ':') - s++; - } - bytes = cfg_allocz(sizeof(*bytes) + len); + int len = bstrhextobin(s, NULL); + if (len < 0) + cf_error("Invalid hex string"); - bytes->length = len; - b = &bytes->data[0]; - s = sb; - errno = 0; - for (i = 0; i < len; i++) { - *b = bstrtobyte16(s); - if (errno == ERANGE) - cf_error("Invalid hex string"); - b++; - s += 2; - if (*s == ':') - s++; - } - cf_lval.bs = bytes; + bs = cfg_allocz(sizeof(struct bytestring) + len); + bs->length = bstrhextobin(s, bs->data); + ASSERT(bs->length == len); + + cf_lval.bs = bs; return BYTESTRING; } diff --git a/lib/string.h b/lib/string.h index 2829943d..161b7651 100644 --- a/lib/string.h +++ b/lib/string.h @@ -33,6 +33,9 @@ u64 bstrtoul10(const char *str, char **end); u64 bstrtoul16(const char *str, char **end); byte bstrtobyte16(const char *str); +int bstrhextobin(const char *s, byte *b); +int bstrbintohex(const byte *b, size_t len, char *buf, size_t size, char delim); + int patmatch(const byte *pat, const byte *str); static inline char *xbasename(const char *str) diff --git a/lib/strtoul.c b/lib/strtoul.c index a5b11f68..e0c0142f 100644 --- a/lib/strtoul.c +++ b/lib/strtoul.c @@ -25,7 +25,7 @@ bstrtoul10(const char *str, char **end) errno = ERANGE; return UINT64_MAX; } - + out *= 10; out += (**end) - '0'; } @@ -60,29 +60,91 @@ bstrtoul16(const char *str, char **end) return UINT64_MAX; } -byte -bstrtobyte16(const char *str) +static int +fromxdigit(char c) { - byte out = 0; - for (int i=0; i<2; i++) { - switch (str[i]) { - case '0' ... '9': - out *= 16; - out += str[i] - '0'; - break; - case 'a' ... 'f': - out *= 16; - out += str[i] + 10 - 'a'; - break; - case 'A' ... 'F': - out *= 16; - out += str[i] + 10 - 'A'; - break; - default: - errno = ERANGE; + switch (c) + { + case '0' ... '9': + return c - '0'; + case 'a' ... 'f': + return c + 10 - 'a'; + case 'A' ... 'F': + return c + 10 - 'A'; + default: + return -1; + } +} + +int +bstrhextobin(const char *s, byte *b) +{ + int len = 0; + int hi = 0; + + for (; *s; s++) + { + int v = fromxdigit(*s); + if (v < 0) + { + if (strchr(" :-", *s) && !hi) + continue; + else return -1; } + + if (len == INT32_MAX) + return -1; + + if (b) + { + if (!hi) + b[len] = (v << 4); + else + b[len] |= v; + } + + len += hi; + hi = !hi; } - return out; + return !hi ? len : -1; +} + +static char +toxdigit(uint b) +{ + if (b < 10) + return ('0' + b); + else if (b < 16) + return ('a' + b - 10); + else + return 0; +} + +int +bstrbintohex(const byte *b, size_t len, char *buf, size_t size, char delim) +{ + ASSERT(size >= 6); + char *bound = buf + size - 3; + + size_t i; + for (i = 0; i < len; i++) + { + if (buf > bound) + { + strcpy(buf - 4, "..."); + return -1; + } + + uint x = b[i]; + buf[0] = toxdigit(x >> 4); + buf[1] = toxdigit(x & 0xF); + buf[2] = delim; + buf += 3; + } + + buf[i ? -1 : 0] = 0; + + return 0; } From fc3547880aafad726509f0514df2d5e0bb140728 Mon Sep 17 00:00:00 2001 From: Alexander Zubkov Date: Thu, 24 Aug 2023 04:30:42 +0200 Subject: [PATCH 2/2] Filter: Add bytestring type - Rename BYTESTRING lexem to BYTETEXT, not to collide with 'bytestring' type name - Add bytestring type with id T_BYTESTRING (0x2c) - Add from_hex() filter function to create bytestring from hex string - Add filter test cases for bytestring type Minor changes by committer. --- bird-gdb.py | 1 + conf/cf-lex.l | 2 +- conf/confbase.Y | 2 +- filter/config.Y | 26 +++++++++++++++++--------- filter/data.c | 10 ++++++++++ filter/data.h | 2 ++ filter/f-inst.c | 15 +++++++++++++++ filter/f-inst.h | 1 + filter/test.conf | 30 ++++++++++++++++++++++++++++++ nest/config.Y | 2 +- proto/radv/config.Y | 4 ++-- 11 files changed, 81 insertions(+), 14 deletions(-) diff --git a/bird-gdb.py b/bird-gdb.py index 3cf65a9c..262035dc 100644 --- a/bird-gdb.py +++ b/bird-gdb.py @@ -34,6 +34,7 @@ class BIRDFValPrinter(BIRDPrinter): "T_IP": "ip", "T_NET": "net", "T_STRING": "s", + "T_BYTESTRING": "bs", "T_PATH_MASK": "path_mask", "T_PATH": "ad", "T_CLIST": "ad", diff --git a/conf/cf-lex.l b/conf/cf-lex.l index 965e1e3f..e789e864 100644 --- a/conf/cf-lex.l +++ b/conf/cf-lex.l @@ -272,7 +272,7 @@ WHITE [ \t] ASSERT(bs->length == len); cf_lval.bs = bs; - return BYTESTRING; + return BYTETEXT; } ({XIGIT}*::|({XIGIT}*:){3,})({XIGIT}*|{DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+) { diff --git a/conf/confbase.Y b/conf/confbase.Y index 3dd5fed7..da750d38 100644 --- a/conf/confbase.Y +++ b/conf/confbase.Y @@ -106,7 +106,7 @@ CF_DECLS %token VPN_RD %token CF_SYM_KNOWN CF_SYM_UNDEFINED %token TEXT -%token BYTESTRING +%token BYTETEXT %type ipa_scope %type expr bool pxlen4 diff --git a/filter/config.Y b/filter/config.Y index a1e5e9f1..e2d133a9 100644 --- a/filter/config.Y +++ b/filter/config.Y @@ -301,7 +301,7 @@ CF_DECLS CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN, ACCEPT, REJECT, ERROR, INT, BOOL, IP, TYPE, PREFIX, RD, PAIR, QUAD, EC, LC, - SET, STRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST, + SET, STRING, BYTESTRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST, IF, THEN, ELSE, CASE, FOR, IN, DO, TRUE, FALSE, RT, RO, UNKNOWN, GENERIC, @@ -317,13 +317,14 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN, MIN, MAX, EMPTY, FILTER, WHERE, EVAL, ATTRIBUTE, + FROM_HEX, BT_ASSERT, BT_TEST_SUITE, BT_CHECK_ASSIGN, BT_TEST_SAME, FORMAT) %nonassoc THEN %nonassoc ELSE %type cmds_int cmd_prep -%type term cmd cmd_var cmds cmds_scoped constant constructor print_list var var_init var_list function_call symbol_value bgp_path_expr bgp_path bgp_path_tail +%type term term_bs cmd cmd_var cmds cmds_scoped constant constructor print_list var var_init var_list function_call symbol_value bgp_path_expr bgp_path bgp_path_tail %type dynamic_attr %type static_attr %type filter where_filter @@ -404,6 +405,7 @@ type: | EC { $$ = T_EC; } | LC { $$ = T_LC; } | STRING { $$ = T_STRING; } + | BYTESTRING { $$ = T_BYTESTRING; } | BGPMASK { $$ = T_PATH_MASK; } | BGPPATH { $$ = T_PATH; } | CLIST { $$ = T_CLIST; } @@ -714,13 +716,14 @@ bgp_path_tail: ; constant: - NUM { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_INT, .val.i = $1, }); } - | TRUE { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 1, }); } - | FALSE { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 0, }); } - | TEXT { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_STRING, .val.s = $1, }); } - | fipa { $$ = f_new_inst(FI_CONSTANT, $1); } - | VPN_RD { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_RD, .val.ec = $1, }); } - | net_ { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_NET, .val.net = $1, }); } + NUM { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_INT, .val.i = $1, }); } + | TRUE { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 1, }); } + | FALSE { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 0, }); } + | TEXT { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_STRING, .val.s = $1, }); } + | BYTETEXT { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BYTESTRING, .val.bs = $1, }); } + | fipa { $$ = f_new_inst(FI_CONSTANT, $1); } + | VPN_RD { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_RD, .val.ec = $1, }); } + | net_ { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_NET, .val.net = $1, }); } | '[' ']' { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_SET, .val.t = NULL, }); } | '[' set_items ']' { DBG( "We've got a set here..." ); @@ -868,9 +871,14 @@ term: /* | term '.' LEN { $$->code = P('P','l'); } */ + | term_bs | function_call ; +term_bs: + FROM_HEX '(' term ')' { $$ = f_new_inst(FI_FROM_HEX, $3); } + ; + break_command: ACCEPT { $$ = F_ACCEPT; } | REJECT { $$ = F_REJECT; } diff --git a/filter/data.c b/filter/data.c index 56d746fd..1d740b0d 100644 --- a/filter/data.c +++ b/filter/data.c @@ -46,6 +46,7 @@ static const char * const f_type_str[] = { [T_IP] = "ip", [T_NET] = "prefix", [T_STRING] = "string", + [T_BYTESTRING] = "bytestring", [T_PATH_MASK] = "bgpmask", [T_PATH] = "bgppath", [T_CLIST] = "clist", @@ -222,6 +223,12 @@ val_compare(const struct f_val *v1, const struct f_val *v2) } } +static inline int +bs_same(const struct bytestring *bs1, const struct bytestring *bs2) +{ + return (bs1->length == bs2->length) && !memcmp(bs1->data, bs2->data, bs1->length); +} + static inline int pmi_same(const struct f_path_mask_item *mi1, const struct f_path_mask_item *mi2) { @@ -286,6 +293,8 @@ val_same(const struct f_val *v1, const struct f_val *v2) return 0; switch (v1->type) { + case T_BYTESTRING: + return bs_same(v1->val.bs, v2->val.bs); case T_PATH_MASK: return pm_same(v1->val.path_mask, v2->val.path_mask); case T_PATH_MASK_ITEM: @@ -585,6 +594,7 @@ val_format(const struct f_val *v, buffer *buf) case T_BOOL: buffer_puts(buf, v->val.i ? "TRUE" : "FALSE"); return; case T_INT: buffer_print(buf, "%u", v->val.i); return; case T_STRING: buffer_print(buf, "%s", v->val.s); return; + case T_BYTESTRING: bstrbintohex(v->val.bs->data, v->val.bs->length, buf2, 1000, ':'); buffer_print(buf, "%s", buf2); return; case T_IP: buffer_print(buf, "%I", v->val.ip); return; case T_NET: buffer_print(buf, "%N", v->val.net); return; case T_PAIR: buffer_print(buf, "(%u,%u)", v->val.i >> 16, v->val.i & 0xffff); return; diff --git a/filter/data.h b/filter/data.h index b3767f7b..f44e08e1 100644 --- a/filter/data.h +++ b/filter/data.h @@ -58,6 +58,7 @@ enum f_type { T_LCLIST = 0x29, /* Large community list */ T_RD = 0x2a, /* Route distinguisher for VPN addresses */ T_PATH_MASK_ITEM = 0x2b, /* Path mask item for path mask constructors */ + T_BYTESTRING = 0x2c, T_SET = 0x80, T_PREFIX_SET = 0x81, @@ -73,6 +74,7 @@ struct f_val { ip_addr ip; const net_addr *net; const char *s; + const struct bytestring *bs; const struct f_tree *t; const struct f_trie *ti; const struct adata *ad; diff --git a/filter/f-inst.c b/filter/f-inst.c index 33436853..8a2f474e 100644 --- a/filter/f-inst.c +++ b/filter/f-inst.c @@ -1555,6 +1555,21 @@ } + INST(FI_FROM_HEX, 1, 1) { /* Convert hex text to bytestring */ + ARG(1, T_STRING); + + int len = bstrhextobin(v1.val.s, NULL); + if (len < 0) + runtime("Invalid hex string"); + + struct bytestring *bs; + bs = falloc(sizeof(struct bytestring) + len); + bs->length = bstrhextobin(v1.val.s, bs->data); + ASSERT(bs->length == (size_t) len); + + RESULT(T_BYTESTRING, bs, bs); + } + INST(FI_FORMAT, 1, 1) { /* Format */ ARG_ANY(1); RESULT(T_STRING, s, val_format_str(fpool, &v1)); diff --git a/filter/f-inst.h b/filter/f-inst.h index 72b080f8..3912df08 100644 --- a/filter/f-inst.h +++ b/filter/f-inst.h @@ -19,6 +19,7 @@ #include "filter/data.h" #include "lib/buffer.h" #include "lib/flowspec.h" +#include "lib/string.h" /* Flags for instructions */ enum f_instruction_flags { diff --git a/filter/test.conf b/filter/test.conf index e9e3af89..c014dadd 100644 --- a/filter/test.conf +++ b/filter/test.conf @@ -237,6 +237,36 @@ bt_test_suite(t_string, "Testing string matching"); +/* + * Testing bytestings + * ------------------ + */ + +function t_bytestring() +{ + bytestring bs1 = hex:; + bytestring bs2 = hex:0112233445566778899aabbccddeeff0; + + bt_assert(format(bs1) = ""); + bt_assert(format(bs2) = "01:12:23:34:45:56:67:78:89:9a:ab:bc:cd:de:ef:f0"); + bt_assert(hex:01:12:23:34:45:56:67:78:89:9a:ab:bc:cd:de:ef:f0 = bs2); + bt_assert(01:12:23:34:45:56:67:78:89:9a:ab:bc:cd:de:ef:f0 = bs2); + bt_assert(0112233445566778899aabbccddeeff0 = bs2); + bt_assert(hex:01234567 = hex:01:23:45:67); + bt_assert(hex:0123456789abcdef != bs2); + bt_assert(hex:0123456789abcdef != hex:0123); + bt_assert(format(hex:0123456789abcdef) = "01:23:45:67:89:ab:cd:ef"); + bt_assert(from_hex(" ") = bs1); + bt_assert(from_hex("01:12:23:34:45:56:67:78:89:9a:ab:bc:cd:de:ef:f0") = bs2); + bt_assert(from_hex(format(bs2)) = bs2); + bt_assert(from_hex(" 0112:23-34455667 78-89 - 9a-ab bc:cd : de:eff0 ") = bs2); +} + +bt_test_suite(t_bytestring, "Testing bytestrings"); + + + + /* * Testing pairs * ------------- diff --git a/nest/config.Y b/nest/config.Y index c83c715b..9197f985 100644 --- a/nest/config.Y +++ b/nest/config.Y @@ -548,7 +548,7 @@ pass_key: PASSWORD | KEY; password_item_begin: pass_key text { init_password_list(); init_password($2, strlen($2), password_id++); } - | pass_key BYTESTRING { init_password_list(); init_password($2->data, $2->length, password_id++); } + | pass_key BYTETEXT { init_password_list(); init_password($2->data, $2->length, password_id++); } ; password_item_params: diff --git a/proto/radv/config.Y b/proto/radv/config.Y index eeafe6f4..9653cd7b 100644 --- a/proto/radv/config.Y +++ b/proto/radv/config.Y @@ -73,7 +73,7 @@ radv_proto_item: | PREFIX radv_prefix { add_tail(&RADV_CFG->pref_list, NODE this_radv_prefix); } | RDNSS { init_list(&radv_dns_list); } radv_rdnss { add_tail_list(&RADV_CFG->rdnss_list, &radv_dns_list); } | DNSSL { init_list(&radv_dns_list); } radv_dnssl { add_tail_list(&RADV_CFG->dnssl_list, &radv_dns_list); } - | CUSTOM OPTION TYPE expr VALUE BYTESTRING { radv_add_to_custom_list(&RADV_CFG->custom_list, $4, $6); } + | CUSTOM OPTION TYPE expr VALUE BYTETEXT { radv_add_to_custom_list(&RADV_CFG->custom_list, $4, $6); } | TRIGGER net_ip6 { RADV_CFG->trigger = $2; } | PROPAGATE ROUTES bool { RADV_CFG->propagate_routes = $3; } ; @@ -138,7 +138,7 @@ radv_iface_item: | PREFIX radv_prefix { add_tail(&RADV_IFACE->pref_list, NODE this_radv_prefix); } | RDNSS { init_list(&radv_dns_list); } radv_rdnss { add_tail_list(&RADV_IFACE->rdnss_list, &radv_dns_list); } | DNSSL { init_list(&radv_dns_list); } radv_dnssl { add_tail_list(&RADV_IFACE->dnssl_list, &radv_dns_list); } - | CUSTOM OPTION TYPE expr VALUE BYTESTRING { radv_add_to_custom_list(&RADV_IFACE->custom_list, $4, $6); } + | CUSTOM OPTION TYPE expr VALUE BYTETEXT { radv_add_to_custom_list(&RADV_IFACE->custom_list, $4, $6); } | RDNSS LOCAL bool { RADV_IFACE->rdnss_local = $3; } | DNSSL LOCAL bool { RADV_IFACE->dnssl_local = $3; } | CUSTOM OPTION LOCAL bool { RADV_IFACE->custom_local = $4; }