Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net: tcp: Parse and store send MSS #4657

Merged
merged 3 commits into from
Nov 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions subsys/net/ip/net_context.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ static struct tcp_backlog_entry {
u32_t recv_max_ack;
u32_t send_seq;
u32_t send_ack;
u16_t send_mss;
struct k_delayed_work ack_timer;
} tcp_backlog[CONFIG_NET_TCP_BACKLOG_SIZE];

Expand Down Expand Up @@ -174,7 +175,8 @@ static int tcp_backlog_find(struct net_pkt *pkt, int *empty_slot)
return -EADDRNOTAVAIL;
}

static int tcp_backlog_syn(struct net_pkt *pkt, struct net_context *context)
static int tcp_backlog_syn(struct net_pkt *pkt, struct net_context *context,
u16_t send_mss)
{
int empty_slot = -1;
int ret;
Expand All @@ -198,6 +200,7 @@ static int tcp_backlog_syn(struct net_pkt *pkt, struct net_context *context)
tcp_backlog[empty_slot].recv_max_ack = context->tcp->recv_max_ack;
tcp_backlog[empty_slot].send_seq = context->tcp->send_seq;
tcp_backlog[empty_slot].send_ack = context->tcp->send_ack;
tcp_backlog[empty_slot].send_mss = send_mss;

k_delayed_work_init(&tcp_backlog[empty_slot].ack_timer,
backlog_ack_timeout);
Expand Down Expand Up @@ -232,6 +235,7 @@ static int tcp_backlog_ack(struct net_pkt *pkt, struct net_context *context)
context->tcp->recv_max_ack = tcp_backlog[r].recv_max_ack;
context->tcp->send_seq = tcp_backlog[r].send_seq + 1;
context->tcp->send_ack = tcp_backlog[r].send_ack;
context->tcp->send_mss = tcp_backlog[r].send_mss;

k_delayed_work_cancel(&tcp_backlog[r].ack_timer);
memset(&tcp_backlog[r], 0, sizeof(struct tcp_backlog_entry));
Expand Down Expand Up @@ -1073,10 +1077,9 @@ static int tcp_hdr_len(struct net_pkt *pkt)
{
struct net_tcp_hdr hdr, *tcp_hdr;

/* "Offset": 4-bit field in high nibble, units of dwords */
tcp_hdr = net_tcp_get_hdr(pkt, &hdr);
if (tcp_hdr) {
return 4 * (tcp_hdr->offset >> 4);
return NET_TCP_HDR_LEN(tcp_hdr);
}

return 0;
Expand Down Expand Up @@ -1610,9 +1613,22 @@ NET_CONN_CB(tcp_syn_rcvd)
*/
if (NET_TCP_FLAGS(tcp_hdr) == NET_TCP_SYN) {
int r;
int opt_totlen;
struct net_tcp_options tcp_opts = {
.mss = NET_TCP_DEFAULT_MSS,
};

net_tcp_print_recv_info("SYN", pkt, tcp_hdr->src_port);

opt_totlen = NET_TCP_HDR_LEN(tcp_hdr)
- sizeof(struct net_tcp_hdr);
/* We expect MSS option to be present (opt_totlen > 0),
* so call unconditionally.
*/
if (net_tcp_parse_opts(pkt, opt_totlen, &tcp_opts) < 0) {
return NET_DROP;
}

net_tcp_change_state(tcp, NET_TCP_SYN_RCVD);

/* Set TCP seq and ack which are then stored in the backlog */
Expand All @@ -1621,7 +1637,9 @@ NET_CONN_CB(tcp_syn_rcvd)
sys_get_be32(tcp_hdr->seq) + 1;
context->tcp->recv_max_ack = context->tcp->send_seq + 1;

r = tcp_backlog_syn(pkt, context);
/* Get MSS from TCP options here*/

r = tcp_backlog_syn(pkt, context, tcp_opts.mss);
if (r < 0) {
if (r == -EADDRINUSE) {
NET_DBG("TCP connection already exists");
Expand Down
80 changes: 78 additions & 2 deletions subsys/net/ip/tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ struct net_tcp *net_tcp_alloc(struct net_context *context)
tcp_context[i].send_seq = tcp_init_isn();
tcp_context[i].recv_max_ack = tcp_context[i].send_seq + 1u;
tcp_context[i].recv_wnd = min(NET_TCP_MAX_WIN, NET_TCP_BUF_MAX_LEN);
tcp_context[i].send_mss = NET_TCP_DEFAULT_MSS;

tcp_context[i].accept_cb = NULL;

Expand Down Expand Up @@ -599,7 +600,7 @@ u16_t net_tcp_get_recv_mss(const struct net_tcp *tcp)
static void net_tcp_set_syn_opt(struct net_tcp *tcp, u8_t *options,
u8_t *optionlen)
{
u16_t recv_mss;
u32_t recv_mss;

*optionlen = 0;

Expand All @@ -610,7 +611,8 @@ static void net_tcp_set_syn_opt(struct net_tcp *tcp, u8_t *options,
recv_mss = 0;
}

UNALIGNED_PUT(htonl((u32_t)recv_mss | NET_TCP_MSS_HEADER),
recv_mss |= (NET_TCP_MSS_OPT << 24) | (NET_TCP_MSS_SIZE << 16);
UNALIGNED_PUT(htonl(recv_mss),
(u32_t *)(options + *optionlen));

*optionlen += NET_TCP_MSS_SIZE;
Expand Down Expand Up @@ -1234,3 +1236,77 @@ struct net_buf *net_tcp_set_chksum(struct net_pkt *pkt, struct net_buf *frag)

return frag;
}

int net_tcp_parse_opts(struct net_pkt *pkt, int opt_totlen,
struct net_tcp_options *opts)
{
struct net_buf *frag = pkt->frags;
u16_t pos = net_pkt_ip_hdr_len(pkt)
+ net_pkt_ipv6_ext_len(pkt)
+ sizeof(struct net_tcp_hdr);
u8_t opt, optlen;

/* TODO: this should be done for each TCP pkt, on reception */
if (pos + opt_totlen > net_pkt_get_len(pkt)) {
NET_ERR("Truncated pkt len: %d, expected: %d",
(int)net_pkt_get_len(pkt), pos + opt_totlen);
return -EINVAL;
}

while (opt_totlen) {
frag = net_frag_read(frag, pos, &pos, sizeof(opt), &opt);
opt_totlen--;

/* https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml#tcp-parameters-1 */
/* "Options 0 and 1 are exactly one octet which is their
* kind field. All other options have their one octet
* kind field, followed by a one octet length field,
* followed by length-2 octets of option data."
*/
if (opt == NET_TCP_END_OPT) {
break;
} else if (opt == NET_TCP_NOP_OPT) {
continue;
}

if (!opt_totlen) {
optlen = 0;
goto error;
}

frag = net_frag_read(frag, pos, &pos, sizeof(optlen), &optlen);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Along the discussion on IRC, I'm adding single check at the top of function.

opt_totlen--;
if (optlen < 2) {
goto error;
}

/* Subtract opt/optlen size now to avoid doing this
* repeatedly.
*/
optlen -= 2;
if (opt_totlen < optlen) {
goto error;
}

switch (opt) {
case NET_TCP_MSS_OPT:
if (optlen != 2) {
goto error;
}
frag = net_frag_read(frag, pos, &pos,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Along the discussion on IRC, I'm adding single check at the top of function.

optlen, (u8_t *)&opts->mss);
break;
default:
frag = net_frag_skip(frag, pos, &pos, optlen);
break;
}

opt_totlen -= optlen;
}

return 0;

error:
NET_ERR("Invalid TCP opt: %d len: %d", opt, optlen);
return -EINVAL;
}
55 changes: 50 additions & 5 deletions subsys/net/ip/tcp.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ enum net_tcp_state {

#define NET_TCP_FLAGS(hdr) (hdr->flags & NET_TCP_CTL)

/* Length of TCP header, including options */
/* "offset": 4-bit field in high nibble, units of dwords */
#define NET_TCP_HDR_LEN(hdr) (4 * ((hdr)->offset >> 4))

/* RFC 1122 4.2.2.6 "If an MSS option is not received at connection
* setup, TCP MUST assume a default send MSS of 536"
*/
#define NET_TCP_DEFAULT_MSS 536

/* TCP max window size */
#define NET_TCP_MAX_WIN (4 * 1024)

Expand All @@ -80,11 +89,22 @@ enum net_tcp_state {

#define NET_TCP_MAX_OPT_SIZE 8

#define NET_TCP_MSS_HEADER 0x02040000 /* MSS option */
#define NET_TCP_WINDOW_HEADER 0x30300 /* Window scale option */

#define NET_TCP_MSS_SIZE 4 /* MSS option size */
#define NET_TCP_WINDOW_SIZE 3 /* Window scale option size */
/* TCP Option codes */
#define NET_TCP_END_OPT 0
#define NET_TCP_NOP_OPT 1
#define NET_TCP_MSS_OPT 2
#define NET_TCP_WINDOW_SCALE_OPT 3

/* TCP Option sizes */
#define NET_TCP_END_SIZE 1
#define NET_TCP_NOP_SIZE 1
#define NET_TCP_MSS_SIZE 4
#define NET_TCP_WINDOW_SCALE_SIZE 3

/** Parsed TCP option values for net_tcp_parse_opts() */
struct net_tcp_options {
u16_t mss;
};

/* Max received bytes to buffer internally */
#define NET_TCP_BUF_MAX_LEN 1280
Expand Down Expand Up @@ -148,7 +168,15 @@ struct net_tcp {
*/
struct k_sem connect_wait;

/**
* Current TCP receive window for our side
*/
u16_t recv_wnd;

/**
* Send MSS for the peer
*/
u16_t send_mss;
};

static inline bool net_tcp_is_used(struct net_tcp *tcp)
Expand Down Expand Up @@ -426,6 +454,23 @@ struct net_buf *net_tcp_set_chksum(struct net_pkt *pkt, struct net_buf *frag);
*/
u16_t net_tcp_get_chksum(struct net_pkt *pkt, struct net_buf *frag);

/**
* @brief Parse TCP options from network packet.
*
* Parse TCP options, returning MSS value (as that the only one we
* handle so far).
*
* @param pkt Network packet
* @param opt_totlen Total length of options to parse
* @param opts Pointer to TCP options structure. (Each option is updated
* only if present, so the structure must be initialized with the default
* values.)
*
* @return 0 if no error, <0 in case of error
*/
int net_tcp_parse_opts(struct net_pkt *pkt, int opt_totlen,
struct net_tcp_options *opts);

#else

static inline u16_t net_tcp_get_chksum(struct net_pkt *pkt,
Expand Down