aboutsummaryrefslogtreecommitdiffstats
path: root/test/packetdrill/parser.y
diff options
context:
space:
mode:
Diffstat (limited to 'test/packetdrill/parser.y')
-rw-r--r--test/packetdrill/parser.y1739
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;
+}
+;