Проброс и перенаправление портов в iptables
Проброс трафика за NAT или проброс трафика на другой сервер
Чаще всего проброс трафика используется, если мы находимся в локальной сети и от внешнего мира отделены шлюзом. Для того, чтобы открыть доступ для локальных служб (ssh, web, ftp), нам необходимо пробросить порты. Поскольку в качестве шлюза мы будем использовать сервер на Linux, то осуществлять данные действия будем с помощью iptables.
Определимся с переменными, которые будут использоваться в статье:
$EXT_IP - внешний, реальный IP-адрес шлюза;
$INT_IP - внутренний IP-адрес шлюза, в локальной сети;
$LAN_IP - внутренний IP-адрес сервера, предоставляющего службы внешнему миру;
$SRV_PORT - порт службы. Для веб-сервера равен 80, для SMTP - 25 и т.д.;
eth0 - внешний интерфейс шлюза. Именно ему присвоен сетевой адрес $EXT_IP;
eth1 - внутренний интерфейс шлюза, с адресом $INT_IP;
Проброс портов
На шлюз приходит пакет, который мы должны перенаправить на нужный сервер в локальной сети перед принятием решения о маршрутизации, то есть - в цепочке PREROUTING таблицы nat.
1 | iptables -t nat -A PREROUTING --dst $EXT_IP -p tcp --dport $SRV_PORT -j DNAT --to-destination $LAN_IP |
Рассмотрим пример
1 | iptables -t nat -A PREROUTING --dst 1.2.3.4 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.50 |
Если входящий пакет пришёл извне на шлюз (1.2.3.4), но предназначен веб-серверу (порт 80), то адрес назначения подменяется на локальный адрес 192.168.1.50. И впоследствии маршрутизатор передаст пакет в локальную сеть.
Дальше принимается решение о маршрутизации. В результате пакет пойдёт по цепочке FORWARD таблицы filter, поэтому в неё надо добавить разрешающее правило. Оно может выглядеть, например, так:
1 | iptables -I FORWARD 1 -i eth0 -o eth1 -d $LAN_IP -p tcp -m tcp --dport $SRV_PORT -j ACCEPT |
Рассмотрим пример
1 | iptables -I FORWARD 1 -i eth0 -o eth1 -d 192.168.0.22 -p tcp -m tcp --dport 80 -j ACCEPT |
Пропустить пакет, который пришёл на внешний интерфейс, уходит с внутреннего интерфейса и предназначен веб-серверу (192.168.1.50:80) локальной сети.
С одной стороны, этих двух правил уже достаточно для того, чтобы любые клиенты за пределами локальной сети успешно подключались к внутреннему серверу. С другой - а что будет, если попытается подключиться клиент из локальной сети? Подключение просто не состоится: стороны не поймут друг друга.
Допустим, 192.168.1.31 - ip-адрес клиента внутри локальной сети.
- Пользователь вводит в адресную строку браузера адрес example.com;
- Сервер обращается к DNS и разрешает имя example.com в адрес 1.2.3.4;
- Маршрутизатор понимает, что это внешний адрес и отправляет пакет на шлюз;
- Шлюз, в соответствии с нашим правилом, подменяет в пакете адрес 1.2.3.4 на 192.168.1.50, после чего отправляет пакет серверу;
- Веб-сервер видит, что клиент находится в этой же локальной сети (обратный адрес пакета - 192.168.1.31) и пытается передать данные напрямую клиенту, в обход шлюза;
- Клиент игнорирует ответ, потому что он приходит не с 1.2.3.4, а с 192.168.1.50;
- Клиент и сервер ждут, но связи и обмена данными нет.
Есть два способа избежать данной ситуации.
Первый - разграничивать обращения к серверу изнутри и извне, для этого создать на локальном DNS-сервере А-запись для example.com указывающую на 192.168.1.50
Второй - с помощью того же iptables заменить обратный адрес пакета.
Правило должно быть добавлено после принятия решения о маршрутизации и перед непосредственной отсылкой пакета. То есть - в цепочке POSTROUTING таблицы nat.
1 | iptables -t nat -A POSTROUTING --dst $LAN_IP -p tcp --dport $SRV_PORT -j SNAT --to-source $INT_IP |
Рассмотрим пример
1 | iptables -t nat -A POSTROUTING --dst 192.168.1.50 -p tcp --dport 80 -j SNAT --to-source 192.168.1.1 |
Если пакет предназначен веб-серверу, то обратный адрес клиента заменяется на внутренний адрес шлюза.
Этим мы гарантируем, что ответный пакет пойдёт через шлюз.
Надо дополнительно отметить, что это правило важно только для внутренних клиентов. Ответ внешним клиентам пойдёт через шлюз в любом случае.
Но, пока что, для нормальной работы этого недостаточно. Предположим, что в качестве клиента выступает сам шлюз.
В соответствии с нашими предыдущими правилами он будет гонять трафик от себя к себе и представлять исходящие пакеты транзитными.
Исправляем это:
1 | iptables -t nat -A OUTPUT --dst $EXT_IP -p tcp --dport $SRV_PORT -j DNAT --to-destination $LAN_IP |
Рассмотрим пример
1 | iptables -t nat -A OUTPUT --dst 1.2.3.4 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.50 |
Теперь для удобства запилим скрипт, чтобы не прописывать правила каждый раз вручную.
rules.sh
1 |
|
Теперь, чтобы обеспечить доступ извне к локальному FTP по адресу 192.168.1.52, достаточно набрать в консоли от имени супер-пользователя:
1 | ./rules.sh 192.168.1.52 20,21 |
Перенаправление портов
Перенаправление портов нужно в том случае, если мы хотим «замаскировать» внутреннюю службу, обеспечив к ней доступ извне не по стандартному, а совсем по другому порту.
Пусть $FAKE_PORT
- обманный порт на внешнем интерфейсе шлюза, подключившись к которому мы должны попасть на адрес $LAN_IP
и порт $SRV_PORT
.
Набор правил для iptables будет отличаться несущественно, поэтому приведу сразу пример итогового скрипта для ленивых.
1 |
|