CreateUnicastIpAddressEntry 后无法获取 UnicastIpAddressEntry

Unable to GetUnicastIpAddressEntry after CreateUnicastIpAddressEntry

背景: 我正在努力加快使用 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.

的速度

问题:

我希望再次检索 IP 地址。相反,我得到了 ERROR_NOT_FOUND (2).

分析: 我知道 IP 地址正在进入系统,因为如果我 运行 CreateUnicastIpAddressEntry 第二次使用相同的 IP 地址,我会得到 ERROR_OBJECT_ALREADY_EXISTS.

问题:

有没有对这两种 ip 方法有经验的人知道这个错误代码在这种情况下意味着什么?对于这两种 windows ip 方法,输入和返回相同的 ip 地址是否合理?

CreateUnicastIpAddressEntry 的示例代码需要一些工作,所以如果有人想尝试的话,我可以将它和我的更改一起上传到某个地方。 GetUnicastIpAddressEntry 示例代码几乎 运行 开箱即用。

编辑 1:

以下是修改后的示例代码,说明了我必须进行的更改,以便 CreateUnicastIpAddressEntry() 正常工作,并且 MFC 能够创建套接字、绑定到它并侦听它。

我修改的 CreateUnicastIpAddressEntry() 示例代码使其适用于 IPv6。我所有的评论都以 RT:date 开头。所有其余的都是原始示例代码编写者的。我还硬编码了一个特定生成的 IPv6 Slaac 地址,其中 2600:8806:2700 取自我特定的路由器广告的前缀,bf72 是子网 ID,就我而言,它是 1 到 65535 之间的随机唯一数字. 而 596c:919b:9499:e0db 是一个单一的 RFC7217 兼容接口 id,这里用于测试目的。

运行这段代码将地址输入到内部地址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;
    }

这里是 MFC 套接字、绑定和侦听代码片段,展示了如何使用 MFC 使其与 IPv6 IP 地址一起工作。 Microsoft 文档说 MFC 不适用于 IPv6,但这是因为 Create 函数的地址族参数默认为 AF_INET (IPv4)。所以如果调用MFC的底层函数,address family参数可以设置为AF_INET6.

这里是修改后的 MFC Socket 调用:

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;
  }

注意单独的 Bind 调用,一个用于 AF_INET 这是标准 MFC 代码,另一个用于 AF_INET6.

这是绑定调用:

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;
}

注意对 EnterUnicastIpAddrIntoInternalTable() 的调用。这可能是您想要使用修改后的 CreateUnicastIpAddressEntry() 代码将新地址获取到内部 table.

的地方

全部放在一起,IP地址将在netstat -a的读数中显示为LISTENING

现在有效:

修复 CreateUnicastIpAddressEntry 的示例代码后,我能够在 windows 内部 IP 地址中安装生成的 ipv6 slaac IP 地址 table 在 PC 上。然后有两种方法可以证明它的存在:运行 我遇到问题的 GetUnicastAddressEntry 示例代码,或者只是 运行 应用程序以查看 bind()listen() 现在工作了。我做了后者并观察到,使用 netstat -a,RFC7217 生成的地址确实作为监听套接字出现在读数中。

其他或未来 RFC7217 IPv6 SLAAC 实施者注意事项:

我无法理解 Global Routing Prefix 是什么,因为 RFC7217 没有对此进行定义。这是 ipv6 slaac 地址的正确图表:

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

我说正确是因为找出 RFC7217 预期的网络 ID 的正确格式是个问题。为此,我去了 RFC3587。但是标准中存在格式错误,这导致了关于 Global Routing Prefix 图的勘误表。请注意,除了实现 RFC7217 涵盖的 Interface ID 之外,您还应该实现 RFC3587 描述的 16 位 Subnet IDThe subnet field is designed to be structured hierarchically由站点管理员 。然而,使用路由通告 (RA) 前缀的整个 64 位似乎工作得很好。 7217 说您可以使用 RA 的前缀或链接的本地,这取决于我认为您的应用程序。我使用 RA 是因为我希望我得到的 ip 地址是全局 routable.

当前限制:

目前,Microsoft 要求 CreateUnicastIpAddressEntry API 调用以 administrator 权限执行。在 Microsoft's Developer Community 中,我提出了这个请求:以用户而不是管理员身份调用 CreateUnicastIpAddressEntry 函数。我认为 站点管理员 一词让 Microsoft 感到困惑,认为 管理员权限 是必要的。 IMO 不是,它给最终用户带来了不当和笨拙的负担。

其他 RFC7212 IPv6 SLAAC Windows C++ 实现:

据我所知,这是第一个 windows 实现。

结论:

如果没有分发ip地址生成的能力(阅读:从ISPs手中夺取前缀委托),就没有办法用自有节点实现真正的分布式去中心化应用。有了这个能力,实现 DApps 成为可能。有了私人生成的全球单播 IP 地址,人们将不再需要将他或她的任何类型的数据或密钥复制到集中式平台中。实施 RFC7217 解决了这个互联网问题。

最后,IPv6 专家目前认为所有 IPv6 地址都需要从您的 ISP 授权。这是一个不幸的误解,因为它本质上限制了最终下游应用程序的分布式。此 windows 实施证明并非如此。