Ograniczenia dostępu do internetu
Ostatnia zmiana: 2018-05-07 18:02

Zakładamy że używamy routera z OpenWrt. Podłączamy się do niego urządzeniem (przewodowo lub bezprzewodowo, nie ma znaczenia), w domyślnej konfiguracji Openwrt urządzenie uzyskuje dostęp do internetu. Co w przypadku kiedy chcemy ograniczyć ten dostęp w jakiś sposób? Robimy sieć dla gości i chcielibyśmy ograniczyć transfer np. do 1GB tygodniowo lub nie więcej niż 2Mb/s. Mamy dzieci i nie chcemy żeby korzystały z internetu popołudniami, kiedy mają odrabiać lekcje, ale czasami potrzebujemy im dać dostęp żeby mogły poszukać informacji np. na wikipedii. Lub po prostu mamy wifi w biurze i domyślnie chcemy wyłączyć je na weekend. Ten poradnik pokazuje różne metody blokowania lub ograniczenia użytkowników. Elementem identyfikującym użytkowników będzie adres mac urządzenia klienciego. W całym poradniku blokowany będzie hipotetyczny adres MAC 01:02:03:04:05:06. Zakładamy też że firewall OpenWrt ma domyślną konfigurację: ze strony lan akceptuje cały ruch, a zajmujemy się blokowaniem określonych hostów. Jeżeli chcemy mieć odwrotnie to należy zmienić firewall (sekcja lan) oraz odpowiednio dostosować logikę tworzonych reguł - akceptować (ACCEPT) zamiast odrzucać (REJECT) połączenia.
Rozwiązania przetestowano na LEDE 17.01.

Całkowita blokada urządzenia

Tymczasowa

Całkowicie blokujemy dostęp do internetu. Można to zrobić tworząc w konsoli odpowiednią regułę iptables:


    # iptables -I FORWARD -m mac --mac-source 01:02:03:04:05:06 -j REJECT

Od tej pory nastąpi blokada dostępu do internetu dla tego adresu MAC. Blokada będzie obowiązywać do restartu routera, do restartu firewalla (lub np. zmiany konfiguracji co powoduje restart firewalla) lub do ręcznego usunięcia reguły.
Aby ręcznie usunąć regułę należy posłużyć się poleceniem:


    # iptables -D FORWARD -m mac --mac-source 01:02:03:04:05:06 -j REJECT

Stała

Tworzymy po prostu odpowiednią regułę w firewallu OpenWrt korzystając z uci:


    # uci set firewall.mac010203040506=rule
    # uci set firewall.mac010203040506.src='lan'
    # uci set firewall.mac010203040506.dest='wan'
    # uci set firewall.mac010203040506.src_mac='01:02:03:04:05:06'
    # uci set firewall.mac010203040506.target='REJECT'
    # uci commit firewall
    # /etc/init.d/firewall restart

W/w polecenie tworzą tzw. "sekcję nazwaną" (patrz poradnik o uci) dzięki czemu można później bezproblemowo manipulować zawartością reguły bez jej szukania w całym firewallu a jako nazwę sekcji wykorzystałem zapis związany z adresem mac. Oczywiście można używać prawie dowolnej nazwy typu "malgosia" pamiętając o ograniczeniach uci: żadnych spacji, znaków specjalnych, myślników itd.

Zdjęcie blokady można wykonać na dwa sposoby:
- przez wyłączenie reguły:


    # uci set firewall.mac010203040506.enabled=0
    # uci commit firewall
    # /etc/init.d/firewall restart

- przez jej usunięcie z firewalla:


    # uci del firewall.mac010203040506
    # uci commit firewall
    # /etc/init.d/firewall restart

Blokada określonych zasobów

(dotyczy blokady zasobów w internecie, nie w sieci lokalnej)
Powyższe reguły można lekko zmodyfikować - np. zablokować dostęp do www, zezwalając na resztę usług:


    # uci set firewall.mac010203040506_80=rule
    # uci set firewall.mac010203040506_80.src='lan'
    # uci set firewall.mac010203040506_80.dest='wan'
    # uci set firewall.mac010203040506_80.src_mac='01:02:03:04:05:06'
    # uci set firewall.mac010203040506_80.proto=tcp
    # uci set firewall.mac010203040506_80.dest_port=80
    # uci set firewall.mac010203040506_80.target='REJECT'
    # uci set firewall.mac010203040506_443=rule
    # uci set firewall.mac010203040506_443.src='lan'
    # uci set firewall.mac010203040506_443.dest='wan'
    # uci set firewall.mac010203040506_443.src_mac='01:02:03:04:05:06'
    # uci set firewall.mac010203040506_443.proto=tcp
    # uci set firewall.mac010203040506_443.dest_port=443
    # uci set firewall.mac010203040506_443.target='REJECT'
    # uci commit firewall
    # /etc/init.d/firewall restart

Można blokować:
- protokół (proto)
- port docelowy (dest_port)
- adres IP docelowy (dest_ip) - uwaga: podajemy tu adres ip lub nazwę domeny, nie pełny adres URL!
Należy pamiętać że jeżeli podamy adres domeny to w momencie tworzenia reguły zostanie ona zamieniona na odpowiedni jej adres IP i reguła będzie obowiązywała dla danego adresu IP. Jeżeli domena ma więcej adresów to powyższy sposób zablokuje tylko jeden z nich i całość nie będzie działać tak jak trzeba. Wtedy można posłużyć się innym sposobem, na przykład z wykorzystaniem ipset. Przykład do blokowania facebooka:


    # opkg update
    # opkg remove dnsmasq
    # opkg install dnsmasq-full
    # /etc/init.d/dnsmasq enable
    # opkg install ipset
    # uci set firewall.facebook=ipset
    # uci set firewall.facebook.name='facebook'
    # uci set firewall.facebook.match='dest_ip'
    # uci set firewall.facebook.storage='hash'
    # uci set firewall.facebook.family='ipv4'
    # uci set firewall.mac010203040506_facebook=rule
    # uci set firewall.mac010203040506_facebook.src='lan'
    # uci set firewall.mac010203040506_facebook.dest='wan'
    # uci set firewall.mac010203040506_facebook.src_mac='01:02:03:04:05:06'
    # uci set firewall.mac010203040506_facebook.proto=tcp
    # uci set firewall.mac010203040506_facebook.ipset=facebook
    # uci set firewall.mac010203040506_facebook.target='REJECT'
    # uci add_list dhcp.@dnsmasq[0].ipset='/fb.com/facebook'
    # uci add_list dhcp.@dnsmasq[0].ipset='/facebook.com/facebook'
    # uci commit
    # /etc/init.d/dnsmasq restart
    # /etc/init.d/firewall restart

Oczywiście w takim przypadku urządzenie musi korzystać z routera jako serwera DNS, ponieważ to dnsmasq zajmuje się gromadzeniem adresów IP blokowanej domeny (wg przykładu: fb.com i facebook.com), a iptables korzystając z tych adresów robi blokadę.
Powyższy przykład też nie jest idealny, bo np. blokując witryny typu google możemy też zablokować inne usługi korzystające z tych samych adresów IP.
Opis firewalla można znaleźć na wiki openwrt.

Aby zdjąć w/w blokady należy je albo usunąć z firewalla albo ustawić dodatkowo opcję enabled=0 i zrestartować firewall, identycznie jak to przedstawiono w pierwszym przykładzie.

Dostęp czasowy - w określonych godzinach

Czyli np. tzw. kontrola rodzicielska. Ustawiamy tak firewalla, żeby zablokować dostęp do internetu od poniedziałku do piątku w godzinach 14.30 do 22:45:


    # uci firewall.mac010203040506_weekend=rule
    # uci firewall.mac010203040506_weekend.src='lan'
    # uci firewall.mac010203040506_weekend.dest='wan'
    # uci firewall.mac010203040506_weekend.src_mac='01:02:03:04:05:06'
    # uci firewall.mac010203040506_weekend.weekdays='mon tue wed thu fri' #lub '! sat sun'
    # uci firewall.mac010203040506_weekend.target='REJECT'
    # uci firewall.mac010203040506_weekend.start_time='14:30:00'
    # uci firewall.mac010203040506_weekend.stop_time='22:45:00'
    # uci commit firewall
    # /etc/init.d/firewall restart

UWAGA: router musi mieć ustawiony aktualny czas!
Aby zdjąć blokadę należy ją albo usunąć z firewalla albo ustawić dodatkowo opcję enabled=0 i zrestartować firewall, identycznie jak to przedstawiono w pierwszym przykładzie.

Dostęp czasowy - ilość zużytego czasu

Inny przykład - chcemy zapewnić dostęp do internetu łączenie przez 2 godziny w ciągu dnia.

WiFi

Rozwiązanie dla klientów bezprzewodowych było już przedstawione na forum i ten przykład z niego korzysta.

Robimy nowy skrypt:


    # touch /usr/bin/timequotas.sh
    # chmod 755 /usr/bin/timequotas.sh

Robimy edycję pliku (vi /usr/bin/timequotas.sh), wpisujmy:


    #!/bin/sh
    HOST="01:02:03:04:05:06"

    # limit w minutach
    LIMIT=120

    F=/tmp/timequota-$HOST
    if [ -e $F ]; then
        USEDTIME=$(cat $F)
    else
        USEDTIME=0
    fi

    T=$(iw dev wlan0 station dump | grep -A 1 $HOST | awk '/inactive time/{print $3}')
    [ -z "$T" ] && exit 0
    if [ $T -lt 60000 ]; then
        USEDTIME=$((USEDTIME + 1))
        echo $USEDTIME > $F
    fi
    if [ $USEDTIME -gt $LIMIT ]; then
        iptables -C FORWARD -m mac --mac-source $HOST -j REJECT || \
            iptables -I FORWARD -m mac --mac-source $HOST -j REJECT
    fi
    exit 0

Skrypt odczytuje czy urządzenie o podanym adresie jest podłączone przez wifi. Jeżeli tak to zwiększa swój wewnętrzny licznik; jeżeli został przekroczony limit to tworzona jest reguła blokująca. Do działania niezbędne jest jeszcze wykonywanie tego skryptu w cronie co minutę:


    # echo "*/1 * * * * /usr/bin/timequotas.sh" >> /etc/crontabs/root
    # /etc/init.d/cron restart

Zostaje tylko zniesienie limitu dziennego - to też w cronie, jeden raz o północy:


    # echo "0 0 * * * echo 0 > /tmp/timequota-01:02:03:04:05:06; iptables -D FORWARD -m mac --mac-source 01:02:03:04:05:06 -j REJECT" >> /etc/crontabs/root
    # /etc/init.d/cron restart


Jeżeli chcemy dać dodatkowe np. 15 minut to robimy:


    # echo $((120-15)) > "/tmp/timequota-01:02:03:04:05:06"
    # iptables -D FORWARD -m mac --mac-source 01:02:03:04:05:06 -j REJECT

120 to dzienny limit który jest sprawdzany w skrypcie, 15 - czas jaki został do nałożenia limitu.

Jeżeli chcemy usunąć blokadę to należy wykonać to samo co w cronie, czyli:


    # echo 0 > "/tmp/timequota-01:02:03:04:05:06"
    # iptables -D FORWARD -m mac --mac-source 01:02:03:04:05:06 -j REJECT

Należy pamiętać że restart routera powoduje zlikwidowanie liczników, więc jeżeli ktoś chce je zachować to należy wszystkie pliki /tmp/timequota-* systematycznie backupować i odtwarzać przy starcie routera. Lub jeżeli mamy np. extroota to zmienić ich położenie w skryptach wskazując na pendrive.

Połączenia kablowe

Dla połączeń kablowych sprawa się komplikuje, ponieważ host może mieć ustawiony np. statyczny adres IP. Z pewnym przybliżeniem można wykorzystać więc fakt, że jeżeli transmitował ostatnio jakieś dane to powinien być w tablicy ARP. Robimy identyczne rozwiązanie jak dla wifi tylko należy zmodyfikować skrypt na następujący:


    #!/bin/sh
    HOST="01:02:03:04:05:06"

    # limit w minutach
    LIMIT=120

    F=/tmp/$HOST
    if [ -e $F ]; then
        USEDTIME=$(cat $F)
    else
        USEDTIME=0
    fi

    T=$(grep -e "$HOST" /proc/net/arp | grep "0x2")
    [ -z "$T" ] && exit 0
    USEDTIME=$((USEDTIME + 1))
    echo $USEDTIME > $F
    if [ $USEDTIME -gt $LIMIT ]; then
        iptables -C FORWARD -m mac --mac-source $HOST -j REJECT || \
            iptables -I FORWARD -m mac --mac-source $HOST -j REJECT
    fi
    exit 0


Limit transferu

Chcemy ograniczyć dostęp np. na 50MB dziennie. Instalujemy odpowiedni moduł iptables:


    # opkg install iptables-mod-extra

Na początek robimy regułę ograniczającą (na razie do testów, do wykonania w konsoli, żeby mieć orientację jak całość działa):


    # QUOTA=$((50 * 1024 * 1024))
    # iptables -N mac010203040506_limit50mb
    # iptables -A mac010203040506_limit50mb -m quota --quota $QUOTA -j ACCEPT
    # iptables -A mac010203040506_limit50mb -j REJECT
    # iptables -A forwarding_rule -j mac010203040506_limit50mb -s 192.168.1.100
    # iptables -A forwarding_rule -j mac010203040506_limit50mb -d 192.168.1.100

Powyższe reguły zakładają nowy łańcuch który zlicza ilość przesłanych danych od i z adresu IP 192.168.1.100. Jeżeli przekroczony zostanie podany limit to następuje przejście do następnej reguły, czyli w tym przypadku odrzucenie połączeń. I do tego łańcucha podłączamy nasz adres IP. Ale to tylko zlicza, więc należy też go czyścić co określony czas, czyli np. raz dziennie o północy wykorzystując cron:


    # echo "0 0 * * * iptables -Z mac010203040506_limit50mb " >> /etc/crontabs/root
    # /etc/init.d/cron restart

Wykorzystując cron możemy zrobić odnowienie limitu dziennie, co miesiąc, w określonym dniu miesiąca, co dwa tygodnie lub np. w każdy czwartek. Zależy to tylko od tego jak zrobimy wpis w cronie.
Jeżeli chcemy dodać inny adres IP do tego samego limitu - po prostu powielamy ostatnie dwie linię z innym adresem. jeżeli chcemy zrobić inny limit dla innego adresu - tworzymy ponownie nowy zestaw reguł. Adres IP można uzyskać np. z tablicy ARP, choć lepiej zrobić tzw. static dhcp aby mieć pewność że adres IP dla danego MAC będzie zawsze taki sam.

W/w polecenia obowiązują tylko do restartu routera, identycznie jak zliczone limity. Jeżeli chcemy aby przetrwało restart sprzętu to musimy takie reguły tworzyć podczas startu/restartu firewalla. I tu mamy kolejny problem - restart firewalla powoduje wyzerowanie liczników, więc musimy także wstępnie ustawić zawartość liczników oraz cyklicznie je backupować. Więc teraz będzie ta trudniejsza cześć:
- skrypt backupujący


    # touch /usr/bin/quotas-backup.sh
    # chmod 755 /usr/bin/quotas-backup.sh


Robimy edycję pliku (vi /usr/bin/quotas-backup.sh), wpisujmy:


    #!/bin/sh
    RULE=mac010203040506_limit50mb

    O=$(iptables -vxnL $RULE 2>/dev/null)
    PKTS=0
    BYTES=0
    if [ -n "$O" ]; then
        PKTS=$(iptables -vxnL $RULE | awk '/quota:/{print $1}')
        [ -z "$PKTS" ] && PKTS=0
        BYTES=$(iptables -vxnL $RULE | awk '/quota:/{print $2}')
        [ -z "$BYTES" ] && BYTES=0
    fi
    FBACKUP=/etc/quotas
    touch $FBACKUP
    sed -i '/'$RULE'/d' $FBACKUP
    echo "$RULE $PKTS $BYTES" >> $FBACKUP
    exit 0

Skrypt sprawdza czy istnieje określony łańcuch w iptables, jeżeli tak to pobiera liczbę pakietów i bajtów przesłanych a następnie wraz z nazwą łańcucha zapisywane jest to do pliku /etc/quotas. Można sprawdzić działanie programu po prostu go wykonując:


    # quotas-backup.sh
    # cat /etc/quotas

Jeżeli widzimy nazwę łańcucha (w tym przypadku: mac010203040506_limit50mb) oraz dwie wartości (lub zera jeżeli nie było łańcucha) to wszystko działa. Teraz wystarczy umieścić wywołanie programu w cronie. Żeby nie zniszczyć flash routera będziemy dane backupować co 15 min:


    # echo "*/15 * * * * /usr/bin/quotas-backup.sh" >> /etc/crontabs/root
    # /etc/init.d/cron restart

- skrypt odtwarzający regułę i backup
Do pliku /etc/firewall.user dopisujemy:


    RULE=mac010203040506_limit50mb

    touch /etc/quotas
    PKTS=$(awk '/'$RULE'/{print $2}' /etc/quotas)
    [ -z "$PKTS" ] && PKTS=0
    BYTES=$(awk '/'$RULE'/{print $3}' /etc/quotas)
    [ -z "$BYTES" ] && BYTES=0
    QUOTA=$((50 * 1024 * 1024))
    iptables -N $RULE
    iptables -A $RULE -m quota --quota $QUOTA --set-counters $PKTS $BYTES -j ACCEPT
    iptables -A $RULE -j REJECT
    iptables -A forwarding_rule -j $RULE -s 192.168.1.100
    iptables -A forwarding_rule -j $RULE -d 192.168.1.100

Skrypt jest lekką przeróbką w/w przykładu - z pliku z backupem odczytujemy wartości pakietów i bajtów dla danej reguły a następnie ją tworzymy ustawiając wstępnie liczniki. Wykonujemy ostatni test:


    # /etc/init.d/firewall restart
    # iptables -vxnL mac010203040506_limit50mb

Jeżeli wyświetliła się reguła z ustawionymi licznikami to zrobiliśmy właśnie limit transferu. Należy pamiętać że w cronie musi być jeszcze zerowanie limitu co określony czas, np raz dziennie:


    # echo "0 0 * * * iptables -Z mac010203040506_limit50mb " >> /etc/crontabs/root
    # /etc/init.d/cron restart


Jeżeli chcemy dodać jeszcze trochę transferu po blokadzie to należy albo wykonać to samo polecenie co wykonuje cron (iptables -Z mac010203040506_limit50mb - spowoduje to odnowienie limitu) albo zmienić plik /etc/quotas zmniejszając liczniki bajtów przy danej regule i restartując firewalla.

W powyższych regułach używamy adresu IP a nie MAC. Aby uzyskać bieżący adres IP mają zdefiniowany static dhcp:


    # uci -q get dhcp.$(uci show dhcp | grep '01:02:03:04:05:06' | cut -f2 -d.).ip

Limity przepustowości

Generalnie mówimy tu o tzw. QoS, temat został poruszony w innym dokumencie. Rozwiązań i skryptów jest bardzo wiele, choć wszystkie bazują na zestawie narzędzi tc/ip/iptables.

Zakończenie

Praktycznie każde przedstawione ograniczenie można dość szybko wyłączyć i włączyć wg potrzeb, więc można zrealizować funkcjonalność "kuponów" zezwalających na dalszy dostęp do internetu.
Należy także pamiętać o tym że limity są usuwanie w cronie o określonej godzinie. Jeżeli router jest wyłączony w tym czasie to limity mogą nadal obowiązywać następnego dnia - nie będą odnowione.
Każda blokada powoduje odcięcie urządzenia od internetu co kończy się lakonicznym komunikatem "brak połączenia" w przeglądarce. Jeżeli mamy ręcznie tworzone reguły blokowania to można pokusić się o zrobienie prostego przekierowania na lokalną stronę www zawierającą informację o powodzie zablokowania.