Настройка iptables от простого к сложному. Часть 3.

Давайте посмотрим, как работать с тем, что называют iptables routing, или перенаправлением пакетов при помощи iptables.
В первой и второй частях мы рассматривали настройку iptables, касающуюся блокировки пакетов, а теперь посмотрим, как не блокировать их, а направлять туда, куда нам нужно.

Маскарадинг (или маскирование)

Маскарадинг — это метод обработки пакетов, при котором пакеты передаются через некоторую машину, работающую как шлюз. Эта машина при пересылке пакетов помечает их, чтобы знать, какой машине в сети вернуть полученный ответ. Таким образом, несколько машин из внутренней сети могут обращаться к внешней сети, а извне это будет выглядеть так, как будто обращения идут от той самой машины, являющейся шлюзом. Маскарадинг связан в первую очередь с NAT (Network Address Translation), пакеты при трансляции адресов маскируются, чтобы ответ вернулся именно к источнику запроса.
Например, у нас есть некоторая локальная сеть с адресами 192.168.0.0/24, в этой сети есть шлюз с адресом 192.168.0.1, имеющий два сетевых интерфейса, eth0 и eth1. eth0 — внешний, подключенный к провайдеру, например, с адресом 192.168.100.25, eth1 — внутренний, подключенный к локальной сети, тот самый, на котором адрес 192.168.0.1. Необходимо обеспечить работу всех клиентов из локальной сети в сети Интернет таким образом, чтобы это было для них прозрачно.
В таком случае в первую очередь необходимо включить форвардинг пакетов между сетевыми интерфейсами шлюза, чтобы пропускать трафик из внутренней сети наружу. Есть два варианта, как это можно сделать. Первый — раскомментировать в файле /etc/sysctl.conf строчку

net.ipv4.ip_forward=1

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

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

Этот способ заработает без перезагрузки. Можно использовать оба, а можно в скрипте, например, использовать при загрузке правил iptables только второй.
После этого мы можем задать правило для адресной трансляции:

iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -d ! 192.168.0.0/24 -j MASQUERADE

Это правило после обработки пакетов осуществит маскирование, если пакеты из внутренней сети направлены куда-то в другую подсеть. Если нам нужно маскировать пакеты для конкретной подсети, к примеру, из одной локальной подсети (192.168.2.0/24) в другую (192.168.0.0/24), то мы можем создать следующее правило:

iptables -t nat -A POSTROUTING -s 192.168.2.0/24 -d 192.168.0.0/24 -j MASQUERADE

Перенаправление пакетов на другой порт

Перенаправление портов можно использовать для самых разных задач. Например, перенаправление пользователей из разных сетей на разные экземпляры веб-сервера, перенаправление порта на другой, если изменился порт какого-то сервиса, настройка прозрачного проксирования и так далее. Общая идея в том, что пакеты с определенного порта перенаправляются на другой порт на том же сетевом интерфейсе той же самой машины, либо на loopback’е.
Для перенаправления порта, например, 80, с внешнего интерфейса на порт 80 на loopback-интерфейсе мы можем использовать правило

iptables -t nat -A PREROUTING -d 192.168.0.1/32 -p tcp -m tcp --dport 80 -j DNAT --to-destination 127.0.0.1:80

Это правило позволит перенаправить пакеты на loopback, изменив назначение пакета путем трансляции адреса (Destination NAT), и после этого можно будет их отфильтровать в цепочке, которая будет задана для loopback-интерфейса. Естественно, нужно будет сделать и обратную трансляцию:

iptables -t nat -A POSTROUTING -s 127.0.0.1/32 -p tcp -m tcp --dport 80 -j SNAT --to-source 192.168.0.1

Таким образом, происходит обратный процесс, при этом изменяется Source NAT, то есть, транслируется адрес источника соединения.

Форвардинг портов на другую машину

По сути форвардинг портов на другую машину не отличается от форварда портов в пределах одной машины, но по существу это не одно и то же, поскольку пакеты будут передаваться не в пределах одной машины, как в случае с loopback-интерфейсом, когда фактически пакеты транслируются в пределах сетевого стека. Обычно форвардинг портов производится с определенного порта внешнего интерфейса на определенный порт машины во внутренней сети, поэтому между сетевыми интерфейсами должен быть настроен форвардинг. Например, проброс порта 3389 для работы удаленного рабочего стола (RDP) с внешнего сетевого интерфейса (192.168.100.25) на порт 3389 на одну из машин во внутренней сети (192.168.0.15):

iptables -t nat -A PREROUTING -d 192.168.100.25/32 -p tcp -m tcp --dport 3389 -j DNAT --to-destination 192.168.0.15:3389
iptables -t nat -A POSTROUTING -s 192.168.0.15/32 -p tcp -m tcp --dport 3389 -j SNAT --to-source 192.168.100.25

Таблица форвардинга

При настройке iptables мы уже использовали таблицу форвардинга, но единственное, что мы делали — это очищали правила командой

iptables -F FORWARD

В этой таблице не рекомендуется фильтровать трафик, рекомендуется ее использовать только для перенаправления трафика. Фильтрацию необходимо выполнять либо до форвардинга, либо уже после. В таблице FORWARD вы определяете, куда должны форвардиться пакеты, а куда нет. Например:
— разрешить форвардинг между eth0 и eth1
— разрешить форвардинг между eth0 и eth2
— запретить форвардинг между eth1 и eth2
Правила в таком случае могут выглядеть так:

iptables -A FORWARD -i eth0 -o eth1 -j ACCEPT
iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT
iptables -A FORWARD -i eth0 -o eth2 -j ACCEPT
iptables -A FORWARD -i eth2 -o eth0 -j ACCEPT
iptables -A FORWARD -i eth1 -o eth2 -j DROP
iptables -A FORWARD -i eth2 -o eth1 -j DROP

либо еще короче:

iptables -A FORWARD -i eth1 -o eth2 -j DROP
iptables -A FORWARD -i eth2 -o eth1 -j DROP

Разница между DROP и REJECT

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

[wysija_form id=»2″]

Настройка iptables от простого к сложному. Часть 3.: 18 комментариев

  1. An

    Думаю, что второе правило неправильное

    iptables -t nat -A PREROUTING -d 192.168.100.25/32 -p tcp -m tcp --dport 3389 -j DNAT --to-destination 192.168.0.15:3389
    iptables -t nat -A POSTROUTING -d 192.168.0.15/32 -p tcp -m tcp --dport 3389 -j SNAT --to-source 192.168.100.25
    

    должно быть, наверное, так -s 192.168.0.15/32

    iptables -t nat -A POSTROUTING -s 192.168.0.15/32 -p tcp -m tcp --dport 3389 -j SNAT --to-source 192.168.100.25
  2. Александр

    Здравствуйте. Помогите пожалуйста, готову сломал с этими роутингами . Есть компутер с виртуал босом и виртуальой машиной. На хост машине поднят виртуальный интерфейс vboxnet0 с ип 192.168.56.1 и виртуалка, котрая лежит в этой подсети.

    делаю правила на хост машине

    iptables -t nat -A PREROUTING -d 192.168.56.1/32 -p tcp -m tcp —dport 8081 -j DNAT —to-destination 127.0.0.1:8081

    iptables -t nat -A POSTROUTING -s 127.0.0.1/32 -p tcp -m tcp —dport 8081 -j SNAT —to-source 192.168.56.1

    Но при попытке соединитьс на 192.168.56.1:8081 соедененние не устанавливается ни в какую.

    1. Maxim Norin Автор записи

      Здравствуйте. Честно говоря, не очень понятно, что вы хотите сделать.
      Из тех правил, что вы написали, вы пытаетесь перенаправить соединение с 192.168.56.1:8081 на 127.0.0.1 (loopback).
      Правильно ли я понимаю, что вы хотите при открытии в браузере http://192.168.56.1:8081 попадать на виртуальную машину с адресом в подсети 192.168.56.0/24?

  3. Сергей

    Здравствуйте, Максим!
    Тоже к вам за помощью :) Буду очень признателен за любой пинок в нужном направлении…
    Задача стоит следующая:
    Две машины (master, slave) соединены по протоколу Modbus tcp (порт 502). Master может писать и читать в/из slave, slave умеет только отвечать.
    Вклиниваюсь третей машиной (arper) между ними. Делаю что-то типа ARP spoofing. Пытаюсь настроить arper таким образом, чтобы не пропускать только пакеты на запись от master в slave. Пакеты на запись удается идентифицировать при помощи -m string —hex-string ‘|XX|’.
    Если создаю правило: iptables -A FORWARD -p tcp —dport 502 -m string —algo bm —from 30 —to 31 —hex-string ‘|06|’ -j DROP, то запись не проходит, но master рвет соединение после неудачной попытки записать.
    Если пытаюсь перенаправить пакет на другой slave: iptables -t nat A PREROUTING -p tcp —dport 502 -m string —algo bm —from 30 —to 31 —hex-string ‘|06|’ -j DNAT —to-destination fakeslave, то тут не работает -m string.
    Заранее спасибо!!!

    1. Maxim Norin Автор записи

      Здравствуйте, Сергей.
      Насколько помню, до пакета, в котором содержится —hex-string, при использовании -t nat, не дойдёт, потому что в таблицу NAT попадет только первый пакет.
      А в первом случае соединение рвется, скорее всего, по причине того, что мастер получает ACK/RST и считает, что слейв больше недоступен.
      К сожалению, тяжело что-то посоветовать, когда нет возможности посмотреть. Могу пару вариантов предложить:
      1) Попробовать -j REJECT. В этом случае мастер хотя бы получит ответ, можно посмотреть, как он себя поведет
      2) Посмотреть документацию по haproxy, там вроде можно было смотреть содержимое пакетов.

  4. Сергей

    Здравствуйте, Максим.
    Спасибо за очень дельную статью.
    Тоже проблема. Сервер с Ubuntu. На нем 2 интерфейса: lo и eth0 (внешний). На lo слушается порт 2755. На eth0 на порт 2754 прилетают SYN пакеты (вижу их tcpdumpом). На lo не пробрасываются.
    Правила:
    iptables -A FORWARD -i eth0 -o lo -j ACCEPT
    iptables -A FORWARD -i lo -o eth0 -j ACCEPT

    iptables -t nat -A PREROUTING -d 172.31.35.125/32 -p tcp -m tcp —dport 2754 -j DNAT —to-destination 127.0.0.1:2755
    iptables -t nat -A POSTROUTING -s 127.0.0.1/32 -p tcp -m tcp —dport 2755 -j SNAT —to-source 172.31.35.125

    iptables -A FORWARD -i eth0 -o lo -p tcp —syn —dport 2754 -m conntrack —ctstate NEW -j ACCEPT
    iptables -A FORWARD -i eth0 -o lo -m conntrack —ctstate ESTABLISHED,RELATED -j ACCEPT
    iptables -A FORWARD -i lo -o eth0 -m conntrack —ctstate ESTABLISHED,RELATED -j ACCEPT

    Форвардинг включен. Пробовал убирать 3 последние правила и действовать строго по статье. Ничего толкового. (((

    1. Maxim Norin Автор записи

      Добрый день.
      На самом деле редирект портов на loopback — это не нормальная ситуация, лучше так не делать, не уверен, что в более-менее новых версиях ядра получится даже при использовании

      sysctl -w net.ipv4.conf.all.accept_local=1

      и

      sysctl -w net.ipv4.conf.all.route_local=1

      Лучше поднять контейнер (Docker, например) и использовать стандартные средства для анонсирования порта наружу.
      Но если очень надо, попробуйте проверить сначала перенаправление порта куда-то еще, на другой компьютер, а потом попробовать настройки sysctl с «local».
      И да, как-то многовато правил :)

      1. Сергей

        Полностью согласен. Связывать между собой внутренний и внешний интерфейсы — не лучшая идея. Но дело в том, что на локальном интерфейсе порт слушается сервером sshd. И порт этот — вход в обратный ssh-туннель, который имеет выход уже на мой рабочий компьютер. Есть ли возможность запускать в контейнере демоны? И не будет ли это «из пушки по воробьям»?
        Ведь, собственно, задача довольно проста. Сделать так, чтобы клиентская часть приложения, которая стучится на сервер в определенный порт,
        попадала через обратный ssh-туннель на рабочий компьютер (это сделано для того, чтобы пройти за NAT и серый адрес провайдера).

        «…и использовать стандартные средства для анонсирования порта наружу…». Какие именно средства?

        1. Maxim Norin Автор записи

          Демоны в контейнере — это скорее нет, но можно запустить sshd с опцией -D, чтобы он работал не как демон.
          В данном случае это не «из пушки по воробьям», наоборот, это позволит один раз собрать контейнер и потом его переиспользовать столько раз, сколько надо.
          Стандартные средства — это EXPOSE port-number в Dockerfile’е.
          Ну или как вариант сконфигурировать sshd так, чтобы он слушал порт на всех интерфейсах.
          Опять же, если используется reverse port forwarding, то ему можно указать, на каком интерфейсе слушать порт.
          Если напишете конфигурацию sshd и какой командой делаете проброс порта, можно более предметно посмотреть.
          Может быть проблему можно будет как-то проще решить.

  5. Сергей

    Спасибо за советы. Попробую. Поберегите себя, маэстро, в пол-второго ночи отвечать на нелепые вопросы — это не путь к бодрости и хорошему самочувствию.

    Создавать туннель приходится через Putty. И, хотя команды идентичны ssh Linux, что-то у них не доработано.
    Putty (вкладка ssh>tunnels):
    Port forwarding:
    v Local ports accept connections from other hosts
    v Remote ports do the same
    Forwarded ports:
    4Rhost2404.zapto.org:2754 localhost:2755

    Логи подключения:
    2018-05-26 07:13:11 Access granted
    2018-05-26 07:13:11 Opening session as main channel
    2018-05-26 07:13:11 Opened main channel
    2018-05-26 07:13:11 Requesting remote port host2404.zapto.org:2754 forward to localhost:2755
    2018-05-26 07:13:11 Remote port forwarding from host2404.zapto.org:2754 enabled (это elastic IP — внешний адрес сервера)
    2018-05-26 07:13:11 Allocated pty (ospeed 38400bps, ispeed 38400bps)
    2018-05-26 07:13:11 Started a shell/command

    Но на сервере:
    lsof -nPE -i | grep 2754
    sshd 4897 ubuntu 9u IPv6 28224 0t0 TCP [::1]:2754 (LISTEN)
    sshd 4897 ubuntu 10u IPv4 28225 0t0 TCP 127.0.0.1:2754 (LISTEN)
    (то есть слушает все равно на локальном интерфейсе)

    Конфигурация sshd:

    # Package generated configuration file
    # See the sshd_config(5) manpage for details

    # What ports, IPs and protocols we listen for
    Port 22
    # Use these options to restrict which interfaces/protocols sshd will bind to
    #ListenAddress ::
    #ListenAddress 0.0.0.0
    Protocol 2

    #Privilege Separation is turned on for security
    UsePrivilegeSeparation yes

    # Lifetime and size of ephemeral version 1 server key
    KeyRegenerationInterval 3600

    sshd уже запускается почему-то с ключом -D.

    1. Maxim Norin Автор записи

      Ну, это смотря в каком часовом поясе.
      Еще один вопрос: у SSH сервера есть опция GatewayPorts (в файле /etc/ssh/sshd_config).
      Она как раз разрешает биндить порты на внешнем интерфейсе, при reverse tunnel, например. Какое у нее значение, yes или no?
      Если no, то надо будет поменять на yes и перезагрузить/перезапустить сервис sshd.
      И можно указать в настройках PuTTY 0.0.0.0:2754:localhost:2755 и посмотреть, прокинется ли порт на внешний интерфейс.

      1. Сергей

        Спасибо, Максим. Сердечная благодарность, всё получилось.
        Но как???!!!!! В /etc/ssh/sshd_config вообще не было опции GatewayPorts. Сервак-то амазоновский. Вписал её сам. И почему при пробросе порта надо указывать для Putty 0.0.0.0, а не внешний адрес сервера? Я, кстати, во время неудачных попыток создания обратного туннеля пробовал прописать не адрес, а DNS. Putty его съела и нормально резольвила.
        Контейнеры пока делаю с помощью LXC. Docker показался мне трудноватым для освоения: очень много документации, от которой могут начаться тошнотики, хоть у меня и свободное владение английским.

        1. Maxim Norin Автор записи

          Всегда пожалуйста.
          0.0.0.0 нужно для того, чтобы проще было проверить, что на внешнем интерфейсе порт слушается, исключив при этом обращение к dns.
          По поводу LXC и Docker прекрасно вас понимаю, сам так же начинал. Да, LXC проще, порог вхождения для использования ниже, но Docker больше подходит для масштабирования, в том числе в облаке Amazon, например. И да, у него есть свои минусы тоже. Давно собираюсь по этому поводу статью написать, но как-то руки не доходят.

  6. Юрий

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

    Суть такая, что необходимо обратиться по по ssh по нестандартному порту 222 порту с ПК по ipv4 на vps сервер, далее как-то нужно с этот запрос сфорвордить через другой айпи ipv6 на сторонний сервер на 22 порт. Т.о. данный vps «как бы» будет являться прокси сервером, но это всё ещё усложняется тем, что удалённый роутер, куда должен я пробраться имеет динамический ipv6, поэтому нужно чтобы запрос поступал на dns имя его.

    1. Maxim Norin Автор записи

      Проброс портов таким образом через iptables не будет работать как вы ожидаете. В момент добавления записи можно использовать доменное имя для редиректа, но оно будет резолвиться только один раз, в момент добавления записи. Если IP-адрес поменяется, iptables все равно будет использовать старый.
      В этом случае может иметь смысл использовать проброс портов при помощи SSH. Либо обратный туннель с удаленного роутера, либо (что проще) просто форвард портов на локальную машину (ssh -L) таким образом, что при подключении можно будет подключаться на localhost клиента.

  7. Mx

    Добрый день, спасибо за статью. Подскажите пожалуйста, поможет мне iptables для решения следующей задачи.

    Впервые работаю с Google Cloud, настроил там виртуальную машину ака VPS, очень долго разбирался с тем, как мне открыть порты, но дело было не в гугловском фаерволе, а в том, что они на своих серверах используют вместо привычного адреса локалхоста 127.0.0.1, т.н. ‘адрес всех сетевых интерфейсов’ — 0.0.0.0. Но некоторый софт мне так и не удалось запустить на 0.0.0.0 и он работает на привычном 127.0.0.1, но к нему нет доступа по внешнему IP.

    Поможет ли мне в этом случае, что-то вроде редиректа или рероутинга с 0.0.0.0 на 127.0.0.1, для того, чтобы к серверу был доступ по внешнему IP с указанием порта?

    Обычно всё работает из коробки, я просто запускаю сервер на IP впски и например 9100 порту, и сразу есть доступ к интерфейсу, а у гугла как-то всё заморочено выходит :)

    1. Maxim Norin Автор записи

      Добрый день. В теории поможет, на практике может быть удобнее и проще поставить на сервер nginx, заодно через него статику раздать. Я, к сожалению, не работал с Google Cloud, в основном с Amazon Web Services, поэтому точнее не готов сказать.

Обсуждение закрыто.