Используя Linux, как указать, какие данные интерфейса Ethernet передаются - PullRequest
14 голосов
/ 06 октября 2008

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

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

1 Ответ

18 голосов
/ 06 октября 2008

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

Вместо этого вы должны использовать SO_BINDTODEVICE sockopt. Это делает две вещи:

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

Если у вас есть несколько интерфейсов, между которыми вы хотите переключиться, я бы предложил создать один сокет на интерфейс. Поскольку вы также будете получать пакеты только на интерфейс, к которому привязаны, вам нужно будет добавить все эти сокеты в 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-адресом источника по «неправильному» пути могут быть отброшены.

...