Невозможно получитьUnicastIpAddressEntry после CreateUnicastIpAddressEntry
Предыстория:
я пытаюсь освоить использование IPv6- адресов, совместимых с RFC7217. С этой целью я написал код, который создает действительный адрес ipv6 с возможностью маршрутизации, например2600:8806:2700:115:c4a3:36d8:77e2:cd1e
. Я знаю, что мне нужно ввести новый адрес в окна, прежде чем я смогу привязать к нему (). Я решил, что эти два метода помогут. Итак, используя один из своих IP-адресов, я выполнил образец кода, найденный в CreateUnicastIpAddressEntry. Затем, используя тот же IP-адрес, я выполнил образец кода, найденный в GetUnicastIpAddressEntry.
Проблема:
Я ожидал снова получить IP-адрес. Вместо этого я получил ERROR_NOT_FOUND (2).
Анализ: я знаю, что IP-адрес попадает в систему, потому что, если я запускаю CreateUnicastIpAddressEntry второй раз с тем же IP-адресом, я получаю ERROR_OBJECT_ALREADY_EXISTS.
Вопрос:
Кто-нибудь, имеющий опыт использования этих двух методов ip, знает, что означает этот код ошибки в этом контексте? Является ли ввод и получение одного и того же IP-адреса разумным ожиданием для этих двух методов Windows IP?
Пример кода для CreateUnicastIpAddressEntry требует некоторой доработки, поэтому я могу загрузить его с моими изменениями куда-нибудь, если кто-то захочет попробовать. Пример кода GetUnicastIpAddressEntry почти сразу запускается.
Edit1:
Ниже приведен модифицированный пример кода, иллюстрирующий изменения, которые мне пришлось внести, чтобы CreateUnicastIpAddressEntry()
чтобы работать и MFC, чтобы иметь возможность создавать сокет, связываться с ним и прослушивать его.
В CreateUnicastIpAddressEntry()
пример кода, который я изменил, чтобы он работал для IPv6. Все мои комментарии начинаются с RT: дата. Все остальное - это оригинальный пример кода. Я также жестко запрограммировал определенный сгенерированный IPv6-адрес Slaac, где2600:8806:2700
взято из префикса моего конкретного объявления маршрутизатора, bf72
- это идентификатор подсети, который для моих целей представляет собой случайное уникальное число от 1 до 65535. И 596c:919b:9499:e0db
- это единственный идентификатор интерфейса, совместимый с RFC7217, используемый здесь для целей тестирования.
Запуск этого кода вводит адрес во внутреннюю таблицу адресов.
#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, чтобы он работал с IP-адресом IPv6. В документах Microsoft говорится, что MFC не работает для IPv6, но это связано с тем, что для параметра семейства адресов функции Create по умолчанию установлено значение AF_INET (IPv4). Поэтому, если вы вызываете базовые функции MFC, параметр семейства адресов может быть установлен в 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()
код для внесения нового адреса во внутреннюю таблицу.
Все вместе, IP-адрес будет выглядеть как LISTENING
в считывании netstat -a
.
1 ответ
Что сейчас работает:
После исправления примера кода для CreateUnicastIpAddressEntry я смог установить сгенерированныйipv6 slaac
IP-адрес в таблице внутренних IP-адресов Windows на ПК. Тогда было два способа доказать его существование: запустить образец кода 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, было проблемой. Для этого я обратился к RFC3587. Но в стандарте была ошибка формата, которая привела к ошибкам, касающимсяGlobal Routing Prefix
диаграмма. Обратите внимание, что помимо реализацииInterface ID
который охватывает RFC7217, вам также следует реализовать 16-битный Subnet ID
RFC3587 описывает это так: Поле подсети спроектировано таким образом, чтобы администраторы сайта могли иерархически структурировать его. Однако использование всех 64 бит префикса Routing Advertising (RA), похоже, работает нормально. 7217 говорит, что вы можете использовать префикс RA или Linked Local, я полагаю, в зависимости от вашего приложения. Я использовал RA, потому что хотел, чтобы мои результирующие IP-адреса были глобально маршрутизируемыми.
Текущее ограничение:
В настоящее время Microsoft требует, чтобы CreateUnicastIpAddressEntry
Вызов API должен выполняться с administrator
привилегии. вMicrosoft's Developer Community
Я сделал этот запрос: вызовите функцию CreateUnicastIpAddressEntry как пользователь, а не как администратор. Я думаю, что слова " администратор сайта" сбили Microsoft с толку и заставили думать, что права администратора необходимы. ИМО, это не так и представляет собой чрезмерное и неуклюжее бремя для конечного пользователя.
Другие реализации RFC7212 IPv6 SLAAC для Windows C++:
Насколько мне известно, это первая реализация Windows.
Вывод:
Без возможности распределить генерацию IP-адресов (читай: вырвать делегирование префикса от интернет-провайдеров) невозможно реализовать настоящие распределенные децентрализованные приложения с собственными узлами. Благодаря этой возможности становится возможным внедрение DApps. Благодаря частным образом сгенерированным глобальным одноадресным IP-адресам больше не нужно будет позволять копировать свои данные или ключи любого рода на централизованные платформы. Реализация RFC7217 устраняет эту проблему с Интернетом.
Наконец, эксперты по IPv6 в настоящее время считают, что все IPv6-адреса необходимо делегировать от вашего интернет-провайдера. Это досадное заблуждение, поскольку оно по своей сути ограничивает распределенность получаемых нижестоящих приложений. Эта реализация Windows доказывает обратное.