PeerGuardian w oparciu o ipset i iptables na Debian linux
Spis treści
Wiele linux'owych klientów torrent'a umożliwia ładowanie zewnętrznej listy z zakresami adresów IP i
ta lista ma służyć jako swego rodzaju filtr połączeń chroniący nas przed różnego rodzaju
organizacjami, które mogą zbierać i przetwarzać informacje na temat naszego IP i tego, co on
porabia w sieci p2p. Oczywiście, kwestia czy korzystać z takiego typu rozwiązania jest bardzo
dyskusyjna i wiele osób jest zdania, że to tak naprawdę w niczym nie pomoże, a wręcz nawet
przyczynia się do samounicestwienia sieci p2p. Także taki filtr może czasem przynieść więcej szkody
niż pożytku, zwłaszcza gdy się go używa lekkomyślnie, czyli na zasadzie, że ten który blokuje
więcej adresów IP musi być lepszy. Poniżej zostanie opisane rozwiązanie zakładające wykorzystanie
rozszerzenia ipset
, które w połączeniu z iptables
da nam w pełni działający filtr połączeń p2p
(coś na wzór popularnego oprogramowania PeerGuardian), dzięki któremu będziemy w stanie wyciąć
dowolny ruch i to nie tylko przy korzystaniu z różnych klientów torrent, np. qbittorrent, ale
także i przy zwykłym przeglądaniu stron www.
Reguły dla ipset'a
Standardowo pakietu ipset
nie ma zainstalowanego w Debianie i trzeba go dociągnąć z repozytorium
APT przy pomocy apt-get
/aptitude
. Jako, że pakiet ipset
znajduje się w oficjalnym repo tej
dystrybucji, to nie powinno być problemu z jego instalacją. Musimy także stworzyć katalog, w którym
to będziemy trzymać listy z adresami IP. Ja obrałem sobie folder /etc/peerblock/
. Dodatkowo,
musimy utworzyć osobny folder z konfiguracją dla iptables, np. /etc/filtr/
i od tego katalogu
zaczniemy.
Potrzebne nam będą trzy pliki: base.sh
, ipset.sh
oraz iptables_raw.sh
:
# mkdir /etc/filtr/
# cd /etc/filtr/
# touch base.sh ipset.sh iptables_raw.sh
# chmod 700 *
Plik base.sh
W pliku base.sh
znajduje się podstawowy filtr dla stacji klienckich. Będzie on aplikowany zawsze
wtedy, gdy będziemy z jakiegoś powodu potrzebowali zdjąć główną zaporę. W ten sposób nie zostaniemy
bez ochrony. Plik ma poniższą postać:
#!/bin/sh
iptables_stop()
{
ipt="$(which iptables)"
ip6t="$(which ip6tables)"
$ipt -P INPUT DROP
$ipt -P FORWARD DROP
$ipt -P OUTPUT ACCEPT
$ip6t -P INPUT DROP
$ip6t -P FORWARD DROP
$ip6t -P OUTPUT ACCEPT
for iptable in $ipt $ip6t
do
for table in \
"-t raw" \
"-t mangle" \
"-t filter" \
"-t nat"
do
$iptable $table -F
$iptable $table -X
done
done
$ipt -t filter -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
$ipt -t filter -A INPUT -i lo -j ACCEPT
$ipt -t filter -A INPUT -m conntrack --ctstate INVALID -j DROP
$ipt -t filter -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
$ipt -t filter -A INPUT -p tcp -j REJECT --reject-with tcp-reset
$ipt -t filter -A INPUT -j REJECT --reject-with icmp-proto-unreachable
$ip6t -t filter -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
$ip6t -t filter -A INPUT -i lo -j ACCEPT
$ip6t -t filter -A INPUT -m conntrack --ctstate INVALID -j DROP
$ip6t -t filter -A INPUT -p tcp -j REJECT --reject-with tcp-reset
$ip6t -t filter -A INPUT -p udp -j REJECT --reject-with icmp6-port-unreachable
}
ipset_stop()
{
ips=$(which ipset)
sets=$($ips -n --list)
for set in $sets
do
name=$($ips list $set 2> /dev/null | head -n 1 | cut -d ' ' -f2)
if [ "$name" = "$set" ]; then
$ips destroy $set 2>&1
fi
done
}
iptables_stop
ipset_stop
Plik ipset.sh
Następnie tworzymy plik ipset.sh
, który będzie odpowiedzialny za tworzenie list z adresami:
#!/bin/sh
ips="$(which ipset)"
$ips create bt_level1 hash:net family inet maxelem 270000
$ips create bt_spyware hash:net family inet maxelem 4000
$ips create bt_webexploit hash:net family inet maxelem 2000
$ips create whitelist hash:ip family inet maxelem 500
sets="$($ips -n --list)"
for set in $sets
do
cat /etc/peerblock/$set.gz |
gunzip |
cut -d: -f2 |
grep -E "^[-0-9.]+$" |
gawk -v my_set=$set '{print "add " my_set " " $1}' |
$ips restore -exist;
done
Mamy tutaj do obróbki trzy listy, które będziemy pobierać z serwisu iblocklist.com. Naturalnie, można sobie dobrać jakiekolwiek zestawy.
Plik iptables_raw.sh
Musimy także utworzyć konfigurację dla filtra iptables
. Interesuje nas głównie tablica raw
.
Tworzymy plik iptables_raw.sh
o poniższej treści:
#!/bin/sh
ipt="$(which iptables) -t raw"
$ipt -F
$ipt -X
$ipt -N peerblock_in
$ipt -N peerblock_out
$ipt -A PREROUTING -j peerblock_in
$ipt -A OUTPUT -j peerblock_out
$ipt -A peerblock_in -p all -m set --match-set whitelist src -j ACCEPT
$ipt -A peerblock_in -p udp -m multiport ! --sports 80,443 -m set --match-set bt_level1 src -j DROP
$ipt -A peerblock_in -p tcp -m multiport ! --sports 80,443 -m set --match-set bt_level1 src -j DROP
$ipt -A peerblock_in -m set --match-set bt_spyware src -j DROP
$ipt -A peerblock_in -m set --match-set bt_webexploit src -j DROP
$ipt -A peerblock_out -p all -m set --match-set whitelist dst -j ACCEPT
$ipt -A peerblock_out -p tcp -m multiport ! --dports 80,443 -m set --match-set bt_level1 dst -j DROP
$ipt -A peerblock_out -p udp -m multiport ! --dports 80,443 -m set --match-set bt_level1 dst -j DROP
$ipt -A peerblock_out -m set --match-set bt_spyware dst -j DROP
$ipt -A peerblock_out -m set --match-set bt_webexploit dst -j DROP
Zanim pakiety dotrą do celu, trafiają pierw do tablicy raw
i tam można je wstępnie odfiltrować.
Stworzyliśmy tam dwa dodatkowe łańcuchy, po jednym dla pakietów przychodzących i wychodzących.
Kluczowa sprawa to wywołanie modułu przy pomocy parametru -m set
i dopasowanie pakietów do
odpowiedniego filtra w ipset
przy pomocy --match-set
. Ja mam 4 filtry, po 1 regule dla każdego,
w każdym łańcuchu, łącznie daje 8 wpisów. Białą listę dajemy na początek, tak by pakiety, które
zostałyby zablokowane przez przez 3 pozostałe filtry, były przepuszczane bez większego problemu i
wędrowały do tablicy filter
. Lista bt_level1
jest to typowa lista p2p i generalnie lepiej jest
jej nie używać na portach 80
i 443
, czyli na stronach www, bo ona nawet gógla blokuje.
Najlepiej ustawić sobie zakres portów, na których klient bittorrent'a może dokonywać połączeń. U
mnie qbittorrent ma zabronione łączenie się na porty poniżej numeru 1024 i na dobrą sprawę,
ustawiłem mu zakres 10k portów od 55000 do 65000. I praktycznie można by sprecyzować ten zakres
w regule iptables. Pozostałe dwa filtry są już użyte bardziej restrykcyjnie ale to dlatego, że
zakresów adresów IP jest tam relatywnie niewielki i w ciągu dnia trafia mi się tam może 20-30
pakietów. Różnica w stosunku do tego dużego filtra polega na tym, że te dwa mniejsze filtrują ruch
również dla http i https.
Praca dla cron'a
Co prawda skrypt z filtrem będzie ładowany przy starcie systemu ale on nie pobiera list. Zamiast
tego, ładuje je tylko do ipset'a. Ku temu jest kilka powodów. Pierwszym jest oczywiście spowolnienie
startu systemu. Kolejnym powodem może być brak sieci. Jak nie patrzeć, sieć zostanie dopiero
uruchomiona jak listy zostaną zaaplikowane, czyli muszą już istnieć na dysku. Najlepszym wyjściem w
tej sytuacji jest rozdzielenie kwestii pobierania i aplikowania list. Listy będą aktualizowane co
dzień, dlatego też najlepiej po prostu obarczyć tym zadaniem cron'a i adresy z plików, które on
stworzy, ładować do ipset'a. Tworzymy zatem plik /etc/cron.daily/peerblock_list
o poniższej
treści:
#!/bin/sh
ping -c 1 onet.pl > /dev/null 2>&1
retVal="$?"
if [ "$retVal" != "0" ]; then
echo "Network connection not detected, so peerblock lists are not being updated."
exit 0
fi
#echo "pozyskiwanie listy bt_level1..."
curl --silent --show-error -L "http://list.iblocklist.com/?list=bt_level1&fileformat=p2p&archiveformat=gz" > /etc/peerblock/bt_level1.gz
#echo "pozyskiwanie listy bt_spyware..."
curl --silent --show-error -L "http://list.iblocklist.com/?list=bt_spyware&fileformat=p2p&archiveformat=gz" > /etc/peerblock/bt_spyware.gz
#echo "pozyskiwanie listy bt_webexploit..."
curl --silent --show-error -L "http://list.iblocklist.com/?list=ghlzqtqxnzctvvajwwag&fileformat=p2p&archiveformat=gz" > /etc/peerblock/bt_webexploit.gz
Ta lista będzie pobierana raz dziennie. Niemniej jednak, potrzebujemy wszystkich z powyższych plików przed odpaleniem zapory. Dlatego też odpalmy ww. skrypt ręcznie, by pliki zostały pobrane i umieszczone w odpowiednim miejscu.
Usługa dla systemd
Pozostało nam jeszcze napisanie odpowiedniego pliku usługi dla systemd . Jeśli wcześniej
zbudowaliśmy sobie firewall, to musimy jedynie dostosować plik
/etc/systemd/system/firewall.service
. Jego zawartość powinna być mniej więcej taka:
[Unit]
Description=firewall
Documentation=man:iptables
DefaultDependencies=no
Wants=network-pre.target systemd-modules-load.service
Before=network-pre.target shutdown.target
After=systemd-modules-load.service
Conflicts=shutdown.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c "/etc/filtr/ipset.sh"
ExecStart=/bin/sh -c "/etc/filtr/iptables_raw.sh"
ExecStart=/bin/sh -c "/etc/filtr/iptables_mangle.sh"
ExecStart=/bin/sh -c "/etc/filtr/iptables_nat.sh"
ExecStart=/bin/sh -c "/etc/filtr/iptables_filter.sh"
ExecStart=/bin/sh -c "/etc/filtr/ip6tables_raw.sh"
ExecStart=/bin/sh -c "/etc/filtr/ip6tables_mangle.sh"
ExecStart=/bin/sh -c "/etc/filtr/ip6tables_nat.sh"
ExecStart=/bin/sh -c "/etc/filtr/ip6tables_filter.sh"
ExecStop=/bin/sh -c "/etc/filtr/base.sh"
[Install]
WantedBy=multi-user.target
To w zasadzie tyle pracy z naszej strony. W kilku plikach nie umieszczaliśmy żadnych reguł, a to z tego względu, że mogą się one różnić w przypadku każdej maszyny.
Trzeba także pamiętać, że ilość wpisów na listach ulega zmianie i może się zdarzyć tak, że limit
ustawiony w zmiennej maxelem
w pliku ipset.sh
zostanie przekroczony. Gdy taka sytuacja wystąpi,
to wystarczy podbić ten limit. Nie należy jednak z nim przesadzać. Co prawda, przy długości 290000
taka lista zajmuje koło 3,5 MiB RAMu. Nie jest to dużo ale jeśli byśmy tam mieli tylko jeden adres,
to ilość okupowanej pamięci nie ulegnie zmianie. To i tak sporo lepiej w porównaniu do
qbittorrent'a, który za obsługę tej samej listy chciał ponad 200MiB.
Niekompatybilny format list ipset'a
Format listy pobranej z iblocklist.com
różni się od tego, który można załadować do ipset
.
Wpisy w przykładowej liście wyglądają mniej więcej tak, jak to widać poniżej:
# List distributed by iblocklist.com
China Internet Information Center (CNNIC):1.2.4.0-1.2.4.255
China Internet Information Center (CNNIC):1.2.8.0-1.2.8.255
SMSHoax FakeAV Fraud Trojan:1.9.75.8-1.9.75.8
SMSHoax FakeAV Fraud Trojan:1.9.189.65-1.9.189.65
...
A trzeba je przerobić do postaci:
add bt_level1 2.115.176.190
add bt_level1 62.52.2.0/26
add bt_level1 212.174.156.0/23
...
Za to odpowiada plik ipset.sh
, a konkretnie ten poniży kod:
for set in $sets
do
cat /etc/peerblock/$set.gz |
gunzip |
cut -d: -f2 |
grep -E "^[-0-9.]+$" |
gawk -v my_set=$set '{print "add " my_set " " $1}' |
$ips restore -exist;
done
Poniżej wyjaśnienie poszczególnych etapów przetwarzania listy:
cat /etc/peerblock/$set.gz
wczytuje do pamięci plik listygunzip
wypakowuje ten plikcut -d: -f2
dzieli linijki po znaku dwukropka i części powstałe w taki sposób oznacza kolejno 1,2, etc. W tym przypadku, jako że linijka ma tylko jeden dwukropek, zostanie podzielona na 2 części oraz zostanie wycięty kawałek od dwukropka do końca wiersza.grep -E "^[-0-9.]+$"
nie wiem co dokładnie znaczy ale chyba wyciąga z wyniku wpisy, które mają cyfry, kropki i znak minusa powtórzone co najmniej raz. Czyli wynik w postaci62.27.97.232-62.27.97.235
. Takie zakresy IP zostaną automatycznie przepisane przezipset
z uwzględnieniem maski podsieci (patrz ipcalc).gawk -v my_set=$set '{print "add " my_set " " $1}'
dodaje na początku frazęadd bt_level1
Tak przerobioną listę można bez problemu teraz podać w ipset
.
Biała lista
Ja w powyższy sposób zaimplementowałem sobie 3 listy z adresami IP ale jest jeszcze jedna, którą warto mieć. Jest to lista z adresami, którym udzielimy dostępu do naszej maszyny. Te filtry mogą i z pewnością będą blokować sporo adresów, które są niewinne i dlatego przydałoby się mieć już gotowe rozwiązanie na wypadek, gdy ktoś z naszych znajomych trafi na taką listę.
Tworzymy zatem plik /etc/peerblock/whitelist
i dodajemy tam adresy w formie 91.121.10.104
:
# cat /etc/peerblock/whitelist
91.121.10.104
Po edycji, plik kompresujemy programem gzip
:
# cd /etc/peerblock/
# gzip whitelist
Logowanie blokowanych przez ipset pakietów
Jeśli pakiety zostaną zablokowane z jakiegoś adresu IP, a my nie wiemy z jakiego, to możemy włączyć
logowanie dodając do regułek iptables do łańcuchów PREROUTING
oraz OUTPUT
tablicy raw
coś na
wzór poniższej linijki:
-A OUTPUT -p tcp -m multiport ! --dports 80,443 -m set --match-set bt_level1 dst -j LOG -m limit --limit 10/m --log-level 4 --log-prefix "*** BT_LEVEL1 *** "
Oczywiście to tylko przykład logowania, prawdopodobnie trzeba będzie tę regułę sobie dostosować. Jedna rzecz, o której trzeba pamiętać, to by ta reguła znalazła się przed faktyczną regułą filtrującą ruch. Jeśli tego nie zrobimy, to pakiety nigdy nie dotrą do reguły logowania, bo zostaną wcześniej zablokowane.
I to w sumie wszystko. Testowałem tego PeerGuardian'a opartego o ipset
i iptables
na swoim
Debianie i to rozwiązanie po prostu działa. Jeśli kogoś przerażają kroki opisane w niniejszym
artykule, to może pokusić się o zaimplementowanie prawie gotowe rozwiązanie.