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
.