Назад | Перейти на главную страницу

DNAT с использованием iptables работает только для трафика, входящего на eth0

У меня есть машина с двумя интерфейсами, которые имеют разные маршрутизируемые адреса IPv4. Чтобы вернуть трафик на правильный интерфейс, я использовал этот ответ и комментарий, и это работает: я могу подключиться к машине по ssh, используя любой IP-адрес. (Я не знаю, имеет ли это какое-либо влияние, но может.)

Теперь я добавил следующие правила iptables для обратного проксирования / DNAT используя этот ответ. Теперь мой сценарий загрузки выглядит так:

# eth0 is automatically brought up and gets 192.168.1.2 as IP

sudo ip link set eth1 up
sudo ip addr add 192.168.2.2/24 dev eth1
sudo ip rule add from 192.168.2.2 table frometh1
sudo ip route add default via 192.168.2.1 dev eth1 table frometh1

sudo iptables -t nat -A PREROUTING -i eth0 -p tcp -m tcp --dport 443 -j DNAT --to-destination 10.0.0.1:443
sudo iptables -t nat -A PREROUTING -i eth1 -p tcp -m tcp --dport 443 -j DNAT --to-destination 10.0.0.1:443
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE

echo 1 > /proc/sys/net/ipv4/ip_forward

Первоначально -i eth* было опущено, а правило DNAT состояло только из одной команды. Правила MASQUERADE также были одной командой путем обмена -o eth* для -d 10.0.0.1. Я разделил их, пытаясь сделать интерфейсы явными, на случай, если iptables неявно берет один из интерфейсов.

Когда я пытаюсь достичь 192.168.1.2:443, трафик перенаправляется и маскируется, как ожидалось. Счетчик попаданий для правильного правила DNAT в таблице PREROUTING увеличивается, а правильное правило MASQUERADE увеличивается. Я могу успешно установить TCP-соединение.

При попытке достичь 192.168.2.2:443, Я вижу трафик, входящий в интерфейс, и счетчик попаданий для правильного правила DNAT в таблице PREROUTING увеличивается. Но исходящего пакета нет, и ни одно из двух правил MASQUERADE в POSTROUTING не увеличивается. TCP SYN никогда не достигает удаленной системы (10.0.0.1).

Обратите внимание, что не имеет значения, происходит ли пересылка через eth0 или eth1, можно перенаправить трафик на цель (10.0.0.1) через eth0 или eth1.

Почему правила iptables работают только для eth0, а не для eth1?

Изменить: может быть важно знать, что проксирование в пользовательском пространстве работает нормально:

mkfifo /tmp/fifo
sudo nc -kvlp 443 </tmp/fifo | nc 10.0.0.1 443 >/tmp/fifo

Позволяет трафику идти в обе стороны, как описано в этом блоге. Проблема с этим решением заключается в том, что удаленный конец видит только одно постоянное TCP-соединение. Он должен видеть одно соединение для каждого клиента и обрабатывать несколько клиентов одновременно.

Сначала нарисуем топологию вашей сети.

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

Проверьте вывод nstat -az 'TcpExtIPReversePathFilter' команда. Скорее всего показывает ненулевой счетчик, который увеличивается при проверках.

Проверьте маршрутизацию командой ip route get 10.0.0.1 from <external-host-ip> iif eth1. Я думаю это покажет что-то вроде invalid cross-device link.

Вы можете увидеть текущее состояние rp_filter на интерфейсах в ip netconf show вывод команды.

Отключить rp_filter или лучше установить это в loose Режим. В rp_filter предотвращает подделку IP-адресов в нисходящих сетях, что широко используется в атаках с усилением DDoS. В вашем случае настройка не добавляет новых рисков, потому что ваш хост Linux не является основным шлюзом для подключенных сетей.

Это не последняя проблема в вашей настройке. Устранить проблему с rp_filter если существует, и я расширю ответ, чтобы ваша конфигурация работала.

Обновить

Теперь, когда rp_filter проблема решена, давайте попробуем заставить конфигурацию работать должным образом.

Начнем с описания того, что происходит и идет не так.

  1. Клиентский хост отправляет пакет на ext2.IP. Исходный пакет выглядит как

C.IP:<someport> -> <ext2.IP>:443

  1. Пакет прибывает в GW2 и после прохождения через перенаправление портов будет выглядеть, как показано ниже, и будет отправлено на хост linux.

C.IP:<someport> -> 192.168.2.2:443

  1. Когда пакет прибывает на хост Linux, правило DNAT снова перезаписывает пункт назначения в 10.0.0.1:443. Теперь у нас есть пакет в виде:

C.IP:<someport> -> 10.0.0.1:443

  1. Только после того, как пункт назначения переписывает, Linux ищет маршрут (решение о маршрутизации). Поскольку исходный адрес в пакетах остается исходным, ваше дополнительное правило маршрутизации не будет задействовано, и пакет будет маршрутизироваться через основную таблицу маршрутизации (я предполагаю, что GW1 будет использоваться). Мы улучшим его, потому что в этом основная причина проблемы.

  2. Пакет отправляется на GW1 через eth0 интерфейс. Исходный адрес будет перезаписан -o eth0 -j MASQUERADE правило. Пакет будет преобразован в:

192.168.1.2:<otherport> -> 10.0.0.1:443 с MAC-адресом назначения GW1.

  1. В GW1 пересылает этот пакет во внешнюю сеть с дополнительной перезаписью адреса источника.

<ext1.IP>:<otherport2> -> 10.0.0.1:443

  1. Удаленная система получает пакет, обрабатывает его и отвечает на него датаграммой tcp с флагами SYN-ACK. Ответ будет выглядеть так:

10.0.0.1:443 -> <ext1.IP>:<otherport2>

  1. В GW1 получает ответ, выполняет обратную трансляцию адреса назначения и отправляет ответ далее на хост linux:

10.0.0.1:443 -> 192.168.1.2:<otherport>

  1. Наконец, хост linux получает SYN-ACK answer, также выполняет обратный перевод адреса и пытается найти маршрут для дальнейшей пересылки ответа. Но после этого шага что-то пошло не так, но это просто следствие проблемы на шаге 4. Для лучшего понимания нужно проверить набор правил брандмауэра.

10.0.0.1:443 -> <C.IP>:<someport>

Решение

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

  1. Создайте две дополнительные таблицы маршрутизации (по одной на каждый восходящий канал)
ip route add 192.168.1.0/24 dev eth0 table 10
ip route add 0/0 via 192.168.1.1 dev eth0 table 10

ip route add 192.168.2.0/24 dev eth1 table 11
ip route add 0/0 via 192.168.2.1 dev eth1 table 11
  1. Создайте правила маршрутизации с совпадением по входному интерфейсу:
ip rule add iif eth0 lookup 10 pref 1010
ip rule add iif eth1 lookup 11 pref 1011

Все пакеты, полученные на конкретном интерфейсе, будут маршрутизироваться по ограниченной таблице маршрутизации, в которой есть маршруты только для одного интерфейса. Это простейшее решение, но оно устраняет возможность хоста Linux пересылать пакеты между интерфейсами (из eth0 к eth1 и наоборот). Если это вам не подходит, есть другой способ, но более сложный. Опишу, если нужно.