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

@ -41,6 +41,7 @@ __set_errno (const char *fn, int ln, int val)
}
#define set_errno(val) __set_errno (__PRETTY_FUNCTION__, __LINE__, (val))
int find_winsock_errno (DWORD why);
void __reg2 __set_winsock_errno (const char *fn, int ln);
#define set_winsock_errno() __set_winsock_errno (__FUNCTION__, __LINE__)

View File

@ -590,6 +590,10 @@ class fhandler_socket: public fhandler_base
int getpeereid (pid_t *pid, uid_t *euid, gid_t *egid);
int socketpair (int af, int type, int protocol, int flags,
fhandler_socket *fh_out);
int setsockopt (int level, int optname, const void *optval,
__socklen_t optlen);
int getsockopt (int level, int optname, const void *optval,
__socklen_t *optlen);
int open (int flags, mode_t mode = 0);
void __reg3 read (void *ptr, size_t& len);

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;
}

View File

@ -198,7 +198,7 @@ static const errmap_t wsock_errmap[] = {
{0, NULL, 0}
};
static int
int
find_winsock_errno (DWORD why)
{
for (int i = 0; wsock_errmap[i].s != NULL; ++i)
@ -762,194 +762,24 @@ cygwin_recvfrom (int fd, void *buf, size_t len, int flags,
return res;
}
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];
}
/* exported as setsockopt: POSIX.1-2001, POSIX.1-2008, SVr4, 4.4BSD */
extern "C" int
cygwin_setsockopt (int fd, int level, int optname, const void *optval,
socklen_t optlen)
{
bool ignore = false;
int res = -1;
int ret = -1;
__try
{
fhandler_socket *fh = get (fd);
if (!fh)
__leave;
/* 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
res = fh->af_local_set_no_getpeereid ();
__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 (optlen < (socklen_t) sizeof (int))
{
set_errno (EINVAL);
__leave;
}
if (fh->get_socket_type () == SOCK_STREAM)
ignore = true;
break;
case SO_RCVTIMEO:
case SO_SNDTIMEO:
if (optlen < (socklen_t) sizeof (struct timeval))
{
set_errno (EINVAL);
__leave;
}
if (timeval_to_ms ((struct timeval *) optval,
(optname == SO_RCVTIMEO)
? fh->rcvtimeo () : fh->sndtimeo ()))
res = 0;
else
set_errno (EDOM);
__leave;
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 setsockopt (or not) */
if (ignore)
res = 0;
else
{
res = setsockopt (fh->get_socket (), level, optname,
(const char *) optval, optlen);
if (res == SOCKET_ERROR)
{
set_winsock_errno ();
__leave;
}
}
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:
fh->saw_reuseaddr (*(int *) optval);
break;
case SO_RCVBUF:
fh->rmem (*(int *) optval);
break;
case SO_SNDBUF:
fh->wmem (*(int *) optval);
break;
default:
break;
}
break;
default:
break;
}
}
__except (EFAULT)
{
res = -1;
if (fh)
ret = fh->setsockopt (level, optname, optval, optlen);
}
__except (EFAULT) {}
__endtry
syscall_printf ("%R = setsockopt(%d, %d, %y, %p, %d)",
res, fd, level, optname, optval, optlen);
return res;
ret, fd, level, optname, optval, optlen);
return ret;
}
/* exported as getsockopt: POSIX.1-2001, POSIX.1-2008, SVr4, 4.4BSD */
@ -957,170 +787,19 @@ extern "C" int
cygwin_getsockopt (int fd, int level, int optname, void *optval,
socklen_t *optlen)
{
bool ignore = false;
bool onebyte = false;
int res = -1;
int ret = -1;
__try
{
fhandler_socket *fh = get (fd);
if (!fh)
__leave;
/* 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);
__leave;
}
res = fh->getpeereid (&cred->pid, &cred->uid, &cred->gid);
if (!res)
*optlen = (socklen_t) sizeof *cred;
__leave;
}
break;
case SO_REUSEADDR:
{
unsigned int *reuseaddr = (unsigned int *) optval;
if (*optlen < (socklen_t) sizeof *reuseaddr)
{
set_errno (EINVAL);
__leave;
}
*reuseaddr = fh->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);
__leave;
}
DWORD ms = (optname == SO_RCVTIMEO) ? fh->rcvtimeo ()
: fh->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;
res = 0;
__leave;
}
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 getsockopt (or not) */
if (ignore)
res = 0;
else
{
res = getsockopt (fh->get_socket (), level, optname, (char *) optval,
(int *) optlen);
if (res == SOCKET_ERROR)
{
set_winsock_errno ();
__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;
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;
}
}
__except (EFAULT)
{
res = -1;
if (fh)
ret = fh->getsockopt (level, optname, optval, optlen);
}
__except (EFAULT) {}
__endtry
syscall_printf ("%R = getsockopt(%d, %d, %y, %p, %p)",
res, fd, level, optname, optval, optlen);
return res;
ret, fd, level, optname, optval, optlen);
return ret;
}
/* POSIX.1-2001 */