Linux: как я могу использовать определенный сетевой интерфейс?
Это можно было бы считать продолжением этого более раннего SO вопроса.
В идеале, я хотел бы заключить в тюрьму процесс только с использованием определенного интерфейса, несмотря ни на что. Он будет создавать TCP-соединения, отправлять UDP-дейтаграммы и прослушивать широковещательные передачи UDP. В настоящее время я делаю следующее:
- Определите IP-адрес используемого интерфейса.
- Создайте правило политики IP для маршрутизации всех пакетов, поступающих из интерфейса, на этот IP
- Создать другое правило политики IP для маршрутизации всех пакетов, поступающих с этого IP-адреса на этот интерфейс
- Настройка таблицы маршрутизации по умолчанию для каждого правила
Теперь это работает, в основном, но клиентский процесс также должен быть готов к игре. То есть, он должен привязываться к конкретному IP-интерфейсу, который он хочет использовать, и я думаю, что мне нужно также установить SO_BINDTODEVICE
. (Тем не менее, я продолжаю читать противоречивую информацию о том, работает ли SO_BINDTODEVICE
при использовании TCP или UDP.) К счастью, клиентское приложение - это Python, и я могу расширить класс сокета, чтобы сделать все это прозрачно. Но я не уверен, что это полное решение, особенно в отношении приема трансляций.
Мои вопросы:
-
Делает ли SO_BINDTODEVICE
то, что я хочу здесь? Или он эффективен только для сырых сокетов? Кто-то отметил, что "SO_BINDTODEVICE
в сокете не гарантирует, что сокет будет принимать только пакеты, которые поступают на этот провод/антенну физического интерфейса". Если это действительно так, то что делает SO_BINDTODEVICE
?
-
Есть ли способ сделать это так, чтобы локальный IP-адрес не был уникальным? Это не было бы проблемой, кроме того, что сервер DHCP на одном интерфейсе может выделять ему IP-адрес, который используется другим интерфейсом, что путает таблицу маршрутизации.
-
Как получать трансляции только с определенного интерфейса? Привязка к определенному IP-видимому, делает его игнорировать трансляции, что имеет смысл, но не совсем то, что я ищу.
Я работаю на Ubuntu 8.04 w/Linux kernel 2.6.26. Возможность одновременного доступа к одной и той же подсети в двух разных сетях через два разных интерфейса является неоспоримым требованием, что делает его (в основном) невосприимчивым к "не делайте этого".:)
Ответы
Ответ 1
После тяжелого уик-энда я рад представить решение, в котором рассматривается большинство из того, что я обсуждал ранее, с почти нулевыми проблемами.
Существует sysctl, называемый net.ipv4.conf.all.rp_filter, который может быть установлен на 0, чтобы отключить проверку источника:
rp_filter - INTEGER
2 - do source validation by reversed path, as specified in RFC1812
Recommended option for single homed hosts and stub network
routers. Could cause troubles for complicated (not loop free)
networks running a slow unreliable protocol (sort of RIP),
or using static routes.
1 - (DEFAULT) Weaker form of RP filtering: drop all the packets
that look as sourced at a directly connected interface, but
were input from another interface.
0 - No source validation.
Это также можно установить для каждого интерфейса, используя /proc/sys/net/ipv 4/conf/ <interface>
/rp_filter.
Как пояснил один плакат, он делает IP-маршрутизацию "менее детерминированной" в том смысле, что пакеты, поступающие из одной подсети, не гарантируют, что всегда выходят из одного и того же интерфейса. В этом случае это именно то, что нужно. Пожалуйста, сделайте дополнительное исследование, чтобы определить, действительно ли это то, что вы хотите.
Трансляции по-прежнему проблематичны по причинам, которые я не понимаю, но я, наконец, доволен этой проблемой, и я надеюсь, что это поможет другим.
Ответ 2
Что касается моего общего вопроса, для этого есть несколько способов сделать это:
-
Сложный способ изменения таблицы маршрутизации и сотрудничества с каждым процессом. Так я описал выше. Одно из преимуществ заключается в том, что он работает из пользовательского пространства. Я добавил несколько дополнительных заметок и ответил на мои конкретные вопросы ниже.
-
Напишите пользовательскую модель ядра, которая полностью игнорирует таблицу маршрутизации, если установлен SO_BINDTODEVICE
. Однако клиентский процесс по-прежнему требуется для вызова setsockopt(SOL_SOCKET, SO_BINDTODEVICE, dev)
. Этот вариант определенно не для слабонервных.
-
Виртуализировать процесс. Это, вероятно, не подходит для многих людей, и это принесет собственный набор головных болей, в основном с настройкой. Но стоит упомянуть.
Варианты 1 и 2 требуют, чтобы процессы для отказа выполнялись так, как нам бы хотелось. Это можно частично устранить, создав динамическую библиотеку, которая захватывает вызов socket() для создания сокета, а затем сразу же привязывает его к устройству перед возвратом дескриптора. Это описано более подробно здесь.
После нескольких исследований и большого количества Googling я могу сделать несколько выводов о том, как ведет себя ядро Linux 2.6.26. Обратите внимание, что это, вероятно, все поведение, специфичное для реализации, и, возможно, даже специфичное для ядра. Проверьте свою собственную платформу, прежде чем принимать решение о внедрении функций, основанных на моей единственной точке данных.
-
SO_BINDTODEVICE
действительно делает то, что он говорит, по крайней мере для UDP.
-
Уникальные IP-адреса для каждого интерфейса кажутся необходимыми, поскольку мы используем таблицы маршрутизации. Пользовательский модуль ядра может обойти это ограничение.
-
Чтобы получать трансляции на определенном интерфейсе, сначала привязывайте устройство к SO_BINDTODEVICE
, а затем привязывайте к широковещательному адресу с помощью обычного вызова bind(). Связывание устройства должно выполняться прежде всего. Затем сокет будет получать только те передачи, которые поступают на этот интерфейс.
Я протестировал его, сначала создав сокет. Затем я привязал его к определенному интерфейсу с помощью setsockopt(SOL_SOCKET, SO_BINDTODEVICE, dev)
. Наконец, я связал его с широковещательным адресом. С другого компьютера я отправил широковещательную передачу, которая будет получена через несвязанный интерфейс. Соединитель, связанный с устройством, не получил эту трансляцию, что имеет смысл. Удалите вызов setsockopt(SOL_SOCKET, SO_BINDTODEVICE, dev)
, и широковещательная передача будет принята.
Следует также упомянуть, что здесь можно использовать setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
. Обратите внимание, что семантика SO_REUSEADDR
изменяется с широковещательными адресами. В частности, законно иметь два сокета, связанных с широковещательным адресом и одним и тем же портом на одном компьютере, если оба они имеют SO_REUSEADDR
.
UPDATE: SO_BINDTODEVICE
с трансляциями, похоже, чревато опасностью, в частности, приемом широковещательных кадров. Я наблюдал за широковещательными кадрами, полученными на одном интерфейсе, но одновременно исчезал на другом. Похоже, что они выполняются локальной таблицей маршрутизации, но не защищены от правил политики IP. Тем не менее, я не уверен на 100% об этом, и я только упоминаю его как отправную точку, если вы хотите продолжить его. Все это сказать: используйте на свой страх и риск. В интересах времени я открыл сырой сокет на интерфейсе и сам разбирал заголовки Ethernet и IP.
Ответ 3
Не прямой ответ на ваш вопрос, а просто FYI. Как вы упомянули выше, это решение может быть слишком большим для того, что вам нужно/нужно делать.
Мне лично нравится идея создания модуля ядра ядра сетевого стека, который позволит мне это сделать. Таким образом, у меня есть полный контроль над многоадресными и модульными кадрами, идущими и выходящими из пользовательского пространства. Вам нужно будет использовать что-то вроде сокеты netlink для отправки/получения данных в приложение драйвера и приложения пользователя и из него, но он работает очень хорошо и очень быстро.
Вы также можете подключиться к любому уровню стека таким образом... Ethernet или IP. Таким образом, имея полный контроль над тем, что вы отправляете/получаете.
Вот пример article, в котором говорится о подключении к стеку netfilter.
Примечание. эта статья перехватывает IP-стек, а также старую. Я знаю, что API изменились, но многие из этих статей по-прежнему применяются практически и теоретически.
Если вы хотите подключиться к слою моста, вы должны использовать аналогичный механизм, но укажите
BR_LOCAL_IN instead of NF_IP_LOCAL_IN
Примечание. Это очень похоже на открытие сырого сокета на интерфейсе. Вам придется самостоятельно создавать свои фреймы.
Ответ 4
вы могли бы попытаться ограничить пространство имен процессов в одном интерфейсе. Для выполнения задания вам понадобится сборка ядра с CONFIG_NETNS (большинство ядер современных дистрибутивов) и некоторые script. Пример конфигурации