问题描述
背景:我正在尝试加快使用 RFC7217 兼容的ipv6地址的速度.为此,我编写了创建有效的可路由ipv6地址(如2600:8806:2700:115:c4a3:36d8:77e2:cd1e
)的代码.我知道我需要先在Windows中输入新地址,然后才能绑定().我认为这两种方法可以解决问题.因此,使用我的IP地址之一,我执行了 CreateUnicastIpAddressEntry .然后,使用相同的IP地址,执行了 GetUnicastIpAddressEntry .
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.
问题:
我希望再次获取IP地址.相反,我得到了 ERROR_NOT_FOUND (2).
I expected to retrieve the ip address again. Instead, I got ERROR_NOT_FOUND (2).
分析:我知道该IP地址正在进入系统,因为如果我第二次使用相同的IP地址运行CreateUnicastIpAddressEntry,则会得到 ERROR_OBJECT_ALREADY_EXISTS .
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.
问题:
在这两种ip方法中经验丰富的人是否知道此错误代码在此上下文中的含义?对于这两个Windows ip方法,输入并返回相同的IP地址是否合理?
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?
CreateUnicastIpAddressEntry的示例代码需要一些工作,因此,如果有人想尝试,可以将我的更改及其上载到某个地方. GetUnicastIpAddressEntry示例代码几乎是开箱即用的.
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.
以下是修改后的示例代码,说明了为了使CreateUnicastIpAddressEntry()
工作并使MFC能够创建套接字,绑定到该套接字并监听它,我必须进行的更改.
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.
我修改了CreateUnicastIpAddressEntry()
示例代码以使其适用于IPv6.我所有的评论都以RT:date开头.其余的都是原始示例代码编写者的.我还对生成的特定IPv6 Slaac地址进行了硬编码,其中2600:8806:2700
是从我的特定路由器广告的前缀中提取的,bf72
是子网ID,对我来说,它是1到65535之间的随机唯一数字.并且是单个RFC7217兼容的接口ID,此处用于测试目的.
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;
}
这是MFC套接字,绑定和侦听代码片段,显示了如何使用MFC,以便它可以与IPv6 ip地址一起使用. Microsoft文档说MFC不适用于IPv6,但这是因为Create函数的地址族参数默认为AF_INET(IPv4).因此,如果调用MFC的基础函数,则可以将地址族参数设置为AF_INET6.
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.
这是经过修改的MFC Socket
调用:
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;
}
请注意分别的Bind
调用,一个用于标准MFC代码的AF_INET,一个用于AF_INET6.
Notice the separate Bind
calls, one for AF_INET which is standard MFC code, and one for 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()
代码将新地址获取到内部表中的地方.
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.
所有内容放在一起,ip地址在netstat -a
的读数中将显示为LISTENING
.
All put together, the ip address will appear as LISTENING
in the readout of netstat -a
.
推荐答案
现在有效的方法:
修复了 CreateUnicastIpAddressEntry 的示例代码后,我能够在PC上的Windows内部ip地址表中安装生成的ipv6 slaac
ip地址.然后,有两种方法可以证明其存在:运行我遇到问题的 GetUnicastAddressEntry 示例代码,或者只是运行应用程序以查看bind()
和listen()
现在是否可以工作.我做了后者,并使用netstat -a
观察到,RFC7217生成的地址确实确实在监听器中显示为读数.
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.
其他或以后的RFC7217 IPv6 SLAAC实施者注意:
由于RFC7217没有定义Global Routing Prefix
,所以我在理解Global Routing Prefix
时遇到了问题.这是ipv6 slaac
地址的正确图表:
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--------------------->|
我说正确,因为找出RFC7217期望的正确的网络ID格式是一个问题.为此,我去了 RFC3587 .但是标准中存在格式错误,这导致了有关Global Routing Prefix
图的勘误表.请注意,除了实现RFC7217涵盖的Interface ID
之外,您还应该实现RFC3587所描述的16位Subnet ID
:子网字段旨在由站点管理员分层构建.但是,使用路由广告(RA)前缀的整个64位似乎很好. 7217表示您可以使用RA的前缀或链接的本地",具体取决于您假定的应用程序.我之所以使用RA,是因为我希望得到的ip地址可以全局路由.
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.
当前限制:
当前,Microsoft要求使用administrator
特权执行CreateUnicastIpAddressEntry
API调用.在Microsoft's Developer Community
中,我发出了以下请求:以用户身份而不是管理员身份调用CreateUnicastIpAddressEntry函数.我认为站点管理员一词使Microsoft感到困惑,认为必须具有 administrator特权. IMO并非如此,并且给最终用户带来了不适当的笨拙负担.
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.
其他RFC7212 IPv6 SLAAC Windows C ++实现:
据我所知,这是Windows的第一个实现.
To my knowledge this is the first windows implementation.
结论:
如果没有分发IP地址生成的能力(请参阅:来自ISP的wrest前缀委托),则无法利用自有节点来实现真正的分布式分散式应用程序.借助此功能,可以实现 DApps 成为可能.使用私有生成的全球单播IP地址,人们将不再需要将其数据或任何类型的密钥复制到集中式平台中.实施RFC7217可以解决此互联网问题.
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.
最后,IPv6专家目前认为所有IPv6地址都需要从您的ISP委派.这是一个不幸的误解,因为它固有地限制了最终下游应用程序的分布范围. Windows的实现反而证明了这一点.
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.
这篇关于CreateUnicastIpAddressEntry之后无法获取UniUnicastIpAddressEntry的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!