Cygwin: Implement GSO/GRO support

- getsockopt (SOL_UDP, UDP_SEGMENT)
- setsockopt (SOL_UDP, UDP_SEGMENT)
- getsockopt (SOL_UDP, UDP_GRO)
- setsockopt (SOL_UDP, UDP_GRO)
- sendmsg with SOL_UDP/UDP_SEGMENT control message
- recvmsg, convert Winsock UDP_COALESCED_INFO (DWORD) control message to
  Linux compatible SOL_UDP/UDP_GRO (uint16_t)
This commit is contained in:
Corinna Vinschen 2022-05-05 21:56:57 +02:00
parent 34a9570ff8
commit fb6346835e
2 changed files with 199 additions and 13 deletions

View File

@ -25,6 +25,7 @@
#include <w32api/mswsock.h>
#include <w32api/mstcpip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <unistd.h>
#include <asm/byteorder.h>
#include <sys/socket.h>
@ -38,6 +39,7 @@
#include "cygheap.h"
#include "shared_info.h"
#include "wininfo.h"
#include "tls_pbuf.h"
#define ASYNC_MASK (FD_READ|FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT)
#define EVENT_MASK (FD_READ|FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT|FD_CLOSE)
@ -1335,6 +1337,31 @@ fhandler_socket_wsock::recvmsg (struct msghdr *msg, int flags)
msg->msg_controllen = wsamsg.Control.len;
if (!CYGWIN_VERSION_CHECK_FOR_USING_ANCIENT_MSGHDR)
msg->msg_flags = wsamsg.dwFlags;
/* if a UDP_GRO packet is present, convert gso_size from Windows DWORD
to Linux-compatible uint16_t. We don't have to change the
msg_control block layout for that, assuming applications do as they
have been told and only use CMSG_FIRSTHDR/CMSG_NXTHDR/CMSG_DATA to
access control messages. The cmsghdr alignment saves our ass here! */
if (msg->msg_controllen && get_socket_type () == SOCK_DGRAM
&& (get_addr_family () == AF_INET || get_addr_family () == AF_INET6))
{
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR (msg);
cmsg;
cmsg = CMSG_NXTHDR (msg, cmsg))
{
if (cmsg->cmsg_level == SOL_UDP
&& cmsg->cmsg_type == UDP_GRO)
{
PDWORD gso_size_win = (PDWORD) CMSG_DATA(cmsg);
uint16_t *gso_size_cyg = (uint16_t *) CMSG_DATA(cmsg);
uint16_t gso_size = (uint16_t) *gso_size_win;
*gso_size_cyg = gso_size;
break;
}
}
}
}
return ret;
}
@ -1540,16 +1567,102 @@ fhandler_socket_inet::sendto (const void *in_ptr, size_t len, int flags,
}
ssize_t
fhandler_socket_inet::sendmsg (const struct msghdr *msg, int flags)
fhandler_socket_inet::sendmsg (const struct msghdr *in_msg, int flags)
{
struct sockaddr_storage sst;
int len = 0;
DWORD old_gso_size = MAXDWORD;
ssize_t ret;
/* Copy incoming msghdr into a local copy. We only access this from
here on. Thus, make sure not to manipulate user space data. */
struct msghdr local_msg = *in_msg;
struct msghdr *msg = &local_msg;
if (msg->msg_name
&& get_inet_addr_inet ((struct sockaddr *) msg->msg_name,
msg->msg_namelen, &sst, &len) == SOCKET_ERROR)
return SOCKET_ERROR;
/* Check for our optmem_max value */
if (msg->msg_controllen > NT_MAX_PATH)
{
set_errno (ENOBUFS);
return SOCKET_ERROR;
}
/* WSASendMsg is supported only for datagram and raw sockets. */
if (get_socket_type () != SOCK_DGRAM && get_socket_type () != SOCK_RAW)
msg->msg_controllen = 0;
/* If we actually have control data, copy it to local storage. Control
messages only handled by us have to be dropped from the msg_control
block, and we don't want to change user space data. */
tmp_pathbuf tp;
if (msg->msg_controllen)
{
void *local_cmsg = tp.c_get ();
memcpy (local_cmsg, msg->msg_control, msg->msg_controllen);
msg->msg_control = local_cmsg;
}
/* Check for control message we handle inside Cygwin. Right now this
only affects UDP sockets, so check here early. */
if (msg->msg_controllen && get_socket_type () == SOCK_DGRAM)
{
struct cmsghdr *cmsg;
bool dropped = false;
for (cmsg = CMSG_FIRSTHDR (msg);
cmsg;
cmsg = dropped ? cmsg : CMSG_NXTHDR (msg, cmsg))
{
dropped = false;
/* cmsg within bounds? */
if (cmsg->cmsg_len < sizeof (struct cmsghdr)
|| cmsg->cmsg_len > (size_t) msg->msg_controllen
- ((uintptr_t) cmsg
- (uintptr_t) msg->msg_control))
{
set_errno (EINVAL);
return SOCKET_ERROR;
}
/* UDP_SEGMENT? Override gso_size for this single sendmsg. */
if (cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_SEGMENT)
{
/* 16 bit unsigned, as on Linux */
DWORD gso_size = *(uint16_t *) CMSG_DATA(cmsg);
int size = sizeof old_gso_size;
/* Save the old gso_size and set the requested one. */
if (::getsockopt (get_socket (), IPPROTO_UDP, UDP_SEGMENT,
(char *) &old_gso_size, &size) == SOCKET_ERROR
|| ::setsockopt (get_socket (), IPPROTO_UDP, UDP_SEGMENT,
(char *) &gso_size, sizeof gso_size)
== SOCKET_ERROR)
{
set_winsock_errno ();
return SOCKET_ERROR;
}
/* Drop message from msgbuf, Windows doesn't know it. */
size_t cmsg_size = CMSG_ALIGN (cmsg->cmsg_len);
struct cmsghdr *cmsg_next = CMSG_NXTHDR (msg, cmsg);
if (cmsg_next)
memmove (cmsg, cmsg_next, (char *) msg->msg_control
+ msg->msg_controllen
- (char *) cmsg_next);
msg->msg_controllen -= cmsg_size;
dropped = true;
/* Avoid infinite loop */
if (msg->msg_controllen <= 0)
{
cmsg = NULL;
msg->msg_controllen = 0;
}
}
}
}
/* Copy over msg_iov into an equivalent WSABUF array. */
WSABUF wsabuf[msg->msg_iovlen];
WSABUF *wsaptr = wsabuf;
const struct iovec *iovptr = msg->msg_iov;
@ -1558,15 +1671,18 @@ fhandler_socket_inet::sendmsg (const struct msghdr *msg, int flags)
wsaptr->len = iovptr->iov_len;
(wsaptr++)->buf = (char *) (iovptr++)->iov_base;
}
/* Disappointing but true: Even if WSASendMsg is supported, it's only
supported for datagram and raw sockets. */
DWORD controllen = (DWORD) ((get_socket_type () == SOCK_STREAM)
? 0 : msg->msg_controllen);
/* Eventually copy over to a WSAMSG and call send_internal with that. */
WSAMSG wsamsg = { msg->msg_name ? (struct sockaddr *) &sst : NULL, len,
wsabuf, (DWORD) msg->msg_iovlen,
{ controllen, (char *) msg->msg_control },
{ (DWORD) msg->msg_controllen,
msg->msg_controllen ? (char *) msg->msg_control : NULL },
0 };
return send_internal (&wsamsg, flags);
ret = send_internal (&wsamsg, flags);
if (old_gso_size != MAXDWORD)
::setsockopt (get_socket (), IPPROTO_UDP, UDP_SEGMENT,
(char *) &old_gso_size, sizeof old_gso_size);
return ret;
}
ssize_t
@ -1681,7 +1797,7 @@ fhandler_socket_inet::setsockopt (int level, int optname, const void *optval,
{
bool ignore = false;
int ret = -1;
unsigned int timeout;
unsigned int winsock_val;
/* Preprocessing setsockopt. Set ignore to true if setsockopt call should
get skipped entirely. */
@ -1774,7 +1890,6 @@ fhandler_socket_inet::setsockopt (int level, int optname, const void *optval,
break;
case IPPROTO_IPV6:
{
switch (optname)
{
case IPV6_TCLASS:
@ -1785,8 +1900,6 @@ fhandler_socket_inet::setsockopt (int level, int optname, const void *optval,
default:
break;
}
}
default:
break;
case IPPROTO_TCP:
@ -1851,9 +1964,9 @@ fhandler_socket_inet::setsockopt (int level, int optname, const void *optval,
{
/* convert msecs to secs. Values < 1000 ms are converted to
0 secs, just as in WinSock. */
timeout = *(unsigned int *) optval / MSPERSEC;
winsock_val = *(unsigned int *) optval / MSPERSEC;
optname = TCP_MAXRT;
optval = (const void *) &timeout;
optval = (const void *) &winsock_val;
}
break;
@ -1918,6 +2031,49 @@ fhandler_socket_inet::setsockopt (int level, int optname, const void *optval,
break;
}
break;
case IPPROTO_UDP:
/* Check for dgram socket early on, so we don't have to do this for
every option. Also, WinSock returns EINVAL. */
if (type != SOCK_DGRAM)
{
set_errno (EOPNOTSUPP);
return -1;
}
if (optlen < (socklen_t) sizeof (int))
{
set_errno (EINVAL);
return ret;
}
switch (optname)
{
case UDP_SEGMENT:
if (*(int *) optval < 0 || *(int *) optval > USHRT_MAX)
{
set_errno (EINVAL);
return -1;
}
break;
case UDP_GRO:
/* In contrast to Windows' UDP_RECV_MAX_COALESCED_SIZE option,
Linux' UDP_GRO option is just a bool. The max. packet size
is dynamically evaluated from the MRU. There's no easy,
reliable way to get the MRU. We assume that this is what Windows
will do internally anyway and, given UDP_RECV_MAX_COALESCED_SIZE
defines a *maximum* size for aggregated packages, we just choose
the maximum sensible value. FIXME? IP_MTU_DISCOVER / IP_MTU */
winsock_val = *(int *) optval ? USHRT_MAX : 0;
optval = &winsock_val;
break;
default:
break;
}
break;
default:
break;
}
/* Call Winsock setsockopt (or not) */
@ -2118,6 +2274,16 @@ fhandler_socket_inet::getsockopt (int level, int optname, const void *optval,
}
break;
case IPPROTO_UDP:
/* Check for dgram socket early on, so we don't have to do this for
every option. Also, WinSock returns EINVAL. */
if (type != SOCK_DGRAM)
{
set_errno (EOPNOTSUPP);
return -1;
}
break;
default:
break;
}
@ -2155,6 +2321,7 @@ fhandler_socket_inet::getsockopt (int level, int optname, const void *optval,
break;
}
break;
case IPPROTO_TCP:
switch (optname)
{
@ -2174,6 +2341,21 @@ fhandler_socket_inet::getsockopt (int level, int optname, const void *optval,
default:
break;
}
break;
case IPPROTO_UDP:
switch (optname)
{
case UDP_GRO:
/* Convert to bool option */
*(unsigned int *) optval = *(unsigned int *) optval ? 1 : 0;
break;
default:
break;
}
break;
default:
break;
}

View File

@ -33,6 +33,10 @@
#ifndef _NETINET_UDP_H
#define _NETINET_UDP_H
#define UDP_SEGMENT 2 /* WinSock UDP_SEND_MSG_SIZE */
#define UDP_GRO 3 /* WinSock UDP_RECV_MAX_COALESCED_SIZE,
also == UDP_COALESCED_INFO */
/*
* Udp protocol header.
* Per RFC 768, September, 1981.