Книга: Сетевое программирование. От основ до приложений
Назад: Глава 14. Введение в сетевое программирование для ОС Windows
Дальше: Глава 16. Сокетный API в ОС Windows

Глава 15. Адресация в ОС Windows

В ближайшие 10 лет я не вижу большого коммерческого потенциала у интернета.

Билл Гейтс, из выступления на COMDEX, 1994

Введение

В этой главе мы рассмотрим особенности API для работы с адресами в ОС Windows. Мы уделим внимание структурам данных и функциям, используемым в WinSock для выполнения операций с IDN, с IPv4- и IPv6-адресами, таким как преобразо­вание имен в адреса и обратно или разбор строки адреса.

Также мы рассмотрим функции WinSock, позволяющие получать информацию о локальных и удаленных точках подключения.

Затем, по аналогии с главой 2, мы рассмотрим, как работать с DNS и переводить доменные имена в IP-адреса и обратно.

Мы также познакомимся с базой служб, что позволит запрашивать сетевые ресурсы у DNS-серверов и регистрировать свои ресурсы, которые мы хотим предоставлять. Кроме того, коснемся функций регистрации служб в базе и удаления их из базы.

В конце главы затронем тонкие моменты работы с DNS и настройку резолвера.

Структуры адресов IPv4 и IPv6

Структура для адресации в сокете аналогична той, что используется в POSIX-сокетах и была рассмотрена в главе 2.

Приложения указывают IPv4-адрес и порт для обмена через структуру sockaddr_in:

typedef struct sockaddr_in

{

     short          sin_family;

     u_short        sin_port;

     struct in_addr sin_addr;

     char           sin_zero[8];

} SOCKADDR_IN, *PSOCKADDR_IN, *LPSOCKADDR_IN;

Структура sockaddr_in6 тоже аналогична описанной в главе 6. Но структуры адресов для конкретных протоколов определяются иначе.

Для IPv4 структура определена в winsock2.h:

struct in_addr

{

    union

    {

        // IPv4-адрес в формате четырех символов u_char.

        struct

        {

            u_char s_b1;

            u_char s_b2;

            u_char s_b3;

            u_char s_b4;

        } S_un_b;

        // IPv4-адрес в формате двух u_shorts.

        struct

        {

            u_short s_w1;

            u_short s_w2;

        } S_un_w;

        // Адрес IPv4 в формате u_long.

        u_long S_addr;

    } S_un;

};

А для IPv6 в in6addr.h:

typedef struct in6_addr

{

    union

    {

        u_char  Byte[16];

        u_short Word[8];

    } u;

} IN6_ADDR, *PIN6_ADDR, FAR *LPIN6_ADDR;

Структура sockaddr_storage определена в ws2def.h. Она аналогична POSIX-структуре, но имеет дополнительное имя типа — SOCKADDR_STORAGE.

Существует еще одна крайне похожая на sockaddr_storage структура — sockaddr_storage_xp.

Она требуется для работы функций WSK — WinSock Kernel, то есть сокетов в режиме ядра.

Мы данной работы в режиме ядра не касаемся и эту структуру не рассматриваем.

Некоторые функции, например WSASetService(), работают с адресной структурой CSADDR_INFO, которая определена следующим образом:

#include <nspapi.h>

#include <winsock2.h>

 

// Адреса, которые возвращаются с ответом.

typedef struct _CSADDR_INFO

{

    // Локальный адрес, например, установленный bind().

    SOCKET_ADDRESS LocalAddr;

    // Адрес удаленного абонента, который установлен, например connect().

    SOCKET_ADDRESS RemoteAddr;

    // Тип сокета, например SOCK_STREAM.

    int iSocketType;

    // Используемый протокол, например IPPROTO_TCP.

    int iProtocol;

} CSADDR_INFO, *PCSADDR_INFO, *LPCSADDR_INFO;

Эту структуру также может быть удобно использовать для описания соединения между узлами.

Рис. 15.1. Структура адреса CSADDR_INFO

Как видно из рис. 15.1, структуры, которые хранят локальный и удаленный адреса, здесь тоже нестандартные:

typedef struct sockaddr

{

#if (_WIN32_WINNT < 0x0600)

    u_short sa_family;

#else

    // Семейство адресов

    ADDRESS_FAMILY sa_family;

#endif

    // Данные адреса.

    char sa_data[14];

} SOCKADDR, *PSOCKADDR, FAR *LPSOCKADDR;

 

typedef struct _SOCKET_ADDRESS

{

    // Указатель на адрес.

    LPSOCKADDR lpSockaddr;

    // Длина адреса.

    int iSockaddrLength;

} SOCKET_ADDRESS, *PSOCKET_ADDRESS, *LPSOCKET_ADDRESS;

Формирование и конвертация адресов

Для преобразования адресов WinSock API предоставляет большое количество функций, разобраться в которых сразу может быть сложно. Попробуем рассмот­реть большую их часть в этом разделе.

WinSock также содержит расширенные функции, обозначаемые суффиксом Ex.

Например GetAddrInfoEx() — аналог рассмотренной далее GetAddrInfo().

Помимо того что данные функции содержат некоторые дополнительные параметры, например, GetAddrInfoEx() позволяет выбирать провайдер имен, отличный от DNS, они часто используются как асинхронные. Разбирать их в этой главе мы не будем — это одна из тем книги 2.

Некоторые функции для работы с адресами

В WinAPI существует набор функций для работы с числовыми значениями IP-адресов. Большая часть из этих функций не документирована, но они могут быть полезны. Здесь мы рассмотрим некоторые из них.

Для сравнения IP-адресов могут быть использованы следующие функции:

#include <mstcpip.h>

 

// Сравнить адреса одного семейства.

bool INET_ADDR_EQUAL(ADDRESS_FAMILY af, const void* a, const void* b);

 

// Сравнить невыровненные адреса.

bool INET_UNALIGNED_ADDR_EQUAL(ADDRESS_FAMILY af,

                               const void* a, const void* b);

 

// Здесь используется универсальная структура SOCKADDR,

// и семейства адресов сравниваются в первую очередь.

bool INETADDR_ISEQUAL(const SOCKADDR *a, const SOCKADDR *b)

Параметры функций сравнения:

af — семейство адресов: AF_INET либо AF_INET6;

a и b — сравниваемые адреса.

Функции для проверки IP-адреса на то, инициализирован ли он или является подстановочным адресом для всех интерфейсов:

// Это any-адрес, то есть адрес всех сетевых интерфейсов (0.0.0.0)?

bool INET_IS_ADDR_UNSPECIFIED(ADDRESS_FAMILY af, const void* a);

bool INET_IS_UNALIGNED_ADDR_UNSPECIFIED(ADDRESS_FAMILY af, const void* a);

 

// То же самое для SOCKADDR.

bool INETADDR_ISUNSPECIFIED(const SOCKADDR *a);

bool INETADDR_ISANY(const SOCKADDR *a);

 

// Вернуть any-адрес.

const UCHAR* INET_ADDR_UNSPECIFIED(ADDRESS_FAMILY af);

 

// Установить переданный по указателю адрес в any.

void INETADDR_SETANY(PSOCKADDR a);

Проверка на то, что это адрес локальной петли, и получение этого адреса:

// Это адрес локальной петли?

bool INET_IS_ADDR_LOOPBACK(ADDRESS_FAMILY af, const void* a);

bool INETADDR_ISLOOPBACK(const SOCKADDR *a);

 

// Установить значение адреса в адрес локальной петли.

void INETADDR_SETLOOPBACK(PSOCKADDR a);

Проверка на то, что адрес широковещательный или адресует группу многоадресной рассылки:

// Это широковещательный адрес?

bool INET_IS_ADDR_BROADCAST(ADDRESS_FAMILY af, const void* a);

 

// Это адрес многоадресной рассылки?

bool INET_IS_ADDR_MULTICAST(ADDRESS_FAMILY af, const void* a);

Определение длины адресов:

// Определить длину адреса, то есть размер структур IN_ADDR или IN6_ADDR

// для указанного семейства.

size_t INET_ADDR_LENGTH(ADDRESS_FAMILY af);

 

// Определить длину структуры SOCKADDR_IN6 или SOCKADDR_IN.

size_t INET_SOCKADDR_LENGTH(ADDRESS_FAMILY af);

Получение и установка адресного поля из структуры, которая содержит адрес и порт:

// Вернуть поле адреса: sin_addr или sin6_addr.

PUCHAR INETADDR_ADDRESS(const SOCKADDR* a);

 

// Установить поле адреса.

VOID INETADDR_SET_ADDRESS(PSOCKADDR a, const UCHAR *Address);

Получение из той же структуры порта:

// Вернуть поле порта.

USHORT INETADDR_PORT(const SOCKADDR *a);

 

// Установить порт.

void INETADDR_SET_PORT(PSOCKADDR a, USHORT Port);

Определение того, что переданный IPv6-адрес лежит в диапазоне IPv4-адресов.

// Это IPv6-адрес из диапазона IPv4-адресов?

bool INETADDR_ISV4MAPPED(const SOCKADDR *a);

Данные функции, хотя и не документированы, используются уже давно и, вероятно, будут сохранены в API. Какие-то из них можно использовать в коде приложений.

POSIX-совместимые функции

Для получения IP-адресов из строки рекомендуется использовать функции, описанные в главе 2, особенно в кросс-платформенном коде:

#include <ws2tcpip.h>

 

int WSAAPI inet_pton(int family, PCSTR pszAddrString, PVOID pAddrBuf);

Параметры функции inet_pton():

family — семейство адресов: AF_INET либо AF_INET6.

• pszAddrString — указатель на строку-источник, содержащую текстовое представление IP-адреса.

pAddrBuf — указатель на буфер, в котором хранится числовое представление IP-адреса в сетевом порядке байтов.

В случае успеха, так же как и inet_pton() в POSIX, функция вернет 1, а буфер pAddrBuf будет содержать IP-адрес в сетевом порядке байтов.

PCSTR WSAAPI inet_ntop(

    int family,

    const void *pAddr,

    PSTR pStringBuf,

    size_t stringBufSize

);

Параметры функции inet_ntop():

pAddr — указатель на IP-адрес в сетевом байте для преобразования в строку.

• pStringBuf — указатель на буфер, в который сохраняется строковое представление IP-адреса, завершающееся нулем. Размер буфера должен быть не менее 16 символов для IPv4-адреса и не менее 46 символов для IPv6.

stringBufSize — длина буфера pStringBuf.

Возвращаемое значение такое же, как и для POSIX inet_ntop().

Их Windows-аналоги — InetPton() и InetNtop() — были рассмотрены в разделе об ANSI- и Unicode-версиях функций.

Внимание! Данные функции поддерживают только Internet-адреса, то есть IPv4 и IPv6.

Функция inet_addr(), используется для формирования IPv4-адреса:

WINSOCK_API_LINKAGE unsigned long WSAAPI inet_addr(const char FAR * cp);

Параметр cp — строка, заканчивающаяся нулем, представляющая числа адреса в «точечной» нотации.

Числа могут быть десятичными, восьмеричными или шестнадцатеричными. Причем их возможно смешивать и задавать не все компоненты.

Функция вернет значение типа unsigned long. Например:

int result = WSAStartup(MAKEWORD(2, 2), &wsaData);

 

...

// Число, содержащее IP-адрес.

unsigned long ul_addr = inet_addr(argv[1]);

 

// Различные варианты ошибок.

switch (ul_addr)

{

    case INADDR_NONE:

    case INADDR_ANY:

    {

        WSACleanup();

        throw ...

    }

}

Обратная функция:

char FAR * WSAAPI inet_ntoa(in_addr in);

Она преобразует IP-адрес в строковый вид. Так же как и в POSIX, в ОС Windows данные функции являются устаревшими.

В некоторых версиях ОС Windows это могут быть не функции, а макросы. В любом случае пользоваться ими нежелательно по следующим причинам:

• они поддерживают только адреса IPv4 и помечены как нерекомендованные;

• они не являются переносимыми.

Функции WSAStringToAddress() и WSAAddressToString()

Еще одна функция, которая преобразует сетевой адрес из текстовой формы в двоичную:

#include <winsock2.h>

 

int WSAAPI WSAStringToAddress(

    LPSTR addressString,

    int addressFamily,

    LPWSAPROTOCOL_INFO lpProtocolInfo,

    LPSOCKADDR lpAddress,

    LPINT lpAddressLength

);

Параметры функции WSAStringToAddress():

addressString — строка адреса в стандартной текстовой форме.

• addressFamily — семейство адресов, к которому принадлежит адрес в строке addressString.

• lpProtocolInfo — необязательный указатель на структуру LPWSAPROTOCOL_INFOA либо LPWSAPROTOCOL_INFOW. В общем случае на LPWSAPROTOCOL_INFO. Если этот параметр нулевой, вызов направляется провайдеру первого протокола, поддерживающего заданное семейство адресов.

• lpAddress — указатель на структуру, в которой будет сохранен адрес.

lpAddressLength — указатель на длину буфера lpAddress. Если вызов функции успешен, в переменную, на которую указывает этот параметр, будет записан размер структуры sockaddr, возвращенный в параметре lpAddress.

Функция вернет 0 в случае успеха, а в случае ошибки — SOCKET_ERROR.

Если размер буфера недостаточен, функция завершается с кодом ошибки WSAEFAULT, который можно получить, вызвав WSAGetLastError(), как будет рассмотрено в главе 24. А в переменную, на которую указывает lpAddressLength, сохраняется требуемый размера буфера.

Это позволяет динамически подбирать размер при необходимости.

Существует и обратная функция, преобразующая адрес из структуры sockaddr в печатную форму:

int WSAAPI WSAAddressToString(

    LPSOCKADDR lpsaAddress,

    DWORD dwAddressLength,

    LPWSAPROTOCOL_INFO lpProtocolInfo,

    LPSTR lpszAddressString,

    LPDWORD lpdwAddressStringLength

);

Параметры функции WSAAddressToString():

lpsaAddress — указатель на структуру sockaddr, которая будет преобразована в строку.

• dwAddressLength — длина адреса в структуре sockaddr, то есть sizeof(sockaddr). Размер отличается в разных протоколах.

• lpProtocolInfo — указатель на структуру WSAPROTOCOL_INFO конкретного провайдера. Если этот параметр нулевой, вызов направляется провайдеру первого протокола, поддерживающего заданное семейство адресов.

• lpszAddressString — указатель на буфер, в который будет сохранен адрес.

lpdwAddressStringLength — размер буфера адреса. После успешного завершения переменная, на которую указывает параметр, будет содержать размер строки, включая завершающий ее нулевой символ.

Возвращаемые значения те же, что и у предыдущей функции.

Особенность этих функций в том, что вместо указателя на буфер они принимают указатель на структуру sockaddr, в которую сохраняют результат. Кроме того, они позволяют явно выбирать провайдер протокола, используемый для преобразования адресов.

Данные функции поддерживают IPv6, начиная с Windows XP SP1.

Функции RTL

Начиная с версии Windows Vista, в IP Helper существует заголовочный файл ip2string.h, в котором объявлены более гибкие функции преобразования адресов в печатную форму и обратно:

// Строка в IPv4.

NTSYSAPI NTSTATUS RtlIpv4StringToAddress(

    PCSTR s,

    BOOLEAN strict,

    PCSTR *terminator,

    in_addr *addr

);

 

// Строка в IPv6.

NTSYSAPI NTSTATUS RtlIpv6StringToAddress(

    PCSTR s,

    PCSTR *terminator,

    in6_addr *addr

);

Параметры функций RtlIpv4StringToAddress() и RtlIpv6StringToAddress():

s — строка, которая содержит IP-адрес.

• strict — флаг формата. Если значение истинно, строка должна быть IPv4-адресом, представленным строго в десятичном формате из четырех чисел, разделенных точками. В противном случае допускается любая из форм строкового представления IPv4-адреса: десятичная, восьмеричная или шестнадцатеричная.

• terminator — параметр, который получает указатель на символ, завершающий преобразованную строку.

addr — указатель на структуру, в которую будет сохранено двоичное представление адреса.

Существуют и расширенные варианты данных функций:

// Преобразовать строку в IPv4.

NTSYSAPI NTSTATUS RtlIpv4StringToAddressEx(

    PCSTR addressString,

    BOOLEAN strict,

    in_addr *address,

    PUSHORT port

);

 

// Преобразовать строку в IPv6.

NTSYSAPI NTSTATUS RtlIpv6StringToAddressExA(

    PCSTR addressString,

    in6_addr *address,

    PULONG scopeId,

    PUSHORT port

);

Параметры функций RtlIpv4StringToAddressEx() и RtlIpv6StringToAddressExA():

addressString — строка, содержащая IP-адрес.

• strict — см. в описании RtlIpv4StringToAddress() выше.

• address — указатель на выходную переменную для сохранения адреса.

• port — указатель на переменную, в которую должен сохраниться номер порта. Он сохраняется в сетевом порядке байтов. Если в строке он не указан, порт устанавливается равным 0.

scopeId — указатель на идентификатор области IPv6-адреса. Если строка адреса не содержит строкового представления идентификатора области, в этом параметре возвращается 0.

Функции возвращают STATUS_SUCCESS в случае успеха, STATUS_INVALID_PARAMETER в случае некорректного значения параметра адреса либо иную ошибку в других случаях. Инициализации WinSock эти функции не требуют.

Для работы с Ethernet-адресами в том же файле объявлены следующие функции:

// Преобразование Ethernet-адреса в строку.

NTSYSAPI PSTR RtlEthernetAddressToString(

    const DL_EUI48 *addr,

    PSTR s

);

 

// Преобразование Ethernet-адреса из строки.

NTSYSAPI NTSTATUS RtlEthernetStringToAddress(

    PCSTR s,

    PCSTR *terminator,

    DL_EUI48 *addr

);

Параметры функций RtlEthernetAddressToString() и RtlEthernetStringToAddress():

s — строка адреса. Входной или выходной буфер. Формат строки — шесть пар шестнадцатеричных символов, разделенных дефисами, например: F4-CE-46-2D-90-8C.

• terminator — указатель на символ, завершающий преобразованную строку.

addr — Ethernet-адрес в сетевом порядке байтов.

У функции, преобразующей адрес в строку, возвращаемое значение — адрес буфера в случае успеха или нулевой указатель в случае ошибки. Обратная функция возвращает такие же значения, как и функции преобразования IP-адресов, описанные выше.

Структура Ethernet-адреса определена в заголовочном файле netiodef.h:

// Уникальный идентификатор в рамках организации

// — Organizationally Unique Identifier.

union _DL_OUI

{

    UINT8 Byte[3];

    // Первый байт.  0bxxxxxxLG.

    struct

    {

        // Младший значащий бит.

        UINT8 Group : 1;

        UINT8 Local : 1;

    };

};

 

typedef union _DL_OUI DL_OUI, *PDL_OUI;

 

// Идентификатор расширения — Extension Identifier — для адресов EUI-48.

union _DL_EI48

{

    UINT8 Byte[3];

};

 

typedef union _DL_EI48 DL_EI48, *PDL_EI48;

 

// Адрес EUI-48.

union _DL_EUI48

{

    UINT8 Byte[6];

    struct

    {

        DL_OUI Oui;

        DL_EI48 Ei48;

    };

};

 

typedef union _DL_EUI48 DL_EUI48, *PDL_EUI48;

Но включать данный файл обычно не стоит, так как структура непрозрачная и объявлена в нескольких других заголовочных файлах.

Данные функции удобны, но не переносимы. Кроме того, функции, начинающиеся с Rtl, то есть Run-Time Library, не включены в Windows SDK, поскольку они содержатся в библиотеке Ntdll.dll, иными словами, являются оболочками над вызовами ядра. На это же указывает макрос NTSYSAPI.

Макрос NTSYSAPI при использовании функции раскрывается в __declspec(dllimport).

Целесообразность их использования зависит от того, планируется ли в будущем переносить код на другую ОС.

Конвертация между длиной префикса адреса и маской подсети

В IP Helper API имеется несколько полезных функций для конвертации маски в длину префикса и длины в маску:

#include <netioapi.h>

 

NETIOAPI_API ConvertLengthToIpv4Mask(ULONG MaskLength, PULONG Mask);

NETIOAPI_API ConvertIpv4MaskToLength(ULONG Mask, PUINT8 MaskLength);

Параметры функций ConvertLengthToIpv4Mask() и ConvertIpv4MaskToLength():

MaskLength — длина префикса;

Mask — маска.

Функции вернут NO_ERROR в случае успеха.

Вызывать функцию для конвертации длины префикса в маску удобно следующим образом:

union

{

    // Маска как число.

    ULONG ul;

    // Маска по байтам.

    uint8_t b[4];

} mask;

 

ConvertLengthToIpv4Mask(netmask_length, &mask.ul);

Разбор строки адреса

Строку, которая содержит адрес и порт, можно разобрать, используя системный API:

#include <iphlpapi.h>

 

typedef struct NET_ADDRESS_INFO_

{

    NET_ADDRESS_FORMAT Format;

    union

    {

        struct

        {

            WCHAR Address[DNS_MAX_NAME_BUFFER_LENGTH];

            WCHAR Port[6];

        } NamedAddress;

 

        SOCKADDR_IN Ipv4Address;

        SOCKADDR_IN6 Ipv6Address;

        SOCKADDR IpAddress;

  };

} NET_ADDRESS_INFO, *PNET_ADDRESS_INFO;

 

DWORD ParseNetworkString(

    const WCHAR *NetworkString,

    DWORD Types, PNET_ADDRESS_INFO AddressInfo,

    USHORT *PortNumber, BYTE *PrefixLength);

Параметры функции ParseNetworkString():

NetworkString — строка для разбора.

Types — тип строки:

NET_STRING_IP_ADDRESS — IPv4-адрес в стандартной десятичной нотации с точками или адрес IPv6 в стандартной шестнадцатеричной нотации. Идентификатор области IPv6 может присутствовать, а порт или префикс — нет. Соответствует NET_STRING_IPV4_ADDRESS | NET_STRING_IPV6_ADDRESS.

NET_STRING_IP_ADDRESS_NO_SCOPE — адрес IPv4 или адрес IPv6 без идентификатора области. Соответствует NET_STRING_IPV4_ADDRESS | NET_STRING_IPV6_ADDRESS_NO_SCOPE.

NET_STRING_IP_SERVICE — служба IPv4 или служба IPv6. Порт обязателен в строке. Идентификатор области IPv6 может присутствовать, префикс — нет. Соответствует NET_STRING_IPV4_SERVICE | NET_STRING_IPV6_SERVICE.

NET_STRING_IP_SERVICE_NO_SCOPE — служба IPv4 или служба IPv6. Порт обязателен в строке. Идентификатор области IPv6 и префикс не должны присутствовать в строке. Соответствует NET_STRING_IPV4_SERVICE | NET_STRING_IPV6_SERVICE_NO_SCOPE.

NET_STRING_IP_NETWORK — сеть IPv4 или IPv6. Префикс в нотации CIDR требуется как часть сетевой строки, а порт или идентификатор области — нет. Соответствует NET_STRING_IPV4_NETWORK | NET_STRING_IPV6_NETWORK.

NET_STRING_ANY_ADDRESS — адрес IPv4, адрес IPv6 или имя DNS. Идентификатор области IPv6 может присутствовать в строке, сетевой порт или префикс — нет. Соответствует типам NET_STRING_NAMED_ADDRESS | NET_STRING_IP_ADDRESS.

NET_STRING_ANY_ADDRESS_NO_SCOPE — адрес IPv4, адрес IPv6 или имя DNS. Идентификатор области IPv6, порт и префикс не должны присутствовать в строке. Соответствует типам NET_STRING_NAMED_ADDRESS | NET_STRING_IP_ADDRESS_NO_SCOPE.

NET_STRING_ANY_SERVICE — служба IPv4, IPv6 или имя DNS. Порт должен присутствовать в строке. Идентификатор области IPv6 может присутствовать, а префикс — нет. Соответствует типам NET_STRING_NAMED_SERVICE | NET_STRING_IP_SERVICE.

NET_STRING_ANY_SERVICE_NO_SCOPE — указывает на службу IPv4, IPv6 или имя DNS. Порт требуется в строке, а идентификатора области IPv6 и префикса в ней быть не должно. Соответствует NET_STRING_NAMED_SERVICE | NET_STRING_IP_SERVICE_NO_SCOPE.

AddressInfo — сюда будет записан разобранный адрес.

PortNumber — по указателю в эту переменную будет записан порт в порядке байтов узла. Если порта в строке нет, будет записан 0.

PrefixLength — сюда будет записана длина префикса.

В случае успеха функция вернет ERROR_SUCCESS. Она может вернуть ERROR_INSUFFICIENT_BUFFER, если размер буфера недостаточен для записи адреса.

Преобразование IDN

Для работы с интернационализированными доменными именами, описанными в RFC 3490 «Internationalizing Domain Names in Applications (IDNA)», существует несколько функций WinAPI.

Рассмотрим их прототипы:

#include <winnls.h>

 

// Преобразовать IDN в Punycode.

int IdnToUnicode(

    DWORD dwFlags,

    LPCWSTR lpASCIICharStr,

    int cchASCIIChar,

    LPWSTR lpUnicodeCharStr,

    int cchUnicodeChar

);

 

// Преобразовать IDN или другую метку в форму NamePrep,

// указанную в RFC 3491.

int IdnToNameprepUnicode(

    DWORD dwFlags,

    LPCWSTR lpUnicodeCharStr,

    int cchUnicodeChar,

    LPWSTR lpNameprepCharStr,

    int cchNameprepChar

);

 

// Преобразовать IDN в Punycode.

int IdnToAscii(

    DWORD dwFlags,

    LPCWSTR lpUnicodeCharStr,

    int cchUnicodeChar,

    LPWSTR lpASCIICharStr,

    int cchASCIIChar

);

Параметры функций IdnToUnicode(), IdnToNameprepUnicode(), IdnToAscii():

dwFlags — флаги, определяющие параметры преобразования:

• IDN_ALLOW_UNASSIGNED — разрешить включение неназначенных кодовых точек во входную строку. Если они присутствуют и не разрешены, функция вернет ошибку ERROR_INVALID_NAME. Флаг позволяет функции обрабатывать символы, которые в настоящее время недопустимы в IDN, но могут быть разрешены в более поздних версиях стандарта IDNA.

• IDN_USE_STD3_ASCII_RULES — отфильтровать символы ASCII, которые не разрешены в именах STD3. Единственными символами ASCII, разрешенными во входной строке, являются буквы, цифры и дефис. Строка не может начинаться или заканчиваться дефисом.

• IDN_EMAIL_ADDRESS — если флаг установлен, в случае ошибки в адресе e-mail будет возвращен резервный адрес, если он доступен, например <local>@gmail.com.

• IDN_RAW_PUNYCODE — отключить проверку и сопоставление Punycode.

• lpASCIICharStr — указатель на буфер, который получает строку, состоящую только из набора символов ASCII.

• cchASCIIChar — размер входной ASCII-строки либо размер буфера для выходной ASCII-строки в lpASCIICharStr.

• lpUnicodeCharStr — указатель на Unicode-строку, представляющую IDN в кодировке UTF-16.

cchUnicodeChar — размер входной Unicode-строки, переданной в lpUnicodeCharStr, либо размер выходного буфера для Unicode-строки.

Сначала в функции передается входной буфер, затем — выходной. Если в качестве выходного буфера передан нулевой указатель и в соответствующее значение длины установлено в 0, функции вернут требуемый размер выходного буфера.

Иначе в случае успеха будет возвращено количество символов в буфере результата, а в случае ошибки — 0.

Внимание! POSIX-функции возвращают 0 в случае успеха, но данные функции возвращают 0 в случае ошибки.

Чтобы получить расширенную информацию об ошибке, приложение может вызвать функцию GetLastError() с одним из следующих кодов ошибки:

ERROR_INSUFFICIENT_BUFFER — предоставленный размер буфера был недостаточно большим или равным 0.

ERROR_INVALID_FLAGS — значения, предоставленные для флагов, недействительны.

ERROR_INVALID_NAME — указано недопустимое имя.

ERROR_INVALID_PARAMETER — одно из значений параметра было недопустимым.

ERROR_NO_UNICODE_TRANSLATION — в строке обнаружен недопустимый код Unicode.

В главах 24 и 25 мы подробнее рассмотрим обработку ошибок и исключительных ситуаций, в частности данную функцию.

Функции для работы с DNS

Функции, описанные в главе 2, доступны и в ОС Windows. В большинстве случаев уместно использовать их. Но для настройки и тонкой работы с DNS существует специфичный Windows API.

База узлов и работа с ней

В составе WinSock имеется структура hostent, которая описана в главе 2. Она похожа на структуру в POSIX, но параметры семейства адресов имеют тип short, а не int:

typedef struct hostent

{

    // Официальное имя узла, FQDN.

    char *h_name;

    // Псевдонимы.

    char **h_aliases;

    // Семейство адресов.

    short h_addrtype;

    // Длина адреса.

    short h_length;

    // Список адресов.

    char **h_addr_list;

} HOSTENT, *PHOSTENT, *LPHOSTENT;

С данной структурой работают функции gethostbyaddr() для получения структуры по адресу узла и gethostbyname() по его имени.

Напоминаем, что данные функции являются устаревшими, поэтому использовать их не рекомендуется. Но при необходимости их описание можно посмотреть в MSDN.

Функции для обхода базы узлов в WinSock отсутствуют.

Получение списка адресов с помощью getaddrinfo()

Прототипы функций для получения адресов с использованием DNS:

#include <ws2tcpip.h>

 

int WSAAPI getaddrinfo(

    PCSTR pNodeName,

    PCSTR pServiceName,

    const ADDRINFOA *pHints,

    PADDRINFOA *ppResult

);

 

int WSAAPI GetAddrInfoW(

    PCWSTR pNodeName,

    PCWSTR pServiceName,

    const ADDRINFOW *pHints,

    PADDRINFOW *ppResult

);

 

#define GetAddrInfoA getaddrinfo

Существует также функция GetAddrInfoEx(), которая помимо работы в асинхронном режиме, дает возможность задавать таймаут ответа.

Функция getaddrinfo() имеет POSIX-совместимый прототип:

pNodeName — имя узла либо строка, которая содержит его IPv4-адрес в «точечной» нотации либо IPv6-адрес в шестнадцатеричной нотации.

• pServiceName — строка, которая содержит имя службы или порт.

• pHints — указатель на структуру, аналогичную addrinfo.

ppResult — список возвращенных адресов.

Функция также возвращает 0 при успехе и код ошибки в ином случае.

В nspapi.h объявлена функция GetAddressByName(), которая запрашивает пространство имен, чтобы получить информацию о сетевом адресе для указанной сетевой службы.

Данная функция является устаревшей и не используется в WinSock2. Поэтому мы ее рассматривать не будем.

Структура ADDRINFOA немного отличается от POSIX-структуры addrinfo:

struct addrinfo

{

    // AI_PASSIVE, AI_CANONNAME и т.д.

    int ai_flags;

    // AF_UNSPEC, AF_INET, AF_INET6, AF_NETBIOS, AF_BTH, AF_IRDA.

    int ai_family;

    // SOCK_STREAM, SOCK_DGRAM.

    int ai_socktype;

    // IPPROTO_TCP, IPPROTO_UDP, IPPROTO_RM или IPPROTO_PGM для надежной

    // широковещательной передачи.

    int ai_protocol;

    // Размер ai_addr в байтах.

    size_t ai_addrlen;

    // Полное каноническое имя. Здесь присутствует отличие:

    // в POSIX-структуре данное поле расположено после ai_addr.

    char *ai_canonname;

    // struct sockaddr_in или _in6.

    struct sockaddr *ai_addr;

    // Следующий узел списка.

    struct addrinfo *ai_next;

} ADDRINFOA, *PADDRINFOA;

Разница в том, что поле ai_canonname расположено перед ai_addr, тогда как в POSIX-структуре — после.

Структура ADDRINFOW, которая является типом аргумента функции GetAddrInfoW(), отличается тем, что для канонического имени в ней используется строка широких символов:

typedef struct addrinfoW

{

    int ai_flags;

    int ai_family;

    int ai_socktype;

    int ai_protocol;

    size_t ai_addrlen;

    // Каноническое имя в UNICODE.

    PWSTR ai_canonname;

    struct sockaddr  *ai_addr;

    struct addrinfoW *ai_next;

} ADDRINFOW, *PADDRINFOW;

В поле ai_flags поддерживаются флаги, которые есть в POSIX, но отличаются от тех, которые есть в Linux. Флаги AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST, AI_NUMERICSERV, AI_V4MAPPED работают, как описано в главе 2.

Флаг AI_ADDRCONFIG говорит о том, что GetAddrInfoW() будет выполнять разрешение имен только в том случае, если настроен глобальный адрес, а не адрес локальной петли.

Флаги AI_IDN, AI_CANONIDN, AI_IDN_ALLOW_UNASSIGNED, AI_IDN_USE_STD3_ASCII_RULES не поддерживаются.

Windows также имеет несколько флагов, которые используются только в этой ОС, причем в разных версиях они разные. Следующие флаги поддерживаются, начиная с Windows Vista, и относятся только к адресам электронной почты, то есть к провайдеру пространства имен NS_EMAIL:

AI_NON_AUTHORITATIVE — если флаг установлен, провайдер возвращает результаты как с авторитетных, так и с неавторитетных DNS-серверов. В противном случае — только с авторитетных.

• AI_SECURE — если установлен бит AI_SECURE, провайдер будет возвращать результаты, полученные безопасно, чтобы свести к минимуму возможность спуфинга.

• AI_RETURN_PREFERRED_NAMES — если флаг установлен, провайдер вернет предпочтительные имена для публикации. В параметре pNodeName не должно указываться имя.

Авторитетный DNS-сервер является конечным владельцем IP-адреса домена, поиск которого производится, и хранит информацию о записях DNS.

Авторитетный DNS-сервер обычно является последним шагом в цепочке рекурсивных запросов IP-адреса и содержит информацию, специфичную для доменного имени, которое он обслуживает.

Кроме него могут существовать, например, кэширующие DNS-серверы.

Еще несколько флагов поддерживаются, начиная с Windows 7 и 8:

AI_FQDN — если установлен этот флаг и указана только одна компонента имени, функция getaddrinfo() вернет полное доменное имя в атрибуте ai_canonname результирующей структуры addrinfo. При установке флага AI_CANONNAME возвращается каноническое имя из записи DNS, которое может отличаться от FQDN. Поэтому флаги AI_FQDN и AI_CANONNAME нельзя устанавливать одновременно: это приведет к ошибке EAI_BADFLAGS.

• AI_FILESERVER — подсказка провайдеру пространства имен о том, что запрашиваемое имя хоста используется в сценарии совместного доступа к файлам. Провайдер пространства имен может игнорировать этот флаг.

AI_DISABLE_IDN_ENCODING — отключить автоматическое кодирование IDN в Punycode.

Удалять выделенные структуры нужно, используя те же функции, что и в POSIX:

void WSAAPI freeaddrinfo(PADDRINFOA pAddrInfo);

void WSAAPI FreeAddrInfoW(PADDRINFOW pAddrInfo);

 

#define FreeAddrInfoA freeaddrinfo

 

#ifdef UNICODE

#define FreeAddrInfo FreeAddrInfoW

#else

#define FreeAddrInfo FreeAddrInfoA

#endif

Получение сообщений об ошибках

Для преобразования кода ошибки getaddrinfo() в строку используется уже описанная в главе 2 функция gai_strerror().

В Windows эта функция представляет собой вызов FormatMessage() с флагом FORMAT_MESSAGE_FROM_SYSTEM. То есть ошибки getaddrinfo() здесь не отличаются от других системных ошибок.

Пример использования данной функции на практике приведен в главе 2 — это резолвер, который успешно компилируется и работает в ОС Windows.

Регистрация и удаление имен

Иногда требуется зарегистрировать имя некоторой службы и связанные адреса в базе. Для этого используется следующая функция:

#include <ws2tcpip.h>

 

int WSAAPI SetAddrInfoEx(

    PCTSTR pName,

    PCTSTR pServiceName,

    SOCKET_ADDRESS *pAddresses,

    DWORD dwAddressCount,

    LPBLOB lpBlob,

    DWORD dwFlags,

    DWORD dwNameSpace,

    LPGUID lpNspId,

    timeval *timeout,

    LPOVERLAPPED lpOverlapped,

    LPLOOKUPSERVICE_COMPLETION_ROUTINE lpCompletionRoutine,

    LPHANDLE lpNameHandle

);

Параметры функции SetAddrInfoEx():

pName — имя, под которым должны быть зарегистрированы адреса, либо имя для удаления адреса.

pServiceName — имя службы, ассоциированное с именем в pName.

pAddresses — указатель на необязательный массив адресов.

dwAddressCount — число адресов в массиве. Если 0, все адреса для pName будут удалены.

lpBlob — данные, специфичные для провайдера.

dwFlags — флаги, специфичные для провайдера.

dwNameSpace — идентификатор пространства имен, например:

NS_ALL — все активные неймспейсы;

NS_DNS — данные, получаемые от DNS, в том числе IP-адреса;

• NS_EMAIL — пространство имен электронной почты.

• lpNspId — необязательный идентификатор конкретного провайдера имен, который используется для работы с адресами.

• timeout — тайм-аут ожидания, по завершении которого вызов будет завершен принудительно.

• lpOverlapped — необязательный указатель на структуру, используемую для перекрывающегося ввода-вывода.

• lpCompletionRoutine — необязательный указатель на процедуру, вызываемую при успешном завершении операции.

lpNameHandle — параметр, требуемый для асинхронных операций. Зарезервирован и должен быть равен 0.

В случае успеха функция возвращает 0, в случае неудачи — код ошибки. Регистрацию и отмену регистрации на момент написания книги поддерживает только провайдер NS_EMAIL, начиная с Windows Vista.

Эта функция не поддерживает асинхронные операции, и относящиеся к ним параметры не используются.

Остальные провайдеры, в том числе работающие в пространстве имен NS_DNS, данную функциональность не поддерживают.

Преобразование адресов в имена

Для обратного преобразования используется функция getnameinfo():

#include <ws2tcpip.h>

 

// Максимальный размер для буфера, на который указывает pServiceBuffer.

#define NI_MAXSERV 32

// Максимальный размер для буфера, на который указывает pNodeBuffer.

#define NI_MAXHOST 1025

 

int WSAAPI getnameinfo(

    const SOCKADDR *pSockaddr,

    socklen_t sockaddrLength,

    PCHAR pNodeBuffer,

    DWORD nodeBufferSize,

    PCHAR pServiceBuffer,

    DWORD serviceBufferSize,

    int flags

);

 

int WSAAPI GetNameInfoW(

    const SOCKADDR *pSockaddr,

    socklen_t sockaddrLength,

    PWCHAR pNodeBuffer,

    DWORD nodeBufferSize,

    PWCHAR pServiceBuffer,

    DWORD serviceBufferSize,

    int flags

);

 

#define GetNameInfoA getnameinfo

Макросы, содержащие максимальный размер, также определены в ws2tcpip.h.

Прототипы функций почти не отличаются от POSIX-вариантов, рассмотренных в главе 2. Значения флагов также одинаковые, но флаг NI_NUMERICSCOPE отсутствует.

Возвращаемые значения также совместимы. Данные функции поддерживаются, начиная с Windows XP SP2.

База служб

В ОС Windows имеется возможность получать данные сетевых служб подобно тому, как описано в главе 2. Однако для этого используется иной API.

К базе можно делать запросы, описанные структурой WSAQUERYSET:

#include <winsock2.h>

 

typedef struct _WSAQuerySet

{

    // Размер структуры: sizeof(WSAQUERYSET).

    DWORD dwSize;

    // Имя службы. Может содержать подстановочные символы.

    LPTSTR lpszServiceInstanceName;

    // Идентификатор класса. Обязателен для запроса.

    LPGUID lpServiceClassId;

    // Версия провайдера имен.

    LPWSAVERSION lpVersion;

    // Необязательный комментарий. Игнорируется запросом.

    LPTSTR lpszComment;

    // Пространство имен: NS_ALL, NS_DNS, NS_EMAIL и т.п.

    // Может использоваться запросом.

    DWORD dwNameSpace;

    // Необязательный GUID провайдера имен. Если он передан в запросе,

    // будет запрошен только указанный провайдер.

    LPGUID lpNSProviderId;

    // Указатель на необязательную начальную точку запроса

    // в иерархическом пространстве имен.

    LPTSTR lpszContext;

    // Размер в байтах массива ограничений протокола.

    DWORD dwNumberOfProtocols;

    // Опциональный список протоколов.

    // Возвращены будут только службы, которые используют эти протоколы.

    LPAFPROTOCOLS lpafpProtocols;

    // Указатель на необязательную строку запроса.

    LPTSTR lpszQueryString;

    // Количество элементов в массиве lpcsaBuffer. Не используется запросом.

    DWORD dwNumberOfCsAddrs;

    // Указатель на массив структур CSADDR_INFO. Не используется запросом,

    // но может быть использован в ответе вместе с полем выше.

    LPCSADDR_INFO lpcsaBuffer;

    // Не используется запросом.

    DWORD dwOutputFlags;

    // Данные, специфичные для провайдера.

    LPBLOB lpBlob;

} WSAQUERYSET, *PWSAQUERYSET, *LPWSAQUERYSET;

Структура WSAQUERYSET изображена на рис. 15.2. В эту же структуру будет возвращаться результат запроса.

Рис. 15.2. Структура WSAQUERYSET

В lpBlob хранится рассмотренная ранее структура hostent. Формат ее хранения показан на рис. 15.3.

Указатели внутри нее — это смещения данных от начала атрибута lpBlobData. Поэтому чтобы получить данные как через обычные указатели, необходимо добавить к ним базовый адрес структуры.

В массивах h_aliases и h_addr_list указатели также являются смещениями, каждое 32-битное поле в месте ссылки будет состоять из смещений.

Рис. 15.3. Структура hostent в BLOB структуры WSAQUERYSET

Для провайдеров имен версии 2 используется структура WSAQUERYSET2.

Она отличается от WSAQUERYSET только тем, что в новой структуре отсутствует поле lpServiceClassId.

Используемые протоколы описываются в поле lpafpProtocols, которое содержит массив структур AFPROTOCOLS:

#include <nspapi.h>

#include <winsock2.h>

 

// Структура протоколов, которыми могут ограничиваться запросы.

typedef struct _AFPROTOCOLS

{

    // Семейство, например AF_INET.

    int iAddressFamily;

    // Протокол, например IPPROTO_TCP.

    int iProtocol;

} AFPROTOCOLS, *PAFPROTOCOLS, *LPAFPROTOCOLS;

Функция WSALookupServiceBegin() выполняет первый запрос:

#include <winsock2.h>

 

int WSAAPI WSALookupServiceBegin(

    LPWSAQUERYSET lpqsRestrictions,

    DWORD dwControlFlags,

    LPHANDLE lphLookup

);

Параметры функции WSALookupServiceBegin():

lpqsRestrictions — критерии поиска.

• dwControlFlags — флаги, управляющие глубиной поиска:

• LUP_DEEP — сделать рекурсивный запрос и вернуть не только первый уровень.

• LUP_CONTAINERS или LUP_NOCONTAINERS — возвращать только контейнеры или не возвращать контейнеры. Обычно этот флаг используется для того, чтобы запрашивать устройства, например, при работе с Bluetooth.

• LUP_NEAREST — упорядочить результаты по метрике расстояния. Как будет рассчитана метрика, зависит от провайдера.

• LUP_RETURN_NAME — получить имя в lpszServiceInstanceName.

• LUP_RETURN_TYPE — получить тип в lpServiceClassId.

LUP_RETURN_VERSION — получить версию в lpVersion.

LUP_RETURN_COMMENT — получить комментарий в lpszComment.

LUP_RETURN_ADDR — получить адреса в lpcsaBuffer.

LUP_RETURN_BLOB — получить данные провайдера в lpBlob.

LUP_RETURN_ALIASES — возвращать информацию о псевдонимах при следующих запросах, выполняемых через WSALookupServiceNext(). Каждый возвращаемый псевдоним будет иметь установленный флаг RESULT_IS_ALIAS.

LUP_RETURN_QUERY_STRING — получить строку запроса.

LUP_RETURN_ALL — установить все флаги LUP_RETURN_.

LUP_FLUSHPREVIOUS — указывает провайдеру отбросить последний набор результатов, который был слишком велик для буфера, и перейти к следующему набору результатов. Используется в качестве значения параметра dwControlFlags в WSALookupServiceNext().

LUP_FLUSHCACHE — игнорировать кэш, если провайдер его использует. Например, в случае DNS будет сделан запрос к серверу, даже если запись уже получена.

LUP_RES_SERVICE — указывает, находится ли основной ответ в RemoteAddr- или в LocalAddr- атрибутах структуры CSADDR_INFO.

lphLookup — переменная, в которую будет сохранен дескриптор, используемый при вызове WSALookupServiceNext().

Некоторые пространства имен, такие как распределенная система каталогов Whois++, поддерживают задание в строке lpszQueryString расширенных SQL-запросов.

Эта функция «открывает базу служб». При успешном выполнении она возвращает 0 и записывает в lphLookup дескриптор, который будет использован при вызовах WSALookupServiceNext() для получения последовательности результатов:

int WSAAPI WSALookupServiceNext(

    HANDLE hLookup,

    DWORD dwControlFlags,

    LPDWORD lpdwBufferLength,

    LPWSAQUERYSET lpqsResults

);

 

int WSAAPI WSALookupServiceEnd(

    HANDLE hLookup

);

Параметры функции WSALookupServiceNext():

hLookup — дескриптор, возвращенный функцией WSALookupServiceBegin().

• dwControlFlags — флаги, такие же, как в функции WSALookupServiceBegin().

• lpdwBufferLength — длина буфера lpqsResults в байтах.

lpqsResults — буфер результатов.

Функция WSALookupServiceEnd() «закрывает базу», то есть завершает выполнение запроса. В случае ошибки функции возвращают SOCKET_ERROR.

Хотя указание большего или меньшего количества флагов не является ошибкой, флаги при вызове WSALookupServiceNext() не могут расширять фильтр и запрашивать больше возвращаемых структур, чем было запрошено в WSALookupServiceBegin().

Флаги комбинируются с флагами WSALookupServiceBegin(). Но флаги, указанные в WSALookupServiceNext(), применяются только к текущему вызову и не сохраняются для последующих.

В базе можно зарегистрировать новую службу либо удалить существующую. За это отвечает следующая функция:

#include <winsock2.h>

 

int WSAAPI WSASetService(

    LPWSAQUERYSET lpqsRegInfo,

    WSAESETSERVICEOP essoperation,

    DWORD dwControlFlags

);

Параметры функции WSASetService():

lpqsRegInfo — указатель на структуру WSAQUERYSET для регистрации или дерегистрации службы.

• essoperation — операция, выполняемая функцией:

• RNRSERVICE_REGISTER — зарегистрировать службу. Если такая служба уже зарегистрирована, при установленном флаге SERVICE_MULTIPLE — добавить новый адрес службы, иначе — переписать запись о ней полностью.

• RNRSERVICE_DEREGISTER — удалить службу из реестра. При установленном флаге SERVICE_MULTIPLE — удалить все адреса, не удаляя службу.

• RNRSERVICE_DELETE — удалить службу из динамического и постоянного пространства имен. При установленном флаге SERVICE_MULTIPLE — удалить заданные адреса.

• dwControlFlags — дополнительные флаги:

• SERVICE_MULTIPLE — флаг управляет адресами, позволяя добавлять или удалять адреса, по которым доступна служба.

В зависимости от провайдера эта операция может отправлять широковещательные сообщения или менять содержимое постоянного хранилища.

В nspapi.h есть также функции SetService() и GetService(), позволяющие зарегистрировать и получить службу. Эти функции требуются для совместимости с WinSock 1.1 и уже устарели.

Функция WSASetService() принимает набор адресов службы из массива структур CSADDR_INFO в поле lpcsaBuffer. Функции перечисления служб также заполняют этот массив, если был указан флаг LUP_RETURN_ADDR.

Службы могут принадлежать некоторому классу, описывающему пространства имен, в которых будет зарегистрирована служба. Класс задает ее некоторые характеристики. Например, требует ли служба логического соединения для взаимодействия с ней или ориентирована на обмен без соединения.

Если требуется использовать классы служб, их можно установить при регистрации службы; если классы больше не нужны, их можно удалить. Для этих операций применяются следующие функции:

// Установить класс службы.

int WSAAPI WSAInstallServiceClass(

    LPWSASERVICECLASSINFO lpServiceClassInfo

);

 

// Удалить класс.

int WSAAPI WSARemoveServiceClass(

    int LPGUID lpServiceClassId

);

Устанавливаемый класс описывается структурой WSASERVICECLASSINFO:

#include <winsock2.h>

 

typedef struct _WSANSClassInfo

{

    // Название параметра, например TCPPORT.

    LPTSTR lpszName;

    // Идентификатор пространства имен.

    DWORD dwNameSpace;

    // Тип параметра, например REG_DWORD.

    DWORD dwValueType;

    // Размер параметра в байтах.

    DWORD dwValueSize;

    // Указатель на значение параметра.

    LPVOID lpValue;

} WSANSCLASSINFO, *PWSANSCLASSINFO, *LPWSANSCLASSINFO;

 

typedef struct _WSAServiceClassInfo

{

    // Идентификатор класса.

    LPGUID lpServiceClassId;

    // Имя класса.

    LPTSTR lpszServiceClassName;

    // Количество элементов в массиве lpClassInfos.

    DWORD dwCount;

    // Массив параметров.

    LPWSANSCLASSINFO lpClassInfos;

} WSASERVICECLASSINFO, *PWSASERVICECLASSINFO, *LPWSASERVICECLASSINFO;

Как показано на рис. 15.4, класс — это просто группа, имеющая следующие параметры:

• идентификатор;

• имя;

• параметры, специфичные для пространства имен.

Рис. 15.4. Структура WSASERVICECLASSINFO

Параметры зарегистрированного класса можно получить, используя функцию WSAGetServiceClassInfo():

int WSAAPI WSAGetServiceClassInfo(

    LPGUID lpProviderId,

    LPGUID lpServiceClassId,

    LPDWORD lpdwBufSize,

    LPWSASERVICECLASSINFOA lpServiceClassInfo

);

Параметры функции WSAGetServiceClassInfo():

lpProviderId — указатель на GUID провайдера пространства имен.

• lpServiceClassId — указатель на GUID, идентифицирующий класс.

• lpdwBufSize — размер указателя lpServiceClassInfo в байтах, как входного, так и выходного. Если функция завершается с ошибкой WSAEFAULT, в этом параметре она вернет минимальный размер буфера, необходимый для получения записи.

lpServiceClassInfo — указатель на структуру WSASERVICECLASSINFO, содержащую информацию о классе обслуживания.

В случае успеха функция вернет 0.

В качестве примера реализуем программу, которая выводит данные о службе ECHO. Сначала инициализируем сетевую подсистему. Затем вызовем WSALookupServiceBegin():

// Определение GUID служб.

extern "C"

{

#include <svcguid.h>

}

...

 

int main(int argc, char* argv[])

{

    uint32_t result = 0;

    HANDLE h_lookup = 0;

    WSAQUERYSET first_query = {};

 

    // Идентификатор службы: SVCID_UDP = 7.

    GUID guid = SVCID_ECHO_UDP;

 

    try

    {

        socket_wrapper::SocketWrapper sw;

 

        first_query.dwSize = sizeof(WSAQUERYSET);

        first_query.lpServiceClassId = &guid;

        // "Открыть базу" служб. Для проверки на корректность можно

        // использовать макросы.

        if (FAILED(WSALookupServiceBegin(&first_query,

                                         LUP_RETURN_NAME |

                                         LUP_RETURN_COMMENT |

                                         LUP_RETURN_ADDR, &h_lookup)))

        {

            throw std::system_error(sw.get_last_error_code(),

                                    std::system_category(),

                                    "Error on WSALookupServiceBegin()");

        }

GUID различных служб описан в svcguid.h, который мы включили.

Далее в цикле вызываем WSALookupServiceNext(), чтобы получить другие описания при их наличии.

Первый вызов необходим, чтобы выделить буфер подходящего размера:

        uint32_t result = 0;

        // Цикл получения служб.

        while (true)

        {

            WSAQUERYSET test_query = {};

            DWORD length = sizeof(test_query);

            // Первый вызов функции должен завершиться с ошибкой

            // из-за того, что ему передается запрос с нулевыми полями.

            // Но вызов запишет в length требуемую длину буфера под ответ.

            if (SUCCEEDED(WSALookupServiceNext(h_lookup, 0, &length,

                                               &test_query)))

            {

                std::cerr << "Impossible" << std::endl;

                break;

            }

 

            auto err_code = GetLastError();

            if (WSAEFAULT == err_code)

            {

                // Выделить буфер нужной длины.

                auto pdata_shared = std::make_shared<char[]>(length);

                WSAQUERYSET* p_data =

                    reinterpret_cast<WSAQUERYSET*>(pdata_shared.get());

                // А это уже реальный вызов, который возвращает ответ.

                if (FAILED(WSALookupServiceNext(h_lookup, 0, &length,

                                                p_data)))

                {

                    result = WSAGetLastError();

                    // Windows может вернуть два разных кода ошибки, если

                    // результатов больше нет.

                    if ((WSA_E_NO_MORE == result) || (WSAENOMORE == result))

                    {

                        std::cout << "No more records found!" << std::endl;

                        break;

                    }

                    // Реальная ошибка.

                    else throw std::system_error(sw.get_last_error_code(),

                             std::system_category(),

                             "Error on WSALookupServiceNext()");

                }

Покажем, как вывести адреса службы. Существуют службы вообще без адресов, например SVCID_HOSTNAME, возвращающая имя узла.

В данном случае адрес будет только один:

                // Вывести имя службы, номер пространства имен, а также

                // количество адресов.

                std::wcout

                    << "Service instance name: "

                    << p_data->lpszServiceInstanceName << "\n"

                    << "Name space num: " << p_data->dwNameSpace << "\n"

                    << "Address count:  " << p_data->dwNumberOfCsAddrs

                    << std::endl;

                // Вывести адреса службы.

                for (size_t i = 0; i < p_data->dwNumberOfCsAddrs; ++i)

                {

                    if (IPPROTO_UDP == p_data->lpcsaBuffer[i].iProtocol)

                    {

                        std::array<char, INET_ADDRSTRLEN> addr;

                        const auto& sa = reinterpret_cast<const sockaddr_in*>(

                            p_data->lpcsaBuffer[i].RemoteAddr.lpSockaddr);

                        // Для примера выводим только IPv4-адреса.

                        assert(AF_INET == sa->sin_family);

 

                        std::cout

                            << inet_ntop(AF_INET, &sa->sin_addr, addr.data(),

                                         addr.size())

                            << std::endl;

                    }

                }

            }

Завершим работу с базой служб, вызвав функцию WSALookupServiceEnd() на дескрипторе, который был получен при открытии базы:

            else if (result != WSA_E_NO_MORE && result != WSAENOMORE)

            {

                break;

            }

            else

                throw std::system_error(sw.get_last_error_code(),

                                        std::system_category(),

                                        "Error on WSALookupServiceNext()");

        };

        // Завершить работу с базой служб.

        if (WSALookupServiceEnd(h_lookup))

            throw std::system_error(

                sw.get_last_error_code(), std::system_category(),

                "WSALookupServiceEnd(hlookup) failed");

    }

...

После запуска примера будет выведено имя службы, номер пространства имен и адрес службы:

D:\build\bin> b01-ch15-service_base.exe

Service instance name: win-ws

Namespace num: 12

Address count:  1

192.168.3.254

Тонкая работа с DNS

Так же как в Linux, в ОС Windows существует API для работы с DNS-резолвером. Его функции и структуры объявлены в заголовочном файле windns.h. Обычно для работы достаточно вызова функции getaddrinfo() и подобных. Необходимость напрямую работать с резолвером возникает редко, но в некоторых сценариях этот API бывает востребован. Например, показательной является установка DNS-серверов для каждого приложения с возможностью использовать как серверы для приложения, так и системные.

Мы не будем подробно рассматривать API полностью, а лишь систематизируем его, чтобы было проще обращаться к MSDN.

Особенности API DNS-резолвера

Некоторые функции резолвера существуют не только для ANSI и широких символов, но и для UTF-8. Большинство этих функций отличаются суффиксами _A, _W и _UTF8.

Например, функция DnsQuery() существует в трех экземплярах: DnsQuery_A(), DnsQuery_W() и DnsQuery_UTF8().

Единственная функция, имеющая «классический» суффикс без подчеркивания, — DnsReplaceRecordSet(), которая раскрывается в DnsReplaceRecordSetA(), DnsReplaceRecordSetW() и DnsReplaceRecordSetUTF8().

«Обычные» ANSI и широкосимвольные функции можно использовать как обычно, а версию UTF-8 придется вызывать явно. Причем эти версии функции принимают строку из однобайтовых символов.

Многие функции из этого раздела выделяют память, которую требуется освободить, для чего используются отдельные функции.

Часть функций, в основном те, которые выполняют запросы, — асинхронные, поэтому существуют функции для их отмены.

Функции для вспомогательных операций

Зачастую перед работой с DNS или непосредственно в процессе работы требуется выполнять различные «простые» операции. Например, сравнить имена без учета регистра, нормализовав их исключением лишних строк:

#include <windns.h>

 

// Сравнить два имени DNS.

bool DnsNameCompare(PCTSTR pName1, PCTSTR pName2);

Или проверить имя на корректность:

#include <windns.h>

 

// Проверить состояние указанного DNS-имени.

DNS_STATUS DnsValidateName(PCTSTR pszName, DNS_NAME_FORMAT Format);

 

// Проверить заданный IP-адрес на корректность

// DNS сервера, способного выполнять запросы.

DNS_STATUS DnsValidateServerStatus(PSOCKADDR server, PCWSTR queryName,

                                   PDWORD serverStatus);

Первая функция выполняет проверки на формальную корректность: длину имени, допустимые символы, правильность расположения точек и т.д. — и в зависимости от этого возвращает разные ошибки.

Возвращаемый тип DNS_STATUS — обычный unsigned long:

typedef _Return_type_success_(return == 0) LONG DNS_STATUS;

typedef DNS_STATUS *PDNS_STATUS;

Возвращаемое значение EXIT_SUCCESS говорит о том, что функции завершились успешно.

Существуют функции для работы с учетными данными:

// Создать контекст для набора учетных данных и вернуть его хэндл.

// В этом контексте будут храниться учетные данные некоторого аккаунта.

DNS_STATUS DnsAcquireContextHandle(DWORD CredentialFlags, PVOID Credentials,

                                   PHANDLE pContext);

 

// Освободить контекст, хранящий учетные данные.

void DnsReleaseContextHandle(PHANDLE pContext);

Созданный этими функциями контекст, содержащий учетные данные, может использоваться далее, например, в функциях работы с ресурсными записями DNS.

Функции инициирования запросов

Запросы — основа работы с DNS. Для их выполнения существует набор функций в составе API резолвера.

Создать сообщение с DNS-запросом в буфере типа DNS_MESSAGE_BUFFER:

bool DnsWriteQuestionToBuffer_W(

    PDNS_MESSAGE_BUFFER pDnsBuffer,

    PDWORD pdwBufferSize, PCWSTR pszName, WORD wType,

    WORD Xid, bool fRecursionDesired);

Существует также аналогичная функция DnsWriteQuestionToBuffer_UTF8().

Основной интерфейс для запросов к DNS, который предоставляет функциональность для выполнения разрешения имен:

DNS_STATUS DnsQuery(PCSTR pszName, WORD wType, DWORD Options, PVOID pExtra,

                    PDNS_RECORD *ppQueryResults, PVOID *pReserved);

Выполнить асинхронный DNS-запрос:

DNS_STATUS DnsQueryEx(PDNS_QUERY_REQUEST pQueryRequest,

                      PDNS_QUERY_RESULT pQueryResults,

                      PDNS_QUERY_CANCEL pCancelHandle);

Запросить конфигурацию локального хоста или конкретного сетевого интерфейса:

DNS_STATUS DnsQueryConfig(DNS_CONFIG_TYPE Config, DWORD Flag,

                          PCWSTR pwsAdapterName, PVOID pReserved,

                          PVOID pBuffer, PDWORD pBufLen);

Дает возможность получить доменное имя, список DNS-серверов, информацию о сетевом адаптере и т.п.

Отправить произвольный DNS-запрос из «сырых» данных, например уже сформированный пакет запроса:

DNS_STATUS DnsQueryRaw(DNS_QUERY_RAW_REQUEST *queryRequest,

                       DNS_QUERY_RAW_CANCEL *cancelHandle)

Выполнить запрос, используя mDNS. Система mDNS не требует наличия сервера имен. Она выполняет поиск заданных служб, рассылая широковещательные UDP-запросы:

DNS_STATUS DnsStartMulticastQuery(PMDNS_QUERY_REQUEST pQueryRequest,

                                  PMDNS_QUERY_HANDLE pHandle)

Асинхронные запросы могут быть отменены следующими функциями:

// Отменить выполняемый запрос к DNS, инициированный вызовом DnsQuery().

DNS_STATUS DnsCancelQuery(PDNS_QUERY_CANCEL pCancelHandle);

 

// Отменить запрос, инициированный вызовом DnsQueryRaw().

DNS_STATUS DnsCancelQueryRaw(DNS_QUERY_RAW_CANCEL *cancelHandle);

 

// Остановить запрос DNS-SD.

DNS_STATUS DnsServiceResolveCancel(PDNS_SERVICE_CANCEL pCancelHandle);

 

// Отменить запрос к mDNS.

DNS_STATUS DnsStopMulticastQuery(PMDNS_QUERY_HANDLE pHandle);

Работа с записями

Для работы с ресурсными записями DNS существует отдельный набор функций.

Сравнение записей:

// Сравнить две ресурсные DNS-записи.

bool DnsRecordCompare(PDNS_RECORD pRecord1, PDNS_RECORD pRecord2);

 

// Сравнить два набора ресурсных записей.

bool DnsRecordSetCompare(PDNS_RECORD pRR1, PDNS_RECORD pRR2,

                         PDNS_RECORD *ppDiff1, PDNS_RECORD *ppDiff2);

Копирование записей:

// Создать копию соответствующей ресурсной записи.

PDNS_RECORD DnsRecordCopyEx(PDNS_RECORD pRecord, DNS_CHARSET CharSetIn,

                            DNS_CHARSET CharSetOut);

 

// Создать копию набора ресурсных записей.

PDNS_RECORD DnsRecordSetCopyEx(PDNS_RECORD pRecordSet, DNS_CHARSET CharSetIn,

                               DNS_CHARSET CharSetOut);

В процессе копирования функция может выполнять конвертацию в другую кодировку.

Функции для замены и изменения записей в наборе:

// Заменить существующий набор ресурсных записей.

DNS_STATUS DnsReplaceRecordSet(PDNS_RECORD pReplaceSet, DWORD Options,

                               HANDLE hContext, PVOID pExtraInfo,

                               PVOID pReserved);

 

// Добавить, изменить либо удалить ресурсную запись в наборе.

DNS_STATUS DnsModifyRecordsInSet(PDNS_RECORD pAddRecords,

                                 PDNS_RECORD pDeleteRecords,

                                 DWORD Options, HANDLE hCredentials,

                                 PVOID pExtraList, PVOID pReserved);

Чтобы извлечь записи из набора, можно использовать следующие функции:

// Извлечь записи ресурсов из DNS-сообщения в виде экземпляров

// структур DNS_RECORD.

DNS_STATUS DnsExtractRecordsFromMessage_W(PDNS_MESSAGE_BUFFER pDnsBuffer,

                                          WORD wMessageLength,

                                          PDNS_RECORD *ppRecord);

 

// Отделить первую запись из набора.

PDNS_RECORD DnsRecordSetDetach(PDNS_RECORD pRecordList);

Работа со службами

Ниже описан набор функций для работы со службами, которые предоставляет DNS-сервер, а также со службами, которые опубликованы по mDNS.

// Инициировать запрос DNS-SD для обнаружения служб в локальной сети.

DNS_STATUS DnsServiceBrowse(

    PDNS_SERVICE_BROWSE_REQUEST pRequest,

    PDNS_SERVICE_CANCEL pCancel

);

 

// Отменить запрос DNS-SD обнаружения служб.

DNS_STATUS DnsServiceBrowseCancel(PDNS_SERVICE_CANCEL pCancelHandle);

Получить информацию о конкретной службе, объявленной в локальной сети:

DNS_STATUS DnsServiceResolve(PDNS_SERVICE_RESOLVE_REQUEST pRequest,

                             PDNS_SERVICE_CANCEL pCancel);

Можно не только получать сервисы, но и добавлять свои. Для этого служат функции описания и регистрации сервисов, а также вспомогательные функции.

Функция DnsServiceConstructInstance() позволяет описать параметры регистрируемой службы и получить корректную структуру описания:

// Создать структуру DNS_SERVICE_INSTANCE из описывающих ее данных.

PDNS_SERVICE_INSTANCE DnsServiceConstructInstance(

    PCWSTR pServiceName,

    PCWSTR pHostName,

    PIP4_ADDRESS pIp4,

    PIP6_ADDRESS pIp6,

    WORD wPort,

    WORD wPriority,

    WORD wWeight,

    DWORD dwPropertiesCount,

    PCWSTR *keys,

    PCWSTR *values

);

 

// Копировать объект структуры DNS_SERVICE_INSTANCE.

PDNS_SERVICE_INSTANCE DnsServiceCopyInstance(PDNS_SERVICE_INSTANCE pOrig);

 

// Освободить ресурсы, ассоциированные со структурой DNS_SERVICE_INSTANCE.

void DnsServiceFreeInstance(PDNS_SERVICE_INSTANCE pInstance);

Зарегистрировать описанную службу или удалить регистрацию можно, используя следующие функции:

// Зарегистрировать службу, информация о которой может быть получена

// с помощью DNS.

DWORD DnsServiceRegister(PDNS_SERVICE_REGISTER_REQUEST pRequest,

                         PDNS_SERVICE_CANCEL pCancel);

 

// Отменить регистрацию службы, когда она больше не требуется.

DnsServiceDeRegister(PDNS_SERVICE_REGISTER_REQUEST pRequest,

                     PDNS_SERVICE_CANCEL pCancel);

 

// Отменить выполняемую операцию регистрации или отмены регистрации.

DWORD DnsServiceRegisterCancel(PDNS_SERVICE_CANCEL pCancelHandle);

На DNS-сервере описания служб обычно хранятся в записях расположения службы, имеющих тип SRV. Каждая такая запись содержит хост, порт и протокол службы.

Работа с прокси

Выполнять запросы к DNS-серверам можно через прокси. Следующая функция позволяет получить настройки прокси:

#include <windns.h>

 

DWORD DnsGetProxyInformation(

    PCWSTR hostName, DNS_PROXY_INFORMATION *proxyInformation,

    DNS_PROXY_INFORMATION *defaultProxyInformation,

    DNS_PROXY_COMPLETION_ROUTINE completionRoutine,

    void *completionContext);

Функции для установки настроек прокси отсутствуют, скорее всего потому, что настройки являются глобальными, или из-за того, что вызов работает через RPC и функциональности установки, технически способной работать удаленно, не подразумевалось.

Настройки для приложений

Следующие функции позволяют установить и получить настройки DNS для приложений без изменения системных настроек:

#include <windns.h>

 

// Настроить DNS для конкретного приложения.

DWORD DnsSetApplicationSettings(DWORD cServers,

                                const DNS_CUSTOM_SERVER *pServers,

                                const DNS_APPLICATION_SETTINGS *pSettings);

 

// Получить настройки DNS, специфичные для приложения.

DWORD DnsGetApplicationSettings(DWORD *pcServers,

                                DNS_CUSTOM_SERVER **ppDefaultServers,

                                DNS_APPLICATION_SETTINGS *pSettings);

Эти функции работают со следующей структурой:

// Заголовочные файлы лучше включать именно в таком порядке,

// чтобы использовать DNS_CUSTOM_SERVER вместе с ServerAddr.

#include <ws2ipdef.h>

#include <windns.h>

 

typedef struct _DNS_APPLICATION_SETTINGS

{

    // Должен быть установлен DNS_APP_SETTINGS_VERSION1.

    ULONG Version;

    // Опции.

    ULONG64 Flags;

} DNS_APPLICATION_SETTINGS;

 

// Описание сервера для приложения.

typedef struct _DNS_CUSTOM_SERVER

{

    // Тип сервера, определяющий транспорт запроса.

    DWORD dwServerType;

    // Флаги.

    ULONG64 ullFlags;

 

    union

    {

        // Шаблон для DoH.

        PWSTR pwszTemplate;

        // Имя узла.

        PWSTR pwszHostname;

    };

    // Хранилище SOCKADDR_INET.

    char MaxSa[DNS_ADDR_MAX_SOCKADDR_LENGTH];

} DNS_CUSTOM_SERVER;

В структуре DNS_APPLICATION_SETTINGS поле опций может принимать одно значение — DNS_APP_SETTINGS_EXCLUSIVE_SERVERS, которое говорит о том, что необходимо использовать только DNS-серверы приложения, а не системные.

Тип сервера может быть следующим:

DNS_CUSTOM_SERVER_TYPE_UDP — «обычный» DNS с выполнением запросов по UDP, то есть с использованием небезопасного канала передачи запроса.

DNS_CUSTOM_SERVER_TYPE_DOH — сервер типа DoH. В этом случае запросы выполняются по HTTPS, что повышает безопасность.

В поле ullFlags структуры DNS_CUSTOM_SERVER определен единственный флаг — DNS_CUSTOM_SERVER_UDP_FALLBACK. Он говорит о том, что сервер может вернуться к небезопасному каналу в случае отказа DoH.

Освобождение ресурсов

Функции ниже используются, чтобы освободить память, выделенную предыдущими вызовами:

#include <windns.h>

 

typedef enum

{

    // Освобождаемые данные являются плоской структурой.

    DnsFreeFlat = 0,

    // Освобождаемые данные — список записей ресурсов,

    // который включает подполя DNS_RECORD.

    DnsFreeRecordList,

    // Освобождаемые данные — разобранное поле сообщения.

    DnsFreeParsedMessageFields

} DNS_FREE_TYPE;

 

// Освободить память, выделенную функцией DnsQuery() для полученных

// записей DNS.

void DnsFree(PVOID pData, DNS_FREE_TYPE FreeType);

 

// Освободить память, выделенную для структуры DNS_QUERY_RAW_RESULT,

// которая используется при вызове DnsQueryRaw().

void DnsQueryRawResultFree(DNS_QUERY_RAW_RESULT *queryResults);

 

// Освободить память, выделенную функцией DnsQuery() для записей DNS.

void DnsRecordListFree(PDNS_RECORD pRecordList, DNS_FREE_TYPE FreeType);

DnsRecordListFree() в новых версиях ОС Windows представляет собой не функцию, а макрос, вызывающий DnsFree():

#if (_WIN32_WINNT >= 0x0501)

#define DnsRecordListFree(p,t) DnsFree(p, DnsFreeRecordList)

...

Память, которая была выделена функциями для работы с прокси, освобождается с помощью отдельного набора функций:

// Освободить память, выделенную для поля proxyName в экземпляре структуры

// DNS_PROXY_INFORMATION, выделенной функцией DnsGetProxyInformation().

void DnsFreeProxyName(PWSTR proxyName);

 

// Освободить массив пользовательских серверов,

// возвращаемый DnsGetApplicationSettings().

void DnsFreeCustomServers(DWORD *pcServers, DNS_CUSTOM_SERVER **ppServers);

Параметры последней функции:

pcServers — указатель на количество серверов в массиве ppServers. После вызова функции будет равен 0.

• ppServers — указатель на массив DNS_CUSTOM_SERVER, содержащий элементы pcServers. После вызова функции сюда тоже будет записан 0.

Получение настроек DNS-интерфейса

Более подробно работу с интерфейсами мы изучим позже, а сейчас разберем функции IP Helper API для настройки DNS-интерфейсов:

#include <netioapi.h>

 

// Получить настройки DNS.

NETIO_STATUS GetInterfaceDnsSettings(GUID Interface,

                                     DNS_INTERFACE_SETTINGS *Settings);

 

// Установить настройки DNS.

NETIO_STATUS SetInterfaceDnsSettings(GUID Interface,

                                     const DNS_INTERFACE_SETTINGS *Settings);

Параметры функций GetInterfaceDnsSettings() и SetInterfaceDnsSettings():

Interface — GUID интерфейса, для которого требуется получить или установить настройки.

Settings — структура, которая содержит настройки или в которую они будут записаны.

Возвращаемые значения — NO_ERROR в случае успеха или код ошибки.

Функции работают со структурой DNS_INTERFACE_SETTINGS, причем функция GetInterfaceDnsSettings() сама выделяет память:

#include <netioapi.h>

 

typedef struct _DNS_INTERFACE_SETTINGS

{

    // Версия структуры. Должна быть DNS_INTERFACE_SETTINGS_VERSION1.

    ULONG Version;

    // Битовые флаги.

    ULONG64 Flags;

    // Доменное имя адаптера.

    PWSTR Domain;

    // Адреса серверов имен, разделенные запятыми или пробелами.

    PWSTR NameServer;

    // Имена поиска, разделенные так же, как адреса DNS-серверов.

    PWSTR SearchList;

    // Истина, если динамическая регистрация адаптера включена.

    ULONG RegistrationEnabled;

    // Истина, если регистрация адаптера включена.

    ULONG RegisterAdapterName;

    // Включение mDNS и LLMNR — Link-Local Multicast Name Resolution.

    ULONG EnableLLMNR;

    // Истина, если имя адаптера должно использоваться как суффикс поиска.

    ULONG QueryAdapterName;

    // Список DNS-серверов.

    PWSTR ProfileNameServer;

} DNS_INTERFACE_SETTINGS;

Поле версии при вызове функции определяет тип передаваемой структуры и может иметь следующие значения:

DNS_INTERFACE_SETTINGS_VERSION1 — для структуры выше.

• DNS_INTERFACE_SETTINGS_VERSION2 — должна быть передана расширенная структура DNS_INTERFACE_SETTINGS_EX, которая включает DNS_INTERFACE_SETTINGS.

DNS_INTERFACE_SETTINGS_VERSION3 — передается структура DNS_INTERFACE_SETTINGS3, аналогичная DNS_INTERFACE_SETTINGS, но содержащая набор свойств DNS-сервера, список имен для поиска и т.п.

Структура для расширенных параметров:

typedef struct _DNS_INTERFACE_SETTINGS_EX

{

    DNS_INTERFACE_SETTINGS SettingsV1;

    // Зарезервированное поле.

    ULONG DisableUnconstrainedQueries;

    //  Имена поиска, разделенные запятыми или пробелами.

    PWSTR SupplementalSearchList;

} DNS_INTERFACE_SETTINGS_EX;

Значения флагов следующие:

DNS_SETTING_IPV6 — настроить параметры интерфейса только для сетевого стека IPv6. Если этот параметр задан, все IP-адреса в атрибутах NameServer и ProfileNameServer должны быть IPv6-адресами.

• DNS_SETTING_NAMESERVER — настроить DNS-серверы статического адаптера на указанном интерфейсе через атрибут NameServer.

DNS_SETTING_SEARCHLIST — настроить список поиска DNS-суффиксов для конкретного подключения с помощью атрибута SearchList.

DNS_SETTING_REGISTRATION_ENABLED — включить динамическую регистрацию DNS для заданного адаптера. По умолчанию в системе включена.

DNS_SETTING_DOMAIN — установить DNS-суффикс подключения для заданного адаптера из атрибута Domain.

DNS_SETTINGS_ENABLE_LLMNR — включить разрешение имен с помощью LLMNR и mDNS на указанном адаптере. По умолчанию в системе включено.

DNS_SETTINGS_QUERY_ADAPTER_NAME — включить использование имени адаптера в качестве суффикса для запросов DNS. По умолчанию в системе включено.

DNS_SETTING_PROFILE_NAMESERVER — настроить DNS-серверы статического профиля на интерфейсе, то есть «обычные» DNS-серверы, с помощью атрибута ProfileNameServer.

Выделенную память необходимо удалить, используя следующую функцию:

#include <netioapi.h>

 

void FreeInterfaceDnsSettings(DNS_INTERFACE_SETTINGS *Settings);

Информацию о том, как использовать API, а также что они позволяют изменить, можно понять из назначения полей структуры DNS_INTERFACE_SETTINGS.

Прочие функции

Для получения имени узла WinSock предоставляет функции gethostname() и GetHostNameW():

#include <winsock2.h>

 

int WSAAPI gethostname(char FAR *name, int namelen);

int WSAAPI GetHostNameW(PWSTR name, int namelen);

Функции для установки имени узла, а также получения его идентификатора в WinSock отсутствуют. Но имя узла можно установить, вызвав функцию SetComputerName() из группы API системной информации:

#include <sysinfoapi.h>

 

// Установить имя узла.

bool SetComputerName(LPCTSTR lpComputerName);

// Получить имя узла.

bool GetComputerName(LPTSTR lpBuffer, LPDWORD nSize);

Эти функции устанавливают и получают NetBIOS-имя.

Для работы с DNS-именами локального узла существует еще один набор функций:

#include <sysinfoapi.h>

 

// Тип имени для установки или получения.

typedef enum _COMPUTER_NAME_FORMAT

{

    ComputerNameNetBIOS,

    ComputerNameDnsHostname,

    ComputerNameDnsDomain,

    ComputerNameDnsFullyQualified,

    ComputerNamePhysicalNetBIOS,

    ComputerNamePhysicalDnsHostname,

    ComputerNamePhysicalDnsDomain,

    ComputerNamePhysicalDnsFullyQualified,

    ComputerNameMax

} COMPUTER_NAME_FORMAT;

 

// Установить имя.

bool SetComputerNameEx(

    COMPUTER_NAME_FORMAT NameType,

    LPCTSTR lpBuffer

);

// Получить имя.

bool GetComputerNameEx(

    COMPUTER_NAME_FORMAT NameType,

    LPSTR lpBuffer,

    LPDWORD nSize

);

Параметры функций SetComputerNameEx() и GetComputerNameEx():

NameType — входной параметр, задающий тип устанавливаемого или получаемого имени, указанный в перечислении COMPUTER_NAME_FORMAT.

• lpBuffer — входной или выходной буфер.

nSize — размер в символах буфера, в который должно быть записано имя. Если размер недостаточен, в этот параметр будет записан необходимый размер.

Функции возвращают истинное значение в случае успеха.

DNS-имя можно преобразовать в NetBIOS-совместимое имя, вызвав следующую функцию:

#include <winbase.h>

 

bool DnsHostnameToComputerName(

    LPCTSTR Hostname,

    LPTSTR ComputerName,

    LPDWORD nSize

);

Эта функция почти не используется, поэтому мы не будем разбирать ее подробно. При желании это можно сделать, обратившись к MSDN.

Для преобразования имен объектов службы каталогов существует функция TranslateName(), которая может работать и с DNS-именами. Но службу каталогов мы в книге не рассматриваем и этой функции касаться тоже не будем.

Для получения структур описания протоколов существуют POSIX-совместимые функции:

struct protoent

{

    // Официальное имя протокола.

    char *p_name;

    // Список псевдонимов. Завершается нулем.

    char **p_aliases;

    // Номер протокола в хостовом порядке байтов.

    short p_proto;

} PROTOENT, *PPROTOENT, *LPPROTOENT;

 

protoent *WSAAPI getprotobynumber(int number);

protoent *WSAAPI getprotobyname(const char *name);

Напомним, что данные функции являются устаревшими и лучше использовать getaddrinfo().

Функции для получения описания служб:

struct servent

{

    // Имя службы.

    char FAR * s_name;

    // Альтернативные имена, массив завершается нулем.

    char FAR * FAR * s_aliases;

#ifdef _WIN64

    // Имя протокола.

    char FAR * s_proto;

    // Порт.

    short s_port;

#else

    short s_port;

    char FAR * s_proto;

#endif

} SERVENT, *PSERVENT, *LPSERVENT;

 

servent *WSAAPI getservbyport(int port, const char *proto);

servent *WSAAPI getservbyname(const char *name, const char *proto);

Их описание приведено в главе 3. Видим, что структура servent несколько отличается от POSIX-варианта.

Для получения информации о точках подключения — адреса локального и удаленного абонентов — WinSock предоставляет POSIX-совместимые функции:

int WSAAPI getsockname(SOCKET s, sockaddr *name, int *namelen);

int WSAAPI getpeername(SOCKET s, sockaddr *name, int *namelen);

Они были подробно описаны в главе 5.

Резюме

В этой главе мы рассмотрели основные функции и структуры, предоставляемые WinSock для управления DNS, а также функции для работы с адресами IPv4 и IPv6.

Структуры адресов IPv4 и IPv6 аналогичны тем, что используются в POSIX-сокетах. WinSock предоставляет совместимые с POSIX функции для работы с ними. Например, получение списка адресов с помощью DNS осуществляется при помощи функции getaddrinfo(), а для обратного преобразования используется функция getnameinfo(), — их описание приведено в главе 2.

Для описанных операций существуют также Windows-реализации. Пример — WSAStringToAddress() и WSAAddressToString(). Таких функций достаточно много. Rtl-функции, помимо работы с IP-адресами, позволяют выполнять преобразования MAC-адресов.

В ОС Windows также существуют базы узлов и служб. Для навигации по базе служб используются функции WSALookupServiceBegin(), WSALookupServiceNext(), WSALookupServiceEnd().

Зарегистрировать новую службу в базе либо удалить из нее существующую возможно с помощью функции WSASetService().

Службы могут принадлежать некоторому классу, описывающему пространства имен, в которых будет зарегистрирована служба. Этот класс может задавать некоторые характеристики службы. Для работы с классами используются функции WSAGetServiceClassInfo(), WSAInstallServiceClass() и WSARemoveServiceClass().

Для работы с DNS существует Windows-специфичный набор функций, включая функции в IP Helper API для настройки DNS на интерфейсах. DNS можно настроить на конкретных интерфейсах либо даже специально для отдельных приложений. Использование этих функций позволит реализовать достаточно сложные сценарии адресации.

Вопросы и задания

1. Существуют ли отличия структур адресов от POSIX-совместимых аналогов? Если да, то какие?

2. Зачем нужна структура CSADDR_INFO?

3. Можно ли использовать POSIX-совместимые функции для работы с IPv6-адресами в WinSock? Если да, какие примеры таких функций можно привести?

4. В чем чаще всего проявляются отличия функций с суффиксом Ex от функций с теми же именами, но без суффикса? Объясните на примере GetAddrInfoEx() и GetAddrInfo().

5. Какие семейства адресов поддерживает функция inet_ntop() в WinSock?

6. В чем отличия функции WSAAddressToString() от inet_ntop()?

7. В чем преимущества и недостатки Rtl-функций, таких как RtlIpv4StringToAddress()? Какие проблемы могут возникнуть при их использовании?

8. Как сконвертировать маску подсети в длину префикса?

9. Как преобразовать IDN в ANSI-строку?

10. В чем отличие функции getaddrinfo() в WinSock от ее POSIX-аналога?

11. Для чего нужна база служб в ОС Windows?

12. Как найти службу в базе по имени?

13. Какие особенности имеются у API DNS-резолвера?

14. Каким образом можно определить TTL полученной записи DNS?

15. Как настроить прокси-сервер для конкретного приложения?

16. Как получить и установить имя узла?

17. В чем отличие структуры servent в WinSock от ее POSIX-версии?

18. Как получить информацию о локальной точке подключения с помощью WinSock API?

19. Напишите программу, которая в качестве входного параметра будет принимать IP-адрес и выводить имена узлов, зарегистрированных в DNS для этого адреса.

Назад: Глава 14. Введение в сетевое программирование для ОС Windows
Дальше: Глава 16. Сокетный API в ОС Windows