/**************************************************************************** * net/udp/udp_send_buffered.c * * Copyright (C) 2018 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * 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 #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 ****************************************************************************/ /**************************************************************************** * Name: sendto_writebuffer_release * * Description: * Release the write buffer at the head of the write buffer queue. * * Parameters: * dev - The structure of the network driver that caused the event * psock - Socket state structure * * Returned Value: * None * * Assumptions: * The network is locked * ****************************************************************************/ static void sendto_writebuffer_release(FAR struct socket *psock, FAR struct udp_conn_s *conn) { FAR struct udp_wrbuffer_s *wrb; int ret = OK; do { /* 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. * * 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. * * 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, FAR struct net_driver_s *dev) { /* 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. * * 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 * * 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. * * 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); wrb->wb_start = clock_systimer(); /* 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 */