背景:
我正在尝试加快使用符合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 地址将显示LISTENING
为netstat -a
.