From ea1e5318d5479ca841beab601a4c3dd7cce627ad Mon Sep 17 00:00:00 2001 From: Corinna Vinschen Date: Tue, 20 Feb 2018 18:01:40 +0100 Subject: [PATCH] Cygwin: set/getsockopt: Move implementation into fhandler_socket class This requires to export find_winsock_errno from net.cc. Signed-off-by: Corinna Vinschen --- winsup/cygwin/cygerrno.h | 1 + winsup/cygwin/fhandler.h | 4 + winsup/cygwin/fhandler_socket.cc | 330 +++++++++++++++++++++++++++++ winsup/cygwin/net.cc | 347 ++----------------------------- 4 files changed, 348 insertions(+), 334 deletions(-) diff --git a/winsup/cygwin/cygerrno.h b/winsup/cygwin/cygerrno.h index afcae4cb0..009ae635a 100644 --- a/winsup/cygwin/cygerrno.h +++ b/winsup/cygwin/cygerrno.h @@ -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__) diff --git a/winsup/cygwin/fhandler.h b/winsup/cygwin/fhandler.h index ab6fb6e91..ce9d9246e 100644 --- a/winsup/cygwin/fhandler.h +++ b/winsup/cygwin/fhandler.h @@ -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); diff --git a/winsup/cygwin/fhandler_socket.cc b/winsup/cygwin/fhandler_socket.cc index a7c702e7e..26d4716b4 100644 --- a/winsup/cygwin/fhandler_socket.cc +++ b/winsup/cygwin/fhandler_socket.cc @@ -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; +} diff --git a/winsup/cygwin/net.cc b/winsup/cygwin/net.cc index 7d73790a6..dc81fb7de 100644 --- a/winsup/cygwin/net.cc +++ b/winsup/cygwin/net.cc @@ -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 */