/**************************************************************************** * net/tcp/tcp_recvwindow.c * * SPDX-License-Identifier: Apache-2.0 * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include "tcp/tcp.h" /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: tcp_calc_rcvsize * * Description: * Calculate the possible max TCP receive buffer size for the connection. * * Input Parameters: * conn - The TCP connection. * recvwndo - The TCP receive window size * * Returned Value: * The value of the TCP receive buffer size. * ****************************************************************************/ static uint32_t tcp_calc_rcvsize(FAR struct tcp_conn_s *conn, uint32_t recvwndo) { #if CONFIG_NET_RECV_BUFSIZE > 0 uint32_t recvsize; uint32_t desire; recvsize = conn->readahead ? conn->readahead->io_pktlen : 0; if (conn->rcv_bufs > recvsize) { desire = conn->rcv_bufs - recvsize; if (recvwndo > desire) { recvwndo = desire; } } else { recvwndo = 0; } #endif return recvwndo; } /**************************************************************************** * Name: tcp_maxrcvwin * * Description: * Calculate the possible max TCP receive window for the connection. * * Input Parameters: * conn - The TCP connection. * * Returned Value: * The value of the TCP receive window. ****************************************************************************/ static uint32_t tcp_maxrcvwin(FAR struct tcp_conn_s *conn) { uint32_t recvwndo; /* Calculate the max possible window size for the connection. * This needs to be in sync with tcp_get_recvwindow(). */ recvwndo = tcp_calc_rcvsize(conn, (CONFIG_IOB_NBUFFERS - CONFIG_IOB_THROTTLE) * CONFIG_IOB_BUFSIZE); #ifdef CONFIG_NET_TCP_WINDOW_SCALE recvwndo >>= conn->rcv_scale; #endif if (recvwndo > UINT16_MAX) { recvwndo = UINT16_MAX; } #ifdef CONFIG_NET_TCP_WINDOW_SCALE recvwndo <<= conn->rcv_scale; #endif return recvwndo; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: tcp_get_recvwindow * * Description: * Calculate the TCP receive window for the specified device. * * Input Parameters: * dev - The device whose TCP receive window will be updated. * conn - The TCP connection structure holding connection information. * * Returned Value: * The value of the TCP receive window to use. * ****************************************************************************/ uint32_t tcp_get_recvwindow(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn) { uint32_t tailroom; uint32_t recvwndo; int niob_avail; /* Update the TCP received window based on read-ahead I/O buffer * and IOB chain availability. * The amount of read-ahead * data that can be buffered is given by the number of IOBs available * (ignoring competition with other IOB consumers). */ if (conn->readahead != NULL) { tailroom = iob_tailroom(conn->readahead); } else { tailroom = 0; } niob_avail = iob_navail(true); /* Is there a a queue entry and IOBs available for read-ahead buffering? */ if (niob_avail > 0) { /* The optimal TCP window size is the amount of TCP data that we can * currently buffer via TCP read-ahead buffering for the device packet * buffer. This logic here assumes that all IOBs are available for * TCP buffering. * * Assume that all of the available IOBs are can be used for buffering * on this connection. * * REVISIT: In an environment with multiple, active read-ahead TCP * sockets (and perhaps multiple network devices) or if there are * other consumers of IOBs (such as for TCP write buffering) then the * total number of IOBs will all not be available for read-ahead * buffering for this connection. */ recvwndo = tailroom + (niob_avail * CONFIG_IOB_BUFSIZE); } #if CONFIG_IOB_THROTTLE > 0 else if (conn->readahead == NULL) { /* Advertise maximum segment size for window edge if here is no * available iobs on current "free" connection. * * Note: hopefully, a single mss-sized packet can be queued by * the throttled=false case in tcp_datahandler(). */ int niob_avail_no_throttle = iob_navail(false); recvwndo = tcp_rx_mss(dev); if (recvwndo > niob_avail_no_throttle * CONFIG_IOB_BUFSIZE) { recvwndo = niob_avail_no_throttle * CONFIG_IOB_BUFSIZE; } } #endif else /* niob_avail == 0 */ { /* No IOBs are available. * Advertise the edge of window to zero. * * NOTE: If no IOBs are available, then the next packet will be * lost if there is no listener on the connection. */ recvwndo = tailroom; } recvwndo = tcp_calc_rcvsize(conn, recvwndo); #ifdef CONFIG_NET_TCP_OUT_OF_ORDER /* Calculate the minimum desired size */ if (conn->nofosegs > 0) { uint32_t desire = conn->ofosegs[0].left - tcp_getsequence(conn->rcvseq); int bufsize = tcp_ofoseg_bufsize(conn); if (desire < tcp_rx_mss(dev)) { desire = tcp_rx_mss(dev); } if (TCP_SEQ_LT(recvwndo, bufsize)) { recvwndo = 0; } else { recvwndo -= bufsize; } if (recvwndo < desire) { recvwndo = desire; } } #endif /* CONFIG_NET_TCP_OUT_OF_ORDER */ #ifdef CONFIG_NET_TCP_WINDOW_SCALE recvwndo >>= conn->rcv_scale; #endif if (recvwndo > UINT16_MAX) { recvwndo = UINT16_MAX; } #ifdef CONFIG_NET_TCP_WINDOW_SCALE recvwndo <<= conn->rcv_scale; #endif return recvwndo; } bool tcp_should_send_recvwindow(FAR struct tcp_conn_s *conn) { FAR struct net_driver_s *dev = conn->dev; uint32_t win; uint32_t maxwin; uint32_t oldwin; uint32_t rcvseq; uint32_t adv; uint16_t mss; /* Note: rcv_adv can be smaller than rcvseq. * For examples, when: * * - we shrunk the window * - zero window probes advanced rcvseq */ rcvseq = tcp_getsequence(conn->rcvseq); if (TCP_SEQ_GT(conn->rcv_adv, rcvseq)) { oldwin = TCP_SEQ_SUB(conn->rcv_adv, rcvseq); } else { oldwin = 0; } win = tcp_get_recvwindow(dev, conn); /* If the window doesn't extend, don't send. */ if (win <= oldwin) { ninfo("Returning false: " "rcvseq=%" PRIu32 ", rcv_adv=%" PRIu32 ", " "old win=%" PRIu32 ", new win=%" PRIu32 "\n", rcvseq, conn->rcv_adv, oldwin, win); return false; } adv = win - oldwin; /* The following conditions are inspired from NetBSD TCP stack. * * - If we can extend the window by the half of the max possible size, * send it. * * - If we can extend the window by 2 * mss, send it. */ maxwin = tcp_maxrcvwin(conn); if (2 * adv >= maxwin) { ninfo("Returning true: " "adv=%" PRIu32 ", maxwin=%" PRIu32 "\n", adv, maxwin); return true; } /* Revisit: the real expected size should be used instead. * E.g. consider the path MTU */ mss = tcp_rx_mss(dev); if (adv >= 2 * mss) { ninfo("Returning true: " "adv=%" PRIu32 ", mss=%" PRIu16 ", maxwin=%" PRIu32 "\n", adv, mss, maxwin); return true; } ninfo("Returning false: " "adv=%" PRIu32 ", mss=%" PRIu16 ", maxwin=%" PRIu32 "\n", adv, mss, maxwin); return false; }