Книга: Сетевое программирование. От основ до приложений
Назад: Глава 18. Управление сетью в ОС Windows
Дальше: Глава 19. IP Helper API

Резервирование портов

Некоторые приложения должны работать только на портах из определенного диапазона. Для предоставления такой возможности ОС Windows имеет несколько ioctl и группу функций в библиотеке IP Helper, которые управляют резервированием портов.

Резервирование портов TCP или UDP выполняется следующими ioctl:

SIO_ACQUIRE_PORT_RESERVATION — зарезервировать порты TCP или UDP для вызывающего приложения. Может резервировать как определенный порт для работы, так и любой доступный порт или диапазон портов.

• SIO_ASSOCIATE_PORT_RESERVATION — связать сокет с зарезервированным блоком портов TCP или UDP. Этот ioctl должен быть вызван до привязки сокета. Если к сокету уже привязан адрес и порт, назначенный ему порт будет выбран из резерва портов, определенного заданным токеном. Если из указанного резерва нет доступных портов, вызов функции привязки адреса завершится ошибкой.

SIO_RELEASE_PORT_RESERVATION — освободить зарезервированные ранее порты TCP или UDP.

Следующие функции IP Helper API создают резервирование в постоянном хранилище и возвращают токен резервирования:

#include <iphlpapi.h>

 

// Для TCP-портов.

ULONG CreatePersistentTcpPortReservation(

    USHORT StartPort,

    USHORT NumberOfPorts,

    PULONG64 Token

);

 

// Для UDP-портов.

ULONG CreatePersistentUdpPortReservation(

    USHORT StartPort,

    USHORT NumberOfPorts,

    PULONG64 Token

);

Этот токен приложение должно передавать в рассмотренные ioctl, чтобы зарезервировать диапазон портов. Из этого диапазона оно затем может получить какой-либо порт.

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

// Для TCP-портов.

ULONG LookupPersistentTcpPortReservation(

    USHORT StartPort,

    USHORT NumberOfPorts,

    PULONG64 Token

);

 

// Для UDP-портов.

ULONG LookupPersistentUdpPortReservation(

    USHORT StartPort,

    USHORT NumberOfPorts,

    PULONG64 Token

);

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

// Для TCP-портов.

ULONG DeletePersistentTcpPortReservation(

    USHORT StartPort,

    USHORT NumberOfPorts

);

 

// Для UDP-портов.

ULONG DeletePersistentUdpPortReservation(

    USHORT StartPort,

    USHORT NumberOfPorts

);

Все функции выше при успешном завершении вернут код ошибки NO_ERROR.

Работа с IP-адресами

Для работы с IP-адресами используются следующие ioctl:

SIO_ADDRESS_LIST_QUERY — получает список всех IP-адресов, которые сокет может использовать для прослушивания. При обычной настройке система имеет два IP-адреса: локальной петли — 127.0.0.1 — и сетевого интерфейса. Однако можно иметь как несколько интерфейсов, так и несколько IP-адресов на одном интерфейсе. Данный ioctl вернет структуру SOCKET_ADDRESS_LIST, которую мы уже рассматривали.

• SIO_ADDRESS_LIST_SORT — принимает SOCKET_ADDRESS_LIST, возвращенную из команды SIO_ADDRESS_LIST_QUERY, и сортирует адреса, а также заполняет соответствующие идентификаторы области действия адресов IPv6.

• SIO_ADDRESS_LIST_CHANGE — с помощью этого вызова можно зарегистрироваться на уведомление FD_ADDRESS_LIST_CHANGE о смене IP-адресов в системе. Уведомление может быть получено функциями WSAEventSelect() или WSAAsyncSelect().

Настройки UDP

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

SIO_UDP_CONNRESET — переключить распространение ошибок PORT_UNREACHABLE на отправляющий UDP-сокет. По умолчанию любые ошибки ICMP, генерируемые трафиком, отправленным через сокет UDP, не приводят к ошибкам при работе с сокетом. Например, если данные отправляются абоненту, который их не ожидает, по ICMP может быть отправлена ошибка PORT_UNREACHABLE. Через этот вызов можно включить распространение ошибки на отправляющий сокет. Приход ICMP-пакета приведет к ошибке WSAECONNRESET при вызове функции приема данных.

• SIO_UDP_NETRESET — переключить распространение ошибок NET_UNREACHABLE на отправляющий UDP-сокет. Эти ошибки могут быть отправлены по ICMP, когда истек TTL дейтаграммы. В остальном аналогичен вызову SIO_UDP_CONNRESET.

Настройки TCP

Часть настроек TCP выполняется через такие вызовы ioctl, как:

SIO_KEEPALIVE_VALS — задать время ожидания и интервал проверки активности TCP-соединения. Делает то же, что и опция SO_KEEPALIVE, но переопределяет интервал, который задан в реестре.

• SIO_LOOPBACK_FAST_PATH — настроить TCP-сокет для уменьшения задержки. Ускоряет операции на loopback-интерфейсе. Включение оптимизации приводит к тому, что пакеты проходят только через транспортный уровень вместо традиционной петли, которая требует работы сетевого уровня. Также выключается алгоритм избежания перегрузок.

• SIO_TCP_INITIAL_RTO — установить начальные параметры повторной передачи: начальный RTT и число передач SYN.

• SIO_TCP_INFO — получить статистику TCP для сокета. В отличие от получения статистики TCP с помощью функции GetPerTcpConnectionEStats() не требует загрузки и фильтрации таблицы подключений TCP, выполняемых с повышенными привилегиями. В зависимости от входного параметра, 0 или 1, вернет структуры TCP_INFO_v0 или TCP_INFO_v1 из mstcpip.h. Мы не будем их рассматривать: они подробно описаны в MSDN.

• SIO_TCP_SET_ICW — задать Initial Congestion Window, то есть начальное окно для алгоритма предотвращения коллизий, ограничивающее количество данных в первом RTT. На клиенте позволяет задать 2, 4 или 10 MSS, на сервере — до 64 MSS. До ОС Windows 10 и Windows Server 2012 R2 начальное окно по умолчанию равно 4 MSS. После — 10 MSS.

SIO_TCP_SET_ACK_FREQUENCY — задать число отложенных подтверждений ACK. То же, что опция TCP_QUICKACK в Linux, но, будучи один раз включенным, режим не сбрасывается. Если установить 1, режим отложенных подтверждений будет выключен.

Вызов SIO_KEEPALIVE_VALS принимает в качестве входного параметра указатель на структуру:

struct tcp_keepalive

{

    // Включить или выключить keep-alive.

    u_long  onoff;

    // Тайм-аут в случае ответа по TCP.

    u_long  keepalivetime;

    // Интервал отправки пакетов в случае отсутствия ответа.

    u_long  keepaliveinterval;

};

SIO_TCP_INITIAL_RTO принимает структуру:

#include <mstcpip.h>

 

typedef struct _TCP_INITIAL_RTO_PARAMETERS

{

    // Начальное значение RTT в миллисекундах.

    USHORT Rtt;

    // Максимальное количество передач SYN.

    UCHAR  MaxSynRetransmissions;

} TCP_INITIAL_RTO_PARAMETERS, *PTCP_INITIAL_RTO_PARAMETERS;

Поле Rtt также может принимать следующие значения:

TCP_INITIAL_RTO_UNSPECIFIED_RTT — использовать заданные администратором настройки.

TCP_INITIAL_RTO_DEFAULT_RTT — использовать системные значения по умолчанию.

Точно так же поле MaxSynRetransmissions может принимать сходные значения:

TCP_INITIAL_RTO_UNSPECIFIED_MAX_SYN_RETRANSMISSIONS — использовать заданное администратором значение.

TCP_INITIAL_RTO_DEFAULT_MAX_SYN_RETRANSMISSIONS — системное значение по умолчанию.

Значение по умолчанию для SIO_TCP_SET_ACK_FREQUENCY задается в ключе реестра

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\<Interface GUID>\TcpAckFrequency.

Буквально это количество ожидающих подтверждений до того, как таймер отложенного подтверждения будет проигнорирован. Значение по умолчанию — 2, максимальное — 255. 1 включает подтверждение каждого пакета, 0 трактуется как значение по умолчанию.

Широковещательный трафик

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

SIO_GET_BROADCAST_ADDRESS — получить широковещательный адрес для использования в функциях sendto() и WSASendTo().

• SIO_RCVALL — включить получение всех пакетов, которые проходят через сетевой интерфейс. Принимает дескриптор сокета. Тип сокета должен быть SOCK_RAW. Работает и для IPv6, в этом случае протоколом сокета должен быть указан IPPROTO_IPV6. Вызов принимает несколько значений:

• RCVALL_OFF — выключить неразборчивый режим.

• RCVALL_ON — включить неразборчивый режим. Адаптер начнет принимать все пакеты.

• RCVALL_SOCKETLEVELONLY — нереализованный режим.

• RCVALL_IPLEVEL или RCVALL_MAX — адаптер будет принимать только кадры, направленные ему. Он не переходит в неразборчивый режим. Но IP-уровень стека перенаправляет все пакеты IPv4 и IPv6 на уровень пользователя.

SIO_RCVALL_IF — получать все пакеты с заданного интерфейса. Принимает индекс сетевого интерфейса.

Внимание! Размер буфера для вызова SIO_RCVALL должен иметь значение sizeof(RCVALL_VALUE).

Иногда в коде передают значение true вызову SIO_RCVALL. Это работает из-за того, что константа RCVALL_ON имеет значение 1. Но лучше так не делать, поскольку с точки зрения семантики вызова подобное значение некорректно.

Многоадресная рассылка

Хотя многоадресную рассылку в книге мы не рассматриваем, здесь мы перечислим ioctl для управления ею:

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

• SIO_RCVALL_IGMPMCAST — позволяет сокету получать весь многоадресный IP-трафик IGMP в сети. Остальной трафик игнорируется. Сокет должен быть создан с указанием протокола IPPROTO_IGMP.

• SIO_RCVALL_MCAST — позволяет сокету получать весь многоадресный IP-трафик в сети. То же, что и SIO_RCVALL, но возвращается только многоадресный трафик IPv4 с IP-адресов в диапазоне от 224.0.0.0 до 239.255.255.255, а не все IP-пакеты.

• SIO_RCVALL_MCAST_IF — задать интерфейс для входящего многоадресного трафика.

• SIO_INDEX_MCASTIF — задать индекс интерфейса для исходящего многоадресного трафика.

• SIO_INDEX_ADD_MCAST — добавить интерфейс с заданным индексом в группу многоадресной рассылки. Входной параметр — структура ip_mreq, в которой поле imr_interface содержит индекс интерфейса.

• SIO_INDEX_DEL_MCAST — удалить интерфейс из группы многоадресной рассылки.

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

Структура ip_mreq:

#include <ws2ipdef.h>

 

typedef struct ip_mreq

{

    // IPv4-адрес группы многоадресной рассылки.

    IN_ADDR imr_multiaddr;

    // В зависимости от SIO индекс либо IPv4-адрес интерфейса.

    IN_ADDR imr_interface;

} IP_MREQ, *PIP_MREQ;

Управление фильтром адресов многоадресной рассылки

Эти ioctl можно использовать для установки состояния многоадресной рассылки в одном вызове вместо нескольких вызовов IP_ADD_SOURCE_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP или IP_BLOCK_SOURCE и IP_UNBLOCK_SOURCE:

SIO_SET_MULTICAST_FILTER или SIOCSIPMSFILTER — установить состояние многоадресной рассылки.

SIO_GET_MULTICAST_FILTER или SIOCGIPMSFILTER — извлечь набор фильтров многоадресной рассылки для данного сокета.

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

#include <ws2ipdef.h>

 

struct ip_msfilter

{

    // Адрес многоадресной рассылки.

    struct in_addr imsf_multiaddr;

    // Локальный интерфейс для присоединения к группе.

    struct in_addr imsf_interface;

    // Включено или исключено состояние фильтра, что указывается

    // с помощью определений MCAST_INCLUDE и MCAST_EXCLUDE соответственно.

    u_long imsf_fmode;

    // Количество исходных адресов IPv4 в массиве imsf_slist.

    u_long imsf_numsrc;

    // Массив адресов.

    struct in_addr imsf_slist[1];

};

Для работы с данными ioctl требуется сеть, в которой узлы обеспечивают поддержку IGMPv3 и ОС Windows не ниже XP. Существует несколько удобных функций, упрощающих работу с данными ioctl.

Функции для установки и получения фильтров IPv4:

#include <ws2tcpip.h>

 

// Установить фильтр для IPv4.

int setipv4sourcefilter(

    SOCKET Socket,

    IN_ADDR Interface,

    IN_ADDR Group,

    MULTICAST_MODE_TYPE FilterMode,

    ULONG SourceCount,

    const IN_ADDR *SourceList

);

 

// Получить фильтр для IPv4.

int getipv4sourcefilter(

    SOCKET Socket,

    IN_ADDR Interface,

    IN_ADDR Group,

    MULTICAST_MODE_TYPE *FilterMode,

    ULONG *SourceCount,

    IN_ADDR *SourceList

);

Универсальные функции, которые работают как с IPv4, так и с IPv6:

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

int setsourcefilter(

    SOCKET Socket,

    ULONG Interface,

    const SOCKADDR *Group,

    int GroupLength,

    MULTICAST_MODE_TYPE FilterMode,

    ULONG SourceCount,

    const SOCKADDR_STORAGE *SourceList);

 

// Получить фильтр.

int getsourcefilter(

    SOCKET Socket,

    ULONG Interface,

    const SOCKADDR *Group,

    int GroupLength,

    MULTICAST_MODE_TYPE *FilterMode,

    ULONG *SourceCount,

    SOCKADDR_STORAGE *SourceList

);

Мы не будем описывать их подробно: смысл параметров несложно понять; кроме того, они документированы в MSDN и мы не разбираем детали фильтрации.

QoS

Опции для работы с QoS в основном устарели и существуют для обеспечения совместимости:

SIO_CHK_QOS — получить информацию о характеристиках трафика QoS.

• SIO_GET_GROUP_QOS — получить структуру QoS, связанную с группой сокетов, к которой принадлежит этот сокет.

• SIO_GET_QOS — получить структуры QoS, связанные с сокетом.

• SIO_SET_GROUP_QOS — зарезервирована.

• SIO_SET_QOS — связать структуры QoS с сокетом.

• SIO_SET_PRIORITY_HINT — установить приоритет трафика для данного сокета. Опция не влияет на входящий трафик, однако исходящие пакеты будут формироваться с учетом данного приоритета. Пакеты с высоким приоритетом будут отправлены раньше.

ioctl для WFP

Для управления платформой фильтрации существует несколько ioctl:

SIO_QUERY_WFP_CONNECTION_REDIRECT_CONTEXT — получить контекст перенаправления принятого подключения TCP/IP.

• SIO_QUERY_WFP_CONNECTION_REDIRECT_RECORDS — получить запись перенаправления принятого подключения TCP/IP.

• SIO_SET_WFP_CONNECTION_REDIRECT_RECORDS — задать запись перенаправления принятого подключения TCP/IP.

• SIO_QUERY_WFP_ALE_ENDPOINT_HANDLE — запросить дескриптор конечной точки ALE.

— это набор уровней WFP в режиме ядра, которые используются для фильтрации с отслеживанием состояния. Он показан на рис. 18.3.

Рис. 18.3. ALE

ioctl для повышения безопасности

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

SIO_SET_SECURITY — установить параметры безопасности сокета.

SIO_QUERY_SECURITY — получить параметры безопасности сокета.

Вызов SIO_SET_SECURITY делает то же, что и следующая функция:

#include <Ws2tcpip.h>

 

int WSASetSocketSecurity(

    SOCKET Socket,

    const SOCKET_SECURITY_SETTINGS* SecuritySettings,

    ULONG SecuritySettingsLen,

    LPWSAOVERLAPPED Overlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTINE CompletionRoutine

);

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

#include <mstcpip.h>

 

typedef enum _SOCKET_SECURITY_PROTOCOL

{

    SOCKET_SECURITY_PROTOCOL_DEFAULT,

    SOCKET_SECURITY_PROTOCOL_IPSEC,

    SOCKET_SECURITY_PROTOCOL_IPSEC2,

    SOCKET_SECURITY_PROTOCOL_INVALID

} SOCKET_SECURITY_PROTOCOL;

 

typedef struct _SOCKET_SECURITY_SETTINGS

{

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

    SOCKET_SECURITY_PROTOCOL securityProtocol;

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

    ULONG securityFlags;

} SOCKET_SECURITY_SETTINGS;

Атрибут securityProtocol может принимать следующие значения:

SOCKET_SECURITY_PROTOCOL_DEFAULT — используется политика безопасности по умолчанию.

• SOCKET_SECURITY_PROTOCOL_IPSEC — используется IPsec.

SOCKET_SECURITY_PROTOCOL_INVALID — политика безопасности не используется.

Следующие флаги позволяют задать параметры. На данный момент это режим шифрования:

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

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

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

#include <mstcpip.h>

 

int WSAAPI WSAQuerySocketSecurity(

    SOCKET Socket,

    const SOCKET_SECURITY_QUERY_TEMPLATE *SecurityQueryTemplate,

    ULONG SecurityQueryTemplateLen,

    SOCKET_SECURITY_QUERY_INFO *SecurityQueryInfo,

    ULONG *SecurityQueryInfoLen,

    LPWSAOVERLAPPED Overlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTINE CompletionRoutine

);

Она принимает шаблон того, что требуется вернуть, и возвращает структуру SOCKET_SECURITY_QUERY_INFO:

#include <mstcpip.h>

 

typedef struct _SOCKET_SECURITY_QUERY_TEMPLATE

{

    // Протокол, используемый для защиты трафика.

    SOCKET_SECURITY_PROTOCOL SecurityProtocol;

    // IP-адрес однорангового узла, для которого запрашиваются

    // сведения о безопасности.

    SOCKADDR_STORAGE PeerAddress;

    // Маска доступа, используемая для возврата токенов учетной записи и узла.

    // Может быть равна 0, если данные поля не требуются.

    ULONG PeerTokenAccessMask;

} SOCKET_SECURITY_QUERY_TEMPLATE;

 

typedef struct _SOCKET_SECURITY_QUERY_INFO

{

    // Протокол защиты трафика.

    SOCKET_SECURITY_PROTOCOL SecurityProtocol;

    // Флаги.

    ULONG Flags;

    // Дескриптор токена доступа учетной записи, под которой

    // выполняется приложение.

    UINT64 PeerApplicationAccessTokenHandle;

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

    UINT64 PeerMachineAccessTokenHandle;

} SOCKET_SECURITY_QUERY_INFO;

Если были возвращены дескрипторы токенов, они должны быть закрыты.

Возвращаемые флаги могут иметь следующие значения:

SOCKET_INFO_CONNECTION_SECURED — трафик защищается протоколом безопасности.

SOCKET_INFO_CONNECTION_ENCRYPTED — трафик шифруется. При наличии этого флага SOCKET_INFO_CONNECTION_SECURED установлен всегда.

Следующие вызовы позволяют указать или удалить SPN — Service Principal Name, уникальный идентификатор экземпляра службы:

SIO_SET_PEER_TARGET_NAME — установить SPN для сокета.

SIO_DELETE_PEER_TARGET_NAME — удалить SPN для сокета.

SPN используется Kerberos для связи экземпляра службы с учетной записью, под которой эта служба используется, и позволяет клиентскому приложению запросить проверку подлинности службы для учетной записи, даже если у клиента нет имени учетной записи.

В свою очередь, Kerberos используется для аутентификации и авторизации в Active Directory. Подробнее о нем и о SPN можно узнать в разделе MSDN .

Функции WSASetSocketPeerTargetName() и WSADeleteSocketPeerTargetName() позволяют упростить добавление и удаление SPN:

#include <Ws2tcpip.h>

 

int WSASetSocketPeerTargetName(

    SOCKET Socket,

    const SOCKET_PEER_TARGET_NAME* PeerTargetName,

    ULONG PeerTargetNameLen,

    LPWSAOVERLAPPED Overlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTINE CompletionRoutine

);

 

int WSADeleteSocketPeerTargetName(

    SOCKET Socket,

    const struct sockaddr* PeerAddr,

    ULONG PeerAddrLen,

    LPWSAOVERLAPPED Overlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTINE CompletionRoutine

);

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

SPN описывается следующей структурой:

#include <mstcpip.h>

 

typedef struct _SOCKET_PEER_TARGET_NAME

{

    // Протокол, используемый для защиты трафика.

    SOCKET_SECURITY_PROTOCOL SecurityProtocol;

    // IP-адрес узла.

    SOCKADDR_STORAGE PeerAddress;

    // Размер структуры, включая имя в массиве AllStrings.

    ULONG PeerTargetNameStringLen;

    // SPN.

    wchar_t AllStrings[0];

} SOCKET_PEER_TARGET_NAME;

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

Прочие вызовы

Изменение параметров транспорта, совместимости и расширений:

SIO_QUERY_TRANSPORT_SETTING — получить параметр транспорта сокета. Только для TCP-сокетов.

• SIO_APPLY_TRANSPORT_SETTING — применить параметр транспорта к сокету:

• REAL_TIME_NOTIFICATION_CAPABILITY — запрос на установку настроек уведомлений реального времени. Единственный параметр транспорта, определенный для TCP-сокетов в ОС Windows 8 и ОС Windows Server 2012.

• ASSOCIATE_NAMERES_CONTEXT — хэндл FQDN, на основе которого будет применяться политика. Определен для ОС Windows 10 и ОС Windows Server 2016.

• SIO_GET_EXTENSION_FUNCTION_POINTER — получить указатель на функцию расширения, которую поддерживает сокетный провайдер. Как входной параметр вызов принимает известный GUID. Например, по GUID, равному WSAID_ACCEPTEX, он вернет указатель на функцию AcceptEx(). В современных ОС Windows для провайдеров от Microsoft использовать данный механизм обычно не требуется, но он позволяет не компоноваться с лишней DLL, обычно mswsock.dll, и убрать один промежуточный вызов функции.

• SIO_GET_MULTIPLE_EXTENSION_FUNCTION_POINTER — получить указатели на функции расширения ввода-вывода. Используется конкретно для получения указателей на функции RIO — зарегистрированного ввода-вывода, рассмотренного в книге 2. Входной параметр — GUID, равный WSAID_MULTIPLE_RIO. Выходной буфер должен содержать указатель на RIO_EXTENSION_FUNCTION_TABLE — таблицу с адресами функций RIO.

• SIO_SET_COMPATIBILITY_MODE — настроить поведение сетевого стека по умолчанию в зависимости от версии Windows. Можно настроить разное поведение в ответ на уменьшение размера буфера приема TCP или максимального размера окна. Весьма интересный ioctl, позволяющий, например, проверить, как будет работать сеть в приложении на разных версиях ОС Windows, запустив его только на старшей версии. Реально же используется для установки режимов совместимости, предоставляемых ОС для запуска приложений, которые в текущей версии работают некорректно.

SIO_PRIORITY_HINT — необходим для управления алгоритмом LEDBAT. Принимает значение типа PRIORITY_STATUS — структуры, которая содержит два 32-битных поля: для отправителя и для получателя. Подробнее описан в статье .

Для параметра вызова SIO_QUERY_TRANSPORT_SETTING используется GUID параметра в структуре:

#include <transportsettingcommon.h>

 

typedef struct TRANSPORT_SETTING_ID

{

    GUID Guid;

} TRANSPORT_SETTING_ID, *PTRANSPORT_SETTING_ID;

Для SIO_APPLY_TRANSPORT_SETTING структура идентификатора всегда находится перед данными, передаваемыми как параметр вызова.

Например, запрос REAL_TIME_NOTIFICATION_CAPABILITY вернет структуру REAL_TIME_NOTIFICATION_SETTING_OUTPUT:

#include <mstcpip.h>

 

typedef enum

{

    CONTROL_CHANNEL_TRIGGER_STATUS_INVALID = 0,

    CONTROL_CHANNEL_TRIGGER_STATUS_SOFTWARE_SLOT_ALLOCATED = 1,

    CONTROL_CHANNEL_TRIGGER_STATUS_HARDWARE_SLOT_ALLOCATED = 2,

    CONTROL_CHANNEL_TRIGGER_STATUS_POLICY_ERROR = 3,

    CONTROL_CHANNEL_TRIGGER_STATUS_SYSTEM_ERROR = 4,

    CONTROL_CHANNEL_TRIGGER_STATUS_TRANSPORT_DISCONNECTED = 5,

    CONTROL_CHANNEL_TRIGGER_STATUS_SERVICE_UNAVAILABLE = 6

} CONTROL_CHANNEL_TRIGGER_STATUS, *PCONTROL_CHANNEL_TRIGGER_STATUS;

 

typedef struct _REAL_TIME_NOTIFICATION_SETTING_OUTPUT

{

    CONTROL_CHANNEL_TRIGGER_STATUS ChannelStatus;

} REAL_TIME_NOTIFICATION_SETTING_OUTPUT,

*PREAL_TIME_NOTIFICATION_SETTING_OUTPUT;

Он принимает следующую структуру:

#include <mstcpip.h>

 

typedef struct _REAL_TIME_NOTIFICATION_SETTING_INPUT

{

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

    TRANSPORT_SETTING_ID TransportSettingId;

    // GUID события брокера для этого идентификатора транспорта.

    GUID BrokerEventGuid;

} REAL_TIME_NOTIFICATION_SETTING_INPUT, *PREAL_TIME_NOTIFICATION_SETTING_INPUT;

Видим, что первый атрибут структуры — идентификатор параметра транспорта TRANSPORT_SETTING_ID, за которым следуют значения параметров.

В структуре ASSOCIATE_NAMERES_CONTEXT_INPUT первый атрибут структуры тоже идентификатор:

#include <mstcpip.h>

 

typedef struct _ASSOCIATE_NAMERES_CONTEXT_INPUT

{

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

    TRANSPORT_SETTING_ID TransportSettingId;

    // Хэндл FQDN.

    UINT64 Handle;

} ASSOCIATE_NAMERES_CONTEXT_INPUT, *PASSOCIATE_NAMERES_CONTEXT_INPUT;

Аргумент вызова SIO_SET_COMPATIBILITY_MODE — структура:

#include <mswsock.h>

// Здесь определены константы.

#include <Sdkddkver.h>

 

typedef struct _WSA_COMPATIBILITY_MODE

{

    // Запрашиваемое поведение.

    WSA_COMPATIBILITY_BEHAVIOR_ID BehaviorId;

    // Версия NTDDI, используемого в заданной версии ОС.

    ULONG TargetOsVersion;

} WSA_COMPATIBILITY_MODE, *PWSA_COMPATIBILITY_MODE;

Значения типа поведения:

WsaBehaviorReceiveBuffering — для поля версии больше NTDDI_LONGHORN допускается уменьшение размера приемного буфера TCP в этом сокете с использованием параметра сокета SO_RCVBUF после установки TCP-соединения. Если для атрибута TargetOsVersion установлено значение, предшествующее Windows Vista, уменьшение размера приемного буфера TCP в этом сокете с использованием параметра сокета SO_RCVBUF не допускается после установки соединения.

• WsaBehaviorAutoTuning — для поля версии больше NTDDI_LONGHORN включается автонастройка окна приема. Коэффициент масштабирования окна TCP уменьшается до 2 по сравнению со значением по умолчанию, равным 8. Для меньших версий автонастройка окна приема и опция масштабирования окна TCP отключены, а максимальный размер окна приема ограничен 65 535 байтами.

• WsaBehaviorAll — запросить все возможные варианты поведения, определенные для WSA_COMPATIBILITY_BEHAVIOR_ID.

Значения поля версии:

NTDDI_LONGHORN — Windows Vista;

• NTDDI_WS03 — Windows Server 2003;

• NTDDI_WINXP — Windows XP;

NTDDI_WIN2K — Windows 2000.

Мы перечислили только константы, указанные в MSDN. На практике их значительно больше: NTDDI_WS08, NTDDI_WS08SP2, NTDDI_WS08SP3, NTDDI_WS08SP4, NTDDI_WIN7, NTDDI_WIN8, NTDDI_WINBLUE и т.д.

Управление дескриптором

Сокет в системе адресуется дескриптором или хэндлом, поэтому для него работают функции, применимые для всех дескрипторов. Их функциональность частично пересекается с функцией fcntl(), отсутствующей в ОС Windows.

Определить, что дескриптор является сокетом, позволит функция GetFileType():

#include <fileapi.h>

 

DWORD GetFileType(HANDLE hFile);

Результат FILE_TYPE_PIPE говорит о том, что дескриптор ссылается на сокет, именованный либо анонимный канал.

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

#include <handleapi.h>

 

// Получить флаги дескриптора.

bool GetHandleInformation(HANDLE hObject, LPDWORD lpdwFlags);

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

bool SetHandleInformation(HANDLE hObject, DWORD dwMask, DWORD dwFlags);

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

hObject — дескриптор.

• lpdwFlags — указатель на переменную для получения флагов.

• dwMask — маска установки флагов. Устанавливаются и сбрасываются только флаги, бит которых в маске равен 1.

dwFlags — флаги для установки.

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

Флаги могут принимать следующие значения:

HANDLE_FLAG_INHERIT — означает, что новый процесс, который создается через вызов CreateProcess(), унаследует этот дескриптор.

HANDLE_FLAG_PROTECT_FROM_CLOSE — дескриптор нельзя закрыть функцией CloseHandle().

Следовательно, вызов функции GetHandleInformation() позволяет определить, был ли создан сокет с установленным флагом WSA_FLAG_NO_HANDLE_INHERIT.

Проблемы, связанные с наследованием дескрипторов, рассмотрены в главе 23.

Так же как в Unix-подобных ОС, дескриптор только идентифицирует объект ядра «сокет». При этом дескрипторов может быть несколько.

Иногда требуется создать второй экземпляр дескриптора в другом процессе. В Unix-подобных системах для передачи дескрипторов существует сообщение SCM_RIGHTS, описанное в главе 9. В ОС Windows обычно для этого используется функция DuplicateHandle() из handleapi.h. Она подходит для почтовых ящиков и каналов. Однако для сокетов она не подойдет — для них следует использовать WSADuplicateSocket():

#include <winsock2.h>

 

int WSAAPI WSADuplicateSocket(

    SOCKET s,

    DWORD dwProcessId,

    LPWSAPROTOCOL_INFO lpProtocolInfo

);

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

s — дескриптор сокета.

• dwProcessId — PID процесса, в который будет произведено дублирование.

lpProtocolInfo — указатель на буфер, в который функция запишет структуру WSAPROTOCOL_INFO.

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

DuplicateHandle() некорректно выполняет подсчет ссылок на объект сокета, поэтому для WinSock используется отдельная функция.

Внимание! На дублируемых сокетах QoS должен быть выключен!

После вызова этой функции необходимо передать структуру, на которую указывает lpProtocolInfo, в процесс, куда был клонирован дескриптор, и затем вызвать с ней функцию WSASocket() в целевом процессе.

Пример вызова:

...

STARTUPINFO si_parent = { .cb = sizeof(STARTUPINFO) };

PROCESS_INFORMATION pi_child;

 

// Создать новый процесс.

if (CreateProcess(nullptr, exec_name, nullptr, nullptr, false,

                  CREATE_NEW_CONSOLE, nullptr, nullptr, &si_parent,

                  &pi_child))

{

    // После инициализации передать экземпляр в дочерний процесс.

    WSAPROTOCOL_INFO protocol_info;

 

    // Получить информацию о протоколе, которая используется при

    // дублировании сокета.

    if (SOCKET_ERROR == WSADuplicateSocket(client_sock, pi_child.dwProcessId,

                                           &protocol_info))

    {

        std::cerr

            << "WSADuplicateSocket(): failed. "

            << "Error = " << WSAGetLastError());

        throw ...;

    }

 

// Далее передать экземпляр структуры в дочерний процесс.

...

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

Чтобы продублировать сокет, функции WSASocket() нужно передать экземпляр структуры WSAPROTOCOL_INFO, а вместо семейства адресов, типа и протокола подставить константу FROM_PROTOCOL_INFO:

// Получить структуру, например, через общую память.

auto h_mapping = OpenFileMapping(FILE_MAP_READ, false, "pi_mapping");

auto mm_view = MapViewOfFile(h_mapping, FILE_MAP_READ, 0, 0, 0);

 

if (mm_view)

{

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

    memcpy(&protocol_info, mm_view, sizeof(WSAPROTOCOL_INFO));

 

    UnmapViewOfFile(mm_view);

 

    // Создать для описанного сокета новый дескриптор.

    SOCKET duplicated_sock = WSASocket(FROM_PROTOCOL_INFO,

                                       FROM_PROTOCOL_INFO,

                                       FROM_PROTOCOL_INFO, &protocol_info,

                                       0, 0);

 

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

}

В Python на основе данной функции реализованы методы сокета share() и from_share():

def socket.share(process_id)

def socket.fromshare(data)

Первый метод дублирует сокет в процесс с идентификатором process_id и возвращает некоторый байтовый объект, который нужно передать в целевой процесс и вызвать с ним метод from_share().

Также в Python существует метод socket.dup(), который дублирует сокетный дескриптор в текущем процессе:

def socket.dup()

Он вызывает функцию с его идентификатором:

#ifdef MS_WINDOWS

    WSAPROTOCOL_INFOW info;

...

    if (WSADuplicateSocketW(fd, GetCurrentProcessId(), &info))

        return set_error();

 

    newfd = WSASocketW(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO,

                       FROM_PROTOCOL_INFO, &info, 0, WSA_FLAG_OVERLAPPED);

Выше мы привели основные функции, которые реализуют специфическое поведение для сокетов. К сокетам применимы и некоторые другие функции, работающие с дескрипторами. Но они не являются частью сетевого WinAPI, и мы их не описываем.

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

На рис. 18.4 показано, как в ОС Windows производится системный вызов, например, к драйверам устройств.

Рис. 18.4. Схема вызова функции драйвера

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

#include <ioapiset.h>

 

bool DeviceIoControl(

    HANDLE hDevice,

    DWORD dwIoControlCode,

    LPVOID lpInBuffer,

    DWORD nInBufferSize,

    LPVOID lpOutBuffer,

    DWORD nOutBufferSize,

    LPDWORD lpBytesReturned,

    LPOVERLAPPED lpOverlapped

);

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

hDevice — дескриптор сетевого адаптера либо его имя в ОС Windows 2000 и более новых версиях.

• dwIoControlCode — код вызова.

• lpInBuffer — указатель на буфер, содержащий OID запроса, который использует NDIS.

• nInBufferSize — размер входного буфера.

• lpOutBuffer — буфер, который получает данные, возвращенные NDIS.

• nOutBufferSize — размер выходного буфера.

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

lpOverlapped — структура для перекрывающегося ввода-вывода.

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

Дескриптор сетевого адаптера можно получить, вызвав функцию CreateFile().

Как мы уже видели в главе 17, функция CreateFile() не только отвечает за работу с файлами, но и позволяет «открывать» почтовые ящики, каналы, а также устройства ввода-вывода, в том числе коммуникационные. Например, порт: "\\.\COM10".

Открыть сетевое устройство можно по его GUID, например: "\\?\ROOT#NET#0002#{cac88484-7515-4c03-82e6-71a87abac361}\ovpn-dco".

Функция вернет дескриптор, который можно использовать в DeviceIoControl().

Для универсального интерфейса сетевых драйверов NDIS самым востребованным вызовом является IOCTL_NDIS_QUERY_GLOBAL_STATS.

Он служит для получения различных данных об устройстве.

Список сетевых адаптеров хранится в ключах реестра

"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\NetworkCards\Nnn"

Значения этих переменных могут быть использованы, чтобы получить дескриптор адаптера, для чего его необходимо передать как имя файла в CreateFile().

Внимание! Начиная с Windows Vista, сетевая подсистема ядра перешла с NDIS 5.1 на NDIS 6, в котором данный вызов объявлен устаревшим.

Управление сетевыми адаптерами

Адаптеры описываются списком структур IP_ADAPTER_INFO, по одной для каждого адаптера на локальном узле:

#include <iptypes.h>

 

typedef struct _IP_ADAPTER_INFO

{

    // Следующий адаптер в списке.

    struct _IP_ADAPTER_INFO *Next;

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

    DWORD ComboIndex;

    // Имя адаптера.

    char AdapterName[MAX_ADAPTER_NAME_LENGTH + 4];

    // Текстовое описание адаптера.

    char Description[MAX_ADAPTER_DESCRIPTION_LENGTH + 4];

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

    UINT AddressLength;

    // MAC-адрес.

    BYTE Address[MAX_ADAPTER_ADDRESS_LENGTH];

    // Индекс адаптера.

    DWORD Index;

    // IANA-тип адаптера.

    UINT Type;

    // Флаг включения DHCP.

    UINT DhcpEnabled;

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

    PIP_ADDR_STRING CurrentIpAddress;

    // Список IP-адресов данного адаптера.

    IP_ADDR_STRING IpAddressList;

    // IP-адреса шлюзов.

    IP_ADDR_STRING GatewayList;

    // Адреса DHCP-серверов.

    IP_ADDR_STRING DhcpServer;

    // Использует ли адаптер WINS.

    BOOL HaveWins;

    // Основной сервер WINS.

    IP_ADDR_STRING PrimaryWinsServer;

    // Вторичный сервер WINS.

    IP_ADDR_STRING SecondaryWinsServer;

    // Время получения от DHCP-сервера текущего адреса и других параметров.

    time_t LeaseObtained;

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

    time_t LeaseExpires;

} IP_ADAPTER_INFO, *PIP_ADAPTER_INFO;

Структуру IP_ADDR_STRING мы опишем далее. Она содержит адрес, маску и указатель на следующий элемент списка.

Функция GetAdaptersInfo() возвращает список адаптеров:

#include <iphlpapi.h>

 

ULONG GetAdaptersInfo(PIP_ADAPTER_INFO AdapterInfo, PULONG SizePointer);

В SizePointer будет записано количество элементов в списке AdapterInfo.

Чтобы получить дополнительные сведения о конкретном адаптере, используется функция GetPerAdapterInfo():

#include <iphlpapi.h>

 

typedef struct _IP_PER_ADAPTER_INFO_W2KSP1

{

    // Включена ли автоматическая настройка адресов без DHCP — APIPA.

    UINT AutoconfigEnabled;

    // Настроен ли адрес с использованием APIPA.

    UINT AutoconfigActive;

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

    PIP_ADDR_STRING CurrentDnsServer;

    // Список используемых DNS-серверов.

    IP_ADDR_STRING DnsServerList;

} IP_PER_ADAPTER_INFO_W2KSP1, *PIP_PER_ADAPTER_INFO_W2KSP1;

 

DWORD GetPerAdapterInfo(ULONG IfIndex, PIP_PER_ADAPTER_INFO pPerAdapterInfo,

                        PULONG pOutBufLen);

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

IfIndex — индекс адаптера, для которого требуется получить информацию.

• pPerAdapterInfo — указатель на структуру IP_PER_ADAPTER_INFO для получения сведений об адаптере.

pOutBufLen — размер выходного буфера.

APIPA — Automatic Private IP Addressing — то же самое, что и ZeroConf. Она позволяет создавать IP-сети без явной конфигурации.

Функции GetPerAdapterInfo() требуется указать индекс адаптера. Этот индекс по имени адаптера возвращает функция GetAdapterIndex():

DWORD GetAdapterIndex(LPWSTR AdapterName, PULONG IfIndex);

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

AdapterName — указатель на имя сетевого адаптера в кодировке UTF-8.

IfIndex — указатель на возвращаемый индекс сетевого адаптера.

Функция GetAdaptersAddresses() позволяет получить IP-адреса, связанные с конкретным адаптером. Эта функция поддерживает адресацию как IPv4, так и IPv6:

ULONG GetAdaptersAddresses(ULONG Family, ULONG Flags, PVOID Reserved,

    PIP_ADAPTER_ADDRESSES AdapterAddresses, PULONG SizePointer);

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

Family — извлекаемое семейство адресов:

• AF_UNSPEC — IPv4 и IPv6;

• AF_INET — IPv4;

• AF_INET6 — IPv6.

• Flags — флаги, определяющие извлекаемые поля.

• Reserved — зарезервированный параметр, который должен быть равен nullptr.

• AdapterAddresses — указатель на буфер, который будет содержать список структур IP_ADAPTER_ADDRESSES.

SizePointer — размер буфера, содержащего информацию об адаптерах.

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

Часть из них поддерживается большинством ОС:

GAA_FLAG_SKIP_UNICAST — не возвращать адреса однонаправленных адаптеров.

• GAA_FLAG_SKIP_ANYCAST — не возвращать адреса IPv6 anycast.

• GAA_FLAG_SKIP_MULTICAST — не возвращать адреса многоадресной рассылки.

• GAA_FLAG_SKIP_DNS_SERVER — не возвращать адреса DNS-серверов.

• GAA_FLAG_INCLUDE_PREFIX — вернуть список префиксов IP-адресов для этого адаптера. Если этот флаг установлен, для адресов IPv6 и IPv4 возвращаются префиксы IP-адресов.

GAA_FLAG_SKIP_FRIENDLY_NAME — не возвращать имя адаптера.

Следующие флаги поддерживаются только версиями, начиная с Windows Vista:

GAA_FLAG_INCLUDE_WINS_INFO — вернуть адреса серверов WINS.

• GAA_FLAG_INCLUDE_GATEWAYS — вернуть адреса шлюзов по умолчанию.

• GAA_FLAG_INCLUDE_ALL_INTERFACES — вернуть адреса для всех интерфейсов NDIS.

GAA_FLAG_INCLUDE_TUNNEL_BINDINGORDER — вернуть адреса, отсортированные по порядку привязки туннеля.

Флаг GAA_FLAG_INCLUDE_ALL_COMPARTMENTS зарезервирован и пока не поддерживается. Он задает возврат обратных адресов во всех секциях маршрутизации.

Структура IP_ADAPTER_ADRESSES — список, который содержит множество данных: тип интерфейса, его индекс, адреса, метрики и т.п. Она достаточно объемная и .

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

int main()

{

    // Буфер для информации об одном адаптере.

    std::vector<IP_ADAPTER_ADDRESSES> adapters(1);

    ULONG out_size = adapters.size() * sizeof(IP_ADAPTER_ADDRESSES);

 

    // Первый вызов: если повезет, данные будут получены.

    if (const auto ret_code = GetAdaptersAddresses(AF_UNSPEC, 0, nullptr,

                                  adapters.data(), &out_size);

        ret_code != NO_ERROR)

    {

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

        if (ERROR_BUFFER_OVERFLOW == ret_code)

        {

            // Увеличить размер буфера.

            std::cout

                << "Increasing buffer [" << adapters.size() << " -> "

                << out_size / sizeof(IP_ADAPTER_ADDRESSES) + 1 << "]"

                << std::endl;

            adapters.resize(out_size / sizeof(IP_ADAPTER_ADDRESSES) + 1);

            out_size = adapters.size() * sizeof(IP_ADAPTER_ADDRESSES);

            // И повторить вызов.

            if (GetAdaptersAddresses(AF_UNSPEC, 0, nullptr,

                                     adapters.data(), &out_size) != NO_ERROR)

            {

                std::cerr << "Can't get adapters addresses!" << std::endl;

                return EXIT_FAILURE;

            }

        }

        else

        {

            std::cerr << "Can't get adapters addresses!" << std::endl;

            return EXIT_FAILURE;

        }

    }

Если вызов завершился успешно, остается только выполнить цикл по списку адаптеров, в котором выводятся их параметры:

    // Инициализировать сеть, чтобы выполнить

    // конвертацию адреса в строку функцией WSAAddressToString().

    socket_wrapper::SocketWrapper sw;

    // Цикл вывода параметров адаптеров.

    for (auto adapter = adapters.data(); adapter; adapter = adapter->Next)

    {

        // Имена в структуре состоят из широких символов, выводим их

        // в поток wcout.

        std::wcout << "Adapter name: " << adapter->FriendlyName

            << " (" << adapter->AdapterName << ")\n"

            << "Description: " << adapter->Description << "\n"

            << "IPv4 interface index: " << adapter->IfIndex << "\n"

            << "IPv6 interface index: " << adapter->Ipv6IfIndex << "\n"

            << "MTU: " << adapter->Mtu << "\n"

            << "DNS Suffix: " << adapter->DnsSuffix << "\n"

            << "Have IPv4: " << adapter->Ipv4Enabled << "\n"

            << "Have IPv6: " << adapter->Ipv6Enabled << "\n"

            << "Adapter type: ";

        // Тип адаптера.

        switch (adapter->IfType)

        {

            case IF_TYPE_ETHERNET_CSMACD:

                std::cout << "Ethernet device\n"; break;

            case IF_TYPE_PPP:

                std::cout << "PPP network interface\n"; break;

            case IF_TYPE_SOFTWARE_LOOPBACK:

                std::cout << "Software loopback\n"; break;

            case IF_TYPE_REGULAR_1822:

                std::cout << "1822\n"; break;

            case IF_TYPE_IEEE80211:

                std::cout << "Wireless IEEE 802.11\n"; break;

            default: std::cout << "Other\n";

        }

Выведем оперативный статус адаптера:

        std::cout << "Operational status: ";

        // Перечисление в строку.

        switch (adapter->OperStatus)

        {

            case IfOperStatusUp: std::cout << "IfOperStatusUp\n"; break;

            case IfOperStatusDown: std::cout << "IfOperStatusDown\n"; break;

            case IfOperStatusTesting:

                std::cout << "IfOperStatusTesting\n"; break;

            case IfOperStatusUnknown:

                std::cout << "IfOperStatusUnknown\n"; break;

            case IfOperStatusDormant:

                std::cout << "IfOperStatusDormant\n"; break;

            case IfOperStatusNotPresent:

                std::cout << "IfOperStatusNotPresent\n"; break;

            case IfOperStatusLowerLayerDown:

                std::cout << "IfOperStatusLowerLayerDown\n"; break;

            default: std::cout << "Other\n";

        };

Адаптеры могут иметь указатель на связанные IP-адреса. Это тоже список. ­Выведем его в отдельном цикле:

        for (auto addr = adapter->FirstUnicastAddress; addr;

             addr = addr->Next)

        {

            std::string ip(INET_ADDRSTRLEN, 0);

            DWORD sz = ip.size();

            // Конвертируем структуру наиболее подходящей функцией.

            if (!WSAAddressToString(addr->Address.lpSockaddr,

                                    addr->Address.iSockaddrLength, nullptr,

                                    ip.data(), &sz))

                std::cout << "My address = " << ip.c_str() << std::endl;

        }

 

        std::cout << std::endl;

    }

 

    return EXIT_FAILURE;

}

Для преобразования адреса используем ранее описанную функцию WSAAddressToString(). Именно она требует инициализации сетевой подсистемы вследствие использования вызова к сокетному провайдеру.

Запустим пример:

D:\build\bin> b01-ch18-get-adapters.exe

Increasing buffer [1 -> 37]

Adapter name: Ethernet ({6B875C37-F4FA-458A-A745-E04E64BE49BF})

Description: Realtek PCIe GBE Family Controller

IPv4 interface index: 6

IPv6 interface index: 0

MTU: 1500

DNS Suffix:

Have IPv4: 1

Have IPv6: 0

Adapter type: Ethernet device

Operational status: IfOperStatusUp

My address = 192.168.3.254

 

Adapter name: Loopback Pseudo-Interface 1 ({1B9974C7-CD33-11EB-A704-806E6F6E6963})

Description: Software Loopback Interface 1

IPv4 interface index: 1

IPv6 interface index: 1

MTU: 4294967295

DNS Suffix:

Have IPv4: 1

Have IPv6: 1

Adapter type: Software loopback

Operational status: IfOperStatusUp

My address = ::1

My address = 127.0.0.1

Видим, что в системе есть два адаптера. Первый — Ethernet-адаптер, который имеет только IPv4-адрес. Второй — интерфейс локальной петли с двумя адресами: IPv4 и IPv6.

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

Для получения информации о таких адаптерах используется функция GetUniDirectionalAdapterInfo():

#include <ipexport.h>

#include <iphlpapi.h>

 

typedef ULONG IPAddr;

 

typedef struct _IP_UNIDIRECTIONAL_ADAPTER_ADDRESS

{

    // Количество IPv4-адресов в Address.

    ULONG  NumAdapters;

    // Массив адресов.

    IPAddr Address[1];

} IP_UNIDIRECTIONAL_ADAPTER_ADDRESS, *PIP_UNIDIRECTIONAL_ADAPTER_ADDRESS;

 

DWORD GetUniDirectionalAdapterInfo(PIP_UNIDIRECTIONAL_ADAPTER_ADDRESS

                                   pIPIfInfo, PULONG dwOutBufLen);

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

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

dwOutBufLen — размер структуры, на которую указывает параметр pIPIfInfo.

При неудачном завершении функции возвращают код ошибки, иначе — NO_ERROR или ERROR_SUCCESS.

Управление сетевыми интерфейсами

В главе 10 мы уже говорили о том, что интерфейс — это абстракция уровня IP, а адаптер — абстракция уровня канала передачи данных. И хотя мы не всегда четко разграничивали интерфейсы и адаптеры, так как во многих случаях они соотносятся один к одному и эти термины можно считать взаимозаменяемыми, для функций управления интерфейсами это различие имеет значение.

Получение интерфейсов и их свойств

Таблицу интерфейсов можно получить, используя функцию GetIfTable(), которая возвращает все логические и физические интерфейсы в локальной системе.

#include <netioapi.h>

 

DWORD GetIfTable(PMIB_IFTABLE pIfTable, PULONG pdwSize, bool bOrder);

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

pIfTable — указатель на буфер, получающий таблицу интерфейсов.

• pdwSize — указатель на размер буфера в байтах. Функция изменит размер, если он недостаточен.

bOrder — если этот параметр принимает истинное значение, таблица будет отсортирована в порядке возрастания индексов интерфейсов.

Таблица интерфейсов представлена структурой, включающей массив и количество элементов в нем:

#include <ifmib.h>

 

typedef struct _MIB_IFTABLE

{

    // Количество элементов таблицы.

    DWORD dwNumEntries;

    // Массив элементов.

    MIB_IFROW table[ANY_SIZE];

} MIB_IFTABLE, *PMIB_IFTABLE;

Ряд таблицы:

#include <ifmib.h>

 

typedef struct _MIB_IFROW

{

    // Название интерфейса.

    WCHAR wszName[MAX_INTERFACE_NAME_LEN];

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

    IF_INDEX dwIndex;

    // IANA-тип интерфейса.

    IFTYPE dwType;

    // MTU интерфейса.

    DWORD dwMtu;

    // Скорость интерфейса в битах в секунду

    DWORD dwSpeed;

    // Длина физического адреса в байтах.

    DWORD dwPhysAddrLen;

    // Физический адрес.

    UCHAR bPhysAddr[MAXLEN_PHYSADDR];

    // Административное состояние интерфейса.

    DWORD dwAdminStatus;

    // Рабочее состояние интерфейса.

    INTERNAL_IF_OPER_STATUS dwOperStatus;

В ОС Windows различаются поля dwAdminStatus — административное состояние — и dwOperStatus — оперативное состояние. Подробно разница между ними была описана в главе 10.

Административное состояние, которое задано администратором:

NET_IF_ADMIN_STATUS_UP — интерфейс инициализируется и включается.

• NET_IF_ADMIN_STATUS_DOWN — интерфейс не работает. Его нельзя использовать для передачи или получения сетевых данных.

NET_IF_ADMIN_STATUS_TESTING — интерфейс находится в тестовом режиме. Никакие сетевые данные не могут быть переданы или получены.

Эти константы определены в файле netioapi.h, а используются в структурах, описанных далее.

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

IF_OPER_STATUS_NON_OPERATIONAL — интерфейс локальной сети отключен, например, из-за конфликта адресов.

• IF_OPER_STATUS_UNREACHABLE — неподключенный интерфейс глобальной сети.

• IF_OPER_STATUS_DISCONNECTED — для интерфейса локальной сети: сетевой кабель не подключен. Для интерфейса глобальной сети: нет оператора.

• IF_OPER_STATUS_CONNECTING — интерфейс глобальной сети, находящийся в процессе подключения.

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

IF_OPER_STATUS_OPERATIONAL — состояние по умолчанию для адаптеров локальной сети.

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

    // Количество сотых секунд, в течение которых работает интерфейс,

    // начиная с последней загрузки системы. Не поддерживается.

    DWORD dwLastChange;

    // Количество октетов, полученных интерфейсом.

    DWORD dwInOctets;

    // Количество одноадресных пакетов, прошедших через интерфейс.

    DWORD dwInUcastPkts;

    // Количество неодноадресных пакетов, полученных интерфейсом.

    DWORD dwInNUcastPkts;

    // Количество отброшенных пакетов.

    DWORD dwInDiscards;

    // Количество входящих пакетов с ошибками.

    DWORD dwInErrors;

    // Количество входящих пакетов, отброшенных из-за неизвестного протокола.

    DWORD dwInUnknownProtos;

    // Количество октетов, отправленных через интерфейс.

    DWORD dwOutOctets;

    // Количество одноадресных пакетов, отправленных через интерфейс.

    DWORD dwOutUcastPkts;

    // Количество неодноадресных пакетов, отправленных через интерфейс.

    DWORD dwOutNUcastPkts;

    // Количество отброшенных исходящих пакетов.

    DWORD dwOutDiscards;

    // Количество исходящих пакетов отброшенных из-за ошибок.

    DWORD dwOutErrors;

    // Длина очереди передачи. Сейчас не используется.

    DWORD dwOutQLen;

    // Длина поля описания интерфейса.

    DWORD dwDescrLen;

    // Поле описания интерфейса.

    UCHAR bDescr[MAXLEN_IFDESCR];

} MIB_IFROW, *PMIB_IFROW;

Чтобы получить данные по конкретному интерфейсу, в структуре MIB_IFROW необходимо заполнить поле dwIndex, а затем передать указатель на структуру функции GetIfEntry():

DWORD GetIfEntry(PMIB_IFROW pIfRow);

Если функция завершится успешно, она вернет значение NO_ERROR.

Начиная с версии Windows Vista, для работы с интерфейсами были добавлены новые функции:

GetIfTable2().

GetIfTable2Ex() — расширенная версия функции GetIfTable2(), которая позволяет выбрать уровень извлекаемой информации интерфейса.

#include <netioapi.h>

 

NETIO_STATUS GetIfTable2(PMIB_IF_TABLE2 *table);

NETIO_STATUS GetIfTable2Ex(MIB_IF_TABLE_LEVEL level, PMIB_IF_TABLE2 *table);

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

table — указатель на буфер, получающий таблицу интерфейсов.

• level — уровень извлекаемых сведений об интерфейсе:

• MibIfTableNormal — возвращается информация из верхней части стека фильтров.

• MibIfTableRaw — возвращается информация с уровня интерфейса.

Параметр level требуется для того, чтобы узнавать некоторые статистические данные об адаптере, минуя фильтры. Например, фильтр может изменить состояние активности интерфейса с up на down, и если требуется получить информацию непосредственно с устройства, необходимо использовать значение MibIfTableRaw.

В отличие от функции GetIfTable(), для которой буфер должен быть предварительно выделен, эти функции сами выделяют буфер, который затем требуется освободить, вызвав функцию:

void FreeMibTable(PVOID Memory);

Внимание! Хотя оба варианта функций, как с суффиксом 2, так и без него, выполняют сходные действия, они работают с абсолютно разными структурами, которые между собой несовместимы.

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

#include <netioapi.h>

 

typedef struct _MIB_IF_TABLE2

{

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

    ULONG NumEntries;

    // Массив структур.

    MIB_IF_ROW2 Table[ANY_SIZE];

} MIB_IF_TABLE2, *PMIB_IF_TABLE2;

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

typedef struct _MIB_IF_ROW2

{

    // Локальный уникальный идентификатор интерфейса.

    NET_LUID InterfaceLuid;

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

    NET_IFINDEX InterfaceIndex;

    // Глобальный уникальный идентификатор.

    GUID InterfaceGuid;

    // Псевдоним интерфейса.

    WCHAR Alias[IF_MAX_STRING_SIZE + 1];

    // Описание интерфейса.

    WCHAR Description[IF_MAX_STRING_SIZE + 1];

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

    ULONG PhysicalAddressLength;

    // MAC-адрес.

    UCHAR PhysicalAddress[IF_MAX_PHYS_ADDRESS_LENGTH];

    // Постоянный MAC-адрес.

    UCHAR PermanentPhysicalAddress[IF_MAX_PHYS_ADDRESS_LENGTH];

    // MTU интерфейса.

    ULONG Mtu;

    // IANA-тип интерфейса.

    IFTYPE Type;

    // Метод инкапсуляции, который используется туннелем.

    // TUNNEL_TYPE_NONE — не туннель, TUNNEL_TYPE_DIRECT — инкапсуляция в IP.

    // TUNNEL_TYPE_6TO4 — IPv6 в IPv4 и т.п.

    TUNNEL_TYPE TunnelType;

    // Тип носителя NDIS.

    NDIS_MEDIUM MediaType;

    // Тип физической среды.

    NDIS_PHYSICAL_MEDIUM PhysicalMediumType;

    // Тип доступа интерфейса.

    NET_IF_ACCESS_TYPE AccessType;

    // Направление обмена данными.

    NET_IF_DIRECTION_TYPE DirectionType;

...

// Далее идут флаги, поля состояния и статистики интерфейса.

} MIB_IF_ROW2, *PMIB_IF_ROW2;

MAC-адрес можно изменить программно, и поэтому в структуре возвращается два поля: текущий MAC-адрес и постоянный.

Тип носителя — это тип нижележащей физической сети. Например, NdisMedium802_3 — это сеть Ethernet, NdisMedium802_5 — Token Ring и т.п. Все типы перечислены в MSDN.

Тип физической среды — это коммуникационная среда, которая передает данные непосредственно:

NdisPhysicalMediumUnspecified — не указана. Например, односторонние спутниковые каналы являются таковыми.

• NdisPhysicalMediumWirelessLan — беспроводная сеть 802.11.

• NdisPhysicalMediumCableModem — кабельный модем DOCSIS.

• NdisPhysicalMediumPhoneLine — телефонные линии PSTN.

NdisPhysicalMediumNative802_11 — сеть 802.11.

Всего определено порядка 20 типов среды передачи, которые также подробно описаны в MSDN.

Типы доступа:

NET_IF_ACCESS_LOOPBACK — локальная петля. Интерфейс получает то, что отправил.

• NET_IF_ACCESS_BROADCAST — широковещательный доступ. Интерфейс обеспечивает встроенную поддержку служб многоадресной или широковещательной рассылки. Например, Ethernet.

• NET_IF_ACCESS_POINT_TO_POINT — «точка-точка».

• NET_IF_ACCESS_POINT_TO_MULTI_POINT — «точка-многоточка». Носители с несколькими доступами или non-broadcast multiple access network, NBMA. Например, внутренний интерфейс RAS или X.25.

NET_IF_ACCESS_MAXIMUM — максимально возможное значение для перечисления. Не является допустимым.

Тип направления обмена данными:

NET_IF_DIRECTION_SENDRECEIVE — двунаправленный интерфейс.

• NET_IF_DIRECTION_SENDONLY — интерфейс, который может только отправлять данные.

• NET_IF_DIRECTION_RECEIVEONLY — интерфейс, который может только принимать данные.

NET_IF_DIRECTION_MAXIMUM — максимально возможное значение типа. Не является допустимым.

Рабочее состояние интерфейса также содержится в таблице, но здесь не показано. Его тип отличается от типа в MIB_IFROW. Остальные поля во многом похожи.

Внимание! После завершения работы с таблицей ее нужно удалить, вызвав функцию FreeMibTable().

В новом API существуют аналогичные функции для получения данных конкретного интерфейса:

NETIO_STATUS GetIfEntry2(PMIB_IF_ROW2 Row);

NETIO_STATUS GetIfEntry2Ex(MIB_IF_ENTRY_LEVEL Level, PMIB_IF_ROW2 Row);

Чтобы ими воспользоваться, необходимо заполнить в передаваемой структуре атрибуты InterfaceLuid или InterfaceIndex.

Если InterfaceLuid и InterfaceIndex не соответствуют друг другу и указывают на разные адаптеры, будет использоваться InterfaceLuid.

Получить количество интерфейсов позволяет функция GetNumberOfInterfaces():

#include <iphlpapi.h>

 

DWORD GetNumberOfInterfaces(PDWORD pdwNumIf);

Результат будет записан по указателю pdwNumIf. Ее вызов может быть полезен для предварительного выделения буфера при вызове функции GetIfEntry().

Функция GetInterfaceInfo() возвращает таблицу, содержащую имена и соответствующие индексы для интерфейсов:

#include <ipexport.h>

#include <iphlpapi.h>

 

typedef struct _IP_INTERFACE_INFO

{

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

    LONG NumAdapters;

    // Массив отображений

    IP_ADAPTER_INDEX_MAP Adapter[1];

} IP_INTERFACE_INFO, *PIP_INTERFACE_INFO;

 

typedef struct _IP_ADAPTER_INDEX_MAP

{

    // Индекс интерфейса, связанный с адаптером.

    ULONG Index;

    // Имя адаптера либо его GUID.

    WCHAR Name[MAX_ADAPTER_NAME];

} IP_ADAPTER_INDEX_MAP, *PIP_ADAPTER_INDEX_MAP;

 

DWORD GetInterfaceInfo(PIP_INTERFACE_INFO pIfTable, PULONG dwOutBufLen);

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

pIfTable — указатель на буфер;

dwOutBufLen — размер буфера, на который указывает параметр pIfTable.

При успешном вызове функция вернет NO_ERROR.

Изменение конфигурации

С помощью функции SetIfEntry() можно изменить административный статус интерфейса — включить его или выключить:

#include <iphlpapi.h>

 

DWORD SetIfEntry(PMIB_IFROW pIfRow);

В передаваемой структуре следует задать индекс определенного интерфейса в атрибуте dwIndex и необходимое значение атрибута dwAdminStatus.

Вызов, введенный начиная с Windows Vista, позволяет задать свойства интерфейса:

#include <netioapi.h>

 

NETIO_STATUS SetIpNetEntry2(PMIB_IPNET_ROW2 Row);

Чтобы указать адаптер, следует установить атрибут InterfaceLuid или InterfaceIndex. Также должны быть корректно установлены атрибуты PermanentAddress, PhysicalAddress и PhysicalAddressLength.

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

Управлять интерфейсами могут только администраторы.

Взаимное преобразование идентификаторов интерфейса

Основные идентификаторы, которыми адресуются интерфейсы в ОС Windows, — LUID и GUID.

LUID — это локальный уникальный идентификатор (Locally Unique IDentifier):

#include <ifdef.h>

 

typedef union _NET_LUID_LH

{

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

    ULONG64 Value;

    struct

    {

        ULONG64 Reserved : 24;

        // Индекс LUID.

        ULONG64 NetLuidIndex : 24;

        // IANA-тип интерфейса.

        ULONG64 IfType : 16;

    } Info;

} NET_LUID_LH, *PNET_LUID_LH;

 

typedef NET_LUID_LH NET_LUID;

typedef NET_LUID* PNET_LUID;

 

// Тип идентификатора локального интерфейса -

// Locally unique datalink interface.

typedef NET_LUID IF_LUID, *PIF_LUID;

Второй тип идентификатора — GUID (Globally Unique IDentifier). Это число, которое является глобальным идентификатором, не имеющим смысловой нагрузки.

Список индексов LUID хранится в ключах реестра:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NDIS\IfTypes[тип интерфейса]\IfUsedNetLuidIndices.

Выделить же интерфейс можно через вызов функции NDIS NdisIfAllo­cateNetLuidIndex().

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

#include <netioapi.h>

 

// GUID в LUID.

NETIO_STATUS ConvertInterfaceGuidToLuid(

    const GUID *InterfaceGuid,

    PNET_LUID  InterfaceLuid

);

 

// LUID в GUID.

NETIO_STATUS ConvertInterfaceLuidToGuid(

    const NET_LUID *InterfaceLuid,

    GUID *InterfaceGuid

);

Индекс также уникален для интерфейса в системе, и он может быть однозначно преобразован в LUID:

typedef ULONG NET_IFINDEX, *PNET_IFINDEX;

 

// Индекс в LUID.

NETIO_STATUS ConvertInterfaceIndexToLuid(

    NET_IFINDEX InterfaceIndex,

    PNET_LUID InterfaceLuid

);

 

// LUID в индекс.

NETIO_STATUS ConvertInterfaceLuidToIndex(

    const NET_LUID *InterfaceLuid,

    PNET_IFINDEX InterfaceIndex

);

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

NETIO_STATUS if_indextoname(NET_IFINDEX InterfaceIndex, PCHAR InterfaceName);

 

NETIO_STATUS if_nametoindex(PCSTR InterfaceName);

Более современный вариант — использовать преобразование имени в LUID и обратно:

// LUID в имя.

NETIO_STATUS ConvertInterfaceLuidToNameW(

    const NET_LUID *InterfaceLuid,

    PSTR InterfaceName,

    size_t Length

);

 

// Имя в LUID.

NETIO_STATUS ConvertInterfaceNameToLuidW(

    const WCHAR *InterfaceName,

    NET_LUID *InterfaceLuid

);

Указанные функции работают только с широкими строками.

Псевдонимы интерфейсов также могут быть преобразованы в LUID и обратно:

#include <netioapi.h>

 

NETIO_STATUS ConvertInterfaceAliasToLuid(

    const WCHAR *InterfaceAlias,

    PNET_LUID InterfaceLuid

);

 

NETIO_STATUS ConvertInterfaceLuidToAlias(

    const NET_LUID *InterfaceLuid,

    PWSTR InterfaceAlias,

    size_t Length

);

Как пример, в реализованной на C части модуля socket в CPython метод socket.if_nameindex() использует данные функции.

Сначала вызывается GetIfTable2Ex():

#ifdef MS_WINDOWS

    // Указатель на таблицу интерфейсов.

    PMIB_IF_TABLE2 tbl;

    // Код возврата функции.

    int ret;

 

    // Получить таблицу интерфейсов.

    if ((ret = GetIfTable2Ex(MibIfTableRaw, &tbl)) != NO_ERROR)

    {

         // Управление ссылками в Python, далее эти места будут пропущены.

         Py_DECREF(list);

         // Вместо GetLastError () используется ret.

         return PyErr_SetFromWindowsErr(ret);

    }

Затем в цикле производится конвертация идентификаторов интерфейсов в имена:

    for (ULONG i = 0; i < tbl->NumEntries; ++i)

    {

        MIB_IF_ROW2 r = tbl->Table[i];

 

        // Буфер для имени.

        WCHAR buf[NDIS_IF_MAX_STRING_SIZE + 1];

 

        // Сконвертировать LUID в имя, которое будет записано в buf.

        if ((ret = ConvertInterfaceLuidToNameW(&r.InterfaceLuid, buf,

                                                Py_ARRAY_LENGTH(buf))))

        {

            // Помним, что необходимо освобождать память.

            FreeMibTable(tbl);

            // Вместо GetLastError() используется код возврата.

            return PyErr_SetFromWindowsErr(ret);

        }

 

        // Построить кортеж, содержащий индекс и имя, затем добавим в список.

        PyObject *tuple = Py_BuildValue("Iu", r.InterfaceIndex, buf);

        if (tuple == nullptr || PyList_Append(list, tuple) == -1)

        {

          FreeMibTable(tbl);

          return nullptr;

        }

    }

 

    // Очистить память, выделенную под таблицу.

    FreeMibTable(tbl);

 

    return list;

Затем таблица освобождается, а в Python возвращаются полученные данные интерфейсов.

Резюме

Хотя в ОС Windows, так же как в Linux, после создания сокета можно управлять различными его атрибутами с помощью функций getsockopt(), setsockopt(), номенклатура опций в этих системах значительно отличается.

Кроме того, в ОС Windows функции ioctl() и fcntl() отсутствуют. Их заменяют функции ioctlsocket(), WSAIoctl() и несколько функций для управления дескрипторами. Поскольку Netlink-подобного интерфейса в ОС Windows тоже нет, ioctl существует очень много, а их состав расширяется с выходом каждой новой версии системы.

Функция WSAIoctl() может работать в overlapped-режиме, чтобы предотвращать длительное ожидание завершения некоторых вызовов.

Вызовы существуют для управления уведомлениями, интерфейсами, буферами, для привязки обработки данных сокета к определенным ядрам процессора, резервирования портов, настроек TCP и UDP, настройки WFP и т.д.

Для удобства работы с вызовами и опциями в API существуют функции-обертки. Большая часть из них объявлена в заголовочном файле ws2tcpip.h.

Работать со вспомогательными данными в WinSock можно, используя функции WSASendMsg() и WSARecvMsg(), которые относятся к расширениям от Microsoft.

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

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

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

1. Зачем нужны функции-обертки над опциями?

2. В каких случаях следует использовать функции-обертки для установки опций, а в каких — нет?

3. Как получить время, в течение которого используется подключенный сокет?

4. Когда стоит использовать опцию сокета SO_OPENTYPE? Почему?

5. Какую задачу выполняет опция SO_REUSE_UNICASTPORT?

6. Что будет, если опция SO_UPDATE_ACCEPT_CONTEXT на сокете выключена?

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

8. Как включить получение явных уведомлений о перегрузке?

9. Каким образом можно ограничить диапазон IPv6-адресов, с которыми может обмениваться сокет? Для чего это может быть нужно?

10. Как включить объединение UDP-сообщений в одну дейтаграмму? Для чего это может потребоваться?

11. Какие функции используются для работы со вспомогательными данными в WinSock?

12. Для чего используются функции ioctlsocket() и WSAIoctl()? В чем между ними разница?

13. Чем отличается в ОС Windows функция ioctlsocket() от своего аналога ioctl() в Linux?

14. Как можно избежать блокировки выполнения кода при использовании функций ioctl в Windows?

15. Как получить список сетевых интерфейсов в WinSock, используя вызов ioctl?

16. Какие ioctl позволяют определить идеальный размер буфера отправки? Как ими пользоваться?

17. Как ограничить диапазон портов для приложения?

18. Каким образом включить для сокета неразборчивый режим и какие для этого должны быть соблюдены условия?

19. Как включить для сокета обязательное шифрование трафика?

20. Какие функции используются для управления дескрипторами сокетов?

21. Каким образом возможно переключить наследование дочерним процессом дескриптора конкретного сокета? Для чего это может быть полезно?

22. Как в ОС Windows вызвать функцию драйвера сетевого устройства? Для чего это может потребоваться?

23. Как в ОС Windows узнать, что сетевой интерфейс работает?

24. Какая функция изменяет административное состояние интерфейса?

25. Чем отличается функция для работы с интерфейсами GetIfTable2Ex() от функции GetIfTable2()?

26. В начале главы показан пример установки MTU с помощью функции-обертки. Создайте свой пример, в котором MTU устанавливается без использования функций-оберток.

27. Расширьте пример, выводящий все сетевые адаптеры в системе. Отключите сетевые адаптеры, у которых отсутствует подключение к сети.


Назад: Глава 18. Управление сетью в ОС Windows
Дальше: Глава 19. IP Helper API