net/udp: Support zero-length UDP datagrams

According to RFC768 page2 and referring to the Linux implementation, the message with udp length of 0 is supported.

Signed-off-by: gaohedong <gaohedong@xiaomi.com>
This commit is contained in:
gaohedong
2025-09-05 16:31:40 +08:00
committed by Xiang Xiao
parent c72ae882da
commit 4d525505e4
5 changed files with 153 additions and 138 deletions
+2 -1
View File
@@ -74,7 +74,8 @@ ssize_t psock_sendmsg(FAR struct socket *psock, FAR struct msghdr *msg,
{ {
/* Verify that non-NULL pointers were passed */ /* Verify that non-NULL pointers were passed */
if (msg == NULL || msg->msg_iov == NULL || msg->msg_iov->iov_base == NULL) if (msg == NULL || msg->msg_iov == NULL ||
(psock->s_type != SOCK_DGRAM && msg->msg_iov->iov_base == NULL))
{ {
return -EINVAL; return -EINVAL;
} }
+1 -1
View File
@@ -103,7 +103,7 @@ void udp_poll(FAR struct net_driver_s *dev, FAR struct udp_conn_s *conn)
/* If the application has data to send, setup the UDP/IP header */ /* If the application has data to send, setup the UDP/IP header */
if (dev->d_sndlen > 0) if (dev->d_len > 0)
{ {
udp_send(dev, conn); udp_send(dev, conn);
return; return;
+1 -1
View File
@@ -155,7 +155,7 @@ void udp_send(FAR struct net_driver_s *dev, FAR struct udp_conn_s *conn)
ninfo("UDP payload: %d (%d) bytes\n", dev->d_sndlen, dev->d_len); ninfo("UDP payload: %d (%d) bytes\n", dev->d_sndlen, dev->d_len);
if (dev->d_sndlen > 0) if (dev->d_len > 0)
{ {
#ifdef CONFIG_NET_IPv4 #ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_IPv6 #ifdef CONFIG_NET_IPv6
+128 -130
View File
@@ -687,136 +687,134 @@ ssize_t psock_udp_sendto(FAR struct socket *psock, FAR const void *buf,
BUF_DUMP("psock_udp_sendto", buf, len); BUF_DUMP("psock_udp_sendto", buf, len);
if (len > 0) net_lock();
{
net_lock();
#if CONFIG_NET_SEND_BUFSIZE > 0 #if CONFIG_NET_SEND_BUFSIZE > 0
/* If the send buffer size exceeds the send limit, /* If the send buffer size exceeds the send limit,
* wait for the write buffer to be released * wait for the write buffer to be released
*/ */
while (udp_wrbuffer_inqueue_size(conn) + len > conn->sndbufs) while (udp_wrbuffer_inqueue_size(conn) + len > conn->sndbufs)
{ {
if (nonblock)
{
ret = -EAGAIN;
goto errout_with_lock;
}
ret = net_sem_timedwait_uninterruptible(&conn->sndsem,
udp_send_gettimeout(start, timeout));
if (ret < 0)
{
if (ret == -ETIMEDOUT)
{
ret = -EAGAIN;
}
goto errout_with_lock;
}
}
#endif /* CONFIG_NET_SEND_BUFSIZE */
/* Allocate a write buffer. Careful, the network will be momentarily
* unlocked here.
*/
#ifdef CONFIG_NET_JUMBO_FRAME
/* alloc iob of gso pkt for udp data */
wrb = udp_wrbuffer_tryalloc(len + udpip_hdrsize(conn) +
CONFIG_NET_LL_GUARDSIZE);
#else
if (nonblock) if (nonblock)
{ {
wrb = udp_wrbuffer_tryalloc(); ret = -EAGAIN;
goto errout_with_lock;
} }
else
ret = net_sem_timedwait_uninterruptible(&conn->sndsem,
udp_send_gettimeout(start, timeout));
if (ret < 0)
{ {
wrb = udp_wrbuffer_timedalloc(udp_send_gettimeout(start, if (ret == -ETIMEDOUT)
timeout));
}
#endif
if (wrb == NULL)
{
/* A buffer allocation error occurred */
nerr("ERROR: Failed to allocate write buffer\n");
if (nonblock || timeout != UINT_MAX)
{ {
ret = -EAGAIN; ret = -EAGAIN;
} }
else
{
ret = -ENOMEM;
}
goto errout_with_lock; goto errout_with_lock;
} }
}
#endif /* CONFIG_NET_SEND_BUFSIZE */
/* Initialize the write buffer /* Allocate a write buffer. Careful, the network will be momentarily
* * unlocked here.
* Check if the socket is connected */
*/
if (_SS_ISCONNECTED(conn->sconn.s_flags)) #ifdef CONFIG_NET_JUMBO_FRAME
/* alloc iob of gso pkt for udp data */
wrb = udp_wrbuffer_tryalloc(len + udpip_hdrsize(conn) +
CONFIG_NET_LL_GUARDSIZE);
#else
if (nonblock)
{
wrb = udp_wrbuffer_tryalloc();
}
else
{
wrb = udp_wrbuffer_timedalloc(udp_send_gettimeout(start, timeout));
}
#endif
if (wrb == NULL)
{
/* A buffer allocation error occurred */
nerr("ERROR: Failed to allocate write buffer\n");
if (nonblock || timeout != UINT_MAX)
{ {
/* Yes.. get the connection address from the connection structure */ ret = -EAGAIN;
}
else
{
ret = -ENOMEM;
}
goto errout_with_lock;
}
/* Initialize the write buffer
*
* Check if the socket is connected
*/
if (_SS_ISCONNECTED(conn->sconn.s_flags))
{
/* Yes.. get the connection address from the connection structure */
#ifdef CONFIG_NET_IPv4 #ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_IPv6 #ifdef CONFIG_NET_IPv6
if (conn->domain == PF_INET) if (conn->domain == PF_INET)
#endif #endif
{ {
FAR struct sockaddr_in *addr4 = FAR struct sockaddr_in *addr4 =
(FAR struct sockaddr_in *)&wrb->wb_dest; (FAR struct sockaddr_in *)&wrb->wb_dest;
addr4->sin_family = AF_INET; addr4->sin_family = AF_INET;
addr4->sin_port = conn->rport; addr4->sin_port = conn->rport;
net_ipv4addr_copy(addr4->sin_addr.s_addr, conn->u.ipv4.raddr); net_ipv4addr_copy(addr4->sin_addr.s_addr, conn->u.ipv4.raddr);
memset(addr4->sin_zero, 0, sizeof(addr4->sin_zero)); memset(addr4->sin_zero, 0, sizeof(addr4->sin_zero));
} }
#endif /* CONFIG_NET_IPv4 */ #endif /* CONFIG_NET_IPv4 */
#ifdef CONFIG_NET_IPv6 #ifdef CONFIG_NET_IPv6
#ifdef CONFIG_NET_IPv4 #ifdef CONFIG_NET_IPv4
else
#endif
{
FAR struct sockaddr_in6 *addr6 =
(FAR struct sockaddr_in6 *)&wrb->wb_dest;
addr6->sin6_family = AF_INET6;
addr6->sin6_port = conn->rport;
net_ipv6addr_copy(addr6->sin6_addr.s6_addr,
conn->u.ipv6.raddr);
}
#endif /* CONFIG_NET_IPv6 */
}
/* Not connected. Use the provided destination address */
else else
#endif
{ {
memcpy(&wrb->wb_dest, to, tolen); FAR struct sockaddr_in6 *addr6 =
udp_connect(conn, to); (FAR struct sockaddr_in6 *)&wrb->wb_dest;
addr6->sin6_family = AF_INET6;
addr6->sin6_port = conn->rport;
net_ipv6addr_copy(addr6->sin6_addr.s6_addr, conn->u.ipv6.raddr);
} }
#endif /* CONFIG_NET_IPv6 */
}
/* Skip l2/l3/l4 offset before copy */ /* Not connected. Use the provided destination address */
udpiplen = udpip_hdrsize(conn); else
{
memcpy(&wrb->wb_dest, to, tolen);
udp_connect(conn, to);
}
iob_reserve(wrb->wb_iob, CONFIG_NET_LL_GUARDSIZE); /* Skip l2/l3/l4 offset before copy */
iob_update_pktlen(wrb->wb_iob, udpiplen, false);
/* Copy the user data into the write buffer. We cannot wait for udpiplen = udpip_hdrsize(conn);
* buffer space if the socket was opened non-blocking.
*/
iob_reserve(wrb->wb_iob, CONFIG_NET_LL_GUARDSIZE);
iob_update_pktlen(wrb->wb_iob, udpiplen, false);
/* Copy the user data into the write buffer. We cannot wait for
* buffer space if the socket was opened non-blocking.
*/
if (len > 0)
{
if (nonblock) if (nonblock)
{ {
ret = iob_trycopyin(wrb->wb_iob, (FAR uint8_t *)buf, ret = iob_trycopyin(wrb->wb_iob, (FAR uint8_t *)buf,
@@ -845,48 +843,48 @@ ssize_t psock_udp_sendto(FAR struct socket *psock, FAR const void *buf,
{ {
goto errout_with_wrb; goto errout_with_wrb;
} }
}
/* Dump I/O buffer chain */ /* Dump I/O buffer chain */
UDP_WBDUMP("I/O buffer chain", wrb, wrb->wb_iob->io_pktlen, 0); UDP_WBDUMP("I/O buffer chain", wrb, wrb->wb_iob->io_pktlen, 0);
/* sendto_eventhandler() will send data in FIFO order from the /* sendto_eventhandler() will send data in FIFO order from the
* conn->write_q. * conn->write_q.
* *
* REVISIT: Why FIFO order? Because it is easy. In a real world * REVISIT: Why FIFO order? Because it is easy. In a real world
* environment where there are multiple network devices this might * environment where there are multiple network devices this might
* be inefficient because we could be sending data to different * be inefficient because we could be sending data to different
* device out-of-queued-order to optimize performance. Sending * device out-of-queued-order to optimize performance. Sending
* data to different networks from a single UDP socket is probably * data to different networks from a single UDP socket is probably
* not a very common use case, however. * not a very common use case, however.
*/
empty = sq_empty(&conn->write_q);
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);
if (empty)
{
/* The new write buffer lies at the head of the write queue. 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.
*/ */
empty = sq_empty(&conn->write_q); ret = sendto_next_transfer(conn);
if (ret < 0)
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);
if (empty)
{ {
/* The new write buffer lies at the head of the write queue. Set sq_remlast(&conn->write_q);
* up for the next packet transfer by setting the connection goto errout_with_wrb;
* address to the address of the next packet now at the header of
* the write buffer queue.
*/
ret = sendto_next_transfer(conn);
if (ret < 0)
{
sq_remlast(&conn->write_q);
goto errout_with_wrb;
}
} }
net_unlock();
} }
net_unlock();
/* Return the number of bytes that will be sent */ /* Return the number of bytes that will be sent */
return len; return len;
+21 -5
View File
@@ -197,12 +197,28 @@ static uint16_t sendto_eventhandler(FAR struct net_driver_s *dev,
{ {
/* Copy the user data into d_appdata and send it */ /* Copy the user data into d_appdata and send it */
int ret = devif_send(dev, pstate->st_buffer, pstate->st_buflen, if (pstate->st_buflen > 0)
udpip_hdrsize(pstate->st_conn));
if (ret <= 0)
{ {
pstate->st_sndlen = ret; int ret = devif_send(dev, pstate->st_buffer, pstate->st_buflen,
goto end_wait; udpip_hdrsize(pstate->st_conn));
if (ret <= 0)
{
pstate->st_sndlen = ret;
goto end_wait;
}
}
else
{
if (netdev_iob_prepare(dev, false, 0) != OK)
{
pstate->st_sndlen = -ENOMEM;
goto end_wait;
}
iob_update_pktlen(dev->d_iob, udpip_hdrsize(pstate->st_conn),
false);
dev->d_sndlen = 0;
dev->d_len = dev->d_iob->io_pktlen;
} }
#ifdef NEED_IPDOMAIN_SUPPORT #ifdef NEED_IPDOMAIN_SUPPORT