Шлюз на базе Linux iptables

Поменял механизм подключения к интернету: сменил pppoe на нормальную статику. В качестве домашнего маршрутизатора у меня компьютер, являющийся помимо этого ещё файловой помойкой и небольшим веб-сервером, где собственно и крутится этот сайт.

Меняя настройки iptables под новое соединение, заметил необходимость систематизировать их и привести порядок.

Существует 3 основных таблицы:

  • nat — управление сетевой трансляцией;
  • mangle — операции с трафиком;
  • filter — фильтрация трафика.

Есть ещё raw и security, но они имеют узкую специализацию и редко используются.

Существует 3 базовых цепочки в таблице filter, которыми мы будем орудовать:

  • INPUT — входящий трафик в сервер;
  • OUTPUT — исходящий трафик с сервера;
  • FORWARD — транзитный трафик (пришёл с другого хоста в адрес третьего хоста, т.е. использующий данный сервер в качестве шлюза).

Static IP

Дано:

  • Локальная сеть: 192.168.1.0/24
  • Внутренний IP: 192.168.1.1
  • Внешний IP: 212.122.1.1
  • Внутренний интерфейс: eth0
  • Внешний интерфейс: eth1

В большинстве случаев существующие настройки хранятся в следующем файле — /var/lib/iptables/rules-save

Для начала несколько полезных скриптиков.

Набор разрешающих политик — rules-accept:

*nat
:PREROUTING ACCEPT
:INPUT ACCEPT
:OUTPUT ACCEPT
:POSTROUTING ACCEPT
COMMIT
*mangle
:PREROUTING ACCEPT
:INPUT ACCEPT
:FORWARD ACCEPT
:OUTPUT ACCEPT
:POSTROUTING ACCEPT
COMMIT
*filter
:INPUT ACCEPT
:FORWARD ACCEPT
:OUTPUT ACCEPT
COMMIT

Загружаются они командой:

iptables-restore < rules-accept

Очистка всех настроек iptables — flush.sh:

#!/bin/bash
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X

Сброс настроек и загрузка сохранённого конфига — reload.sh:

#!/bin/bash
flush.sh
iptables-restore /var/lib/iptables/rules-save

Вывод всей информации о политиках iptables — status.sh:

#!/bin/bash
echo "Table: filter"
echo
iptables -v -t filter -L --line-numbers
echo
echo "Table: mangle"
echo
iptables -v -t mangle -L --line-numbers
echo
echo "Table: nat"
echo
iptables -v -t nat -L --line-numbers

Итак, для того, чтобы заработал шлюз достаточно отдать следующую команду (этот же механизм будет регулировать с какого IP мы будем выходить во внешний мир для заданной подсети, если провайдер предоставляет несколько внешних IP-адресов):

iptables -t nat -A POSTROUTING -o eth1 -j SNAT --to-source 212.122.1.1 # NAT через внешний IP

Работать будет, но всё всем будет разрешать, как для внутренних пользователей, так и для обитателей внешнего мира. Это не наш метод.

Первым делом вносим базовые ограничения для чёткого разделения внешнего и внутреннего мира:

iptables -A FORWARD ! -i eth1 -j ACCEPT # пропускать трафик пришедший не из внешнего мира
iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # разрешить все пакеты, идущие в ответ на наши запросы
iptables -P FORWARD DROP # весь неразрешённый транзитный трафик блокировать

Далее вносим ограничения с учётом состояния пакетов и направления трафика:

iptables -A INPUT ! -i eth1 -j ACCEPT # разрешить любой трафик из LAN
iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # разрешить все пакеты, идущие в ответ на наши запросы
iptables -P INPUT DROP # весь неразрешённый входящий трафик блокировать

Так уже трафик будет ходить из LAN в WAN без препятствий, а вот из WAN в LAN никто подключиться не сможет. Так например никто не сможет рулить уже моим медиа-центром и торренто-качалкой, а так же залазить на мою файло-помойку, которые стоят на компе-маршрутизаторе.

Учитывая схему работы netfilter выделим общие правила для устранения проблемных пакетов мы сделаем на максимально раннюю стадию, общую для всего трафика — Prerouting/mangle (данные политики лучше ставить с максимальным приоритетом, поэтому вместо параметра append -A мы воспользуемся направленной вставкой с порядковым номером):

iptables -t mangle -I PREROUTING 1 -m conntrack --ctstate INVALID -j DROP # пакеты со статусом INVALID
iptables -t mangle -I PREROUTING 2 -p tcp ! --syn -m conntrack --ctstate NEW -j DROP # пакеты со статусом NEW без SYN пакетов
iptables -t mangle -I PREROUTING 3 -p tcp --tcp-flags ALL ALL -j DROP # пакеты XMAS
iptables -t mangle -I PREROUTING 4 -p tcp --tcp-flags ALL FIN -j DROP # пакеты FIN
iptables -t mangle -I PREROUTING 5 -p tcp --tcp-flags ALL NONE -j DROP # неправильные NULL пакеты

Так же у меня на компьютере стоит SSH, HTTP и HTTPS сервисы:

iptables -A INPUT -i eth1 -p tcp -m tcp --dport 22 -j ACCEPT
iptables -A INPUT -i eth1 -p tcp -m tcp --dport 80 -j ACCEPT
iptables -A INPUT -i eth1 -p tcp -m tcp --dport 443 -j ACCEPT

Помимо прочего в локалке есть компьютер 192.168.1.2 с Windows, к которому я подключаюсь по RDP:

iptables -t nat -A PREROUTING -i eth1 -p tcp -m tcp --dport 3389 -j DNAT --to-destination 192.168.1.2:3389 # настраиваем NAT для Windows RDP
iptables -A FORWARD -d 192.168.1.2 -i eth1 -p tcp -m tcp --dport 3389 -j ACCEPT # разрешаем соответствующий трафик

В завершении картины можно опционально настроить ответы на попытки подключения по неразрешённым портам и протоколам (своеобразная маскировка firewall от обнаружения его типичного поведения):

iptables -A INPUT -p tcp -m recent --set --name TCP-PORTSCAN -j REJECT --reject-with tcp-rst # на TCP ответим пакетом RST
iptables -A INPUT -p udp -m recent --set --name UDP-PORTSCAN -j REJECT --reject-with icmp-port-unreachable # на UDP ответим пакетом с сообщением ICMP port unreachable
iptables -A INPUT -j REJECT --reject-with icmp-proto-unreachable # остальным ответим сообщением ICMP protocol unreachable

Итого все настройки можно запилить в 1 простенький скриптик с удобными переменными для настройки под себя — iptables-nat.sh.

Развёрнутую инструкцию можно найти на Викиучебнике.

P.S.: маленькая фенечка для любителей systemd (и networkd в нагрузку) — в файл настройки сети в раздел [Network] надо добавить IPForward=1 иначе networkd заблокирует forwarding сколько бы вы не мучали ядро ОС.

Dynamic IP

В предыдущей статье рассматривался вариант с подключением по статике. Там всё просто и красиво, но что делать если есть динамический IP да ещё и с туннелем PPPoE?

На самом деле я недавно находился именно в таких условиях.

Чтобы избавиться от жёсткой привязки к IP-адресу внешнего интерфейса, самым простым вариантом будет использование MASQUERADE вместо SNAT:

iptables -t nat -D POSTROUTING -s 192.168.1.0/24 -o eth1 -j SNAT --to-source 212.122.1.1
iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE

Данный вариант работает чуточку медленнее, но скорее всего вы не заметите разницу. Тут остаётся только привязка к внешнему интерфейсу, не более.

А вот проблему PPPoE я решал наверняка в два этапа. Для начала настроим связку демона ppp и iptables.

# vi /etc/ppp/pppoe.conf
CLAMPMSS=1452
# iptables -t mangle -I FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

После этого переподключить соединение PPPoE. Все пакеты c данными свыше 1452 (40 на служебные данные TCP, 8 съедает PPPoE) будут разбиваться силами маршрутизатора. Не очень эффективное решение, но проблему решает.

Вторым этапом было по DHCP раздавать в локальной сети размер пакета. В качестве DHCP-сервера у меня dnsmasq:

# vi /etc/dnsmasq.d/main.conf
dhcp-option=eth0,26,1492

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

Применение правил на загрузке

apt-get install iptables-persistent

Полезные материалы