Давайте посмотрим, как работать с тем, что называют 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″]
Думаю, что второе правило неправильное
должно быть, наверное, так -s 192.168.0.15/32
Согласен, поправил
Здравствуйте. Помогите пожалуйста, готову сломал с этими роутингами . Есть компутер с виртуал босом и виртуальой машиной. На хост машине поднят виртуальный интерфейс 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 соедененние не устанавливается ни в какую.
Здравствуйте. Честно говоря, не очень понятно, что вы хотите сделать.
Из тех правил, что вы написали, вы пытаетесь перенаправить соединение с 192.168.56.1:8081 на 127.0.0.1 (loopback).
Правильно ли я понимаю, что вы хотите при открытии в браузере http://192.168.56.1:8081 попадать на виртуальную машину с адресом в подсети 192.168.56.0/24?
Здравствуйте, Максим!
Тоже к вам за помощью :) Буду очень признателен за любой пинок в нужном направлении…
Задача стоит следующая:
Две машины (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.
Заранее спасибо!!!
Здравствуйте, Сергей.
Насколько помню, до пакета, в котором содержится —hex-string, при использовании -t nat, не дойдёт, потому что в таблицу NAT попадет только первый пакет.
А в первом случае соединение рвется, скорее всего, по причине того, что мастер получает ACK/RST и считает, что слейв больше недоступен.
К сожалению, тяжело что-то посоветовать, когда нет возможности посмотреть. Могу пару вариантов предложить:
1) Попробовать -j REJECT. В этом случае мастер хотя бы получит ответ, можно посмотреть, как он себя поведет
2) Посмотреть документацию по haproxy, там вроде можно было смотреть содержимое пакетов.
Здравствуйте, Максим.
Спасибо за очень дельную статью.
Тоже проблема. Сервер с 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 последние правила и действовать строго по статье. Ничего толкового. (((
Добрый день.
На самом деле редирект портов на loopback — это не нормальная ситуация, лучше так не делать, не уверен, что в более-менее новых версиях ядра получится даже при использовании
sysctl -w net.ipv4.conf.all.accept_local=1
и
sysctl -w net.ipv4.conf.all.route_local=1
Лучше поднять контейнер (Docker, например) и использовать стандартные средства для анонсирования порта наружу.
Но если очень надо, попробуйте проверить сначала перенаправление порта куда-то еще, на другой компьютер, а потом попробовать настройки sysctl с «local».
И да, как-то многовато правил :)
Полностью согласен. Связывать между собой внутренний и внешний интерфейсы — не лучшая идея. Но дело в том, что на локальном интерфейсе порт слушается сервером sshd. И порт этот — вход в обратный ssh-туннель, который имеет выход уже на мой рабочий компьютер. Есть ли возможность запускать в контейнере демоны? И не будет ли это «из пушки по воробьям»?
Ведь, собственно, задача довольно проста. Сделать так, чтобы клиентская часть приложения, которая стучится на сервер в определенный порт,
попадала через обратный ssh-туннель на рабочий компьютер (это сделано для того, чтобы пройти за NAT и серый адрес провайдера).
«…и использовать стандартные средства для анонсирования порта наружу…». Какие именно средства?
Демоны в контейнере — это скорее нет, но можно запустить sshd с опцией -D, чтобы он работал не как демон.
В данном случае это не «из пушки по воробьям», наоборот, это позволит один раз собрать контейнер и потом его переиспользовать столько раз, сколько надо.
Стандартные средства — это EXPOSE port-number в Dockerfile’е.
Ну или как вариант сконфигурировать sshd так, чтобы он слушал порт на всех интерфейсах.
Опять же, если используется reverse port forwarding, то ему можно указать, на каком интерфейсе слушать порт.
Если напишете конфигурацию sshd и какой командой делаете проброс порта, можно более предметно посмотреть.
Может быть проблему можно будет как-то проще решить.
Спасибо за советы. Попробую. Поберегите себя, маэстро, в пол-второго ночи отвечать на нелепые вопросы — это не путь к бодрости и хорошему самочувствию.
Создавать туннель приходится через 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.
Ну, это смотря в каком часовом поясе.
Еще один вопрос: у SSH сервера есть опция GatewayPorts (в файле /etc/ssh/sshd_config).
Она как раз разрешает биндить порты на внешнем интерфейсе, при reverse tunnel, например. Какое у нее значение, yes или no?
Если no, то надо будет поменять на yes и перезагрузить/перезапустить сервис sshd.
И можно указать в настройках PuTTY 0.0.0.0:2754:localhost:2755 и посмотреть, прокинется ли порт на внешний интерфейс.
Спасибо, Максим. Сердечная благодарность, всё получилось.
Но как???!!!!! В /etc/ssh/sshd_config вообще не было опции GatewayPorts. Сервак-то амазоновский. Вписал её сам. И почему при пробросе порта надо указывать для Putty 0.0.0.0, а не внешний адрес сервера? Я, кстати, во время неудачных попыток создания обратного туннеля пробовал прописать не адрес, а DNS. Putty его съела и нормально резольвила.
Контейнеры пока делаю с помощью LXC. Docker показался мне трудноватым для освоения: очень много документации, от которой могут начаться тошнотики, хоть у меня и свободное владение английским.
Всегда пожалуйста.
0.0.0.0 нужно для того, чтобы проще было проверить, что на внешнем интерфейсе порт слушается, исключив при этом обращение к dns.
По поводу LXC и Docker прекрасно вас понимаю, сам так же начинал. Да, LXC проще, порог вхождения для использования ниже, но Docker больше подходит для масштабирования, в том числе в облаке Amazon, например. И да, у него есть свои минусы тоже. Давно собираюсь по этому поводу статью написать, но как-то руки не доходят.
Здравствуйте! Помогите пожалуйста решить задачу, уже сломал себе мозг, не знаю как сделать.
Суть такая, что необходимо обратиться по по ssh по нестандартному порту 222 порту с ПК по ipv4 на vps сервер, далее как-то нужно с этот запрос сфорвордить через другой айпи ipv6 на сторонний сервер на 22 порт. Т.о. данный vps «как бы» будет являться прокси сервером, но это всё ещё усложняется тем, что удалённый роутер, куда должен я пробраться имеет динамический ipv6, поэтому нужно чтобы запрос поступал на dns имя его.
Проброс портов таким образом через iptables не будет работать как вы ожидаете. В момент добавления записи можно использовать доменное имя для редиректа, но оно будет резолвиться только один раз, в момент добавления записи. Если IP-адрес поменяется, iptables все равно будет использовать старый.
В этом случае может иметь смысл использовать проброс портов при помощи SSH. Либо обратный туннель с удаленного роутера, либо (что проще) просто форвард портов на локальную машину (ssh -L) таким образом, что при подключении можно будет подключаться на localhost клиента.
Добрый день, спасибо за статью. Подскажите пожалуйста, поможет мне 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 порту, и сразу есть доступ к интерфейсу, а у гугла как-то всё заморочено выходит :)
Добрый день. В теории поможет, на практике может быть удобнее и проще поставить на сервер nginx, заодно через него статику раздать. Я, к сожалению, не работал с Google Cloud, в основном с Amazon Web Services, поэтому точнее не готов сказать.