Skip to content
udp_psock_sendto_buffered.c 22.1 KiB
Newer Older
/****************************************************************************
 * net/udp/udp_send_buffered.c
 *
 *   Copyright (C) 2018 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#if defined(CONFIG_NET) && defined(CONFIG_NET_UDP) && \
    defined(CONFIG_NET_UDP_WRITE_BUFFERS)

#if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_NET_UDP_WRBUFFER_DEBUG)
/* Force debug output (from this file only) */

#  undef  CONFIG_DEBUG_NET
#  define CONFIG_DEBUG_NET 1
#endif

#include <sys/types.h>
#include <sys/socket.h>

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <debug.h>
#include <debug.h>

#include <arch/irq.h>
#include <nuttx/clock.h>
#include <nuttx/net/net.h>
#include <nuttx/mm/iob.h>
#include <nuttx/net/netdev.h>
#include <nuttx/net/arp.h>
#include <nuttx/net/udp.h>

#include "netdev/netdev.h"
#include "socket/socket.h"
#include "inet/inet.h"
#include "arp/arp.h"
#include "icmpv6/icmpv6.h"
#include "neighbor/neighbor.h"
#include "udp/udp.h"
#include "devif/devif.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/
/* If both IPv4 and IPv6 support are both enabled, then we will need to build
 * in some additional domain selection support.
 */

#if defined(CONFIG_NET_IPv4) && defined(CONFIG_NET_IPv6)
#  define NEED_IPDOMAIN_SUPPORT 1
#endif

#define UDPIPv4BUF ((struct udp_hdr_s *)&dev->d_buf[NET_LL_HDRLEN(dev) + IPv4_HDRLEN])
#define UDPIPv6BUF ((struct udp_hdr_s *)&dev->d_buf[NET_LL_HDRLEN(dev) + IPv6_HDRLEN])

/* Debug */

#ifdef CONFIG_NET_UDP_WRBUFFER_DUMP
#  define BUF_DUMP(msg,buf,len) lib_dumpbuffer(msg,buf,len)
#else
#  define BUF_DUMP(msg,buf,len)
#  undef  UDP_WBDUMP
#  define UDP_WBDUMP(msg,wrb,len,offset)
#endif

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

#ifdef NEED_IPDOMAIN_SUPPORT
static inline void sendto_ipselect(FAR struct net_driver_s *dev,
                                   FAR struct udp_conn_s *conn);
#endif
#ifdef CONFIG_NET_ETHERNET
static inline bool sendto_addrcheck(FAR struct udp_conn_s *conn,
                                    FAR struct net_driver_s *dev);
#else
#  define sendto_addrcheck(c,d) (true)
#endif
#ifdef CONFIG_NET_SOCKOPTS
static inline int sendto_timeout(FAR struct socket *psock,
                                 FAR struct udp_conn_s *conn);
#endif
static int sendto_next_transfer(FAR struct socket *psock,
                                FAR struct udp_conn_s *conn);
static uint16_t sendto_eventhandler(FAR struct net_driver_s *dev,
                                    FAR void *pvconn, FAR void *pvpriv,
                                    uint16_t flags);

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 *   Release the write buffer at the head of the write buffer queue.
 * Input Parameters:
 *   dev   - The structure of the network driver that caused the event
 *   psock - Socket state structure
 ****************************************************************************/

static void sendto_writebuffer_release(FAR struct socket *psock,
                                       FAR struct udp_conn_s *conn)
  FAR struct udp_wrbuffer_s *wrb;
  int ret = OK;
      /* Check if the write queue became empty */
      if (sq_empty(&conn->write_q))
        {
          /* Yes.. stifle any further callbacks until more write data is
           * enqueued.
           */
          psock->s_sndcb->flags = 0;
          psock->s_sndcb->priv  = NULL;
          psock->s_sndcb->event = NULL;
          wrb = NULL;
        }
      else
        {
          /* Remove the write buffer from the head of the write buffer queue
           * and release it.
           */

          wrb = (FAR struct udp_wrbuffer_s *)sq_remfirst(&conn->write_q);
          DEBUGASSERT(wrb != NULL);
          udp_wrbuffer_release(wrb);

          /* Set up for the next packet transfer by setting the connection
           * address to the address of the next packet now at the header of
           * the write buffer queue.
           */
         ret = sendto_next_transfer(psock, conn);
       }
    }
  while (wrb != NULL && ret < 0);
}

/****************************************************************************
 * Name: sendto_ipselect
 *
 * Description:
 *   If both IPv4 and IPv6 support are enabled, then we will need to select
 *   which one to use when generating the outgoing packet.  If only one
 *   domain is selected, then the setup is already in place and we need do
 *   nothing.
 *
 * Input Parameters:
 *   dev   - The structure of the network driver that caused the event
 *   psock - Socket state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   The network is locked
 *
 ****************************************************************************/

#ifdef NEED_IPDOMAIN_SUPPORT
static inline void sendto_ipselect(FAR struct net_driver_s *dev,
                                   FAR struct udp_conn_s *conn)
{
  /* Which domain the socket support */

  if (conn->domain == PF_INET)
    {
      /* Select the IPv4 domain */

      udp_ipv4_select(dev);
    }
  else /* if (conn->domain == PF_INET6) */
    {
      /* Select the IPv6 domain */

      DEBUGASSERT(conn->domain == PF_INET6);
      udp_ipv6_select(dev);
    }
}
#endif

/****************************************************************************
 * Name: sendto_addrcheck
 *
 * Description:
 *   Check if the destination IP address is in the IPv4 ARP or IPv6 Neighbor
 *   tables.  If not, then the send won't actually make it out... it will be
 *   replaced with an ARP request (IPv4) or a Neighbor Solicitation (IPv6).
 *
 *   NOTE 1: This could be an expensive check if there are a lot of
 *   entries in the ARP or Neighbor tables.
 *
 *   NOTE 2: If we are actually harvesting IP addresses on incoming IP
 *   packets, then this check should not be necessary; the MAC mapping
 *   should already be in the ARP table in many cases (IPv4 only).
 *
 *   NOTE 3: If CONFIG_NET_ARP_SEND then we can be assured that the IP
 *   address mapping is already in the ARP table.
 *
 * Input Parameters:
 *   conn - The UDP connection structure
 *   dev  - Polling network device
 *
 * Returned Value:
 *   true - The Ethernet MAC address is in the ARP or Neighbor table (OR
 *          the network device is not Ethernet).
 *
 * Assumptions:
 *   The network is locked
 *
 ****************************************************************************/

#ifdef CONFIG_NET_ETHERNET
static inline bool sendto_addrcheck(FAR struct udp_conn_s *conn,
{
  /* REVISIT: Could the MAC address not also be in a routing table? */

  if (dev->d_lltype != NET_LL_ETHERNET)
    {
      return true;
    }

#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_IPv6
  if (conn->domain == PF_INET)
#endif
    {
#if !defined(CONFIG_NET_ARP_IPIN) && !defined(CONFIG_NET_ARP_SEND)
      return (arp_find(conn->u.ipv4.raddr) != NULL);
#else
      return true;
#endif
    }
#endif /* CONFIG_NET_IPv4 */

#ifdef CONFIG_NET_IPv6
#ifdef CONFIG_NET_IPv4
  else
#endif
    {
#if !defined(CONFIG_NET_ICMPv6_NEIGHBOR)
      return (neighbor_findentry(conn->u.ipv6.raddr) != NULL);
#else
      return true;
#endif
    }
#endif /* CONFIG_NET_IPv6 */
}
#endif /* CONFIG_NET_ETHERNET */

/****************************************************************************
 * Name: sendto_timeout
 *
 * Description:
 *   Check for send timeout.
 *
 * Input Parameters:
 *   pstate - sendto state structure
 *
 * Returned Value:
 *   TRUE:timeout FALSE:no timeout
 *
 * Assumptions:
 *   The network is locked
 *
 ****************************************************************************/

#ifdef CONFIG_NET_SOCKOPTS
static inline int sendto_timeout(FAR struct socket *psock,
                                 FAR struct udp_conn_s *conn)
{
  FAR struct udp_wrbuffer_s *wrb;

  /* Peek at the head of the write queue (without altering the write queue). */

  wrb = (FAR struct udp_wrbuffer_s *)sq_peek(&conn->write_q);
  if (wrb != NULL)
    {
      /* Check for a timeout configured via setsockopts(SO_SNDTIMEO).
       * If none... we well let the send wait forever.
       */

      if (psock->s_sndtimeo != 0)
        {
          /* Check if the configured timeout has elapsed */

          return net_timeo(wrb->wb_start, psock->s_sndtimeo);
        }
    }

  /* No timeout */

  return FALSE;
}
#endif /* CONFIG_NET_SOCKOPTS */

/****************************************************************************
 * Name: sendto_next_transfer
 *
 * Description:
 *   Setup for the next packet transfer
 *
 * Input Parameters:
 *   psock - Socket state structure
 *   conn  - The UDP connection structure
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int sendto_next_transfer(FAR struct socket *psock,
                                FAR struct udp_conn_s *conn)
{
  FAR struct udp_wrbuffer_s *wrb;
  FAR struct net_driver_s *dev;
  int ret;

  /* Set the UDP "connection" to the destination address of the write buffer
   * at the head of the queue.
   */

  wrb = (FAR struct udp_wrbuffer_s *)sq_peek(&conn->write_q);
  if (wrb == NULL)
    {
      ninfo("Write buffer queue is empty\n");
      return -ENOENT;
    }
  ret = udp_connect(conn, (FAR const struct sockaddr *)&wrb->wb_dest);
  if (ret < 0)
    {
      nerr("ERROR: udp_connect failed: %d\n", ret);
      return ret;
    }

 /* Get the device that will handle the remote packet transfers.  This
  * should never be NULL.
  */

 dev = udp_find_raddr_device(conn);
 if (dev == NULL)
   {
     nerr("ERROR: udp_find_raddr_device failed\n");
     return -ENETUNREACH;
   }

  /* Make sure that the device is in the UP state */

  if ((dev->d_flags & IFF_UP) == 0)
    {
      nwarn("WARNING: device is DOWN\n");
      return -EHOSTUNREACH;
    }

  /* If this is not the same device that we used in the last call to
   * udp_callback_alloc(), then we need to release and reallocate the old
   * callback instance.
   */

  if (psock->s_sndcb != NULL && conn->dev != dev)
    {
      udp_callback_free(conn->dev, conn, psock->s_sndcb);
      psock->s_sndcb = NULL;
    }

  /* Allocate resources to receive a callback from this device if the
   * callback is not already in place.
   */

  if (psock->s_sndcb == NULL)
    {
      psock->s_sndcb = udp_callback_alloc(dev, conn);
    }

  /* Test if the callback has been allocated */

  if (psock->s_sndcb == NULL)
    {
      /* A buffer allocation error occurred */

      nerr("ERROR: Failed to allocate callback\n");
      return -ENOMEM;
    }

  conn->dev = dev;

  /* Set up the callback in the connection */

  psock->s_sndcb->flags = (UDP_POLL | NETDEV_DOWN);
  psock->s_sndcb->priv  = (FAR void *)psock;
  psock->s_sndcb->event = sendto_eventhandler;

  /* Notify the device driver of the availability of TX data */

  netdev_txnotify_dev(dev);
  return OK;
}

/****************************************************************************
 * Name: sendto_eventhandler
 *
 * Description:
 *   This function is called to perform the actual send operation when
 *   polled by the lower, device interfacing layer.
 *
 * Input Parameters:
 *   dev      The structure of the network driver that caused the event
 *   conn     The connection structure associated with the socket
 *   flags    Set of events describing why the callback was invoked
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   The network is locked
 *
 ****************************************************************************/

static uint16_t sendto_eventhandler(FAR struct net_driver_s *dev,
                                    FAR void *pvconn, FAR void *pvpriv,
                                    uint16_t flags)
{
  FAR struct udp_conn_s *conn = (FAR struct udp_conn_s *)pvconn;
  FAR struct socket *psock = (FAR struct socket *)pvpriv;

  ninfo("flags: %04x\n", flags);

  /* Check if the network device has gone down  */

  if ((flags & NETDEV_DOWN) != 0)
    {
      ninfo("Device down: %04x\n", flags);

      /* Free the write buffer at the head of the queue and attempt to setup
       * the next transfer.
       */
      sendto_writebuffer_release(psock, conn);
      return flags;
    }

  /* Check for a normal polling cycle and if the outgoing packet is
   * available.  It would not be available if it has been claimed by a send
   * event serving a different thread -OR- if the output buffer currently
   * contains unprocessed incoming data.  In these cases we will just have
   * to wait for the next polling cycle.
   *
   * And, of course, we can do nothing if we have no data in the write
   * buffers to send.
   */

  if (dev->d_sndlen <= 0 && (flags & UDP_NEWDATA) == 0 &&
      (flags & UDP_POLL) != 0 && !sq_empty(&conn->write_q))
    {
      /* Check if the destination IP address is in the ARP  or Neighbor
       * table.  If not, then the send won't actually make it out... it
       * will be replaced with an ARP request or Neighbor Solicitation.
       */

      if (sendto_addrcheck(conn, dev))
        {
          FAR struct udp_wrbuffer_s *wrb;
          size_t sndlen;

          /* Peek at the head of the write queue (but don't remove anything
           * from the write queue yet).  We know from the above test that
           * the write_q is not empty.
           */

          wrb = (FAR struct udp_wrbuffer_s *)sq_peek(&conn->write_q);
          DEBUGASSERT(wrb != NULL);

          /* Get the amount of data that we can send in the next packet.
           * We will send either the remaining data in the buffer I/O
           * buffer chain, or as much as will fit given the MSS and current
           * window size.
           */

          sndlen = wrb->wb_iob->io_pktlen;
          ninfo("wrb=%p sndlen=%u\n", wrb, sndlen);

#ifdef NEED_IPDOMAIN_SUPPORT
          /* If both IPv4 and IPv6 support are enabled, then we will need to
           * select which one to use when generating the outgoing packet.
           * If only one domain is selected, then the setup is already in
           * place and we need do nothing.
           */

          sendto_ipselect(dev, conn);
#endif
          /* Then set-up to send that amount of data with the offset
           * corresponding to the size of the IP-dependent address structure.
           */

          devif_iob_send(dev, wrb->wb_iob, sndlen, 0);

          /* Free the write buffer at the head of the queue and attempt to
           * setup the next transfer.
          sendto_writebuffer_release(psock, conn);

          /* Only one data can be sent by low level driver at once,
           * tell the caller stop polling the other connections.
           */

          flags &= ~UDP_POLL;
        }
    }

#ifdef CONFIG_NET_SOCKOPTS
  /* We cannot send the data now.  Check for a timeout. */

  else if (sendto_timeout(psock, conn))
    {
      /* Free the write buffer at the head of the queue and attempt to setup
       * the next transfer.
       */
      sendto_writebuffer_release(psock, conn);
    }
#endif /* CONFIG_NET_SOCKOPTS */

  /* Continue waiting */

  return flags;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: psock_udp_sendto
 *
 * Description:
 *   This function implements the UDP-specific logic of the standard
 *   sendto() socket operation.
 *
 * Input Parameters:
 *   psock    A pointer to a NuttX-specific, internal socket structure
 *   buf      Data to send
 *   len      Length of data to send
 *   flags    Send flags
 *   to       Address of recipient
 *   tolen    The length of the address structure
 *
 *   NOTE: All input parameters were verified by sendto() before this
 *   function was called.
 *
 * Returned Value:
 *   On success, returns the number of characters sent.  On  error,
 *   a negated errno value is returned.  See the description in
 *   net/socket/sendto.c for the list of appropriate return value.
 *
 ****************************************************************************/

ssize_t psock_udp_sendto(FAR struct socket *psock, FAR const void *buf,
                         size_t len, int flags, FAR const struct sockaddr *to,
                         socklen_t tolen)
{
  FAR struct udp_conn_s *conn;
  FAR struct udp_wrbuffer_s *wrb;
  int ret = OK;

  /* Make sure that we have the IP address mapping */

  conn = (FAR struct udp_conn_s *)psock->s_conn;
  DEBUGASSERT(conn);

#if defined(CONFIG_NET_ARP_SEND) || defined(CONFIG_NET_ICMPv6_NEIGHBOR)
#ifdef CONFIG_NET_ARP_SEND
#ifdef CONFIG_NET_ICMPv6_NEIGHBOR
  if (psock->s_domain == PF_INET)
#endif
    {
      /* Make sure that the IP address mapping is in the ARP table */

      ret = arp_send(conn->u.ipv4.raddr);
    }
#endif /* CONFIG_NET_ARP_SEND */

#ifdef CONFIG_NET_ICMPv6_NEIGHBOR
#ifdef CONFIG_NET_ARP_SEND
  else
#endif
    {
      /* Make sure that the IP address mapping is in the Neighbor Table */

      ret = icmpv6_neighbor(conn->u.ipv6.raddr);
    }
#endif /* CONFIG_NET_ICMPv6_NEIGHBOR */

  /* Did we successfully get the address mapping? */

  if (ret < 0)
    {
      nerr("ERROR: Not reachable\n");
      return -ENETUNREACH;
    }
#endif /* CONFIG_NET_ARP_SEND || CONFIG_NET_ICMPv6_NEIGHBOR */

  /* Dump the incoming buffer */

  BUF_DUMP("psock_udp_send", buf, len);

  /* Set the socket state to sending */

  psock->s_flags = _SS_SETSTATE(psock->s_flags, _SF_SEND);

  if (len > 0)
    {
      /* Allocate a write buffer.  Careful, the network will be momentarily
       * unlocked here.
       */

      net_lock();
      wrb = udp_wrbuffer_alloc();
      if (wrb == NULL)
        {
          /* A buffer allocation error occurred */

          nerr("ERROR: Failed to allocate write buffer\n");
          ret = -ENOMEM;
          goto errout_with_lock;
        }

      /* Initialize the write buffer */

      memcpy(&wrb->wb_dest, to, tolen);
#ifdef CONFIG_NET_SOCKOPTS

      /* Copy the user data into the write buffer.  We cannot wait for
       * buffer space if the socket was opened non-blocking.
       */

      if (_SS_ISNONBLOCK(psock->s_flags))
        {
          ret = iob_trycopyin(wrb->wb_iob, (FAR uint8_t *)buf, len, 0, false);
        }
      else
        {
          ret = iob_copyin(wrb->wb_iob, (FAR uint8_t *)buf, len, 0, false);
        }

      if (ret < 0)
        {
          goto errout_with_wrb;
        }

      /* Dump I/O buffer chain */

      UDP_WBDUMP("I/O buffer chain", wrb, wrb->wb_iob->io_pktlen, 0);

      /* sendto_eventhandler() will send data in FIFO order from the
       * conn->write_q.
       *
       * REVISIT:  Why FIFO order?  Because it is easy.  In a real world
       * environment where there are multiple network devices this might
       * be inefficient because we could be sending data to different
       * device out-of-queued-order to optimize performance.  Sending
       * data to different networks from a single UDP socket is probably
       * not a very common use case, however.
       */

      sq_addlast(&wrb->wb_node, &conn->write_q);
      ninfo("Queued WRB=%p pktlen=%u write_q(%p,%p)\n",
            wrb, wrb->wb_iob->io_pktlen,
            conn->write_q.head, conn->write_q.tail);

      /* Set up for the next packet transfer by setting the connection
       * address to the address of the next packet now at the header of the
       * write buffer queue.
       */

     ret = sendto_next_transfer(psock, conn);
     if (ret < 0)
       {
         (void)sq_remlast(&conn->write_q);
         goto errout_with_wrb;
       }

      net_unlock();
    }

  /* Set the socket state to idle */

  psock->s_flags = _SS_SETSTATE(psock->s_flags, _SF_IDLE);

  /* Return the number of bytes that will be sent */

  return len;

errout_with_wrb:
  udp_wrbuffer_release(wrb);

errout_with_lock:
  net_unlock();
  return ret;
}

#endif /* CONFIG_NET && CONFIG_NET_UDP && CONFIG_NET_UDP_WRITE_BUFFERS */