Было бы неплохо возвращать деньги за дефектное ПО, вот только это за год обанкротит всю индустрию разработки.
Эндрю С. Таненбаум, «Компьютерные сети»
В этой главе мы рассмотрим вспомогательную библиотеку IP Helper, предоставляющую API для работы с параметрами конфигурации протоколов и сетевых устройств.
Часть API IP Helper уже была рассмотрена в прошлых главах. В этой главе описываются структуры данных и API для получения статистики, уведомлений об изменении конфигурации сети, работы с DHCP и т.п.
Далее мы коснемся диагностического API, в том числе для работы с ICMP, и на примере утилиты ping разберемся, как его использовать.
Закончим главу описанием функций, предоставляющих информацию о состоянии протоколов TCP/IP, а также об интерфейсах, маршрутах и статистике, которые применяются чаще всего и которые мы еще не описывали.
В случае успеха все функции IP Helper возвращают значение NO_ERROR, если не было оговорено иное.
Данную главу можно использовать в качестве справочника при дальнейшей работе с книгой.
Для получения общей информации о конфигурации сети можно использовать функцию GetNetworkParams(). Эта функция возвращает, например, список DNS-серверов, используемых локальным компьютером:
#include <iphlpapi.h>
DWORD GetNetworkParams(PFIXED_INFO pFixedInfo, PULONG pOutBufLen);
Параметры функции GetNetworkParams():
• pFixedInfo — указатель на структуру FIXED_INFO, которая получает сетевые параметры.
• pOutBufLen — указатель на размер структуры, в который будет записан необходимый размер буфера, если переданного размера недостаточно.
Структура FIXED_INFO содержит список IP-структур IP_ADDR_STRING:
#include <iphlpapi.h>
#include <iptypes.h>
// Элемент списка адресов.
typedef struct _IP_ADDR_STRING
{
// Указатель на следующий элемент.
struct _IP_ADDR_STRING *Next;
// Строка IP-адреса в десятичной нотации.
IP_ADDRESS_STRING IpAddress;
// Строка маски.
IP_MASK_STRING IpMask;
// Запись сетевой таблицы, используемая функциями AddIPAddress()
// и DeleteIPAddress().
DWORD Context;
} IP_ADDR_STRING, *PIP_ADDR_STRING;
Этот список содержится в передаваемой функции структуре FIXED_INFO:
typedef struct
{
// Имя локального узла.
char HostName[MAX_HOSTNAME_LEN + 4];
// Имя домена локального узла.
char DomainName[MAX_DOMAIN_NAME_LEN + 4];
// Зарезервировано. Вместо нее используется следующее поле.
PIP_ADDR_STRING CurrentDnsServer;
// Список адресов DNS-серверов.
IP_ADDR_STRING DnsServerList;
// Тип узла.
UINT NodeType;
// Область DHCP.
char ScopeId[MAX_SCOPE_ID_LEN + 4];
// Включена ли маршрутизация?
UINT EnableRouting;
// Этот узел — прокси?
UINT EnableProxy;
// Включена ли служба DNS на локальном узле.
UINT EnableDns;
} FIXED_INFO_W2KSP1, *PFIXED_INFO_W2KSP1;
// В новых версиях ОС структура FIXED_INFO аналогична FIXED_INFO_W2KSP1.
#if (NTDDI_VERSION >= NTDDI_WIN2KSP1)
typedef FIXED_INFO_W2KSP1 FIXED_INFO;
typedef FIXED_INFO_W2KSP1 *PFIXED_INFO;
#endif
Тип узла NetBIOS в NodeType влияет на тип разрешения имен и может иметь следующие значения:
• BROADCAST_NODETYPE — широковещательный узел. Пытается разрешить имена путем широковещательной рассылки.
• PEER_TO_PEER_NODETYPE — одноранговый узел.
• MIXED_NODETYPE — смешанный тип. Сначала использует широковещание, затем WINS.
• HYBRID_NODETYPE — гибридный узел. Сначала использует WINS, затем широковещание.
Большую часть параметров узла, которые возвращает функция GetNetworkParams(), можно получить, вызвав из командной строки команду ipconfig /all.
В Windows 10 были добавлены функции для получения состояния и способа тарификации подключения к сети, а также определения наличия доступа в интернет. Эти функции опираются на структуру NL_NETWORK_CONNECTIVITY_HINT:
#include <nldef.h>
typedef struct _NL_NETWORK_CONNECTIVITY_HINT
{
// Уровень сетевого подключения.
NL_NETWORK_CONNECTIVITY_LEVEL_HINT ConnectivityLevel;
// Стоимость подключения, затраты на его использование.
NL_NETWORK_CONNECTIVITY_COST_HINT ConnectivityCost;
// Истина, если подключение исчерпывает лимит данных.
BOOLEAN ApproachingDataLimit;
// Истина, если подключение уже превысило лимит данных.
BOOLEAN OverDataLimit;
// Подключение в роуминге.
BOOLEAN Roaming;
} NL_NETWORK_CONNECTIVITY_HINT;
В структуре используются следующие перечисления уровней подключения:
#include <nldef.h>
// Определяет константы, указывающие на уровень сетевого подключения.
typedef enum _NL_NETWORK_CONNECTIVITY_LEVEL_HINT
{
// Неизвестный уровень подключения.
NetworkConnectivityLevelHintUnknown = 0,
// Подключение отсутствует.
NetworkConnectivityLevelHintNone,
// Имеется подключение только к локальной сети.
NetworkConnectivityLevelHintLocalAccess,
// Имеется доступ в интернет.
NetworkConnectivityLevelHintInternetAccess,
// Ограниченное интернет-подключение.
NetworkConnectivityLevelHintConstrainedInternetAccess,
// Интерфейс скрыт для подключений.
NetworkConnectivityLevelHintHidden
} NL_NETWORK_CONNECTIVITY_LEVEL_HINT;
«Ограниченное подключение» — такое, при котором есть локальный доступ к веб-порталу, требующему авторизации, чтобы подключиться к интернету. Пример такого подключения — общественный Wi-Fi.
Внимание! Данный статус не гарантирует, что такой портал был обнаружен.
Перечисление NL_NETWORK_CONNECTIVITY_COST_HIN определяет константы, указывающие способ тарификации подключения к сети:
// Стоимость подключения.
typedef enum _NL_NETWORK_CONNECTIVITY_COST_HINT
{
// Сведения о расходах недоступны.
NetworkConnectivityCostHintUnknown = 0,
// Неограниченное подключение.
NetworkConnectivityCostHintUnrestricted,
// Некоторый объем трафика предоставлен бесплатно.
NetworkConnectivityCostHintFixed,
// Плата за трафик.
NetworkConnectivityCostHintVariable
} NL_NETWORK_CONNECTIVITY_COST_HINT;
Порядок работы с функциями для получения структуры описания подключений должен быть понятен:
#include <netioapi.h>
// Получить данные по всем интерфейсам.
NETIO_STATUS GetNetworkConnectivityHint(
NL_NETWORK_CONNECTIVITY_HINT *ConnectivityHint
);
// Получить данные по заданному интерфейсу.
NETIO_STATUS GetNetworkConnectivityHintForInterface(
NET_IFINDEX InterfaceIndex,
NL_NETWORK_CONNECTIVITY_HINT *ConnectivityHint
);
Можно вызвать функцию либо для получения общего состояния, либо для получения состояния конкретного адаптера по индексу.
Также существует асинхронная функция, выдающая оповещение при появлении информации о соединении:
// Тип функции обратного вызова.
typedef void (NETIOAPI_API_ *PNETWORK_CONNECTIVITY_HINT_CHANGE_CALLBACK)(
// Пользовательское значение.
void* CallerContext,
// Состояние подключения.
NL_NETWORK_CONNECTIVITY_HINT ConnectivityHint
);
// Функция получения уведомления о состоянии интерфейса.
NETIO_STATUS NotifyNetworkConnectivityHintChange(
PNETWORK_CONNECTIVITY_HINT_CHANGE_CALLBACK Callback,
void* CallerContext,
BOOLEAN InitialNotification,
PHANDLE NotificationHandle
);
Параметры функции NotifyNetworkConnectivityHintChange():
• Callback — функция обратного вызова, которая будет вызвана, когда информация о соединении будет доступна.
• CallerContext — значение, которое может задать пользователь для передачи в функцию обратного вызова.
• InitialNotification — истина, если должно быть предоставлено уведомление об инициализации.
• NotificationHandle — указатель на хэндл, который будет использован для уведомления.
Тип NETIO_STATUS, возвращаемый функцией, — DWORD.
IP Helper позволяет управлять IP-адресами, связанными с интерфейсами на локальном компьютере.
Функция GetIpAddrTable() возвращает таблицу, содержащую сопоставление IP-адресов с интерфейсами:
#include <iphlpapi.h>
DWORD GetIpAddrTable(PMIB_IPADDRTABLE pIpAddrTable, PULONG pdwSize,
bool bOrder);
Параметры функции GetIpAddrTable():
• pIpAddrTable — указатель на буфер, который получает таблицу сопоставления адресов интерфейса с адресами IPv4.
• pdwSize — размер буфера в байтах, на который указывает pIpAddrTable.
• bOrder — если истина, возвращаемая таблица будет отсортирована в порядке возрастания адресов.
С одним и тем же интерфейсом может быть связано более одного IP-адреса. Таблица описывается следующей структурой:
#include <ipmib.h>
typedef struct _MIB_IPADDRTABLE
{
// Количество элементов в таблице.
DWORD dwNumEntries;
// Таблица адресов.
MIB_IPADDRROW table[ANY_SIZE];
} MIB_IPADDRTABLE, *PMIB_IPADDRTABLE;
typedef struct _MIB_IPADDRROW_XP
{
// IPv4 в сетевом порядке байтов.
DWORD dwAddr;
// Индекс связанного с адресом интерфейса.
IF_INDEX dwIndex;
// Маска подсети в сетевом порядке байтов.
DWORD dwMask;
// Широковещательный адрес.
DWORD dwBCastAddr;
// Максимальный размер пересборки полученных дейтаграмм.
DWORD dwReasmSize;
// Не используется.
unsigned short unused1;
// Тип или состояние адреса.
unsigned short wType;
} MIB_IPADDRROW_XP, *PMIB_IPADDRROW_XP;
typedef MIB_IPADDRROW_XP MIB_IPADDRROW;
typedef MIB_IPADDRROW_XP *PMIB_IPADDRROW;
Атрибут wType может принимать следующие значения:
• MIB_IPADDR_PRIMARY — основной IP-адрес.
• MIB_IPADDR_DYNAMIC — динамический IP-адрес, например полученный по DHCP.
• MIB_IPADDR_DISCONNECTED — адрес на отключенном интерфейсе.
• MIB_IPADDR_DELETED — адрес в процессе удаления.
• MIB_IPADDR_TRANSIENT — переходный адрес, например адрес устройств, не имеющих постоянного подключения к сети. Это адрес, который выдается на фиксированное время.
Добавить IP-адреса к интерфейсу можно с помощью функции AddIPAddress():
#include <iphlpapi.h>
DWORD AddIPAddress(IPAddr Address, IPMask IpMask, DWORD IfIndex,
PULONG NTEContext, PULONG NTEInstance);
Параметры функции AddIPAddress():
• Address — IPv4-адрес, добавляемый адаптеру.
• IpMask — маска подсети.
• IfIndex — индекс адаптера, которому необходимо добавить IPv4-адрес.
• NTEContext — при успешном вызове этот параметр указывает на контекст для добавленного IPv4-адреса.
• NTEInstance — при успешном возвращении этот параметр указывает на экземпляр NTE для добавленного IPv4-адреса.
Чтобы удалить IP-адреса, ранее добавленные с помощью AddIPAddress(), используется функция DeleteIPAddress():
#include <iphlpapi.h>
DWORD DeleteIPAddress(ULONG NTEContext);
Она принимает контекст, возвращенный функцией DeleteIPAddress().
С версии Windows Vista для разных типов адресов существует отдельный набор API, которые работают с отдельными типами адресов и соответствующих им структур.
API для «обычных» unicast-адресов:
#include <netioapi.h>
void NETIOAPI_API_ InitializeUnicastIpAddressEntry(
PMIB_UNICASTIPADDRESS_ROW Row
);
NETIO_STATUS CreateUnicastIpAddressEntry(const MIB_UNICASTIPADDRESS_ROW *Row);
NETIO_STATUS GetUnicastIpAddressEntry(PMIB_UNICASTIPADDRESS_ROW Row);
NETIO_STATUS SetUnicastIpAddressEntry(const MIB_UNICASTIPADDRESS_ROW *Row);
NETIO_STATUS DeleteUnicastIpAddressEntry(const MIB_UNICASTIPADDRESS_ROW *Row);
Для anycast-адресов:
#include <netioapi.h>
NETIO_STATUS CreateAnycastIpAddressEntry(const MIB_ANYCASTIPADDRESS_ROW *Row);
NETIO_STATUS GetAnycastIpAddressEntry(PMIB_ANYCASTIPADDRESS_ROW Row);
NETIO_STATUS DeleteAnycastIpAddressEntry(const MIB_ANYCASTIPADDRESS_ROW *Row);
Для multicast-адресов:
#include <netioapi.h>
NETIO_STATUS GetMulticastIpAddressTable(
ADDRESS_FAMILY Family,
PMIB_MULTICASTIPADDRESS_TABLE *Table
);
NETIO_STATUS GetMulticastIpAddressEntry(PMIB_MULTICASTIPADDRESS_ROW Row);
В связи с тем, что объем книги ограничен, мы не будем их рассматривать подробно. Но для примера используем данные функции из Python через модуль ctypes:
import ctypes
# windll будет использован для импорта DLL.
from ctypes import windll
from ctypes.wintypes import DWORD, USHORT
import ipaddress
from socket import htonl
def GetIpAddrTable():
size = DWORD()
# Первый вызов должен вернуть размер.
windll.iphlpapi.GetIpAddrTable(None, ctypes.byref(size), 0)
# Структура, отражающая ряд таблицы.
class MIB_IPADDRROW(ctypes.Structure):
_fields_ = [
('dwAddr', DWORD),
('dwIndex', DWORD),
('dwMask', DWORD),
('dwBCastAddr', DWORD),
('dwReasmSize', DWORD),
('unused1', USHORT),
('wType', USHORT),
]
# Таблица адресов. Количество структур известно.
class MIB_IPADDRTABLE(ctypes.Structure):
_fields_ = [('dwNumEntries', DWORD),
('table', MIB_IPADDRROW * size.value)]
Модуль несложен в использовании: даже нет необходимости загружать библиотеку, потому что она уже предоставляется модулем.
Первый вызов был сделан «вхолостую», чтобы получить необходимый размер буфера. Затем мы сформировали требуемую для вызова структуру, используя типы WinAPI. Причем определенный в предыдущем вызове размер буфера сразу был заложен в класс.
Теперь создадим экземпляр таблицы и вызовем API-функцию повторно:
# Создать объект таблицы.
ip_table = MIB_IPADDRTABLE()
# Вызвать функцию WinAPI.
if (rc := windll.iphlpapi.GetIpAddrTable(ctypes.byref(ip_table),
ctypes.byref(size), 0)) != 0:
raise OSError(f'GetIpAddrTable returned {rc}')
return ip_table
addrs = GetIpAddrTable()
# Записей в таблице может быть больше, чем реальных адресов.
# Поэтому обход делаем только по элементам dwNumEntries.
for i in range(addrs.dwNumEntries):
# Адреса в хостовом порядке байтов. Их нужно преобразовать в сетевой.
print(ipaddress.ip_address(htonl(addrs.table[i].dwAddr)))
Пример выведет IP-адреса Ethernet-адаптера и локальной петли:
D:\network-programming-book-code> py src\book01\ch19\python\ctypes_example.py
192.168.3.254
127.0.0.1
Если функции нет в модуле, то кроме загрузчика, предоставляемого модулем, в ctypes всегда доступны функции LoadLibrary() и GetProcAddress(), которые загружают произвольную DLL обычным для ОС Windows способом.
Также стоит отметить, что в API-вызовах иногда возможно использовать значения констант из модуля socket, так как их стараются не менять, даже если константа не передается C-вызову.
Для работы с адресами, полученными по DHCP, используются следующие функции:
#include <ipexport.h>
#include <iphlpapi.h>
typedef struct _IP_ADAPTER_INDEX_MAP
{
// Индекс сетевого интерфейса.
ULONG Index;
// Имя интерфейса.
WCHAR Name[MAX_ADAPTER_NAME];
} IP_ADAPTER_INDEX_MAP, *PIP_ADAPTER_INDEX_MAP;
// Освободить IP-адрес, ранее полученный от DHCP.
DWORD IpReleaseAddress(PIP_ADAPTER_INDEX_MAP AdapterInfo);
// Обновить аренду адреса, выданного по DHCP.
DWORD IpRenewAddress(PIP_ADAPTER_INDEX_MAP AdapterInfo);
В атрибуте имени можно указать GUID адаптера. Функции работают только с IPv4-адресами, для IPv6-адресов функций нет.
Функция SetIpStatistics() включает или отключает IP-переадресацию и задает значение TTL по умолчанию:
#include <iphlpapi.h>
DWORD SetIpStatistics(PMIB_IPSTATS pIpStats);
ULONG SetIpStatisticsEx(PMIB_IPSTATS Statistics, ULONG Family);
Параметры функций SetIpStatistics() и SetIpStatisticsEx():
• pIpStats или Statistics — указатель на статистику.
• Family — семейство адресов AF_INET или AF_INET6.
Чтобы переключить IP-перенаправление, требуется установить параметр Forwarding:
#include <ipmib.h>
typedef struct _MIB_IPSTATS_LH
{
// Флаг переадресации.
union
{
DWORD dwForwarding;
MIB_IPSTATS_FORWARDING Forwarding;
};
// TTL по умолчанию.
DWORD dwDefaultTTL;
// Далее идет статистика принятых, отправленных, пересланных
// и отброшенных пакетов и фрагментов.
DWORD dwInReceives;
DWORD dwInHdrErrors;
DWORD dwInAddrErrors;
DWORD dwForwDatagrams;
DWORD dwInUnknownProtos;
DWORD dwInDiscards;
DWORD dwInDelivers;
DWORD dwOutRequests;
DWORD dwRoutingDiscards;
DWORD dwOutDiscards;
DWORD dwOutNoRoutes;
DWORD dwReasmTimeout;
DWORD dwReasmReqds;
DWORD dwReasmOks;
DWORD dwReasmFails;
DWORD dwFragOks;
DWORD dwFragFails;
DWORD dwFragCreates;
DWORD dwNumIf;
DWORD dwNumAddr;
DWORD dwNumRoutes;
} MIB_IPSTATS_LH, *PMIB_IPSTATS_LH;
Значение параметра dwForwarding может быть следующим:
• MIB_IP_FORWARDING — IP-пересылка включена.
• MIB_IP_NOT_FORWARDING — IP-пересылка выключена.
• MIB_USE_CURRENT_FORWARDING — использовать текущее значение параметра форвардинга, то есть не менять его.
Для TTL необходимо установить числовое значение либо специальное MIB_USE_CURRENT_TTL, чтобы не изменять TTL.
Для задания TTL существует и отдельная функция:
#include <iphlpapi.h>
DWORD SetIpTTL(UINT nTTL);
IP Helper предоставляет возможность поиска полезной для сетевого администрирования информации. Следующие функции извлекают статистику IP и ICMP.
Функция GetIcmpStatistics() возвращает статистику ICMP:
#include <iphlpapi.h>
ULONG GetIcmpStatistics(PMIB_ICMP Statistics);
Statistics — указатель на структуру MIB_ICMP, которая получает статистику IP-адресов для локального компьютера.
Структура содержит статистику входящих и исходящих пакетов:
typedef struct _MIBICMPINFO
{
// Статистика входящих.
MIBICMPSTATS icmpInStats;
// Статистика исходящих.
MIBICMPSTATS icmpOutStats;
} MIBICMPINFO;
typedef struct _MIB_ICMP
{
MIBICMPINFO stats;
} MIB_ICMP, *PMIB_ICMP;
Данные статистики описываются отдельной структурой:
#include <ipmib.h>
typedef struct _MIBICMPSTATS
{
// Количество сообщений.
DWORD dwMsgs;
// Количество ошибок.
DWORD dwErrors;
// Количество сообщений, не достигших места назначения.
DWORD dwDestUnreachs;
// Количество сообщений о превышении TTL.
DWORD dwTimeExcds;
// Количество сообщений об ошибках в параметрах IP-заголовков.
DWORD dwParmProbs;
// Количество сообщений о подавлении источника, которые отправляются,
// когда отправитель должен уменьшить скорость передачи.
DWORD dwSrcQuenchs;
// Количество сообщений о перенаправлении.
DWORD dwRedirects;
// Количество эхо-запросов.
DWORD dwEchos;
// Количество эхо-ответов.
DWORD dwEchoReps;
// Количество запросов о временных метках.
DWORD dwTimestamps;
// Количество ответов.
DWORD dwTimestampReps;
// Количество запросов масок подсетей.
DWORD dwAddrMasks;
// Количество ответов на запросы масок.
DWORD dwAddrMaskReps;
} MIBICMPSTATS, *PMIBICMPSTATS;
Функция GetIpStatistics() получает более подробную статистику для IP:
ULONG GetIpStatistics(PMIB_IPSTATS Statistics);
ULONG GetIpStatisticsEx(PMIB_IPSTATS Statistics, ULONG Family);
Параметры функций GetIpStatistics() и GetIpStatisticsEx():
• Statistics — указатель на структуру MIB_IPSTATS, описанную ранее.
• Family — семейство адресов AF_INET или AF_INET6.
Хотя ОС Windows накладывает ограничения на работу с raw-сокетами, в IP Helper имеется дополнительный API для обхода этих ограничений, например для работы с ICMP.
Схема работы вызова ICMP-функций не вполне очевидна и показана на рис. 19.1.
Рис. 19.1. Схема вызова ICMP-функций
Чтобы начать с ним работать, требуется создать дескриптор, используя следующие функции:
#include <icmpapi.h>
// Для ICMP.
HANDLE IcmpCreateFile();
// Для ICMP6.
HANDLE Icmp6CreateFile();
Используя возвращенный функциями дескриптор, возможно отправлять и принимать ICMP-пакеты:
DWORD IcmpSendEcho(
HANDLE IcmpHandle,
IPAddr DestinationAddress,
LPVOID RequestData,
WORD RequestSize,
PIP_OPTION_INFORMATION RequestOptions,
LPVOID ReplyBuffer,
DWORD ReplySize,
DWORD Timeout
);
Параметры функций IcmpSendEcho():
• IcmpHandle — дескриптор ICMP, возвращенный функцией IcmpCreateFile().
• DestinationAddress — адрес назначения запроса.
• RequestData — буфер с данными запроса.
• RequestSize — размер буфера запроса.
• RequestOptions — необязательный указатель на параметры заголовка.
• ReplyBuffer — буфер ответа.
• ReplySize — размер буфера ответа.
• Timeout — время ожидания ответа.
Функция вернет количество ответов в буфере или 0 в случае ошибки. Код ошибки можно получить, вызвав функцию GetLastError().
Опции описаны следующей структурой:
#include <ipexport.h>
typedef struct ip_option_information
{
// TTL в заголовке IPv4 или количество промежуточных узлов для IPv6.
UCHAR Ttl;
// TOS для IPv4. Атрибут игнорируется.
UCHAR Tos;
// Поле флагов:
// IP_FLAG_REVERSE — маршрутизация от источника.
// IP_FLAG_DF — не фрагментировать пакет.
UCHAR Flags;
// Размер опций IP.
UCHAR OptionsSize;
// Указатель на опции IP.
PUCHAR OptionsData;
} IP_OPTION_INFORMATION, *PIP_OPTION_INFORMATION;
Функции возвращают ответы как структуры в буфере:
typedef struct icmp_echo_reply
{
// Адрес ответившего узла.
IPAddr Address;
// Код состояния IP. Например, IP_SUCCESS при успехе запроса.
ULONG Status;
// RTT.
ULONG RoundTripTime;
// Количество байтов в ответе.
USHORT DataSize;
USHORT Reserved;
// Данные ответа.
PVOID Data;
// Параметры IP-заголовка.
struct ip_option_information Options;
} ICMP_ECHO_REPLY, *PICMP_ECHO_REPLY;
В атрибуте Status хранится код состояния IP, который возвращается с ICMP-ответом:
• IP_SUCCESS — запрос выполнен успешно.
• IP_BUF_TOO_SMALL — буфер ответов слишком мал.
• IP_DEST_NET_UNREACHABLE — целевая сеть недоступна.
И так далее.
Функция IcmpSendEcho2() позволяет также отправлять ICMP-пакеты, используя при этом процедуры завершения ввода-вывода или события для асинхронной работы:
DWORD IcmpSendEcho2(
HANDLE IcmpHandle,
HANDLE Event,
PIO_APC_ROUTINE ApcRoutine,
PVOID ApcContext,
IPAddr DestinationAddress,
LPVOID RequestData,
WORD RequestSize,
PIP_OPTION_INFORMATION RequestOptions,
LPVOID ReplyBuffer,
DWORD ReplySize,
DWORD Timeout
);
Дополнительные параметры функции:
• Event — событие, которое наступает при получении ответа.
• ApcRoutine — функция, вызываемая, когда получен ICMP-ответ.
• ApcContext — параметр, который передается в функцию обратного вызова.
Существует также функция IcmpSendEcho2Ex(), позволяющая задать адрес источника.
Это полезно, чтобы выбрать, через какой интерфейс будет отправлен запрос, если узел содержит несколько таких интерфейсов.
Если функция вызывается синхронно, она вернет количество ответов в буфере или 0 в случае ошибки. При асинхронном вызове будет возвращено значение ERROR_IO_PENDING.
В буфер также будут записаны структуры ICMP_ECHO_REPLY, но за ними следуют параметры и данные. Поэтому размер буфера должен быть равен для N возможных ответов:
N * sizeof(ICMP_ECHO_REPLY) + RequestSize + 8
Здесь RequestSize — размер запроса, 8 — размер кода ошибки ICMP.
После вызова следует вызвать функцию для постобработки буфера.
Функция для постобработки:
DWORD IcmpParseReplies(LPVOID ReplyBuffer, DWORD ReplySize);
Параметры функции IcmpParseReplies():
• ReplyBuffer — буфер, переданный в IcmpSendEcho2().
• ReplySize — размер буфера.
После вызова этой функции в буфере будут содержаться нормально инициализированные структуры ICMP_ECHO_REPLY.
Для ICMP6 предоставляется функция, аналогичная функции для ICMP:
DWORD Icmp6SendEcho2(
HANDLE IcmpHandle,
HANDLE Event,
PIO_APC_ROUTINE ApcRoutine,
PVOID ApcContext,
sockaddr_in6 *SourceAddress,
sockaddr_in6 *DestinationAddress,
LPVOID RequestData,
WORD RequestSize,
PIP_OPTION_INFORMATION RequestOptions,
LPVOID ReplyBuffer,
DWORD ReplySize,
DWORD Timeout
);
Видно, что для функции IcmpSendEcho2() отличается тип адресов.
Ответ, возвращаемый в массиве, для IPv6 несколько проще:
typedef struct _IPV6_ADDRESS_EX
{
USHORT sin6_port;
ULONG sin6_flowinfo;
USHORT sin6_addr[8];
ULONG sin6_scope_id;
} IPV6_ADDRESS_EX, *PIPV6_ADDRESS_EX;
typedef struct icmpv6_echo_reply_lh
{
// Адрес.
IPV6_ADDRESS_EX Address;
// Код состояния.
ULONG Status;
unsigned int RoundTripTime;
} ICMPV6_ECHO_REPLY_LH, *PICMPV6_ECHO_REPLY_LH;
После вызова функции также необходимо выполнить постобработку буфера, вызвав:
DWORD Icmp6ParseReplies(LPVOID ReplyBuffer, DWORD ReplySize);
Она вернет 1 в случае успеха или 0 в случае ошибки. Если запрос ICMP завершился успешно, атрибут Status будет равен IP_SUCCESS, в противном случае IP_TTL_EXPIRED_TRANSIT.
Внимание! Для функции IcmpSendEcho() вызывать функцию постобработки буфера не требуется.
Когда работа с ICMP завершена, хэндл требуется закрыть, вызвав функцию:
bool IcmpCloseHandle(HANDLE IcmpHandle);
После успешного закрытия хэндла функция вернет истину.
Рассмотрим, как использовать IP Helper API и ICMP API, в частности, на примере утилиты ping. Пример утилиты, работающий через raw-сокеты, также скомпилируется и будет работать на ОС Windows, но ICMP API позволяет выполнять отправку ICMP-пакетов просто и без прав администратора.
Схема работы ping, реализованной далее, показана на рис. 19.2.
Рис. 19.2. Схема работы ping
Поскольку мы используем функцию IcmpSendEcho(), а не IcmpSendEcho2(), вызывать IcmpParseReplies() не требуется. Функция IcmpCloseFile(), показанная на рисунке, будет вызвана неявно.
Включим необходимые заголовочные файлы:
...
extern "C"
{
#include <ipexport.h>
#include <icmpapi.h>
#include <icmpapi.h>
}
Чтобы автоматически закрывать хэндл при выходе из области видимости, инкапсулируем его в класс:
struct IcmpHandle
{
// Создать хэндл через IcmpCreateFile().
IcmpHandle() : handle(IcmpCreateFile())
{
if (INVALID_HANDLE_VALUE == handle)
{
throw std::system_error(GetLastError(), std::system_category(),
"IcmpCreateFile() error!");
}
}
~IcmpHandle()
{
// Закрыть хэндл.
if (!IcmpCloseHandle(handle))
{
std::cerr
<< "IcmpCloseHandle failed with code " << GetLastError()
<< std::endl;
}
}
// Оператор преобразования к типу HANDLE.
operator HANDLE() const noexcept { return handle; }
private:
const HANDLE handle;
};
Определим несколько перечислений, которые содержат различные опции ping:
// Тип функции для расчета доступности узла.
enum class t_ping_func
{
tpf_avg, tpf_max, tpf_min
};
// Опции пинга.
enum ping_options : unsigned char
{
// Проверка доступности.
PING_OPT_AVAIL = 0x01,
// Увеличить уровень подробности вывода.
PING_OPT_VERBOSE = 0x02,
// Не вызывать Sleep() между пингами.
PING_OPT_NOSLEEP = 0x04
};
Зададим несколько значений по умолчанию:
// Задержка после отправки запроса в миллисекундах
constexpr auto PING_DEF_DELAY = 1000;
// Количество пакетов.
constexpr auto PING_DEF_COUNT = 3;
// Размер данных пакета в байтах.
constexpr auto PING_DEF_DATA_SIZE = 32;
// Функция расчета по умолчанию.
constexpr auto PING_DEF_FUNC = t_ping_func::tpf_max;
//
// Ограничения.
//
// Максимальный размер данных.
constexpr auto PING_MAX_DATA_SIZE = 0xffe0;
// Максимальное время пинга (максимальное время повторной передачи
// около 4 минут)
constexpr auto PING_MAX_TIME = 250000;
Определим структуру, которая содержит итоговые настройки:
struct t_ping_data
{
// Узел для ping.
char* host{0};
// Поле флагов, которые инициализируются опциями из ping_options,
// такими как PING_OPT_VERBOSE.
DWORD opts{0};
// Функция для расчета RTT.
t_ping_func ping_func{t_ping_func::tpf_avg};
// Количество отправляемых пакетов.
int count{0};
// Задержка после отправки запроса.
int delay{0};
// Размер буфера данных.
size_t buf_sz{0};
// Шаблон.
char pattern{0};
};
По результатам анализа опций и на основе значений по умолчанию заполним ее параметрами позже.
Далее рассмотрим служебные функции. Первая создает буфер ответа:
PICMP_ECHO_REPLY create_reply_buffer(std::vector<char> &buf,
std::vector<char> &reply_holder)
{
// Создать в памяти структуру ответа.
auto p_reply = new(reply_holder.data()) ICMP_ECHO_REPLY;
// Структура уже обнулена.
p_reply->Data = buf.data();
assert(buf.size() <= (std::numeric_limits<USHORT>::max)());
p_reply->DataSize = static_cast<USHORT>(buf.size());
return p_reply;
}
Следующая функция возвращает время ожидания ответа, основываясь на том, какой тип расчета доступности узла выбран:
int get_end_time(DWORD end_time, const t_ping_data * const wping,
DWORD rtt)
{
// Выбрать тип расчета ожидания.
switch (wping->ping_func)
{
case t_ping_func::tpf_max:
return max(rtt, end_time);
break;
case t_ping_func::tpf_avg:
return end_time + rtt;
break;
case t_ping_func::tpf_min:
return min(rtt, end_time);
break;
default:
break;
}
return 0;
}
Основная работа выполняется функцией ping(). Сначала формируются буферы запроса и ответа:
bool ping(const t_ping_data * const wping)
{
// Опции IP.
IP_OPTION_INFORMATION ip_info{};
// Буфер запроса.
std::vector<char> buf;
assert(wping);
// Сформировать буфер запроса.
if (wping->buf_sz > 0)
{
if (wping->buf_sz > PING_MAX_DATA_SIZE)
{
std::cerr
<< "Error: maximum ping data size = " << PING_MAX_DATA_SIZE
<< ", but you asked size = " << wping->buf_sz << std::endl;
throw std::logic_error("Maximum ping data size exceeded!");
}
buf.resize(wping->buf_sz);
if (wping->opts & PING_OPT_VERBOSE)
std::cout << "Data pattern: 0x" << wping->pattern << std::endl;
// Заполнить буфер шаблоном.
std::fill(buf.begin(), buf.begin() + wping->buf_sz, wping->pattern);
}
// Буфер ответа.
std::vector<char> reply_buf(sizeof(ICMP_ECHO_REPLY) + buf.size());
// Сформировать буфер.
auto echo_reply = create_reply_buffer(buf, reply_buf);
Теперь создадим дескриптор ICMP, который будем использовать для обмена сообщениями:
// Создать хэндл ICMP.
IcmpHandle hndl_icmp;
// Получить адреса клиента.
auto addrs = socket_wrapper::get_client_info(wping->host, 0, SOCK_STREAM,
AF_INET);
addrinfo *ai = addrs.get();
// Требуется IPv4-адрес.
while (ai->ai_family != AF_INET)
{
if (!ai->ai_next) throw std::logic_error(
"Can't resolve IPv4 for the host!");
ai = ai->ai_next;
}
// IPv4-адрес.
sockaddr_in ia_dest;
std::copy(ai->ai_addr, ai->ai_addr + 1,
reinterpret_cast<sockaddr*>(&ia_dest));
if (INADDR_NONE == ia_dest.sin_addr.s_addr ||
ia_dest.sin_family != AF_INET)
{
if (wping->opts & PING_OPT_VERBOSE)
std::cerr << "Can't lookup destination!" << std::endl;
throw std::logic_error("Can't lookup destination!");
}
Сформируем буфер с IP-адресом и заполним структуру опций IP-пакета:
// Буфер с адресом.
std::string ip_buf(INET_ADDRSTRLEN, 0);
bool ret_status = false;
// Время ответа.
int end_time;
// Количество ответов, пришедших на запросы.
DWORD good_packets_cnt = 0;
// Некоторые разумные значения по умолчанию.
ip_info.Ttl = 255;
ip_info.Tos = 0;
ip_info.Flags = 0;
ip_info.OptionsSize = 0;
ip_info.OptionsData = nullptr;
if (wping->opts & PING_OPT_VERBOSE)
{
std::cout
<< "Count = " << wping->count
<< "\nSize = " << wping->buf_sz + sizeof(ICMP_ECHO_REPLY)
<< "\nDelay = " << wping->delay
<< "\nSending to \""
<< inet_ntop(AF_INET, &ia_dest.sin_addr, ip_buf.data(),
ip_buf.size())
<< "\""
<< std::endl;
}
// Формирование предельного времени для проверки доступности.
end_time = (t_ping_func::tpf_min == wping->ping_func) ? PING_MAX_TIME : 0;
Наконец, запустим цикл отправки запросов и получения ответов:
for (int i = 0; i < wping->count; ++i)
{
echo_reply->Status = IP_SUCCESS;
// Отправить эхо-запрос ICMP и получить ответ:
DWORD rep_cnt = IcmpSendEcho(
hndl_icmp,
reinterpret_cast<const sockaddr_in*>(&ia_dest)->sin_addr.s_addr,
buf.data(), static_cast<WORD>(buf.size()),
&ip_info, echo_reply,
static_cast<DWORD>(sizeof(ICMP_ECHO_REPLY) + buf.size()),
wping->delay
);
// Ожидать после отправки.
if (!(wping->opts & PING_OPT_NOSLEEP)) Sleep(wping->delay);
В случае успешного вызова функции будет выполнен цикл по ответам:
if (rep_cnt > 0 && (IP_SUCCESS == echo_reply->Status))
{
// Для каждого ответа.
while (rep_cnt--)
{
// Получить время, в течение которого узел будет считаться
// доступным.
end_time = get_end_time(end_time, wping,
echo_reply->RoundTripTime);
if (wping->opts & PING_OPT_VERBOSE)
{
std::cout
<< "Received from: "
<< inet_ntop(AF_INET, &echo_reply->Address,
ip_buf.data(), ip_buf.size())
<< "\nStatus: " << echo_reply->Status
<< "\nRoundtrip time: "
<< echo_reply->RoundTripTime << "ms"
<< "\nTTL: "
<< static_cast<int>(echo_reply->Options.Ttl)
<< "\nBytes: " << echo_reply->DataSize
<< std::endl;
}
// Количество пакетов, на которые пришли ответы.
++good_packets_cnt;
}
}
Если узел не ответил, увеличим время ожидания между запросом и ответом:
else
{
// Узел не ответил.
if (wping->opts & PING_OPT_VERBOSE)
{
std::cout
<< "Host " << wping->host
<< " (resolved:\""
<< inet_ntop(AF_INET, &ia_dest.sin_addr,
ip_buf.data(), ip_buf.size())
<< "\") doesn't respond!"
<< std::endl;
}
// Скорректировать время ожидания.
end_time += get_end_time(end_time, wping, wping->delay);
}
}
ret_status = good_packets_cnt != 0;
Время используется для проверки на доступность. В конце функции хэндл для работы с ICMP требуется закрыть:
if (!(wping->opts & PING_OPT_AVAIL))
{
if (ret_status)
{
// Проверка узла на доступность.
std::cout
<< ((t_ping_func::tpf_avg != wping->ping_func) ? end_time :
static_cast<float>(end_time) / (good_packets_cnt + 1))
<< std::endl;
}
else std::cout << "-1" << std::endl;
}
// Хэндл ICMP закроется при выходе класса из области видимости.
return ret_status;
}
Настройка структуры и вызов функции ping() производятся в функции main():
int main(int argc, char **argv)
{
if (argc != 2)
{
std::cout << "Usage: " << argv[0] << " <hostname>" << std::endl;
return EXIT_FAILURE;
}
socket_wrapper::SocketWrapper sw;
struct t_ping_data wping;
// Настройка пинга, которая может быть выполнена через
// аргументы командной строки.
wping.opts = PING_OPT_VERBOSE; // PING_OPT_NOSLEEP, PING_OPT_AVAIL
wping.count = PING_DEF_COUNT;
wping.buf_sz = PING_DEF_DATA_SIZE;
wping.delay = PING_DEF_DELAY;
wping.ping_func = PING_DEF_FUNC;
wping.host = argv[1];
wping.pattern = 'a';
// Запуск пинга либо в режиме проверки доступности, либо в обычном режиме.
if (wping.opts & PING_OPT_AVAIL)
std::cout << (ping(&wping) ? 1 : 0) << std::endl;
else ping(&wping);
return EXIT_SUCCESS;
}
Запустим приложение. Вывод будет похож на следующий:
D:\build\bin> b01-ch19-windows-ping.exe localhost
Data pattern: 0xa
Count = 3
Size = 72
Delay = 1000
Sending to "127.0.0.1"
Received from: 127.0.0.1
Status: 0
Roundtrip time: 0ms
TTL: 128
Bytes: 32
Received from: 127.0.0.1
Status: 0
Roundtrip time: 0ms
TTL: 128
Bytes: 32
Received from: 127.0.0.1
Status: 0
Roundtrip time: 0ms
TTL: 128
Bytes: 32
0
Видим, что ping выводит состояние, RTT, TTL и количество байтов данных.
Для работы с ARP в IP Helper также поддерживается набор функций.
Как было описано в главе 10, ARP-таблица содержит сопоставление IP-адресов с физическими адресами:
#include <ipmib.h>
// Элемент таблицы.
typedef struct _MIB_IPNETROW
{
// Индекс сетевого адаптера.
DWORD dwIndex;
// Длина физического адреса в байтах.
DWORD dwPhysAddrLen;
// Физический адрес.
BYTE bPhysAddr[8];
// IPv4-адрес.
DWORD dwAddr;
// Тип ARP-записи.
union
{
DWORD dwType;
MIB_IPNET_TYPE Type;
};
} MIB_IPNETROW, *PMIB_IPNETROW;
// Структура, описывающая ARP-таблицу.
typedef struct _MIB_IPNETTABLE
{
// Количество элементов в таблице.
DWORD dwNumEntries;
// Таблица элементов.
MIB_IPNETROW table[ANY_SIZE];
} MIB_IPNETTABLE, *PMIB_IPNETTABLE;
Тип ARP-записи:
• 0x00000001 или MIB_IPNET_TYPE_OTHER — другой узел.
• 0x00000002 или MIB_IPNET_TYPE_INVALID — некорректная ARP-запись. Недоступная или неполная.
• 0x00000003 или MIB_IPNET_TYPE_DYNAMIC — динамическая ARP-запись.
• 0x00000004 или MIB_IPNET_TYPE_STATIC — статическая ARP-запись.
Функция для получения ARP-таблицы:
#include <iphlpapi.h>
ULONG GetIpNetTable(PMIB_IPNETTABLE IpNetTable, PULONG SizePointer,
bool Order);
Параметры функции GetIpNetTable():
• IpNetTable — указатель на буфер, который получает ARP-таблицу.
• SizePointer — количество байтов в буфере, на который указывает IpNetTable. Если буфер меньше возвращаемого размера структуры, функция установит этот параметр в требуемое значение размера и вернет код ошибки ERROR_INSUFFICIENT_BUFFER.
• Order — если истина, возвращаемая таблица будет отсортирована в порядке возрастания IP-адресов.
// Создать новую ARP-запись.
DWORD CreateIpNetEntry(PMIB_IPNETROW pArpEntry);
// Изменить существующую ARP-запись.
DWORD SetIpNetEntry(PMIB_IPNETROW pArpEntry);
// Удалить ARP-запись.
DWORD DeleteIpNetEntry(PMIB_IPNETROW pArpEntry);
// Удалить все записи в ARP-таблице для интерфейса с указанным индексом.
DWORD FlushIpNetTable(DWORD dwIfIndex);
В pArpEntry для изменения либо добавления записи таблицы необходимо задать все атрибуты, для удаления — хотя бы поля dwIndex и dwAddr.
Очищать ARP-таблицу, создавать и менять в ней записи может только администратор.
Чтобы объединить две сети в одну, можно использовать технику Proxy ARP. Она заключается в том, что на ARP-запрос IP-адреса узла из другой подсети отвечает маршрутизатор. Он подставляет свой MAC-адрес и работает как прокси.
Для управления записями Proxy ARP существуют функции, позволяющие добавлять и удалять записи:
DWORD CreateProxyArpEntry(DWORD dwAddress, DWORD dwMask, DWORD dwIfIndex);
DWORD DeleteProxyArpEntry(DWORD dwAddress, DWORD dwMask, DWORD dwIfIndex);
Параметры функций CreateProxyArpEntry() и DeleteProxyArpEntry():
• dwAddress — IPv4-адрес, для которого локальный узел выступает прокси-сервером.
• dwMask — маска подсети для IPv4-адреса, указанного в dwAddress.
• dwIfIndex — индекс интерфейса.
Используя функцию SendARP(), можно отправить запрос ARP в локальную сеть:
DWORD SendARP(IPAddr DestIP, IPAddr SrcIP, PVOID pMacAddr, PULONG PhyAddrLen);
Параметры функции SendARP():
• DestIP — IPv4-адрес назначения в виде структуры IPAddr.
• SrcIP — исходный IPv4-адрес в виде структуры IPAddr.
• pMacAddr — указатель на буфер возвращаемого значения, в который будет записан физический адрес для DestIP.
• PhyAddrLen — указатель на размер выходного буфера в байтах. Функция запишет по этому адресу количество байтов, реально записанных в буфер.
В случае успеха все функции возвращают код NO_ERROR.
Работа с таблицей маршрутизации выполняется не через вызовы ioctl, как в Unix-подобных системах, а через функции IP Helper.
Для работы с маршрутами предоставляются такие функции, как CreateForwardEntry() и CreateForwardEntry2() для создания маршрутов в таблице маршрутизации. Существуют и аналогичные функции для получения, изменения, удаления записей маршрутов.
Включить маршрутизацию позволяет функция EnableRouter(), выключить — функция UnenableRouter().
Функция GetBestRoute() позволяет найти оптимальный маршрут до указанного адреса IP, а функция GetRTTAndHopCount() определяет RTT до адреса.
Отдельное подмножество API-функций CreateIpNetEntry2(), GetIpNetEntry2(), SetIpNetEntry2(), GetIpNetTable2(), DeleteIpNetEntry2(), FlushIpNetTable2() и прочие из группы IpNet-функций служат для управления адресами соседей.
Используя построенную таблицу, можно получить физические адреса по IP-адресам, вызвав функцию ResolveIpNetEntry2() или более старую ResolveNeighbor().
Функция NotifyAddrChange() используется для запроса уведомлений о любых изменениях в сопоставлении IP-адресов и интерфейсов на узле.
Уведомления о любых изменениях таблицы маршрутизации позволяет запросить функция NotifyRouteChange():
#include <iphlpapi.h>
// Уведомлять при изменении адресов.
DWORD NotifyAddrChange(PHANDLE Handle, LPOVERLAPPED overlapped);
// Уведомлять при каждом изменении маршрутов.
DWORD NotifyRouteChange(PHANDLE Handle, LPOVERLAPPED overlapped);
Параметры функций NotifyAddrChange() и NotifyRouteChange():
• Handle — указатель на переменную, которая получает дескриптор для использования в асинхронном уведомлении.
• overlapped — указатель на структуру OVERLAPPED для уведомления вызывающего объекта.
Уведомления просто сигнализируют об изменениях. Чтобы понять, что изменилось, используются другие вспомогательные функции, такие как GetIPAddrTable() и GetBestRoute().
Отменить получение уведомлений можно следующей функцией:
#include <iphlpapi.h>
bool CancelIPChangeNotify(LPOVERLAPPED notifyOverlapped);
В notifyOverlapped должен быть передан указатель на ту же структуру, что использовалась в предыдущих функциях для включения уведомлений.
Реализуем пример, который будет ожидать уведомлений об изменении таблицы маршрутизации и затем выводить лучший маршрут до заданного сервера:
int main()
{
// Создать событие, которое наступит при приходе уведомления.
OVERLAPPED overlap = {.hEvent = WSACreateEvent()};
if (WSA_INVALID_EVENT == overlap.hEvent)
{
std::cerr
<< "WSACreateEvent error: " << WSAGetLastError() << std::endl;
return EXIT_FAILURE;
}
HANDLE hand = nullptr;
// Включить уведомления.
if (NO_ERROR != NotifyRouteChange(&hand, &overlap))
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
std::cerr
<< "NotifyRouteChange error: "
<< WSAGetLastError() << std::endl;
return EXIT_FAILURE;
}
}
Сначала мы создали новое событие и записали его дескриптор в структуру overlap. Затем включили уведомления, использовав эту структуру. Теперь, когда таблица маршрутизации изменится, событие будет активировано.
Следующий шаг — нахождение IP-адреса интерфейса, от которого должен быть построен маршрут. Сделать это можно несколькими способами. Например, получив адаптеры вызовом функции GetAdaptersAddresses(), как мы уже делали ранее. Но в данном примере мы для этого вызовем функцию GetIpAddrTable():
ULONG mib_size = 0;
// Первый вызов приведет к ошибке, но функция установит требуемый
// размер буфера.
if (const auto result = GetIpAddrTable(nullptr, &mib_size, false);
ERROR_INSUFFICIENT_BUFFER != result)
{
// Завершиться, если это другая ошибка, не связанная с буфером.
std::cerr
<< "GetIpAddrTable() failed with code " << result << std::endl;
return EXIT_FAILURE;
}
// Создать буфер заданного размера.
std::vector<MIB_IPADDRTABLE> mib_table(
mib_size / sizeof(MIB_IPADDRTABLE) + 1);
// Повторно вызвать функцию для получения адресов.
if (const auto result = GetIpAddrTable(mib_table.data(), &mib_size,
false);
NO_ERROR != result)
{
std::cerr
<< "GetIpAddrTable() failed with code " << result << std::endl;
return EXIT_FAILURE;
}
std::string ip_addr(INET_ADDRSTRLEN, 0);
uint32_t my_ip_addr = 0;
// Выполнить итерацию по таблице адресов.
for (size_t i = 0; i < mib_table[0].dwNumEntries; ++i)
{
auto row = mib_table[0].table[i];
// Если это интерфейс локальной петли, ищем дальше.
if (INET_IS_ADDR_LOOPBACK(AF_INET, &row.dwAddr)) continue;
// Адрес найден, его необходимо сохранить и выйти из цикла.
my_ip_addr = row.dwAddr;
std::cout
<< "My IP = "
<< inet_ntop(AF_INET, &row.dwAddr, ip_addr.data(), ip_addr.size())
<< std::endl;
break;
}
При вызове этой функции, если размер буфера недостаточен, будет возвращен код ошибки ERROR_INSUFFICIENT_BUFFER. Если это так, увеличиваем размер буфера и делаем вызов повторно.
Когда адреса получены, следует пропустить все неподходящие интерфейсы, а нужный адрес сохранить.
В коде примера выше проводится минимальная проверка, так как в данном тестовом примере известно, что у машины, на которой он запускается, всего два интерфейса: локальная петля и Ethernet. Проверки в реальном приложении должны быть строже и обширнее.
Теперь вызовем функцию GetBestRoute() для получения маршрута к заданному серверу и будем ожидать события изменения таблицы маршрутизации:
// Адрес известного DNS-сервера Google.
uint32_t google_dns_addr = 0;
inet_pton(AF_INET, "8.8.8.8", &google_dns_addr);
MIB_IPFORWARDROW fr_row;
// Получить лучший маршрут от нашего интерфейса до сервера.
if (!NO_ERROR == GetBestRoute(google_dns_addr, my_ip_addr, &fr_row))
{
std::cerr << "Could not get best route" << std::endl;
return EXIT_FAILURE;
}
// Выведем номер интерфейса, а также следующий промежуточный узел.
std::cout
<< "1. Iface index: " << fr_row.dwForwardIfIndex
<< " ["
<< inet_ntop(AF_INET, &fr_row.dwForwardNextHop, ip_addr.data(),
ip_addr.size())
<< "]"
<< std::endl;
// Ожидать наступления события изменения таблицы маршрутизации.
if (WAIT_OBJECT_0 == WaitForSingleObject(overlap.hEvent, INFINITE))
std::cout << "Routing table changed." << std::endl;
После того как событие наступило, отменим получение нотификаций и вызовем функцию получения лучшего маршрута повторно, чтобы увидеть изменения:
// Отменить существующее ожидание.
CancelIPChangeNotify(&overlap);
// Получить лучший маршрут от нас до сервера.
if (NO_ERROR != GetBestRoute(google_dns_addr, my_ip_addr, &fr_row))
{
std::cerr << "Could not get best route" << std::endl;
return EXIT_FAILURE;
}
std::cout
<< "2. Iface index: " << fr_row.dwForwardIfIndex << " ["
<< inet_ntop(AF_INET, &fr_row.dwForwardNextHop, ip_addr.data(),
ip_addr.size())
<< "]" << std::endl;
return EXIT_SUCCESS;
}
Теперь запустим пример и выполним действия, приводящие к изменению таблицы. Например, очистим ее следующей командой:
D:\build\bin> route -f
Запущенный пример будет ожидать наступления события и после выполнения очистки таблицы завершится:
D:\build\bin> b01-ch19-route-notification.exe
My IP = 192.168.3.254
1. Iface index: 6 [192.168.3.1]
Routing table changed.
Второй маршрут не был построен, так как после очистки в таблице маршруты отсутствуют. Теперь запустим пример второй раз и восстановим таблицу маршрутизации. Простейший способ это сделать — вызвать переподключение, которое произойдет, если выдернуть из адаптера сетевой кабель и затем воткнуть его обратно. Либо выключить адаптер программно и включить снова.
В примере мы видим, что в первый раз маршрут не был построен, поскольку таблица была пуста:
D:\build\bin> b01-ch19-route-notification.exe
My IP = 192.168.3.254
Routing table changed.
2. Iface index: 6 [192.168.3.1]
Но после ее изменения маршруты добавились, событие наступило и мы смогли получить маршрут к узлу.
В данном случае следующий узел маршрута всегда один и тот же, потому что сетевой адаптер подключен к сети через маршрутизатор, IP-адрес которого — 192.168.3.1.
Функция NotifyIpInterfaceChange() требуется для получения уведомлений об изменениях IP-интерфейсов:
#include <netioapi.h>
NETIO_STATUS NotifyIpInterfaceChange(
ADDRESS_FAMILY Family,
PIPINTERFACE_CHANGE_CALLBACK Callback,
PVOID CallerContext,
bool InitialNotification,
HANDLE *NotificationHandle
);
Параметры функции NotifyIpInterfaceChange():
• Family — AF_UNSPEC, AF_INET или AF_INET6.
• Callback — указатель на функцию, вызываемую при изменении интерфейса.
• CallerContext — значение, которое может задать пользователь для передачи в функцию обратного вызова.
• InitialNotification — истина, если должно быть отправлено уведомление при инициализации.
• NotificationHandle — указатель на хэндл, который будет использован для уведомления.
Помимо данной функции существуют те, которые включают уведомления от Teredo, но мы их не рассматриваем.
Чтобы отменить уведомления, которые были запрошены этой функцией, а также другими из netioapi.h, необходимо вызвать следующую функцию:
#include <netioapi.h>
NETIO_STATUS CancelMibChangeNotify2(HANDLE NotificationHandle);
IP Helper позволяет получить доступ к информации о транспортных протоколах на локальном узле.
Следующие функции возвращают таблицу соединений TCP:
#include <iphlpapi.h>
// Получить таблицу TCP-соединений поверх IPv4.
ULONG GetTcpTable(PMIB_TCPTABLE TcpTable, PULONG SizePointer, bool Order);
ULONG GetTcpTable2(PMIB_TCPTABLE2 TcpTable, PULONG SizePointer, bool Order);
// Получить таблицу TCP-соединений поверх IPv6.
ULONG GetTcp6Table(PMIB_TCP6TABLE TcpTable, PULONG SizePointer, bool Order);
ULONG GetTcp6Table2(PMIB_TCP6TABLE2 TcpTable, PULONG SizePointer, bool Order);
Параметры функций GetTcpTable(), GetTcpTable2(), GetTcp6Table(), GetTcp6Table2():
• TcpTable — указатель на буфер, в который будет записана таблица TCP-подключений.
• SizePointer — количество байтов во входном буфере. Если размер буфера недостаточен, сюда будет записан требуемый размер и возвращен код ERROR_INSUFFICIENT_BUFFER.
• Order — если истина, подключения будут отсортированы в порядке «локальный IP-адрес, порт, удаленный адрес, порт».
Таблица представляет собой массив структур, который содержит адреса, порты и состояние подключения:
#include <tcpmib.h>
// Ряд таблицы.
typedef struct _MIB_TCPROW_LH
{
// Состояние подключения.
union
{
DWORD dwState;
MIB_TCP_STATE State;
};
// Локальный IPv4-адрес. Может быть нулевым, если сокет прослушивающий.
DWORD dwLocalAddr;
// Локальный порт. Используются младшие 16 бит слова.
DWORD dwLocalPort;
// Адрес удаленного абонента. Если сокет прослушивающий, не используется.
DWORD dwRemoteAddr;
// Порт удаленного абонента. К нему применимы ограничения выше.
DWORD dwRemotePort;
} MIB_TCPROW, MIB_TCPROW_LH, *PMIB_TCPROW_LH;
typedef struct _MIB_TCPTABLE
{
// Количество элементов таблицы.
DWORD dwNumEntries;
// Ряды таблицы.
MIB_TCPROW table[ANY_SIZE];
} MIB_TCPTABLE, *PMIB_TCPTABLE;
Поле State, которое отражает состояние TCP-подключения, может принимать следующие значения:
• MIB_TCP_STATE_CLOSED — TCP-подключение находится в состоянии CLOSED. В действительности записей с таким состоянием в таблице нет. Их просто не существует.
• MIB_TCP_STATE_LISTEN — состояние прослушивания ожидает запрос на подключение.
• MIB_TCP_STATE_SYN_SENT — состояние SYN-SENT. Ожидается подключение после отправки SYN.
• MIB_TCP_STATE_SYN_RCVD — состояние SYN-RECEIVED, ожидается подтверждение запроса на подключение после отправки SYN.
• MIB_TCP_STATE_ESTAB — TCP-подключение установлено. Данные могут быть доставлены пользователю.
• MIB_TCP_STATE_FIN_WAIT1 — состояние FIN-WAIT-1. Ожидается запрос на завершение подключения от удаленного абонента или подтверждение ранее отправленного запроса на отключение.
• MIB_TCP_STATE_FIN_WAIT2 — состояние FIN-WAIT-2. Ожидается запрос на завершение подключения от удаленного абонента.
• MIB_TCP_STATE_CLOSE_WAIT — состояние CLOSE-WAIT. Ожидается запрос на завершение подключения от пользователя.
• MIB_TCP_STATE_CLOSING — состояние CLOSING. Ожидается подтверждение запроса на завершение подключения от удаленного абонента.
• MIB_TCP_STATE_LAST_ACK — состояние LAST-ACK. Ожидается подтверждение запроса на завершение подключения, ранее отправленного удаленному абоненту.
• MIB_TCP_STATE_TIME_WAIT — состояние TIME-WAIT. Ожидать время таймера, чтобы убедиться, что удаленный абонент получил подтверждение запроса на завершение подключения.
• MIB_TCP_STATE_DELETE_TCB — состояние удаления TCB, блока управления передачей.
В строки таблицы PMIB_TCPTABLE2 добавляются еще два поля: PID владеющего соединением процесса и состояние разгрузки на сетевой адаптер.
Эта таблица выглядит так:
typedef struct _MIB_TCPROW2
{
// Поля те же, что и в структуре MIB_TCP_TABLE.
DWORD dwState;
// Адреса и порты.
DWORD dwLocalAddr;
DWORD dwLocalPort;
DWORD dwRemoteAddr;
DWORD dwRemotePort;
// Идентификатор процесса, к которому привязан контекст соединения.
DWORD dwOwningPid;
// Состояние разгрузки соединения.
TCP_CONNECTION_OFFLOAD_STATE dwOffloadState;
} MIB_TCPROW2, *PMIB_TCPROW2;
Значения состояния разгрузки dwOffloadState:
• TcpConnectionOffloadStateInHost — подключение не разгружено и обрабатывается сетевым стеком на локальном узле.
• TcpConnectionOffloadStateOffloading — подключение в процессе разгрузки, которая пока не завершена.
• TcpConnectionOffloadStateOffloaded — подключение разгружено и обрабатывается контроллером сетевого интерфейса.
• TcpConnectionOffloadStateUploading — TCP-подключение передается обратно в сетевой стек узла, но процесс восстановления подключения к узлу не завершен.
• TcpConnectionOffloadStateMax — недопустимое максимально возможное значение для типа перечисления.
Таблицы для IPv6-версий функций такие же, как и для IPv4, но типы адресов не DWORD, а IN6_ADDR.
Получать выборочные данные о подключениях можно через следующую функцию:
DWORD GetExtendedTcpTable(
PVOID pTcpTable, PDWORD pdwSize, bool bOrder, ULONG ulAf,
TCP_TABLE_CLASS TableClass, ULONG Reserved
);
Параметры функции GetExtendedTcpTable():
• pTcpTable — указатель на буфер для таблицы.
• pdwSize — размер буфера. Сюда же будет записан требуемый размер, если переданного недостаточно.
• bOrder — флаг включения упорядочивания записей в таблице.
• ulAf — запрашиваемое семейство адресов: AF_INET или AF_INET6.
• TableClass — тип извлекаемых записей:
• TCP_TABLE_BASIC_LISTENER — таблица MIB_TCPTABLE, содержащая прослушивающие сокеты.
• TCP_TABLE_BASIC_CONNECTIONS — таблица MIB_TCPTABLE, содержащая все подключенные TCP-соединения.
• TCP_TABLE_BASIC_ALL — таблица MIB_TCPTABLE, содержащая все конечные точки TCP на локальном компьютере.
• TCP_TABLE_OWNER_PID_LISTENER — MIB_TCPTABLE_OWNER_PID или MIB_TCP6TABLE_OWNER_PID, содержащая все прослушивающие TCP-сокеты.
• TCP_TABLE_OWNER_PID_CONNECTIONS — MIB_TCPTABLE_OWNER_PID или MIB_TCP6TABLE_OWNER_PID, содержащая все подключенные TCP-сокеты.
• TCP_TABLE_OWNER_PID_ALL — MIB_TCPTABLE_OWNER_PID или MIB_TCP6TABLE_OWNER_PID, содержащая все TCP-сокеты.
• TCP_TABLE_OWNER_MODULE_LISTENER — структура MIB_TCPTABLE_OWNER_MODULE или MIB_TCP6TABLE_OWNER_MODULE, содержащая все прослушивающие сокеты.
• TCP_TABLE_OWNER_MODULE_CONNECTIONS — структура MIB_TCPTABLE_OWNER_MODULE или MIB_TCP6TABLE_OWNER_MODULE, содержащая все подключенные TCP-сокеты.
• TCP_TABLE_OWNER_MODULE_ALL — структура MIB_TCPTABLE_OWNER_MODULE или MIB_TCP6TABLE_OWNER_MODULE, содержащая все TCP-сокеты.
• Reserved — зарезервировано и должно быть равно 0.
Доступные запрашиваемые записи зависят от семейства адресов. Подробнее см. в .
Для получения информации о статистике TCP-подключений используются следующие функции:
ULONG GetTcpStatistics(PMIB_TCPSTATS Statistics);
ULONG GetTcpStatisticsEx(PMIB_TCPSTATS Statistics, ULONG Family);
ULONG GetTcpStatisticsEx2(PMIB_TCPSTATS2 Statistics, ULONG Family);
Параметры функций GetTcpStatistics(), GetTcpStatisticsEx(), GetTcpStatisticsEx2():
• Family — запрашиваемое семейство протоколов AF_INET либо AF_INET6.
• Statistics — указатель на структуру MIB_TCPSTATS, в которую будет записана статистика TCP.
Они возвращают следующую структуру:
#include <iphlpapi.h>
typedef struct _MIB_TCPSTATS_LH
{
// Алгоритм ожидания повторной передачи — RTO.
union
{
DWORD dwRtoAlgorithm;
TCP_RTO_ALGORITHM RtoAlgorithm;
};
// Минимальное RTO в миллисекундах.
DWORD dwRtoMin;
// Максимальное RTO в миллисекундах.
DWORD dwRtoMax;
// Максимальное количество подключений. Если -1 — число переменное.
DWORD dwMaxConn;
// Количество активных соединений клиент -> сервер.
DWORD dwActiveOpens;
// Количество подключений к серверному сокету, на котором вызван listen().
DWORD dwPassiveOpens;
// Количество неудачных подключений.
DWORD dwAttemptFails;
// Количество установленных подключений, которые были сброшены.
DWORD dwEstabResets;
// Количество установленных на данный момент подключений.
DWORD dwCurrEstab;
// Количество полученных сегментов.
DWORD dwInSegs;
// Количество отправленных сегментов.
DWORD dwOutSegs;
// Количество перенаправленных сегментов.
DWORD dwRetransSegs;
// Количество ошибок приема.
DWORD dwInErrs;
// Количество сегментов, переданных с флагом RST.
DWORD dwOutRsts;
// Количество соединений, которые присутствуют в системе.
DWORD dwNumConns;
} MIB_TCPSTATS_LH, *PMIB_TCPSTATS_LH;
RtoAlgorithm может быть следующим:
• MIB_TCP_RTO_OTHER — прочее.
• MIB_TCP_RTO_CONSTANT — константное время ожидания.
• MIB_TCP_RTO_RSRE — MIL-STD-1778, см. приложение B.
• MIB_TCP_RTO_VANJ — алгоритм Ван Якобсона.
Управление потоком защищает получателя данных от перегрузки. Но он не защищает сеть, так как абоненты не знают, какой объем канала доступен в момент начала соединения.
Чтобы скорость передачи изменялась соответственно степени загруженности канала, Ван Якобсон и Майкл Дж. Карелс предложили несколько алгоритмов:
• Медленный старт. TCP начинает работать с низкой скоростью. Потом по возможности «разгоняется».
• Предотвращение перегрузки. Если в сети теряются пакеты, это говорит о перегрузке. Скорость понижается.
• Быстрая повторная передача и быстрое восстановление.
Эти алгоритмы сложнее, чем здесь описано, и для их понимания потребуется углубиться в тему.
В структуре PMIB_TCPSTATS2 тип полей числа входящих и исходящих сегментов — DWORD64. А называются эти поля в структуре PMIB_TCPSTATS2 — dw64InSegs и dw64OutSegs.
Функция SetTcpEntry() позволяет разработчику установить состояние указанного TCP-соединения в MIB_TCP_STATE_DELETE_TCB:
#include <iphlpapi.h>
DWORD SetTcpEntry(PMIB_TCPROW pTcpRow);
Параметр pTcpRow указывает на структуру MIB_TCPROW с новой информацией об идентификации TCP-подключения. Также указывается новое состояние для TCP-подключения.
Функция GetPerTcp6ConnectionEStats() возвращает расширенную статистику для TCP-соединения IPv6, а функция SetPerTcp6ConnectionEStats() устанавливает флаги получения статистик чтения и записи для TCP-соединения IPv6.
Эта функция используется для включения или отключения расширенной статистики для TCP-соединения IPv6.
#include <iphlpapi.h>
ULONG GetPerTcp6ConnectionEStats(
PMIB_TCP6ROW Row, TCP_ESTATS_TYPE EstatsType, PUCHAR Rw, ULONG RwVersion,
ULONG RwSize, PUCHAR Ros, ULONG RosVersion, ULONG RosSize, PUCHAR Rod,
ULONG RodVersion, ULONGRodSize
);
ULONG SetPerTcp6ConnectionEStats(
PMIB_TCP6ROW Row, TCP_ESTATS_TYPE EstatsType, PUCHAR Rw, ULONG RwVersion,
ULONG RwSize, ULONG Offset
);
Параметры функций GetPerTcp6ConnectionEStats() и SetPerTcp6ConnectionEStats():
• Row — указатель на структуру MIB_TCP6ROW.
• EstatsType — тип запрашиваемой статистики. Например, TcpConnectionEstatsBandwidth — информация о пропускной способности, которая будет записана в параметр Rw. Таких параметров много, все они .
• Rw — буфер для получения информации о чтении и записи.
• Ros — буфер для получения статической информации, доступной только для чтения.
• Rod — буфер для получения динамической информации, доступной только для чтения.
• Offset — смещение в байтах элемента структуры, на который указывает устанавливаемый параметр Rw. Параметр не используется.
Параметры функций *Version указывают версию запрашиваемых данных, а параметры Size — размеры буферов.
Структура MIB_TCP6ROW содержит адреса и состояние подключения:
typedef struct _MIB_TCP6ROW
{
// Состояние TCP-соединения: MIB_TCP_STATE_CLOSED, MIB_TCP_STATE_LISTEN,
// MIB_TCP_STATE_ESTAB и т.д.
MIB_TCP_STATE State;
// Локальный IPv6-адрес, порт, идентификатор области.
IN6_ADDR LocalAddr;
DWORD dwLocalScopeId;
DWORD dwLocalPort;
// Удаленный IPv6-адрес, порт, идентификатор области.
IN6_ADDR RemoteAddr;
DWORD dwRemoteScopeId;
DWORD dwRemotePort;
} MIB_TCP6ROW, *PMIB_TCP6ROW;
Также IP Helper предоставляет функции для получения данных о модуле, к которому привязан контекст сокета:
DWORD GetOwnerModuleFromTcpEntry(
PMIB_TCPROW_OWNER_MODULE pTcpEntry, TCPIP_OWNER_MODULE_INFO_CLASS Class,
PVOID pBuffer, PDWORD pdwSize
);
DWORD GetOwnerModuleFromTcp6Entry(
PMIB_TCP6ROW_OWNER_MODULE pTcpEntry,
TCPIP_OWNER_MODULE_INFO_CLASS Class, PVOID pBuffer, PDWORD pdwSize
);
Здесь мы эти функции описывать не будем, так как они используются достаточно редко.
Для получения статистики о UDP существует аналогичный набор функций.
Функции типа GetUdpTable() возвращают таблицу UDP:
ULONG GetUdpTable(PMIB_UDPTABLE UdpTable, PULONG SizePointer, bool Order);
ULONG GetUdp6Table(PMIB_UDP6TABLE Udp6Table, PULONG SizePointer, bool Order);
Параметры функций GetUdpTable() и GetUdp6Table():
• UdpTable — указатель на буфер, в который будет записана UDP-таблица.
• SizePointer — размер буфера в байтах. Если размера буфера недостаточно, функция установит параметр равным требуемому количеству байтов в буфере.
• Order — если этот параметр имеет значение «истина», возвращаемая таблица будет отсортирована в порядке возрастания адреса и порта источника.
Структуры для IPv4:
#include <iphlpapi.h>
typedef struct _MIB_UDPROW
{
// IP-адрес конечной точки UDP на локальном узле.
DWORD dwLocalAddr;
// Порт конечной точки UDP на локальном узле.
DWORD dwLocalPort;
} MIB_UDPROW, *PMIB_UDPROW;
typedef struct _MIB_UDPTABLE
{
DWORD dwNumEntries;
MIB_UDPROW table[ANY_SIZE];
} MIB_UDPTABLE, *PMIB_UDPTABLE;
И для IPv6:
typedef struct _MIB_UDP6ROW
{
// IP-адрес конечной точки UDP на локальном узле.
IN6_ADDR dwLocalAddr;
// Идентификатор области IPv6-адреса.
DWORD dwLocalScopeId;
// Локальный порт.
DWORD dwLocalPort;
} MIB_UDP6ROW, *PMIB_UDP6ROW;
Для выборочного получения данных существует такая же функция, как и для TCP:
DWORD GetExtendedUdpTable(
PVOID pUdpTable,
PDWORD pdwSize,
bool bOrder,
ULONG ulAf,
UDP_TABLE_CLASS TableClass,
ULONG Reserved
);
Параметры функции GetExtendedUdpTable():
• pUdpTable — указатель на буфер для таблицы.
• pdwSize — размер буфера. Сюда же будет записан требуемый размер, если переданного недостаточно.
• bOrder — флаг включения упорядочивания записей в таблице.
• ulAf — запрашиваемое семейство адресов: AF_INET или AF_INET6.
• TableClass — тип извлекаемых записей:
• UDP_TABLE_BASIC — структура MIB_UDPTABLE, содержащая все конечные точки UDP локального узла;
• UDP_TABLE_OWNER_PID — MIB_UDPTABLE_OWNER_PID или MIB_UDP6TABLE_OWNER_PID, содержащая все конечные точки UDP;
• UDP_TABLE_OWNER_MODULE — MIB_UDPTABLE_OWNER_MODULE или MIB_UDP6TABLE_OWNER_MODULE.
• Reserved — зарезервировано и должно быть равно нулю.
Функция GetUdpStatistics() и подобные возвращают текущую статистику протокола:
#include <iphlpapi.h>
typedef struct _MIB_UDPSTATS
{
// Количество полученных дейтаграмм.
DWORD dwInDatagrams;
// Количество дейтаграмм, отклоненных из-за некорректного порта.
DWORD dwNoPorts;
// Количество полученных ошибочных дейтаграмм, кроме учтенных в dwNoPorts.
DWORD dwInErrors;
// Количество отправленных дейтаграмм.
DWORD dwOutDatagrams;
// Количество записей в таблице UDP-сокетов, ожидающих дейтаграммы.
DWORD dwNumAddrs;
} MIB_UDPSTATS, *PMIB_UDPSTATS;
ULONG GetUdpStatistics(PMIB_UDPSTATS Stats);
ULONG GetUdpStatisticsEx(PMIB_UDPSTATS Statistics, ULONG Family);
ULONG GetUdpStatisticsEx2(PMIB_UDPSTATS2 Statistics, ULONG Family);
Параметры функций GetUdpStatistics(), GetUdpStatisticsEx(), GetUdpStatisticsEx2():
• Family — запрашиваемое семейство протоколов AF_INET либо AF_INET6;
• Stats — указатель на структуру, которая получает статистику UDP.
В структуре MIB_UDPSTATS2 счетчики принятых и отправленных дейтаграмм являются 64-битными.
Вспомогательный API IP Helper предоставляет интерфейс для механизмов уведомления приложений об изменении настроек конфигурации сети. Поэтому с помощью IP Helper легко отслеживать, например, изменение набора IP-адресов интерфейсов.
Этот API позволяет работать с DHCP, что дает возможность обновлять и удалять выделенные IP-адреса, выданные интерфейсу.
Кроме того, IP Helper позволяет собирать различную статистику, например определять тип подключения и стоимость проходящих через него данных. С его помощью очень просто получить информацию о сетевых интерфейсах, включая IP-адреса, MAC-адреса, статус соединения и другие параметры. Это позволяет программам динамически адаптировать свое поведение в зависимости от сетевого окружения.
IP Helper предоставляет функции, которые помогают в диагностике сетевых проблем. Существует API для работы с протоколами маршрутизации, включая добавление и удаление маршрутов, получение информации о текущих маршрутах и определение наилучшего пути для доставки пакетов данных. Таким образом, в составе этого API представлено множество разнородных функций, так или иначе связанных с разработкой сетевых приложений. Большинство этих функций не переносимо. Разумно использовать библиотеки, чтобы нивелировать эти различия.
В целом API IP Helper является мощным инструментом для разработчиков сетевых приложений под Windows, позволяя им эффективно управлять сетью и получать необходимую информацию о сетевом окружении.
1. Что такое API IP Helper и для чего он используется?
2. Поддерживает ли IP Helper IPv6?
3. Какую библиотеку необходимо подключить для использования IP Helper в проекте?
4. Как с помощью IP Helper API получить адреса интерфейса?
5. Как примерно оценить стоимость данных, проходящих через интерфейс?
6. Каким образом можно обновить адрес, полученный интерфейсом по DHCP?
7. Какие параметры можно установить с помощью функции SetIpStatistics()?
8. Какими способами можно установить TTL интерфейса?
9. Какие возможности предоставляет IP Helper API для мониторинга и отладки сетевых соединений?
10. Почему для работы по ICMP в ОС Windows существует отдельный API?
11. Как, используя IP Helper API, отправить эхо-запрос ICMP и принять ответ?
12. Зачем и в каких случаях используется функция IcmpParseReplies()?
13. Как получить ARP-таблицу?
14. Если адрес какого-либо сетевого интерфейса изменился, как узнать об этом, не прибегая к опросу?
15. Для обнаружения изменения каких параметров сетевого интерфейса можно использовать IP Helper API?
16. Какой механизм использует IP Helper для уведомления приложений об изменении настроек конфигурации сети?
17. Как получить лучший маршрут до сервера?
18. Какую статистику IP Helper позволяет получить для TCP? А для UDP? Какие функции используются для получения этой статистики?
19. Используя функции IP Helper API, измените IP-адрес сетевого интерфейса.
20. Напишите программу, которая использует NotifyAddrChange() для отслеживания изменений в адресах интерфейсов и выводит информацию о добавленных или удаленных адресах.