Простой шлюз в сеть Интернет на nftables в ОС Astra Linux Special Edition
Вместо предисловия
Новая система фильтрации пакетов, фреймворк nftables, работает, начиная с версии Debian 10 Buster. ОС Astra Linux Special Edition (ALSE) основана на Debian и его пакетной базе, поддерживает nftables, начиная с версии 1.7.4.
Для помощи в миграции с Netfilter к nftables я настроил тестовый стенд, который реализует основные базовые задачи для SOHO (Small Office/Home Office) или небольшого предприятия. Эту конфигурацию можно взять за основу при создании своей или использовать все, как есть.
На Хабре уже есть прекрасные статьи, которые в той или иной мере рассказывают про nftables, а также про работу с ним:
Поэтому при работе над этой статьей я подразумевал, что читатель уже с ними знаком и умеет работать с nftables, хотя бы на базовом уровне.
Предлагаемая мной конфигурация решает следующие задачи:
Трансляция сетевых адресов (NAT) и поддержка протоколов, которые она ломает. Будут использованы оба варианта NAT: SNAT и DNATSNAT (Source NAT) — для трансляции частных IP адресов локальной сети во внешние IP адреса. DNAT (Destination NAT) — для обратного преобразования: необходимое условие работы сервисов в DMZ
Демилитаризованная зона (DMZ) с сервисами для интернет, например, web-сайт
Межсетевой экран (FW, файервол). Помним, что NAT — это не файервол =)
Ограничение скорости трафика по IP клиента локальной сети для любителей покачать котиков
Ограничение скорости логирования отброшенных пакетов дисковая подсистема тоже не резиновая
И еще несколько бонусов, но об этом чуть позже.
Схема стенда и адресный план
Схема стенда предполагает, что имеется шлюз (GW) с функцией межсетевого экрана (FW), который подключен к провайдеру (ISP) по той или иной технологии.
И есть две зоны:
LAN — локальная сеть предприятия с компьютерами клиентов
DMZ — демилитаризованная зона, в которой находятся общедоступные сервисы
Путь пакета/фрейма в nftables
Основная схема для понимания логики работы правил файервола.
Предварительная настройка GW
Для того, чтобы наш сервер работал как шлюз и передавал (форвардил) пакеты между интерфейсами, необходимо включить эту функцию и добавить в автозагрузку:
sudo bash -c 'echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf'
sudo sysctl -p
Загружаем модули-помощники для файервола и также добавляем их в автозагрузку:
sudo modprobe -v nf_nat_ftp
sudo modprobe -v nf_nat_sip
sudo modprobe -v nf_nat_tftp
sudo bash -c 'echo "nf_nat_ftp" >> /etc/modules'
sudo bash -c 'echo "nf_nat_sip" >> /etc/modules'
sudo bash -c 'echo "nf_nat_tftp" >> /etc/modules'
Пишем файервол
/etc/firewall.nft
#!/usr/sbin/nft -f
flush ruleset
define LAN_DEV = eth0
define WAN_DEV = eth1
define DMZ_DEV = eth2
define DMZ_WEB_SERVER = 192.168.101.2
# IP адреса с которых можно зайти по SSH на шлюз
define ADMIN_SSH_IP = {
10.0.0.0
}
# Для кого ограничиваем скорость
define LIMIT_SPEED = {
192.168.100.2,
192.168.100.22,
192.168.100.222
}
# Определяем таблицу и цепочки
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
# Разрешаем все соединения на петлевом интерфейсе
iif "lo" accept
# Разрешаем уже установленные соединения
ct state vmap { established : accept, related : accept, invalid : drop }
# Разрешаем SSH для администратора
tcp dport ssh ip saddr $ADMIN_SSH_IP accept
# Логируем отброшенные пакеты
limit rate over 1/second drop
counter log prefix "Drop packet in INPUT: "
}
chain forward {
type filter hook forward priority filter; policy drop;
# Разрешаем все соединения на петлевом интерфейсе
iif "lo" accept
# Вычисляем пакеты с предполагаемой IP Security Option
meta nfproto ipv4 @nh,4,4 6 iif $LAN_DEV oif $WAN_DEV counter jump probably_ip_sec_opt
# Разрешаем уже установленные соединения
ct state vmap { established : accept, related : accept, invalid : drop }
# Разрешаем подключения с LAN
iif $LAN_DEV accept
# Разрешаем подключения к DNAT сервисам
ip daddr $DMZ_WEB_SERVER tcp dport { 80, 443 } oif $DMZ_DEV accept
# Логируем отброшенные пакеты
limit rate over 1/second drop
counter log prefix "Drop packet in FORWARD: "
}
chain output {
type filter hook output priority filter; policy drop;
# Разрешаем все исходящие соединения
oif "lo" accept
# Вычисляем пакеты с предполагаемой IP Security Option
meta nfproto ipv4 @nh,4,4 6 oif $WAN_DEV counter jump probably_ip_sec_opt
# Разрешаем уже установленные соединения
ct state vmap { established : accept, related : accept, invalid : drop }
# Разрешаем Интернет на шлюзе
tcp dport { 80, 443 } accept
# Разрешаем DNS
meta l4proto { tcp, udp } th dport 53 accept
# Разрешаем исходящий ICMP request (ping)
icmp type echo-request accept
# Разрешаем NTP
udp dport 123 accept
# Логируем отброшенные пакеты
limit rate over 1/second drop
counter log prefix "Drop packet in OUTPUT: "
}
chain probably_ip_sec_opt {
meta nfproto ipv4 @nh,160,8 130 counter drop
}
}
# Настройка NAT
table ip nat {
ct helper ftp-standard {
type "ftp" protocol tcp;
}
ct helper sip-5060 {
type "sip" protocol udp;
}
ct helper tftp-69 {
type "tftp" protocol udp;
}
chain prerouting {
type nat hook prerouting priority filter
ct state new ct helper set ip protocol . th dport map { \
udp . 69 : "tftp-69", \
udp . 5060 : "sip-5060", \
tcp . 21 : "ftp-standard" }
# Проброс HTTP/HTTPS на веб сервер в DMZ сегменте
iif $WAN_DEV tcp dport { 80, 443 } dnat to $DMZ_WEB_SERVER
}
chain postrouting {
type nat hook postrouting priority filter
# Применяем NAT для всех исходящих соединений через uplink
oif $WAN_DEV masquerade
}
}
# Ограничение полосы пропускания для IP адресов из LIMIT_SPEED
table inet filter {
limit lim_1mbps { rate over 125 kbytes/second }
limit lim_10mbps { rate over 1250 kbytes/second }
chain policer {
type filter hook forward priority -50
ip saddr $LIMIT_SPEED limit name "lim_1mbps" counter drop
ip daddr $LIMIT_SPEED limit name "lim_1mbps" counter drop
}
}
Пробежимся по основным правилам
Есть две парадигмы фильтрации трафика: все запретить и разрешать по мере необходимости, и наоборот.У нас первый вариант: запрещаем все, а потом смотрим логи, если где-то что-то не хватает. Поэтому в фильтрующих цепочках политика по умолчанию Drop:
table inet filter { chain input { type filter hook input priority filter; policy drop; ... } chain forward { type filter hook forward priority filter; policy drop; ... } chain output { type filter hook output priority filter; policy drop; ... } }
Также есть два типа FW: statefull (с сохранением состояния flow) и stateless (без сохранения). У каждого из них есть свои плюсы и минусы. Будем использовать statefull, т.к. с ним некоторые вещи делать проще:
table inet filter { chain input { ... # Разрешаем уже установленные соединения ct state vmap { established : accept, related : accept, invalid : drop } ... } chain forward { ... # Разрешаем уже установленные соединения ct state vmap { established : accept, related : accept, invalid : drop } ... } chain output { ... # Разрешаем уже установленные соединения ct state vmap { established : accept, related : accept, invalid : drop } ... } }
Для того, чтобы логи по отброшенным пакетам не забивали диски, ограничиваем их скорость (один отброшенный пакет в секунду):
table inet filter { chain input { ... # Логируем отброшенные пакеты limit rate over 1/second drop counter log prefix "Drop packet in INPUT: " } chain forward { ... # Логируем отброшенные пакеты limit rate over 1/second drop counter log prefix "Drop packet in FORWARD: " } chain output { ... # Логируем отброшенные пакеты limit rate over 1/second drop counter log prefix "Drop packet in OUTPUT: " } }
Чтобы не поломать протоколы, которые плохо дружат с NAT, нужны отдельные модули ядра, которые умеют анализировать их на лету.Затем нужно определить хелпер для протокола и связать его с правилом состояния conntrack. Важно, чтобы связывание (ct helper set …) происходило позже (priority filter) работы conntrack.После того как хелпер отработает и промаркирует пакеты статусом related, они станут проходить в правилах «ct state vmap …»
# Настройка NAT table ip nat { ct helper ftp-standard { type "ftp" protocol tcp; } ct helper sip-5060 { type "sip" protocol udp; } ct helper tftp-69 { type "tftp" protocol udp; } chain prerouting { type nat hook prerouting priority filter # Новые подключения по протоколам tftp/ftp/sip ct state new ct helper set ip protocol . th dport map { \ udp . 69 : "tftp-69", \ udp . 5060 : "sip-5060", \ tcp . 21 : "ftp-standard" } ... } ... }
Ограничиваем скорость компьютерам локальной сети (LAN). Здесь вроде все просто, но есть нюанс. Важно, чтобы приоритет цепочки такой, чтобы она выполнилась раньше других в «filter hook forward»:
# Ограничение полосы пропускания для IP адресов из LIMIT_SPEED table inet filter { limit lim_1mbps { rate over 125 kbytes/second } limit lim_10mbps { rate over 1250 kbytes/second } chain policer { type filter hook forward priority -50 ip saddr $LIMIT_SPEED limit name "lim_1mbps" counter drop ip daddr $LIMIT_SPEED limit name "lim_1mbps" counter drop } }
NAT (SNAT, трансляция частных IP адресов локальной сети во внешние) и проброс портов (DNAT — для публикации внутренних ресурсов сети в Интернет) для сервера в DMZ выглядят плюс-минус «как везде» — стандартно:
# Настройка NAT table ip nat { ... chain prerouting { type nat hook prerouting priority filter ... # Проброс HTTP/HTTPS на веб сервер в DMZ сегменте iif $WAN_DEV tcp dport { 80, 443 } dnat to $DMZ_WEB_SERVER } chain postrouting { type nat hook postrouting priority filter # Применяем NAT для всех исходящих соединений через uplink oif $WAN_DEV masquerade } }
Разбор IP Security Option и связь его с мандатным разграничением доступа (МРД) Astra Linux оставим на десерт.
Запускаем «Пепелац» и добавляем в автозагрузку
sudo bash -c "cat << EOF > /etc/systemd/system/nftables.service
[Unit]
Description=nftables firewall
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/nft -f /etc/firewall.nft
ExecStop=/usr/sbin/nft flush ruleset
ExecReload=/usr/sbin/nft -f /etc/firewall.nft
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF"
Чтобы работал nohup в ОС Astra Linux:
sudo sed -i 's/#KillUserProcesses=yes/KillUserProcesses=no/' /etc/systemd/logind.conf
sudo systemctl restart systemd-logind
Регистрируем systemd юнит, включаем, запускаем. Проверяем:
sudo systemctl daemon-reload
sudo systemctl enable nftables
Оставляем лазейку от непредвиденного случая, чтоб через 120 сек очистились fw правила:
nohup sudo sh -c "sleep 120 && systemctl stop nftables" &>/dev/null &
sudo systemctl start nftables
sudo systemctl status nftables
sudo nft -ay list ruleset
Если все ок, то: sudo killall sleep
Тестируем
Ставим пакеты для тестирования
PC1, PC2: sudo apt install iperf ftp curl
GW: sudo apt install conntrack
Test server: sudo apt install iperf curl python3-pytfpdlib python3-pip
DMZ server: sudo apt install python3-pytfpdlib python3-pip
NAT (Трансляция сетевых адресов)
PC1
ping -n -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=101 time=129 ms
--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 129.365/129.365/129.365/0.000 ms
GW
sudo conntrack -E -n -o timestamp
[1722911920.026038] [NEW] icmp 1 30 src=192.168.100.2 dst=8.8.8.8 type=8 code=0 id=1229 [UNREPLIED] src=8.8.8.8 dst=10.0.0.1 type=0 code=0 id=1229
[1722911920.154182] [UPDATE] icmp 1 30 src=192.168.100.2 dst=8.8.8.8 type=8 code=0 id=1229 src=8.8.8.8 dst=10.0.0.1 type=0 code=0 id=1229
^C
conntrack v1.4.5 (conntrack-tools): 2 flow events have been shown.
Как видно, преобразование работает, так как dst=10.0.0.1 —это IP-адрес GW на аплинке. Ответные пакеты идут на него.
Ограничение скорости
Для тестирования скорости будем использовать iperf. Согласно правилам FW, хост PC1 (192.168.100.2) ограничен скоростью 1Mbps.
Проверим это на практике:
Test server
iperf -u -s -i 3 -e
PC1
iperf -u -c 10.0.0.0 -t 0 -b 10M -i 3
В статистике iperf на Test-сервере видим, что все работает:
Test server
iperf -u -s -i 3 -e
------------------------------------------------------------
Server listening on UDP port 5001 with pid 954
Receiving 1470 byte datagrams
UDP buffer size: 208 KByte (default)
------------------------------------------------------------
[ 3] local 10.0.0.0 port 5001 connected with 10.0.0.1 port 48873
[ ID] Interval Transfer Bandwidth Jitter Lost/Total Latency avg/min/max/stdev PPS
[ 3] 0.00-3.00 sec 490 KBytes 1.34 Mbits/sec 0.182 ms 2337/ 2678 (87%) 100.061/99.671/100.611/ 5.438 ms 114 pps
[ 3] 3.00-6.00 sec 369 KBytes 1.01 Mbits/sec 0.139 ms 2424/ 2681 (90%) 100.470/99.682/100.840/ 0.181 ms 85 pps
[ 3] 6.00-9.00 sec 368 KBytes 1.00 Mbits/sec 0.126 ms 2416/ 2672 (90%) 100.489/99.588/100.953/ 0.190 ms 85 pps
[ 3] 9.00-12.00 sec 368 KBytes 1.00 Mbits/sec 0.170 ms 2415/ 2671 (90%) 100.463/99.625/100.790/ 0.189 ms 85 pps
[ 3] 12.00-15.00 sec 369 KBytes 1.01 Mbits/sec 0.108 ms 2425/ 2682 (90%) 100.467/99.642/101.690/ 0.192 ms 85 pps
[ 3] 15.00-18.00 sec 368 KBytes 1.00 Mbits/sec 0.136 ms 2415/ 2671 (90%) 100.477/99.616/100.877/ 0.187 ms 85 pps
[ 3] 18.00-21.00 sec 368 KBytes 1.00 Mbits/sec 0.149 ms 2415/ 2671 (90%) 100.469/99.642/100.592/ 0.181 ms 85 pps
[ 3] 21.00-24.00 sec 369 KBytes 1.01 Mbits/sec 0.163 ms 2425/ 2682 (90%) 100.468/99.651/100.741/ 0.188 ms 85 pps
Теперь также проверим PC2 и убедимся, что скорость не ограничена. Смотрим статистику:
Test server
iperf -u -s -i 3 -e
------------------------------------------------------------
Server listening on UDP port 5001 with pid 958
Receiving 1470 byte datagrams
UDP buffer size: 208 KByte (default)
------------------------------------------------------------
[ 3] local 10.0.0.0 port 5001 connected with 10.0.0.1 port 46685
[ ID] Interval Transfer Bandwidth Jitter Lost/Total Latency avg/min/max/stdev PPS
[ 3] 0.00-3.00 sec 3.75 MBytes 10.5 Mbits/sec 0.075 ms 0/ 2676 (0%) 368.479/368.173/368.966/ 7.127 ms 892 pps
[ 3] 3.00-6.00 sec 3.75 MBytes 10.5 Mbits/sec 0.079 ms 0/ 2675 (0%) 368.619/368.183/369.051/ 0.131 ms 892 pps
[ 3] 6.00-9.00 sec 3.75 MBytes 10.5 Mbits/sec 0.078 ms 0/ 2675 (0%) 368.600/368.177/369.430/ 0.135 ms 892 pps
[ 3] 0.00-11.19 sec 14.0 MBytes 10.5 Mbits/sec 0.095 ms 0/ 9980 (0%) 368.477/368.173/369.430/ 3.692 ms 891 pps
Чтобы проверить ограничения скорости в обратную сторону, необходимо сделать порт-форвардинг (DNAT) для UDP порта 5001.
Добавим пару правил в цепочки:
GW
sudo nft insert rule inet filter forward handle 22 ip daddr 192.168.100.2 udp dport 5001 oifname eth0 accept
sudo nft add rule ip nat prerouting iif eth1 udp dport 5001 dnat to 192.168.100.2
Теперь можно тестировать:
PC1
iperf -u -s -i 3 -e
Test server
iperf -u -c 10.0.0.1 -t 0 -b 10M -i 3
Смотрим статистику:
PC1
iperf -u -s -i 3 -e
------------------------------------------------------------
Server listening on UDP port 5001 with pid 1401
Receiving 1470 byte datagrams
UDP buffer size: 208 KByte (default)
------------------------------------------------------------
[ 3] local 192.168.100.2 port 5001 connected with 10.0.0.0 port 36544
[ ID] Interval Transfer Bandwidth Jitter Lost/Total Latency avg/min/max/stdev PPS
[ 3] 0.00-3.00 sec 490 KBytes 1.34 Mbits/sec 0.112 ms 2336/ 2677 (87%) -98.991/-99.373/-98.426/ 5.389 ms 114 pps
[ 3] 3.00-6.00 sec 369 KBytes 1.01 Mbits/sec 0.109 ms 2425/ 2682 (90%) -99.281/-99.242/-98.485/ 0.170 ms 85 pps
[ 3] 6.00-9.00 sec 368 KBytes 1.00 Mbits/sec 0.122 ms 2416/ 2672 (90%) -99.261/-99.292/-98.416/ 0.160 ms 85 pps
[ 3] 9.00-12.00 sec 368 KBytes 1.00 Mbits/sec 0.172 ms 2415/ 2671 (90%) -99.253/-99.368/-98.388/ 0.169 ms 85 pps
[ 3] 12.00-15.00 sec 369 KBytes 1.01 Mbits/sec 0.187 ms 2424/ 2681 (90%) -99.266/-99.368/-98.357/ 0.191 ms 85 pps
[ 3] 0.00-16.29 sec 2.04 MBytes 1.05 Mbits/sec 15.807 ms 12848/14304 (90%) -98.730/-99.373/152.108/ 7.077 ms 89 pps
1 Mbps работает.
Удаляем пару созданных правил. Просто делаем рестарт FW, чтобы handle не искать:
GW
sudo systemctl stop nftables && sudo systemctl start nftables
DMZ (Демилитаризованная зона)
Создаем тестовый «сервис»:
Server
> test_page
sudo python3 -u -m http.server -d . 80
PC1
curl -I http://192.168.101.2/test_page
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.7.3
Date: Tue, 06 Aug 2024 04:04:28 GMT
Content-type: application/octet-stream
Content-Length: 0
Last-Modified: Tue, 06 Aug 2024 03:57:24 GMT
Test server
curl -I http://10.0.0.1/test_page
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.7.3
Date: Tue, 06 Aug 2024 04:05:49 GMT
Content-type: application/octet-stream
Content-Length: 0
Last-Modified: Tue, 06 Aug 2024 03:57:24 GMT
Важный момент. Сервера, которые сидят в DMZ, в локалку ходить не могут. Но локалка имеет доступ в DMZ.
FTP
Теперь настал черед проверить работу хелперов. Проверим только ftp, чтобы сильно не утомлять читателей.
Test server
sudo python3 -m pyftpdlib -V -p 21
[I 2024-08-06 07:20:04] >>> starting FTP server on 0.0.0.0:21, pid=1299 <<<
[I 2024-08-06 07:20:04] concurrency model: async
[I 2024-08-06 07:20:04] masquerade (NAT) address: None
[I 2024-08-06 07:20:04] passive ports: None
[I 2024-08-06 07:20:15] 10.0.0.1:53562-[] FTP session opened (connect)
[I 2024-08-06 07:20:23] 10.0.0.1:53562-[anonymous] USER 'anonymous' logged in.
^C[I 2024-08-06 07:20:50] received interrupt signal
[I 2024-08-06 07:20:50] >>> shutting down FTP server (2 active socket fds) <<<
[I 2024-08-06 07:20:50] 10.0.0.1:53562-[anonymous] FTP session closed (disconnect).
PC1
ftp 10.0.0.0
Connected to 10.0.0.0.
220 pyftpdlib 1.5.4 ready.
Name (10.0.0.0:astra): anonymous
331 Username ok, send password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
200 Active data connection established.
125 Data connection already open. Transfer starting.
-rw------- 1 astra astra 8 May 20 07:22 .bash_history
-rw-r--r-- 1 astra astra 220 Nov 17 2023 .bash_logout
-rw-r--r-- 1 astra astra 3526 Nov 17 2023 .bashrc
-rw-r--r-- 1 astra astra 807 Nov 17 2023 .profile
226 Transfer complete.
ftp> pass
Passive mode on.
ftp> ls
227 Entering passive mode (10,0,0,0,206,119).
125 Data connection already open. Transfer starting.
-rw------- 1 astra astra 8 May 20 07:22 .bash_history
-rw-r--r-- 1 astra astra 220 Nov 17 2023 .bash_logout
-rw-r--r-- 1 astra astra 3526 Nov 17 2023 .bashrc
-rw-r--r-- 1 astra astra 807 Nov 17 2023 .profile
226 Transfer complete.
ftp> quit
Оба режима протокола ftp работают.
Бонусы
Вариант ограничения скорости через tcconfig
Из nftables убираем функцию ограничения скорости. Для ограничения будем использовать tcconfig. Tcconfig — это Python обертка вокруг довольно известной утилиты tc для управлением QoS.
firewall.nft
Закомментируем полисер в /etc/firewall.nft:
# Ограничение полосы пропускания для IP адресов из LIMIT_SPEED
#table inet filter {
# limit lim_1mbps { rate over 125 kbytes/second }
# limit lim_10mbps { rate over 1250 kbytes/second }
#
# chain policer {
# type filter hook forward priority -50
# ip saddr $LIMIT_SPEED limit name "lim_1mbps" counter drop
# ip daddr $LIMIT_SPEED limit name "lim_1mbps" counter drop
# }
#}
или просто удалить в runtime для проверки tcconfig:
sudo nft delete chain inet filter policer
Ставим tcconfig
sudo pip3 install tcconfig
Ограничиваем скорость на GW
sudo tcset eth0 --rate 100Kbps --delay 100ms --loss 0.1% --direction outgoing --dst-network 192.168.100.2
sudo tcset eth0 --rate 100Kbps --delay 100ms --loss 0.1% --direction incoming --src-network 192.168.100.2
tcshow eth0
{
"eth0": {
"outgoing": {
"dst_network=192.168.100.2/32, protocol=ip": {
"filter_id": "800::800",
"delay": "100ms",
"loss": "0.1%",
"rate": "100Kbps"
}
},
"incoming": {
"src_network=192.168.100.2/32, protocol=ip": {
"filter_id": "800::800",
"delay": "100ms",
"loss": "0.1%",
"rate": "100Kbps"
}
}
}
}
P.S.
Чтобы удалить все полисеры с интерфейса:
sudo tcdel eth0 --all
[INFO] delete eth0 qdisc
[INFO] delete eth0 ingress qdisc
[INFO] delete eth0 ifb device (ifb6682)
Проверим работу tc. Сгенерируем трафик при помощи iperf, как было выше:
Test server
iperf -u -s -i 3 -e
PC1
iperf -u -c 10.0.0.0 -t 0 -b 10M -i 3
Скорость режется до сотки:
Test server
iperf -u -s -i 3 -e
------------------------------------------------------------
Server listening on UDP port 5001 with pid 1314
Receiving 1470 byte datagrams
UDP buffer size: 208 KByte (default)
------------------------------------------------------------
[ 3] local 10.0.0.0 port 5001 connected with 10.0.0.1 port 36050
[ ID] Interval Transfer Bandwidth Jitter Lost/Total Latency avg/min/max/stdev PPS
[ 3] 0.00-3.00 sec 48.8 KBytes 133 Kbits/sec 96.640 ms 0/ 34 (0%) 1387.311/199.866/3250.349/1041.391 ms 11 pps
[ 3] 3.00-6.00 sec 35.9 KBytes 98.0 Kbits/sec 115.222 ms 0/ 25 (0%) 4938.430/3250.349/6246.515/881.983 ms 8 pps
[ 3] 6.00-9.00 sec 34.5 KBytes 94.1 Kbits/sec 118.883 ms 0/ 24 (0%) 8004.727/6246.515/9122.994/847.412 ms 8 pps
[ 3] 9.00-12.00 sec 35.9 KBytes 98.0 Kbits/sec 119.633 ms 0/ 25 (0%) 11045.333/9122.994/12118.339/881.981 ms 8 pps
[ 3] 12.00-15.00 sec 35.9 KBytes 98.0 Kbits/sec 119.799 ms 0/ 25 (0%) 14161.069/12118.339/15114.390/881.968 ms 8 pps
[ 3] 15.00-18.00 sec 35.9 KBytes 98.0 Kbits/sec 119.826 ms 0/ 25 (0%) 17276.943/15114.390/18110.333/881.980 ms 8 pps
Проверим в обратную сторону. Еще раз просверлим дырку:
GW
sudo nft insert rule inet filter forward handle 22 ip daddr 192.168.100.2 udp dport 5001 oifname eth0 accept
sudo nft add rule ip nat prerouting iif eth1 udp dport 5001 dnat to 192.168.100.2
PC1
iperf -u -s -i 3 -e
Test server
iperf -u -c 10.0.0.1 -t 0 -b 10M -i 3
Смотрим статистику:
PC1
iperf -u -s -i 3 -e
------------------------------------------------------------
Server listening on UDP port 5001 with pid 1644
Receiving 1470 byte datagrams
UDP buffer size: 208 KByte (default)
------------------------------------------------------------
[ 3] local 192.168.100.2 port 5001 connected with 10.0.0.0 port 37838
[ ID] Interval Transfer Bandwidth Jitter Lost/Total Latency avg/min/max/stdev PPS
[ 3] 0.00-3.00 sec 48.8 KBytes 133 Kbits/sec 96.658 ms 0/ 34 (0%) 1188.417/ 0.895/3051.724/1040.833 ms 11 pps
[ 3] 3.00-6.00 sec 35.9 KBytes 98.0 Kbits/sec 115.221 ms 0/ 25 (0%) 4731.656/3051.724/6047.665/882.004 ms 8 pps
[ 3] 6.00-9.00 sec 34.5 KBytes 94.1 Kbits/sec 118.850 ms 0/ 24 (0%) 7797.450/6047.665/8923.528/847.373 ms 8 pps
[ 3] 9.00-12.00 sec 35.9 KBytes 98.0 Kbits/sec 119.640 ms 0/ 25 (0%) 10838.408/8923.528/11919.482/881.922 ms 8 pps
[ 3] 12.00-15.00 sec 35.9 KBytes 98.0 Kbits/sec 119.801 ms 0/ 25 (0%) 13954.182/11919.482/14915.472/881.954 ms 8 pps
[ 3] 15.00-18.00 sec 35.9 KBytes 98.0 Kbits/sec 119.822 ms 0/ 25 (0%) 17070.016/14915.472/17911.337/882.041 ms 8 pps
[ 3] 18.00-21.00 sec 35.9 KBytes 98.0 Kbits/sec 119.830 ms 0/ 25 (0%) 20185.716/17911.337/20907.241/882.005 ms 8 pps
Скорость ограничена до 100Kbps — работает.
Ограничение трафика при использовании МРД (Мандатный режим доступа)
Поддержка фильтрации на основе классификационных меток реализована с помощью дополнительного модуля ядра astralabel, обеспечивающего тестирование значений мандатных атрибутов с помощью параметров --maclev и --maccat. Модуль работает только с подсистемой фильтрации Netfilter и не может работать с nftables. Для последнего есть возможность работать с произвольными данными через RAW payload expression (отбор на основе «сырых» данных). К сожалению, фильтра, сопоставимого с возможностями U32, пока нет. Не получится обойти все IP-опции, чтобы найти нужную (как вариант: использовать eBPF / astralabel).
К счастью, если в пакете уже есть какой-либо IP Options, то security-метка в пакет не добавляется. Поэтому, если в IP-пакете будет классификационная метка, то опция будет всего одна.
Все возможные опции и закрепленные за ними RFC можно посмотреть здесь:
https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml
Классификационные метки относятся к IP Security Options:
https://www.rfc-editor.org/rfc/rfc1108.html
IP-пакет имеет опции, если значение поля Internet Header Length (IHL) строго больше 5. Эти четыре бита содержат размер заголовка пакета в 32-битных словах. Минимальное значение равно 5 (5×32=160 бит, 20 байт), максимальное — 15 (60 байт).
Посмотрим на структуру заголовка IP-пакета:
Структура IP-опции следующая:
Чтобы секретная информация случайно не ушла в интернеты, хорошей практикой считается фильтрация пакетов с ненулевыми классификационными метками на аплинке.
Получается, чтобы пофильтровать метки на аплинке, достаточно написать правила для RAW payload expression (отбор на основе «сырых» данных). Синтаксис имеет следующую форму: @base, offset, length
База может быть следующей:
ll — Канальный заголовок, например Ethernet заголовок;
nh —Сетевой заголовок, например IPv4 или IPv6;
th — Транспортный заголовок, например TCP;
ih — Внутренний заголовок / Полезные данные, т.е. после заголовка транспортного уровня.
Относительно базы загружаем по смещению offset нужное число бит length.
Итоговые правила получаются следующие:
/etc/firewall.nft
# Определяем таблицу и цепочки
table inet filter {
chain forward {
...
# Вычисляем пакеты с предполагаемой IP Security Option
meta nfproto ipv4 @nh,4,4 6 iif $LAN_DEV oif $WAN_DEV counter jump probably_ip_sec_opt
...
}
chain output {
...
# Вычисляем пакеты с предполагаемой IP Security Option
meta nfproto ipv4 @nh,4,4 6 oif $WAN_DEV counter jump probably_ip_sec_opt
...
}
chain probably_ip_sec_opt {
meta nfproto ipv4 @nh,160,8 130 counter drop
}
}
@nh,4,4 6 — означает, что IHL поле в IPv4 пакете будет иметь значение 6 (6×32=192 бит, 24 байт).
meta nfproto ipv4 @nh,160,8 130 counter drop — этим правилом мы фильтруем все пакеты с IP Security Options.
Проверим фильтрацию.
PC1 работает на максимальном уровне защищенности (smolensk):
PC1
astra@pc1:~$ sudo astra-modeswitch list
0 base(orel)
1 advanced(voronezh)
2 maximum(smolensk)
astra@pc1:~$
astra@pc1:~$ sudo astra-modeswitch get
2
astra@pc1:~$
При нулевом уровне конфиденциальности (когда в пакетах нет IP Security Options):
PC1
astra@pc1:~$ ping -n -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=101 time=130 ms
--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 130.396/130.396/130.396/0.000 ms
astra@pc1:~$
astra@pc1:~$ sudo tcpdump -i eth0 -nn -s 0 -vvv icmp
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
10:03:48.402999 IP (tos 0x0, ttl 64, id 14316, offset 0, flags [DF], proto ICMP (1), length 84)
192.168.100.2 > 8.8.8.8: ICMP echo request, id 1887, seq 1, length 64
10:03:48.533373 IP (tos 0x0, ttl 101, id 0, offset 0, flags [none], proto ICMP (1), length 84)
8.8.8.8 > 192.168.100.2: ICMP echo reply, id 1887, seq 1, length 64
^C
2 packets captured
2 packets received by filter
0 packets dropped by kernel
astra@pc1:~$
Смотрим счетчики на шлюзе:
GW
astra@gw:~$ sudo nft list ruleset | grep nh
@nh,160,8 130 counter packets 0 bytes 0 drop
astra@gw:~$
Пока по нулям.
Переходим на первый уровень и повторяем то же самое:
PC1
astra@pc1:~$ sudo pdp-exec -u test -l 1:0 -- ping -n -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
--- 8.8.8.8 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
astra@pc1:~$
astra@pc1:~$ sudo tcpdump -i eth0 -nn -s 0 -vvv icmp
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
10:11:06.640292 IP (tos 0x0, ttl 64, id 30150, offset 0, flags [DF], proto ICMP (1), length 88, options (security))
192.168.100.2 > 8.8.8.8: ICMP echo request, id 1928, seq 1, length 64
^C
1 packet captured
1 packet received by filter
0 packets dropped by kernel
astra@pc1:~$
Теперь счетчик ненулевой:
GW
astra@gw:~$ sudo nft list ruleset | grep nh
@nh,160,8 130 counter packets 1 bytes 88 drop
astra@gw:~$
Это будет работать для любого ненулевого уровня секретности.
Подводим итог: предложенную схему можно взять за основу при разработке доступа своей сети в интернет как для домашнего применения, так и для небольшого офиса. В статье я показал методики нагрузочного тестирования и приемы работы с пакетами, которые имеют метки безопасности, характерные для ОС Astra Linux. Также разобрал базовые технологии, которые требуются при подключении сети небольшого офиса к интернету.
Если после прочтения будут возникать какие-то вопросы, буду рад ответить в комментариях.