0

背景: 我正在尝试加快使用符合RFC7217的ipv6 地址。为此,我编写了代码来创建一个有效的可路由 ipv6 地址,例如2600:8806:2700:115:c4a3:36d8:77e2:cd1e. 我知道我需要在 windows 中输入新地址,然后才能绑定到它。我认为这两种方法可以解决问题。因此,使用我的一个 IP 地址,我执行了在CreateUnicastIpAddressEntry中找到的示例代码。然后,使用相同的 IP 地址,我执行了GetUnicastIpAddressEntry中的示例代码。

问题:

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

分析: 我知道 IP 地址正在进入系统,因为如果我使用相同的 IP 地址再次运行 CreateUnicastIpAddressEntry,我会得到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这里用于测试目的。

运行此代码会将地址输入到内部地址表中。

    #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 Socket、Bind 和 Listen 代码片段,展示了如何使用 MFC,以便它可以与 IPv6 ip 地址一起使用。Microsoft 文档说 MFC 不适用于 IPv6,但这是因为 Create 函数的地址族参数默认为 AF_INET (IPv4)。所以如果调用MFC的底层函数,地址族参数可以设置为AF_INET6。

这是修改后的 MFCSocket调用:

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调用,一个用于标准 MFC 代码的 AF_INET,另一个用于 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()代码将新地址放入内部表的地方。

所有放在一起,IP 地址将显示LISTENINGnetstat -a.

4

1 回答 1

0

现在有效:

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

致其他或未来 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 涵盖的内容之外,您还应该实现RFC3587 描述Interface ID的 16 位,如下所示:子网字段被设计为由站点管理员分层构建。但是,使用路由广告 (RA) 前缀的全部 64 位似乎工作得很好。7217 说您可以使用 RA 的前缀或 Linked Local,这取决于我认为的应用程序。我使用 RA 是因为我希望得到的 ip 地址是全球可路由的。Subnet ID

电流限制:

目前,Microsoft 要求以特权CreateUnicastIpAddressEntry执行 API 调用。administratorMicrosoft's Developer CommunityI have made this request: Call the CreateUnicastIpAddressEntry function as user 而不是 administrator。我认为站点管理员这个词已经让微软误以为管理员权限是必要的。IMO 并非如此,它给最终用户带来了过度和笨拙的负担。

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

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

结论:

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

最后,IPv6 专家目前认为所有 IPv6 地址都需要从您的 ISP 委托。这是一个不幸的误解,因为它固有地限制了生成的下游应用程序的分布式性。此 Windows 实现证明并非如此。

于 2019-11-15T22:27:28.990 回答