2008-01-17 Rodrigo Kumpera <rkumpera@novell.com>
[mono.git] / mono / metadata / socket-io.c
index 1728c4f02b32e8c9089d7fa95e40432b4ccc4c53..72888dee513a4db4f760b96b7b9e38bcf5bb43c4 100644 (file)
@@ -14,7 +14,9 @@
 #include <glib.h>
 #include <string.h>
 #include <stdlib.h>
+#ifdef HAVE_UNISTD_H
 #include <unistd.h>
+#endif
 #include <errno.h>
 
 #include <mono/metadata/object.h>
 /* FIXME change this code to not mess so much with the internals */
 #include <mono/metadata/class-internals.h>
 #include <mono/metadata/threadpool-internals.h>
+#include <mono/metadata/domain-internals.h>
 
-#include <sys/time.h> 
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
 #ifdef HAVE_SYS_IOCTL_H
 #include <sys/ioctl.h>
 #endif
@@ -296,6 +301,12 @@ static gint32 convert_socketflags (gint32 sflags)
        return (flags ? flags : -1);
 }
 
+/*
+ * Returns:
+ *    0 on success (mapped mono_level and mono_name to system_level and system_name
+ *   -1 on error
+ *   -2 on non-fatal error (ie, must ignore)
+ */
 static gint32 convert_sockopt_level_and_name(MonoSocketOptionLevel mono_level,
                                             MonoSocketOptionName mono_name,
                                             int *system_level,
@@ -439,7 +450,19 @@ static gint32 convert_sockopt_level_and_name(MonoSocketOptionLevel mono_level,
                        *system_name = IP_PKTINFO;
                        break;
 #endif /* HAVE_IP_PKTINFO */
+
                case SocketOptionName_DontFragment:
+#ifdef HAVE_IP_DONTFRAGMENT
+                       *system_name = IP_DONTFRAGMENT;
+                       break;
+#elif defined HAVE_IP_MTU_DISCOVER
+                       /* Not quite the same */
+                       *system_name = IP_MTU_DISCOVER;
+                       break;
+#else
+                       /* If the flag is not available on this system, we can ignore this error */
+                       return (-2);
+#endif /* HAVE_IP_DONTFRAGMENT */
                case SocketOptionName_AddSourceMembership:
                case SocketOptionName_DropSourceMembership:
                case SocketOptionName_BlockSource:
@@ -669,33 +692,6 @@ gpointer ves_icall_System_Net_Sockets_Socket_Socket_internal(MonoObject *this, g
        }
 #endif
 
-#ifndef PLATFORM_WIN32
-       /* .net seems to set this by default for SOCK_STREAM,
-        * not for SOCK_DGRAM (see bug #36322)
-        *
-        * It seems winsock has a rather different idea of what
-        * SO_REUSEADDR means.  If it's set, then a new socket can be
-        * bound over an existing listening socket.  There's a new
-        * windows-specific option called SO_EXCLUSIVEADDRUSE but
-        * using that means the socket MUST be closed properly, or a
-        * denial of service can occur.  Luckily for us, winsock
-        * behaves as though any other system would when SO_REUSEADDR
-        * is true, so we don't need to do anything else here.  See
-        * bug 53992.
-        */
-       {
-       int ret, true = 1;
-       
-       ret = _wapi_setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &true, sizeof (true));
-       if(ret==SOCKET_ERROR) {
-               *error = WSAGetLastError ();
-               
-               closesocket(sock);
-               return(NULL);
-       }
-       }
-#endif
-       
        return(GUINT_TO_POINTER (sock));
 }
 
@@ -827,7 +823,7 @@ static MonoObject *create_object_from_sockaddr(struct sockaddr *saddr,
        if (saddr->sa_family == AF_UNIX) {
                /* sa_len includes the entire sockaddr size, so we don't need the
                 * N bytes (sizeof (unsigned short)) of the family. */
-               data=mono_array_new(domain, mono_get_byte_class (), sa_size - 2);
+               data=mono_array_new(domain, mono_get_byte_class (), sa_size);
        } else
 #endif
        {
@@ -1059,7 +1055,7 @@ static struct sockaddr *create_sockaddr_from_object(MonoObject *saddr_obj,
                struct sockaddr_un *sock_un;
                int i;
 
-               /* Need a byte for the '\0' terminator, and the first
+               /* Need a byte for the '\0' terminator/prefix, and the first
                 * two bytes hold the SocketAddress family
                 */
                if (len - 2 >= MONO_SIZEOF_SUNPATH) {
@@ -1074,8 +1070,7 @@ static struct sockaddr *create_sockaddr_from_object(MonoObject *saddr_obj,
                                                                i + 2);
                }
                
-               sock_un->sun_path [len - 2] = '\0';
-               *sa_size = sizeof (struct sockaddr_un);
+               *sa_size = len;
 
                return (struct sockaddr *)sock_un;
 #endif
@@ -1122,52 +1117,75 @@ MonoBoolean
 ves_icall_System_Net_Sockets_Socket_Poll_internal (SOCKET sock, gint mode,
                                                   gint timeout, gint32 *error)
 {
-       fd_set fds;
-       int ret = 0;
-       struct timeval tv;
-       struct timeval *tvptr;
-       div_t divvy;
+       MonoThread *thread = NULL;
+       mono_pollfd *pfds;
+       int ret;
        time_t start;
+       
 
        MONO_ARCH_SAVE_REGS;
+       
+       pfds = g_new0 (mono_pollfd, 1);
+       pfds[0].fd = GPOINTER_TO_INT (sock);
+       pfds[0].events = (mode == SelectModeRead) ? MONO_POLLIN :
+               (mode == SelectModeWrite) ? MONO_POLLOUT :
+               (MONO_POLLERR | MONO_POLLHUP | MONO_POLLNVAL);
 
+       timeout = (timeout >= 0) ? (timeout / 1000) : -1;
        start = time (NULL);
        do {
                *error = 0;
-               FD_ZERO (&fds);
-               _wapi_FD_SET (sock, &fds);
-               if (timeout >= 0) {
-                       divvy = div (timeout, 1000000);
-                       tv.tv_sec = divvy.quot;
-                       tv.tv_usec = divvy.rem;
-                       tvptr = &tv;
-               } else {
-                       tvptr = NULL;
-               }
-
-               if (mode == SelectModeRead) {
-                       ret = _wapi_select (0, &fds, NULL, NULL, tvptr);
-               } else if (mode == SelectModeWrite) {
-                       ret = _wapi_select (0, NULL, &fds, NULL, tvptr);
-               } else if (mode == SelectModeError) {
-                       ret = _wapi_select (0, NULL, NULL, &fds, tvptr);
-               } else {
-                       g_assert_not_reached ();
-               }
-
+               
+               ret = mono_poll (pfds, 1, timeout);
                if (timeout > 0 && ret < 0) {
                        int err = errno;
                        int sec = time (NULL) - start;
-
+                       
                        timeout -= sec * 1000;
-                       if (timeout < 0)
+                       if (timeout < 0) {
                                timeout = 0;
+                       }
+                       
                        errno = err;
                }
+               
+               if (ret == -1 && errno == EINTR) {
+                       int leave = 0;
+
+                       if (thread == NULL) {
+                               thread = mono_thread_current ();
+                       }
+                       
+                       leave = mono_thread_test_state (thread, ThreadState_AbortRequested | ThreadState_StopRequested);
+                       
+                       if (leave != 0) {
+                               g_free (pfds);
+                               return(FALSE);
+                       } else {
+                               /* Suspend requested? */
+                               mono_thread_interruption_checkpoint ();
+                       }
+                       errno = EINTR;
+               }
+       } while (ret == -1 && errno == EINTR);
 
-       } while ((ret == SOCKET_ERROR) && (*error == WSAGetLastError ()) == WSAEINTR);
+       if (ret == -1) {
+#ifdef PLATFORM_WIN32
+               *error = WSAGetLastError ();
+#else
+               *error = errno_to_WSA (errno, __func__);
+#endif
+               g_free (pfds);
+               return(FALSE);
+       }
+       
+       g_free (pfds);
 
-       return (ret != SOCKET_ERROR && _wapi_FD_ISSET (sock, &fds));
+       if (ret == 0) {
+               return(FALSE);
+       } else {
+               return (TRUE);
+       }
 }
 
 extern void ves_icall_System_Net_Sockets_Socket_Connect_internal(SOCKET sock, MonoObject *sockaddr, gint32 *error)
@@ -1197,6 +1215,92 @@ extern void ves_icall_System_Net_Sockets_Socket_Connect_internal(SOCKET sock, Mo
        g_free(sa);
 }
 
+/* These #defines from mswsock.h from wine.  Defining them here allows
+ * us to build this file on a mingw box that doesn't know the magic
+ * numbers, but still run on a newer windows box that does.
+ */
+#ifndef WSAID_DISCONNECTEX
+#define WSAID_DISCONNECTEX {0x7fda2e11,0x8630,0x436f,{0xa0, 0x31, 0xf5, 0x36, 0xa6, 0xee, 0xc1, 0x57}}
+typedef BOOL (WINAPI *LPFN_DISCONNECTEX)(SOCKET, LPOVERLAPPED, DWORD, DWORD);
+#endif
+
+#ifndef WSAID_TRANSMITFILE
+#define WSAID_TRANSMITFILE {0xb5367df0,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}}
+typedef BOOL (WINAPI *LPFN_TRANSMITFILE)(SOCKET, HANDLE, DWORD, DWORD, LPOVERLAPPED, LPTRANSMIT_FILE_BUFFERS, DWORD);
+#endif
+
+extern void ves_icall_System_Net_Sockets_Socket_Disconnect_internal(SOCKET sock, MonoBoolean reuse, gint32 *error)
+{
+       int ret;
+       glong output_bytes = 0;
+       GUID disco_guid = WSAID_DISCONNECTEX;
+       GUID trans_guid = WSAID_TRANSMITFILE;
+       LPFN_DISCONNECTEX _wapi_disconnectex = NULL;
+       LPFN_TRANSMITFILE _wapi_transmitfile = NULL;
+       gboolean bret;
+       
+       MONO_ARCH_SAVE_REGS;
+
+       *error = 0;
+       
+#ifdef DEBUG
+       g_message("%s: disconnecting from socket %p (reuse %d)", __func__,
+                 sock, reuse);
+#endif
+
+       /* I _think_ the extension function pointers need to be looked
+        * up for each socket.  FIXME: check the best way to store
+        * pointers to functions in managed objects that still works
+        * on 64bit platforms.
+        */
+       ret = WSAIoctl (sock, SIO_GET_EXTENSION_FUNCTION_POINTER,
+                       (void *)&disco_guid, sizeof(GUID),
+                       (void *)&_wapi_disconnectex, sizeof(void *),
+                       &output_bytes, NULL, NULL);
+       if (ret != 0) {
+               /* make sure that WSAIoctl didn't put crap in the
+                * output pointer
+                */
+               _wapi_disconnectex = NULL;
+
+               /* Look up the TransmitFile extension function pointer
+                * instead of calling TransmitFile() directly, because
+                * apparently "Several of the extension functions have
+                * been available since WinSock 1.1 and are exported
+                * from MSWsock.dll, however it's not advisable to
+                * link directly to this dll as this ties you to the
+                * Microsoft WinSock provider. A provider neutral way
+                * of accessing these extension functions is to load
+                * them dynamically via WSAIoctl using the
+                * SIO_GET_EXTENSION_FUNCTION_POINTER op code. This
+                * should, theoretically, allow you to access these
+                * functions from any provider that supports them..." 
+                * (http://www.codeproject.com/internet/jbsocketserver3.asp)
+                */
+               ret = WSAIoctl (sock, SIO_GET_EXTENSION_FUNCTION_POINTER,
+                               (void *)&trans_guid, sizeof(GUID),
+                               (void *)&_wapi_transmitfile, sizeof(void *),
+                               &output_bytes, NULL, NULL);
+               if (ret != 0) {
+                       _wapi_transmitfile = NULL;
+               }
+       }
+
+       if (_wapi_disconnectex != NULL) {
+               bret = _wapi_disconnectex (sock, NULL, TF_REUSE_SOCKET, 0);
+       } else if (_wapi_transmitfile != NULL) {
+               bret = _wapi_transmitfile (sock, NULL, 0, 0, NULL, NULL,
+                                          TF_DISCONNECT | TF_REUSE_SOCKET);
+       } else {
+               *error = ERROR_NOT_SUPPORTED;
+               return;
+       }
+
+       if (bret == FALSE) {
+               *error = WSAGetLastError ();
+       }
+}
+
 gint32 ves_icall_System_Net_Sockets_Socket_Receive_internal(SOCKET sock, MonoArray *buffer, gint32 offset, gint32 count, gint32 flags, gint32 *error)
 {
        int ret;
@@ -1443,10 +1547,8 @@ void ves_icall_System_Net_Sockets_Socket_Select_internal(MonoArray **sockets, gi
                        if (thread == NULL)
                                thread = mono_thread_current ();
 
-                       mono_monitor_enter (thread->synch_lock);
-                       leave = ((thread->state & ThreadState_AbortRequested) != 0 || 
-                                (thread->state & ThreadState_StopRequested) != 0);
-                       mono_monitor_exit (thread->synch_lock);
+                       leave = mono_thread_test_state (thread, ThreadState_AbortRequested | ThreadState_StopRequested);
+                       
                        if (leave != 0) {
                                g_free (pfds);
                                *sockets = NULL;
@@ -1544,6 +1646,10 @@ void ves_icall_System_Net_Sockets_Socket_GetSocketOption_obj_internal(SOCKET soc
                *error = WSAENOPROTOOPT;
                return;
        }
+       if (ret == -2) {
+               *obj_val = int_to_object (domain, 0);
+               return;
+       }
        
        /* No need to deal with MulticastOption names here, because
         * you cant getsockopt AddMembership or DropMembership (the
@@ -1666,6 +1772,8 @@ void ves_icall_System_Net_Sockets_Socket_GetSocketOption_arr_internal(SOCKET soc
                *error = WSAENOPROTOOPT;
                return;
        }
+       if(ret==-2)
+               return;
 
        valsize=mono_array_length(*byte_val);
        buf=mono_array_addr(*byte_val, guchar, 0);
@@ -1686,10 +1794,9 @@ static struct in_addr ipaddress_to_struct_in_addr(MonoObject *ipaddr)
 
        /* No idea why .net uses a 64bit type to hold a 32bit value...
         *
-        * Internal value of IPAddess is in Network Order, there is no need
-        * to call htonl here.
+        * Internal value of IPAddess is in little-endian order
         */
-       inaddr.s_addr=(guint32)*(guint64 *)(((char *)ipaddr)+field->offset);
+       inaddr.s_addr=GUINT_FROM_LE ((guint32)*(guint64 *)(((char *)ipaddr)+field->offset));
        
        return(inaddr);
 }
@@ -1761,6 +1868,9 @@ void ves_icall_System_Net_Sockets_Socket_SetSocketOption_internal(SOCKET sock, g
                *error = WSAENOPROTOOPT;
                return;
        }
+       if(ret==-2){
+               return;
+       }
 
        /* Only one of obj_val, byte_val or int_val has data */
        if(obj_val!=NULL) {
@@ -1874,7 +1984,21 @@ void ves_icall_System_Net_Sockets_Socket_SetSocketOption_internal(SOCKET sock, g
                }
        } else {
                /* ReceiveTimeout/SendTimeout get here */
-               ret = _wapi_setsockopt (sock, system_level, system_name, (char *) &int_val, sizeof (int_val));
+               switch(name) {
+               case SocketOptionName_DontFragment:
+#ifdef HAVE_IP_MTU_DISCOVER
+                       /* Fiddle with the value slightly if we're
+                        * turning DF on
+                        */
+                       if (int_val == 1) {
+                               int_val = IP_PMTUDISC_DO;
+                       }
+                       /* Fall through */
+#endif
+                       
+               default:
+                       ret = _wapi_setsockopt (sock, system_level, system_name, (char *) &int_val, sizeof (int_val));
+               }
        }
 
        if(ret==SOCKET_ERROR) {
@@ -2133,6 +2257,20 @@ static gboolean hostent_to_IPHostEntry(struct hostent *he, MonoString **h_name,
 
        return(TRUE);
 }
+
+static gboolean ipaddr_to_IPHostEntry(const char *addr, MonoString **h_name,
+                                     MonoArray **h_aliases,
+                                     MonoArray **h_addr_list)
+{
+       MonoDomain *domain = mono_domain_get ();
+
+       *h_name=mono_string_new(domain, addr);
+       *h_aliases=mono_array_new(domain, mono_get_string_class (), 0);
+       *h_addr_list=mono_array_new(domain, mono_get_string_class (), 1);
+       mono_array_setref (*h_addr_list, 0, *h_name);
+
+       return(TRUE);
+}
 #endif
 
 #if defined(AF_INET6) && defined(HAVE_GETHOSTBYNAME2_R)
@@ -2539,7 +2677,7 @@ MonoBoolean ves_icall_System_Net_Dns_GetHostByName_internal(MonoString *host, Mo
        char *hostname;
        gboolean add_local_ips = FALSE;
 #ifdef HAVE_SIOCGIFCONF
-       guchar this_hostname [256];
+       gchar this_hostname [256];
 #endif
        
        MONO_ARCH_SAVE_REGS;
@@ -2605,20 +2743,27 @@ inet_pton (int family, const char *address, void *inaddrp)
 extern MonoBoolean ves_icall_System_Net_Dns_GetHostByAddr_internal(MonoString *addr, MonoString **h_name, MonoArray **h_aliases, MonoArray **h_addr_list)
 {
        char *address;
-
+       const char *version;
+       gboolean v1;
+       
 #ifdef AF_INET6
        struct sockaddr_in saddr;
        struct sockaddr_in6 saddr6;
        struct addrinfo *info = NULL, hints;
        gint32 family;
        char hostname[1024] = {0};
+       int flags = 0;
 #else
        struct in_addr inaddr;
        struct hostent *he;
+       gboolean ret;
 #endif
 
        MONO_ARCH_SAVE_REGS;
 
+       version = mono_get_runtime_info ()->framework_version;
+       v1 = (version[0] == '1');
+
        address = mono_string_to_utf8 (addr);
 
 #ifdef AF_INET6
@@ -2639,16 +2784,26 @@ extern MonoBoolean ves_icall_System_Net_Dns_GetHostByAddr_internal(MonoString *a
        }
        g_free(address);
 
+       if (v1) {
+               flags = NI_NAMEREQD;
+       }
+       
        if(family == AF_INET) {
+#if HAVE_SOCKADDR_IN_SIN_LEN
+               saddr.sin_len = sizeof (saddr);
+#endif
                if(getnameinfo ((struct sockaddr*)&saddr, sizeof(saddr),
                                hostname, sizeof(hostname), NULL, 0,
-                               NI_NAMEREQD) != 0) {
+                               flags) != 0) {
                        return(FALSE);
                }
        } else if(family == AF_INET6) {
+#if HAVE_SOCKADDR_IN6_SIN_LEN
+               saddr6.sin6_len = sizeof (saddr6);
+#endif
                if(getnameinfo ((struct sockaddr*)&saddr6, sizeof(saddr6),
                                hostname, sizeof(hostname), NULL, 0,
-                               NI_NAMEREQD) != 0) {
+                               flags) != 0) {
                        return(FALSE);
                }
        }
@@ -2668,13 +2823,21 @@ extern MonoBoolean ves_icall_System_Net_Dns_GetHostByAddr_internal(MonoString *a
                g_free (address);
                return(FALSE);
        }
-       g_free (address);
 
        if ((he = gethostbyaddr ((char *) &inaddr, sizeof (inaddr), AF_INET)) == NULL) {
-               return(FALSE);
+               if (v1) {
+                       ret = FALSE;
+               } else {
+                       ret = ipaddr_to_IPHostEntry (address, h_name,
+                                                    h_aliases, h_addr_list);
+               }
+       } else {
+               ret = hostent_to_IPHostEntry (he, h_name, h_aliases,
+                                             h_addr_list, FALSE);
        }
 
-       return(hostent_to_IPHostEntry (he, h_name, h_aliases, h_addr_list, FALSE));
+       g_free (address);
+       return(ret);
 #endif
 }