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

TCP memory leak fixes #23334

Merged
merged 8 commits into from
Mar 17, 2020
1 change: 0 additions & 1 deletion drivers/ethernet/eth_stm32_hal.c
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ static void rx_thread(void *arg1, void *unused1, void *unused2)
net_eth_carrier_on(dev_data->iface);
}
while ((pkt = eth_rx(dev)) != NULL) {
net_pkt_print_frags(pkt);
res = net_recv_data(dev_data->iface, pkt);
if (res < 0) {
eth_stats_update_errors_rx(dev_data->iface);
Expand Down
17 changes: 17 additions & 0 deletions include/net/net_pkt.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ struct net_pkt {
* AF_UNSPEC.
*/
u8_t ppp_msg : 1; /* This is a PPP message */

u8_t tcp_first_msg : 1; /* Is this the first time this
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd find both this code comment and commit message which introduces it ("net: pkt: Add flag for telling if this is TCP 1st packet") to be unclear and confusing. To start with, we already maintain linked list of packets which were already sent (and may need to be resent) as tcp->sent_list. So, if a packet is not in that list, it's "1st packet" (ahem to the terminology), and if it's in that list, it's "not 1st packet". So, do we need an extra flag for that? If we do, how about naming it more clear? E.g., "resent" (or "resent_pkt", or "pkt_resent"). But note that we already have flags sent_or_eof and pkt_queued. So, as I said, this is getting more and more confusing.

Copy link
Member Author

Choose a reason for hiding this comment

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

The flag tells is this the 1st time this packet has been sent. This is done in order to avoid doing one extra ref when sending it first time. It is indeed confusing, I try to clarify the documentation a bit. The sent_or_eof flag is for another purpose, there it means whether the pkt was sent in the first place. I tried to use the sent flag for detecting whether we need to ref the pkt in relevant places but it became very convoluted so it was much easier to just add a new flag for this. I agree that there are several flags now in the system and their interaction is hairy, but it is not easy to send the packet as there are two threads involved and the interaction is difficult when we have timers that expire when the packet is not even sent yet.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree that there are several flags now in the system and their interaction is hairy, but it is not easy to send the packet as there are two threads ...

Ok, thanks for clarification.

* pkt is sent, or is this resend
* of a TCP message.
* Used only if
* defined(CONFIG_NET_TCP)
*/
};

union {
Expand Down Expand Up @@ -354,6 +361,16 @@ static inline void net_pkt_set_queued(struct net_pkt *pkt, bool send)
pkt->pkt_queued = send;
}

static inline u8_t net_pkt_tcp_1st_msg(struct net_pkt *pkt)
{
return pkt->tcp_first_msg;
}

static inline void net_pkt_set_tcp_1st_msg(struct net_pkt *pkt, bool is_1st)
{
pkt->tcp_first_msg = is_1st;
}

#if defined(CONFIG_NET_SOCKETS)
static inline u8_t net_pkt_eof(struct net_pkt *pkt)
{
Expand Down
25 changes: 20 additions & 5 deletions subsys/net/ip/net_if.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,10 @@ static inline void net_context_send_cb(struct net_context *context,

static bool net_if_tx(struct net_if *iface, struct net_pkt *pkt)
{
struct net_linkaddr *dst;
struct net_linkaddr ll_dst = {
.addr = NULL
};
struct net_linkaddr_storage ll_dst_storage;
jukkar marked this conversation as resolved.
Show resolved Hide resolved
struct net_context *context;
int status;

Expand All @@ -164,13 +167,25 @@ static bool net_if_tx(struct net_if *iface, struct net_pkt *pkt)

debug_check_packet(pkt);

dst = net_pkt_lladdr_dst(pkt);
/* If there're any link callbacks, with such a callback receiving
* a destination address, copy that address out of packet, just in
* case packet is freed before callback is called.
*/
if (!sys_slist_is_empty(&link_callbacks)) {
jukkar marked this conversation as resolved.
Show resolved Hide resolved
if (net_linkaddr_set(&ll_dst_storage,
net_pkt_lladdr_dst(pkt)->addr,
net_pkt_lladdr_dst(pkt)->len) == 0) {
ll_dst.addr = ll_dst_storage.addr;
ll_dst.len = ll_dst_storage.len;
ll_dst.type = net_pkt_lladdr_dst(pkt)->type;
}
}

context = net_pkt_context(pkt);

if (net_if_flag_is_set(iface, NET_IF_UP)) {
if (IS_ENABLED(CONFIG_NET_TCP) &&
net_pkt_family(pkt) != AF_UNSPEC) {
net_pkt_set_sent(pkt, true);
net_pkt_set_queued(pkt, false);
}

Expand Down Expand Up @@ -236,8 +251,8 @@ static bool net_if_tx(struct net_if *iface, struct net_pkt *pkt)
}
}

if (dst->addr) {
net_if_call_link_cb(iface, dst, status);
if (ll_dst.addr) {
net_if_call_link_cb(iface, &ll_dst, status);
}

return true;
Expand Down
7 changes: 6 additions & 1 deletion subsys/net/ip/net_pkt.c
Original file line number Diff line number Diff line change
Expand Up @@ -1150,7 +1150,12 @@ int net_pkt_alloc_buffer(struct net_pkt *pkt,
#endif

if (!buf) {
NET_ERR("Data buffer allocation failed.");
#if NET_LOG_LEVEL >= LOG_LEVEL_DBG
NET_ERR("Data buffer (%zd) allocation failed (%s:%d)",
alloc_len, caller, line);
#else
NET_ERR("Data buffer (%zd) allocation failed.", alloc_len);
#endif
return -ENOMEM;
}

Expand Down
135 changes: 112 additions & 23 deletions subsys/net/ip/tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -231,19 +231,33 @@ static void tcp_retry_expired(struct k_work *work)
pkt = CONTAINER_OF(sys_slist_peek_head(&tcp->sent_list),
struct net_pkt, sent_list);

if (net_pkt_sent(pkt)) {
do_ref_if_needed(tcp, pkt);
net_pkt_set_sent(pkt, false);
if (k_work_pending(net_pkt_work(pkt))) {
/* If the packet is still pending in TX queue, then do
* not try to resend it again. This can happen if the
* device is so busy that the TX thread has not yet
* finished previous sending of this packet.
*/
NET_DBG("[%p] pkt %p still pending in TX queue",
tcp, pkt);
return;
}

net_pkt_set_queued(pkt, true);
net_pkt_set_tcp_1st_msg(pkt, false);

/* The ref here is for the initial reference which was lost
* when the pkt was sent. Typically the ref count should be 2
* at this point if the pkt is being sent by the driver.
*/
if (!is_6lo_technology(pkt)) {
net_pkt_ref(pkt);
}

if (net_tcp_send_pkt(pkt) < 0 && !is_6lo_technology(pkt)) {
NET_DBG("retry %u: [%p] pkt %p send failed",
tcp->retry_timeout_shift, tcp, pkt);
if (net_pkt_sent(pkt)) {
net_pkt_unref(pkt);
}
/* Undo the ref done above */
net_pkt_unref(pkt);
} else {
NET_DBG("retry %u: [%p] sent pkt %p",
tcp->retry_timeout_shift, tcp, pkt);
Expand Down Expand Up @@ -327,12 +341,6 @@ int net_tcp_release(struct net_tcp *tcp)
return -EINVAL;
}

SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tcp->sent_list, pkt, tmp,
sent_list) {
sys_slist_remove(&tcp->sent_list, NULL, &pkt->sent_list);
net_pkt_unref(pkt);
}

retry_timer_cancel(tcp);
k_sem_reset(&tcp->connect_wait);

Expand All @@ -341,6 +349,43 @@ int net_tcp_release(struct net_tcp *tcp)
timewait_timer_cancel(tcp);

net_tcp_change_state(tcp, NET_TCP_CLOSED);

SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tcp->sent_list, pkt, tmp,
sent_list) {
int refcount;

sys_slist_remove(&tcp->sent_list, NULL, &pkt->sent_list);

/* The packet might get freed when sending it, so if that is
* so, just skip it.
*/
if (atomic_get(&pkt->atomic_ref) == 0) {
continue;
}

/* Make sure we undo the reference done in net_tcp_queue_pkt()
*/
net_pkt_unref(pkt);

/* Release the packet fully unless it is still pending */
refcount = atomic_get(&pkt->atomic_ref);
if (refcount > 0) {
/* If the pkt was already placed to TX queue, let
* it go as it will be released by L2 after it is
* sent.
*/
if (k_work_pending(net_pkt_work(pkt)) ||
net_pkt_sent(pkt)) {
refcount--;
}

while (refcount) {
net_pkt_unref(pkt);
refcount--;
}
}
}

tcp->context = NULL;

key = irq_lock();
Expand Down Expand Up @@ -411,6 +456,9 @@ static int prepare_segment(struct net_tcp *tcp,
pkt_allocated = true;
}

net_pkt_set_tcp_1st_msg(pkt, true);
net_pkt_set_sent(pkt, false);

if (IS_ENABLED(CONFIG_NET_IPV4) &&
net_pkt_family(pkt) == AF_INET) {
status = net_context_create_ipv4_new(context, pkt,
Expand Down Expand Up @@ -851,6 +899,12 @@ static int net_tcp_queue_pkt(struct net_context *context, struct net_pkt *pkt)
retry_timeout(context->tcp));
}

/* Increase the ref count so that we do not lose the packet and
* can resend later if needed. The pkt will be released after we
* have received the ACK or the TCP stream is removed. This is only
* done for non-6lo technologies that will keep the data until ACK
* is received or timeout happens.
*/
do_ref_if_needed(context->tcp, pkt);

return 0;
Expand All @@ -862,6 +916,7 @@ int net_tcp_send_pkt(struct net_pkt *pkt)
struct net_context *ctx = net_pkt_context(pkt);
struct net_tcp_hdr *tcp_hdr;
bool calc_chksum = false;
int ret;

if (!ctx || !ctx->tcp) {
NET_ERR("%scontext is not set on pkt %p",
Expand Down Expand Up @@ -932,7 +987,6 @@ int net_tcp_send_pkt(struct net_pkt *pkt)
*/
if (is_6lo_technology(pkt)) {
struct net_pkt *new_pkt, *check_pkt;
int ret;
bool pkt_in_slist = false;

/*
Expand Down Expand Up @@ -965,13 +1019,19 @@ int net_tcp_send_pkt(struct net_pkt *pkt)
} else {
net_stats_update_tcp_seg_rexmit(
net_pkt_iface(pkt));
net_pkt_set_sent(pkt, true);
}

return ret;
}
}

return net_send_data(pkt);
ret = net_send_data(pkt);
if (ret == 0) {
net_pkt_set_sent(pkt, true);
}

return ret;
}

static void flush_queue(struct net_context *context)
Expand Down Expand Up @@ -1003,6 +1063,7 @@ int net_tcp_send_data(struct net_context *context, net_context_send_cb_t cb,
void *user_data)
{
struct net_pkt *pkt;
int ret;

/* For now, just send all queued data synchronously. Need to
* add window handling and retry/ACK logic.
Expand All @@ -1015,21 +1076,36 @@ int net_tcp_send_data(struct net_context *context, net_context_send_cb_t cb,
continue;
}

if (!net_pkt_sent(pkt)) {
int ret;
/* If this pkt is the first one (not a resend), then we do
* not need to increase the ref count as it is 1 already.
* For a resent packet, the ref count is only 1 atm, and
* the packet would be freed in driver if we do not increase
* it here. This is only done for non-6lo technologies where
* we keep the original packet (by referencing it) for possible
* re-send (if ACK is not received on time).
*/
if (!is_6lo_technology(pkt)) {
if (!net_pkt_tcp_1st_msg(pkt)) {
net_pkt_ref(pkt);
}
}

NET_DBG("[%p] Sending pkt %p (%zd bytes)", context->tcp,
pkt, net_pkt_get_len(pkt));
NET_DBG("[%p] Sending pkt %p (%zd bytes)", context->tcp,
pkt, net_pkt_get_len(pkt));

ret = net_tcp_send_pkt(pkt);
if (ret < 0 && !is_6lo_technology(pkt)) {
NET_DBG("[%p] pkt %p not sent (%d)",
context->tcp, pkt, ret);
ret = net_tcp_send_pkt(pkt);
if (ret < 0) {
NET_DBG("[%p] pkt %p not sent (%d)",
context->tcp, pkt, ret);
if (!is_6lo_technology(pkt)) {
net_pkt_unref(pkt);
}

net_pkt_set_queued(pkt, true);
return ret;
}

net_pkt_set_queued(pkt, true);
net_pkt_set_tcp_1st_msg(pkt, false);
}

/* Just make the callback synchronously even if it didn't
Expand Down Expand Up @@ -1128,8 +1204,19 @@ bool net_tcp_ack_received(struct net_context *ctx, u32_t ack)
}
}

NET_DBG("[%p] Received ACK pkt %p (len %zd bytes)", ctx->tcp,
pkt, net_pkt_get_len(pkt));

sys_slist_remove(list, NULL, head);

/* If we receive a valid ACK, then we need to undo the ref
* set in net_tcp_queue_pkt() (when using non-6lo technology)
* or the ref set in packet creation (for 6lo packet) in order
* to release the pkt.
*/
net_pkt_set_sent(pkt, false);
net_pkt_unref(pkt);

valid_ack = true;
}

Expand Down Expand Up @@ -1845,6 +1932,7 @@ static inline int send_syn_segment(struct net_context *context,
return ret;
}

net_pkt_set_sent(pkt, true);
context->tcp->send_seq++;

return ret;
Expand Down Expand Up @@ -1914,6 +2002,7 @@ static int send_reset(struct net_context *context,
net_pkt_unref(pkt);
}

net_pkt_set_sent(pkt, true);
return ret;
}

Expand Down
13 changes: 10 additions & 3 deletions subsys/net/lib/sockets/sockets.c
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ static inline ssize_t zsock_recv_dgram(struct net_context *ctx,
src_addr, *addrlen);
if (rv < 0) {
errno = -rv;
return -1;
goto fail;
}

/* addrlen is a value-result argument, set to actual
Expand All @@ -781,7 +781,7 @@ static inline ssize_t zsock_recv_dgram(struct net_context *ctx,
*addrlen = sizeof(struct sockaddr_in6);
} else {
errno = ENOTSUP;
return -1;
goto fail;
}
}

Expand All @@ -792,7 +792,7 @@ static inline ssize_t zsock_recv_dgram(struct net_context *ctx,

if (net_pkt_read(pkt, buf, recv_len)) {
errno = ENOBUFS;
return -1;
goto fail;
}

net_stats_update_tc_rx_time(net_pkt_iface(pkt),
Expand All @@ -807,6 +807,13 @@ static inline ssize_t zsock_recv_dgram(struct net_context *ctx,
}

return recv_len;

fail:
if (!(flags & ZSOCK_MSG_PEEK)) {
net_pkt_unref(pkt);
}

return -1;
}

static inline ssize_t zsock_recv_stream(struct net_context *ctx,
Expand Down