From 1672402c7a24c664bdcb374330ee7ddf94d220b0 Mon Sep 17 00:00:00 2001 From: Michael Tuexen <tuexen@fh-muenster.de> Date: Fri, 1 May 2015 08:32:44 +0200 Subject: [PATCH] Add initial support for SCTP tests. Current limitations: * Chunk parameters are not yet supported. * Error causes are not yet supported. * Chunks can't be bundled with COOKIE-ECHO chunks. * Chunk flags are not supported. * Chunk lengths are not supported. * Gap reports and duplicate TSNs are not supported for SACK chunks. * SCTP related ICMP messages are not supported. This version is only tested in a limited way, only on FreeBSD and only in local mode. --- gtests/net/packetdrill/Makefile.common | 2 +- gtests/net/packetdrill/lexer.l | 24 + gtests/net/packetdrill/netdev.c | 4 +- gtests/net/packetdrill/packet.c | 18 +- gtests/net/packetdrill/packet.h | 1 + gtests/net/packetdrill/parser.y | 224 +++- gtests/net/packetdrill/run.c | 5 + gtests/net/packetdrill/run_packet.c | 999 +++++++++++++++++- gtests/net/packetdrill/run_packet.h | 6 + gtests/net/packetdrill/run_system_call.c | 3 +- gtests/net/packetdrill/sctp_packet.c | 692 ++++++++++++ gtests/net/packetdrill/sctp_packet.h | 141 +++ gtests/net/packetdrill/socket.h | 15 + .../net/packetdrill/tests/bsd/sctp/sctp.pkt | 29 + .../tests/bsd/sctp/sctp_active.pkt | 27 + .../tests/bsd/sctp/sctp_handle_init.pkt | 7 + .../tests/bsd/sctp/sctp_init_rtx.pkt | 26 + .../tests/bsd/sctp/sctp_passive.pkt | 27 + 18 files changed, 2195 insertions(+), 55 deletions(-) create mode 100644 gtests/net/packetdrill/sctp_packet.c create mode 100644 gtests/net/packetdrill/sctp_packet.h create mode 100644 gtests/net/packetdrill/tests/bsd/sctp/sctp.pkt create mode 100644 gtests/net/packetdrill/tests/bsd/sctp/sctp_active.pkt create mode 100644 gtests/net/packetdrill/tests/bsd/sctp/sctp_handle_init.pkt create mode 100644 gtests/net/packetdrill/tests/bsd/sctp/sctp_init_rtx.pkt create mode 100644 gtests/net/packetdrill/tests/bsd/sctp/sctp_passive.pkt diff --git a/gtests/net/packetdrill/Makefile.common b/gtests/net/packetdrill/Makefile.common index e58ab854..6490f46f 100644 --- a/gtests/net/packetdrill/Makefile.common +++ b/gtests/net/packetdrill/Makefile.common @@ -20,7 +20,7 @@ packetdrill-lib := \ symbols_openbsd.o \ symbols_netbsd.o \ gre_packet.o icmp_packet.o ip_packet.o \ - tcp_packet.o udp_packet.o udplite_packet.o \ + sctp_packet.o tcp_packet.o udp_packet.o udplite_packet.o \ mpls_packet.o \ run.o run_command.o run_packet.o run_system_call.o \ script.o socket.o system.o \ diff --git a/gtests/net/packetdrill/lexer.l b/gtests/net/packetdrill/lexer.l index cc57de99..d1d3c111 100644 --- a/gtests/net/packetdrill/lexer.l +++ b/gtests/net/packetdrill/lexer.l @@ -174,6 +174,7 @@ htons return _HTONS_; ipv4 return IPV4; ipv6 return IPV6; icmp return ICMP; +sctp return SCTP; udp return UDP; udplite return UDPLITE; gre return GRE; @@ -211,6 +212,29 @@ sinit_num_ostreams return SINIT_NUM_OSTREAMS; sinit_max_instreams return SINIT_MAX_INSTREAMS; sinit_max_attempts return SINIT_MAX_ATTEMPTS; sinit_max_init_timeo return SINIT_MAX_INIT_TIMEO; +DATA return DATA; +INIT return INIT; +INIT_ACK return INIT_ACK; +SACK return SACK; +HEARTBEAT return HEARTBEAT; +HEARTBEAT_ACK return HEARTBEAT_ACK; +ABORT return ABORT; +SHUTDOWN return SHUTDOWN; +SHUTDOWN_ACK return SHUTDOWN_ACK; +ERROR return ERROR; +COOKIE_ECHO return COOKIE_ECHO; +COOKIE_ACK return COOKIE_ACK; +ECNE return ECNE; +CWR return CWR; +SHUTDOWN_COMPLETE return SHUTDOWN_COMPLETE; +tag return TAG; +a_rwnd return A_RWND; +is return IS; +os return OS; +tsn return TSN; +sid return SID; +ssn return SSN; +ppid return PPID; --[a-zA-Z0-9_]+ yylval.string = option(yytext); return OPTION; [-]?[0-9]*[.][0-9]+ yylval.floating = atof(yytext); return FLOAT; [-]?[0-9]+ yylval.integer = atoll(yytext); return INTEGER; diff --git a/gtests/net/packetdrill/netdev.c b/gtests/net/packetdrill/netdev.c index 4c1f1ce8..a4e82e5d 100644 --- a/gtests/net/packetdrill/netdev.c +++ b/gtests/net/packetdrill/netdev.c @@ -347,8 +347,8 @@ static int local_netdev_send(struct netdev *a_netdev, assert(packet->ip_bytes > 0); /* We do IPv4 and IPv6 */ assert(packet->ipv4 || packet->ipv6); - /* We only do TCP, UDP, UDPLite and ICMP */ - assert(packet->tcp || packet->udp || packet->udplite || + /* We only do SCTP, TCP, UDP, UDPLite and ICMP */ + assert(packet->sctp || packet->tcp || packet->udp || packet->udplite || packet->icmpv4 || packet->icmpv6); DEBUGP("local_netdev_send\n"); diff --git a/gtests/net/packetdrill/packet.c b/gtests/net/packetdrill/packet.c index dbb33b17..19b9e6a7 100644 --- a/gtests/net/packetdrill/packet.c +++ b/gtests/net/packetdrill/packet.c @@ -32,6 +32,7 @@ #include "gre_packet.h" #include "ip_packet.h" #include "mpls_packet.h" +#include "sctp_packet.h" /* Info for all types of header we support. */ @@ -54,11 +55,13 @@ struct packet *packet_new(u32 buffer_bytes) struct packet *packet = calloc(1, sizeof(struct packet)); packet->buffer = malloc(buffer_bytes); packet->buffer_bytes = buffer_bytes; + packet->chunk_list = sctp_chunk_list_new(); return packet; } void packet_free(struct packet *packet) { + sctp_chunk_list_free(packet->chunk_list); free(packet->buffer); memset(packet, 0, sizeof(*packet)); /* paranoia to help catch bugs */ free(packet); @@ -142,9 +145,11 @@ static struct packet *packet_copy_with_headroom(struct packet *old_packet, const int bytes_used = packet_end(old_packet) - old_packet->buffer; assert(bytes_used >= 0); assert(bytes_used <= 128*1024); - struct packet *packet = packet_new(bytes_headroom + bytes_used); + struct packet *packet = packet_new(max(bytes_headroom + bytes_used, old_packet->buffer_bytes)); u8 *old_base = old_packet->buffer; u8 *new_base = packet->buffer + bytes_headroom; + struct sctp_chunk_list_item *old_item, *new_item; + struct sctp_chunk *new_chunk; memcpy(new_base, old_base, bytes_used); @@ -159,6 +164,7 @@ static struct packet *packet_copy_with_headroom(struct packet *old_packet, /* Set up layer 3 header pointer. */ packet->ipv4 = offset_ptr(old_base, new_base, old_packet->ipv4); packet->ipv6 = offset_ptr(old_base, new_base, old_packet->ipv6); + /* Set up layer 4 header pointer. */ packet->sctp = offset_ptr(old_base, new_base, old_packet->sctp); packet->tcp = offset_ptr(old_base, new_base, old_packet->tcp); packet->udp = offset_ptr(old_base, new_base, old_packet->udp); @@ -166,6 +172,16 @@ static struct packet *packet_copy_with_headroom(struct packet *old_packet, packet->icmpv4 = offset_ptr(old_base, new_base, old_packet->icmpv4); packet->icmpv6 = offset_ptr(old_base, new_base, old_packet->icmpv6); + for (old_item = old_packet->chunk_list->first; + old_item != NULL; + old_item = old_item->next) { + new_chunk = offset_ptr(old_base, new_base, old_item->chunk); + new_item = sctp_chunk_list_item_new(new_chunk, + old_item->length, + old_item->flags); + sctp_chunk_list_append(packet->chunk_list, new_item); + } + packet->tcp_ts_val = offset_ptr(old_base, new_base, old_packet->tcp_ts_val); packet->tcp_ts_ecr = offset_ptr(old_base, new_base, diff --git a/gtests/net/packetdrill/packet.h b/gtests/net/packetdrill/packet.h index 06ddf02d..dfedcd8d 100644 --- a/gtests/net/packetdrill/packet.h +++ b/gtests/net/packetdrill/packet.h @@ -96,6 +96,7 @@ struct packet { /* Layer 4 */ struct sctp_common_header *sctp; /* start of SCTP common header, if present */ + struct sctp_chunk_list *chunk_list; struct tcp *tcp; /* start of TCP header, if present */ struct udp *udp; /* start of UDP header, if present */ struct udplite *udplite;/* start of UDPLite header, if present */ diff --git a/gtests/net/packetdrill/parser.y b/gtests/net/packetdrill/parser.y index 32f18e9c..b6b22e1e 100644 --- a/gtests/net/packetdrill/parser.y +++ b/gtests/net/packetdrill/parser.y @@ -97,6 +97,7 @@ #include "logging.h" #include "mpls.h" #include "mpls_packet.h" +#include "sctp_packet.h" #include "tcp_packet.h" #include "udp_packet.h" #include "udplite_packet.h" @@ -462,6 +463,8 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string, struct option_list *option; struct event *event; struct packet *packet; + struct sctp_chunk_list_item *chunk_list_item; + struct sctp_chunk_list *chunk_list; struct syscall_spec *syscall; struct command_spec *command; struct code_spec *code; @@ -483,7 +486,7 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string, %token <reserved> ACK ECR EOL MSS NOP SACK SACKOK TIMESTAMP VAL WIN WSCALE PRO %token <reserved> FAST_OPEN %token <reserved> ECT0 ECT1 CE ECT01 NO_ECN -%token <reserved> IPV4 IPV6 ICMP UDP UDPLITE GRE MTU +%token <reserved> IPV4 IPV6 ICMP SCTP UDP UDPLITE GRE MTU %token <reserved> MPLS LABEL TC TTL %token <reserved> OPTION %token <reserved> SRTO_INITIAL SRTO_MAX SRTO_MIN @@ -491,6 +494,10 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string, %token <reserved> SINIT_MAX_INIT_TIMEO %token <reserved> ASSOC_VALUE %token <reserved> SACK_DELAY SACK_FREQ +%token <reserved> DATA INIT INIT_ACK HEARTBEAT HEARTBEAT_ACK ABORT +%token <reserved> SHUTDOWN SHUTDOWN_ACK ERROR COOKIE_ECHO COOKIE_ACK ECNE CWR +%token <reserved> SHUTDOWN_COMPLETE +%token <reserved> TAG A_RWND OS IS TSN SID SSN PPID %token <floating> FLOAT %token <integer> INTEGER HEX_INTEGER %token <string> WORD STRING BACK_QUOTED CODE IPV4_ADDR IPV6_ADDR @@ -500,7 +507,9 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string, %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 udplite_packet_spec +%type <packet> packet_spec +%type <packet> sctp_packet_spec tcp_packet_spec +%type <packet> udp_packet_spec udplite_packet_spec %type <packet> icmp_packet_spec %type <packet> packet_prefix %type <syscall> syscall_spec @@ -527,6 +536,20 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string, %type <expression> inaddr sockaddr msghdr iovec pollfd opt_revents linger %type <expression> sctp_rtoinfo sctp_initmsg sctp_assocval sctp_sackinfo %type <errno_info> opt_errno +%type <chunk_list> sctp_chunk_list_spec +%type <chunk_list_item> sctp_chunk_spec +%type <chunk_list_item> sctp_data_chunk_spec +%type <chunk_list_item> sctp_init_chunk_spec sctp_init_ack_chunk_spec +%type <chunk_list_item> sctp_sack_chunk_spec +%type <chunk_list_item> sctp_heartbeat_chunk_spec sctp_heartbeat_ack_chunk_spec +%type <chunk_list_item> sctp_abort_chunk_spec +%type <chunk_list_item> sctp_shutdown_chunk_spec +%type <chunk_list_item> sctp_shutdown_ack_chunk_spec +%type <chunk_list_item> sctp_error_chunk_spec +%type <chunk_list_item> sctp_cookie_echo_chunk_spec sctp_cookie_ack_chunk_spec +%type <chunk_list_item> sctp_ecne_chunk_spec sctp_cwr_chunk_spec +%type <chunk_list_item> sctp_shutdown_complete_chunk_spec +%type <integer> opt_a_rwnd opt_os opt_is opt_tsn opt_sid opt_ssn opt_ppid %% /* The grammar follows. */ @@ -684,12 +707,207 @@ action ; packet_spec -: tcp_packet_spec { $$ = $1; } +: sctp_packet_spec { $$ = $1; } +| tcp_packet_spec { $$ = $1; } | udp_packet_spec { $$ = $1; } | udplite_packet_spec { $$ = $1; } | icmp_packet_spec { $$ = $1; } ; +sctp_packet_spec +: packet_prefix opt_ip_info SCTP ':' sctp_chunk_list_spec { + char *error = NULL; + struct packet *outer = $1, *inner = NULL; + enum direction_t direction = outer->direction; + + inner = new_sctp_packet(in_config->wire_protocol, direction, $2, $5, + &error); + if (inner == NULL) { + assert(error != NULL); + semantic_error(error); + free(error); + } + + $$ = packet_encapsulate_and_free(outer, inner); +} +; + +sctp_chunk_list_spec +: { $$ = sctp_chunk_list_new(); } +| sctp_chunk_spec { $$ = sctp_chunk_list_new(); + sctp_chunk_list_append($$, $1); } +| sctp_chunk_list_spec ';' sctp_chunk_spec { $$ = $1; + sctp_chunk_list_append($1, $3); } +; + +sctp_chunk_spec +: sctp_data_chunk_spec { $$ = $1; } +| sctp_init_chunk_spec { $$ = $1; } +| sctp_init_ack_chunk_spec { $$ = $1; } +| sctp_sack_chunk_spec { $$ = $1; } +| sctp_heartbeat_chunk_spec { $$ = $1; } +| sctp_heartbeat_ack_chunk_spec { $$ = $1; } +| sctp_abort_chunk_spec { $$ = $1; } +| sctp_shutdown_chunk_spec { $$ = $1; } +| sctp_shutdown_ack_chunk_spec { $$ = $1; } +| sctp_error_chunk_spec { $$ = $1; } +| sctp_cookie_echo_chunk_spec { $$ = $1; } +| sctp_cookie_ack_chunk_spec { $$ = $1; } +| sctp_ecne_chunk_spec { $$ = $1; } +| sctp_cwr_chunk_spec { $$ = $1; } +| sctp_shutdown_complete_chunk_spec { $$ = $1; } +; + +opt_a_rwnd +: { $$ = -1; } +| A_RWND '=' INTEGER { + if (!is_valid_u32($3)) { + semantic_error("a_rwnd value out of range"); + } + $$ = $3; +} + +opt_os +: { $$ = -1; } +| OS '=' INTEGER { + if (!is_valid_u16($3)) { + semantic_error("os value out of range"); + } + $$ = $3; +} + +opt_is +: { $$ = -1; } +| IS '=' INTEGER { + if (!is_valid_u16($3)) { + semantic_error("is value out of range"); + } + $$ = $3; +} + +opt_tsn +: { $$ = -1; } +| TSN '=' INTEGER { + if (!is_valid_u32($3)) { + semantic_error("tsn value out of range"); + } + $$ = $3; +} + +opt_sid +: { $$ = -1; } +| SID '=' INTEGER { + if (!is_valid_u16($3)) { + semantic_error("sid value out of range"); + } + $$ = $3; +} + +opt_ssn +: { $$ = -1; } +| SSN '=' INTEGER { + if (!is_valid_u16($3)) { + semantic_error("ssn value out of range"); + } + $$ = $3; +} + +opt_ppid +: { $$ = -1; } +| PPID '=' INTEGER { + if (!is_valid_u32($3)) { + semantic_error("ppid value out of range"); + } + $$ = $3; +} + +sctp_data_chunk_spec +: DATA '[' opt_tsn opt_sid opt_ssn opt_ppid ']' { + $$ = sctp_data_chunk_new($3, $4, $5, $6); +} + +sctp_init_chunk_spec +: INIT '[' TAG '=' INTEGER opt_a_rwnd opt_os opt_is TSN '=' INTEGER ']' { + if (!is_valid_u32($5)) { + semantic_error("tag value out of range"); + } + if (!is_valid_u32($11)) { + semantic_error("tsn value out of range"); + } + $$ = sctp_init_chunk_new($5, $6, $7, $8, $11); +} + +sctp_init_ack_chunk_spec +: INIT_ACK '[' TAG '=' INTEGER opt_a_rwnd opt_os opt_is TSN '=' INTEGER ']' { + if (!is_valid_u32($5)) { + semantic_error("tag value out of range"); + } + if (!is_valid_u32($11)) { + semantic_error("tsn value out of range"); + } + $$ = sctp_init_ack_chunk_new($5, $6, $7, $8, $11); +} + +sctp_sack_chunk_spec +: SACK '[' opt_tsn opt_a_rwnd']' { + $$ = sctp_sack_chunk_new($3, $4); +} + +sctp_heartbeat_chunk_spec +: HEARTBEAT '[' ']' { + $$ = sctp_heartbeat_chunk_new(0); +} + +sctp_heartbeat_ack_chunk_spec +: HEARTBEAT_ACK '[' ']' { + $$ = sctp_heartbeat_ack_chunk_new(0); +} + +sctp_abort_chunk_spec +: ABORT '[' ']' { + $$ = sctp_abort_chunk_new(0); +} + +sctp_shutdown_chunk_spec +: SHUTDOWN '[' opt_tsn ']' { + $$ = sctp_shutdown_chunk_new($3); +} + +sctp_shutdown_ack_chunk_spec +: SHUTDOWN_ACK '[' ']' { + $$ = sctp_shutdown_ack_chunk_new(0); +} + +sctp_error_chunk_spec +: ERROR '[' ']' { + $$ = sctp_error_chunk_new(0); +} + +sctp_cookie_echo_chunk_spec +: COOKIE_ECHO '[' ']' { + $$ = sctp_cookie_echo_chunk_new(0); +} + +sctp_cookie_ack_chunk_spec +: COOKIE_ACK '[' ']' { + $$ = sctp_cookie_ack_chunk_new(0); +} + +sctp_ecne_chunk_spec +: ECNE '[' opt_tsn ']' { + $$ = sctp_ecne_chunk_new($3); +} + +sctp_cwr_chunk_spec +: CWR '[' opt_tsn ']' { + $$ = sctp_cwr_chunk_new($3); +} + +sctp_shutdown_complete_chunk_spec +: SHUTDOWN_COMPLETE '[' ']' { + $$ = sctp_shutdown_complete_chunk_new(0); +} + tcp_packet_spec : packet_prefix opt_ip_info flags seq opt_ack opt_window opt_tcp_options { char *error = NULL; diff --git a/gtests/net/packetdrill/run.c b/gtests/net/packetdrill/run.c index a3260197..3edfb49b 100644 --- a/gtests/net/packetdrill/run.c +++ b/gtests/net/packetdrill/run.c @@ -108,6 +108,11 @@ static void close_all_sockets(struct state *state) reset_connection(state, socket)) { die("error reseting connection\n"); } + if (socket->protocol == IPPROTO_SCTP && + !state->config->is_wire_client && + abort_association(state, socket)) { + die("error aborting association\n"); + } struct socket *dead_socket = socket; socket = socket->next; socket_free(dead_socket); diff --git a/gtests/net/packetdrill/run_packet.c b/gtests/net/packetdrill/run_packet.c index 070a184b..08e36829 100644 --- a/gtests/net/packetdrill/run_packet.c +++ b/gtests/net/packetdrill/run_packet.c @@ -39,6 +39,8 @@ #include "packet_to_string.h" #include "run.h" #include "script.h" +#include "sctp_iterator.h" +#include "sctp_packet.h" #include "tcp_options_iterator.h" #include "tcp_options_to_string.h" #include "tcp_packet.h" @@ -146,6 +148,7 @@ static struct socket *find_socket_for_live_packet( enum direction_t *direction) { struct socket *socket = state->socket_under_test; /* shortcut */ + if (socket == NULL) return NULL; @@ -188,6 +191,8 @@ static struct socket *handle_listen_for_script_packet( */ struct config *config = state->config; struct socket *socket = state->socket_under_test; /* shortcut */ + struct sctp_init_chunk *init; + struct sctp_chunk_list_item *item; bool match = (direction == DIRECTION_INBOUND); if (!match) @@ -206,6 +211,17 @@ static struct socket *handle_listen_for_script_packet( if (!match) return NULL; + if (packet->sctp != NULL) { + assert(packet->chunk_list != NULL); + item = packet->chunk_list->first; + if ((item != NULL) && + (item->chunk->type == SCTP_INIT_CHUNK_TYPE)) { + init = (struct sctp_init_chunk *)item->chunk; + } else { + return NULL; + } + } + /* Create a child passive socket for this incoming SYN packet. * Any further packets in the test script will be directed to * this child socket. @@ -222,7 +238,12 @@ static struct socket *handle_listen_for_script_packet( get_packet_tuple(packet, &tuple); socket->script.remote = tuple.src; socket->script.local = tuple.dst; - socket->script.remote_isn = ntohl(packet->tcp->seq); + if (packet->tcp != NULL) { + socket->script.remote_isn = ntohl(packet->tcp->seq); + } else { + socket->script.remote_initiate_tag = ntohl(init->initiate_tag); + socket->script.remote_initial_tsn = ntohl(init->initial_tsn); + } socket->script.fd = -1; /* Set up the live info for this socket based @@ -232,7 +253,12 @@ static struct socket *handle_listen_for_script_packet( socket->live.remote.port = htons(next_ephemeral_port(state)); socket->live.local.ip = config->live_local_ip; socket->live.local.port = htons(config->live_bind_port); - socket->live.remote_isn = ntohl(packet->tcp->seq); + if (packet->tcp != NULL) { + socket->live.remote_isn = ntohl(packet->tcp->seq); + } else { + socket->live.remote_initiate_tag = ntohl(init->initiate_tag); + socket->live.remote_initial_tsn = ntohl(init->initial_tsn); + } socket->live.fd = -1; if (DEBUG_LOGGING) { @@ -244,7 +270,12 @@ static struct socket *handle_listen_for_script_packet( DEBUGP("live: remote: %s.%d\n", ip_to_string(&socket->live.remote.ip, remote_string), ntohs(socket->live.remote.port)); - DEBUGP("live: ISN: %u\n", socket->live.remote_isn); + if (packet->tcp != NULL) { + DEBUGP("live: ISN: %u\n", socket->live.remote_isn); + } else { + DEBUGP("live: initiate tag: %u\n", socket->live.remote_initiate_tag); + DEBUGP("live: initial tsn: %u\n", socket->live.remote_initial_tsn); + } } return socket; @@ -266,9 +297,28 @@ static struct socket *handle_connect_for_script_packet( */ struct config *config = state->config; struct socket *socket = state->socket_under_test; /* shortcut */ + struct sctp_init_chunk *init; + struct sctp_chunk_list_item *item; + bool match; - bool match = ((direction == DIRECTION_OUTBOUND) && - packet->tcp->syn && !packet->tcp->ack); + DEBUGP("handle_connect_for_script_packet\n"); + assert(packet->tcp != NULL || packet->sctp != NULL); + if (direction != DIRECTION_OUTBOUND) + return NULL; + if (packet->tcp != NULL) { + match = (packet->tcp->syn && !packet->tcp->ack); + } else { + assert(packet->chunk_list != NULL); + item = packet->chunk_list->first; + if ((item != NULL) && + (item->chunk->type == SCTP_INIT_CHUNK_TYPE)) { + init = (struct sctp_init_chunk *)item->chunk; + match = true; + } else { + init = NULL; + match = false; + } + } if (!match) return NULL; @@ -306,11 +356,17 @@ static struct socket *handle_connect_for_script_packet( /* Fill in the new info about this connection. */ struct tuple tuple; get_packet_tuple(packet, &tuple); - socket->state = SOCKET_ACTIVE_SYN_SENT; socket->script.remote = tuple.dst; socket->script.local = tuple.src; - socket->script.local_isn = ntohl(packet->tcp->seq); - + if (packet->tcp) { + socket->state = SOCKET_ACTIVE_SYN_SENT; + socket->script.local_isn = ntohl(packet->tcp->seq); + } else { + DEBUGP("Moving socket in SOCKET_ACTIVE_INIT_SENT\n"); + socket->state = SOCKET_ACTIVE_INIT_SENT; + socket->script.local_initial_tsn = ntohl(init->initial_tsn); + socket->script.local_initiate_tag = ntohl(init->initiate_tag); + } return socket; } @@ -319,7 +375,13 @@ static struct socket *find_connect_for_live_packet( struct state *state, struct packet *packet, enum direction_t *direction) { + struct sctp_chunks_iterator iter; + struct sctp_chunk *chunk; + struct sctp_init_chunk *init; struct tuple tuple; + char *error; + + DEBUGP("find_connect_for_live_packet\n"); get_packet_tuple(packet, &tuple); *direction = DIRECTION_INVALID; @@ -327,6 +389,14 @@ static struct socket *find_connect_for_live_packet( if (!socket) return NULL; + bool is_sctp_match = + (packet->sctp && + (socket->protocol == IPPROTO_SCTP) && + (socket->state == SOCKET_ACTIVE_INIT_SENT)); + bool is_tcp_match = + (packet->tcp && packet->tcp->syn && !packet->tcp->ack && + (socket->protocol == IPPROTO_TCP) && + (socket->state == SOCKET_ACTIVE_SYN_SENT)); bool is_udp_match = (packet->udp && (socket->protocol == IPPROTO_UDP) && @@ -335,11 +405,8 @@ static struct socket *find_connect_for_live_packet( (packet->udplite && (socket->protocol == IPPROTO_UDPLITE) && (socket->state == SOCKET_ACTIVE_CONNECTING)); - bool is_tcp_match = - (packet->tcp && packet->tcp->syn && !packet->tcp->ack && - (socket->protocol == IPPROTO_TCP) && - (socket->state == SOCKET_ACTIVE_SYN_SENT)); - if (!is_udp_match && !is_tcp_match && !is_udplite_match) + if (!is_sctp_match && !is_tcp_match && + !is_udp_match && !is_udplite_match) return NULL; if (!is_equal_ip(&tuple.dst.ip, &socket->live.remote.ip) || @@ -356,6 +423,17 @@ static struct socket *find_connect_for_live_packet( if (packet->tcp) socket->live.local_isn = ntohl(packet->tcp->seq); + if (packet->sctp) { + error = NULL; + chunk = sctp_chunks_begin(packet, &iter, &error); + if ((error == NULL) && + (chunk != NULL) && + (chunk->type == SCTP_INIT_CHUNK_TYPE)) { + init = (struct sctp_init_chunk *)chunk; + socket->live.local_initiate_tag = ntohl(init->initiate_tag); + socket->live.local_initial_tsn = ntohl(init->initial_tsn); + } + } return socket; } @@ -482,6 +560,85 @@ static int map_inbound_icmp_packet( return STATUS_ERR; } +static int map_inbound_sctp_packet( + struct socket *socket, struct packet *live_packet, char **error) +{ + struct sctp_chunks_iterator iter; + struct sctp_chunk *chunk; + struct sctp_data_chunk *data; + struct sctp_init_chunk *init; + struct sctp_init_ack_chunk *init_ack; + struct sctp_sack_chunk *sack; + struct sctp_shutdown_chunk *shutdown; + struct sctp_ecne_chunk *ecne; + struct sctp_cwr_chunk *cwr; + u32 local_diff, remote_diff; + u16 nr_gap_blocks, nr_dup_tsns, i; + + live_packet->sctp->v_tag = htonl(socket->live.local_initiate_tag); + for (chunk = sctp_chunks_begin(live_packet, &iter, error); + chunk != NULL; + chunk = sctp_chunks_next(&iter, error)) { + if (*error != NULL) { + return STATUS_ERR; + } + DEBUGP("live remote tsn %d, scripte remote tsn %d\n", + socket->live.remote_initial_tsn, socket->script.remote_initial_tsn); + DEBUGP("live local tsn %d, scripte local tsn %d\n", + socket->live.local_initial_tsn, socket->script.local_initial_tsn); + remote_diff = socket->live.remote_initial_tsn - socket->script.remote_initial_tsn; + local_diff = socket->live.local_initial_tsn - socket->script.local_initial_tsn; + switch (chunk->type) { + case SCTP_DATA_CHUNK_TYPE: + data = (struct sctp_data_chunk *)chunk; + data->tsn = htonl(ntohl(data->tsn) + remote_diff); + break; + case SCTP_INIT_CHUNK_TYPE: + init = (struct sctp_init_chunk *)chunk; + init->initial_tsn = htonl(ntohl(init->initial_tsn) + remote_diff); + /* XXX: Does this work in all cases? */ + if (ntohl(init->initiate_tag) == socket->script.local_initiate_tag) { + init->initiate_tag = htonl(socket->live.local_initiate_tag); + } + break; + case SCTP_INIT_ACK_CHUNK_TYPE: + init_ack = (struct sctp_init_ack_chunk *)chunk; + init_ack->initial_tsn = htonl(ntohl(init_ack->initial_tsn) + remote_diff); + /* XXX: Does this work in all cases? */ + if (ntohl(init_ack->initiate_tag) == socket->script.local_initiate_tag) { + init_ack->initiate_tag = htonl(socket->live.local_initiate_tag); + } + break; + case SCTP_SACK_CHUNK_TYPE: + sack = (struct sctp_sack_chunk *)chunk; + DEBUGP("Old SACK cum TSN %d\n", ntohl(sack->cum_tsn)); + sack->cum_tsn = htonl(ntohl(sack->cum_tsn) + local_diff); + DEBUGP("New SACK cum TSN %d\n", ntohl(sack->cum_tsn)); + nr_gap_blocks = ntohs(sack->nr_gap_blocks); + nr_dup_tsns = ntohs(sack->nr_dup_tsns); + for (i = 0; i < nr_dup_tsns; i++) { + sack->block[i].tsn = htonl(ntohl(sack->block[i].tsn) + local_diff); + } + break; + case SCTP_SHUTDOWN_CHUNK_TYPE: + shutdown = (struct sctp_shutdown_chunk *)chunk; + shutdown->cum_tsn = htonl(ntohl(shutdown->cum_tsn) + local_diff); + break; + case SCTP_ECNE_CHUNK_TYPE: + ecne = (struct sctp_ecne_chunk *)chunk; + ecne->lowest_tsn = htonl(ntohl(ecne->lowest_tsn) + local_diff); + break; + case SCTP_CWR_CHUNK_TYPE: + cwr = (struct sctp_cwr_chunk *)chunk; + cwr->lowest_tsn = htonl(ntohl(cwr->lowest_tsn) + local_diff); + break; + default: + break; + } + } + return STATUS_OK; +} + /* Rewrite the IP and TCP, UDP, or ICMP fields in 'live_packet', mapping * inbound packet values (address 4-tuple and sequence numbers in seq, * ACK, SACK blocks) from script values to live values, so that we can @@ -502,6 +659,10 @@ static int map_inbound_packet( if ((live_packet->icmpv4 != NULL) || (live_packet->icmpv6 != NULL)) return map_inbound_icmp_packet(socket, live_packet, error); + if (live_packet->sctp) { + return map_inbound_sctp_packet(socket, live_packet, error); + } + /* If no TCP headers to rewrite, then we're done. */ if (live_packet->tcp == NULL) return STATUS_OK; @@ -547,6 +708,85 @@ static int map_inbound_packet( return STATUS_OK; } +static int map_outbound_live_sctp_packet( + struct socket *socket, + struct packet *live_packet, + struct packet *actual_packet, + struct packet *script_packet, + char **error) +{ + struct sctp_chunks_iterator iter; + struct sctp_chunk *chunk; + struct sctp_data_chunk *data; + struct sctp_init_chunk *init; + struct sctp_init_ack_chunk *init_ack; + struct sctp_sack_chunk *sack; + struct sctp_shutdown_chunk *shutdown; + struct sctp_ecne_chunk *ecne; + struct sctp_cwr_chunk *cwr; + u32 local_diff, remote_diff; + u16 nr_gap_blocks, nr_dup_tsns, i; + + /* FIXME: transform v-tag in the common header*/ + DEBUGP("map_outbound_live_sctp_packet\n"); + for (chunk = sctp_chunks_begin(actual_packet, &iter, error); + chunk != NULL; + chunk = sctp_chunks_next(&iter, error)) { + if (*error != NULL) { + return STATUS_ERR; + } + local_diff = socket->script.local_initial_tsn - socket->live.local_initial_tsn; + remote_diff = socket->script.remote_initial_tsn - socket->live.remote_initial_tsn; + DEBUGP("Chunk type: 0x%02x\n", chunk->type); + switch (chunk->type) { + case SCTP_DATA_CHUNK_TYPE: + data = (struct sctp_data_chunk *)chunk; + data->tsn = htonl(ntohl(data->tsn) + local_diff); + break; + case SCTP_INIT_CHUNK_TYPE: + init = (struct sctp_init_chunk *)chunk; + init->initial_tsn = htonl(ntohl(init->initial_tsn) + local_diff); + /* XXX: Does this work in all cases? */ + if (ntohl(init->initiate_tag) == socket->live.local_initiate_tag) { + init->initiate_tag = htonl(socket->script.local_initiate_tag); + } + break; + case SCTP_INIT_ACK_CHUNK_TYPE: + init_ack = (struct sctp_init_ack_chunk *)chunk; + init_ack->initial_tsn = htonl(ntohl(init_ack->initial_tsn) + local_diff); + /* XXX: Does this work in all cases? */ + if (ntohl(init_ack->initiate_tag) == socket->live.local_initiate_tag) { + init_ack->initiate_tag = htonl(socket->script.local_initiate_tag); + } + break; + case SCTP_SACK_CHUNK_TYPE: + sack = (struct sctp_sack_chunk *)chunk; + sack->cum_tsn = htonl(ntohl(sack->cum_tsn) + remote_diff); + nr_gap_blocks = ntohs(sack->nr_gap_blocks); + nr_dup_tsns = ntohs(sack->nr_dup_tsns); + for (i = 0; i < nr_dup_tsns; i++) { + sack->block[i].tsn = htonl(ntohl(sack->block[i].tsn) + remote_diff); + } + break; + case SCTP_SHUTDOWN_CHUNK_TYPE: + shutdown = (struct sctp_shutdown_chunk *)chunk; + shutdown->cum_tsn = htonl(ntohl(shutdown->cum_tsn) + remote_diff); + break; + case SCTP_ECNE_CHUNK_TYPE: + ecne = (struct sctp_ecne_chunk *)chunk; + ecne->lowest_tsn = htonl(ntohl(ecne->lowest_tsn) + remote_diff); + break; + case SCTP_CWR_CHUNK_TYPE: + cwr = (struct sctp_cwr_chunk *)chunk; + cwr->lowest_tsn = htonl(ntohl(cwr->lowest_tsn) + remote_diff); + break; + default: + break; + } + } + return STATUS_OK; +} + /* Transforms values in the 'actual_packet' by mapping outbound packet * values in the sniffed 'live_packet' (address 4-tuple, sequence * number in seq, timestamp value) from live values to script values @@ -573,6 +813,10 @@ static int map_outbound_live_packet( socket_get_outbound(&socket->script, &script_outbound); set_packet_tuple(actual_packet, &script_outbound); + if (live_packet->sctp) { + return map_outbound_live_sctp_packet(socket, live_packet, actual_packet, script_packet, error); + } + /* If no TCP headers to rewrite, then we're done. */ if (live_packet->tcp == NULL) return STATUS_OK; @@ -718,13 +962,27 @@ static int verify_ipv4( actual_ipv4->protocol, error) || check_field("ipv4_header_length", script_ipv4->ihl, - actual_ipv4->ihl, error) || - check_field("ipv4_total_length", - (ntohs(script_ipv4->tot_len) + - tcp_options_allowance(actual_packet, - script_packet)), - ntohs(actual_ipv4->tot_len), error)) + actual_ipv4->ihl, error)) return STATUS_ERR; + switch (script_ipv4->protocol) { + case IPPROTO_SCTP: + /* FIXME */; + break; + case IPPROTO_TCP: + if (check_field("ipv4_total_length", + (ntohs(script_ipv4->tot_len) + + tcp_options_allowance(actual_packet, + script_packet)), + ntohs(actual_ipv4->tot_len), error)) + return STATUS_ERR; + break; + default: + if (check_field("ipv4_total_length", + ntohs(script_ipv4->tot_len), + ntohs(actual_ipv4->tot_len), error)) + return STATUS_ERR; + break; + } if (verify_outbound_live_ecn(script_packet->ecn, ipv4_ecn_bits(actual_ipv4), @@ -766,6 +1024,465 @@ static int verify_ipv6( return STATUS_OK; } +static int verify_data_chunk(struct sctp_data_chunk *actual_chunk, + struct sctp_data_chunk *script_chunk, + u32 flags, char **error) +{ + if (check_field("sctp_data_chunk_tsn", + ntohl(script_chunk->tsn), + ntohl(actual_chunk->tsn), + error) || + (flags & FLAG_DATA_CHUNK_SID_NOCHECK ? STATUS_OK : + check_field("sctp_data_chunk_sid", + ntohs(script_chunk->sid), + ntohs(actual_chunk->sid), + error)) || + (flags & FLAG_DATA_CHUNK_SSN_NOCHECK? STATUS_OK : + check_field("sctp_data_chunk_ssn", + ntohs(script_chunk->ssn), + ntohs(actual_chunk->ssn), + error)) || + (flags & FLAG_DATA_CHUNK_PPID_NOCHECK? STATUS_OK : + check_field("sctp_data_chunk_ppid", + ntohl(script_chunk->ppid), + ntohl(actual_chunk->ppid), + error))) { + return STATUS_ERR; + } + return STATUS_OK; +} + +static int verify_init_chunk(struct sctp_init_chunk *actual_chunk, + struct sctp_init_chunk *script_chunk, + u32 flags, char **error) +{ + if (check_field("sctp_init_chunk_tag", + ntohl(script_chunk->initiate_tag), + ntohl(actual_chunk->initiate_tag), + error) || + (flags & FLAG_INIT_CHUNK_A_RWND_NOCHECK ? STATUS_OK : + check_field("sctp_init_chunk_a_rwnd", + ntohl(script_chunk->a_rwnd), + ntohl(actual_chunk->a_rwnd), + error)) || + (flags & FLAG_INIT_CHUNK_OS_NOCHECK? STATUS_OK : + check_field("sctp_init_chunk_os", + ntohs(script_chunk->os), + ntohs(actual_chunk->os), + error)) || + (flags & FLAG_INIT_CHUNK_IS_NOCHECK? STATUS_OK : + check_field("sctp_init_chunk_is", + ntohs(script_chunk->is), + ntohs(actual_chunk->is), + error)) || + check_field("sctp_init_chunk_tsn", + ntohl(script_chunk->initial_tsn), + ntohl(actual_chunk->initial_tsn), + error)) { + return STATUS_ERR; + } + /* FIXME: Validate parameters */ + return STATUS_OK; +} + +static int verify_init_ack_chunk(struct sctp_init_ack_chunk *actual_chunk, + struct sctp_init_ack_chunk *script_chunk, + u32 flags, char **error) +{ + if (check_field("sctp_init_ack_chunk_tag", + ntohl(script_chunk->initiate_tag), + ntohl(actual_chunk->initiate_tag), + error) || + (flags & FLAG_INIT_ACK_CHUNK_A_RWND_NOCHECK ? STATUS_OK : + check_field("sctp_init_ack_chunk_a_rwnd", + ntohl(script_chunk->a_rwnd), + ntohl(actual_chunk->a_rwnd), + error)) || + (flags & FLAG_INIT_ACK_CHUNK_OS_NOCHECK? STATUS_OK : + check_field("sctp_init_ack_chunk_os", + ntohs(script_chunk->os), + ntohs(actual_chunk->os), + error)) || + (flags & FLAG_INIT_ACK_CHUNK_IS_NOCHECK? STATUS_OK : + check_field("sctp_init_ack_chunk_is", + ntohs(script_chunk->is), + ntohs(actual_chunk->is), + error)) || + check_field("sctp_init_ack_chunk_tsn", + ntohl(script_chunk->initial_tsn), + ntohl(actual_chunk->initial_tsn), + error)) { + return STATUS_ERR; + } + /* FIXME: Validate parameters */ + return STATUS_OK; +} + +static int verify_sack_chunk(struct sctp_sack_chunk *actual_chunk, + struct sctp_sack_chunk *script_chunk, + u32 flags, char **error) +{ + u16 actual_nr_gap_blocks, actual_nr_dup_tsns; + u16 script_nr_gap_blocks, script_nr_dup_tsns; + u16 i, actual_base, script_base; + + actual_nr_gap_blocks = ntohs(actual_chunk->nr_gap_blocks); + actual_nr_dup_tsns = ntohs(actual_chunk->nr_dup_tsns); + script_nr_gap_blocks = ntohs(script_chunk->nr_gap_blocks); + script_nr_dup_tsns = ntohs(script_chunk->nr_dup_tsns); + + if ((flags & FLAG_SACK_CHUNK_CUM_TSN_NOCHECK ? STATUS_OK : + check_field("sctp_sack_chunk_cum_tsn", + ntohl(script_chunk->cum_tsn), + ntohl(actual_chunk->cum_tsn), + error)) || + (flags & FLAG_SACK_CHUNK_A_RWND_NOCHECK ? STATUS_OK : + check_field("sctp_sack_chunk_a_rwnd", + ntohl(script_chunk->a_rwnd), + ntohl(actual_chunk->a_rwnd), + error)) || + (flags & FLAG_SACK_CHUNK_GAP_BLOCKS_NOCHECK? STATUS_OK : + check_field("sctp_sack_chunk_nr_gap_blocks", + script_nr_gap_blocks, + actual_nr_gap_blocks, + error)) || + (flags & FLAG_SACK_CHUNK_DUP_TSNS_NOCHECK? STATUS_OK : + check_field("sctp_sack_chunk_nr_dup_tsns", + script_nr_dup_tsns, + actual_nr_dup_tsns, + error))) { + return STATUS_ERR; + } + + if ((flags & FLAG_SACK_CHUNK_GAP_BLOCKS_NOCHECK) == 0) { + for (i = 0; i < script_nr_gap_blocks; i++) { + if (check_field("sctp_sack_chunk_gap_block_start", + ntohs(script_chunk->block[i].gap.start), + ntohs(actual_chunk->block[i].gap.start), + error) || + check_field("sctp_sack_chunk_gap_block_end", + ntohs(script_chunk->block[i].gap.end), + ntohs(actual_chunk->block[i].gap.end), + error)) { + return STATUS_ERR; + } + } + } + if ((flags & FLAG_SACK_CHUNK_DUP_TSNS_NOCHECK) == 0) { + for (i = 0; i < script_nr_dup_tsns; i++) { + actual_base = actual_nr_gap_blocks; + if ((flags & FLAG_SACK_CHUNK_GAP_BLOCKS_NOCHECK) == 0) { + script_base = actual_nr_gap_blocks; + } else { + script_base = 0; + } + if (check_field("sctp_sack_chunk_dup_tsn", + ntohl(script_chunk->block[script_base + i].tsn), + ntohl(actual_chunk->block[actual_base + i].tsn), + error)) { + return STATUS_ERR; + } + } + } + return STATUS_OK; +} + +static int verify_heartbeat_chunk(struct sctp_heartbeat_chunk *actual_chunk, + struct sctp_heartbeat_chunk *script_chunk, + u32 flags, char **error) +{ + u16 length; + + if (flags & FLAG_CHUNK_VALUE_NOCHECK) { + return STATUS_OK; + } else { + assert((flags & FLAG_CHUNK_LENGTH_NOCHECK) == 0); + length = ntohs(actual_chunk->length); + assert(length >= sizeof(struct sctp_heartbeat_chunk)); + if (memcmp(actual_chunk->value, + script_chunk->value, + length - sizeof(struct sctp_heartbeat_chunk)) == 0) { + return STATUS_OK; + } else { + return STATUS_ERR; + } + } +} + +static int verify_heartbeat_ack_chunk(struct sctp_heartbeat_ack_chunk *actual_chunk, + struct sctp_heartbeat_ack_chunk *script_chunk, + u32 flags, char **error) +{ + u16 length; + + if (flags & FLAG_CHUNK_VALUE_NOCHECK) { + return STATUS_OK; + } else { + assert((flags & FLAG_CHUNK_LENGTH_NOCHECK) == 0); + length = ntohs(actual_chunk->length); + assert(length >= sizeof(struct sctp_heartbeat_ack_chunk)); + if (memcmp(actual_chunk->value, + script_chunk->value, + length - sizeof(struct sctp_heartbeat_ack_chunk)) == 0) { + return STATUS_OK; + } else { + return STATUS_ERR; + } + } +} + +static int verify_abort_chunk(struct sctp_abort_chunk *actual_chunk, + struct sctp_abort_chunk *script_chunk, + u32 flags, char **error) +{ + /* FIXME: Validate causes */ + return STATUS_OK; +} + +static int verify_shutdown_chunk(struct sctp_shutdown_chunk *actual_chunk, + struct sctp_shutdown_chunk *script_chunk, + u32 flags, char **error) +{ + return (flags & FLAG_SHUTDOWN_CHUNK_CUM_TSN_NOCHECK) ? STATUS_OK : + check_field("sctp_shutdown_chunk_cum_tsn", + ntohl(script_chunk->cum_tsn), + ntohl(actual_chunk->cum_tsn), + error); +} + +static int verify_shutdown_ack_chunk(struct sctp_shutdown_ack_chunk *actual_chunk, + struct sctp_shutdown_ack_chunk *script_chunk, + u32 flags, char **error) +{ + /* Nothing to check */ + return STATUS_OK; +} + +static int verify_error_chunk(struct sctp_error_chunk *actual_chunk, + struct sctp_error_chunk *script_chunk, + u32 flags, char **error) +{ + /* FIXME: Validate causes */ + return STATUS_OK; +} + +static int verify_cookie_echo_chunk(struct sctp_cookie_echo_chunk *actual_chunk, + struct sctp_cookie_echo_chunk *script_chunk, + u32 flags, char **error) +{ + u16 length; + + if (flags & FLAG_CHUNK_VALUE_NOCHECK) { + return STATUS_OK; + } else { + assert((flags & FLAG_CHUNK_LENGTH_NOCHECK) == 0); + length = ntohs(actual_chunk->length); + assert(length >= sizeof(struct sctp_cookie_echo_chunk)); + if (memcmp(actual_chunk->cookie, + script_chunk->cookie, + length - sizeof(struct sctp_cookie_echo_chunk)) == 0) { + return STATUS_OK; + } else { + return STATUS_ERR; + } + } +} + +static int verify_cookie_ack_chunk(struct sctp_cookie_ack_chunk *actual_chunk, + struct sctp_cookie_ack_chunk *script_chunk, + u32 flags, char **error) +{ + /* Nothing to check */ + return STATUS_OK; +} + +static int verify_ecne_chunk(struct sctp_ecne_chunk *actual_chunk, + struct sctp_ecne_chunk *script_chunk, + u32 flags, char **error) +{ + return (flags & FLAG_ECNE_CHUNK_LOWEST_TSN_NOCHECK ? STATUS_OK : + check_field("sctp_ecne_chunk_lowest_tsn", + ntohl(script_chunk->lowest_tsn), + ntohl(actual_chunk->lowest_tsn), + error)); +} + +static int verify_cwr_chunk(struct sctp_cwr_chunk *actual_chunk, + struct sctp_cwr_chunk *script_chunk, + u32 flags, char **error) +{ + return (flags & FLAG_CWR_CHUNK_LOWEST_TSN_NOCHECK ? STATUS_OK : + check_field("sctp_cwr_chunk_lowest_tsn", + ntohl(script_chunk->lowest_tsn), + ntohl(actual_chunk->lowest_tsn), + error)); +} + +static int verify_shutdown_complete_chunk(struct sctp_shutdown_complete_chunk *actual_chunk, + struct sctp_shutdown_complete_chunk *script_chunk, + u32 flags, char **error) +{ + /* Nothing to check */ + return STATUS_OK; +} + +/* Verify that required actual SCTP packet fields are as the script expected. */ +static int verify_sctp( + const struct packet *actual_packet, + const struct packet *script_packet, + int layer, char **error) +{ + struct sctp_chunks_iterator iter; + struct sctp_chunk *actual_chunk; + struct sctp_chunk *script_chunk; + struct sctp_chunk_list_item *script_chunk_item; + u32 flags; + int result; + + DEBUGP("Verifying SCTP packet\n") + DEBUGP("script packet: src port %05u, dst port %05u, v-tag 0x%08x\n", + ntohs(script_packet->sctp->src_port), + ntohs(script_packet->sctp->dst_port), + ntohl(script_packet->sctp->v_tag)); + DEBUGP("actual packet: src port %05u, dst port %05u, v-tag 0x%08x\n", + ntohs(actual_packet->sctp->src_port), + ntohs(actual_packet->sctp->dst_port), + ntohl(actual_packet->sctp->v_tag)); + for (actual_chunk = sctp_chunks_begin((struct packet *)actual_packet, &iter, error), + script_chunk_item = script_packet->chunk_list->first; + actual_chunk != NULL && script_chunk_item != NULL; + actual_chunk = sctp_chunks_next(&iter, error), + script_chunk_item = script_chunk_item->next) { + if (*error != NULL) { + DEBUGP("Error during iteration\n"); + return STATUS_ERR; + } + script_chunk = script_chunk_item->chunk; + flags = script_chunk_item->flags; + assert(script_chunk != NULL); + DEBUGP("script chunk: type %02d, flags 0x%02x, length %04d\n", + script_chunk->type, + script_chunk->flags, + ntohs(script_chunk->length)); + DEBUGP("actual chunk: type %02d, flags 0x%02x, length %04d\n", + actual_chunk->type, + actual_chunk->flags, + ntohs(actual_chunk->length)); + if (check_field("sctp_chunk_type", + script_chunk->type, + actual_chunk->type, + error) || + (flags & FLAG_CHUNK_FLAGS_NOCHECK ? STATUS_OK : + check_field("sctp_chunk_flags", + script_chunk->flags, + actual_chunk->flags, + error)) || + (flags & FLAG_CHUNK_LENGTH_NOCHECK ? STATUS_OK : + check_field("sctp_chunk_length", + ntohs(script_chunk->length), + ntohs(actual_chunk->length), + error))) { + return STATUS_ERR; + } + switch (actual_chunk->type) { + case SCTP_DATA_CHUNK_TYPE: + result = verify_data_chunk((struct sctp_data_chunk *)actual_chunk, + (struct sctp_data_chunk *)script_chunk, + flags, error); + break; + case SCTP_INIT_CHUNK_TYPE: + result = verify_init_chunk((struct sctp_init_chunk *)actual_chunk, + (struct sctp_init_chunk *)script_chunk, + flags, error); + break; + case SCTP_INIT_ACK_CHUNK_TYPE: + result = verify_init_ack_chunk((struct sctp_init_ack_chunk *)actual_chunk, + (struct sctp_init_ack_chunk *)script_chunk, + flags, error); + break; + case SCTP_SACK_CHUNK_TYPE: + result = verify_sack_chunk((struct sctp_sack_chunk *)actual_chunk, + (struct sctp_sack_chunk *)script_chunk, + flags, error); + break; + case SCTP_HEARTBEAT_CHUNK_TYPE: + result = verify_heartbeat_chunk((struct sctp_heartbeat_chunk *)actual_chunk, + (struct sctp_heartbeat_chunk *)script_chunk, + flags, error); + break; + case SCTP_HEARTBEAT_ACK_CHUNK_TYPE: + result = verify_heartbeat_ack_chunk((struct sctp_heartbeat_ack_chunk *)actual_chunk, + (struct sctp_heartbeat_ack_chunk *)script_chunk, + flags, error); + break; + case SCTP_ABORT_CHUNK_TYPE: + result = verify_abort_chunk((struct sctp_abort_chunk *)actual_chunk, + (struct sctp_abort_chunk *)script_chunk, + flags, error); + break; + case SCTP_SHUTDOWN_CHUNK_TYPE: + result = verify_shutdown_chunk((struct sctp_shutdown_chunk *)actual_chunk, + (struct sctp_shutdown_chunk *)script_chunk, + flags, error); + break; + case SCTP_SHUTDOWN_ACK_CHUNK_TYPE: + result = verify_shutdown_ack_chunk((struct sctp_shutdown_ack_chunk *)actual_chunk, + (struct sctp_shutdown_ack_chunk *)script_chunk, + flags, error); + break; + case SCTP_ERROR_CHUNK_TYPE: + result = verify_error_chunk((struct sctp_error_chunk *)actual_chunk, + (struct sctp_error_chunk *)script_chunk, + flags, error); + break; + case SCTP_COOKIE_ECHO_CHUNK_TYPE: + result = verify_cookie_echo_chunk((struct sctp_cookie_echo_chunk *)actual_chunk, + (struct sctp_cookie_echo_chunk *)script_chunk, + flags, error); + break; + case SCTP_COOKIE_ACK_CHUNK_TYPE: + result = verify_cookie_ack_chunk((struct sctp_cookie_ack_chunk *)actual_chunk, + (struct sctp_cookie_ack_chunk *)script_chunk, + flags, error); + break; + case SCTP_ECNE_CHUNK_TYPE: + result = verify_ecne_chunk((struct sctp_ecne_chunk *)actual_chunk, + (struct sctp_ecne_chunk *)script_chunk, + flags, error); + break; + case SCTP_CWR_CHUNK_TYPE: + result = verify_cwr_chunk((struct sctp_cwr_chunk *)actual_chunk, + (struct sctp_cwr_chunk *)script_chunk, + flags, error); + break; + case SCTP_SHUTDOWN_COMPLETE_CHUNK_TYPE: + result = verify_shutdown_complete_chunk((struct sctp_shutdown_complete_chunk *)actual_chunk, + (struct sctp_shutdown_complete_chunk *)script_chunk, + flags, error); + break; + default: + result = STATUS_ERR; + assert(!"unsupported SCTP chunk type"); + break; + } + if (result == STATUS_ERR) { + return STATUS_ERR; + } + } + if (actual_chunk != NULL) { + DEBUGP("actual packet contains more chunks than script packet\n"); + } + if (script_chunk_item != NULL) { + DEBUGP("script packet contains more chunks than actual packet\n"); + } + if ((actual_chunk != NULL) || (script_chunk_item != NULL)) { + asprintf(error, + "live packet and expected packet have not the same number of chunks"); + return STATUS_ERR; + } + return STATUS_OK; +} + /* Verify that required actual TCP header fields are as the script expected. */ static int verify_tcp( const struct packet *actual_packet, @@ -922,6 +1639,7 @@ static int verify_header( [HEADER_IPV6] = verify_ipv6, [HEADER_GRE] = verify_gre, [HEADER_MPLS] = verify_mpls, + [HEADER_SCTP] = verify_sctp, [HEADER_TCP] = verify_tcp, [HEADER_UDP] = verify_udp, [HEADER_UDPLITE] = verify_udplite, @@ -956,8 +1674,11 @@ static int verify_outbound_live_headers( const int script_headers = packet_header_count(script_packet); int i; + DEBUGP("verify_outbound_live_headers\n"); + assert((actual_packet->ipv4 != NULL) || (actual_packet->ipv6 != NULL)); - assert((actual_packet->tcp != NULL) || + assert((actual_packet->sctp != NULL) || + (actual_packet->tcp != NULL) || (actual_packet->udp != NULL) || (actual_packet->udplite != NULL)); @@ -1044,6 +1765,8 @@ static int verify_outbound_live_payload( struct packet *actual_packet, struct packet *script_packet, char **error) { + if (actual_packet->sctp != NULL) + return STATUS_OK; /* Diff the TCP/UDP data payloads. We've already implicitly * checked their length by checking the IP and TCP/UDP headers. */ @@ -1185,7 +1908,9 @@ static bool is_script_packet_match_for_socket( { const bool is_packet_icmp = (packet->icmpv4 || packet->icmpv6); - if (socket->protocol == IPPROTO_TCP) + if (socket->protocol == IPPROTO_SCTP) + return packet->sctp || is_packet_icmp; + else if (socket->protocol == IPPROTO_TCP) return packet->tcp || is_packet_icmp; else if (socket->protocol == IPPROTO_UDP) return packet->udp || is_packet_icmp; @@ -1206,7 +1931,7 @@ static int find_or_create_socket_for_script_packet( DEBUGP("find_or_create_socket_for_script_packet\n"); - if (packet->tcp != NULL) { + if ((packet->tcp != NULL) || (packet->sctp != NULL)) { /* Is this an inbound packet matching a listening * socket? If so, this call will create a new child * socket object. @@ -1243,33 +1968,112 @@ static int do_outbound_script_packet( struct state *state, struct packet *packet, struct socket *socket, char **error) { - DEBUGP("do_outbound_script_packet\n"); + struct sctp_chunk_list_item *item; + struct sctp_chunks_iterator chunk_iter; + struct sctp_parameters_iterator param_iter; + struct sctp_chunk *chunk; + struct sctp_init_ack_chunk *init_ack; + struct sctp_cookie_echo_chunk *cookie_echo; + struct sctp_parameter *parameter; + struct sctp_state_cookie_parameter *state_cookie; int result = STATUS_ERR; /* return value */ struct packet *live_packet = NULL; + u16 cookie_length, chunk_length, parameter_length, parameters_length; + u16 padding_length; + DEBUGP("do_outbound_script_packet\n"); if ((packet->icmpv4 != NULL) || (packet->icmpv6 != NULL)) { asprintf(error, "outbound ICMP packets are not supported"); goto out; } - if ((socket->state == SOCKET_PASSIVE_PACKET_RECEIVED) && - packet->tcp && packet->tcp->syn && packet->tcp->ack) { - /* Script says we should see an outbound server SYNACK. */ - socket->script.local_isn = ntohl(packet->tcp->seq); - DEBUGP("SYNACK script.local_isn: %u\n", - socket->script.local_isn); + if (socket->state == SOCKET_PASSIVE_PACKET_RECEIVED) { + if (packet->tcp && packet->tcp->syn && packet->tcp->ack) { + /* Script says we should see an outbound server SYNACK. */ + socket->script.local_isn = ntohl(packet->tcp->seq); + DEBUGP("SYNACK script.local_isn: %u\n", + socket->script.local_isn); + } + if (packet->sctp) { + assert(packet->chunk_list != NULL); + item = packet->chunk_list->first; + if ((item != NULL) && + (item->chunk->type == SCTP_INIT_ACK_CHUNK_TYPE)) { + init_ack = (struct sctp_init_ack_chunk *)item->chunk; + socket->script.local_initiate_tag = ntohl(init_ack->initiate_tag); + socket->script.local_initial_tsn = ntohl(init_ack->initial_tsn); + DEBUGP("INIT_ACK: script.local_initiate_tag: %u\n", + socket->script.local_initiate_tag); + DEBUGP("INIT_ACK: script.local_initial_tsn: %u\n", + socket->script.local_initial_tsn); + } + } } /* Sniff outbound live packet and verify it's for the right socket. */ if (sniff_outbound_live_packet(state, socket, &live_packet, error)) goto out; - if ((socket->state == SOCKET_PASSIVE_PACKET_RECEIVED) && - packet->tcp && packet->tcp->syn && packet->tcp->ack) { - socket->state = SOCKET_PASSIVE_SYNACK_SENT; - socket->live.local_isn = ntohl(live_packet->tcp->seq); - DEBUGP("SYNACK live.local_isn: %u\n", - socket->live.local_isn); + if (socket->state == SOCKET_PASSIVE_PACKET_RECEIVED) { + if (packet->tcp && packet->tcp->syn && packet->tcp->ack) { + socket->state = SOCKET_PASSIVE_SYNACK_SENT; + socket->live.local_isn = ntohl(live_packet->tcp->seq); + DEBUGP("SYNACK live.local_isn: %u\n", + socket->live.local_isn); + } + if (live_packet->sctp) { + chunk = sctp_chunks_begin(live_packet, &chunk_iter, error); + if ((*error == NULL) && + (chunk != NULL) && + (chunk->type == SCTP_INIT_ACK_CHUNK_TYPE)) { + chunk_length = ntohs(chunk->length); + if (chunk_length < sizeof(struct sctp_init_ack_chunk)) { + asprintf(error, "INIT chunk too short (length=%u)", chunk_length); + goto out; + } + parameters_length = chunk_length - sizeof(struct sctp_init_chunk); + init_ack = (struct sctp_init_ack_chunk *)chunk; + + for (parameter = sctp_parameters_begin(init_ack->parameter, + parameters_length, + ¶m_iter, error); + parameter != NULL; + parameter = sctp_parameters_next(¶m_iter, error)) { + if (*error != NULL) + goto out; + if (ntohs(parameter->type) == SCTP_STATE_COOKIE_PARAMETER_TYPE) { + state_cookie = (struct sctp_state_cookie_parameter *)parameter; + parameter_length = ntohs(state_cookie->length); + if (parameter_length < sizeof(struct sctp_state_cookie_parameter)) { + asprintf(error, "State Cookie parameter too short (length=%u)", parameter_length); + goto out; + } + cookie_length = parameter_length - 4; + padding_length = cookie_length % 4; + if (padding_length > 0) { + padding_length = 4 - padding_length; + } + chunk_length = sizeof(struct sctp_cookie_echo_chunk) + cookie_length; + cookie_echo = (struct sctp_cookie_echo_chunk *)malloc(chunk_length + padding_length); + cookie_echo->type = SCTP_COOKIE_ECHO_CHUNK_TYPE; + cookie_echo->flags = 0; + cookie_echo->length = htons(chunk_length); + memcpy(cookie_echo->cookie, state_cookie->cookie, cookie_length); + memset(cookie_echo->cookie + cookie_length, 0, padding_length); + socket->prepared_state_cookie = cookie_echo; + socket->prepared_state_cookie_length = chunk_length + padding_length; + break; + } + } + socket->live.local_initiate_tag = ntohl(init_ack->initiate_tag); + socket->live.local_initial_tsn = ntohl(init_ack->initial_tsn); + socket->state = SOCKET_PASSIVE_INIT_ACK_SENT; + DEBUGP("INIT_ACK: live.local_initiate_tag: %u\n", + socket->live.local_initiate_tag); + DEBUGP("INIT_ACK: live.local_initial_tsn: %u\n", + socket->live.local_initial_tsn); + } + } } verbose_packet_dump(state, "outbound sniffed", live_packet, @@ -1298,7 +2102,7 @@ static int send_live_ip_packet(struct netdev *netdev, /* We do IPv4 and IPv6 */ assert(packet->ipv4 || packet->ipv6); /* We only do TCP, UDP, UDPLite and ICMP */ - assert(packet->tcp || packet->udp || packet->udplite || + assert(packet->sctp || packet->tcp || packet->udp || packet->udplite || packet->icmpv4 || packet->icmpv6); /* Fill in layer 3 and layer 4 checksums */ @@ -1312,19 +2116,74 @@ static int do_inbound_script_packet( struct state *state, struct packet *packet, struct socket *socket, char **error) { - DEBUGP("do_inbound_script_packet\n"); + struct sctp_init_ack_chunk *init_ack; + struct sctp_chunk_list_item *item; int result = STATUS_ERR; /* return value */ + u16 offset; + bool cooie_echo_replaced = false; + u16 i; - if ((socket->state == SOCKET_PASSIVE_SYNACK_SENT) && - packet->tcp && packet->tcp->ack) { - /* Received the ACK that completes the 3-way handshake. */ - socket->state = SOCKET_PASSIVE_SYNACK_ACKED; - } else if ((socket->state == SOCKET_ACTIVE_SYN_SENT) && - packet->tcp && packet->tcp->syn && packet->tcp->ack) { - /* Received the server's SYNACK, which ACKs our SYN. */ - socket->state = SOCKET_ACTIVE_SYN_ACKED; - socket->script.remote_isn = ntohl(packet->tcp->seq); - socket->live.remote_isn = ntohl(packet->tcp->seq); + DEBUGP("do_inbound_script_packet\n"); + if (packet->tcp) { + if ((socket->state == SOCKET_PASSIVE_SYNACK_SENT) && + packet->tcp->ack) { + /* Received the ACK that completes the 3-way handshake. */ + socket->state = SOCKET_PASSIVE_SYNACK_ACKED; + } else if ((socket->state == SOCKET_ACTIVE_SYN_SENT) && + packet->tcp->syn && packet->tcp->ack) { + /* Received the server's SYNACK, which ACKs our SYN. */ + socket->state = SOCKET_ACTIVE_SYN_ACKED; + socket->script.remote_isn = ntohl(packet->tcp->seq); + socket->live.remote_isn = ntohl(packet->tcp->seq); + } + } + if (packet->sctp) { + item = packet->chunk_list->first; + if ((socket->state == SOCKET_ACTIVE_INIT_SENT) && + (item != NULL) && + (item->chunk->type == SCTP_INIT_ACK_CHUNK_TYPE)) { + init_ack = (struct sctp_init_ack_chunk *)item->chunk; + DEBUGP("Moving socket in SOCKET_ACTIVE_INIT_ACK_RECEIVED\n"); + socket->state = SOCKET_ACTIVE_INIT_ACK_RECEIVED; + socket->script.remote_initiate_tag = ntohl(init_ack->initiate_tag); + socket->script.remote_initial_tsn = ntohl(init_ack->initial_tsn); + socket->live.remote_initiate_tag = ntohl(init_ack->initiate_tag); + socket->live.remote_initial_tsn = ntohl(init_ack->initial_tsn); + DEBUGP("remote_initiate_tag %d, remote_initial_tsn %d\n", ntohl(init_ack->initiate_tag), ntohl(init_ack->initial_tsn)); + } + if (socket->state == SOCKET_PASSIVE_INIT_ACK_SENT) { + for (; item != NULL; item = item->next) { + if (item->chunk->type == SCTP_COOKIE_ECHO_CHUNK_TYPE) { + assert(item->next == NULL); /*FIXME: Handle chunks after the COOKIE_ECHO */ + offset = socket->prepared_state_cookie_length - item->length; + memcpy(item->chunk, socket->prepared_state_cookie, socket->prepared_state_cookie_length); + item->length = socket->prepared_state_cookie_length; + packet->buffer_bytes += offset; + packet->ip_bytes += offset; + if (packet->ipv4) { + packet->ipv4->tot_len = htons(ntohs(packet->ipv4->tot_len) + offset); + } + if (packet->ipv6) { + packet->ipv6->payload_len = htons(ntohs(packet->ipv6->payload_len) + offset); + } + for (i = 0; i < PACKET_MAX_HEADERS; i++) { + if ((packet->ipv4 != NULL && packet->headers[i].h.ipv4 == packet->ipv4) || + (packet->ipv6 != NULL && packet->headers[i].h.ipv6 == packet->ipv6)) { + break; + } + } + assert(packet->headers[i + 1].type == HEADER_SCTP); + packet->headers[i].total_bytes += offset; + packet->headers[i + 1].total_bytes += offset; + cooie_echo_replaced = true; + socket->state = SOCKET_PASSIVE_COOKIE_ECHO_RECEIVED; + } else { + if (cooie_echo_replaced) { + item->chunk = (struct sctp_chunk *)((char *)item->chunk + offset); + } + } + } + } } /* Start with a bit-for-bit copy of the packet from the script. */ @@ -1462,6 +2321,52 @@ int reset_connection(struct state *state, struct socket *socket) return result; } +/* Inject an SCTP packet containing an ABORT chunk to clear the + * association state out of the kernel, so the association does not + * continue to retransmit packets that may be sniffed during later test + * executions and cause false negatives. + */ +int abort_association(struct state *state, struct socket *socket) +{ + char *error = NULL; + struct packet *packet; + struct sctp_chunk_list *chunk_list; + struct tuple live_inbound; + int result = 0; + + if ((socket->live.local_initiate_tag == 0) && + (socket->live.remote_initiate_tag == 0)) { + return STATUS_OK; + } + chunk_list = sctp_chunk_list_new(); + if (socket->live.local_initiate_tag != 0) { + sctp_chunk_list_append(chunk_list, sctp_abort_chunk_new(0)); + } else { + sctp_chunk_list_append(chunk_list, sctp_abort_chunk_new(0x01)); + } + packet = new_sctp_packet(socket->address_family, + DIRECTION_INBOUND, ECN_NONE, + chunk_list, &error); + if (packet == NULL) + die("%s", error); + /* Rewrite addresses and port to match inbound live traffic. */ + socket_get_inbound(&socket->live, &live_inbound); + set_packet_tuple(packet, &live_inbound); + /* Rewrite the verification tag in the SCTP common header */ + if (socket->live.local_initiate_tag != 0) { + packet->sctp->v_tag = htonl(socket->live.local_initiate_tag); + } else { + packet->sctp->v_tag = htonl(socket->live.remote_initiate_tag); + } + + /* Inject live packet into kernel. */ + result = send_live_ip_packet(state->netdev, packet); + + packet_free(packet); + + return result; +} + struct packets *packets_new(void) { struct packets *packets = calloc(1, sizeof(struct packets)); diff --git a/gtests/net/packetdrill/run_packet.h b/gtests/net/packetdrill/run_packet.h index 194676d9..daecd8dc 100644 --- a/gtests/net/packetdrill/run_packet.h +++ b/gtests/net/packetdrill/run_packet.h @@ -58,4 +58,10 @@ extern int run_packet_event(struct state *state, extern int reset_connection(struct state *state, struct socket *socket); +/* Inject an SCTP packet containing an ABORT chunk to clear the association + * state out of the kernel. + */ +extern int abort_association(struct state *state, + struct socket *socket); + #endif /* __RUN_PACKET_H__ */ diff --git a/gtests/net/packetdrill/run_system_call.c b/gtests/net/packetdrill/run_system_call.c index aa9c3a90..6181c7c9 100644 --- a/gtests/net/packetdrill/run_system_call.c +++ b/gtests/net/packetdrill/run_system_call.c @@ -788,7 +788,8 @@ static int run_syscall_accept(struct state *state, } if ((socket->state == SOCKET_PASSIVE_SYNACK_SENT) || /* TFO */ - (socket->state == SOCKET_PASSIVE_SYNACK_ACKED)) { + (socket->state == SOCKET_PASSIVE_SYNACK_ACKED) || + (socket->state == SOCKET_PASSIVE_COOKIE_ECHO_RECEIVED)) { assert(is_equal_ip(&socket->live.remote.ip, &ip)); assert(is_equal_port(socket->live.remote.port, htons(port))); diff --git a/gtests/net/packetdrill/sctp_packet.c b/gtests/net/packetdrill/sctp_packet.c new file mode 100644 index 00000000..9d1777e2 --- /dev/null +++ b/gtests/net/packetdrill/sctp_packet.c @@ -0,0 +1,692 @@ +/* + * Copyright 2015 Michael Tuexen + * + * 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: tuexen@fh-muenster.de (Michael Tuexen) + * + * Implementation of module for formatting SCTP packets. + */ + +#include "logging.h" +#include "sctp_packet.h" +#include "ip_packet.h" +#include "sctp.h" + +/* + * ToDo: + * - Add support for chunk flags (fix hard coded flags for DATA chunk) + * - Add support for user data length in DATA chunks (fix hard coded payload) + * - Add support for parameters (fix hard coded state cookie in INIT-ACK) + * - Add support for error causes + */ + +struct sctp_chunk_list_item * +sctp_chunk_list_item_new(struct sctp_chunk *chunk, u32 length, u32 flags) +{ + struct sctp_chunk_list_item *item; + + item = malloc(sizeof(struct sctp_chunk_list_item)); + assert(item != NULL); + item->next = NULL; + item->chunk = chunk; + item->length = length; + item->flags = flags; + return item; +} + +struct sctp_chunk_list_item * +sctp_data_chunk_new(s64 tsn, s64 sid, s64 ssn, s64 ppid) +{ + struct sctp_chunk_list_item *item; + struct sctp_data_chunk *chunk; + u32 flags; + u16 payload_len = 1000; + + flags = 0; + chunk = malloc(sizeof(struct sctp_data_chunk) + payload_len); + assert(chunk != NULL); + chunk->type = SCTP_DATA_CHUNK_TYPE; + chunk->flags = 0x03; /* FIXME */ + chunk->length = htons(sizeof(struct sctp_data_chunk) + payload_len); + if (tsn == -1) { + chunk->tsn = htonl(0); + flags |= FLAG_DATA_CHUNK_TSN_NOCHECK; + } else { + chunk->tsn = htonl((u32)tsn); + } + if (sid == -1) { + chunk->sid = htons(0); + flags |= FLAG_DATA_CHUNK_SID_NOCHECK; + } else { + chunk->sid = htons((u16)sid); + } + if (ssn == -1) { + chunk->ssn = htons(0); + flags |= FLAG_DATA_CHUNK_SSN_NOCHECK; + } else { + chunk->ssn = htons((u16)ssn); + } + if (ppid == -1) { + chunk->ppid = htons(0); + flags |= FLAG_DATA_CHUNK_PPID_NOCHECK; + } else { + chunk->ppid = htons((u32)ppid); + } + memset(chunk->data, 0, payload_len); + item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk, + (u32)sizeof(struct sctp_data_chunk) + payload_len, + flags); + return item; +} + +struct sctp_chunk_list_item * +sctp_init_chunk_new(s64 tag, s64 a_rwnd, s64 os, s64 is, s64 tsn) +{ + struct sctp_chunk_list_item *item; + struct sctp_init_chunk *chunk; + u32 flags; + + flags = 0; + chunk = malloc(sizeof(struct sctp_init_chunk)); + assert(chunk != NULL); + chunk->type = SCTP_INIT_CHUNK_TYPE; + chunk->flags = 0; + /* FIXME */ + flags |= FLAG_CHUNK_LENGTH_NOCHECK; + chunk->length = htons(sizeof(struct sctp_init_chunk)); + chunk->initiate_tag = htonl((u32)tag); + if (a_rwnd == -1) { + chunk->a_rwnd = htonl(0); + flags |= FLAG_INIT_CHUNK_A_RWND_NOCHECK; + } else { + chunk->a_rwnd = htonl((u32)a_rwnd); + } + if (os == -1) { + chunk->os = htons(0); + flags |= FLAG_INIT_CHUNK_OS_NOCHECK; + } else { + chunk->os = htons((u16)os); + } + if (is == -1) { + chunk->is = htons(0); + flags |= FLAG_INIT_CHUNK_IS_NOCHECK; + } else { + chunk->is = htons((u16)is); + } + chunk->initial_tsn = htonl((u32)tsn); + item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk, + (u32)sizeof(struct sctp_init_chunk), + flags); + return item; +} + +/* FIXME: Don't fake the cookie that way... */ +struct sctp_chunk_list_item * +sctp_init_ack_chunk_new(s64 tag, s64 a_rwnd, s64 os, s64 is, s64 tsn) +{ + struct sctp_chunk_list_item *item; + struct sctp_init_ack_chunk *chunk; + struct sctp_state_cookie_parameter state_cookie_parameter; + u32 flags; + + flags = 0; + chunk = malloc(sizeof(struct sctp_init_ack_chunk) + sizeof(struct sctp_state_cookie_parameter)); + assert(chunk != NULL); + chunk->type = SCTP_INIT_ACK_CHUNK_TYPE; + chunk->flags = 0; + /* FIXME */ + flags |= FLAG_CHUNK_LENGTH_NOCHECK; + chunk->length = htons(sizeof(struct sctp_init_ack_chunk) + sizeof(struct sctp_state_cookie_parameter)); + chunk->initiate_tag = htonl((u32)tag); + if (a_rwnd == -1) { + chunk->a_rwnd = htonl(0); + flags |= FLAG_INIT_CHUNK_A_RWND_NOCHECK; + } else { + chunk->a_rwnd = htonl((u32)a_rwnd); + } + if (os == -1) { + chunk->os = htons(0); + flags |= FLAG_INIT_CHUNK_OS_NOCHECK; + } else { + chunk->os = htons((u16)os); + } + if (is == -1) { + chunk->is = htons(0); + flags |= FLAG_INIT_CHUNK_IS_NOCHECK; + } else { + chunk->is = htons((u16)is); + } + chunk->initial_tsn = htonl((u32)tsn); + state_cookie_parameter.type = htons(SCTP_STATE_COOKIE_PARAMETER_TYPE); + state_cookie_parameter.length = htons(sizeof(struct sctp_state_cookie_parameter)); + memcpy(chunk->parameter, &state_cookie_parameter, sizeof(struct sctp_state_cookie_parameter)); + item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk, + (u32)sizeof(struct sctp_init_ack_chunk) + sizeof(struct sctp_state_cookie_parameter), + flags); + return item; +} + +struct sctp_chunk_list_item * +sctp_sack_chunk_new(s64 cum_tsn, s64 a_rwnd) +{ + struct sctp_chunk_list_item *item; + struct sctp_sack_chunk *chunk; + u32 flags; + + flags = 0; + chunk = malloc(sizeof(struct sctp_sack_chunk)); + assert(chunk != NULL); + chunk->type = SCTP_SACK_CHUNK_TYPE; + chunk->flags = 0; + chunk->length = htons(sizeof(struct sctp_sack_chunk)); + if (cum_tsn == -1) { + chunk->cum_tsn = htonl(0); + flags |= FLAG_SACK_CHUNK_CUM_TSN_NOCHECK; + } else { + chunk->cum_tsn = htonl((u32)cum_tsn); + } + if (a_rwnd == -1) { + chunk->a_rwnd = htonl(0); + flags |= FLAG_SACK_CHUNK_A_RWND_NOCHECK; + } else { + chunk->a_rwnd = htonl((u32)a_rwnd); + } + chunk->nr_gap_blocks = htons(0); + chunk->nr_dup_tsns = htons(0); + item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk, + (u32)sizeof(struct sctp_sack_chunk), + flags); + return item; +} + +struct sctp_chunk_list_item * +sctp_heartbeat_chunk_new(u8 flags) +{ + struct sctp_chunk_list_item *item; + struct sctp_heartbeat_chunk *chunk; + + chunk = malloc(sizeof(struct sctp_heartbeat_chunk)); + assert(chunk != NULL); + chunk->type = SCTP_HEARTBEAT_CHUNK_TYPE; + chunk->flags = flags; + chunk->length = htons(sizeof(struct sctp_heartbeat_chunk)); + item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk, + (u32)sizeof(struct sctp_heartbeat_chunk), + FLAG_CHUNK_LENGTH_NOCHECK | FLAG_CHUNK_VALUE_NOCHECK); + return item; +} + +struct sctp_chunk_list_item * +sctp_heartbeat_ack_chunk_new(u8 flags) +{ + struct sctp_chunk_list_item *item; + struct sctp_heartbeat_ack_chunk *chunk; + + chunk = malloc(sizeof(struct sctp_heartbeat_ack_chunk)); + assert(chunk != NULL); + chunk->type = SCTP_HEARTBEAT_ACK_CHUNK_TYPE; + chunk->flags = flags; + chunk->length = htons(sizeof(struct sctp_heartbeat_ack_chunk)); + item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk, + (u32)sizeof(struct sctp_heartbeat_ack_chunk), + FLAG_CHUNK_LENGTH_NOCHECK | FLAG_CHUNK_VALUE_NOCHECK); + return item; +} + +struct sctp_chunk_list_item * +sctp_abort_chunk_new(u8 flags) +{ + struct sctp_chunk_list_item *item; + struct sctp_abort_chunk *chunk; + + chunk = malloc(sizeof(struct sctp_abort_chunk)); + assert(chunk != NULL); + chunk->type = SCTP_ABORT_CHUNK_TYPE; + chunk->flags = flags; + chunk->length = htons(sizeof(struct sctp_abort_chunk)); + item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk, + (u32)sizeof(struct sctp_abort_chunk), + 0); + return item; +} + +struct sctp_chunk_list_item * +sctp_shutdown_chunk_new(s64 cum_tsn) +{ + struct sctp_chunk_list_item *item; + struct sctp_shutdown_chunk *chunk; + u32 flags; + + flags = 0; + chunk = malloc(sizeof(struct sctp_shutdown_chunk)); + assert(chunk != NULL); + chunk->type = SCTP_SHUTDOWN_CHUNK_TYPE; + chunk->flags = 0; + chunk->length = htons(sizeof(struct sctp_shutdown_chunk)); + if (cum_tsn == -1) { + chunk->cum_tsn = htonl(0); + flags |= FLAG_SHUTDOWN_CHUNK_CUM_TSN_NOCHECK; + } else { + chunk->cum_tsn = htonl((u32)cum_tsn); + } + + item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk, + (u32)sizeof(struct sctp_shutdown_chunk), + flags); + return item; +} + +struct sctp_chunk_list_item * +sctp_shutdown_ack_chunk_new(u8 flags) +{ + struct sctp_chunk_list_item *item; + struct sctp_shutdown_ack_chunk *chunk; + + chunk = malloc(sizeof(struct sctp_shutdown_ack_chunk)); + assert(chunk != NULL); + chunk->type = SCTP_SHUTDOWN_ACK_CHUNK_TYPE; + chunk->flags = flags; + chunk->length = htons(sizeof(struct sctp_shutdown_ack_chunk)); + item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk, + (u32)sizeof(struct sctp_shutdown_ack_chunk), + 0); + return item; +} + +struct sctp_chunk_list_item * +sctp_error_chunk_new(u8 flags) +{ + struct sctp_chunk_list_item *item; + struct sctp_error_chunk *chunk; + + chunk = malloc(sizeof(struct sctp_error_chunk)); + assert(chunk != NULL); + chunk->type = SCTP_ERROR_CHUNK_TYPE; + chunk->flags = flags; + chunk->length = htons(sizeof(struct sctp_error_chunk)); + item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk, + (u32)sizeof(struct sctp_error_chunk), + FLAG_CHUNK_LENGTH_NOCHECK | FLAG_CHUNK_VALUE_NOCHECK); + return item; +} + +struct sctp_chunk_list_item * +sctp_cookie_echo_chunk_new(u8 flags) +{ + struct sctp_chunk_list_item *item; + struct sctp_cookie_echo_chunk *chunk; + + chunk = malloc(sizeof(struct sctp_cookie_echo_chunk)); + assert(chunk != NULL); + chunk->type = SCTP_COOKIE_ECHO_CHUNK_TYPE; + chunk->flags = flags; + chunk->length = htons(sizeof(struct sctp_cookie_echo_chunk)); + item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk, + (u32)sizeof(struct sctp_cookie_echo_chunk), + FLAG_CHUNK_LENGTH_NOCHECK | FLAG_CHUNK_VALUE_NOCHECK); + return item; +} + +struct sctp_chunk_list_item * +sctp_cookie_ack_chunk_new(u8 flags) +{ + struct sctp_chunk_list_item *item; + struct sctp_cookie_ack_chunk *chunk; + + chunk = malloc(sizeof(struct sctp_cookie_ack_chunk)); + assert(chunk != NULL); + chunk->type = SCTP_COOKIE_ACK_CHUNK_TYPE; + chunk->flags = flags; + chunk->length = htons(sizeof(struct sctp_cookie_ack_chunk)); + item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk, + (u32)sizeof(struct sctp_cookie_ack_chunk), + 0); + return item; +} + +struct sctp_chunk_list_item * +sctp_ecne_chunk_new(s64 lowest_tsn) +{ + struct sctp_chunk_list_item *item; + struct sctp_ecne_chunk *chunk; + u32 flags; + + flags = 0; + chunk = malloc(sizeof(struct sctp_ecne_chunk)); + assert(chunk != NULL); + chunk->type = SCTP_ECNE_CHUNK_TYPE; + chunk->flags = 0; + chunk->length = htons(sizeof(struct sctp_ecne_chunk)); + if (lowest_tsn == -1) { + chunk->lowest_tsn = htonl(0); + flags |= FLAG_ECNE_CHUNK_LOWEST_TSN_NOCHECK; + } else { + chunk->lowest_tsn = htonl((u32)lowest_tsn); + } + + item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk, + (u32)sizeof(struct sctp_ecne_chunk), + flags); + return item; +} + +struct sctp_chunk_list_item * +sctp_cwr_chunk_new(s64 lowest_tsn) +{ + struct sctp_chunk_list_item *item; + struct sctp_cwr_chunk *chunk; + u32 flags; + + flags = 0; + chunk = malloc(sizeof(struct sctp_cwr_chunk)); + assert(chunk != NULL); + chunk->type = SCTP_CWR_CHUNK_TYPE; + chunk->flags = 0; + chunk->length = htons(sizeof(struct sctp_cwr_chunk)); + if (lowest_tsn == -1) { + chunk->lowest_tsn = htonl(0); + flags |= FLAG_CWR_CHUNK_LOWEST_TSN_NOCHECK; + } else { + chunk->lowest_tsn = htonl((u32)lowest_tsn); + } + + item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk, + (u32)sizeof(struct sctp_cwr_chunk), + flags); + return item; +} + +struct sctp_chunk_list_item * +sctp_shutdown_complete_chunk_new(u8 flags) +{ + struct sctp_chunk_list_item *item; + struct sctp_shutdown_complete_chunk *chunk; + + chunk = malloc(sizeof(struct sctp_shutdown_complete_chunk)); + assert(chunk != NULL); + chunk->type = SCTP_SHUTDOWN_COMPLETE_CHUNK_TYPE; + chunk->flags = flags; + chunk->length = htons(sizeof(struct sctp_shutdown_complete_chunk)); + item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk, + (u32)sizeof(struct sctp_shutdown_complete_chunk), + 0); + return item; +} + +struct sctp_chunk_list *sctp_chunk_list_new(void) +{ + struct sctp_chunk_list *list; + + list = malloc(sizeof(struct sctp_chunk_list)); + assert(list != NULL); + list->first = NULL; + list->last = NULL; + list->length = 0; + return list; +} + +void sctp_chunk_list_append(struct sctp_chunk_list *list, + struct sctp_chunk_list_item *item) +{ + assert(item->next == NULL); + if (list->last == NULL) { + assert(list->first == NULL); + assert(list->length == 0); + list->first = item; + } else { + assert(list->first != NULL); + list->last->next = item; + } + list->last = item; + list->length += item->length; +} + +void sctp_chunk_list_free(struct sctp_chunk_list *list) +{ + struct sctp_chunk_list_item *current_item, *next_item; + + assert(list != NULL); + current_item = list->first; + while (current_item != NULL) { + next_item = current_item->next; + assert(next_item != NULL || current_item == list->last); + assert(current_item->chunk != NULL); + free(current_item); + current_item = next_item; + } + free(list); +} + +struct packet *new_sctp_packet(int address_family, + enum direction_t direction, + enum ip_ecn_t ecn, + struct sctp_chunk_list *list, + char **error) +{ + struct packet *packet; /* the newly-allocated result packet */ + struct header *sctp_header; /* the SCTP header info */ + struct sctp_chunk_list_item *item; + /* Calculate lengths in bytes of all sections of the packet */ + const int ip_option_bytes = 0; + const int ip_header_bytes = (ip_header_min_len(address_family) + + ip_option_bytes); + const int sctp_header_bytes = sizeof(struct sctp_common_header); + const int sctp_chunk_bytes = list->length; + const int ip_bytes = + ip_header_bytes + sctp_header_bytes + sctp_chunk_bytes; + bool overbook = false; + + /* Sanity-check all the various lengths */ + if (ip_option_bytes & 0x3) { + asprintf(error, "IP options are not padded correctly " + "to ensure IP header is a multiple of 4 bytes: " + "%d excess bytes", ip_option_bytes & 0x3); + return NULL; + } + assert((ip_header_bytes & 0x3) == 0); + + if (ip_bytes > MAX_SCTP_DATAGRAM_BYTES) { + asprintf(error, "SCTP packet too large"); + return NULL; + } + + if (direction == DIRECTION_INBOUND) { + for (item = list->first; item != NULL; item = item->next) { + switch (item->chunk->type) { + case SCTP_DATA_CHUNK_TYPE: + if (item->flags & FLAG_CHUNK_FLAGS_NOCHECK) { + asprintf(error, + "chunk flags must be specified for inbound packets"); + return NULL; + } + if (item->flags & FLAG_CHUNK_LENGTH_NOCHECK) { + asprintf(error, + "chunk length must be specified for inbound packets"); + return NULL; + } + if (item->flags & FLAG_DATA_CHUNK_TSN_NOCHECK) { + asprintf(error, + "TSN must be specified for inbound packets"); + return NULL; + } + if (item->flags & FLAG_DATA_CHUNK_SID_NOCHECK) { + asprintf(error, + "SID must be specified for inbound packets"); + return NULL; + } + if (item->flags & FLAG_DATA_CHUNK_SSN_NOCHECK) { + asprintf(error, + "SSN must be specified for inbound packets"); + return NULL; + } + if (item->flags & FLAG_DATA_CHUNK_PPID_NOCHECK) { + asprintf(error, + "PPID must be specified for inbound packets"); + return NULL; + } + break; + case SCTP_INIT_CHUNK_TYPE: + if (item->flags & FLAG_INIT_CHUNK_A_RWND_NOCHECK) { + asprintf(error, + "A_RWND must be specified for inbound packets"); + return NULL; + } + if (item->flags & FLAG_INIT_CHUNK_OS_NOCHECK) { + asprintf(error, + "OS must be specified for inbound packets"); + return NULL; + } + if (item->flags & FLAG_INIT_CHUNK_IS_NOCHECK) { + asprintf(error, + "IS must be specified for inbound packets"); + return NULL; + } + break; + case SCTP_INIT_ACK_CHUNK_TYPE: + if (item->flags & FLAG_INIT_ACK_CHUNK_A_RWND_NOCHECK) { + asprintf(error, + "A_RWND must be specified for inbound packets"); + return NULL; + } + if (item->flags & FLAG_INIT_ACK_CHUNK_OS_NOCHECK) { + asprintf(error, + "OS must be specified for inbound packets"); + return NULL; + } + if (item->flags & FLAG_INIT_ACK_CHUNK_IS_NOCHECK) { + asprintf(error, + "IS must be specified for inbound packets"); + return NULL; + } + break; + case SCTP_SACK_CHUNK_TYPE: + if (item->flags & FLAG_SACK_CHUNK_CUM_TSN_NOCHECK) { + asprintf(error, + "CUM_TSN must be specified for inbound packets"); + return NULL; + } + if (item->flags & FLAG_SACK_CHUNK_A_RWND_NOCHECK) { + asprintf(error, + "A_RWND must be specified for inbound packets"); + return NULL; + } + if (item->flags & FLAG_SACK_CHUNK_GAP_BLOCKS_NOCHECK) { + asprintf(error, + "GAP_BLOCKS must be specified for inbound packets"); + return NULL; + } + if (item->flags & FLAG_SACK_CHUNK_DUP_TSNS_NOCHECK) { + asprintf(error, + "DUP_TSNS must be specified for inbound packets"); + return NULL; + } + break; + case SCTP_HEARTBEAT_CHUNK_TYPE: + break; + case SCTP_HEARTBEAT_ACK_CHUNK_TYPE: + break; + case SCTP_ABORT_CHUNK_TYPE: + if (item->flags & FLAG_CHUNK_LENGTH_NOCHECK) { + asprintf(error, + "error causes must be specified for inbound packets"); + return NULL; + } + break; + case SCTP_SHUTDOWN_CHUNK_TYPE: + if (item->flags & FLAG_SHUTDOWN_CHUNK_CUM_TSN_NOCHECK) { + asprintf(error, + "TSN must be specified for inbound packets"); + return NULL; + } + break; + case SCTP_SHUTDOWN_ACK_CHUNK_TYPE: + break; + case SCTP_ERROR_CHUNK_TYPE: + if (item->flags & FLAG_CHUNK_LENGTH_NOCHECK) { + asprintf(error, + "error causes must be specified for inbound packets"); + return NULL; + } + break; + case SCTP_COOKIE_ECHO_CHUNK_TYPE: + overbook = true; + break; + case SCTP_COOKIE_ACK_CHUNK_TYPE: + break; + case SCTP_ECNE_CHUNK_TYPE: + if (item->flags & FLAG_ECNE_CHUNK_LOWEST_TSN_NOCHECK) { + asprintf(error, + "LOWEST_TSN must be specified for inbound packets"); + return NULL; + } + break; + case SCTP_CWR_CHUNK_TYPE: + if (item->flags & FLAG_CWR_CHUNK_LOWEST_TSN_NOCHECK) { + asprintf(error, + "LOWEST_TSN must be specified for inbound packets"); + return NULL; + } + break; + case SCTP_SHUTDOWN_COMPLETE_CHUNK_TYPE: + break; + default: + asprintf(error, "Unknown chunk type 0x%02x", item->chunk->type); + return NULL; + } + } + } + + /* Allocate and zero out a packet object of the desired size */ + packet = packet_new(overbook ? MAX_SCTP_DATAGRAM_BYTES : ip_bytes); + memset(packet->buffer, 0, overbook ? MAX_SCTP_DATAGRAM_BYTES : ip_bytes); + + packet->direction = direction; + packet->flags = 0; + packet->ecn = ecn; + + /* Set IP header fields */ + set_packet_ip_header(packet, address_family, ip_bytes, ecn, + IPPROTO_SCTP); + + sctp_header = packet_append_header(packet, HEADER_SCTP, sctp_header_bytes); + sctp_header->total_bytes = sctp_header_bytes + sctp_chunk_bytes; + + /* Find the start of the SCTP common header of the packet */ + packet->sctp = (struct sctp_common_header *) (ip_start(packet) + ip_header_bytes); + u8 *sctp_chunk_start = (u8 *) (packet->sctp + 1); + + /* Set SCTP header fields */ + packet->sctp->src_port = htons(0); + packet->sctp->dst_port = htons(0); + packet->sctp->v_tag = htonl(0); + packet->sctp->crc32c = htonl(0); + + for (item = list->first; item != NULL; item = item->next) { + DEBUGP("Copy in a chunk of length %d\n", item->length); + memcpy(sctp_chunk_start, item->chunk, item->length); + DEBUGP("Old location: %p\n", (void *)item->chunk); + free(item->chunk); + item->chunk = (struct sctp_chunk *)sctp_chunk_start; + DEBUGP("New location: %p\n", (void *)item->chunk); + sctp_chunk_start += item->length; + } + free(packet->chunk_list); + packet->chunk_list = list; + packet->ip_bytes = ip_bytes; + return packet; +} diff --git a/gtests/net/packetdrill/sctp_packet.h b/gtests/net/packetdrill/sctp_packet.h new file mode 100644 index 00000000..b2e90e85 --- /dev/null +++ b/gtests/net/packetdrill/sctp_packet.h @@ -0,0 +1,141 @@ +/* + * Copyright 2015 Michael Tuexen + * + * 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: tuexen@fh-muenster.de (Michael Tuexen) + * + * Interface for module for formatting SCTP packets. + */ + +#ifndef __SCTP_PACKET_H__ +#define __SCTP_PACKET_H__ + +#include "types.h" +#include "packet.h" +#include "sctp.h" + +struct sctp_chunk_list_item { + struct sctp_chunk_list_item *next; + struct sctp_chunk *chunk; + /* total length in bytes */ + u32 length; + /* metadata */ + u32 flags; +}; + +struct sctp_chunk_list { + struct sctp_chunk_list_item *first; + struct sctp_chunk_list_item *last; + /* length in bytes */ + u32 length; +}; + +#define FLAG_CHUNK_TYPE_NOCHECK 0x00000001 +#define FLAG_CHUNK_FLAGS_NOCHECK 0x00000002 +#define FLAG_CHUNK_LENGTH_NOCHECK 0x00000004 +#define FLAG_CHUNK_VALUE_NOCHECK 0x00000008 + +struct sctp_chunk_list_item * +sctp_chunk_list_item_new(struct sctp_chunk *chunk, u32 length, u32 flags); + +#define FLAG_DATA_CHUNK_TSN_NOCHECK 0x00000100 +#define FLAG_DATA_CHUNK_SID_NOCHECK 0x00000200 +#define FLAG_DATA_CHUNK_SSN_NOCHECK 0x00000400 +#define FLAG_DATA_CHUNK_PPID_NOCHECK 0x00000800 + +struct sctp_chunk_list_item * +sctp_data_chunk_new(s64 tsn, s64 sid, s64 ssn, s64 ppid); + +#define FLAG_INIT_CHUNK_A_RWND_NOCHECK 0x00000100 +#define FLAG_INIT_CHUNK_OS_NOCHECK 0x00000200 +#define FLAG_INIT_CHUNK_IS_NOCHECK 0x00000400 + +struct sctp_chunk_list_item * +sctp_init_chunk_new(s64 tag, s64 a_rwnd, s64 os, s64 is, s64 tsn); + +#define FLAG_INIT_ACK_CHUNK_A_RWND_NOCHECK 0x00000100 +#define FLAG_INIT_ACK_CHUNK_OS_NOCHECK 0x00000200 +#define FLAG_INIT_ACK_CHUNK_IS_NOCHECK 0x00000400 + +struct sctp_chunk_list_item * +sctp_init_ack_chunk_new(s64 tag, s64 a_rwnd, s64 os, s64 is, s64 tsn); + +#define FLAG_SACK_CHUNK_CUM_TSN_NOCHECK 0x00000100 +#define FLAG_SACK_CHUNK_A_RWND_NOCHECK 0x00000200 +#define FLAG_SACK_CHUNK_GAP_BLOCKS_NOCHECK 0x00000400 +#define FLAG_SACK_CHUNK_DUP_TSNS_NOCHECK 0x00000800 + +struct sctp_chunk_list_item * +sctp_sack_chunk_new(s64 cum_tsn, s64 a_rwnd); + +struct sctp_chunk_list_item * +sctp_heartbeat_chunk_new(u8 flags); + +struct sctp_chunk_list_item * +sctp_heartbeat_ack_chunk_new(u8 flags); + +struct sctp_chunk_list_item * +sctp_abort_chunk_new(u8 flags); + +#define FLAG_SHUTDOWN_CHUNK_CUM_TSN_NOCHECK 0x00000100 + +struct sctp_chunk_list_item * +sctp_shutdown_chunk_new(s64 cum_tsn); + +struct sctp_chunk_list_item * +sctp_shutdown_ack_chunk_new(u8 flags); + +struct sctp_chunk_list_item * +sctp_error_chunk_new(u8 flags); + +struct sctp_chunk_list_item * +sctp_cookie_echo_chunk_new(u8 flags); + +struct sctp_chunk_list_item * +sctp_cookie_ack_chunk_new(u8 flags); + +#define FLAG_ECNE_CHUNK_LOWEST_TSN_NOCHECK 0x00000100 + +struct sctp_chunk_list_item * +sctp_ecne_chunk_new(s64 lowest_tsn); + +#define FLAG_CWR_CHUNK_LOWEST_TSN_NOCHECK 0x00000100 + +struct sctp_chunk_list_item * +sctp_cwr_chunk_new(s64 lowest_tsn); + +struct sctp_chunk_list_item * +sctp_shutdown_complete_chunk_new(u8 flags); + +struct sctp_chunk_list *sctp_chunk_list_new(void); + +void sctp_chunk_list_append(struct sctp_chunk_list *list, + struct sctp_chunk_list_item *item); + +void sctp_chunk_list_free(struct sctp_chunk_list *list); + +/* Create and initialize a new struct packet containing a SCTP packet. + * On success, returns a newly-allocated packet. On failure, returns NULL + * and fills in *error with an error message. + */ +extern struct packet *new_sctp_packet(int address_family, + enum direction_t direction, + enum ip_ecn_t ecn, + struct sctp_chunk_list *chunk_list, + char **error); +#endif /* __SCTP_PACKET_H__ */ diff --git a/gtests/net/packetdrill/socket.h b/gtests/net/packetdrill/socket.h index 41b23c88..f38515f8 100644 --- a/gtests/net/packetdrill/socket.h +++ b/gtests/net/packetdrill/socket.h @@ -43,9 +43,13 @@ enum socket_state_t { SOCKET_PASSIVE_PACKET_RECEIVED, /* after receiving first packet */ SOCKET_PASSIVE_SYNACK_SENT, /* after sending SYNACK */ SOCKET_PASSIVE_SYNACK_ACKED, /* after server's SYN is ACKed */ + SOCKET_PASSIVE_INIT_ACK_SENT, + SOCKET_PASSIVE_COOKIE_ECHO_RECEIVED, SOCKET_ACTIVE_CONNECTING, /* after connect() call */ SOCKET_ACTIVE_SYN_SENT, /* after sending client's SYN */ SOCKET_ACTIVE_SYN_ACKED, /* after client's SYN is ACKed */ + SOCKET_ACTIVE_INIT_SENT, /* after sending client's INIT */ + SOCKET_ACTIVE_INIT_ACK_RECEIVED /* after receiving server's INIT-ACK */ }; /* A TCP/UDP/IP address for an endpoint. */ @@ -64,9 +68,17 @@ struct tuple { struct socket_state { int fd; /* file descriptor for this socket */ struct endpoint local; /* local endpoint address */ + /* TCP specific */ u32 local_isn; /* initial TCP sequence (host order) */ + /* SCTP specific (in host byte order ) */ + u32 local_initial_tsn; /* initial TSN */ + u32 local_initiate_tag; /* v-tag expected from the peer */ struct endpoint remote; /* remote endpoint address */ + /* TCP specific */ u32 remote_isn; /* initial TCP sequence (host order) */ + /* SCTP specific (in host byte order) */ + u32 remote_initial_tsn; /* initial TSN */ + u32 remote_initiate_tag; /* v-tag expected by the peer */ }; /* The runtime state for a socket */ @@ -104,6 +116,9 @@ struct socket { struct tcp last_injected_tcp_header; u32 last_injected_tcp_payload_len; + struct sctp_cookie_echo_chunk *prepared_state_cookie; + u16 prepared_state_cookie_length; + struct socket *next; /* next in linked list of sockets */ }; diff --git a/gtests/net/packetdrill/tests/bsd/sctp/sctp.pkt b/gtests/net/packetdrill/tests/bsd/sctp/sctp.pkt new file mode 100644 index 00000000..a4a590a3 --- /dev/null +++ b/gtests/net/packetdrill/tests/bsd/sctp/sctp.pkt @@ -0,0 +1,29 @@ + 0.0 `sysctl -w net.inet.sctp.ecn_enable=0` ++0.0 `sysctl -w net.inet.sctp.pr_enable=0` ++0.0 `sysctl -w net.inet.sctp.asconf_enable=0` ++0.0 `sysctl -w net.inet.sctp.auth_enable=0` ++0.0 `sysctl -w net.inet.sctp.reconfig_enable=0` ++0.0 `sysctl -w net.inet.sctp.nrsack_enable=0` ++0.0 `sysctl -w net.inet.sctp.pktdrop_enable=0` ++0.0 socket(..., SOCK_STREAM, IPPROTO_SCTP) = 3 ++0.0 fcntl(3, F_GETFL) = 0x2 (flags O_RDWR) ++0.0 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 ++0.1 connect(3, ..., ...) = -1 EINPROGRESS (Operation now in progress) ++0.0 > sctp: INIT[tag=1 tsn=0] ++0.0 < sctp: INIT_ACK[tag=2 a_rwnd=1500 os=1 is =1 tsn=3] // faked ++0.0 > sctp: COOKIE_ECHO[] // syntax not clear ++0.0 < sctp: COOKIE_ACK[] ++0.0 getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 ++1.0 write(3, ..., 1000) = 1000 ++0.0 > sctp: DATA[tsn=0 sid=0 ssn=0 ppid=0] ++0.0 < sctp: SACK[tsn=0 a_rwnd=1500] ++1.0 < sctp: DATA[tsn=3 sid=0 ssn=0 ppid=0] // How to handle ++0.0 > sctp: SACK[tsn=3] ++0.0 read(3, ..., 2000) = 1000 ++1.0 < sctp: DATA[tsn=4 sid=0 ssn=1 ppid=0] ++0.0 read(3, ..., 2000) = 1000 ++0.2 > sctp: SACK[tsn=4] ++0.0 close(3) = 0 ++0.0 > sctp: SHUTDOWN[tsn=4] ++0.0 < sctp: SHUTDOWN_ACK[] ++0.0 > sctp: SHUTDOWN_COMPLETE[] diff --git a/gtests/net/packetdrill/tests/bsd/sctp/sctp_active.pkt b/gtests/net/packetdrill/tests/bsd/sctp/sctp_active.pkt new file mode 100644 index 00000000..1cc9a618 --- /dev/null +++ b/gtests/net/packetdrill/tests/bsd/sctp/sctp_active.pkt @@ -0,0 +1,27 @@ ++0.0 socket(..., SOCK_STREAM, IPPROTO_SCTP) = 3 ++0.0 fcntl(3, F_GETFL) = 0x2 (flags O_RDWR) ++0.0 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 +// Check the handshake with en empty(!) cookie ++0.1 connect(3, ..., ...) = -1 EINPROGRESS (Operation now in progress) ++0.0 > sctp: INIT[tag=1 tsn=0] ++0.1 < sctp: INIT_ACK[tag=2 a_rwnd=1500 os=1 is =1 tsn=3] // faked cookie ++0.0 > sctp: COOKIE_ECHO[] // syntax not clear ++0.1 < sctp: COOKIE_ACK[] ++0.0 getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 +// Send some data. ++1.0 write(3, ..., 1000) = 1000 ++0.0 > sctp: DATA[tsn=0 sid=0 ssn=0 ppid=0] ++0.1 < sctp: SACK[tsn=0 a_rwnd=1500] +// Receive some data ++1.0 < sctp: DATA[tsn=3 sid=0 ssn=0 ppid=0] // How to handle ++0.0 read(3, ..., 2000) = 1000 ++0.0 > sctp: SACK[tsn=3] +// Receive more data, observe delayed SACK ++1.0 < sctp: DATA[tsn=4 sid=0 ssn=1 ppid=0] ++0.0 read(3, ..., 2000) = 1000 ++0.2 > sctp: SACK[tsn=4] +// Tear down the association ++0.0 close(3) = 0 ++0.0 > sctp: SHUTDOWN[tsn=4] ++0.1 < sctp: SHUTDOWN_ACK[] ++0.0 > sctp: SHUTDOWN_COMPLETE[] diff --git a/gtests/net/packetdrill/tests/bsd/sctp/sctp_handle_init.pkt b/gtests/net/packetdrill/tests/bsd/sctp/sctp_handle_init.pkt new file mode 100644 index 00000000..eafcaf51 --- /dev/null +++ b/gtests/net/packetdrill/tests/bsd/sctp/sctp_handle_init.pkt @@ -0,0 +1,7 @@ + 0.0 socket(..., SOCK_STREAM, IPPROTO_SCTP) = 3 ++0.0 bind(3, ..., ...) = 0 ++0.0 listen(3, 1) = 0 +// The initiate tag in the INIT chunk MUST NOT be zero. ++0.1 < sctp: INIT[tag=0, a_rwnd=1500, is=1, os=1, tsn=0] ++0.0 > sctp: ABORT[INVALID_MANDATORY_PARAMETER[]] ++0.0 close(3) = 0 diff --git a/gtests/net/packetdrill/tests/bsd/sctp/sctp_init_rtx.pkt b/gtests/net/packetdrill/tests/bsd/sctp/sctp_init_rtx.pkt new file mode 100644 index 00000000..2254235e --- /dev/null +++ b/gtests/net/packetdrill/tests/bsd/sctp/sctp_init_rtx.pkt @@ -0,0 +1,26 @@ + 0.0 `sysctl -w net.inet.sctp.ecn_enable=0` ++0.0 `sysctl -w net.inet.sctp.pr_enable=0` ++0.0 `sysctl -w net.inet.sctp.asconf_enable=0` ++0.0 `sysctl -w net.inet.sctp.auth_enable=0` ++0.0 `sysctl -w net.inet.sctp.reconfig_enable=0` ++0.0 `sysctl -w net.inet.sctp.nrsack_enable=0` ++0.0 `sysctl -w net.inet.sctp.pktdrop_enable=0` ++0.0 socket(..., SOCK_STREAM, IPPROTO_SCTP) = 3 ++0.0 fcntl(3, F_GETFL) = 0x2 (flags O_RDWR) ++0.0 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 ++0.0 setsockopt(3, IPPROTO_SCTP, SCTP_RTOINFO, {srto_initial=100, srto_max=800, srto_min=100}, 16) = 0 ++0.0 connect(3, ..., ...) = -1 EINPROGRESS (Operation now in progress) +// Check the number and the timing of the restransmissions ++0.0 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...] ++0.1 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...] ++0.2 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...] ++0.4 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...] ++0.8 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...] ++0.8 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...] ++0.8 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...] ++0.8 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...] ++0.8 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...] +// Look as if we don't send a final ABORT. Shouldn't we? +// +1.0 > sctp: ABORT[] ++1.0 getsockopt(3, SOL_SOCKET, SO_ERROR, [ETIMEDOUT], [4]) = 0 ++0.0 close(3) = 0 diff --git a/gtests/net/packetdrill/tests/bsd/sctp/sctp_passive.pkt b/gtests/net/packetdrill/tests/bsd/sctp/sctp_passive.pkt new file mode 100644 index 00000000..563e0756 --- /dev/null +++ b/gtests/net/packetdrill/tests/bsd/sctp/sctp_passive.pkt @@ -0,0 +1,27 @@ ++0.0 socket(..., SOCK_STREAM, IPPROTO_SCTP) = 3 +// Check the handshake with en empty(!) cookie ++0.0 bind(3, ..., ...) = 0 ++0.0 listen(3, 1) = 0 ++0.0 < sctp: INIT[tag=1 a_rwnd=1500 os=1 is=1 tsn=0] ++0.0 > sctp: INIT_ACK[tag=2 tsn=10] // faked cookie ++0.1 < sctp: COOKIE_ECHO[] // syntax not clear ++0.0 > sctp: COOKIE_ACK[] ++0.0 accept(3, ..., ...) = 4 +// Send some data. ++1.0 write(4, ..., 1000) = 1000 ++0.0 > sctp: DATA[tsn=10 sid=0 ssn=0 ppid=0] ++0.1 < sctp: SACK[tsn=10 a_rwnd=1500] +// Receive some data ++1.0 < sctp: DATA[tsn=0 sid=0 ssn=0 ppid=0] // How to handle payload? ++0.0 read(4, ..., 2000) = 1000 ++0.0 > sctp: SACK[tsn=0] +// Receive more data, observe delayed SACKi ++1.0 < sctp: DATA[tsn=1 sid=0 ssn=1 ppid=0] ++0.0 read(4, ..., 2000) = 1000 ++0.2 > sctp: SACK[tsn=1] +// Tear down the association ++1.0 < sctp: SHUTDOWN[tsn=0] ++0.0 > sctp: SHUTDOWN_ACK[] ++0.0 < sctp: SHUTDOWN_COMPLETE[] ++0.0 close(4) = 0 ++0.0 close(3) = 0 -- GitLab