Cygwin: setsockopt/getsockopt: Clean up code
Rearrange setsockopt/getsockopt into per level/per optname preprocessing switch, actual call, per level/per optname postprocessing switch for better readability as well as extensibility. Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
This commit is contained in:
parent
b08e6690a6
commit
f8ce691223
|
@ -797,6 +797,7 @@ extern "C" int
|
||||||
cygwin_setsockopt (int fd, int level, int optname, const void *optval,
|
cygwin_setsockopt (int fd, int level, int optname, const void *optval,
|
||||||
socklen_t optlen)
|
socklen_t optlen)
|
||||||
{
|
{
|
||||||
|
bool ignore = false;
|
||||||
int res = -1;
|
int res = -1;
|
||||||
|
|
||||||
__try
|
__try
|
||||||
|
@ -805,102 +806,127 @@ cygwin_setsockopt (int fd, int level, int optname, const void *optval,
|
||||||
if (!fh)
|
if (!fh)
|
||||||
__leave;
|
__leave;
|
||||||
|
|
||||||
/* Switch off the AF_LOCAL handshake and thus SO_PEERCRED handling
|
/* Preprocessing setsockopt. Set ignore to true if setsockopt call
|
||||||
for AF_LOCAL/SOCK_STREAM sockets. This allows to handle special
|
should get skipped entirely. */
|
||||||
situations in which connect is called before a listening socket
|
switch (level)
|
||||||
accepts connections.
|
|
||||||
FIXME: In the long run we should find a more generic solution which
|
|
||||||
doesn't require a blocking handshake in accept/connect to exchange
|
|
||||||
SO_PEERCRED credentials. */
|
|
||||||
if (level == SOL_SOCKET && optname == SO_PEERCRED)
|
|
||||||
{
|
{
|
||||||
|
case SOL_SOCKET:
|
||||||
|
switch (optname)
|
||||||
|
{
|
||||||
|
case SO_PEERCRED:
|
||||||
|
/* Switch off the AF_LOCAL handshake and thus SO_PEERCRED
|
||||||
|
handling for AF_LOCAL/SOCK_STREAM sockets. This allows to
|
||||||
|
handle special situations in which connect is called before
|
||||||
|
a listening socket accepts connections.
|
||||||
|
FIXME: In the long run we should find a more generic solution
|
||||||
|
which doesn't require a blocking handshake in accept/connect
|
||||||
|
to exchange SO_PEERCRED credentials. */
|
||||||
if (optval || optlen)
|
if (optval || optlen)
|
||||||
set_errno (EINVAL);
|
set_errno (EINVAL);
|
||||||
else
|
else
|
||||||
res = fh->af_local_set_no_getpeereid ();
|
res = fh->af_local_set_no_getpeereid ();
|
||||||
__leave;
|
__leave;
|
||||||
|
|
||||||
|
case SO_REUSEADDR:
|
||||||
|
/* Per POSIX we must not be able to reuse a complete duplicate
|
||||||
|
of a local TCP address (same IP, same port), even if
|
||||||
|
SO_REUSEADDR has been set. This behaviour is maintained in
|
||||||
|
WinSock for backward compatibility, while the WinSock
|
||||||
|
standard behaviour of stream socket binding is equivalent to
|
||||||
|
the POSIX behaviour as if SO_REUSEADDR has been set.
|
||||||
|
The SO_EXCLUSIVEADDRUSE option has been added to allow an
|
||||||
|
application to request POSIX standard behaviour in the
|
||||||
|
non-SO_REUSEADDR case.
|
||||||
|
|
||||||
|
To emulate POSIX socket binding behaviour, note that
|
||||||
|
SO_REUSEADDR has been set but don't call setsockopt.
|
||||||
|
Instead fhandler_socket::bind sets SO_EXCLUSIVEADDRUSE if
|
||||||
|
the application did not set SO_REUSEADDR. */
|
||||||
|
if (fh->get_socket_type () == SOCK_STREAM)
|
||||||
|
ignore = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IPPROTO_IP:
|
||||||
|
/* Old applications still use the old WinSock1 IPPROTO_IP values. */
|
||||||
|
if (CYGWIN_VERSION_CHECK_FOR_USING_WINSOCK1_VALUES)
|
||||||
|
optname = convert_ws1_ip_optname (optname);
|
||||||
|
switch (optname)
|
||||||
|
{
|
||||||
|
case IP_TOS:
|
||||||
|
/* Winsock doesn't support setting the IP_TOS field with
|
||||||
|
setsockopt and TOS was never implemented for TCP anyway.
|
||||||
|
setsockopt returns WinSock error 10022, WSAEINVAL when
|
||||||
|
trying to set the IP_TOS field. We just return 0 instead. */
|
||||||
|
ignore = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IPPROTO_IPV6:
|
||||||
|
{
|
||||||
|
switch (optname)
|
||||||
|
{
|
||||||
|
case IPV6_TCLASS:
|
||||||
|
/* Unsupported */
|
||||||
|
ignore = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Old applications still use the old WinSock1 IPPROTO_IP values. */
|
/* Call setsockopt (or not) */
|
||||||
if (level == IPPROTO_IP && CYGWIN_VERSION_CHECK_FOR_USING_WINSOCK1_VALUES)
|
if (ignore)
|
||||||
optname = convert_ws1_ip_optname (optname);
|
|
||||||
|
|
||||||
/* Per POSIX we must not be able to reuse a complete duplicate of a
|
|
||||||
local TCP address (same IP, same port), even if SO_REUSEADDR has been
|
|
||||||
set. That's unfortunately possible in WinSock, and this has never
|
|
||||||
been changed to maintain backward compatibility. Instead, the
|
|
||||||
SO_EXCLUSIVEADDRUSE option has been added to allow an application to
|
|
||||||
request POSIX standard behaviour in the non-SO_REUSEADDR case.
|
|
||||||
|
|
||||||
However, the WinSock standard behaviour of stream socket binding
|
|
||||||
is equivalent to the POSIX behaviour as if SO_REUSEADDR has been set.
|
|
||||||
So what we do here is to note that SO_REUSEADDR has been set, but not
|
|
||||||
actually hand over the request to WinSock. This is tested in
|
|
||||||
fhandler_socket::bind(), so that SO_EXCLUSIVEADDRUSE can be set if
|
|
||||||
the application did not set SO_REUSEADDR. This should reflect the
|
|
||||||
POSIX socket binding behaviour as close as possible with WinSock. */
|
|
||||||
if (level == SOL_SOCKET && optname == SO_REUSEADDR
|
|
||||||
&& fh->get_socket_type () == SOCK_STREAM)
|
|
||||||
res = 0;
|
res = 0;
|
||||||
else
|
else
|
||||||
|
{
|
||||||
res = setsockopt (fh->get_socket (), level, optname,
|
res = setsockopt (fh->get_socket (), level, optname,
|
||||||
(const char *) optval, optlen);
|
(const char *) optval, optlen);
|
||||||
|
if (res == SOCKET_ERROR)
|
||||||
|
{
|
||||||
|
set_winsock_errno ();
|
||||||
|
__leave;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (optlen == sizeof (int))
|
if (optlen == sizeof (int))
|
||||||
syscall_printf ("setsockopt optval=%x", *(int *) optval);
|
debug_printf ("setsockopt optval=%x", *(int *) optval);
|
||||||
|
|
||||||
if (res)
|
/* Postprocessing setsockopt, setting fhandler_socket members, etc. */
|
||||||
|
switch (level)
|
||||||
{
|
{
|
||||||
/* KB 248611:
|
case SOL_SOCKET:
|
||||||
|
|
||||||
Windows 2000 and above don't support setting the IP_TOS field
|
|
||||||
with setsockopt. Additionally, TOS was always (also under 9x
|
|
||||||
and NT) only implemented for UDP and ICMP, never for TCP.
|
|
||||||
|
|
||||||
The difference is that beginning with Windows 2000 the
|
|
||||||
setsockopt call returns WinSock error 10022, WSAEINVAL when
|
|
||||||
trying to set the IP_TOS field, instead of just ignoring the
|
|
||||||
call. This is *not* explained in KB 248611, but only in KB
|
|
||||||
258978.
|
|
||||||
|
|
||||||
Either case, the official workaround is to add a new registry
|
|
||||||
DWORD value HKLM/System/CurrentControlSet/Services/Tcpip/...
|
|
||||||
... Parameters/DisableUserTOSSetting, set to 0, and reboot.
|
|
||||||
|
|
||||||
Sidenote: The reasoning for dropping ToS in Win2K is that ToS
|
|
||||||
per RFC 1349 is incompatible with DiffServ per RFC 2474/2475.
|
|
||||||
|
|
||||||
We just ignore the return value of setting IP_TOS entirely.
|
|
||||||
|
|
||||||
CV 2014-04-16: Same for IPV6_TCLASS
|
|
||||||
FIXME: Same for IPV6_RECVTCLASS? */
|
|
||||||
if (level == IPPROTO_IP && optname == IP_TOS
|
|
||||||
&& WSAGetLastError () == WSAEINVAL)
|
|
||||||
{
|
|
||||||
debug_printf ("Faked IP_TOS success");
|
|
||||||
res = 0;
|
|
||||||
}
|
|
||||||
else if (level == IPPROTO_IPV6 && optname == IPV6_TCLASS
|
|
||||||
&& WSAGetLastError () == WSAENOPROTOOPT)
|
|
||||||
{
|
|
||||||
debug_printf ("Faked IPV6_TCLASS success");
|
|
||||||
res = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
set_winsock_errno ();
|
|
||||||
}
|
|
||||||
else if (level == SOL_SOCKET)
|
|
||||||
switch (optname)
|
switch (optname)
|
||||||
{
|
{
|
||||||
case SO_REUSEADDR:
|
case SO_REUSEADDR:
|
||||||
fh->saw_reuseaddr (*(int *) optval);
|
fh->saw_reuseaddr (*(int *) optval);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SO_RCVBUF:
|
case SO_RCVBUF:
|
||||||
fh->rmem (*(int *) optval);
|
fh->rmem (*(int *) optval);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SO_SNDBUF:
|
case SO_SNDBUF:
|
||||||
fh->wmem (*(int *) optval);
|
fh->wmem (*(int *) optval);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -920,6 +946,8 @@ extern "C" int
|
||||||
cygwin_getsockopt (int fd, int level, int optname, void *optval,
|
cygwin_getsockopt (int fd, int level, int optname, void *optval,
|
||||||
socklen_t *optlen)
|
socklen_t *optlen)
|
||||||
{
|
{
|
||||||
|
bool ignore = false;
|
||||||
|
bool onebyte = false;
|
||||||
int res = -1;
|
int res = -1;
|
||||||
|
|
||||||
__try
|
__try
|
||||||
|
@ -927,51 +955,112 @@ cygwin_getsockopt (int fd, int level, int optname, void *optval,
|
||||||
fhandler_socket *fh = get (fd);
|
fhandler_socket *fh = get (fd);
|
||||||
if (!fh)
|
if (!fh)
|
||||||
__leave;
|
__leave;
|
||||||
if (optname == SO_PEERCRED && level == SOL_SOCKET)
|
|
||||||
|
/* Preprocessing getsockopt. Set ignore to true if getsockopt call
|
||||||
|
should get skipped entirely. */
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case SOL_SOCKET:
|
||||||
|
switch (optname)
|
||||||
|
{
|
||||||
|
case SO_PEERCRED:
|
||||||
{
|
{
|
||||||
struct ucred *cred = (struct ucred *) optval;
|
struct ucred *cred = (struct ucred *) optval;
|
||||||
res = fh->getpeereid (&cred->pid, &cred->uid, &cred->gid);
|
res = fh->getpeereid (&cred->pid, &cred->uid, &cred->gid);
|
||||||
__leave;
|
__leave;
|
||||||
}
|
}
|
||||||
else if (optname == SO_REUSEADDR && level == SOL_SOCKET)
|
break;
|
||||||
|
|
||||||
|
case SO_REUSEADDR:
|
||||||
{
|
{
|
||||||
unsigned int *reuseaddr = (unsigned int *) optval;
|
unsigned int *reuseaddr = (unsigned int *) optval;
|
||||||
*reuseaddr = fh->saw_reuseaddr();
|
*reuseaddr = fh->saw_reuseaddr();
|
||||||
*optlen = sizeof *reuseaddr;
|
*optlen = sizeof *reuseaddr;
|
||||||
res = 0;
|
ignore = true;
|
||||||
__leave;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IPPROTO_IP:
|
||||||
/* Old applications still use the old WinSock1 IPPROTO_IP values. */
|
/* Old applications still use the old WinSock1 IPPROTO_IP values. */
|
||||||
if (level == IPPROTO_IP && CYGWIN_VERSION_CHECK_FOR_USING_WINSOCK1_VALUES)
|
if (CYGWIN_VERSION_CHECK_FOR_USING_WINSOCK1_VALUES)
|
||||||
optname = convert_ws1_ip_optname (optname);
|
optname = convert_ws1_ip_optname (optname);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Call getsockopt (or not) */
|
||||||
|
if (ignore)
|
||||||
|
res = 0;
|
||||||
|
else
|
||||||
|
{
|
||||||
res = getsockopt (fh->get_socket (), level, optname, (char *) optval,
|
res = getsockopt (fh->get_socket (), level, optname, (char *) optval,
|
||||||
(int *) optlen);
|
(int *) optlen);
|
||||||
if (res == SOCKET_ERROR)
|
if (res == SOCKET_ERROR)
|
||||||
|
{
|
||||||
set_winsock_errno ();
|
set_winsock_errno ();
|
||||||
else if (level == SOL_SOCKET && optname == SO_ERROR)
|
__leave;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Postprocessing getsockopt, setting fhandler_socket members, etc.
|
||||||
|
Set onebyte to true for options returning a BOOLEAN instead of a
|
||||||
|
boolean DWORD. */
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case SOL_SOCKET:
|
||||||
|
switch (optname)
|
||||||
|
{
|
||||||
|
case SO_ERROR:
|
||||||
{
|
{
|
||||||
int *e = (int *) optval;
|
int *e = (int *) optval;
|
||||||
debug_printf ("WinSock SO_ERROR = %d", *e);
|
debug_printf ("WinSock SO_ERROR = %d", *e);
|
||||||
*e = find_winsock_errno (*e);
|
*e = find_winsock_errno (*e);
|
||||||
}
|
}
|
||||||
else if (*optlen == 1)
|
break;
|
||||||
|
|
||||||
|
case SO_KEEPALIVE:
|
||||||
|
case SO_DONTROUTE:
|
||||||
|
onebyte = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IPPROTO_TCP:
|
||||||
|
switch (optname)
|
||||||
|
{
|
||||||
|
case TCP_NODELAY:
|
||||||
|
onebyte = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onebyte)
|
||||||
{
|
{
|
||||||
/* Regression in Vista and later: instead of a 4 byte BOOL value,
|
/* Regression in Vista and later: instead of a 4 byte BOOL value,
|
||||||
a 1 byte BOOLEAN value is returned, in contrast to older systems
|
a 1 byte BOOLEAN value is returned, in contrast to older systems
|
||||||
and the documentation. Since an int type is expected by the
|
and the documentation. Since an int type is expected by the
|
||||||
calling application, we convert the result here. For some reason
|
calling application, we convert the result here. For some reason
|
||||||
only three BSD-compatible socket options seem to be affected. */
|
only three BSD-compatible socket options seem to be affected. */
|
||||||
if ((level == SOL_SOCKET
|
|
||||||
&& (optname == SO_KEEPALIVE || optname == SO_DONTROUTE))
|
|
||||||
|| (level == IPPROTO_TCP && optname == TCP_NODELAY))
|
|
||||||
{
|
|
||||||
BOOLEAN *in = (BOOLEAN *) optval;
|
BOOLEAN *in = (BOOLEAN *) optval;
|
||||||
int *out = (int *) optval;
|
int *out = (int *) optval;
|
||||||
*out = *in;
|
*out = *in;
|
||||||
*optlen = 4;
|
*optlen = 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
__except (EFAULT)
|
__except (EFAULT)
|
||||||
{
|
{
|
||||||
res = -1;
|
res = -1;
|
||||||
|
|
Loading…
Reference in New Issue