Cygwin: set/getsockopt: Move implementation into fhandler_socket class

This requires to export find_winsock_errno from net.cc.

Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
This commit is contained in:
Corinna Vinschen
2018-02-20 18:01:40 +01:00
parent 044ab77dcc
commit ea1e5318d5
4 changed files with 348 additions and 334 deletions

View File

@@ -2685,3 +2685,333 @@ fhandler_socket::getpeereid (pid_t *pid, uid_t *euid, gid_t *egid)
__endtry
return -1;
}
static int
convert_ws1_ip_optname (int optname)
{
static int ws2_optname[] =
{
0,
IP_OPTIONS,
IP_MULTICAST_IF,
IP_MULTICAST_TTL,
IP_MULTICAST_LOOP,
IP_ADD_MEMBERSHIP,
IP_DROP_MEMBERSHIP,
IP_TTL,
IP_TOS,
IP_DONTFRAGMENT
};
return (optname < 1 || optname > _WS1_IP_DONTFRAGMENT)
? optname
: ws2_optname[optname];
}
int
fhandler_socket::setsockopt (int level, int optname, const void *optval,
socklen_t optlen)
{
bool ignore = false;
int ret = -1;
/* Preprocessing setsockopt. Set ignore to true if setsockopt call should
get skipped entirely. */
switch (level)
{
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)
set_errno (EINVAL);
else
ret = af_local_set_no_getpeereid ();
return ret;
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 (optlen < (socklen_t) sizeof (int))
{
set_errno (EINVAL);
return ret;
}
if (get_socket_type () == SOCK_STREAM)
ignore = true;
break;
case SO_RCVTIMEO:
case SO_SNDTIMEO:
if (optlen < (socklen_t) sizeof (struct timeval))
{
set_errno (EINVAL);
return ret;
}
if (timeval_to_ms ((struct timeval *) optval,
(optname == SO_RCVTIMEO) ? rcvtimeo ()
: sndtimeo ()))
ret = 0;
else
set_errno (EDOM);
return ret;
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;
}
/* Call Winsock setsockopt (or not) */
if (ignore)
ret = 0;
else
{
ret = ::setsockopt (get_socket (), level, optname, (const char *) optval,
optlen);
if (ret == SOCKET_ERROR)
{
set_winsock_errno ();
return ret;
}
}
if (optlen == (socklen_t) sizeof (int))
debug_printf ("setsockopt optval=%x", *(int *) optval);
/* Postprocessing setsockopt, setting fhandler_socket members, etc. */
switch (level)
{
case SOL_SOCKET:
switch (optname)
{
case SO_REUSEADDR:
saw_reuseaddr (*(int *) optval);
break;
case SO_RCVBUF:
rmem (*(int *) optval);
break;
case SO_SNDBUF:
wmem (*(int *) optval);
break;
default:
break;
}
break;
default:
break;
}
return ret;
}
int
fhandler_socket::getsockopt (int level, int optname, const void *optval,
socklen_t *optlen)
{
bool ignore = false;
bool onebyte = false;
int ret = -1;
/* 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;
if (*optlen < (socklen_t) sizeof *cred)
{
set_errno (EINVAL);
return ret;
}
ret = getpeereid (&cred->pid, &cred->uid, &cred->gid);
if (!ret)
*optlen = (socklen_t) sizeof *cred;
return ret;
}
break;
case SO_REUSEADDR:
{
unsigned int *reuseaddr = (unsigned int *) optval;
if (*optlen < (socklen_t) sizeof *reuseaddr)
{
set_errno (EINVAL);
return ret;
}
*reuseaddr = saw_reuseaddr();
*optlen = (socklen_t) sizeof *reuseaddr;
ignore = true;
}
break;
case SO_RCVTIMEO:
case SO_SNDTIMEO:
{
struct timeval *time_out = (struct timeval *) optval;
if (*optlen < (socklen_t) sizeof *time_out)
{
set_errno (EINVAL);
return ret;
}
DWORD ms = (optname == SO_RCVTIMEO) ? rcvtimeo () : sndtimeo ();
if (ms == 0 || ms == INFINITE)
{
time_out->tv_sec = 0;
time_out->tv_usec = 0;
}
else
{
time_out->tv_sec = ms / MSPERSEC;
time_out->tv_usec = ((ms % MSPERSEC) * USPERSEC) / MSPERSEC;
}
*optlen = (socklen_t) sizeof *time_out;
ret = 0;
return ret;
}
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);
break;
default:
break;
}
/* Call Winsock getsockopt (or not) */
if (ignore)
ret = 0;
else
{
ret = ::getsockopt (get_socket (), level, optname, (char *) optval,
(int *) optlen);
if (ret == SOCKET_ERROR)
{
set_winsock_errno ();
return ret;
}
}
/* Postprocessing getsockopt, setting fhandler_socket members, etc. Set
onebyte true for options returning BOOLEAN instead of a boolean DWORD. */
switch (level)
{
case SOL_SOCKET:
switch (optname)
{
case SO_ERROR:
{
int *e = (int *) optval;
debug_printf ("WinSock SO_ERROR = %d", *e);
*e = find_winsock_errno (*e);
}
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, a
1 byte BOOLEAN value is returned, in contrast to older systems and
the documentation. Since an int type is expected by the calling
application, we convert the result here. For some reason only three
BSD-compatible socket options seem to be affected. */
BOOLEAN *in = (BOOLEAN *) optval;
int *out = (int *) optval;
*out = *in;
*optlen = 4;
}
return ret;
}