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

Как перенаправить запросы на порт 80 на localhost: 3000 с помощью nftables?

Я хочу, чтобы сетевой трафик, поступающий на 192.168.0.1:80, был перенаправлен на 127.0.0.1:3000. И я хотел бы, чтобы отображение ответа тоже было обработано. Мои полные правила таблицы NAT и фильтров вставлены ниже.

Я могу получать соединения через порт 80. Однако мне не удалось перенаправить трафик на localhost: 3000.

add table inet filter
add chain inet filter input { type filter hook input priority 0; policy accept; }
add chain inet filter forward { type filter hook forward priority 0; policy accept; }
add chain inet filter output { type filter hook output priority 0; policy accept; }
add rule inet filter input ct state related,established  counter accept
add rule inet filter input ip protocol icmp counter accept
add rule inet filter input iifname "lo" counter accept
add rule inet filter input ct state new  tcp dport 80 counter accept
add rule inet filter input ct state new  tcp dport 4489 counter accept
add rule inet filter input ct state new  tcp dport 8080 counter accept
add rule inet filter input iifname "tun0" ct state new  tcp dport 139 counter accept
add rule inet filter input iifname "tun0" ct state new  tcp dport 445 counter accept
add rule inet filter input ct state new  udp dport 1194 counter accept
add rule inet filter input counter reject with icmp type host-prohibited
add rule inet filter forward counter reject with icmp type host-prohibited
add table nat
add chain nat prerouting { type nat hook prerouting priority -100; }
add chain nat postrouting { type nat hook postrouting priority 100; }
add rule nat prerouting redirect
add rule nat prerouting tcp dport 80 redirect to 3000
add rule nat prerouting iifname eth0 tcp dport { 80, 443 } dnat 127.0.0.1:3000
add rule nat postrouting oifname eth0 snat to 192.168.0.1

Постараюсь адресовать и дополнить Собственный рабочий ответ OP и дальнейшие комментарии, которые включают некоторые оставшиеся вопросы:

  • почему net.ipv4.conf.eth0.route_localnet=1 нужно?
  • Почему нужно разрешить порт 3000 eth0 скорее, чем вот?

а также устранит незначительные проблемы с безопасностью.

Во-первых, вот обязательная схема потока пакетов в Netfilter и General Networking:

Эта схема была сделана с iptables в виду, но столы может (и делает это в большинстве наборов правил по умолчанию) использовать одни и те же хуки в одних и тех же местах.

Когда пакет прибывает на сетевой уровень (IP-уровень 3), он обрабатывается различными подсистемами. Обычно там будет только стек маршрутизации, но здесь Netfilter обеспечивает себе крючки (Conntrack, включая обработку NAT после начального пакета) или для столы.

Netfilter (Conntrack) или столы не заботится о маршрутизации (если, например, столы использует специализированные выражения, относящиеся к рузвон), они оставляют это стеку маршрутизации: они манипулируют адресами и портами и столы затем проверяет доступные свойства, такие как интерфейсы, адреса и порты.

Так:

  • пакет в новом соединении (таким образом, ip nat prerouting) прибывает из eth0 с (например) адресом источника 192.0.2.2 и портом 45678 назначения на адрес 192.168.0.1 и порт 80 (или 443).

  • то ip nat prerouting dnat Правило совпадает и говорит сетевой фильтр (его Conntrack подсистема), чтобы изменить адрес назначения на 127.0.0.1 и порт назначения на 3000. Это не меняет никаких других свойств пакета. В частности, пакет все еще прибыл из eth0.

  • стек маршрутизации (решение о маршрутизации в схеме) не зависит от Netfilter, поэтому логически остается независимым от него и не учитывает предыдущие изменения. Теперь он должен обрабатывать пакет из 192.0.2.2 и адресата 127.0.0.1.

    Это аномалия: это позволит увидеть диапазон адресов, зарезервированный для обратной связи, «в Интернете», как указано в RFC 1122:

    (грамм) { 127, <any> }

    Внутренний адрес обратной связи хоста. Адреса этой формы НЕ ДОЛЖНЫ появляться вне хоста.

    который явно обработанный в ядре Linux стек маршрутизации: рассматривать это как марсианский пункт назначения (т.е. отбрасывает пакет), если только расслаблен с помощью route_localnet=1 на соответствующем интерфейсе. Вот почему в этом конкретном случае net.ipv4.conf.eth0.route_localnet=1 должен быть установлен.

  • аналогично, следующий столы правило, на этот раз от ловушки ввода фильтра, видит пакет с входным интерфейсом eth0 но с портом назначения теперь 3000. Таким образом, он должен разрешить порт назначения 3000 и больше не должен разрешать 80 (или 443) принимать его. Так что правило следует сократить до:

    iifname "eth0" tcp dport {4489, 3000} counter accept
    

    потому что он никогда не увидит пакеты из eth0 с целевым tcp-портом 80 или 443: все они были изменены на порт 3000 в предыдущем хуке предварительной маршрутизации nat. Более того, для объяснения, предположим, что такие пакеты были замечены, они будут приняты, но поскольку на портах 80 или 443 не будет процесса прослушивания (он прослушивает порт 3000), стек tcp отправит обратно сброс TCP, чтобы отклонить связь.

    Также пока стек маршрутизации обеспечивает некоторые отношения между 127.0.0.0/8 и вот интерфейс (более расслабленный с route_localnet=1), как было сказано ранее, это не касается сетевой фильтр или столы которые не возражают против маршрутизации. Кроме того, если это было так, для ввод интерфейс это будет источник адрес, который не изменился, а не место назначения адрес, который будет относиться к вывод интерфейс, который даже не имеет реального значения в ввод дорожка: oif или oifname здесь нельзя использовать. Сам факт быть в входной крючок фильтра уже означает, что оцененный пакет прибывает на хост для местный процесс, как показано на схеме.

    ОБНОВЛЕНИЕ: На самом деле ранее данное правило следует изменить в дальнейшем по соображениям безопасности: порт 3000 разрешен, но не только для пункта назначения 127.0.0.1. Таким образом, соединение с 192.168.0.1:3000 может получить TCP RST, который намекает, что здесь есть что-то особенное, вместо того, чтобы не получать никакого ответа. В этом случае:

    • либо используйте это (которое включает очень странное второе правило):

      iifname "eth0" tcp dport 4489 counter accept
      iifname "eth0" ip daddr 127.0.0.1 tcp dport 3000 counter accept
      

      который, потому что route_localnet=1, по-прежнему позволяет настроенной системе в той же локальной сети 192.168.0.0/24 получать доступ к службе вообще без использования NAT, отправляя пакеты с 127.0.0.1 по проводам, даже если это, вероятно, не дает никакого выигрыша. Например, другая система Linux с этими 4 командами:

      sysctl -w net.ipv4.conf.eth0.route_localnet=1
      ip address delete 127.0.0.1/8 dev lo # can't have 127.0.0.1 also local
      ip route add 127.0.0.1/32 via 192.168.0.1 # via, that way no suspicious ARP *broadcast* for 127.0.0.1 will be seen elsewhere.
      socat tcp4:127.0.0.1:3000 -
      
    • или вместо этого, также защищая случай выше, более общий и предпочтительный:

      iifname "eth0" tcp dport 4489 counter accept
      ct status dnat counter accept
      
      • он сохраняет несвязанный порт 4489 / tcp разрешенным, как и раньше
      • ct status dnat Спички если пакет был ранее DNATed хостом: таким образом, он примет любые предыдущие изменения без необходимости повторно указывать, какой порт это был (все еще можно указать это или что-то еще, чтобы еще больше сузить объем того, что принимается): теперь значение порта 3000 также больше не нужно явно указывать.
      • таким образом, он также не разрешает прямые подключения к порту 3000, поскольку в этом случае не было бы DNAT.
  • для полноты: то же самое происходит в (не совсем) обратном порядке вывода и ответов. net.ipv4.conf.eth0.route_localnet=1 позволяет изначально сгенерированным исходящим пакетам от 127.0.0.1 до 192.0.2.2 не обрабатываться как марсианские источники (=> drop) в пути вывода решение о маршрутизации, прежде чем они смогут быть "без DNAT" обратно на исходный предполагаемый адрес источника (192.168.0.1) с помощью сетевой фильтр (Conntrack) в одиночестве.


Конечно, используя route_localnet=1 это своего рода ослабляющая безопасность (не совсем актуальна с соответствующими правилами брандмауэра, но не все системы используют брандмауэр) и требует соответствующих знаний по его использованию (например: копирование столы один только набор правил в другом месте больше не будет работать без route_localnet=1 установка).

Теперь, когда проблемы безопасности были решены в приведенных выше пояснениях (см. «ОБНОВЛЕНИЕ»), если бы приложению было разрешено прослушивать 192.168.0.1 (или любой адрес), а не только 127.0.0.1, эквивалентную конфигурацию можно было бы выполнить без включения route_localnet=1, изменив в ip nat prerouting:

iif eth0 tcp dport { 80, 443 } counter dnat 127.0.0.1:3000

кому:

iif eth0 tcp dport { 80, 443 } counter dnat to 192.168.0.1:3000

или просто:

  iif eth0 tcp dport { 80, 443 } counter redirect to :3000

которые не сильно отличаются: redirect изменяет пункт назначения на основной IP-адрес хоста на интерфейсе eth0 который равен 192.168.0.1, поэтому в большинстве случаев будет происходить то же самое.

Ты можешь использовать iptables-translate если у вас уже есть действующее правило iptables и вы хотите увидеть его эквивалент в nftables.

Например, действующее правило iptables для этого перенаправления будет:

-t nat -A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 3000

Накормите это iptables-translate и вы получите:

[root@vmtest-centos8 ~]# iptables-translate -t nat -A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 3000
nft add rule ip nat PREROUTING tcp dport 80 counter redirect to :3000

Никаких других правил nat для этого не требуется, хотя похоже, что у вас могут быть и другие перенаправления, которые вы также хотите установить. Сделайте то же самое для них.

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

Со следующей конфигурацией nftables и вводом sysctl -w net.ipv4.conf.eth0.route_localnet=1 в приглашении оболочки я могу подключиться к службе, прослушивающей localhost: 3000, подключившись к гипотетическому внешнему IP-адресу (192.168.0.1:80), связанному с eth0. Однако мне непонятно, почему это работает.

Включая порт 3000 в линейку iifname "eth0" tcp dport {4489, 80, 443, 3000} counter accept необходимо для того, чтобы это работало.

flush ruleset

table inet filter {
        chain input {
                type filter hook input priority 0; policy drop;

                ct state established,related accept

                iif lo counter accept

                icmp type echo-request counter accept

                iifname "eth0" tcp dport {4489, 80, 443, 3000} counter accept
  }
}

table ip nat {
        chain prerouting  {

                type nat hook prerouting priority -100;

                iif eth0 tcp dport { 80, 443 } counter dnat 127.0.0.1:3000
        }
}