diff --git a/include/packet_defs.h b/include/packet_defs.h index bc89383b..48d3d0c3 100644 --- a/include/packet_defs.h +++ b/include/packet_defs.h @@ -169,6 +169,7 @@ struct tcp_hdr { #define TCP_OPT_END_OF_OPTIONS 0 #define TCP_OPT_NO_OP 1 #define TCP_OPT_MSS 2 +#define TCP_OPT_WS 3 #define TCP_OPT_TIMESTAMP 8 struct tcp_mss_opt { uint8_t kind; @@ -176,6 +177,11 @@ struct tcp_mss_opt { beui16_t mss; } __attribute__((packed)); +struct tcp_ws_opt { + uint8_t kind; + uint8_t length; + uint8_t scale; +} __attribute__((packed)); struct tcp_timestamp_opt { uint8_t kind; diff --git a/include/tas_memif.h b/include/tas_memif.h index e5929cb8..7f19305e 100644 --- a/include/tas_memif.h +++ b/include/tas_memif.h @@ -262,13 +262,17 @@ struct flextcp_pl_flowst { /** Sequence number of queue pointer bumps */ uint16_t bump_seq; - // 56 + /** TX Window scale factor */ + uint8_t tx_window_scale; + /** RX Window scale factor */ + uint8_t rx_window_scale; + // 58 /********************************************************/ /* read-write fields */ - /** spin lock */ - volatile uint32_t lock; + /** Duplicate ack count */ + uint16_t rx_dupack_cnt; /** Bytes available for received segments at next position */ uint32_t rx_avail; @@ -279,8 +283,9 @@ struct flextcp_pl_flowst { uint32_t rx_next_seq; /** Bytes available in remote end for received segments */ uint32_t rx_remote_avail; - /** Duplicate ack count */ - uint32_t rx_dupack_cnt; + + /** spin lock */ + volatile uint32_t lock; #ifdef FLEXNIC_PL_OOO_RECV /* Start of interval of out-of-order received data */ diff --git a/tas/config.c b/tas/config.c index 50ed301a..ceb97183 100644 --- a/tas/config.c +++ b/tas/config.c @@ -48,6 +48,7 @@ enum cfg_params { CP_TCP_TXBUF_LEN, CP_TCP_HANDSHAKE_TO, CP_TCP_HANDSHAKE_RETRIES, + CP_TCP_WINDOW_SCALE, CP_CC, CP_CC_CONTROL_GRANULARITY, CP_CC_CONTROL_INTERVAL, @@ -123,6 +124,9 @@ static struct option opts[] = { { .name = "tcp-handshake-retries", .has_arg = required_argument, .val = CP_TCP_HANDSHAKE_RETRIES }, + { .name = "tcp-window-scale", + .has_arg = required_argument, + .val = CP_TCP_WINDOW_SCALE }, { .name = "cc", .has_arg = required_argument, .val = CP_CC }, @@ -327,6 +331,12 @@ int config_parse(struct configuration *c, int argc, char *argv[]) goto failed; } break; + case CP_TCP_WINDOW_SCALE: + if (parse_int8(optarg, &c->tcp_window_scale) != 0) { + fprintf(stderr, "tcp window scale parsing failed\n"); + goto failed; + } + break; case CP_CC: if (!strcmp(optarg, "dctcp-win")) { c->cc_algorithm = CONFIG_CC_DCTCP_WIN; @@ -565,6 +575,7 @@ static int config_defaults(struct configuration *c, char *progname) c->tcp_txbuf_len = 8192; c->tcp_handshake_to = 10000; c->tcp_handshake_retries = 10; + c->tcp_window_scale = 0; c->cc_algorithm = CONFIG_CC_DCTCP_RATE; c->cc_control_granularity = 50; c->cc_control_interval = 2; @@ -635,6 +646,8 @@ static void print_usage(struct configuration *c, char *progname) "[default: %"PRIu32"]\n" " --tcp-handshake-retries=RETRIES Handshake retries " "[default: %"PRIu32"]\n" + " --tcp-window-scale=SCALE Window scale factor " + "[default: %"PRIu8"]\n" "\n" "Congestion control parameters:\n" " --cc=ALGORITHM Congestion-control algorithm " @@ -711,7 +724,7 @@ static void print_usage(struct configuration *c, char *progname) progname, c->shm_len, c->nic_rx_len, c->nic_tx_len, c->app_kin_len, c->app_kout_len, c->tcp_rtt_init, c->tcp_link_bw, c->tcp_rxbuf_len, c->tcp_txbuf_len, - c->tcp_handshake_to, c->tcp_handshake_retries, + c->tcp_handshake_to, c->tcp_handshake_retries, c->tcp_window_scale, c->cc_control_granularity, c->cc_control_interval, c->cc_rexmit_ints, (double) c->cc_dctcp_weight / UINT32_MAX, c->cc_dctcp_min, c->cc_const_rate, c->cc_timely_tlow, c->cc_timely_thigh, diff --git a/tas/fast/fast_flows.c b/tas/fast/fast_flows.c index 623efafb..e18abcf3 100644 --- a/tas/fast/fast_flows.c +++ b/tas/fast/fast_flows.c @@ -66,10 +66,11 @@ static void flow_rx_seq_write(struct flextcp_pl_flowst *fs, uint32_t seq, static void flow_tx_segment(struct dataplane_context *ctx, struct network_buf_handle *nbh, struct flextcp_pl_flowst *fs, uint32_t seq, uint32_t ack, uint32_t rxwnd, uint16_t payload, - uint32_t payload_pos, uint32_t ts_echo, uint32_t ts_my, uint8_t fin); + uint32_t payload_pos, uint32_t ts_echo, uint32_t ts_my, + uint8_t window_scale, uint8_t fin); static void flow_tx_ack(struct dataplane_context *ctx, uint32_t seq, uint32_t ack, uint32_t rxwnd, uint32_t echo_ts, uint32_t my_ts, - struct network_buf_handle *nbh, struct tcp_timestamp_opt *ts_opt); + uint8_t window_scale, struct network_buf_handle *nbh, struct tcp_timestamp_opt *ts_opt); static void flow_reset_retransmit(struct flextcp_pl_flowst *fs); static inline void tcp_checksums(struct network_buf_handle *nbh, @@ -195,7 +196,7 @@ int fast_flows_qman(struct dataplane_context *ctx, uint32_t queue, /* send out segment */ flow_tx_segment(ctx, nbh, fs, tx_seq, ack, rx_wnd, len, tx_pos, - fs->tx_next_ts, ts, fin); + fs->tx_next_ts, ts, fs->rx_window_scale, fin); unlock: fs_unlock(fs); return ret; @@ -499,7 +500,7 @@ int fast_flows_packet(struct dataplane_context *ctx, } } - fs->rx_remote_avail = f_beui16(p->tcp.wnd); + fs->rx_remote_avail = f_beui16(p->tcp.wnd) << fs->tx_window_scale; /* make sure we don't receive anymore payload after FIN */ if ((fs->rx_base_sp & FLEXNIC_PL_FLOWST_RXFIN) == FLEXNIC_PL_FLOWST_RXFIN && @@ -631,7 +632,7 @@ int fast_flows_packet(struct dataplane_context *ctx, /* if we need to send an ack, also send packet to TX pipeline to do so */ if (trigger_ack) { flow_tx_ack(ctx, fs->tx_next_seq, fs->rx_next_seq, fs->rx_avail, - fs->tx_next_ts, ts, nbh, opts->ts); + fs->tx_next_ts, ts, fs->rx_window_scale, nbh, opts->ts); } fs_unlock(fs); @@ -758,7 +759,7 @@ int fast_flows_bump(struct dataplane_context *ctx, uint32_t flow_id, * we're not sending anyways. */ if (new_avail == 0 && rx_avail_prev == 0 && fs->rx_avail != 0) { flow_tx_segment(ctx, nbh, fs, fs->tx_next_seq, fs->rx_next_seq, - fs->rx_avail, 0, 0, fs->tx_next_ts, ts, 0); + fs->rx_avail, 0, 0, fs->tx_next_ts, ts, fs->rx_window_scale, 0); ret = 0; } @@ -877,7 +878,8 @@ static void flow_rx_seq_write(struct flextcp_pl_flowst *fs, uint32_t seq, static void flow_tx_segment(struct dataplane_context *ctx, struct network_buf_handle *nbh, struct flextcp_pl_flowst *fs, uint32_t seq, uint32_t ack, uint32_t rxwnd, uint16_t payload, - uint32_t payload_pos, uint32_t ts_echo, uint32_t ts_my, uint8_t fin) + uint32_t payload_pos, uint32_t ts_echo, uint32_t ts_my, + uint8_t window_scale, uint8_t fin) { uint16_t hdrs_len, optlen, fin_fl; struct pkt_tcp *p = network_buf_buf(nbh); @@ -915,7 +917,7 @@ static void flow_tx_segment(struct dataplane_context *ctx, p->tcp.seqno = t_beui32(seq); p->tcp.ackno = t_beui32(ack); TCPH_HDRLEN_FLAGS_SET(&p->tcp, 5 + optlen / 4, TCP_PSH | TCP_ACK | fin_fl); - p->tcp.wnd = t_beui16(MIN(0xFFFF, rxwnd)); + p->tcp.wnd = t_beui16(MIN(0xFFFF, rxwnd >> window_scale)); p->tcp.chksum = 0; p->tcp.urgp = t_beui16(0); @@ -955,7 +957,7 @@ static void flow_tx_segment(struct dataplane_context *ctx, } static void flow_tx_ack(struct dataplane_context *ctx, uint32_t seq, - uint32_t ack, uint32_t rxwnd, uint32_t echots, uint32_t myts, + uint32_t ack, uint32_t rxwnd, uint32_t echots, uint32_t myts, uint8_t window_scale, struct network_buf_handle *nbh, struct tcp_timestamp_opt *ts_opt) { struct pkt_tcp *p; @@ -998,7 +1000,7 @@ static void flow_tx_ack(struct dataplane_context *ctx, uint32_t seq, p->tcp.seqno = t_beui32(seq); p->tcp.ackno = t_beui32(ack); TCPH_HDRLEN_FLAGS_SET(&p->tcp, TCPH_HDRLEN(&p->tcp), TCP_ACK | ecn_flags); - p->tcp.wnd = t_beui16(MIN(0xFFFF, rxwnd)); + p->tcp.wnd = t_beui16(MIN(0xFFFF, rxwnd >> window_scale)); p->tcp.urgp = t_beui16(0); /* fill in timestamp option */ diff --git a/tas/include/config.h b/tas/include/config.h index d85d5014..623e9594 100644 --- a/tas/include/config.h +++ b/tas/include/config.h @@ -63,6 +63,8 @@ struct configuration { uint32_t tcp_handshake_to; /** # of retries for dropped handshake packets */ uint32_t tcp_handshake_retries; + /** Window scale configuration */ + uint8_t tcp_window_scale; /** IP address for this host */ uint32_t ip; /** IP prefix length for this host */ diff --git a/tas/slow/internal.h b/tas/slow/internal.h index 9652189e..37c70485 100644 --- a/tas/slow/internal.h +++ b/tas/slow/internal.h @@ -117,24 +117,26 @@ enum nicif_connection_flags { /** * Register flow (must be called from poll thread). * - * @param db Doorbell ID - * @param mac_remote MAC address of the remote host - * @param ip_local Local IP address - * @param port_local Local port number - * @param ip_remote Remote IP address - * @param port_remote Remote port number - * @param rx_base Base address of circular receive buffer - * @param rx_len Length of circular receive buffer - * @param tx_base Base address of circular transmit buffer - * @param tx_len Length of circular transmit buffer - * @param remote_seq Next sequence number expected from remote host - * @param local_seq Next sequence number for transmission - * @param app_opaque Opaque value to pass in notificaitions - * @param flags See #nicif_connection_flags. - * @param rate Congestion rate to set [Kbps] - * @param fn_core FlexNIC emulator core for the connection - * @param flow_group Flow group - * @param pf_id Pointer to location where flow id should be stored + * @param db Doorbell ID + * @param mac_remote MAC address of the remote host + * @param ip_local Local IP address + * @param port_local Local port number + * @param ip_remote Remote IP address + * @param port_remote Remote port number + * @param rx_base Base address of circular receive buffer + * @param rx_len Length of circular receive buffer + * @param tx_base Base address of circular transmit buffer + * @param tx_len Length of circular transmit buffer + * @param remote_seq Next sequence number expected from remote host + * @param local_seq Next sequence number for transmission + * @param app_opaque Opaque value to pass in notificaitions + * @param flags See #nicif_connection_flags. + * @param rate Congestion rate to set [Kbps] + * @param rx_window_scale Window scale factor for RX window + * @param tx_window_scale Window scale factor for TX window + * @param fn_core FlexNIC emulator core for the connection + * @param flow_group Flow group + * @param pf_id Pointer to location where flow id should be stored * * @return 0 on success, <0 else */ @@ -142,7 +144,8 @@ int nicif_connection_add(uint32_t db, uint64_t mac_remote, uint32_t ip_local, uint16_t port_local, uint32_t ip_remote, uint16_t port_remote, uint64_t rx_base, uint32_t rx_len, uint64_t tx_base, uint32_t tx_len, uint32_t remote_seq, uint32_t local_seq, uint64_t app_opaque, - uint32_t flags, uint32_t rate, uint32_t fn_core, uint16_t flow_group, + uint32_t flags, uint32_t rate, uint8_t rx_window_scale, uint8_t tx_window_scale, + uint32_t fn_core, uint16_t flow_group, uint32_t *pf_id); /** @@ -476,6 +479,10 @@ struct connection { uint32_t local_seq; /** Timestamp received with SYN/SYN-ACK packet */ uint32_t syn_ts; + /** TX Window scale factor */ + uint8_t tx_window_scale; + /** RX Window scale factor */ + uint8_t rx_window_scale; /**@}*/ /** diff --git a/tas/slow/nicif.c b/tas/slow/nicif.c index 4ae95642..01ceca87 100644 --- a/tas/slow/nicif.c +++ b/tas/slow/nicif.c @@ -176,7 +176,8 @@ int nicif_connection_add(uint32_t db, uint64_t mac_remote, uint32_t ip_local, uint16_t port_local, uint32_t ip_remote, uint16_t port_remote, uint64_t rx_base, uint32_t rx_len, uint64_t tx_base, uint32_t tx_len, uint32_t remote_seq, uint32_t local_seq, uint64_t app_opaque, - uint32_t flags, uint32_t rate, uint32_t fn_core, uint16_t flow_group, + uint32_t flags, uint32_t rate, uint8_t rx_window_scale, uint8_t tx_window_scale, + uint32_t fn_core, uint16_t flow_group, uint32_t *pf_id) { struct flextcp_pl_flowst *fs; @@ -222,6 +223,8 @@ int nicif_connection_add(uint32_t db, uint64_t mac_remote, uint32_t ip_local, fs->flow_group = flow_group; fs->lock = 0; fs->bump_seq = 0; + fs->tx_window_scale = tx_window_scale; + fs->rx_window_scale = rx_window_scale; fs->rx_avail = rx_len; fs->rx_next_pos = 0; diff --git a/tas/slow/tcp.c b/tas/slow/tcp.c index 9291cecd..9b4b9856 100644 --- a/tas/slow/tcp.c +++ b/tas/slow/tcp.c @@ -70,6 +70,7 @@ struct backlog_slot { struct tcp_opts { struct tcp_mss_opt *mss; + struct tcp_ws_opt *ws; struct tcp_timestamp_opt *ts; }; @@ -96,7 +97,7 @@ static void listener_accept(struct listener *l); static inline uint16_t port_alloc(void); static inline int send_control(const struct connection *conn, uint16_t flags, - int ts_opt, uint32_t ts_echo, uint16_t mss_opt); + int ts_opt, uint32_t ts_echo, uint16_t mss_opt, uint8_t ws_opt); static inline int send_reset(const struct pkt_tcp *p, const struct tcp_opts *opts); static inline int parse_options(const struct pkt_tcp *p, uint16_t len, @@ -411,7 +412,7 @@ int tcp_close(struct connection *conn) conn->local_seq = tx_seq; if (!tx_c || !rx_c) { - send_control(conn, TCP_RST, 0, 0, 0); + send_control(conn, TCP_RST, 0, 0, 0, 0); } cc_conn_remove(conn); @@ -464,7 +465,7 @@ void tcp_timeout(struct timeout *to, enum timeout_type type) conn_timeout_arm(c, TO_TCP_HANDSHAKE); /* re-send SYN packet */ - send_control(c, TCP_SYN | TCP_ECE | TCP_CWR, 1, 0, TCP_MSS); + send_control(c, TCP_SYN | TCP_ECE | TCP_CWR, 1, 0, TCP_MSS, c->rx_window_scale); } static void conn_packet(struct connection *c, const struct pkt_tcp *p, @@ -499,7 +500,7 @@ static void conn_packet(struct connection *c, const struct pkt_tcp *p, } send_control(c, TCP_SYN | TCP_ACK | ecn_flags, 1, - f_beui32(opts->ts->ts_val), TCP_MSS); + f_beui32(opts->ts->ts_val), TCP_MSS, c->rx_window_scale); } else if (c->status == CONN_OPEN && (TCPH_FLAGS(&p->tcp) & TCP_SYN) == TCP_SYN) { @@ -509,7 +510,7 @@ static void conn_packet(struct connection *c, const struct pkt_tcp *p, { /* silently ignore a FIN for an already closed connection: TODO figure out * why necessary*/ - send_control(c, TCP_ACK, 1, 0, 0); + send_control(c, TCP_ACK, 1, 0, 0, 0); } else { fprintf(stderr, "tcp_packet: unexpected connection state %u\n", c->status); } @@ -527,7 +528,7 @@ static int conn_arp_done(struct connection *conn) conn_timeout_arm(conn, TO_TCP_HANDSHAKE); /* send SYN */ - send_control(conn, TCP_SYN | TCP_ECE | TCP_CWR, 1, 0, TCP_MSS); + send_control(conn, TCP_SYN | TCP_ECE | TCP_CWR, 1, 0, TCP_MSS, conn->rx_window_scale); CONN_DEBUG0(conn, "SYN SENT\n"); return 0; @@ -557,6 +558,11 @@ static int conn_syn_sent_packet(struct connection *c, const struct pkt_tcp *p, c->local_seq = f_beui32(p->tcp.ackno); c->syn_ts = f_beui32(opts->ts->ts_val); + if (opts->ws == NULL) + c->tx_window_scale = 0; + else + c->tx_window_scale = opts->ws->scale; + /* enable ECN if SYN-ACK confirms */ if (ecn_flags == TCP_ECE) { c->flags |= NICIF_CONN_ECN; @@ -572,6 +578,7 @@ static int conn_syn_sent_packet(struct connection *c, const struct pkt_tcp *p, c->remote_ip, c->remote_port, c->rx_buf - (uint8_t *) tas_shm, c->rx_len, c->tx_buf - (uint8_t *) tas_shm, c->tx_len, c->remote_seq, c->local_seq, c->opaque, c->flags, c->cc_rate, + c->rx_window_scale, c->tx_window_scale, c->fn_core, c->flow_group, &c->flow_id) != 0) { @@ -584,7 +591,7 @@ static int conn_syn_sent_packet(struct connection *c, const struct pkt_tcp *p, c->status = CONN_OPEN; /* send ACK */ - send_control(c, TCP_ACK, 1, c->syn_ts, 0); + send_control(c, TCP_ACK, 1, c->syn_ts, 0, 0); CONN_DEBUG0(c, "conn_syn_sent_packet: ACK sent\n"); @@ -604,7 +611,7 @@ static int conn_reg_synack(struct connection *c) } /* send ACK */ - send_control(c, TCP_SYN | TCP_ACK | ecn_flags, 1, c->syn_ts, TCP_MSS); + send_control(c, TCP_SYN | TCP_ACK | ecn_flags, 1, c->syn_ts, TCP_MSS, c->rx_window_scale); appif_accept_conn(c, 0); @@ -658,6 +665,7 @@ static inline struct connection *conn_alloc(void) conn->rx_len = config.tcp_rxbuf_len; conn->tx_buf = (uint8_t *) tas_shm + off_tx; conn->tx_len = config.tcp_txbuf_len; + conn->rx_window_scale = config.tcp_window_scale; conn->to_armed = 0; return conn; @@ -926,6 +934,10 @@ static void listener_accept(struct listener *l) c->remote_seq = f_beui32(p->tcp.seqno) + 1; c->local_seq = 1; /* TODO: generate random */ c->syn_ts = f_beui32(opts.ts->ts_val); + if (opts.ws == NULL) + c->tx_window_scale = 0; + else + c->tx_window_scale = opts.ws->scale; /* check if ECN is offered */ ecn_flags = TCPH_FLAGS(&p->tcp) & (TCP_ECE | TCP_CWR); @@ -945,6 +957,7 @@ static void listener_accept(struct listener *l) c->remote_ip, c->remote_port, c->rx_buf - (uint8_t *) tas_shm, c->rx_len, c->tx_buf - (uint8_t *) tas_shm, c->tx_len, c->remote_seq, c->local_seq + 1, c->opaque, c->flags, c->cc_rate, + c->rx_window_scale, c->tx_window_scale, c->fn_core, c->flow_group, &c->flow_id) != 0) { @@ -967,19 +980,22 @@ static void listener_accept(struct listener *l) static inline int send_control_raw(uint64_t remote_mac, uint32_t remote_ip, uint16_t remote_port, uint16_t local_port, uint32_t local_seq, uint32_t remote_seq, uint16_t flags, int ts_opt, uint32_t ts_echo, - uint16_t mss_opt) + uint16_t mss_opt, uint8_t ws_opt) { uint32_t new_tail; struct pkt_tcp *p; struct tcp_mss_opt *opt_mss; + struct tcp_ws_opt *opt_ws; struct tcp_timestamp_opt *opt_ts; uint8_t optlen; - uint16_t len, off_ts, off_mss; + uint16_t len, off_ts, off_ws, off_mss; /* calculate header length depending on options */ optlen = 0; off_mss = optlen; optlen += (mss_opt ? sizeof(*opt_mss) : 0); + off_ws = optlen; + optlen += (ws_opt ? sizeof(*opt_ws) : 0); off_ts = optlen; optlen += (ts_opt ? sizeof(*opt_ts) : 0); optlen = (optlen + 3) & ~3; @@ -1026,6 +1042,14 @@ static inline int send_control_raw(uint64_t remote_mac, uint32_t remote_ip, opt_mss->mss = t_beui16(mss_opt); } + /* if requested: add ws option */ + if (ws_opt) { + opt_ws = (struct tcp_ws_opt *) ((uint8_t *) (p + 1) + off_ws); + opt_ws->kind = TCP_OPT_WS; + opt_ws->length = sizeof(*opt_ws); + opt_ws->scale = ws_opt; + } + /* if requested: add timestamp option */ if (ts_opt) { opt_ts = (struct tcp_timestamp_opt *) ((uint8_t *) (p + 1) + off_ts); @@ -1046,11 +1070,11 @@ static inline int send_control_raw(uint64_t remote_mac, uint32_t remote_ip, } static inline int send_control(const struct connection *conn, uint16_t flags, - int ts_opt, uint32_t ts_echo, uint16_t mss_opt) + int ts_opt, uint32_t ts_echo, uint16_t mss_opt, uint8_t ws_opt) { return send_control_raw(conn->remote_mac, conn->remote_ip, conn->remote_port, conn->local_port, conn->local_seq, conn->remote_seq, flags, ts_opt, - ts_echo, mss_opt); + ts_echo, mss_opt, ws_opt); } static inline int send_reset(const struct pkt_tcp *p, @@ -1068,7 +1092,7 @@ static inline int send_reset(const struct pkt_tcp *p, memcpy(&remote_mac, &p->eth.src, ETH_ADDR_LEN); return send_control_raw(remote_mac, f_beui32(p->ip.src), f_beui16(p->tcp.src), f_beui16(p->tcp.dest), f_beui32(p->tcp.ackno), f_beui32(p->tcp.seqno) + 1, - TCP_RST | TCP_ACK, ts_opt, ts_val, 0); + TCP_RST | TCP_ACK, ts_opt, ts_val, 0, 0); } static inline int parse_options(const struct pkt_tcp *p, uint16_t len, @@ -1110,6 +1134,14 @@ static inline int parse_options(const struct pkt_tcp *p, uint16_t len, } opts->mss = (struct tcp_mss_opt *) (opt + off); + } else if (opt_kind == TCP_OPT_WS) { + if (opt_len != sizeof(struct tcp_ws_opt)) { + fprintf(stderr, "parse_options: window scale option size wrong (expect %zu " + "got %u)\n", sizeof(struct tcp_ws_opt), opt_len); + return -1; + } + + opts->ws = (struct tcp_ws_opt *) (opt + off); } else if (opt_kind == TCP_OPT_TIMESTAMP) { if (opt_len != sizeof(struct tcp_timestamp_opt)) { fprintf(stderr, "parse_options: opt_len=%u so=%zu\n", opt_len, sizeof(struct tcp_timestamp_opt));