diff options
author | Jianfeng Tan <henry.tjf@antfin.com> | 2019-11-18 06:59:50 +0000 |
---|---|---|
committer | Jianfeng Tan <henry.tjf@antfin.com> | 2020-03-05 01:31:33 +0800 |
commit | 78c896b3b3127515478090c19447e27dc406427e (patch) | |
tree | d6d67d4683e9ca0409f9984a834547a572fb5310 /test/packetdrill/parser.y | |
parent | e4380f4866091fd92a7a57667dd938a99144f9cd (diff) |
TLDKv2dev-next-socket
Signed-off-by: Jianfeng Tan <henry.tjf@antfin.com>
Signed-off-by: Jielong Zhou <jielong.zjl@antfin.com>
Signed-off-by: Jian Zhang <wuzai.zj@antfin.com>
Signed-off-by: Chen Zhao <winters.zc@antfin.com>
Change-Id: I55c39de4c6cd30f991f35631eb507f770230f08e
Diffstat (limited to 'test/packetdrill/parser.y')
-rw-r--r-- | test/packetdrill/parser.y | 1739 |
1 files changed, 1739 insertions, 0 deletions
diff --git a/test/packetdrill/parser.y b/test/packetdrill/parser.y new file mode 100644 index 0000000..70219bd --- /dev/null +++ b/test/packetdrill/parser.y @@ -0,0 +1,1739 @@ +%{ +/* + * Copyright 2013 Google Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +/* + * Author: Author: ncardwell@google.com (Neal Cardwell) + * + * This is the parser for the packetdrill script language. It is + * processed by the bison parser generator. + * + * For full documentation see: http://www.gnu.org/software/bison/manual/ + * + * Here is a quick and dirty tutorial on bison: + * + * A bison parser specification is basically a BNF grammar for the + * language you are parsing. Each rule specifies a nonterminal symbol + * on the left-hand side and a sequence of terminal symbols (lexical + * tokens) and or nonterminal symbols on the right-hand side that can + * "reduce" to the symbol on the left hand side. When the parser sees + * the sequence of symbols on the right where it "wants" to see a + * nonterminal on the left, the rule fires, executing the semantic + * action code in curly {} braces as it reduces the right hand side to + * the left hand side. + * + * The semantic action code for a rule produces an output, which it + * can reference using the $$ token. The set of possible types + * returned in output expressions is given in the %union section of + * the .y file. The specific type of the output for a terminal or + * nonterminal symbol (corresponding to a field in the %union) is + * given by the %type directive in the .y file. The action code can + * access the outputs of the symbols on the right hand side by using + * the notation $1 for the first symbol, $2 for the second symbol, and + * so on. + * + * The lexer (generated by flex from lexer.l) feeds a stream of + * terminal symbols up to this parser. Parser semantic actions can + * access the lexer output for a terminal symbol with the same + * notation they use for nonterminals. + * + * Here's an example rule with its semantic action in {} braces: + * + * tcp_option + * ... + * | MSS INTEGER { + * $$ = tcp_option_new(...); + * ... + * $$->data.mss.bytes = htons($2); + * } + * + * This rule basically says: + * + * When the parser wants to see a tcp_option, if it sees an MSS from + * the lexer followed by an INTEGER from the lexer then run the + * action code that (a) stores in the output $$ a pointer to a + * struct tcp_option object, and then (b) stores in that object the + * value of the INTEGER token (accessed with $2). + * + */ + +/* The first part of the .y file consists of C code that bison copies + * directly into the top of the .c file it generates. + */ + +#include "types.h" + +#include <arpa/inet.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include "gre_packet.h" +#include "ip.h" +#include "ip_packet.h" +#include "icmp_packet.h" +#include "logging.h" +#include "mpls.h" +#include "mpls_packet.h" +#include "tcp_packet.h" +#include "udp_packet.h" +#include "parse.h" +#include "script.h" +#include "tcp.h" +#include "tcp_options.h" + +/* This include of the bison-generated .h file must go last so that we + * can first include all of the declarations on which it depends. + */ +#include "parser.h" + +/* Change this YYDEBUG to 1 to get verbose debug output for parsing: */ +#define YYDEBUG 0 +#if YYDEBUG +extern int yydebug; +#endif + +extern FILE *yyin; +extern int yylineno; +extern char *yytext; +extern int yylex(void); +extern int yyparse(void); +extern int yywrap(void); +extern const char *cleanup_cmd; + +/* This mutex guards all parser global variables declared in this file. */ +pthread_mutex_t parser_mutex = PTHREAD_MUTEX_INITIALIZER; + +/* The input to the parser: the path name of the script file to parse. */ +static const char* current_script_path = NULL; + +/* The starting line number of the input script statement that we're + * currently parsing. This may be different than yylineno if bison had + * to look ahead and lexically scan a token on the following line to + * decide that the current statement is done. + */ +static int current_script_line = -1; + +/* + * We uses this object to look up configuration info needed during + * parsing (such as whether packets are IPv4 or IPv6). + */ +struct config *in_config = NULL; + +/* The output of the parser: an output script containing + * 1) a linked list of options + * 2) a linked list of events + */ +static struct script *out_script = NULL; + +/* The test invocation to pass back to parse_and_finalize_config(). */ +struct invocation *invocation; + +/* Copy the script contents into our single linear buffer. */ +void copy_script(const char *script_buffer, struct script *script) +{ + DEBUGP("copy_script\n"); + + free(script->buffer); + script->length = strlen(script_buffer); + script->buffer = strdup(script_buffer); + assert(script->buffer != NULL); + + DEBUGP("copy_script: %d bytes\n", script->length); +} + +/* Read the script file into a single linear buffer. */ +void read_script(const char *script_path, struct script *script) +{ + int size = 0; + + DEBUGP("read_script(%s)\n", script_path); + + while (script->buffer == NULL) { + struct stat script_info; + int fd = -1; + + /* Allocate a buffer big enough for the whole file. */ + if (stat(script_path, &script_info) != 0) + die("parse error: stat() of script file '%s': %s\n", + script_path, strerror(errno)); + + /* Pick a buffer size larger than the file, so we'll + * know if the file grew. + */ + size = max((int)script_info.st_size, size) + 1; + + script->buffer = malloc(size); + assert(script->buffer != NULL); + + /* Read the file into our buffer. */ + fd = open(script_path, O_RDONLY); + if (fd < 0) + die("parse error opening script file '%s': %s\n", + script_path, strerror(errno)); + + script->length = read(fd, script->buffer, size); + if (script->length < 0) + die("parse error reading script file '%s': %s\n", + script_path, strerror(errno)); + + /* If we filled the buffer, then probably another + * process wrote more to the file since our stat call, + * so we should try again. + */ + if (script->length == size) { + free(script->buffer); + script->buffer = NULL; + script->length = 0; + } + + if (close(fd)) + die_perror("close"); + } + DEBUGP("read_script: %d bytes\n", script->length); +} + + +/* The public entry point for the script parser. Parses the + * text script file with the given path name and fills in the script + * object with the parsed representation. + */ +int parse_script(struct config *config, + struct script *script, + struct invocation *callback_invocation) +{ + /* This bison-generated parser is not multi-thread safe, so we + * have a lock to prevent more than one thread using the + * parser at the same time. This is useful in the wire server + * context, where in general we may have more than one test + * thread running at the same time. + */ + if (pthread_mutex_lock(&parser_mutex) != 0) + die_perror("pthread_mutex_lock"); + +#if YYDEBUG + yydebug = 1; +#endif + + /* Now parse the script from our buffer. */ + yyin = fmemopen(script->buffer, script->length, "r"); + if (yyin == NULL) + die_perror("fmemopen: parse error opening script buffer"); + + current_script_path = config->script_path; + in_config = config; + out_script = script; + invocation = callback_invocation; + + /* We have to reset the line number here since the wire server + * can do more than one yyparse(). + */ + yylineno = 1; + + int result = yyparse(); /* invoke bison-generated parser */ + current_script_path = NULL; + + if (fclose(yyin)) + die_perror("fclose: error closing script buffer"); + + /* Unlock parser. */ + if (pthread_mutex_unlock(&parser_mutex) != 0) + die_perror("pthread_mutex_unlock"); + + return result ? STATUS_ERR : STATUS_OK; +} + +/* Bison emits code to call this method when there's a parse-time error. + * We print the line number and the error message. + */ +static void yyerror(const char *message) +{ + fprintf(stderr, "%s:%d: parse error at '%s': %s\n", + current_script_path, yylineno, yytext, message); +} + +/* After we finish parsing each line of a script, we analyze the + * semantics of the line. If we encounter an error then we print the + * error message to stderr and exit with an error. + */ +static void semantic_error(const char* message) +{ + assert(current_script_line >= 0); + die("%s:%d: semantic error: %s\n", + current_script_path, current_script_line, message); +} + +/* This standard callback is invoked by flex when it encounters + * the end of a file. We return 1 to tell flex to return EOF. + */ +int yywrap(void) +{ + return 1; +} + +/* Create and initalize a new expression. */ +static struct expression *new_expression(enum expression_t type) +{ + struct expression *expression = calloc(1, sizeof(struct expression)); + expression->type = type; + return expression; +} + +/* Create and initalize a new integer expression with the given + * literal value and format string. + */ +static struct expression *new_integer_expression(s64 num, const char *format) +{ + struct expression *expression = new_expression(EXPR_INTEGER); + expression->value.num = num; + expression->format = format; + return expression; +} + +/* Create and initalize a new one-element expression_list. */ +static struct expression_list *new_expression_list( + struct expression *expression) +{ + struct expression_list *list; + list = calloc(1, sizeof(struct expression_list)); + list->expression = expression; + list->next = NULL; + return list; +} + +/* Add the expression to the end of the list. */ +static void expression_list_append(struct expression_list *list, + struct expression *expression) +{ + while (list->next != NULL) { + list = list->next; + } + list->next = new_expression_list(expression); +} + +/* Create and initialize a new option. */ +static struct option_list *new_option(char *name, char *value) +{ + struct option_list *opt = calloc(1, sizeof(struct option_list)); + opt->name = name; + opt->value = value; + return opt; +} + +/* Create and initialize a new event. */ +static struct event *new_event(enum event_t type) +{ + struct event *e = calloc(1, sizeof(struct event)); + e->type = type; + e->time_usecs_end = NO_TIME_RANGE; + e->offset_usecs = NO_TIME_RANGE; + return e; +} + +static int parse_hex_byte(const char *hex, u8 *byte) +{ + if (!isxdigit((int)hex[0]) || !isxdigit((int)hex[1])) { + return STATUS_ERR; /* need two hex digits per byte */ + } + char buf[] = { hex[0], hex[1], '\0' }; + char* buf_end = NULL; + u32 byte_value = strtoul(buf, &buf_end, 16); + assert(byte_value <= 0xff); + assert(buf_end == buf + 2); + *byte = byte_value; + return STATUS_OK; +} + +/* Converts a hex string in 'hex' into bytes and stores them in a + * buffer 'buf' of length 'buf_len' bytes; returns number of bytes in + * out_len. Works for hex strings of arbitrary size, such as very long + * TCP Fast Open cookies. + */ +static int parse_hex_string(const char *hex, u8 *buf, int buf_len, + int *out_len) +{ + u8 *out = buf; + u8 *buf_end = buf + buf_len; + while (hex[0] != '\0') { + if (out >= buf_end) { + return STATUS_ERR; /* ran out of output space */ + } + if (parse_hex_byte(hex, out)) + return STATUS_ERR; /* bad character */ + hex += 2; + out += 1; + } + *out_len = out - buf; + assert(*out_len <= buf_len); + return STATUS_OK; +} + +static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string, + char **error, bool exp) +{ + int cookie_string_len = strlen(cookie_string); + if (cookie_string_len & 1) { + asprintf(error, + "TCP fast open cookie has an odd number of digits"); + return NULL; + } + int cookie_bytes = cookie_string_len / 2; /* 2 hex chars per byte */ + int max_bytes = exp ? MAX_TCP_FAST_OPEN_EXP_COOKIE_BYTES : + MAX_TCP_FAST_OPEN_COOKIE_BYTES; + if (cookie_bytes > max_bytes) { + asprintf(error, "TCP fast open cookie too long"); + asprintf(error, "TCP fast open cookie of %d bytes " + "exceeds maximum cookie length of %d bytes", + cookie_bytes, max_bytes); + return NULL; + } + u8 option_bytes = cookie_bytes + (exp ? TCPOLEN_EXP_FASTOPEN_BASE : + TCPOLEN_FASTOPEN_BASE); + struct tcp_option *option; + option = tcp_option_new(exp ? TCPOPT_EXP : TCPOPT_FASTOPEN, + option_bytes); + if (exp) + option->data.fast_open_exp.magic = htons(TCPOPT_FASTOPEN_MAGIC); + + int parsed_bytes = 0; + /* Parse cookie. This should be an ASCII hex string + * representing an even number of bytes (4-16 bytes). But we + * do not enforce this, since we want to allow test cases that + * supply invalid cookies. + */ + if (parse_hex_string(cookie_string, + exp ? option->data.fast_open_exp.cookie : + option->data.fast_open.cookie, + exp ? sizeof(option->data.fast_open_exp.cookie): + sizeof(option->data.fast_open.cookie), + &parsed_bytes)) { + free(option); + asprintf(error, + "TCP fast open cookie '%s' is not a valid hex string", + cookie_string); + return NULL; + } + assert(parsed_bytes == cookie_bytes); + return option; +} + +static struct tcp_option *new_md5_option(const char *digest_string, + char **error) +{ + struct tcp_option *option; + int digest_string_len = strlen(digest_string); + int digest_bytes = digest_string_len / 2; + int parsed_bytes = 0; + + if (digest_bytes > TCP_MD5_DIGEST_LEN) { + asprintf(error, "TCP MD5 digest longer than 16 bytes"); + return NULL; + } + + option = tcp_option_new(TCPOPT_MD5SIG, TCPOLEN_MD5_BASE + digest_bytes); + + /* Parse MD5 digest. This should be an ASCII hex string representing 16 + * bytes. But we allow smaller buffers, since we want to allow test + * cases that supply invalid cookies. + */ + if (parse_hex_string(digest_string, + option->data.md5.digest, + sizeof(option->data.md5.digest), + &parsed_bytes)) { + free(option); + asprintf(error, "TCP MD5 digest is not a valid hex string"); + return NULL; + } + assert(parsed_bytes <= digest_bytes); + return option; +} + +static struct packet *append_gre(struct packet *packet, struct expression *expr) +{ + struct gre *gre = &expr->value.gre; + char *error = NULL; + if (gre_header_append(packet, gre, &error)) + semantic_error(error); + free(expr); + return packet; +} + +%} + +%locations +%expect 3 /* we expect shift/reduce conflicts */ +/* The %union section specifies the set of possible types for values + * for all nonterminal and terminal symbols in the grammar. + */ +%union { + s64 integer; + double floating; + char *string; + char *reserved; + s64 time_usecs; + enum direction_t direction; + enum ip_ecn_t ip_ecn; + struct tos_spec tos_spec; + struct ip_info ip_info; + struct mpls_stack *mpls_stack; + struct mpls mpls_stack_entry; + u16 port; + s32 window; + u16 urg_ptr; + u32 sequence_number; + struct { + int protocol; /* IPPROTO_TCP or IPPROTO_UDP */ + u32 start_sequence; + u16 payload_bytes; + } tcp_sequence_info; + struct option_list *option; + struct event *event; + struct packet *packet; + struct syscall_spec *syscall; + struct command_spec *command; + struct code_spec *code; + struct tcp_option *tcp_option; + struct tcp_options *tcp_options; + struct expression *expression; + struct expression_list *expression_list; + struct errno_spec *errno_info; + struct { + u16 src_port; + u16 dst_port; + } port_info; +} + +/* The specific type of the output for a symbol is given by the %type + * directive. By convention terminal symbols returned from the lexer + * have ALL_CAPS names, and nonterminal symbols have lower_case names. + */ +%token ELLIPSIS +%token <reserved> SA_FAMILY SIN_PORT SIN_ADDR _HTONS_ INET_ADDR INET6_ADDR +%token <reserved> MSG_NAME MSG_IOV MSG_FLAGS MSG_CONTROL +%token <reserved> CMSG_LEVEL CMSG_TYPE CMSG_DATA +%token <reserved> FD EVENTS REVENTS ONOFF LINGER +%token <reserved> U32 U64 PTR +%token <reserved> ACK ECR EOL MSS NOP SACK SACKOK TIMESTAMP VAL WIN WSCALE +%token <reserved> URG MD5 FAST_OPEN FAST_OPEN_EXP +%token <reserved> TOS FLAGS FLOWLABEL +%token <reserved> ECT0 ECT1 CE ECT01 NO_ECN +%token <reserved> IPV4 IPV6 ICMP UDP RAW GRE MTU ID +%token <reserved> MPLS LABEL TC TTL +%token <reserved> OPTION +%token <reserved> SUM OFF KEY SEQ +%token <reserved> NONE CHECKSUM SEQUENCE PRESENT +%token <reserved> EE_ERRNO EE_CODE EE_DATA EE_INFO EE_ORIGIN EE_TYPE +%token <reserved> SCM_SEC SCM_NSEC +%token <floating> FLOAT +%token <integer> INTEGER HEX_INTEGER +%token <string> WORD STRING BACK_QUOTED CODE IPV4_ADDR IPV6_ADDR +%type <direction> direction +%type <ip_info> ip_info opt_ip_info +%type <tos_spec> tos_spec +%type <ip_ecn> ip_ecn +%type <option> option options opt_options +%type <event> event events event_time action +%type <time_usecs> time opt_end_time +%type <packet> packet_spec tcp_packet_spec udp_packet_spec icmp_packet_spec +%type <packet> packet_prefix +%type <syscall> syscall_spec +%type <command> command_spec +%type <code> code_spec +%type <mpls_stack> mpls_stack +%type <mpls_stack_entry> mpls_stack_entry +%type <integer> opt_mpls_stack_bottom +%type <integer> opt_icmp_mtu +%type <integer> gre_flags_list gre_flags gre_flag +%type <integer> gre_sum gre_off gre_key gre_seq +%type <integer> opt_icmp_echo_id +%type <integer> flow_label +%type <string> icmp_type opt_icmp_code flags +%type <string> opt_tcp_fast_open_cookie hex_blob +%type <string> opt_note note word_list +%type <string> option_flag option_value script +%type <string> opt_comma opt_equals +%type <window> opt_window +%type <urg_ptr> opt_urg_ptr +%type <sequence_number> opt_ack +%type <tcp_sequence_info> seq opt_icmp_echoed +%type <tcp_options> opt_tcp_options tcp_option_list +%type <tcp_option> tcp_option sack_block_list sack_block +%type <string> function_name +%type <expression_list> expression_list function_arguments +%type <expression> expression binary_expression array sub_expr_list +%type <expression> any_int decimal_integer hex_integer +%type <expression> inaddr in6addr sockaddr msghdr iovec pollfd opt_revents linger +%type <expression> opt_cmsg cmsg_expr +%type <expression> scm_timestamping_expr +%type <expression> sock_extended_err_expr +%type <expression> mpls_stack_expression +%type <expression> gre_header_expression +%type <expression> epollev +%type <errno_info> opt_errno +%type <port_info> opt_port_info + +%% /* The grammar follows. */ + +script +: opt_options opt_init_command events opt_cleanup_command { + $$ = NULL; /* The parser output is in out_script */ +} +; + +opt_options +: { + $$ = NULL; + parse_and_finalize_config(invocation); +} +| options { + $$ = $1; + parse_and_finalize_config(invocation); +} +; + +options +: option { + out_script->option_list = $1; + $$ = $1; /* return the tail so we can append to it */ +} +| options option { + $1->next = $2; + $$ = $2; /* return the tail so we can append to it */ +} +; + +option +: option_flag '=' option_value { + $$ = new_option($1, $3); +} +| option_flag { + $$ = new_option($1, NULL); +} + +option_flag +: OPTION { $$ = $1; } +; + +option_value +: INTEGER { $$ = strdup(yytext); } +| WORD { $$ = $1; } +| STRING { $$ = $1; } +| IPV4_ADDR { $$ = $1; } +| IPV6_ADDR { $$ = $1; } +| IPV4 { $$ = strdup("ipv4"); } +| IPV6 { $$ = strdup("ipv6"); } +| WORD '=' WORD { + /* For consistency, allow syntax like: --define=PROTO=IPPROTO_TCP */ + char *lhs = $1, *rhs = $3; + + asprintf(&($$), "%s=%s", lhs, rhs); + free(lhs); + free(rhs); +} +| WORD '=' STRING { + /* For consistency, allow syntax like: --define=CC="reno" */ + char *lhs = $1, *rhs = $3; + + asprintf(&($$), "%s=\"%s\"", lhs, rhs); + free(lhs); + free(rhs); +} +| WORD '=' BACK_QUOTED { + /* For consistency, allow syntax like: --define=SCRIPT=`cleanup` */ + char *lhs = $1, *rhs = $3; + + asprintf(&($$), "%s=`%s`", lhs, rhs); + free(lhs); + free(rhs); +} +; + +opt_init_command +: { } +| init_command { } +; + +init_command +: command_spec { out_script->init_command = $1; } +; + +events +: event { + out_script->event_list = $1; /* save pointer to event list as output + * of parser */ + $$ = $1; /* return the tail so that we can append to it */ +} +| events event { + $1->next = $2; /* link new event to the end of the existing list */ + $$ = $2; /* return the tail so that we can append to it */ +} +; + +event +: event_time action { + $$ = $2; + $$->line_number = $1->line_number; /* use timestamp's line */ + $$->time_usecs = $1->time_usecs; + $$->time_usecs_end = $1->time_usecs_end; + $$->time_type = $1->time_type; + + if ($$->time_usecs_end != NO_TIME_RANGE) { + if ($$->time_usecs_end < $$->time_usecs) + semantic_error("time range is backwards"); + } + if ($$->time_type == ANY_TIME && ($$->type != PACKET_EVENT || + packet_direction($$->event.packet) != DIRECTION_OUTBOUND)) { + yylineno = $$->line_number; + semantic_error("event time <star> can only be used with " + "outbound packets"); + } else if (($$->time_type == ABSOLUTE_RANGE_TIME || + $$->time_type == RELATIVE_RANGE_TIME) && + ($$->type != PACKET_EVENT || + packet_direction($$->event.packet) != DIRECTION_OUTBOUND)) { + yylineno = $$->line_number; + semantic_error("event time range can only be used with " + "outbound packets"); + } + free($1); +} +; + +event_time +: '+' time { + $$ = new_event(INVALID_EVENT); + $$->line_number = @2.first_line; + $$->time_usecs = $2; + $$->time_type = RELATIVE_TIME; +} +| time { + $$ = new_event(INVALID_EVENT); + $$->line_number = @1.first_line; + $$->time_usecs = $1; + $$->time_type = ABSOLUTE_TIME; +} +| '*' { + $$ = new_event(INVALID_EVENT); + $$->line_number = @1.first_line; + $$->time_type = ANY_TIME; +} +| time '~' time { + $$ = new_event(INVALID_EVENT); + $$->line_number = @1.first_line; + $$->time_type = ABSOLUTE_RANGE_TIME; + $$->time_usecs = $1; + $$->time_usecs_end = $3; +} +| '+' time '~' '+' time { + $$ = new_event(INVALID_EVENT); + $$->line_number = @1.first_line; + $$->time_type = RELATIVE_RANGE_TIME; + $$->time_usecs = $2; + $$->time_usecs_end = $5; +} +; + +time +: FLOAT { + if ($1 < 0) { + semantic_error("negative time"); + } + $$ = (s64)($1 * 1.0e6); /* convert float secs to s64 microseconds */ +} +| INTEGER { + if ($1 < 0) { + semantic_error("negative time"); + } + $$ = (s64)($1 * 1000000); /* convert int secs to s64 microseconds */ +} +; + +action +: packet_spec { $$ = new_event(PACKET_EVENT); $$->event.packet = $1; } +| syscall_spec { $$ = new_event(SYSCALL_EVENT); $$->event.syscall = $1; } +| command_spec { $$ = new_event(COMMAND_EVENT); $$->event.command = $1; } +| code_spec { $$ = new_event(CODE_EVENT); $$->event.code = $1; } +; + +packet_spec +: tcp_packet_spec { $$ = $1; } +| udp_packet_spec { $$ = $1; } +| icmp_packet_spec { $$ = $1; } +; + +tcp_packet_spec +: packet_prefix opt_ip_info opt_port_info flags seq opt_ack opt_window opt_urg_ptr opt_tcp_options { + char *error = NULL; + struct packet *outer = $1, *inner = NULL; + enum direction_t direction = outer->direction; + + if (($2.tos.check == TOS_CHECK_ECN) && ($2.tos.value == ECN_ECT01) && + (direction != DIRECTION_OUTBOUND)) { + semantic_error("[ect01] can only be used with outbound packets"); + } + + if (($9 == NULL) && (direction != DIRECTION_OUTBOUND)) { + yylineno = @7.first_line; + semantic_error("<...> for TCP options can only be used with " + "outbound packets"); + } + + inner = new_tcp_packet(in_config->wire_protocol, + direction, $2, $3.src_port, $3.dst_port, $4, + $5.start_sequence, $5.payload_bytes, + $6, $7, $8, $9, &error); + free($4); + free($9); + if (inner == NULL) { + assert(error != NULL); + semantic_error(error); + free(error); + } + + $$ = packet_encapsulate_and_free(outer, inner); +} +; + +udp_packet_spec +: packet_prefix opt_ip_info UDP opt_port_info '(' INTEGER ')' { + char *error = NULL; + struct packet *outer = $1, *inner = NULL; + enum direction_t direction = outer->direction; + + if ($2.tos.check == TOS_CHECK_ECN) { + semantic_error("ECN can only be used with TCP packets"); + } + + if (!is_valid_u16($6)) { + semantic_error("UDP payload size out of range"); + } + + inner = new_udp_packet(in_config->wire_protocol, direction, $2, + $6, $4.src_port, $4.dst_port, &error); + if (inner == NULL) { + assert(error != NULL); + semantic_error(error); + free(error); + } + + $$ = packet_encapsulate_and_free(outer, inner); +} +; + +icmp_packet_spec +: packet_prefix opt_ip_info ICMP icmp_type opt_icmp_code opt_icmp_mtu + opt_icmp_echo_id opt_icmp_echoed { + char *error = NULL; + struct packet *outer = $1, *inner = NULL; + enum direction_t direction = outer->direction; + + if (($2.tos.check == TOS_CHECK_ECN) && ($2.tos.value == ECN_ECT01) && + (direction != DIRECTION_OUTBOUND)) { + semantic_error("[ect01] can only be used with outbound packets"); + } + + inner = new_icmp_packet(in_config->wire_protocol, direction, $4, $5, + $8.protocol, $8.start_sequence, + $8.payload_bytes, $2, $6, $7, &error); + free($4); + free($5); + if (inner == NULL) { + semantic_error(error); + free(error); + } + + $$ = packet_encapsulate_and_free(outer, inner); +} +; + + +packet_prefix +: direction { + $$ = packet_new(PACKET_MAX_HEADER_BYTES); + $$->direction = $1; +} +| packet_prefix IPV4 opt_ip_info IPV4_ADDR '>' IPV4_ADDR ':' { + char *error = NULL; + struct packet *packet = $1; + u8 tos = $3.tos.value; + u8 ttl = $3.ttl; + char *ip_src = $4; + char *ip_dst = $6; + if (ipv4_header_append(packet, ip_src, ip_dst, tos, ttl, &error)) + semantic_error(error); + free(ip_src); + free(ip_dst); + $$ = packet; +} +| packet_prefix IPV6 opt_ip_info IPV6_ADDR '>' IPV6_ADDR ':' { + char *error = NULL; + struct packet *packet = $1; + u8 tos = $3.tos.value; + u8 hop_limit = $3.ttl; + char *ip_src = $4; + char *ip_dst = $6; + if (ipv6_header_append(packet, ip_src, ip_dst, tos, hop_limit, &error)) + semantic_error(error); + free(ip_src); + free(ip_dst); + $$ = packet; +} +| packet_prefix GRE ':' { + struct packet *packet = $1; + struct expression *expr = new_expression(EXPR_GRE); + $$ = append_gre(packet, expr); +} +| packet_prefix GRE opt_comma gre_header_expression ':' { + struct packet *packet = $1; + struct expression *expr = $4; + $$ = append_gre(packet, expr); +} +| packet_prefix MPLS mpls_stack ':' { + char *error = NULL; + struct packet *packet = $1; + struct mpls_stack *mpls_stack = $3; + + if (mpls_header_append(packet, mpls_stack, &error)) + semantic_error(error); + free(mpls_stack); + $$ = packet; +} +; + +gre_header_expression +: gre_flags_list opt_comma + gre_sum opt_comma + gre_off opt_comma + gre_key opt_comma + gre_seq { + $$ = new_expression(EXPR_GRE); + $$->value.gre.flags = htons($1); + $$->value.gre.be16[0] = htons($3); + $$->value.gre.be16[1] = htons($5); + $$->value.gre.be32[1] = htonl($7); + $$->value.gre.be32[2] = htonl($9); +} +; + +gre_flags_list +: FLAGS '[' gre_flags ']' { $$ = $3; } +| FLAGS any_int { $$ = $2->value.num; } +; + +gre_flags +: gre_flag { $$ = $1; } +| gre_flag ',' gre_flags { $$ = $1 | $3; } +; + +gre_flag +: NONE { $$ = 0; } +| CHECKSUM PRESENT { $$ = GRE_FLAG_C; } +| KEY PRESENT { $$ = GRE_FLAG_K; } +| SEQUENCE PRESENT { $$ = GRE_FLAG_S; } +; + +gre_sum +: SUM any_int { $$ = $2->value.num; } +; + +gre_off +: OFF any_int { $$ = $2->value.num; } +; + +gre_key +: KEY opt_equals any_int { $$ = $3->value.num; } +; + +gre_seq +: SEQ any_int { $$ = $2->value.num; } +; + +opt_comma +: { $$ = NULL; } +| ',' { $$ = NULL; } +; + +opt_equals +: { $$ = NULL; } +| '=' { $$ = NULL; } +; + +mpls_stack +: { + $$ = mpls_stack_new(); +} +| mpls_stack mpls_stack_entry { + if (mpls_stack_append($1, $2)) + semantic_error("too many MPLS labels"); + $$ = $1; +} +; + +mpls_stack_entry +: +'(' LABEL INTEGER ',' TC INTEGER ',' opt_mpls_stack_bottom TTL INTEGER ')' { + char *error = NULL; + s64 label = $3; + s64 traffic_class = $6; + bool is_stack_bottom = $8; + s64 ttl = $10; + struct mpls mpls; + + if (new_mpls_stack_entry(label, traffic_class, is_stack_bottom, ttl, + &mpls, &error)) + semantic_error(error); + $$ = mpls; +} +; + +opt_mpls_stack_bottom +: { $$ = 0; } +| '[' WORD ']' ',' { + if (strcmp($2, "S") != 0) + semantic_error("expected [S] for MPLS label stack bottom"); + free($2); + $$ = 1; +} +; + +icmp_type +: WORD { $$ = $1; } +; + +opt_icmp_code +: { $$ = NULL; } +| WORD { $$ = $1; } +; + +/* This specifies the relevant details about the packet echoed by ICMP. */ +opt_icmp_echoed +: { + $$.start_sequence = 0; + $$.payload_bytes = 0; + $$.protocol = IPPROTO_TCP; +} +| '[' UDP '(' INTEGER ')' ']' { + $$.start_sequence = 0; + $$.payload_bytes = $4; + $$.protocol = IPPROTO_UDP; +} +| '[' seq ']' { + $$ = $2; +} +| '[' RAW '(' INTEGER ')' ']' { + $$.payload_bytes = $4; + $$.protocol = IPPROTO_RAW; +} +; + +opt_icmp_mtu +: { $$ = -1; } +| MTU INTEGER { $$ = $2; } +; + +opt_icmp_echo_id +: { $$ = 0; } +| ID INTEGER { $$ = $2; } +; + +opt_port_info +: { + $$.src_port = 0; + $$.dst_port = 0; +} +| INTEGER '>' INTEGER { + if (!is_valid_u16($1)) { + semantic_error("src port out of range"); + } + if (!is_valid_u16($3)) { + semantic_error("dst port out of range"); + } + + $$.src_port = $1; + $$.dst_port = $3; +} +; + +direction +: '<' { $$ = DIRECTION_INBOUND; current_script_line = yylineno; } +| '>' { $$ = DIRECTION_OUTBOUND; current_script_line = yylineno; } +; + +tos_spec +: ip_ecn { $$.check = TOS_CHECK_ECN; $$.value = $1; } +| TOS HEX_INTEGER { + s64 tos = $2; + + if (!is_valid_u8(tos)) { + semantic_error("tos out of range for 8 bits"); + } + + $$.check = TOS_CHECK_TOS; + $$.value = tos; +} +; + +ip_ecn +: NO_ECN { $$ = ECN_NONE; } +| ECT0 { $$ = ECN_ECT0; } +| ECT1 { $$ = ECN_ECT1; } +| ECT01 { $$ = ECN_ECT01; } +| CE { $$ = ECN_CE; } +; + +flags +: WORD { $$ = $1; } +| '.' { $$ = strdup("."); } +| WORD '.' { asprintf(&($$), "%s.", $1); free($1); } +| '-' { $$ = strdup(""); } /* no TCP flags set in segment */ +; + +flow_label +: FLOWLABEL HEX_INTEGER { + s64 flowlabel = $2; + + if (!is_valid_u20(flowlabel)) { + semantic_error("flowlabel out of range for 20 bits"); + } + $$ = flowlabel; +} +; + +ip_info +: tos_spec { + $$.tos.check = $1.check; + $$.tos.value = $1.value; + $$.flow_label = 0; + $$.ttl = 0; +} +| flow_label { + $$.tos.check = TOS_CHECK_NONE; + $$.tos.value = 0; + $$.flow_label = $1; + $$.ttl = 0; +} +| TTL INTEGER { + $$.tos.check = TOS_CHECK_NONE; + $$.tos.value = 0; + $$.flow_label = 0; + $$.ttl = $2; +} +| tos_spec ',' flow_label { + $$.tos.check = $1.check; + $$.tos.value = $1.value; + $$.flow_label = $3; + $$.ttl = 0; +} +; + +opt_ip_info +: { + $$.tos.check = TOS_CHECK_NONE; + $$.tos.value = 0; + $$.flow_label = 0; + $$.ttl = 0; +} +| '(' ip_info ')' { $$ = $2; } +| '[' ip_info ']' { $$ = $2; } +; + +seq +: INTEGER ':' INTEGER '(' INTEGER ')' { + if (!is_valid_u32($1)) { + semantic_error("TCP start sequence number out of range"); + } + if (!is_valid_u32($3)) { + semantic_error("TCP end sequence number out of range"); + } + if (!is_valid_u16($5)) { + semantic_error("TCP payload size out of range"); + } + if ($3 != ($1 +$5)) { + semantic_error("inconsistent TCP sequence numbers and " + "payload size"); + } + $$.start_sequence = $1; + $$.payload_bytes = $5; + $$.protocol = IPPROTO_TCP; +} +; + +opt_ack +: { $$ = 0; } +| ACK INTEGER { + if (!is_valid_u32($2)) { + semantic_error("TCP ack sequence number out of range"); + } + $$ = $2; +} +; + +opt_window +: { $$ = -1; } +| WIN INTEGER { + if (!is_valid_u16($2)) { + semantic_error("TCP window value out of range"); + } + $$ = $2; +} +; + +opt_urg_ptr +: { $$ = 0; } +| URG INTEGER { + if (!is_valid_u16($2)) { + semantic_error("urg_ptr value out of range"); + } + $$ = $2; +} +; + +opt_tcp_options +: { $$ = tcp_options_new(); } +| '<' tcp_option_list '>' { $$ = $2; } +| '<' ELLIPSIS '>' { $$ = NULL; /* FLAG_OPTIONS_NOCHECK */ } +; + +tcp_option_list +: tcp_option { + $$ = tcp_options_new(); + if (tcp_options_append($$, $1)) { + semantic_error("TCP option list too long"); + } +} +| tcp_option_list ',' tcp_option { + $$ = $1; + if (tcp_options_append($$, $3)) { + semantic_error("TCP option list too long"); + } +} +; + +opt_tcp_fast_open_cookie +: { $$ = strdup(""); } +| hex_blob { $$ = $1; } +; + +hex_blob +: WORD { $$ = $1; } +| INTEGER { $$ = strdup(yytext); } +; + +tcp_option +: NOP { $$ = tcp_option_new(TCPOPT_NOP, 1); } +| EOL { $$ = tcp_option_new(TCPOPT_EOL, 1); } +| MSS INTEGER { + $$ = tcp_option_new(TCPOPT_MAXSEG, TCPOLEN_MAXSEG); + if (!is_valid_u16($2)) { + semantic_error("mss value out of range"); + } + $$->data.mss.bytes = htons($2); +} +| WSCALE INTEGER { + $$ = tcp_option_new(TCPOPT_WINDOW, TCPOLEN_WINDOW); + if (!is_valid_u8($2)) { + semantic_error("window scale shift count out of range"); + } + $$->data.window_scale.shift_count = $2; +} +| SACKOK { + $$ = tcp_option_new(TCPOPT_SACK_PERMITTED, + TCPOLEN_SACK_PERMITTED); +} +| SACK sack_block_list { + $$ = $2; +} +| MD5 hex_blob { + char *error = NULL; + $$ = new_md5_option($2, &error); + free($2); + if ($$ == NULL) { + assert(error != NULL); + semantic_error(error); + free(error); + } +} +| TIMESTAMP VAL INTEGER ECR INTEGER { + u32 val, ecr; + $$ = tcp_option_new(TCPOPT_TIMESTAMP, TCPOLEN_TIMESTAMP); + if (!is_valid_u32($3)) { + semantic_error("ts val out of range"); + } + if (!is_valid_u32($5)) { + semantic_error("ecr val out of range"); + } + val = $3; + ecr = $5; + $$->data.time_stamp.val = htonl(val); + $$->data.time_stamp.ecr = htonl(ecr); +} +| FAST_OPEN opt_tcp_fast_open_cookie { + char *error = NULL; + $$ = new_tcp_fast_open_option($2, &error, false); + free($2); + if ($$ == NULL) { + assert(error != NULL); + semantic_error(error); + free(error); + } +} +| FAST_OPEN_EXP opt_tcp_fast_open_cookie { + char *error = NULL; + $$ = new_tcp_fast_open_option($2, &error, true); + free($2); + if ($$ == NULL) { + assert(error != NULL); + semantic_error(error); + free(error); + } +} +; + +sack_block_list +: sack_block { $$ = $1; } +| sack_block_list sack_block { + const int list_block_bytes = $1->length - 2; + assert(list_block_bytes > 0); + assert((list_block_bytes % sizeof(struct sack_block)) == 0); + const int num_blocks = list_block_bytes / sizeof(struct sack_block); + /* Append this SACK block to the end of the array of blocks. */ + memcpy($1->data.sack.block + num_blocks, $2->data.sack.block, + sizeof(struct sack_block)); + $1->length += sizeof(struct sack_block); + free($2); + $$ = $1; +} +; + +sack_block +: INTEGER ':' INTEGER { + $$ = tcp_option_new(TCPOPT_SACK, 2 + sizeof(struct sack_block)); + if (!is_valid_u32($1)) { + semantic_error("TCP SACK left sequence number out of range"); + } + if (!is_valid_u32($3)) { + semantic_error("TCP SACK right sequence number out of range"); + } + $$->data.sack.block[0].left = htonl($1); + $$->data.sack.block[0].right = htonl($3); +} +; + +syscall_spec +: opt_end_time function_name function_arguments '=' + expression opt_errno opt_note { + $$ = calloc(1, sizeof(struct syscall_spec)); + $$->end_usecs = $1; + $$->name = $2; + $$->arguments = $3; + $$->result = $5; + $$->error = $6; + $$->note = $7; +} +; + +opt_end_time +: { $$ = SYSCALL_NON_BLOCKING; } +| ELLIPSIS time { $$ = $2; } +; + +function_name +: WORD { $$ = $1; current_script_line = yylineno; } +; + +function_arguments +: '(' ')' { $$ = NULL; } +| '(' expression_list ')' { $$ = $2; } +; + +expression_list +: expression { $$ = new_expression_list($1); } +| expression_list ',' expression { $$ = $1; expression_list_append($1, $3); } +; + +expression +: ELLIPSIS { + $$ = new_expression(EXPR_ELLIPSIS); +} +| any_int { $$ = $1; } +| WORD { + $$ = new_expression(EXPR_WORD); + $$->value.string = $1; +} +| STRING { + $$ = new_expression(EXPR_STRING); + $$->value.string = $1; + $$->format = "\"%s\""; +} +| STRING ELLIPSIS { + $$ = new_expression(EXPR_STRING); + $$->value.string = $1; + $$->format = "\"%s\"..."; +} +| binary_expression { + $$ = $1; +} +| array { + $$ = $1; +} +| inaddr { + $$ = $1; +} +| in6addr { + $$ = $1; +} +| sockaddr { + $$ = $1; +} +| msghdr { + $$ = $1; +} +| iovec { + $$ = $1; +} +| pollfd { + $$ = $1; +} +| linger { + $$ = $1; +} +| mpls_stack_expression { + $$ = $1; +} +| cmsg_expr { + $$ = $1; +} +| scm_timestamping_expr { + $$ = $1; +} +| sub_expr_list { + $$ = $1; +} +| sock_extended_err_expr { + $$ = $1; +} +| '{' gre_header_expression '}' { + $$ = $2; +} +| epollev { + $$ = $1; +} +; + +any_int +: decimal_integer { $$ = $1; } +| hex_integer { $$ = $1; } +; + +decimal_integer +: INTEGER { + $$ = new_integer_expression($1, "%ld"); +} +; + +hex_integer +: HEX_INTEGER { + $$ = new_integer_expression($1, "%#lx"); +} +; + +binary_expression +: expression '|' expression { /* bitwise OR */ + $$ = new_expression(EXPR_BINARY); + struct binary_expression *binary = + malloc(sizeof(struct binary_expression)); + binary->op = strdup("|"); + binary->lhs = $1; + binary->rhs = $3; + $$->value.binary = binary; +} +| WORD '=' expression { /* symbol = value */ + $$ = new_expression(EXPR_BINARY); + struct binary_expression *binary = + malloc(sizeof(struct binary_expression)); + binary->op = strdup("="); + binary->lhs = new_expression(EXPR_WORD); + binary->lhs->value.string = $1; + binary->rhs = $3; + $$->value.binary = binary; +} +; + +array +: '[' ']' { + $$ = new_expression(EXPR_LIST); + $$->value.list = NULL; +} +| '[' expression_list ']' { + $$ = new_expression(EXPR_LIST); + $$->value.list = $2; +} +; + +inaddr +: INET_ADDR '(' STRING ')' { + __be32 ip_address = inet_addr($3); + $$ = new_integer_expression(ip_address, "%#lx"); +} +; + +in6addr +: INET6_ADDR '(' STRING ')' { + struct in6_addr ipv6_address; + if (inet_pton(AF_INET6, $3, &ipv6_address) != 1) { + semantic_error("cannot parse in6_addr"); + } + $$ = new_expression(EXPR_IN6_ADDR); + $$->value.address_ipv6 = ipv6_address; +} +; + +sockaddr +: '{' SA_FAMILY '=' WORD ',' + SIN_PORT '=' _HTONS_ '(' INTEGER ')' ',' + SIN_ADDR '=' INET_ADDR '(' STRING ')' '}' { + if (strcmp($4, "AF_INET") == 0) { + struct sockaddr_in *ipv4 = malloc(sizeof(struct sockaddr_in)); + memset(ipv4, 0, sizeof(*ipv4)); + ipv4->sin_family = AF_INET; + ipv4->sin_port = htons($10); + if (inet_pton(AF_INET, $17, &ipv4->sin_addr) == 1) { + $$ = new_expression(EXPR_SOCKET_ADDRESS_IPV4); + $$->value.socket_address_ipv4 = ipv4; + } else { + free(ipv4); + semantic_error("invalid IPv4 address"); + } + } else if (strcmp($4, "AF_INET6") == 0) { + struct sockaddr_in6 *ipv6 = malloc(sizeof(struct sockaddr_in6)); + memset(ipv6, 0, sizeof(*ipv6)); + ipv6->sin6_family = AF_INET6; + ipv6->sin6_port = htons($10); + if (inet_pton(AF_INET6, $17, &ipv6->sin6_addr) == 1) { + $$ = new_expression(EXPR_SOCKET_ADDRESS_IPV6); + $$->value.socket_address_ipv6 = ipv6; + } else { + free(ipv6); + semantic_error("invalid IPv6 address"); + } + } +} +; + +msghdr +: '{' MSG_NAME '(' ELLIPSIS ')' '=' ELLIPSIS ',' + MSG_IOV '(' decimal_integer ')' '=' array ',' + MSG_FLAGS '=' expression + opt_cmsg '}' { + struct msghdr_expr *msg_expr = calloc(1, sizeof(struct msghdr_expr)); + $$ = new_expression(EXPR_MSGHDR); + $$->value.msghdr = msg_expr; + msg_expr->msg_name = new_expression(EXPR_ELLIPSIS); + msg_expr->msg_namelen = new_expression(EXPR_ELLIPSIS); + msg_expr->msg_iov = $14; + msg_expr->msg_iovlen = $11; + msg_expr->msg_control = $19; + msg_expr->msg_flags = $18; +} +; + +opt_cmsg +: { $$ = new_expression(EXPR_LIST); } +| ',' MSG_CONTROL '=' array { $$ = $4; } +; + +cmsg_expr +: '{' CMSG_LEVEL '=' expression ',' + CMSG_TYPE '=' expression ',' + CMSG_DATA '=' expression '}' { + struct cmsg_expr *cmsg_expr = calloc(1, sizeof(struct cmsg_expr)); + $$ = new_expression(EXPR_CMSG); + $$->value.cmsg = cmsg_expr; + cmsg_expr->cmsg_level = $4; + cmsg_expr->cmsg_type = $8; + cmsg_expr->cmsg_data = $12; +} +; + +scm_timestamping_expr +: '{' SCM_SEC '=' INTEGER ',' + SCM_NSEC '=' INTEGER '}' { + struct scm_timestamping_expr *ts_expr = + calloc(1, sizeof(struct scm_timestamping_expr)); + ts_expr->ts[0].tv_sec = $4; + ts_expr->ts[0].tv_nsec = $8; + + $$ = new_expression(EXPR_SCM_TIMESTAMPING); + $$->value.scm_timestamping = ts_expr; +} +; + +sub_expr_list +: '{' expression_list '}' { + $$ = new_expression(EXPR_LIST); + $$->value.list = $2; +} +; + +sock_extended_err_expr +: '{' EE_ERRNO '=' expression ',' + EE_ORIGIN '=' expression ',' + EE_TYPE '=' expression ',' + EE_CODE '=' expression ',' + EE_INFO '=' expression ',' + EE_DATA '=' expression '}' { + struct sock_extended_err_expr *ee_expr = + calloc(1, sizeof(struct sock_extended_err_expr)); + ee_expr->ee_errno = $4; + ee_expr->ee_origin = $8; + ee_expr->ee_type = $12; + ee_expr->ee_code = $16; + ee_expr->ee_info = $20; + ee_expr->ee_data = $24; + $$ = new_expression(EXPR_SOCK_EXTENDED_ERR); + $$->value.sock_extended_err = ee_expr; +} +; + +iovec +: '{' ELLIPSIS ',' decimal_integer '}' { + struct iovec_expr *iov_expr = calloc(1, sizeof(struct iovec_expr)); + $$ = new_expression(EXPR_IOVEC); + $$->value.iovec = iov_expr; + iov_expr->iov_base = new_expression(EXPR_ELLIPSIS); + iov_expr->iov_len = $4; +} +; + +pollfd +: '{' FD '=' expression ',' EVENTS '=' expression opt_revents '}' { + struct pollfd_expr *pollfd_expr = calloc(1, sizeof(struct pollfd_expr)); + $$ = new_expression(EXPR_POLLFD); + $$->value.pollfd = pollfd_expr; + pollfd_expr->fd = $4; + pollfd_expr->events = $8; + pollfd_expr->revents = $9; +} +; + +epollev +: '{' EVENTS '=' expression ',' FD '=' expression '}' { + struct epollev_expr *epollev_expr = calloc(1, sizeof(struct epollev_expr)); + $$ = new_expression(EXPR_EPOLLEV); + $$->value.epollev = epollev_expr; + epollev_expr->events = $4; + epollev_expr->fd = $8; +} | '{' EVENTS '=' expression ',' PTR '=' expression '}' { + struct epollev_expr *epollev_expr = calloc(1, sizeof(struct epollev_expr)); + $$ = new_expression(EXPR_EPOLLEV); + $$->value.epollev = epollev_expr; + epollev_expr->events = $4; + epollev_expr->ptr = $8; +} | '{' EVENTS '=' expression ',' U32 '=' expression '}' { + struct epollev_expr *epollev_expr = calloc(1, sizeof(struct epollev_expr)); + $$ = new_expression(EXPR_EPOLLEV); + $$->value.epollev = epollev_expr; + epollev_expr->events = $4; + epollev_expr->u32 = $8; +} | '{' EVENTS '=' expression ',' U64 '=' expression '}' { + struct epollev_expr *epollev_expr = calloc(1, sizeof(struct epollev_expr)); + $$ = new_expression(EXPR_EPOLLEV); + $$->value.epollev = epollev_expr; + epollev_expr->events = $4; + epollev_expr->u64 = $8; +} +; + +opt_revents +: { $$ = new_integer_expression(0, "%ld"); } +| ',' REVENTS '=' expression { $$ = $4; } +; + +linger +: '{' ONOFF '=' INTEGER ',' LINGER '=' INTEGER '}' { + $$ = new_expression(EXPR_LINGER); + $$->value.linger.l_onoff = $4; + $$->value.linger.l_linger = $8; +} +; + +mpls_stack_expression +: +'{' mpls_stack '}' { + $$ = new_expression(EXPR_MPLS_STACK); + $$->value.mpls_stack = $2; +} +; + +opt_errno +: { $$ = NULL; } +| WORD note { + $$ = malloc(sizeof(struct errno_spec)); + $$->errno_macro = $1; + $$->strerror = $2; +} +; + +opt_note +: { $$ = NULL; } +| note { $$ = $1; } +; + +note +: '(' word_list ')' { $$ = $2; } +; + +word_list +: WORD { $$ = $1; } +| FLAGS { $$ = strdup("flags"); } +| word_list WORD { asprintf(&($$), "%s %s", $1, $2); free($1); free($2); } +; + +command_spec +: BACK_QUOTED { + $$ = malloc(sizeof(struct command_spec)); + $$->command_line = $1; + current_script_line = yylineno; +} +; + +code_spec +: CODE { + $$ = calloc(1, sizeof(struct code_spec)); + $$->text = $1; + current_script_line = yylineno; + } +; + +opt_cleanup_command +: { } +| cleanup_command { } +; + +cleanup_command +: command_spec { + out_script->cleanup_command = $1; + cleanup_cmd = out_script->cleanup_command->command_line; +} +; |