scanner: Introduce SCANSTATE_RATE

This is a first exclusive start condition, i.e. one which rejects
unscoped tokens. When tokenizing, flex all too easily falls back into
treating something as STRING when it could be split into tokens instead.
Via an exclusive start condition, the string-fallback can be disabled as
needed.

With rates in typical formatting <NUM><bytes-unit>/<time-unit>,
tokenizer result depended on whitespace placement. SCANSTATE_RATE forces
flex to split the string into tokens and fall back to JUNK upon failure.
For this to work, tokens which shall still be recognized must be enabled
in SCANSTATE_RATE (or all scopes denoted by '*'). This includes any
tokens possibly following SCANSTATE_RATE to please the parser's
lookahead behaviour.

Signed-off-by: Phil Sutter <phil@nwl.cc>
Reviewed-by: Florian Westphal <fw@strlen.de>
This commit is contained in:
Phil Sutter 2025-08-05 22:25:25 +02:00
parent 4b385c1982
commit 9d105581b5
8 changed files with 130 additions and 110 deletions

View File

@ -309,10 +309,6 @@ extern void time_print(uint64_t msec, struct output_ctx *octx);
extern struct error_record *time_parse(const struct location *loc,
const char *c, uint64_t *res);
extern struct error_record *rate_parse(const struct location *loc,
const char *str, uint64_t *rate,
uint64_t *unit);
struct limit_rate {
uint64_t rate, unit;
};

View File

@ -47,6 +47,7 @@ enum startcond_type {
PARSER_SC_META,
PARSER_SC_POLICY,
PARSER_SC_QUOTA,
PARSER_SC_RATE,
PARSER_SC_SCTP,
PARSER_SC_SECMARK,
PARSER_SC_TCP,

View File

@ -1488,67 +1488,6 @@ const struct datatype *set_datatype_alloc(const struct datatype *orig_dtype,
return dtype;
}
static struct error_record *time_unit_parse(const struct location *loc,
const char *str, uint64_t *unit)
{
if (strcmp(str, "second") == 0)
*unit = 1ULL;
else if (strcmp(str, "minute") == 0)
*unit = 1ULL * 60;
else if (strcmp(str, "hour") == 0)
*unit = 1ULL * 60 * 60;
else if (strcmp(str, "day") == 0)
*unit = 1ULL * 60 * 60 * 24;
else if (strcmp(str, "week") == 0)
*unit = 1ULL * 60 * 60 * 24 * 7;
else
return error(loc, "Wrong time format, expecting second, minute, hour, day or week");
return NULL;
}
static struct error_record *data_unit_parse(const struct location *loc,
const char *str, uint64_t *rate)
{
if (strcmp(str, "bytes") == 0)
*rate = 1ULL;
else if (strcmp(str, "kbytes") == 0)
*rate = 1024;
else if (strcmp(str, "mbytes") == 0)
*rate = 1024 * 1024;
else
return error(loc, "Wrong unit format, expecting bytes, kbytes or mbytes");
return NULL;
}
struct error_record *rate_parse(const struct location *loc, const char *str,
uint64_t *rate, uint64_t *unit)
{
const char *slash, *rate_str;
struct error_record *erec;
slash = strchr(str, '/');
if (!slash)
return error(loc, "wrong rate format, expecting {bytes,kbytes,mbytes}/{second,minute,hour,day,week}");
rate_str = strndup(str, slash - str);
if (!rate_str)
memory_allocation_error();
erec = data_unit_parse(loc, rate_str, rate);
free_const(rate_str);
if (erec != NULL)
return erec;
erec = time_unit_parse(loc, slash + 1, unit);
if (erec != NULL)
return erec;
return NULL;
}
static const struct symbol_table boolean_tbl = {
.base = BASE_DECIMAL,
.symbols = {

View File

@ -1113,6 +1113,7 @@ close_scope_osf : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_OSF); }
close_scope_policy : { scanner_pop_start_cond(nft->scanner, PARSER_SC_POLICY); };
close_scope_quota : { scanner_pop_start_cond(nft->scanner, PARSER_SC_QUOTA); };
close_scope_queue : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_QUEUE); };
close_scope_rate : { scanner_pop_start_cond(nft->scanner, PARSER_SC_RATE); };
close_scope_reject : { scanner_pop_start_cond(nft->scanner, PARSER_SC_STMT_REJECT); };
close_scope_reset : { scanner_pop_start_cond(nft->scanner, PARSER_SC_CMD_RESET); };
close_scope_rt : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_RT); };
@ -3557,7 +3558,7 @@ limit_stmt_alloc : LIMIT RATE
}
;
limit_stmt : limit_stmt_alloc limit_args
limit_stmt : limit_stmt_alloc limit_args close_scope_rate
;
limit_args : limit_mode limit_rate_pkts limit_burst_pkts
@ -3652,21 +3653,7 @@ limit_burst_bytes : /* empty */ { $$ = 0; }
| BURST NUM bytes_unit { $$ = $2 * $3; }
;
limit_rate_bytes : NUM STRING
{
struct error_record *erec;
uint64_t rate, unit;
erec = rate_parse(&@$, $2, &rate, &unit);
free_const($2);
if (erec != NULL) {
erec_queue(erec, state->msgs);
YYERROR;
}
$$.rate = rate * $1;
$$.unit = unit;
}
| NUM bytes_unit SLASH time_unit
limit_rate_bytes : NUM bytes_unit SLASH time_unit
{
$$.rate = $1 * $2;
$$.unit = $4;
@ -4897,7 +4884,7 @@ ct_obj_alloc : /* empty */
}
;
limit_config : RATE limit_mode limit_rate_pkts limit_burst_pkts
limit_config : RATE limit_mode limit_rate_pkts limit_burst_pkts close_scope_rate
{
struct limit *limit;
@ -4908,7 +4895,7 @@ limit_config : RATE limit_mode limit_rate_pkts limit_burst_pkts
limit->type = NFT_LIMIT_PKTS;
limit->flags = $2;
}
| RATE limit_mode limit_rate_bytes limit_burst_bytes
| RATE limit_mode limit_rate_bytes limit_burst_bytes close_scope_rate
{
struct limit *limit;

View File

@ -219,6 +219,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
%s SCANSTATE_META
%s SCANSTATE_POLICY
%s SCANSTATE_QUOTA
%x SCANSTATE_RATE
%s SCANSTATE_SCTP
%s SCANSTATE_SECMARK
%s SCANSTATE_TCP
@ -275,12 +276,12 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
"ge" { return GTE; }
">" { return GT; }
"gt" { return GT; }
"," { return COMMA; }
<*>"," { return COMMA; }
"." { return DOT; }
":" { return COLON; }
";" { return SEMICOLON; }
<*>";" { return SEMICOLON; }
"{" { return '{'; }
"}" { return '}'; }
<*>"}" { return '}'; }
"[" { return '['; }
"]" { return ']'; }
"(" { return '('; }
@ -297,7 +298,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
"or" { return '|'; }
"!" { return NOT; }
"not" { return NOT; }
"/" { return SLASH; }
<*>"/" { return SLASH; }
"-" { return DASH; }
"*" { return ASTERISK; }
"@" { scanner_push_start_cond(yyscanner, SCANSTATE_AT); return AT; }
@ -410,12 +411,12 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
"hooks" { return HOOKS; }
}
"counter" { scanner_push_start_cond(yyscanner, SCANSTATE_COUNTER); return COUNTER; }
<*>"counter" { scanner_push_start_cond(yyscanner, SCANSTATE_COUNTER); return COUNTER; }
<SCANSTATE_COUNTER,SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_STMT_SYNPROXY,SCANSTATE_EXPR_OSF,SCANSTATE_TUNNEL>"name" { return NAME; }
<SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT>"packets" { return PACKETS; }
<SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT,SCANSTATE_QUOTA>"bytes" { return BYTES; }
<SCANSTATE_LIMIT,SCANSTATE_QUOTA>"kbytes" { return KBYTES; }
<SCANSTATE_LIMIT,SCANSTATE_QUOTA>"mbytes" { return MBYTES; }
<SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT,SCANSTATE_RATE>"packets" { return PACKETS; }
<SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_RATE>"bytes" { return BYTES; }
<SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_RATE>"kbytes" { return KBYTES; }
<SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_RATE>"mbytes" { return MBYTES; }
"last" { scanner_push_start_cond(yyscanner, SCANSTATE_LAST); return LAST; }
<SCANSTATE_LAST>{
@ -428,7 +429,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
"rules" { return RULES; }
}
"log" { scanner_push_start_cond(yyscanner, SCANSTATE_STMT_LOG); return LOG; }
<*>"log" { scanner_push_start_cond(yyscanner, SCANSTATE_STMT_LOG); return LOG; }
<SCANSTATE_STMT_LOG,SCANSTATE_STMT_NAT,SCANSTATE_IP,SCANSTATE_IP6>"prefix" { return PREFIX; }
<SCANSTATE_STMT_LOG>{
"snaplen" { return SNAPLEN; }
@ -453,8 +454,8 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
"fanout" { return FANOUT;}
}
"limit" { scanner_push_start_cond(yyscanner, SCANSTATE_LIMIT); return LIMIT; }
<SCANSTATE_LIMIT>{
"rate" { return RATE; }
<SCANSTATE_LIMIT,SCANSTATE_RATE>{
"rate" { scanner_push_start_cond(yyscanner, SCANSTATE_RATE); return RATE; }
"burst" { return BURST; }
/* time_unit */
@ -462,17 +463,17 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
"minute" { return MINUTE; }
"week" { return WEEK; }
}
<SCANSTATE_CT,SCANSTATE_LIMIT,SCANSTATE_QUOTA>"over" { return OVER; }
<SCANSTATE_CT,SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_RATE>"over" { return OVER; }
"quota" { scanner_push_start_cond(yyscanner, SCANSTATE_QUOTA); return QUOTA; }
<SCANSTATE_QUOTA>{
<SCANSTATE_QUOTA,SCANSTATE_RATE>{
"until" { return UNTIL; }
}
<SCANSTATE_QUOTA,SCANSTATE_LAST>"used" { return USED; }
"hour" { return HOUR; }
"day" { return DAY; }
<*>"hour" { return HOUR; }
<*>"day" { return DAY; }
"reject" { scanner_push_start_cond(yyscanner, SCANSTATE_STMT_REJECT); return _REJECT; }
<SCANSTATE_STMT_REJECT>{
@ -901,7 +902,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
return STRING;
}
{hexstring} {
<*>{hexstring} {
errno = 0;
yylval->val = strtoull(yytext, NULL, 16);
if (errno != 0) {
@ -911,7 +912,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
return NUM;
}
{decstring} {
<*>{decstring} {
int base = yytext[0] == '0' ? 8 : 10;
char *end;
@ -945,32 +946,32 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
return STRING;
}
{newline_crlf} { return CRLF; }
<*>{newline_crlf} { return CRLF; }
\\{newline} {
<*>\\{newline} {
reset_pos(yyget_extra(yyscanner), yylloc);
}
{newline} {
<*>{newline} {
reset_pos(yyget_extra(yyscanner), yylloc);
return NEWLINE;
}
{tab}+
{space}+
{comment_line} {
<*>{tab}+
<*>{space}+
<*>{comment_line} {
reset_pos(yyget_extra(yyscanner), yylloc);
}
{comment}
<*>{comment}
<<EOF>> {
<*><<EOF>> {
update_pos(yyget_extra(yyscanner), yylloc, 1);
scanner_pop_buffer(yyscanner);
if (YY_CURRENT_BUFFER == NULL)
return TOKEN_EOF;
}
. { return JUNK; }
<*>. { return JUNK; }
%%

View File

@ -49,3 +49,9 @@ limit rate over 10230 mbytes/second;ok
limit rate over 1025 bytes/second burst 512 bytes;ok
limit rate over 1025 kbytes/second burst 1023 kbytes;ok
limit rate over 1025 mbytes/second burst 1025 kbytes;ok
limit rate over 1025bytes/second burst 512bytes;ok;limit rate over 1025 bytes/second burst 512 bytes
limit rate over 1025bytes /second burst 512bytes;ok;limit rate over 1025 bytes/second burst 512 bytes
limit rate over 1025bytes/ second burst 512bytes;ok;limit rate over 1025 bytes/second burst 512 bytes
limit rate over 1025bytes / second burst 512bytes;ok;limit rate over 1025 bytes/second burst 512 bytes
limit rate over 1025 bytes / second burst 512bytes;ok;limit rate over 1025 bytes/second burst 512 bytes

View File

@ -360,3 +360,73 @@
}
}
]
# limit rate over 1025bytes/second burst 512bytes
[
{
"limit": {
"burst": 512,
"burst_unit": "bytes",
"inv": true,
"per": "second",
"rate": 1025,
"rate_unit": "bytes"
}
}
]
# limit rate over 1025bytes /second burst 512bytes
[
{
"limit": {
"burst": 512,
"burst_unit": "bytes",
"inv": true,
"per": "second",
"rate": 1025,
"rate_unit": "bytes"
}
}
]
# limit rate over 1025bytes/ second burst 512bytes
[
{
"limit": {
"burst": 512,
"burst_unit": "bytes",
"inv": true,
"per": "second",
"rate": 1025,
"rate_unit": "bytes"
}
}
]
# limit rate over 1025bytes / second burst 512bytes
[
{
"limit": {
"burst": 512,
"burst_unit": "bytes",
"inv": true,
"per": "second",
"rate": 1025,
"rate_unit": "bytes"
}
}
]
# limit rate over 1025 bytes / second burst 512bytes
[
{
"limit": {
"burst": 512,
"burst_unit": "bytes",
"inv": true,
"per": "second",
"rate": 1025,
"rate_unit": "bytes"
}
}
]

View File

@ -122,3 +122,23 @@ ip test-ip4 output
# limit rate over 1025 mbytes/second burst 1025 kbytes
ip test-ip4 output
[ limit rate 1074790400/second burst 1049600 type bytes flags 0x1 ]
# limit rate over 1025bytes/second burst 512bytes
ip test-ip4 output
[ limit rate 1025/second burst 512 type bytes flags 0x1 ]
# limit rate over 1025bytes /second burst 512bytes
ip test-ip4 output
[ limit rate 1025/second burst 512 type bytes flags 0x1 ]
# limit rate over 1025bytes/ second burst 512bytes
ip test-ip4 output
[ limit rate 1025/second burst 512 type bytes flags 0x1 ]
# limit rate over 1025bytes / second burst 512bytes
ip test-ip4 output
[ limit rate 1025/second burst 512 type bytes flags 0x1 ]
# limit rate over 1025 bytes / second burst 512bytes
ip test-ip4 output
[ limit rate 1025/second burst 512 type bytes flags 0x1 ]