Unable to GetUnicastIpAddressEntry after CreateUnicastIpAddressEntry

516 views Asked by At

Background: I am trying to get up to speed on using RFC7217 compliant ipv6 addresses. To that end I have written code that creates a valid route-able ipv6 address like 2600:8806:2700:115:c4a3:36d8:77e2:cd1e. I know I need to enter the new address into windows before being able to bind() to it. I figured that these two methods would do the trick. So, using one of my ip addresses, I executed the sample code found in CreateUnicastIpAddressEntry. Then, using the same ip address, I executed the sample code found in GetUnicastIpAddressEntry.

Problem:

I expected to retrieve the ip address again. Instead, I got ERROR_NOT_FOUND (2).

Analysis: I know the ip address is getting into the system because if I run CreateUnicastIpAddressEntry a second time with the same ip address, I get ERROR_OBJECT_ALREADY_EXISTS.

Question:

Does anyone experienced in these two ip methods know what this error code means in this context? Is entering and getting back the same ip address a reasonable expectation for these two windows ip methods?

The sample code for CreateUnicastIpAddressEntry needs some work, so I can upload it with my changes somewhere if someone wants to try it. GetUnicastIpAddressEntry sample code almost runs out of the box.

Edit1:

The following is modified sample code illustrating the changes I had to make in order for CreateUnicastIpAddressEntry() to work and MFC to be able to create a socket, bind to it and listen on it.

The CreateUnicastIpAddressEntry() sample code I modified to make it work for IPv6. All my comments begin with RT:date. All the rest are the original sample code writer's. I have also hard-coded a specific generated IPv6 Slaac address where 2600:8806:2700 is taken from my particular Router Advertisement's prefix, bf72 is the subnet id, which for my purposes is a random unique number between 1 and 65535. And 596c:919b:9499:e0db is a single RFC7217 compliant interface id used here for test purposes.

Running this code enters the address into the internal address table.

    #ifndef WIN32_LEAN_AND_MEAN
    #define WIN32_LEAN_AND_MEAN
    #endif

    #include <windows.h>
    #include <winsock2.h>
    #include <ws2ipdef.h> 
    #include <iphlpapi.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <WS2tcpip.h> // RT:191031: for InetPton
    #include <memory>

    // Need to link with Iphlpapi.lib and Ws2_32.lib
    #pragma comment(lib, "iphlpapi.lib")
    #pragma comment(lib, "ws2_32.lib")

    HANDLE gCallbackComplete;
    HANDLE gNotifyEvent;

    void CALLBACK CallCompleted( VOID* callerContext,
      PMIB_UNICASTIPADDRESS_ROW row,
      MIB_NOTIFICATION_TYPE notificationType );

    int main( int argc, char** argv ) 
    {

      // Declare and initialize variables

      unsigned long ipAddress = INADDR_NONE;
      unsigned long ipMask = INADDR_NONE;

      DWORD dwRetVal = 0;

      DWORD dwSize = 0;
      unsigned long status = 0;

      DWORD lastError = 0;
      SOCKADDR_IN6 localAddress;

      NET_LUID interfaceLuid;
      PMIB_IPINTERFACE_TABLE pipTable = NULL;
      MIB_UNICASTIPADDRESS_ROW ipRow;

      CHAR addr[] { "2600:8806:2700:bf72:596c:919b:9499:e0db" }; // RT:191030: an rfc7217 compliant generated ipv6 slaac ip address
      int result = InetPtonA( AF_INET6, addr, &ipAddress ); // RT:191030: converts str addr to network order binary form. Sample code used deprecated inet_addr
      if( ipAddress == INADDR_NONE ) {
        printf( "usage: %s IPv4address IPv4mask\n", argv[ 0 ] );
        exit( 1 );
      }

      status = GetIpInterfaceTable( AF_INET6, &pipTable );
      if( status != NO_ERROR )
      {
        printf( "GetIpInterfaceTable returned error: %ld\n",
          status );
        exit( 1 );
      }

      // Use loopback interface
      interfaceLuid = pipTable->Table[ 0 ].InterfaceLuid;

      localAddress.sin6_family = AF_INET6;
      std::memcpy( localAddress.sin6_addr.u.Byte, &ipAddress, sizeof( localAddress.sin6_addr ) ); //RT:191114 for ipv4 it was  'localAddress.sin_addr.S_un.S_addr = ipAddress;'

      FreeMibTable( pipTable );
      pipTable = NULL;

      // Initialize the row
      InitializeUnicastIpAddressEntry( &ipRow );

      ipRow.InterfaceLuid = interfaceLuid;
      ipRow.Address.Ipv6 = localAddress;

      // Create a Handle to be notified of IP address changes
      gCallbackComplete = CreateEvent( NULL, FALSE, FALSE, NULL );
      if( gCallbackComplete == NULL ) {
        printf( "CreateEvent failed with error: %d\n", GetLastError() );
        exit( 1 );
      }

      // Use NotifyUnicastIpAddressChange to determine when the address is ready
      NotifyUnicastIpAddressChange( AF_INET6, &CallCompleted, NULL, FALSE, &gNotifyEvent );

      status = CreateUnicastIpAddressEntry( &ipRow );
      if( status != NO_ERROR )
      {
        CancelMibChangeNotify2( gNotifyEvent );
        //CancelMibChangeNotify2(gCallbackComplete); // RT:191115: throws exception, commented out for now
        switch( status )
        {
        case ERROR_INVALID_PARAMETER:
          printf( "Error: CreateUnicastIpAddressEntry returned ERROR_INVALID_PARAMETER\n" );
          break;
        case ERROR_NOT_FOUND:
          printf( "Error: CreateUnicastIpAddressEntry returned ERROR_NOT_FOUND\n" );
          break;
        case ERROR_NOT_SUPPORTED:
          printf( "Error: CreateUnicastIpAddressEntry returned ERROR_NOT_SUPPORTED\n" );
          break;
        case ERROR_OBJECT_ALREADY_EXISTS:
          printf( "Error: CreateUnicastIpAddressEntry returned ERROR_OBJECT_ALREADY_EXISTS\n" );
          break;
        case ERROR_ACCESS_DENIED:
          break;
        default:
          //NOTE: Is this case needed? If not, we can remove the ErrorExit() function
          printf( "CreateUnicastIpAddressEntry returned error: %d\n", status );
          break;
        }
        exit( status );

      }
      else
        printf( "CreateUnicastIpAddressEntry succeeded\n" );

      // Set timeout to 6 seconds
      status = WaitForSingleObject( gCallbackComplete, 6000 );
      if( status != WAIT_OBJECT_0 )
      {
        CancelMibChangeNotify2( gNotifyEvent );
        //RT:191115 causes exception. CancelMibChangeNotify2( gCallbackComplete );
        switch( status )
        {
        case WAIT_ABANDONED:
          printf( "Wait on event was abandoned\n" );
          break;
        case WAIT_TIMEOUT:
          printf( "Wait on event timed out\n" );
          break;
        default:
          printf( "Wait on event exited with status %d\n", status );
          break;
        }
        return status;
      }
      printf( "Task completed successfully\n" );
      CancelMibChangeNotify2( gNotifyEvent );
      //RT:191115 exception thrown. CancelMibChangeNotify2( gCallbackComplete );

      exit( 0 );
    }


    void CALLBACK CallCompleted( PVOID callerContext, PMIB_UNICASTIPADDRESS_ROW row, MIB_NOTIFICATION_TYPE notificationType )
    {

      ADDRESS_FAMILY addressFamily;
      SOCKADDR_IN sockv4addr;
      struct in_addr ipv4addr;

      // Ensure that this is the correct notification before setting gCallbackComplete
      // NOTE: Is there a stronger way to do this?
      if( notificationType == MibAddInstance ) {
        printf( "NotifyUnicastIpAddressChange received an Add instance\n" );
        addressFamily = ( ADDRESS_FAMILY )row->Address.si_family;
        switch( addressFamily ) {
        case AF_INET:
          printf( "\tAddressFamily: AF_INET\n" );
          break;
        case AF_INET6:
          printf( "\tAddressFamily: AF_INET6\n" ); // RT:191031: like 0x00000246a7ebbea8 L"2600:8806:2700:115:9cd3:ff59:af28:cb54"
          break;
        default:
          printf( "\tAddressFamily: %d\n", addressFamily );
          break;
        }
        if( addressFamily == AF_INET ) {
          sockv4addr = row->Address.Ipv4;
          ipv4addr = sockv4addr.sin_addr;
          int lResult = InetPtonA( AF_INET, "192.168.0.222", &sockv4addr ); // RT:191030: text to binary form and network byte order. inet_addr was deprecated
          //printf( "IPv4 address:  %s\n", InetPtonA( /*ipv4addr*/ ) );
        }
        if( callerContext != NULL )
          printf( "Received a CallerContext value\n" );

        SetEvent( gCallbackComplete );
      }
      return;
    }

And here are the MFC Socket, Bind and Listen code snippets that show how to use MFC so it will work with an IPv6 ip address. Microsoft docs says MFC doesn't work for IPv6, but this is because the Create function's address family parameter defaults to AF_INET (IPv4). So if you call MFC's underlying functions, the address family parameter can be set to AF_INET6.

Here is the modified MFC Socket call:

INFOMSG_LA_X( L"calls Casyncsocket::Socket(cap s) which calls socket(small s), which calls __imp_load_socket, which is a hidden windows call, no documentation. It does work, however, for af_inet and af_inet6 if the ip address is recognized by the OS.", LogAction::ONCE );
if( Socket( nSocketType, lEvent, nProtocolType, nAddressFormat ) ) // RT:191030: standard mfc (Socket) uses defaults for nprotocoltype (0) and naddressformat (pF_INET), but they can be set if the 2nd socket signature is used with 4 args as is the case here
{
  ASSERT( nAddressFormat == PF_INET || nAddressFormat == PF_INET6 );
  
  if( nAddressFormat == PF_INET && Bind( nSocketPort, lpszSocketAddress ) )
  {
    return TRUE;
  }
  else if( nAddressFormat == PF_INET6 && Ipv6Bind( nSocketPort, lpszSocketAddress ) )
  {
    return TRUE;
  }

Notice the separate Bind calls, one for AF_INET which is standard MFC code, and one for AF_INET6.

And here is the bind call:

BOOL Ipv6Bind( UINT nSocketPort, LPCTSTR lpszSocketAddress )
{
  CString msg;
  bool okay = true;
  INFOX();

  USES_CONVERSION_EX;
  ASSERT( m_hSocket );

  SOCKADDR_IN6 sockAddr6;
  std::memset( &sockAddr6, 0, sizeof( sockAddr6 ) );

  LPSTR lpszAscii;
  if( lpszSocketAddress != NULL )
  {
    lpszAscii = T2A_EX( ( LPTSTR )lpszSocketAddress, _ATL_SAFE_ALLOCA_DEF_THRESHOLD );
    if( lpszAscii == NULL )
    {
      // OUT OF MEMORY
      WSASetLastError( ERROR_NOT_ENOUGH_MEMORY );
      return FALSE;
    }
  }
  else
  {
    lpszAscii = NULL;
  }

  sockAddr6.sin6_family = AF_INET6;

  if( lpszAscii == NULL )
    sockAddr6.sin6_addr.u.Byte[ 0 ] = ( UCHAR )htonl( INADDR_ANY ); // converts a u_long from host to TCP/IP network byte order (which is big-endian)
  else
  {
    int lResult = InetPtonA( AF_INET6, lpszAscii, sockAddr6.sin6_addr.u.Byte ); // RT:191030: text to binary form and network byte order. inet_addr was deprecated
    if( lResult == 0 )
    {
      WSASetLastError( WSAEINVAL );
      return FALSE;
    }
  }

  sockAddr6.sin6_port = htons( ( u_short )nSocketPort );
  if( !Bind( ( SOCKADDR* )&sockAddr6, sizeof( sockAddr6 ) ) )
  {
    DWORD lastError = GetLastError();
    switch( lastError )
    {
    case WSAEADDRNOTAVAIL: // "The requested address is not valid in its context. This error is returned if the specified address pointed to by the name parameter is not a valid local IP address on this computer."
      okay = EnterUnicastIpAddrIntoInternalTable();
      break;

    default:
      msg.Format( L"bind: '%s'", StringsMgr::GetLastErrorString( lastError ).GetString() ); ERRORMSGX( msg );
    }
  }

  return TRUE;
}

Notice the call to EnterUnicastIpAddrIntoInternalTable(). This could be the place where you would want to use the modified CreateUnicastIpAddressEntry() code to get the new address into the internal table.

All put together, the ip address will appear as LISTENING in the readout of netstat -a.

1

There are 1 answers

0
rtischer8277 On BEST ANSWER

What Works Now:

After fixing the sample code for CreateUnicastIpAddressEntry I was able to install a generated ipv6 slaac ip address in the windows internal ip addresses table on a PC. There were then two ways to prove its existence: run the GetUnicastAddressEntry sample code which I was having problems with, or simply run the application to see if the bind() and listen() now worked. I did the latter and observed, using netstat -a, that the RFC7217 generated address did indeed appear in the readout as a listening socket.

Note To Other or Future RFC7217 IPv6 SLAAC Implementers:

I had a problem with understanding what the Global Routing Prefix was, since RFC7217 did not define this. Here is the correct diagram for an ipv6 slaac address:

|<----------Global Routing Prefix---------->|<--------Interface------------------------>|
| 001 |<----45 bits---------->|<--16 bits-->|<-----------64 bits----------------------->|
|<--Internet Routing Prefix-->|<-Subnet ID->|<--------Interface ID--------------------->| 

I say correct because finding out what the correct format of the Network ID that RFC7217 expected, was a problem. For that, I went to RFC3587. But there was a format error in the standard, which lead to an errata concerning the Global Routing Prefix diagram. Note that in addition to implementing the Interface ID which RFC7217 covers, you also ought to implement the 16-bit Subnet ID which RFC3587 describes as this: The subnet field is designed to be structured hierarchically by site administrators. However, using the entire 64 bits of the Routing Advertisement's (RA) prefix seems to work just fine. 7217 says you can either use an RA's prefix or Linked Local, depending on your app I presume. I used the RA because I wanted my resulting ip addresses to be globally routable.

Current Limitation:

Currently, Microsoft requires that the CreateUnicastIpAddressEntry API call be executed with administrator privileges. In the Microsoft's Developer Community I have made this request: Call the CreateUnicastIpAddressEntry function as user instead of as administrator. I think the words site administrator have confused Microsoft into thinking that the administrator privilege is necessary. IMO it is not and presents an undue and clumsy burden on the end user.

Other RFC7212 IPv6 SLAAC Windows C++ Implementations:

To my knowledge this is the first windows implementation.

Conclusion:

Without an ability to distribute ip address generation (read: wrest prefix delegation from the ISPs), there is no way to implement real distributed decentralized applications with self-owned nodes. With this capability implementing DApps becomes possible. With privately generated Global Unicast IP Addresses one will no longer have to let his or her data or keys of any kind be copied into centralized platforms. Implementing RFC7217 fixes this internet issue.

Lastly, IPv6 pundits currently believe all IPv6 addresses need to be delegated from your ISP. This is an unfortunate misconception since it inherently limits the distributed-ness of resulting downstream applications. This windows implementation proves otherwise.