Работа с подсетями при помощи Прокси-ARP

Зачем при работе с подсетями нужен Прокси-ARP?

Конфигурация подсетей, при которых необходимо использование Прокси-ARP, достаточно специфична.

У меня была радио-Ethernet-ISA-8бит-карта. Мне было необходимо подключить ее к нескольким машинам одновременно. Я мог использовать ее на Linux-машине (правда мне для этого пришлось писать драйвер, но это тема для отдельного разговора). В общем, от меня требовалось установить вторую карту в Linux-машину, и некоторым образом объединить две подсети.

Предположим (в качестве примера), что сеть 0 - это локальная сеть Ethernet, подключенная к Linux при помощи NE2000-совместимой карты на интерфейсе eth0. Сеть 1 - это главная сеть, подключенная к Linux при помощи радио-Ethermet-карты на интерфейсе eth1. Машина A - это Linux с обоими интерфейсами. Машина B - любая TCP/IP-машина в сети 0, а машина C - такая же машина в сети 1.

В обычном случае я должен был сделать следующее:

Использовать IP-мост (см. Мини-HOWTO: Мосты), чтобы передавать пакеты между интерфейсами. К сожалению, у радио-Ethernet-карт нет режима "promisc" (они не могут перехватывать весь трафик сети 1). Это связано с маленькой скоростью передачи (2Мбит/сек), а также с тем, что нам совсем не надо обрабатывать весь трафик сети 1. Более того, мосты достаточно хорошо загружают систему!

С другой стороны, я мог использовать подсети и IP-маршрутизацию для передачи данных между подсетями (см. Мини-HOWTO: "Подсети IP"). Это решение подходит только для IP-протокола - ядро Linux будет обрабатывать лишь IP-пакеты, а остальным протоколам (например AppleTalk) потребуются дополнительные программы. Это также потребует новой IP-подсети, что не всегда возможно.

В этом случае я не мог получить новую подсеть, поэтому мне пришлось настроить все так, как будто все машины сети 0 подключены к сети 1. Вот здесь и понадобился Прокси-ARP. Для работы с другими (не-IP) протоколами использовались соответствующие программы - например для маршрутизации пакетов AppleTalk использовался netatalk.

Как использовать Прокси-ARP при работе с подсетями

На самом деле Прокси-ARP необходим только для передачи пакетов из сети 1 в сеть 0. Обратно пакеты идут при помощи стандартной IP-маршрутизации.

В данном случае у сети 1 была 8-битная маска (255.255.255.0). Для сети 0 я использовал 4-битную маску (255.255.255.240), тем самым получив возможность иметь в ней 14 адресов (2 ^ 4 = 16, минус 0-ой и 15-ый). Заметьте что эта подсеть может иметь любое количество бит, которое должно быть меньше количества бит главной сети (т.е. 2, 3, 4, 5, 6 или 7 бит в моем случае)

Все IP-адреса сети 0 (всего их 16) являются подмножеством адресов сети 1. Очень важно, чтобы ни у одной машины в сети 1 не было адреса, входящего в пространство сети 0! В моем случае я "зарезервировал" для сети 0 адреса сети 1, оканчивающиеся на 64 .. 79. В данном примере, адреса 64 и 79 не могут использоваться машинами: 64 - этой адрес сети 0, а 79 - это широковещательный адрес сети 0.

Машине A выделены два IP-адреса, один в адресном пространстве сети 0 для настоящего Ethernet-интерфейса (eth0), а второй в пространстве сети 1 (не сети 0) для интерфейса радио-карты (eth1).

Предположим, что машине C (в сети 1) необходимо послать пакет машине B (в сети 0). Машина C, считая что находится в одной подсети с машиной B, посылает в сети 1 широковещательный запрос с целью узнать Ethernet-адрес машины B. Машина B не получит этот запрос - она не находится в одной физической сети с машиной C - но этот запрос увидит машина A.

Вот здесь и начинается волшебство. Код arp в ядре Linux в машине A, будучи правильно настроен (Прокси-ARP для подсетей), определяет, что ARP-запрос идет из интерфейса сети 1 (eth1), а соответствующий IP-адрес находится в сети 0. В этом случае машина A в ответе на запрос укажет свой собственный Ethernet-адрес.

Машина C сделает запись в своем ARP-кэше, в котором укажет, что IP-адресу машины B соответствует Ethernet-адрес машины A (в данном случае, адрес радио-Ethernet карты). После этого машина C сможет послать пакет машине B на этот Ethernet-адрес, и его получит машина A.

Получив такой пакет, машина A определит, что его получатель не она, а машина B. Код IP-маршрутизации ядра Linux машины A попытается передать пакет машине B в соответствии со своей таблицей маршрутизации (в которой указано, какому интерфейсу, какая сеть соответствует). Однако, IP-адрес машины B соответствует одновременно и сети 0, и сети 1.

Вот здесь происходит вторая часть волшебства. Маска подсети на интерфейсе 0 имеет в двоичном представлении больше единиц (то есть, более конкретизирована), чем маска подсети интерфейса 1. Вследствие этого, код маршрутизации сопоставит этот пакет с интерфейсом eth0, игнорируя соответствие адреса в пакете сети 1 (из которой, собственно, этот пакет и пришел).

Теперь машине A необходим "настоящий" физический (Ethernet) адрес машины B (предположим, что его нет в ARP-кэше). Машина A посылает ARP-запрос, но, в этот раз, код arp в ядре Linux определяет, что запрос идет не из сети 1, и не посылает в ответ прокси-адрес интерфейса eth1. Вместо этого, запрос производится в сети 0 (интерфейс eth0) - этот запрос увидит машина B и пошлет в ответ свой (настоящий) физический (Ethernet) адрес. Теперь машина A может переслать пакет (шедший от машины C) машине B.

Машина B получает пакет от машины C (через машину A) и ей надо послать ответ. В этот раз машина B определяет, что машина C находится в другой подсети (маска 255.255.255.240 подсети машины B исключает из ее адресного пространства все машины, не входящие в сеть 0). В машине B маршрут по умолчанию указывает на машину A - она посылает пакет через машину A. Теперь код маршрутизации пакетов машины A определяет, что IP-адрес получателя (машины C) находится в сети 1, и она посылает этот пакет машине C через радио-Ethernet-интерфейс eth1.

Аналогично (и даже немного проще) происходит с пакетами любых машин обеих сетей, предназначенными для машины A.

Очевидно, что, если другая машина (D) в сети 0 пошлет ARP-запрос относительно физического адреса машины B, машина A получит этот запрос с сети 0 и не пошлет в ответ прокси-адрес интерфейса eth1, определив, что запрос идет из сети 0.

Заметьте, что в машинах B и C (и D) не требуется каких-то специальных IP-настроек. В моем случае - это была дикая смесь из Sun, Macintosh и PC-машин в сети 0, подключенных к остальному миру через Linux-машину A.

Маленькое дополнение: заметьте, что физические (Ethernet) адреса, полученные машинами A, B, C (и D), помещаются в ARP-кэш, и последующие пакеты не вызовут повторной процедуры ARP-запрос-ответ. ARP-кэш обычно удаляет записи с адресами после 5 минут их бездействия.

Настройка Прокси-ARP

Я настроил работу Прокси-ARP с подсетями в ядре Linux версии 2.0.30, но мне сказали, что все это работало еще в пору ядер 1.2.x.

Первое, что надо запомнить: код ARP можно разделить на две части: первая часть входит в ядро - она посылает и получает ARP запросы и ответы, обновляет содержимое ARP-кэша и т.п.; вторая часть - это команда arp(8), позволяющая администратору изменять содержимое ARP-кэша, а остальным пользователям его просматривать.

Первая проблема, с которой я столкнулся, состояла в следующем - программа arp(8), входившая в мой дистрибутив Slackware 3.1, была настолько стара(1994 года выпуска!!!), что вообще не могла работать с кодом arp в ядре (главное доказательство этого - очень странный результат работы команды "arp -a").

Команда arp(8) входит в состав пакета "net-tools-1.33a", который можно взять, практически, где угодно, в том числе и по адресу ftp.linux.org.uk:/pub/linux/Networking/base/ (этот адрес я взял из файла README из этого пакета). Она работает нормально, вместе с ней поставляется нормальная версия man-страниц, в которых все объяснено значительно подробнее, чем в старых.

Получив свежую версию команды arp(8), все изменения я внес в скрипт /etc/rc.d/rc.inet1 (это в Slackware - в других случаях все может быть немного по-другому). Сначала нам надо сменить широковещательный адрес, адрес и сетевую маску интерфейса eth0:

NETMASK=255.255.255.240 # это для 4-битной сети
NETWORK=x.y.z.64        # наш номер сети (замените x.y.z на настоящие цифры вашей сети)
BROADCAST=x.y.z.79      # широковещательный адрес (в моем случае)

Затем добавляем настройку второй Ethernet-карты (после загрузки всех необходимых модулей с драйвером):

/sbin/ifconfig eth1 (name on net 1) broadcast (x.y.z.255) netmask 255.255.255.0

Теперь добавляем запись в таблицу маршрутизации:

/sbin/route add -net (x.y.z.0) netmask 255.255.255.0

Вам также, возможно, понадобится сменить адрес шлюза, используемого по умолчанию.

Теперь настало время добавить строку, включающую Прокси-ARP:

/sbin/arp -i eth1 -Ds ${NETWORK} eth1 netmask ${NETMASK} pub

Здесь мы указываем коду ARP добавить в кэш статическую запись (опция s) для сети ${NETWORK}. Опция -D указывает ARP использовать адрес интерфейса eth1 (вторая запись eth1), что позволяет нам не выяснять физический адрес интерфейса eth1, чтобы вписать его сюда вручную. Опция netmask указывает ARP, что мы работаем с подсетью (т.е. Прокси для всех (IP-адресов) & ${NETMASK} == ${NETWORK} & ${NETMASK}). Опция pub означает, что ARP должен "опубликовать" эту ARP-запись, т.е., что это прокси-запись, и ее надо посылать в ответ на поиск всех IP-адресов этой подсети. Опция -i eth1 означает, что ARP должен отвечать только на запросы, идущие с интерфейса eth1.

Если нам повезет, после загрузки все машины подсети 0 появятся в сети 1. Вы можете проверить на машине A настройку Прокси-ARP, используя следующую команду: (имена и адреса изменены)

bash$ /sbin/arp -an
Address                 HWtype  HWaddress           Flags Mask            Iface
x.y.z.1                 ether   00:00:0C:13:6F:17   C     *               eth1
x.y.z.65                ether   00:40:05:49:77:01   C     *               eth0
x.y.z.67                ether   08:00:20:0B:79:47   C     *               eth0
x.y.z.5                 ether   00:00:3B:80:18:E5   C     *               eth1
x.y.z.64                ether   00:40:96:20:CD:D2   CMP   255.255.255.240 eth1

Вы можете просмотреть содержимое "файла" /proc/net/arp.

Последняя строка - это прокси-запись для подсети. Флаги CMP означают, что запись является статической (введена вручную), и должна быть опубликована. Эта строка будет использоваться только при получении запроса с сети 1, если IP-адрес соответствует сети 0. Заметьте, что команда arp(8) автоматически определила физический адрес интерфейса eth1, и подставила его в соответствующее место (опция -Ds).

Вы также можете проверить правильность таблицу маршрутизации. Ниже приведена моя таблица (имена и адреса снова изменены):

#/bin/netstat -rn
Kernel routing table
Destination     Gateway         Genmask         Flags Metric Ref Use    Iface
x.y.z.64        0.0.0.0         255.255.255.240 U     0      0       71 eth0
x.y.z.0         0.0.0.0         255.255.255.0   U     0      0      389 eth1
127.0.0.0       0.0.0.0         255.0.0.0       U     0      0        7 lo
0.0.0.0         x.y.z.1         0.0.0.0         UG    1      0      573 eth1

Эту же таблицу вы увидите, взглянув на файл /proc/net/route.

Заметьте, что первая запись является подмножеством второй, но таблица маршрутизации отсортирована в порядке приоритета, поэтому строка eth0 будет обрабатываться до строки eth1.

Другие способы работы с подсетями

Существуют и другие способы работы с этими подсетями, кроме Прокси-ARP. О некоторых из них я уже упомянул выше (мосты и маршрутизация):

IP-Маскарадинг (см. мини-HOWTO "IP-Маскарадинг"). При его использовании, сеть 0 будет скрыта за машиной A от остальной части Интернет. При попытке машин сети 0 связаться с остальным миром через машину A, адреса отправителей и номера портов в пакетах будут заменены на машине A так, как будто она сама связывается с внешним миром. Это достаточно красивое решение, но машины сети 1 не смогут связаться с машинами сети 0, потому что для сети 1 сеть 0 не существует. Это, конечно, увеличивает защищенность сети 0, но при этом теряется возможность работы машин сети 1 с машинами сети 0.

Другой способ - IP-туннель, но он поддерживается не всеми операционными системами, поэтому я решил его не использовать.

Использовать Прокси-ARP без подсетей. Теоретически это возможно, просто вам придется в ARP-кэше указать все машины подсети 0 по отдельности, вместо указания ссылки на всю сеть.

Наверно, здесь можно воспользоваться и IP-алиасингом, но я об этом не думал.