How to do local port forwarding with iptables
I have an application (server) listening on port 8080. I want to be able to forward port 80 to it, such that hitting http://localhost resolves my application (on localhost:8080 ). This should be generalized for any port mapping (e.g. 80:8080 => P_src:P_target ), and use best practices for modern *nix machines (e.g. Ubuntu). N.B. This is all done locally, so there is no need to accept connections from anyone but localhost.
1 Answer 1
So after much searching around, I found the answer uses iptables, setting up a NAT, and using the built-ins PREROUTING and OUTPUT.
First, you must have port forwarding enabled:
echo «1» > /proc/sys/net/ipv4/ip_forward
Then you have to add the following rules to your iptables NAT table, using your own values for $ and $ :
iptables -t nat -A PREROUTING -s 127.0.0.1 -p tcp --dport $ -j REDIRECT --to $` iptables -t nat -A OUTPUT -s 127.0.0.1 -p tcp --dport $ -j REDIRECT --to $`
If you want to remove the rules, you simply need to use the -D switch instead of -A for each rule.
I build a nice little script for this that does adding and removing of mappings.
#!/bin/bash # # API: ./forwardPorts.sh add|rm p1:p1' p2:p2' . # # Results in the appending (-A) or deleting (-D) of iptable rule pairs that # would otherwise facilitate port forwarding. # # E.g # sudo iptables -t nat -A PREROUTING -s 127.0.0.1 -p tcp --dport 80 -j REDIRECT --to 8080 # sudo iptables -t nat -A OUTPUT -s 127.0.0.1 -p tcp --dport 80 -j REDIRECT --to 8080 # if [[ $# -lt 2 ]]; then echo "forwardPorts takes a state (i.e. add or rm) and any number port mappings (e.g. 80:8080)"; exit 1; fi case $1 in add ) append_or_delete=A;; rm ) append_or_delete=D;; * ) echo "forwardPorts requires a state (i.e. add or rm) as it's first argument"; exit 1; ;; esac shift 1; # Do a quick check to make sure all mappings are integers # Many thanks to S.O. for clever string splitting: # http://stackoverflow.com/questions/918886/how-do-i-split-a-string-on-a-delimiter-in-bash for map in "$@" do IFS=: read -a from_to_array =~ ^-?3+$ ]] || [[ ! $ =~ ^-?6+$ ]]; then echo "forwardPorts port maps must go from an integer, to an integer (e.g. 443:4443)"; exit 1; fi mappings[$]=$ done # We're shooting for transactional consistency. Only manipulate iptables if all # the rules have a chance to succeed. for map in "$" do IFS=: read -a from_to_array to=$ sudo iptables -t nat -$append_or_delete PREROUTING -s 127.0.0.1 -p tcp --dport $from -j REDIRECT --to $to sudo iptables -t nat -$append_or_delete OUTPUT -s 127.0.0.1 -p tcp --dport $from -j REDIRECT --to $to done exit 0;
Проброс и перенаправление портов в iptables
Чаще всего проброс трафика используется, если мы находимся в локальной сети и от внешнего мира отделены шлюзом. Для того, чтобы открыть доступ для локальных служб (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.
iptables -t nat -A PREROUTING —dst $EXT_IP -p tcp —dport $SRV_PORT -j DNAT —to-destination $LAN_IP
Рассмотрим пример
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, поэтому в неё надо добавить разрешающее правило. Оно может выглядеть, например, так:
iptables -I FORWARD 1 -i eth0 -o eth1 -d $LAN_IP -p tcp -m tcp —dport $SRV_PORT -j ACCEPT
Рассмотрим пример
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.
iptables -t nat -A POSTROUTING —dst $LAN_IP -p tcp —dport $SRV_PORT -j SNAT —to-source $INT_IP
Рассмотрим пример
iptables -t nat -A POSTROUTING —dst 192.168.1.50 -p tcp —dport 80 -j SNAT —to-source 192.168.1.1
Если пакет предназначен веб-серверу, то обратный адрес клиента заменяется на внутренний адрес шлюза.
Этим мы гарантируем, что ответный пакет пойдёт через шлюз.
Надо дополнительно отметить, что это правило важно только для внутренних клиентов. Ответ внешним клиентам пойдёт через шлюз в любом случае.
Но, пока что, для нормальной работы этого недостаточно. Предположим, что в качестве клиента выступает сам шлюз.
В соответствии с нашими предыдущими правилами он будет гонять трафик от себя к себе и представлять исходящие пакеты транзитными.
iptables -t nat -A OUTPUT —dst $EXT_IP -p tcp —dport $SRV_PORT -j DNAT —to-destination $LAN_IP
Рассмотрим пример
iptables -t nat -A OUTPUT —dst 1.2.3.4 -p tcp —dport 80 -j DNAT —to-destination 192.168.1.50
Теперь для удобства запилим скрипт, чтобы не прописывать правила каждый раз вручную.
#!/bin/bash
EXT_IP=«xxx.xxx.xxx.xxx» # внешний, реальный IP-адрес шлюза;
INT_IP=«xxx.xxx.xxx.xxx» # См. выше.
EXT_IF=eth0 # Внешний и внутренний интерфейсы.
INT_IF=eth1 # Для шлюза они вряд ли изменятся, поэтому можно прописать вручную.
LAN_IP=$1 # Локальный адрес сервера передаём скрипту первым параметром,
SRV_PORT=$2 # а тип сервера, в смысле какой порт (или набор портов) открывать — вторым
# Здесь желательно сделать проверку ввода данных, потому что операции достаточно серьёзные.
iptables -t nat -A PREROUTING —dst $EXT_IP -p tcp —dport $SRV_PORT -j DNAT —to-destination $LAN_IP
iptables -t nat -A POSTROUTING —dst $LAN_IP -p tcp —dport $SRV_PORT -j SNAT —to-source $INT_IP
iptables -t nat -A OUTPUT —dst $EXT_IP -p tcp —dport $SRV_PORT -j DNAT —to-destination $LAN_IP
iptables -I FORWARD 1 -i $EXT_IF -o $INT_IF -d $LAN_IP -p tcp -m tcp —dport $SRV_PORT -j ACCEPT
Теперь, чтобы обеспечить доступ извне к локальному FTP по адресу 192.168.1.52, достаточно набрать в консоли от имени супер-пользователя:
Перенаправление портов
Перенаправление портов нужно в том случае, если мы хотим «замаскировать» внутреннюю службу, обеспечив к ней доступ извне не по стандартному, а совсем по другому порту.
Пусть $FAKE_PORT — обманный порт на внешнем интерфейсе шлюза, подключившись к которому мы должны попасть на адрес $LAN_IP и порт $SRV_PORT .
Набор правил для iptables будет отличаться несущественно, поэтому приведу сразу пример итогового скрипта для ленивых.
#!/bin/bash
EXT_IP=«xxx.xxx.xxx.xxx» # внешний, реальный IP-адрес шлюза;
INT_IP=«xxx.xxx.xxx.xxx» # См. выше.
EXT_IF=eth0 # Внешний и внутренний интерфейсы.
INT_IF=eth1 # Для шлюза они вряд ли изменятся, поэтому можно прописать вручную.
FAKE_PORT=$1 # Вначале передаём скрипту «неправильный» порт на внешнем интерфейсе,
LAN_IP=$2 # затем — локальный адрес сервера
SRV_PORT=$3 # и в конце — реальный порт для подключения к серверу
# Здесь опять надо сделать проверку ввода данных, потому что операции всё ещё серьёзные.
iptables -t nat -A PREROUTING -d $EXT_IP -p tcp -m tcp —dport $FAKE_PORT -j DNAT —to-destination $LAN_IP:$SRV_PORT
iptables -t nat -A POSTROUTING -d $LAN_IP -p tcp -m tcp —dport $SRV_PORT -j SNAT —to-source $INT_IP
iptables -t nat -A OUTPUT -d $EXT_IP -p tcp -m tcp —dport $SRV_PORT -j DNAT —to-destination $LAN_IP
iptables -I FORWARD 1 -i $EXT_IF -o $INT_IF -d $LAN_IP -p tcp -m tcp —dport $SRV_PORT -j ACCEPT
Forwarding a Localhost:Port to an ExternalIP:NewPort
We have got an application running on our linux server. From that application, when we try and access localhost(127.0.0.1):localport, it should be forwarded to an external IP. Users will only try and access the localhost on a certain port which will be automatically forwarded. I read up on iptables nat table, but PREROUTING and POSTROUTING will not be applicable if am right since I am accessing a port on localhost from localhost itself which doesn’t touch the network interface at all. Wondering OUTPUT table might be useful but when I tried some combinations, it didn’t work. Am I using the right thing or is it not possible at all to do it? Can someone point me in the right direction?
Local traffic goes over the loopback interface (usually lo ). You should be able to set up iptables rules over it.
user40524 — Am wondering why did you change the style and removed «Thank you» as well? Is there any specific reason that it has to be the style you have edited as?
2 Answers 2
I have figured to do this myself.
2 rules and a flag should be set to achieve this.
Example used here is for telnet localhost XXXX , should forward packets to Ext.er.nal.IP:YYYY .
sysctl -w net.ipv4.conf.all.route_localnet=1
This flag unfortunately exists only on quite latest Linux kernels and not available on an old kernel (there isn’t any alternate flag as well in the old kernel). Am quite not sure which exact kernel is the flag available from though. I believe it is available on kernel versions 3.XX.
This flag is to consider the loopback addresses as a proper source or destination address.
iptables -t nat -A OUTPUT -p tcp —dport XXXX -j DNAT —to-destination Ext.er.nal.IP:YYYY
The above command will alter the packets that is to localhost:XXXX with the destination IP as Ext.er.nal.IP:YYYY
iptables -t nat -A POSTROUTING -j MASQUERADE
The command will alter the source IP as the public ip of your machine.
You could make your rules a bit more strict by adding appropriate source and destination IP and interfaces using -s , -d , -i and -o . See man iptables .
Thanks to John WH Smith and Wurtel. Suggestions were very helpful.