ACE_TAO icon indicating copy to clipboard operation
ACE_TAO copied to clipboard

ACE_SOCK_Connector fails to connect to IPv4-mapped IPv6 address on Windows 10

Open 4WheelsOff opened this issue 3 years ago • 0 comments

Version

6.5.11

Host machine and operating system

Windows 10 Professional (10.0.19042)

Target machine and operating system (if different from host)

Compiler name and version (including patch level)

Visual Studio 2015 (14.0.25123.00 Update 2)

The $ACE_ROOT/ace/config.h file

      #define ACE_HAS_IPV6 1
      #include "config-win32.h"

The $ACE_ROOT/include/makeinclude/platform_macros.GNU file

Not used

Contents of $ACE_ROOT/bin/MakeProjectCreator/config/default.features

Not used

AREA/CLASS/EXAMPLE AFFECTED:

ACE_SOCK_Connector

The problem effects:

Affects run-time behavior of ACE_Sock_Connector on Windows 10. Same code works fine on Linux.

Synopsis

On Windows 10, ACE_SOCK_Connector fails to connect to an IPv4-mapped IPv6 address.

Description

On Windows 10, when connecting to an IPv4-mapped IPv6 remote address, the ACE_SOCK_Connector attempts to bind() the local socket to ACE_IPV6_ANY, but this results in error 10049 (EADDRNOTAVAIL) when connect() is called.

According to MSDN, setsockopt() must set IPV6_V6ONLY = 0 before calling bind(): https://docs.microsoft.com/en-us/windows/win32/winsock/dual-stack-sockets

In the sample program under "REPEAT BY", the local (bind to) address is set in TAO_IIOP_Connector::begin_connection():

#if defined (ACE_HAS_IPV6)
  else if (remote_address.get_type () == AF_INET6)
    {
      local_addr.set (port, ACE_IPV6_ANY);      // Set ACE_Addr::sap_any instead of ACE_IPV6_ANY to avoid explicit bind
    }
#endif /* ACE_HAS_IPV6 */

The socket is created by ACE_SOCK_Connector::connect() which then initiates the connection by calling ACE_SOCK_Connector::shared_connect_start(). A call to bind() attempts to bind to ACE_IPV6_ANY.

// Condition "local_sap != ACE_addr::sap_any" results in an (unnecessary?) explicit bind(). 
  if (local_sap != ACE_Addr::sap_any)
    {
      sockaddr *laddr = reinterpret_cast<sockaddr *> (local_sap.get_addr ());
      int const size = local_sap.get_size ();

      if (ACE_OS::bind (new_stream.get_handle (), laddr, size) == -1)
        {
          // Save/restore errno.
          ACE_Errno_Guard error (errno);
          new_stream.close ();
          return -1;
        }
    }

Upon returning to ACE_SOCK_Connector::connect(), setsockopt() tries to enable dual-stack support, but it is too late:

#if defined(ACE_WIN32) && defined(ACE_HAS_IPV6)
  // On windows, the IPv4-mapped IPv6 address format can only be used on a dual-stack socket.
  const ACE_INET_Addr& remote_address = static_cast<const ACE_INET_Addr&>(remote_sap);
  if (remote_address.is_ipv4_mapped_ipv6()) {
     int zero = 0;
     ACE_OS::setsockopt(new_stream.get_handle(),
        IPPROTO_IPV6,
        IPV6_V6ONLY,
        (char *)&zero,
        sizeof(zero));
  }
#endif

Repeat by

#include "orbsvcs/CosNamingC.h"
#include "tao/corba.h"

int main(int argc, char *argv[])
{
   try
   {
      CORBA::ORB_ptr orb = CORBA::ORB_init(argc, argv);
      using namespace CosNaming;
      // use a specially constructed URL to locate the CORBA Naming Service
      // myhost.mydomain.dom resolves to an IPv4-mapped IPv6 address
      std::string nsURL("corbaloc:iiop:[email protected]:11112/NameService");
      CORBA::Object_var root_ns_obj(orb->string_to_object(nsURL.c_str()));
      NamingContext_var root_ns_ctx(NamingContext::_narrow(root_ns_obj.in()));
   }
   catch (CORBA::Exception& e)
   {
      e._tao_print_exception("failed");
   }
   return 0;
}

----- -ORBDebugLevel 10 Log Output -----

TAO (476|22500) - Did not find default svc.conf
TAO (476|22500) - Completed initializing the process-wide service context
TAO (476|22500) - Default ORB services initialization begins
TAO (476|22500) - Default ORB services initialization completed
TAO (476|22500) - We are the default 2.5.8 ORB ...
TAO (476|22500) - Initializing the orb-specific services
TAO (476|22500) - ORBInitializer_Registry::register_orb_initializer 0 @000001E29B3D8BF0
TAO (476|22500) - TAO_Time_Policy_Manager: loaded time policy strategy 'TAO_SYSTEM_TIME_POLICY'
TAO (476|22500) - Default_Resource_Factory - codeset manager=000001E29B3CDC50
TAO (476|22500) - Codeset_Manager_i::init_ccs, Loaded Codeset translator <UTF8_Latin1_Factory>, ncs = 00010001 tcs = 05010001
TAO (476|22500) - UTF16_BOM_Translator: forceBE 0
TAO (476|22500) - Loaded default protocol <IIOP_Factory>
TAO (476|22500) - Created new ORB <>
TAO (476|22500) - Stub::base_profiles, acquired profile lock this = 0x9b4020a0
TAO (476|22500) - Invocation_Adapter::invoke_i, making a TAO_CS_REMOTE_STRATEGY invocation
TAO (476|22500) - Transport_Cache_Manager_T::fill_set_i, current_size [0], cache_maximum [512]
TAO (476|22500) - Transport_Cache_Manager_T::purge, Cache size after purging is [0]
TAO (476|22500) - IIOP_Connector::begin_connection, to <myhost.mydomain.dom:11112> which should block
TAO (476|22500) - TAO_LF_CH_Event[0]::state_changed_i, state LFS_IDLE->LFS_CONNECTION_WAIT
TAO (476|22500) - IIOP_Connection_Handler[-1690190400]::IIOP_Connection_Handler, this=000001E29B4026D0
TAO (476|22500) - TAO_LF_CH_Event[-1690190400]::state_changed_i, state LFS_CONNECTION_WAIT->LFS_CONNECTION_CLOSED
TAO (476|22500) - Transport[-1690190400]::purge_entry, entry is 0000000000000000
TAO (476|22500) - IIOP_Connector::make_connection, connection to <myhost.mydomain.dom:11112> failed (errno: address not available)
TAO (476|22500) - IIOP_Connection_Handler[-1690190400]::~IIOP_Connection_Handler, this=000001E29B4026D0, transport=000001E29B41BDC0
TAO (476|22500) - Transport[-1690190400]::~Transport
TAO (476|22500) - Transport[-1690190400]::cleanup_queue_i, cleaning up complete queue
TAO (476|22500) - Transport[-1690190400]::cleanup_queue_i, discarded 0 messages, 0 bytes.
TAO (476|22500) - Transport_Connector::connect, make_connection failed
TAO (476|22500) - Stub::next_profile_retry, acquired profile lock this = 0x9b4020a0
(476|22500) EXCEPTION, failed
system exception, ID 'IDL:omg.org/CORBA/TRANSIENT:1.0'
OMG minor code (2), described as 'No usable profile in IOR.', completed = NO

Sample fix/ workaround

Perform setsockopt(IPV6_V6ONLY=0) before bind/connect.

int
ACE_SOCK_Connector::connect (ACE_SOCK_Stream &new_stream,
                             const ACE_Addr &remote_sap,
                             const ACE_Time_Value *timeout,
                             const ACE_Addr &local_sap,
                             int reuse_addr,
                             int /* flags */,
                             int /* perms */,
                             int protocol)
{
  ACE_TRACE ("ACE_SOCK_Connector::connect");

  if (this->shared_open (new_stream,
                         remote_sap.get_type (),
                         protocol,
                         reuse_addr) == -1)
    return -1;

  // must enable dual-stack before bind/connect
#if defined(ACE_WIN32) && defined(ACE_HAS_IPV6)
  // On windows, the IPv4-mapped IPv6 address format can only be used on a dual-stack socket.
  const ACE_INET_Addr& remote_address = static_cast<const ACE_INET_Addr&>(remote_sap);
  if (remote_address.is_ipv4_mapped_ipv6()) {
     int zero = 0;
     ACE_OS::setsockopt(new_stream.get_handle(),
        IPPROTO_IPV6,
        IPV6_V6ONLY,
        (char *)&zero,
        sizeof(zero));
  }
#endif

  if (this->shared_connect_start (new_stream,
                                       timeout,
                                       local_sap) == -1)
    return -1;

Compare with ACE_SOCK_Acceptor::shared_open which correctly orders setsockopt() and bind() :

      /*
       * Handle IPv6-only requests. On Windows, v6-only is the default
       * unless it is turned off. On Linux, v4/v6 dual is the default
       * unless v6-only is turned on.
       * This must be done before attempting to bind the address.
       * On Windows older than Vista this will fail.
       */
      int setting = !!ipv6_only;
      if (-1 == ACE_OS::setsockopt (this->get_handle (),
                                    IPPROTO_IPV6,
                                    IPV6_V6ONLY,
                                    (char *)&setting,
                                    sizeof (setting)))
        error = 1;
      else
        // We probably don't need a bind_port written here.
        // There are currently no supported OS's that define
        // ACE_LACKS_WILDCARD_BIND.
        if (ACE_OS::bind (this->get_handle (),
                          reinterpret_cast<sockaddr *> (&local_inet6_addr),
                          sizeof local_inet6_addr) == -1)
          error = 1;

4WheelsOff avatar Oct 16 '20 17:10 4WheelsOff