Linux: Привязать сокет прослушивания UDP к определенному интерфейсу (или узнать интерфейс, из которого поступила датаграмма)?
У меня есть демон, над которым я работаю, который прослушивает широковещательные пакеты UDP и отвечает также по UDP. Когда приходит пакет, я хотел бы знать, какой IP-адрес (или NIC) пакет пришелК так что я могу ответить с этим IP-адресом в качестве источника. (По причинам, сопряженным с большими трудностями, некоторые пользователи нашей системы хотят подключить две сетевые карты на одной машине к одной подсети. Мы говорим им не делать этого, но они настаивают. Мне не нужно напоминать, насколько это ужасно .)
Кажется, нет никакого способа проверить дейтаграмму и узнать непосредственно ее адрес назначения или интерфейс, на котором она появилась. Основываясь на большом гугле, я обнаружил, что единственный способ выяснить цель дейтаграммы - это иметь один сокет прослушивания на интерфейс и привязывать сокеты к их соответствующим интерфейсам.
Во-первых, мой сокет прослушивания создан следующим образом:
s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
Чтобы связать сокет, первое, что я попробовал, было это, гдеnic
этоchar*
на имя интерфейса:
// Bind to a single interface
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, strlen(nic));
if (rc != 0) { ... }
Это не имеет никакого эффектасовсем и молча терпит неудачу. Имя ASCII (например,eth0
) правильный тип имени для перехода на этот вызов? Почему бы это молча провалилось? В соответствии сman 7 socket
, "Обратите внимание, что это работает только для некоторых типов сокетов, в частности для сокетов AF_INET. Оно не поддерживается для сокетов пакетов (используйте там обычную bind (8))." Я не уверен, что это означает «пакетные сокеты», но это сокет AF_INET.
Итак, следующее, что я попробовал, было это (на основеbind vs SO_BINDTODEVICE сокет):
struct sockaddr_ll sock_address;
memset(&sock_address, 0, sizeof(sock_address));
sock_address.sll_family = PF_PACKET;
sock_address.sll_protocol = htons(ETH_P_ALL);
sock_address.sll_ifindex = if_nametoindex(nic);
rc=bind(s, (struct sockaddr*) &sock_address, sizeof(sock_address));
if (rc < 0) { ... }
Это тоже не получается, но на этот раз с ошибкойCannot assign requested address
, Я также попытался изменить семейство на AF_INET, но это не удается с той же ошибкой.
Остается один вариант - привязать сокеты к определенным IP-адресам. Я могу искать адреса интерфейсов и связываться с ними. К сожалению, это плохой вариант, потому что из-за DHCP и Ethernet-кабелей с горячей заменой адреса могут меняться на лету.
Эта опция также может быть плохой, если речь идет о широковещательных и многоадресных рассылках. Я обеспокоен тем, что привязка к определенному адресу будет означать, что я не могу получать трансляции (которые относятся к адресу, отличному от того, к которому я привязан). Я собираюсь проверить это позже этим вечером и обновить этот вопрос.
Вопросы:
Можно ли привязать сокет прослушивания UDP специально к интерфейсу?Или, альтернативно, есть ли механизм, который я могу использовать, который сообщит моей программе, что адрес интерфейса изменился в момент, когда происходит изменение (в отличие от опроса)?Есть ли другой тип прослушивающего сокета, который я могу создать (у меня есть права суперпользователя), который я могу привязать к определенному интерфейсу, который ведет себя идентично UDP (то есть, кроме необработанных сокетов, где мне бы пришлось самому реализовать UDP)? Например, могу ли я использоватьAF_PACKET
сSOCK_DGRAM
? Я не понимаю все варианты.Может кто-нибудь помочь мне решить эту проблему? Спасибо!
ОБНОВИТЬ:
Привязка к определенным IP-адресам не работает должным образом. В частности, я не могу получать широковещательные пакеты, а именно это я и пытаюсь получить.
ОБНОВИТЬ:
Я пытался с помощьюIP_PKTINFO
а такжеrecvmsg
чтобы получить больше информации о принимаемых пакетах. Я могу получить принимающий интерфейс, адрес получающего интерфейса, целевой адрес отправителя и адрес отправителя. Вот пример отчета, который я получаю при получении одного широковещательного пакета:
Got message from eth0
Peer address 192.168.115.11
Received from interface eth0
Receiving interface address 10.1.2.47
Desination address 10.1.2.47
Что действительно странно, так это то, что адрес eth0 - 10.1.2.9, а адрес ech1 - 10.1.2.47. Так почему же eth0 получает пакеты, которые должны быть получены eth1? Это определенно проблема.
Обратите внимание, что я включил net.ipv4.conf.all.arp_filter, хотя я думаю, что это применимо только к исходящим пакетам.