Используя Linux, как указать, по какому интерфейсу ethernet передаются данные
-
05-07-2019 - |
Вопрос
Я работаю над серверной системой на базе Linux, в которой есть два сетевых интерфейса, оба в одной подсети (пока давайте просто скажем, что они 172.17.32.10
& 172.17.32.11
).Когда я отправляю данные на хост в сети, я хотел бы указать, по какому интерфейсу на моем сервере передаются данные.Мне нужно иметь возможность переключаться с одного интерфейса на другой (или, возможно, даже передавать данные на обоих) программно (статические правила маршрутизации не будут работать для этого приложения).
Я нашел связанный с этим вопрос в StackOverflow, в котором предлагалось использовать библиотеку netlink для изменения маршрутов "на лету".Интуитивно кажется, что это должно сработать, но мне было интересно, есть ли какие-либо другие варианты для достижения того же результата.
Решение
Не хочу вас обидеть, но ответ об использовании bind() совершенно неверен.bind() будет управлять исходным IP-адресом, помещенным в IP-заголовок пакета.Он не определяет, какой интерфейс будет использоваться для отправки пакета:мы обратимся к таблице маршрутизации ядра, чтобы определить, какой интерфейс имеет наименьшие затраты для достижения определенного пункта назначения.(* см. примечание)
Вместо этого вам следует использовать SO_BINDTODEVICE
сокопт.Это делает две вещи:
- Пакеты всегда будут выходить из указанного вами интерфейса, независимо от того, что указано в таблицах маршрутизации ядра.
- Только пакеты, поступающие по указанному интерфейсу, будут переданы сокету.Пакеты, поступающие на другие интерфейсы, не будут.
Если у вас есть несколько интерфейсов, между которыми вы хотите переключаться, я бы предложил создать по одному сокету для каждого интерфейса.Поскольку вы также будете получать пакеты только на тот интерфейс, к которому вы привязаны, вам нужно будет добавить все эти сокеты в свой 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-адресом по "неправильному" пути могут быть отброшены.