Определить, какой IP-адрес получил пакет

#sockets #boost-asio #winsock

#сокеты #boost-asio #winsock

Вопрос:

У меня есть UDP-сервер, который прослушивает все адреса IPv4, то есть INADDR_ANY или 0.0.0.0. На каждый пакет, который он получает от клиента, он должен отвечать IP-адресом точного сетевого интерфейса, который получил пакет. Концептуально он должен помещать адрес источника пакета в полезную нагрузку пакета.

Как я узнаю, какой адрес источника будет использоваться для отправки пакета на определенный адрес назначения при использовании sendto() ? Нужно ли мне перечислять все сетевые интерфейсы вручную, сопоставляя сетевые префиксы (или IPv4-адреса с маской подсети, если на то пошло) с адресом назначения? Если да, то как? Меня бы заинтересовало кроссплатформенное решение, включающее Boost.Asio но если это невозможно, мне нужно использовать Winsock. В будущем мне может понадобиться решение и для Linux.

Ответ №1:

На каждый пакет, который он получает от клиента, он должен отвечать IP-адресом точного сетевого интерфейса, который получил пакет.

Обычно это не то, что вам нужно обрабатывать вручную. Если вы просто sendto() отправили пакет на IP / порт отправителя, который recvfrom() сообщил, ОС направит пакет для вас, основываясь на том, какой интерфейс имеет доступный маршрут к этому отправителю.

Как я узнаю, какой адрес источника будет использоваться для отправки пакета на определенный адрес назначения при использовании sendto() ?

Вы можете использовать setsockopt() для включения IP_PKTINFO опции в прослушивающем сокете UDP, а затем использовать WSARecvMsg() (Windows) или recvmsg() (Linux) вместо recvfrom() . Это даст вам доступ к in_pktinfo структуре, которая сообщает вам IP-адрес назначения, на который был отправлен полученный пакет, и какой интерфейс фактически его получил.

Комментарии:

1. Это отличная новость! Я не знал о recvmsg() / WSARecvMsg() . Я предполагаю, что это также будет работать с сокетом, отличным от IPV6_V6ONLY, и возвращать адреса, сопоставленные с IPv4, верно? Причина, по которой я это делаю, заключается в поддержке какого-то дерьмового протокола, иначе я бы использовал только recvfrom() and sendto() , как вы предложили.

2. @purefanatic для сокета IPv6 (независимо IPV6_V6ONLY от того, включен он у вас или нет), вам необходимо включить опцию IPV6_PKTINFO (Windows) или IPV6_RECVPKTINFO (Linux), а затем WSARecvMsg() / recvmsg() предоставит вам in6_pktinfo struct вместо in_pktinfo .

3. Я вижу. Я забыл упомянуть об одном случае использования: клиент может использовать широковещательный адрес для доступа к серверу. В этом случае сервер все равно должен отправить одноадресный ответ, но информация, полученная из recvmsg() , будет не очень полезной, поскольку она вернет исходный широковещательный адрес или нет?

4. @purefanatic Это даст вам используемый широковещательный IP-адрес, да. Но это все равно даст вам конкретный интерфейс, на котором была получена широковещательная передача, и у вас все равно будет IP / порт отправителя. Таким образом, при необходимости вы можете привязать отдельный сокет к этому интерфейсу и использовать его для отправки обратно этому отправителю. В противном случае, как я уже сказал, просто отправьте отправителю обратно, используя принимающий сокет, и пусть ОС обрабатывает маршрутизацию за вас.