From 6e2076c8e44a4063d98260f9f427977bb04d28f2 Mon Sep 17 00:00:00 2001 From: Matt Palmer Date: Wed, 11 Feb 2015 16:14:20 +1100 Subject: [PATCH 1/8] [PATCH] Make name2address take into account IPv6 addresses --- ext/em.cpp | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/ext/em.cpp b/ext/em.cpp index b8a32b686..9aaa3a1eb 100644 --- a/ext/em.cpp +++ b/ext/em.cpp @@ -20,6 +20,10 @@ See the file COPYING for complete licensing information. // THIS ENTIRE FILE WILL EVENTUALLY BE FOR UNIX BUILDS ONLY. //#ifdef OS_UNIX +#include +#include +#include + #include "project.h" /* The numer of max outstanding timers was once a const enum defined in em.h. @@ -1593,17 +1597,24 @@ struct sockaddr *name2address (const char *server, int port, int *family, int *b } #endif - struct hostent *hp; - hp = gethostbyname ((char*)server); // Windows requires the cast. - if (hp) { - in4.sin_addr.s_addr = ((in_addr*)(hp->h_addr))->s_addr; + + struct addrinfo *ai; + if (getaddrinfo (server, NULL, NULL, &ai) == 0) { if (family) - *family = AF_INET; + *family = ai->ai_family; if (bind_size) - *bind_size = sizeof(in4); - in4.sin_family = AF_INET; - in4.sin_port = htons (port); - return (struct sockaddr*)&in4; + *bind_size = ai->ai_addrlen; + + switch (ai->ai_family) { + case AF_INET: + memcpy(&in4, ai->ai_addr, ai->ai_addrlen); + in4.sin_port = htons(port); + return (struct sockaddr*)&in4; + case AF_INET6: + memcpy(&in6, ai->ai_addr, ai->ai_addrlen); + in6.sin6_port = htons(port); + return (struct sockaddr*)&in6; + } } return NULL; From c3abcda1dc8a9494642dc47d6e79130698c358ec Mon Sep 17 00:00:00 2001 From: Matt Palmer Date: Thu, 12 Feb 2015 10:52:16 +1100 Subject: [PATCH 2/8] [PATCH] Free struct addrinfo after use Missed this in the initial patch. Also avoid a compilation error on Cygwin due to not having IPv6 supported there. --- ext/em.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ext/em.cpp b/ext/em.cpp index 9aaa3a1eb..773a24fbc 100644 --- a/ext/em.cpp +++ b/ext/em.cpp @@ -1609,12 +1609,15 @@ struct sockaddr *name2address (const char *server, int port, int *family, int *b case AF_INET: memcpy(&in4, ai->ai_addr, ai->ai_addrlen); in4.sin_port = htons(port); - return (struct sockaddr*)&in4; +#ifndef CYGWIN case AF_INET6: memcpy(&in6, ai->ai_addr, ai->ai_addrlen); in6.sin6_port = htons(port); - return (struct sockaddr*)&in6; +#endif } + + freeaddrinfo(ai); + return (struct sockaddr*)&in4; } return NULL; From e4a87bc0ea0cef2f17b0598cebae5574d049e64e Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 6 Aug 2015 23:04:59 -0700 Subject: [PATCH 3/8] Use WSAStringToAddress in lieu of inet_pton for IPv6 address detection on Windows --- ext/em.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/em.cpp b/ext/em.cpp index 773a24fbc..fa6b14332 100644 --- a/ext/em.cpp +++ b/ext/em.cpp @@ -1597,7 +1597,6 @@ struct sockaddr *name2address (const char *server, int port, int *family, int *b } #endif - struct addrinfo *ai; if (getaddrinfo (server, NULL, NULL, &ai) == 0) { if (family) From 53d2c01bb61cc9aa9347c250606d578e44e62638 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 6 Aug 2015 23:06:48 -0700 Subject: [PATCH 4/8] Provide AF_UNSPEC hint to getaddrinfo and some cleanups --- ext/em.cpp | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/ext/em.cpp b/ext/em.cpp index fa6b14332..55de0c103 100644 --- a/ext/em.cpp +++ b/ext/em.cpp @@ -20,10 +20,6 @@ See the file COPYING for complete licensing information. // THIS ENTIRE FILE WILL EVENTUALLY BE FOR UNIX BUILDS ONLY. //#ifdef OS_UNIX -#include -#include -#include - #include "project.h" /* The numer of max outstanding timers was once a const enum defined in em.h. @@ -1597,26 +1593,37 @@ struct sockaddr *name2address (const char *server, int port, int *family, int *b } #endif + int ai_family; struct addrinfo *ai; - if (getaddrinfo (server, NULL, NULL, &ai) == 0) { + struct addrinfo hints; + memset (&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + if (getaddrinfo (server, NULL, &hints, &ai) == 0) { if (family) *family = ai->ai_family; if (bind_size) *bind_size = ai->ai_addrlen; - switch (ai->ai_family) { + ai_family = ai->ai_family; + switch (ai_family) { case AF_INET: - memcpy(&in4, ai->ai_addr, ai->ai_addrlen); + memcpy (&in4, ai->ai_addr, ai->ai_addrlen); in4.sin_port = htons(port); -#ifndef CYGWIN + #ifndef __CYGWIN__ case AF_INET6: - memcpy(&in6, ai->ai_addr, ai->ai_addrlen); + memcpy (&in6, ai->ai_addr, ai->ai_addrlen); in6.sin6_port = htons(port); -#endif + #endif } freeaddrinfo(ai); - return (struct sockaddr*)&in4; + + switch (ai_family) { + case AF_INET: + return (struct sockaddr*)&in4; + case AF_INET6: + return (struct sockaddr*)&in6; + } } return NULL; From 840d350dd30bae3f43def0f9029e89e44bda877c Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Wed, 21 Oct 2015 20:46:53 -0700 Subject: [PATCH 5/8] Switch name2address to use getaddrinfo for everything, with some auto_ptr --- ext/em.cpp | 100 +++++++++++--------------------------------------- ext/project.h | 1 + 2 files changed, 23 insertions(+), 78 deletions(-) diff --git a/ext/em.cpp b/ext/em.cpp index 55de0c103..7ef4e66aa 100644 --- a/ext/em.cpp +++ b/ext/em.cpp @@ -34,7 +34,7 @@ static unsigned int SimultaneousAcceptCount = 10; /* Internal helper to convert strings to internet addresses. IPv6-aware. - * Not reentrant or threadsafe, optimized for speed. + * Must free the return value. */ static struct sockaddr *name2address (const char *server, int port, int *family, int *bind_size); @@ -1196,13 +1196,12 @@ const uintptr_t EventMachine_t::ConnectToServer (const char *bind_addr, int bind throw std::runtime_error ("invalid server or port"); int family, bind_size; - struct sockaddr_storage bind_as, *bind_as_ptr = (struct sockaddr_storage*)name2address (server, port, &family, &bind_size); - if (!bind_as_ptr) { + const std::auto_ptr bind_as (name2address (server, port, &family, &bind_size)); + if (!bind_as.get()) { char buf [200]; snprintf (buf, sizeof(buf)-1, "unable to resolve server address: %s", strerror(errno)); throw std::runtime_error (buf); } - bind_as = *bind_as_ptr; // copy because name2address points to a static SOCKET sd = EmSocket (family, SOCK_STREAM, 0); if (sd == INVALID_SOCKET) { @@ -1225,12 +1224,12 @@ const uintptr_t EventMachine_t::ConnectToServer (const char *bind_addr, int bind if (bind_addr) { int bind_to_size, bind_to_family; - struct sockaddr *bind_to = name2address (bind_addr, bind_port, &bind_to_family, &bind_to_size); - if (!bind_to) { + const std::auto_ptr bind_to (name2address (bind_addr, bind_port, &bind_to_family, &bind_to_size)); + if (!bind_to.get()) { close (sd); throw std::runtime_error ("invalid bind address"); } - if (bind (sd, bind_to, bind_to_size) < 0) { + if (bind (sd, bind_to.get(), bind_to_size) < 0) { close (sd); throw std::runtime_error ("couldn't bind to address"); } @@ -1240,8 +1239,7 @@ const uintptr_t EventMachine_t::ConnectToServer (const char *bind_addr, int bind #ifdef OS_UNIX int e_reason = 0; - //if (connect (sd, (sockaddr*)&pin, sizeof pin) == 0) { - if (connect (sd, (struct sockaddr*)&bind_as, bind_size) == 0) { + if (connect (sd, bind_as.get(), bind_size) == 0) { // This is a connect success, which Linux appears // never to give when the socket is nonblocking, // even if the connection is intramachine or to @@ -1320,8 +1318,7 @@ const uintptr_t EventMachine_t::ConnectToServer (const char *bind_addr, int bind #endif #ifdef OS_WIN32 - //if (connect (sd, (sockaddr*)&pin, sizeof pin) == 0) { - if (connect (sd, &bind_as, bind_size) == 0) { + if (connect (sd, bind_as.get(), bind_size) == 0) { // This is a connect success, which Windows appears // never to give when the socket is nonblocking, // even if the connection is intramachine or to @@ -1547,83 +1544,31 @@ name2address struct sockaddr *name2address (const char *server, int port, int *family, int *bind_size) { - // THIS IS NOT RE-ENTRANT OR THREADSAFE. Optimize for speed. - // Check the more-common cases first. - // Return NULL if no resolution. - if (!server || !*server) server = "0.0.0.0"; - static struct sockaddr_in in4; - static struct sockaddr_in6 in6; - - memset (&in4, 0, sizeof(in4)); - memset (&in6, 0, sizeof(in6)); - - if ( (in4.sin_addr.s_addr = inet_addr (server)) != INADDR_NONE) { - if (family) - *family = AF_INET; - if (bind_size) - *bind_size = sizeof(in4); - in4.sin_family = AF_INET; - in4.sin_port = htons (port); - return (struct sockaddr*)&in4; - } - - #if defined(OS_UNIX) && !defined(__CYGWIN__) - if (inet_pton (AF_INET6, server, in6.sin6_addr.s6_addr) > 0) { - if (family) - *family = AF_INET6; - if (bind_size) - *bind_size = sizeof(in6); - in6.sin6_family = AF_INET6; - in6.sin6_port = htons (port); - return (struct sockaddr*)&in6; - } - #elif defined(OS_WIN32) && !defined(__CYGWIN__) - int len = sizeof(in6); - if (WSAStringToAddress ((char *)server, AF_INET6, NULL, (struct sockaddr *)&in6, &len) == 0) { - if (family) - *family = AF_INET6; - if (bind_size) - *bind_size = sizeof(in6); - in6.sin6_family = AF_INET6; - in6.sin6_port = htons (port); - return (struct sockaddr*)&in6; - } - #endif - - int ai_family; struct addrinfo *ai; struct addrinfo hints; memset (&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; - if (getaddrinfo (server, NULL, &hints, &ai) == 0) { + hints.ai_flags = AI_NUMERICSERV; + + char portstr[12]; + snprintf(portstr, sizeof(portstr), "%u", port); + + if (getaddrinfo (server, portstr, &hints, &ai) == 0) { if (family) *family = ai->ai_family; if (bind_size) *bind_size = ai->ai_addrlen; - ai_family = ai->ai_family; - switch (ai_family) { - case AF_INET: - memcpy (&in4, ai->ai_addr, ai->ai_addrlen); - in4.sin_port = htons(port); - #ifndef __CYGWIN__ - case AF_INET6: - memcpy (&in6, ai->ai_addr, ai->ai_addrlen); - in6.sin6_port = htons(port); - #endif - } + // Use "new" so the caller must "delete" or be an auto_ptr + struct sockaddr *addr = (struct sockaddr *)new struct sockaddr_storage; + assert (ai->ai_addrlen <= sizeof(struct sockaddr_storage)); + memcpy (addr, ai->ai_addr, ai->ai_addrlen); freeaddrinfo(ai); - - switch (ai_family) { - case AF_INET: - return (struct sockaddr*)&in4; - case AF_INET6: - return (struct sockaddr*)&in6; - } + return addr; } return NULL; @@ -1644,8 +1589,8 @@ const uintptr_t EventMachine_t::CreateTcpServer (const char *server, int port) int family, bind_size; - struct sockaddr *bind_here = name2address (server, port, &family, &bind_size); - if (!bind_here) + const std::auto_ptr bind_here (name2address (server, port, &family, &bind_size)); + if (!bind_here.get()) return 0; //struct sockaddr_in sin; @@ -1673,8 +1618,7 @@ const uintptr_t EventMachine_t::CreateTcpServer (const char *server, int port) } - //if (bind (sd_accept, (struct sockaddr*)&sin, sizeof(sin))) { - if (bind (sd_accept, bind_here, bind_size)) { + if (bind (sd_accept, bind_here.get(), bind_size)) { //__warning ("binding failed"); goto fail; } diff --git a/ext/project.h b/ext/project.h index d105b67aa..f1780f8ba 100644 --- a/ext/project.h +++ b/ext/project.h @@ -30,6 +30,7 @@ See the file COPYING for complete licensing information. #include #include #include +#include #ifdef OS_UNIX From 9308e9145789297e56ac1735b36166ca99914100 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 26 Oct 2015 05:18:39 -0700 Subject: [PATCH 6/8] Support for IPv6 UDP --- ext/ed.cpp | 27 ++++++++------------------ ext/ed.h | 6 +++--- ext/em.cpp | 49 +++++++++++------------------------------------- ext/em.h | 5 +++++ ext/rubymain.cpp | 2 ++ 5 files changed, 29 insertions(+), 60 deletions(-) diff --git a/ext/ed.cpp b/ext/ed.cpp index 6a84a683c..7d2670562 100644 --- a/ext/ed.cpp +++ b/ext/ed.cpp @@ -1725,7 +1725,8 @@ void DatagramDescriptor::Write() OutboundPage *op = &(OutboundPages[0]); // The nasty cast to (char*) is needed because Windows is brain-dead. - int s = sendto (sd, (char*)op->Buffer, op->Length, 0, (struct sockaddr*)&(op->From), sizeof(op->From)); + int s = sendto (sd, (char*)op->Buffer, op->Length, 0, (struct sockaddr*)&(op->From), + (op->From.sin6_family == AF_INET6 ? sizeof (struct sockaddr_in6) : sizeof (struct sockaddr_in))); #ifdef OS_WIN32 int e = WSAGetLastError(); #else @@ -1837,23 +1838,10 @@ int DatagramDescriptor::SendOutboundDatagram (const char *data, unsigned long le if (!address || !*address || !port) return 0; - sockaddr_in pin; - unsigned long HostAddr; - - HostAddr = inet_addr (address); - if (HostAddr == INADDR_NONE) { - // The nasty cast to (char*) is because Windows is brain-dead. - hostent *hp = gethostbyname ((char*)address); - if (!hp) - return 0; - HostAddr = ((in_addr*)(hp->h_addr))->s_addr; - } - - memset (&pin, 0, sizeof(pin)); - pin.sin_family = AF_INET; - pin.sin_addr.s_addr = HostAddr; - pin.sin_port = htons (port); - + int family, addr_size; + struct sockaddr *addr_here = EventMachine_t::name2address (address, port, &family, &addr_size); + if (!addr_here) + return -1; if (!data && (length > 0)) throw std::runtime_error ("bad outbound data"); @@ -1862,8 +1850,9 @@ int DatagramDescriptor::SendOutboundDatagram (const char *data, unsigned long le throw std::runtime_error ("no allocation for outbound data"); memcpy (buffer, data, length); buffer [length] = 0; - OutboundPages.push_back (OutboundPage (buffer, length, pin)); + OutboundPages.push_back (OutboundPage (buffer, length, *(struct sockaddr_in6*)addr_here)); OutboundDataSize += length; + delete addr_here; #ifdef HAVE_EPOLL EpollEvent.events = (EPOLLIN | EPOLLOUT); diff --git a/ext/ed.h b/ext/ed.h index 367a1e8db..6b404e1cd 100644 --- a/ext/ed.h +++ b/ext/ed.h @@ -299,18 +299,18 @@ class DatagramDescriptor: public EventableDescriptor protected: struct OutboundPage { - OutboundPage (const char *b, int l, struct sockaddr_in f, int o=0): Buffer(b), Length(l), Offset(o), From(f) {} + OutboundPage (const char *b, int l, struct sockaddr_in6 f, int o=0): Buffer(b), Length(l), Offset(o), From(f) {} void Free() {if (Buffer) free (const_cast(Buffer)); } const char *Buffer; int Length; int Offset; - struct sockaddr_in From; + struct sockaddr_in6 From; }; deque OutboundPages; int OutboundDataSize; - struct sockaddr_in ReturnAddress; + struct sockaddr_in6 ReturnAddress; }; diff --git a/ext/em.cpp b/ext/em.cpp index 7ef4e66aa..2f551d189 100644 --- a/ext/em.cpp +++ b/ext/em.cpp @@ -32,12 +32,6 @@ static unsigned int MaxOutstandingTimers = 100000; */ static unsigned int SimultaneousAcceptCount = 10; - -/* Internal helper to convert strings to internet addresses. IPv6-aware. - * Must free the return value. - */ -static struct sockaddr *name2address (const char *server, int port, int *family, int *bind_size); - /* Internal helper to create a socket with SOCK_CLOEXEC set, and fall * back to fcntl'ing it if the headers/runtime don't support it. */ @@ -1542,7 +1536,7 @@ int EventMachine_t::DetachFD (EventableDescriptor *ed) name2address ************/ -struct sockaddr *name2address (const char *server, int port, int *family, int *bind_size) +struct sockaddr *EventMachine_t::name2address (const char *server, int port, int *family, int *bind_size) { if (!server || !*server) server = "0.0.0.0"; @@ -1593,8 +1587,6 @@ const uintptr_t EventMachine_t::CreateTcpServer (const char *server, int port) if (!bind_here.get()) return 0; - //struct sockaddr_in sin; - SOCKET sd_accept = EmSocket (family, SOCK_STREAM, 0); if (sd_accept == INVALID_SOCKET) { goto fail; @@ -1645,40 +1637,21 @@ const uintptr_t EventMachine_t::OpenDatagramSocket (const char *address, int por { uintptr_t output_binding = 0; - SOCKET sd = EmSocket (AF_INET, SOCK_DGRAM, 0); + int family, bind_size; + const std::auto_ptr bind_here (name2address (address, port, &family, &bind_size)); + if (!bind_here.get()) + return 0; + + // from here on, early returns must close the socket! + SOCKET sd = EmSocket (family, SOCK_DGRAM, 0); if (sd == INVALID_SOCKET) goto fail; - // from here on, early returns must close the socket! - - - struct sockaddr_in sin; - memset (&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - sin.sin_port = htons (port); - - - if (address && *address) { - sin.sin_addr.s_addr = inet_addr (address); - if (sin.sin_addr.s_addr == INADDR_NONE) { - hostent *hp = gethostbyname ((char*)address); // Windows requires the cast. - if (hp == NULL) - goto fail; - sin.sin_addr.s_addr = ((in_addr*)(hp->h_addr))->s_addr; - } - } - else - sin.sin_addr.s_addr = htonl (INADDR_ANY); - // Set the new socket nonblocking. - { - if (!SetSocketNonblocking (sd)) - //int val = fcntl (sd, F_GETFL, 0); - //if (fcntl (sd, F_SETFL, val | O_NONBLOCK) == -1) - goto fail; - } + if (!SetSocketNonblocking (sd)) + goto fail; - if (bind (sd, (struct sockaddr*)&sin, sizeof(sin)) != 0) + if (bind (sd, bind_here.get(), bind_size) != 0) goto fail; { // Looking good. diff --git a/ext/em.h b/ext/em.h index ec239d913..051cd52ac 100644 --- a/ext/em.h +++ b/ext/em.h @@ -200,6 +200,11 @@ class EventMachine_t Poller_t GetPoller() { return Poller; } + /* Helper to convert strings to internet addresses. IPv6-aware. + * Must delete the return value (or use an auto_ptr). + */ + static struct sockaddr *name2address(const char *server, int port, int *family, int *bind_size); + private: void _RunTimers(); void _UpdateTime(); diff --git a/ext/rubymain.cpp b/ext/rubymain.cpp index 97c131bd6..be24d9f78 100644 --- a/ext/rubymain.cpp +++ b/ext/rubymain.cpp @@ -525,6 +525,8 @@ t_send_datagram static VALUE t_send_datagram (VALUE self UNUSED, VALUE signature, VALUE data, VALUE data_length, VALUE address, VALUE port) { int b = evma_send_datagram (NUM2BSIG (signature), StringValuePtr (data), FIX2INT (data_length), StringValueCStr(address), FIX2INT(port)); + if (b < 0) + rb_raise (EM_eConnectionError, "%s", "error in sending datagram"); // FIXME: this could be more specific. return INT2NUM (b); } From 0d55e5e31877f4761575bb82c5761a2700431ff7 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 26 Oct 2015 06:17:39 -0700 Subject: [PATCH 7/8] Refactor name2address one more time to take a sockaddr argument and return a bool --- ext/ed.cpp | 9 ++++---- ext/em.cpp | 58 +++++++++++++++++++++++---------------------------- ext/em.h | 5 +---- ext/project.h | 1 - 4 files changed, 31 insertions(+), 42 deletions(-) diff --git a/ext/ed.cpp b/ext/ed.cpp index 7d2670562..f05b8d79e 100644 --- a/ext/ed.cpp +++ b/ext/ed.cpp @@ -1838,9 +1838,9 @@ int DatagramDescriptor::SendOutboundDatagram (const char *data, unsigned long le if (!address || !*address || !port) return 0; - int family, addr_size; - struct sockaddr *addr_here = EventMachine_t::name2address (address, port, &family, &addr_size); - if (!addr_here) + struct sockaddr_in6 addr_here; + size_t addr_here_len = sizeof addr_here; + if (!EventMachine_t::name2address (address, port, (struct sockaddr *)&addr_here, &addr_here_len)) return -1; if (!data && (length > 0)) @@ -1850,9 +1850,8 @@ int DatagramDescriptor::SendOutboundDatagram (const char *data, unsigned long le throw std::runtime_error ("no allocation for outbound data"); memcpy (buffer, data, length); buffer [length] = 0; - OutboundPages.push_back (OutboundPage (buffer, length, *(struct sockaddr_in6*)addr_here)); + OutboundPages.push_back (OutboundPage (buffer, length, addr_here)); OutboundDataSize += length; - delete addr_here; #ifdef HAVE_EPOLL EpollEvent.events = (EPOLLIN | EPOLLOUT); diff --git a/ext/em.cpp b/ext/em.cpp index 2f551d189..3eb1beb55 100644 --- a/ext/em.cpp +++ b/ext/em.cpp @@ -1189,15 +1189,15 @@ const uintptr_t EventMachine_t::ConnectToServer (const char *bind_addr, int bind if (!server || !*server || !port) throw std::runtime_error ("invalid server or port"); - int family, bind_size; - const std::auto_ptr bind_as (name2address (server, port, &family, &bind_size)); - if (!bind_as.get()) { + struct sockaddr_storage bind_as; + size_t bind_as_len = sizeof bind_as; + if (!name2address (server, port, (struct sockaddr *)&bind_as, &bind_as_len)) { char buf [200]; snprintf (buf, sizeof(buf)-1, "unable to resolve server address: %s", strerror(errno)); throw std::runtime_error (buf); } - SOCKET sd = EmSocket (family, SOCK_STREAM, 0); + SOCKET sd = EmSocket (bind_as.ss_family, SOCK_STREAM, 0); if (sd == INVALID_SOCKET) { char buf [200]; snprintf (buf, sizeof(buf)-1, "unable to create new socket: %s", strerror(errno)); @@ -1217,13 +1217,13 @@ const uintptr_t EventMachine_t::ConnectToServer (const char *bind_addr, int bind setsockopt (sd, SOL_SOCKET, SO_REUSEADDR, (char*) &one, sizeof(one)); if (bind_addr) { - int bind_to_size, bind_to_family; - const std::auto_ptr bind_to (name2address (bind_addr, bind_port, &bind_to_family, &bind_to_size)); - if (!bind_to.get()) { + struct sockaddr_storage bind_to; + size_t bind_to_len = sizeof bind_to; + if (!name2address (bind_addr, bind_port, (struct sockaddr *)&bind_to, &bind_to_len)) { close (sd); throw std::runtime_error ("invalid bind address"); } - if (bind (sd, bind_to.get(), bind_to_size) < 0) { + if (bind (sd, (struct sockaddr *)&bind_to, bind_to_len) < 0) { close (sd); throw std::runtime_error ("couldn't bind to address"); } @@ -1233,7 +1233,7 @@ const uintptr_t EventMachine_t::ConnectToServer (const char *bind_addr, int bind #ifdef OS_UNIX int e_reason = 0; - if (connect (sd, bind_as.get(), bind_size) == 0) { + if (connect (sd, (struct sockaddr *)&bind_as, bind_as_len) == 0) { // This is a connect success, which Linux appears // never to give when the socket is nonblocking, // even if the connection is intramachine or to @@ -1312,7 +1312,7 @@ const uintptr_t EventMachine_t::ConnectToServer (const char *bind_addr, int bind #endif #ifdef OS_WIN32 - if (connect (sd, bind_as.get(), bind_size) == 0) { + if (connect (sd, (struct sockaddr *)&bind_as, bind_as_len) == 0) { // This is a connect success, which Windows appears // never to give when the socket is nonblocking, // even if the connection is intramachine or to @@ -1536,7 +1536,7 @@ int EventMachine_t::DetachFD (EventableDescriptor *ed) name2address ************/ -struct sockaddr *EventMachine_t::name2address (const char *server, int port, int *family, int *bind_size) +bool EventMachine_t::name2address (const char *server, int port, struct sockaddr *addr, size_t *addr_len) { if (!server || !*server) server = "0.0.0.0"; @@ -1545,27 +1545,21 @@ struct sockaddr *EventMachine_t::name2address (const char *server, int port, int struct addrinfo hints; memset (&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; - hints.ai_flags = AI_NUMERICSERV; + hints.ai_flags = AI_NUMERICSERV | AI_ADDRCONFIG; char portstr[12]; snprintf(portstr, sizeof(portstr), "%u", port); if (getaddrinfo (server, portstr, &hints, &ai) == 0) { - if (family) - *family = ai->ai_family; - if (bind_size) - *bind_size = ai->ai_addrlen; - - // Use "new" so the caller must "delete" or be an auto_ptr - struct sockaddr *addr = (struct sockaddr *)new struct sockaddr_storage; - assert (ai->ai_addrlen <= sizeof(struct sockaddr_storage)); + assert (ai->ai_addrlen <= *addr_len); memcpy (addr, ai->ai_addr, ai->ai_addrlen); + *addr_len = ai->ai_addrlen; freeaddrinfo(ai); - return addr; + return true; } - return NULL; + return false; } @@ -1582,12 +1576,12 @@ const uintptr_t EventMachine_t::CreateTcpServer (const char *server, int port) */ - int family, bind_size; - const std::auto_ptr bind_here (name2address (server, port, &family, &bind_size)); - if (!bind_here.get()) + struct sockaddr_storage bind_here; + size_t bind_here_len = sizeof bind_here; + if (!name2address (server, port, (struct sockaddr *)&bind_here, &bind_here_len)) return 0; - SOCKET sd_accept = EmSocket (family, SOCK_STREAM, 0); + SOCKET sd_accept = EmSocket (bind_here.ss_family, SOCK_STREAM, 0); if (sd_accept == INVALID_SOCKET) { goto fail; } @@ -1610,7 +1604,7 @@ const uintptr_t EventMachine_t::CreateTcpServer (const char *server, int port) } - if (bind (sd_accept, bind_here.get(), bind_size)) { + if (bind (sd_accept, (struct sockaddr *)&bind_here, bind_here_len)) { //__warning ("binding failed"); goto fail; } @@ -1637,13 +1631,13 @@ const uintptr_t EventMachine_t::OpenDatagramSocket (const char *address, int por { uintptr_t output_binding = 0; - int family, bind_size; - const std::auto_ptr bind_here (name2address (address, port, &family, &bind_size)); - if (!bind_here.get()) + struct sockaddr_storage bind_here; + size_t bind_here_len = sizeof bind_here; + if (!name2address (address, port, (struct sockaddr *)&bind_here, &bind_here_len)) return 0; // from here on, early returns must close the socket! - SOCKET sd = EmSocket (family, SOCK_DGRAM, 0); + SOCKET sd = EmSocket (bind_here.ss_family, SOCK_DGRAM, 0); if (sd == INVALID_SOCKET) goto fail; @@ -1651,7 +1645,7 @@ const uintptr_t EventMachine_t::OpenDatagramSocket (const char *address, int por if (!SetSocketNonblocking (sd)) goto fail; - if (bind (sd, bind_here.get(), bind_size) != 0) + if (bind (sd, (struct sockaddr *)&bind_here, bind_here_len) != 0) goto fail; { // Looking good. diff --git a/ext/em.h b/ext/em.h index 051cd52ac..80cc9710f 100644 --- a/ext/em.h +++ b/ext/em.h @@ -200,10 +200,7 @@ class EventMachine_t Poller_t GetPoller() { return Poller; } - /* Helper to convert strings to internet addresses. IPv6-aware. - * Must delete the return value (or use an auto_ptr). - */ - static struct sockaddr *name2address(const char *server, int port, int *family, int *bind_size); + static bool name2address (const char *server, int port, struct sockaddr *addr, size_t *addr_len); private: void _RunTimers(); diff --git a/ext/project.h b/ext/project.h index f1780f8ba..d105b67aa 100644 --- a/ext/project.h +++ b/ext/project.h @@ -30,7 +30,6 @@ See the file COPYING for complete licensing information. #include #include #include -#include #ifdef OS_UNIX From edbd6394ed70c9a64b0c8e211460aee23412c6b6 Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Mon, 26 Oct 2015 06:19:20 -0700 Subject: [PATCH 8/8] Copy over IPv6 tests from EventMachine LE --- tests/em_test_helper.rb | 79 ++++++++++++++++++++++++ tests/test_ipv4.rb | 125 ++++++++++++++++++++++++++++++++++++++ tests/test_ipv6.rb | 131 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 335 insertions(+) create mode 100644 tests/test_ipv4.rb create mode 100644 tests/test_ipv6.rb diff --git a/tests/em_test_helper.rb b/tests/em_test_helper.rb index b68f347a7..6b6470fc4 100644 --- a/tests/em_test_helper.rb +++ b/tests/em_test_helper.rb @@ -31,6 +31,61 @@ def next_port @@port end + # Returns true if the host have a localhost 127.0.0.1 IPv4. + def self.local_ipv4? + return @@has_local_ipv4 if defined?(@@has_local_ipv4) + begin + get_my_ipv4_address "127.0.0.1" + @@has_local_ipv4 = true + rescue + @@has_local_ipv4 = false + end + end + + # Returns true if the host have a public IPv4 and stores it in + # @@public_ipv4. + def self.public_ipv4? + return @@has_public_ipv4 if defined?(@@has_public_ipv4) + begin + @@public_ipv4 = get_my_ipv4_address "1.2.3.4" + @@has_public_ipv4 = true + rescue + @@has_public_ipv4 = false + end + end + + # Returns true if the host have a localhost ::1 IPv6. + def self.local_ipv6? + return @@has_local_ipv6 if defined?(@@has_local_ipv6) + begin + get_my_ipv6_address "::1" + @@has_local_ipv6 = true + rescue + @@has_local_ipv6 = false + end + end + + # Returns true if the host have a public IPv6 and stores it in + # @@public_ipv6. + def self.public_ipv6? + return @@has_public_ipv6 if defined?(@@has_public_ipv6) + begin + @@public_ipv6 = get_my_ipv6_address "2001::1" + @@has_public_ipv6 = true + rescue + @@has_public_ipv6 = false + end + end + + # Returns an array with the localhost addresses (IPv4 and/or IPv6). + def local_ips + return @@local_ips if defined?(@@local_ips) + @@local_ips = [] + @@local_ips << "127.0.0.1" if self.class.local_ipv4? + @@local_ips << "::1" if self.class.local_ipv6? + @@local_ips + end + def exception_class jruby? ? NativeException : RuntimeError end @@ -69,4 +124,28 @@ def silent $VERBOSE = backup end end + + + private + + def self.get_my_ipv4_address ip + orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily + UDPSocket.open(Socket::AF_INET) do |s| + s.connect ip, 1 + s.addr.last + end + ensure + Socket.do_not_reverse_lookup = orig + end + + def self.get_my_ipv6_address ip + orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily + UDPSocket.open(Socket::AF_INET6) do |s| + s.connect ip, 1 + s.addr.last + end + ensure + Socket.do_not_reverse_lookup = orig + end + end diff --git a/tests/test_ipv4.rb b/tests/test_ipv4.rb new file mode 100644 index 000000000..e132fd359 --- /dev/null +++ b/tests/test_ipv4.rb @@ -0,0 +1,125 @@ +require 'em_test_helper' +require 'socket' + +class TestIPv4 < Test::Unit::TestCase + + if Test::Unit::TestCase.public_ipv4? + + # Tries to connect to www.google.com port 80 via TCP. + # Timeout in 2 seconds. + def test_ipv4_tcp_client + conn = nil + setup_timeout(2) + + EM.run do + conn = EM::connect("www.google.com", 80) do |c| + def c.connected + @connected + end + + def c.connection_completed + @connected = true + EM.stop + end + end + end + + assert conn.connected + end + + # Runs a TCP server in the local IPv4 address, connects to it and sends a specific data. + # Timeout in 2 seconds. + def test_ipv4_tcp_local_server + @@received_data = nil + @local_port = next_port + setup_timeout(2) + + EM.run do + EM::start_server(@@public_ipv4, @local_port) do |s| + def s.receive_data data + @@received_data = data + EM.stop + end + end + + EM::connect(@@public_ipv4, @local_port) do |c| + c.send_data "ipv4/tcp" + end + end + + assert_equal "ipv4/tcp", @@received_data + end + + # Runs a UDP server in the local IPv4 address, connects to it and sends a specific data. + # Timeout in 2 seconds. + def test_ipv4_udp_local_server + @@received_data = nil + @local_port = next_port + setup_timeout(2) + + EM.run do + EM::open_datagram_socket(@@public_ipv4, @local_port) do |s| + def s.receive_data data + @@received_data = data + EM.stop + end + end + + EM::open_datagram_socket(@@public_ipv4, next_port) do |c| + c.send_datagram "ipv4/udp", @@public_ipv4, @local_port + end + end + + assert_equal "ipv4/udp", @@received_data + end + + # Try to connect via TCP to an invalid IPv4. EM.connect should raise + # EM::ConnectionError. + def test_tcp_connect_to_invalid_ipv4 + invalid_ipv4 = "9.9:9" + + EM.run do + begin + error = nil + EM.connect(invalid_ipv4, 1234) + rescue => e + error = e + ensure + EM.stop + assert_equal EM::ConnectionError, (error && error.class) + end + end + end + + # Try to send a UDP datagram to an invalid IPv4. EM.send_datagram should raise + # EM::ConnectionError. + def test_udp_send_datagram_to_invalid_ipv4 + invalid_ipv4 = "9.9:9" + + EM.run do + begin + error = nil + EM.open_datagram_socket(@@public_ipv4, next_port) do |c| + c.send_datagram "hello", invalid_ipv4, 1234 + end + rescue => e + error = e + ensure + EM.stop + assert_equal EM::ConnectionError, (error && error.class) + end + end + end + + + else + warn "no IPv4 in this host, skipping tests in #{__FILE__}" + + # Because some rubies will complain if a TestCase class has no tests + def test_ipv4_unavailable + assert true + end + + end + +end diff --git a/tests/test_ipv6.rb b/tests/test_ipv6.rb new file mode 100644 index 000000000..f3d66c639 --- /dev/null +++ b/tests/test_ipv6.rb @@ -0,0 +1,131 @@ +require 'em_test_helper' + +class TestIPv6 < Test::Unit::TestCase + + if Test::Unit::TestCase.public_ipv6? + + # Tries to connect to ipv6.google.com (2607:f8b0:4010:800::1006) port 80 via TCP. + # Timeout in 6 seconds. + def test_ipv6_tcp_client_with_ipv6_google_com + conn = nil + setup_timeout(6) + + EM.run do + conn = EM::connect("2607:f8b0:4010:800::1006", 80) do |c| + def c.connected + @connected + end + + def c.unbind(reason) + warn "unbind: #{reason.inspect}" if reason # XXX at least find out why it failed + end + + def c.connection_completed + @connected = true + EM.stop + end + end + end + + assert conn.connected + end + + # Runs a TCP server in the local IPv6 address, connects to it and sends a specific data. + # Timeout in 2 seconds. + def test_ipv6_tcp_local_server + @@received_data = nil + @local_port = next_port + setup_timeout(2) + + EM.run do + EM.start_server(@@public_ipv6, @local_port) do |s| + def s.receive_data data + @@received_data = data + EM.stop + end + end + + EM::connect(@@public_ipv6, @local_port) do |c| + def c.unbind(reason) + warn "unbind: #{reason.inspect}" if reason # XXX at least find out why it failed + end + c.send_data "ipv6/tcp" + end + end + + assert_equal "ipv6/tcp", @@received_data + end + + # Runs a UDP server in the local IPv6 address, connects to it and sends a specific data. + # Timeout in 2 seconds. + def test_ipv6_udp_local_server + @@received_data = nil + @local_port = next_port + setup_timeout(2) + + EM.run do + EM.open_datagram_socket(@@public_ipv6, @local_port) do |s| + def s.receive_data data + @@received_data = data + EM.stop + end + end + + EM.open_datagram_socket(@@public_ipv6, next_port) do |c| + c.send_datagram "ipv6/udp", @@public_ipv6, @local_port + end + end + + assert_equal "ipv6/udp", @@received_data + end + + # Try to connect via TCP to an invalid IPv6. EM.connect should raise + # EM::ConnectionError. + def test_tcp_connect_to_invalid_ipv6 + invalid_ipv6 = "1:A" + + EM.run do + begin + error = nil + EM.connect(invalid_ipv6, 1234) + rescue => e + error = e + ensure + EM.stop + assert_equal EM::ConnectionError, (error && error.class) + end + end + end + + # Try to send a UDP datagram to an invalid IPv6. EM.send_datagram should raise + # EM::ConnectionError. + def test_udp_send_datagram_to_invalid_ipv6 + invalid_ipv6 = "1:A" + + EM.run do + begin + error = nil + EM.open_datagram_socket(@@public_ipv6, next_port) do |c| + c.send_datagram "hello", invalid_ipv6, 1234 + end + rescue => e + error = e + ensure + EM.stop + assert_equal EM::ConnectionError, (error && error.class) + end + end + end + + + else + warn "no IPv6 in this host, skipping tests in #{__FILE__}" + + # Because some rubies will complain if a TestCase class has no tests. + def test_ipv6_unavailable + assert true + end + + end + +end