Используя Linux, как указать, какие данные интерфейса Ethernet передаются на

Я работаю над серверной системой на базе Linux, в которой есть два сетевых интерфейса, оба в одной подсети (на данный момент скажем, что они 172.17.32.10 и 172.17.32.11). Когда я отправляю данные на хост в сети, я хотел бы указать, через какой интерфейс на моем сервере передаются данные. Мне нужно иметь возможность переключаться с одного интерфейса на другой (или, может быть, даже передавать на оба) в программном обеспечении (статические правила маршрутизации не будут работать для этого приложения).

Я нашел связанный вопрос в StackOverflow, в котором предлагалось использовать библиотеку netlink для изменения маршрутов на лету. Интуитивно кажется, что это должно работать, но мне было интересно, есть ли другие варианты для достижения того же результата.


person Steve Hawkins    schedule 06.10.2008    source источник


Ответы (1)


Без обид, но ответ об использовании bind() совершенно неверен. bind() будет управлять исходным IP-адресом, помещенным в IP-заголовок пакета. Он не контролирует, какой интерфейс будет использоваться для отправки пакета: будет проведена консультация с таблицей маршрутизации ядра, чтобы определить, какой интерфейс имеет наименьшую стоимость для достижения определенного пункта назначения. (*смотрите примечание)

Instead, you should use an SO_BINDTODEVICE sockopt. This does two things:

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

Если у вас есть несколько интерфейсов, между которыми вы хотите переключаться, я бы предложил создать по одному сокету для каждого интерфейса. Поскольку вы также будете получать пакеты только на интерфейс, к которому вы привязаны, вам нужно будет добавить все эти сокеты к вашему select()/poll()/независимо от того, что вы используете.

#include <net/if.h>

struct ifreq ifr;

memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, "eth1", sizeof(ifr.ifr_name));
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
            (void *)&ifr, sizeof(ifr)) < 0) {
    perror("SO_BINDTODEVICE failed");
}

(*примечание) Bind() к IP-адресу интерфейса может привести к запутанному, но, тем не менее, правильному поведению. Например, если вы bind() на IP-адрес для eth1, но таблица маршрутизации отправляет пакет на eth0, то пакет появится на проводе eth0, но с исходным IP-адресом интерфейса eth1. Это странно, но разрешено, хотя пакеты, отправленные обратно на IP-адрес eth1, будут перенаправлены обратно на eth1. Вы можете проверить это, используя систему Linux с двумя интерфейсами IP. У меня есть один, и я проверял его, и bind() неэффективен для передачи пакета через физический интерфейс.

Хотя технически это разрешено, в зависимости от топологии это может, тем не менее, не работать. Чтобы ослабить распределенные атаки типа «отказ в обслуживании», когда злоумышленники используют поддельные исходные IP-адреса, многие маршрутизаторы теперь выполняют проверки переадресации по обратному пути (RPF). Пакеты с исходным IP-адресом на «неправильном» пути могут быть отброшены.

person DGentry    schedule 06.10.2008
comment
Я удалил свой ответ, ваш значительно лучше изучен. - person Jerub; 06.10.2008
comment
Прежде чем передать struct ifr в setsockopt, вы должны запустить ioctl(s, SIOCGIFINDEX, &ifr), чтобы определить индекс интерфейса по его имени. - person jackhab; 11.01.2011