Netfilter forma part del nucli de Linux i permet manipular els diferents paquets de xarxa i el seu flux de processament.

Introducció

Netfilter proporciona diferents “hooks” (punt d’anclatge) en el flux de processament que et permet registrar funcions per aplicar als paquets de xarxa en aquell punt concret de processament.

Netfilter permet filtrar en múltiples nivells de xarxa:

4 TransportPortTCPUDP
3 NetworkAddressIPv4IPv6ARPICMP
2 Data LinkVLANEthernetPPP

nftables forma part del projecte netfilter i és la utilitat que et permet interactuar amb nefilter mitjançant l’eina nft.

Entorn de treball

Crea *dues màquines server a Isard.

Ha de tenir les interficies "Default", "Wireguard VPN" i "provençana1".

Configura la interficie "provençana1" amb la IP 10.0.0.X/24 on X és el teu número de PC, amb increments de +50.

Per exemple 10.0.0.10 i 10.0.0.60.

$ sudo ip addr add dev enp3s0 10.0.0.x/24
$ sudo ip link set dev enp3s0 up

Modifica el nom del host:

$ sudo hostnamectl set-hostname box-1

Surt i torna a entrar a la màquina.

Tables

Una taula a nftables és un espai de noms que conté una col·lecció de chains, sets, maps, flowtables, i stateful objects.

Family

Cada taula ha de tenir assignat una única family d’adreces que defineix els tipus de paquets que processa aquesta taula.

Quan crees una taula pots escollir entre aquestes famílies d'adreces:

ip La taula filtra paquets IPv4.
ip6 La taula filtra paquets IPv6.
inet La taula filtra paquets IPv4 i IPv6, i pots utilitzar una única regla pels dos tipus de paquets en el que tenen en comú, per exemple el port.
arp La taula filtra paquets ARP a nivell 2, abans que el kernel faci qualsevol gestió a nivell 3.
bridge Un pont conecta dos segments Ethernet i els paquets es reenvien en funció de l'adreça Ethernet. Com que el reenviament es fa a la capa 2, aquest encaminament és transparent als protocols de capa superior com pot ser el protocol IP.
netdev La taula s’enllaça directament a una única interficie de xarxa enlloc de un protocol.
ingress Fa el mateix que netdev, però més fàcil de configurar (des del kernel 5.10).

No hi ha taules predeterminades per a nftables.

Per tant, l'ordre list tables no torna cap resultat si no has definit cap taula.

$  sudo nft list tables

UFW

A UFW vas veure que per defecte UFW està deshabilitat.

Crea un firewall amb UFW:

$ sudo ufw allow 22
$ sudo ufw enable

Pots verificar que s'han creat dues taules amb el nom filter:

$ sudo nft list tables
table ip filter
table ip6 filter

Encara que les dos taules tenen el mateix nom pertanyen a famílies diferents: ip i ip6.

En la taula filter està la regla que permet connexions entrants (dport) al port 22:

$ sudo nft list table filter | grep "dport 22"
                tcp dport 22 counter packets 0 bytes 0 accept
                udp dport 22 counter packets 0 bytes 0 accept

Deshabilita el firewall

$ sudo ufw disable

Eliminar les taules filter:

$ sudo nft delete table filter
$ sudo nft list tables
table ip6 filter

Com que per defecte la família és ip si no indiques altre cosa, només s'ha borrat la taula ip filter,

Borra la taula ip6 filter:

$ sudo nft delete table ip6 filter
$ sudo nft list tables

filter

Quan crees una taula li pots posar qualsevol nom, però per defecte és costum que el nom sigui filter com fa UFW.

Crea el fitxer test.nft:

#!/usr/sbin/nft -f

table ip filter {

}

Executa el fitxer:

$ chmod u+x ./test.nft
$ sudo ./test.nft

Tal com hem vist abans, un cop s'ha afegit la taula l'ordre list tables retorna el nom de la taula:

$ sudo nft list tables
table ip filter

Pots mostrar la informació d'una taula concreta:

$ sudo nft list table filter
table ip filter {
}

Chains

Com has vist a UFW, un firewall és un conjunt de regles que es processen per decidir que és fa amb un paquet.

Però les regles no s’afegeixen directament a les taules sinó a les cadenes.

Una cadena s’enllaça a un dels hooks que proporciona netfilter per tal de poder filtrar els paquets de xarxes en punts diferents del seu processament.

hooks

Els "hooks" que pots utilitzar al configurar una cadena base, i que et permeten manipular els paquets en un punt concret, són aquests:

ingress El paquet acaba se ser processat per la controladora de la NIC
prerouting El paquet no ha estat acceptat pel "flowtable" i està pendent de ser encaminat (a una altre host) o no.
input El paquet es processarà al mateix host i està pendent de ser processat pel procés corresponent.
forward El paquet no es processarà al host
output El paquet s'ha originat per un procés del host.
postrouting El paquet està a punt de sortir del host.

Però no tots els paquets passen per tots aquests hooks: no és el mateix processar un paquet ARP que un paquet IP.

Un paquet ARP només passa pels "hooks" input i output (a més de l'ingress), no hi ha “routing”.

flowchart LR
    S(("ARP")) --> I[input] --> H@{shape: fr-rect, label: "Process"} --> O[ouput]--> E(("ARP"))
    style S fill:#ffaa50
    style E fill:#ffaa50

En canvi, un paquet IP passa per tots els “hooks”:

flowchart LR
    S(("IP")) --> Pre[Prerouting] --> R{Routing} -- no --> I[Input] --> Process@{shape: fr-rect, label: "Process"} --> O[ouput] --> Post
    R -- yes --> F[Forward] --> Post[Postrouting] --> E(("IP"))
    style S fill:#ffaa50
    style E fill:#ffaa50

base chain

Una cadena base s'ha d'enllaçar a alguna d’aquestes fases (o “hooks”).

Afegeix una cadena input a la taula filter:

#!/usr/sbin/nft -f

table ip filter {
    chain input {
        type filter hook input priority 0
    }
}

Verifica que la taula s'ha configurat com tu vols:

sudo nft list table filter
table ip filter {
        chain input {
                type filter hook input priority filter; policy accept;
        }
}

Has creat un cadena “input” que s'enllaça al hook input.

flowchart LR
    S(("IP")) --> Pre[Prerouting] --> R{Routing} -- no --> I[Input] --> Process@{shape: fr-rect, label: "Process"} --> O[ouput] --> Post
    R -- yes --> F[Forward] --> Post[Postrouting] --> E(("IP"))
    style I fill:#f44
    style S fill:#ffaa50
    style E fill:#ffaa50

Per tant, veuràs tots els paquets IPv4 que no s'encaminen a una altre adreça.

default policy

Totes les cadenes "base" tenen una política per defecte que es pot especificar al crear la cadena.

Aquesta és la política que s’aplicarà a un paquet si no hi ha cap regla en la cadena que decideixi que fer amb un paquet.

Si no dius res, per defecte la cadena es crea amb policy accept.

Només pots escollir entre dues polítiques predeterminades:

Només pots escollir entre dues polítiques predeterminades:

accept El paquet seguirà el seu camí.
drop El paquet serà eliminat.

Pots canviar la política per defecte d’una cadena:

#!/usr/sbin/nft -f

table ip filter {
    chain input {
        type filter hook input priority 0; policy drop;
    }
}

Actualitza nftables:

$ sudo ./test.nft

A partir d'ara nftables no permet cap paquet d’entrada i perds la connexió a la màquina virtual.

Per sort, aquesta configuració només funciona mentre la màquina està funcionat.

Apaga la màquina, arranca de nou i connecta't.

Verifica que no hi ha cap taula:

$ sudo nft list tables

output hook

També pots registrar la cadena de sortida:

#!/usr/sbin/nft -f

table ip filter {
    chain input {
        type filter hook input priority 0  
    }
    chain output {
        type filter hook output priority 0
    }
}

Output chain s’enganxa al hook output i veurà els paquets de sortida un cop s’han processat:

flowchart LR
    S(("IP")) --> Pre[Prerouting] --> R{Routing} -- yes --> I[Input] --> Process@{shape: fr-rect, label: "Process"} --> O[ouput] --> Post
    R -- no --> F[Forward] --> Post[Postrouting] --> E(("IP"))
    style I fill:#f44
    style O fill:#f44
    style S fill:#ffaa50
    style E fill:#ffaa50

ingress hook

A diferència dels “hooks” anteriors, un ingress hook és específic d’un dispositiu de xarxa.

Has de crear una taula de la familia netdev:

#!/usr/sbin/nft -f

table ip filter {
    chain input {
        type filter hook input priority 0  
    }
    chain output {
        type filter hook output priority 0
    }
}

table netdev filter {
    chain enp2s0 {
        type filter hook ingress device "enp2s0" priority 0
    }
}

Com que la cadena és específica d’un dispositiu de xarxa, és obligatori especificar el dispositiu on s'enganxarà la cadena.

type

Totes les cadenes que hem creat són de tipus filter:

$ sudo nft list chain filter input
table ip filter {
        chain input {
                type filter hook input priority filter; policy accept;
        }
}

Aquest tipus es fa servir per filtrar paquets de les families arp, bridge, ip, ip6 o inet.

L'altre tipus és route que es fa servir per redirigir els paquets en base a un camp de la capçalera IP o si es modifica la marca del paquet.

El tipus route només es pot fer servir amb les famílies ip, ip6 o inet

priority

Si defineixes més d’una cadena pel mateix “hook” el valor priority indica quina s’utilitzarà primer.

Quan més petit sigui el número, la cadena s’executarà abans.

Afegeix una cadena input-ssh que anirà abans que la cadena input:

#!/usr/sbin/nft -f

table ip filter {
    # ...
    chain input-ssh {
        type filter hook input priority -1  
    }
}

# ...

Rules

Les regles prenen mesures sobre els paquets de xarxa (p. ex. acceptant-los o deixant-los caure) en funció de si coincideixen amb els criteris especificats.

Cada regla consta de zero o més expressions seguides d'una o més declaracions:

Expressions: Cada expressió prova si un paquet coincideix amb un camp de càrrega útil específic o metadades de paquet/flux. Les expressions múltiples s'avaluen linealment d'esquerra a dreta: si la primera expressió coincideix, llavors s'avalua la següent expressió i així successivament.

Si arribem a l'expressió final, aleshores el paquet coincideix amb totes les expressions de la regla i s'executen les declaracions de la regla.

Declaracions Cada declaració fa una acció, com ara establir la marca netfilter, comptar el paquet, registrar el paquet o emetre un veredicte com acceptar o deixar el paquet o saltar a una altra cadena.

Igual que amb les expressions, diverses declaracions s'avaluen linealment d'esquerra a dreta: una sola regla pot fer diverses accions utilitzant diverses sentències.

Tingues en compte que una declaració de veredicte per la seva naturalesa posa fi a la regla.

Afegeix una regla a la cadena output:

    chain output {
        type filter hook output priority 0
        ip daddr 8.8.8.8 counter
    }
  • ip daddr 8.8.8.8 és una expressió que ha de fet match: paquets IP que tenen com adreça destí 8.8.8.8 (daddr = destination address)

  • counter és una declaració: va contant el número de bytes que han activat la regla.

Quan enumeres les regles d’una cadena pots utilitzar modificadors per traduir adreces IP a noms DNS, protocols TCP, etc.

$ sudo nft -N list chain filter output
table ip filter {
        chain output {
                type filter hook output priority filter; policy accept;
                ip daddr dns.google counter packets 0 bytes 0
        }
}

Afegir un comptador és una tècnica molt útil per verificar si una regla funciona:

Fes un ping a 8.8.8.8 i verifica que la regla s’ha activat:

$ ping -c 1 8.8.8.8 > /dev/null
$ sudo nft list chain filter output
table ip filter {
        chain output {
                type filter hook output priority filter; policy accept;
                ip daddr 8.8.8.8 counter packets 1 bytes 84
        }
}

Un comptador compta tant el nombre total de paquets com el total de bytes que ha vist des de l'últim restabliment.

Fes un ping a dns.google i verifica que la regla s’ha activat:

$ ping -c 1 dns.google > /dev/null
$ sudo nft list chain filter output
table ip filter {
        chain output {
                type filter hook output priority filter; policy accept;
                ip daddr 8.8.8.8 counter packets 2 bytes 168
        }
}

Fes un ping a 1.1.1.1 i verifica que la regla no s’ha activat:

$ ping -c 1 1.1.1.1 > /dev/null
$ sudo nft list chain filter output
table ip filter {
        chain output {
                type filter hook output priority filter; policy accept;
                ip daddr 8.8.8.8 counter packets 2 bytes 168
        }
}

Afageix la regla repetida:

    chain output {
        type filter hook output priority 0
        ip daddr 8.8.8.8 counter
        ip daddr 8.8.8.8 counter
    }

Si torne a fer un ping a google pots veure que s'han activat les dos regles:

$ ping -c 1 dns.google > /dev/null
$ sudo nft list chain filter output
table ip filter {
        chain output {
                type filter hook output priority filter; policy accept;
                ip daddr 8.8.8.8 counter packets 4 bytes 336
                ip daddr 8.8.8.8 counter packets 1 bytes 84

Les regles es processen de dalt a baix.

Elimina el comptador de la primera regla i torna a fer un ping:

$ nano test.nft
$ sudo ./test.nft
$ ping -c 1 dns.google > /dev/null
$ sudo nft list chain filter output
table ip filter {
        chain output {
                type filter hook output priority filter; policy accept;
                ip daddr 8.8.8.8 counter packets 5 bytes 420
                ip daddr 8.8.8.8 counter packets 2 bytes 168
                ip daddr 8.8.8.8
                ip daddr 8.8.8.8 counter packets 1 bytes 84

El que has fet és afegir dos regles noves!

El que fa el fitxer és afegir regles, etc.

Modifica el fitxer:

#!/usr/sbin/nft -f

flush ruleset

table ip filter {
    chain output {
        type filter hook output priority 0
        ip daddr 8.8.8.8 counter
        ip daddr 8.8.8.8 drop
        ip daddr 8.8.8.8 counter
    }
}

Pots verur que el primer que fem és eliminar-ho tot amb flush ruleset 🙂!

Però les regles no estan només per contar paquets, sinó per acceptar o rebutjar paquets amb drop.

Si fas de nou un ping l'última regla no es processa i el comptador no s'activa.

$ ping -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

isard@box-1:~$ sudo nft list chain filter output
table ip filter {
        chain output {
                type filter hook output priority filter; policy accept;
                ip daddr 8.8.8.8 counter packets 2 bytes 168
                ip daddr 8.8.8.8 drop
                ip daddr 8.8.8.8 counter packets 0 bytes 0
        }
}

Com que les regles es processen de dalt a baix, la segona regla elimina el paquet: el paquet ja no és processa més en la cadena de regles i no serà acceptat per continuar el seu camí i ser enviat a 8.8.8.8.

Comentaris i variables

A continuació modifica l'script afegint comentaris i variables:

#!/usr/sbin/nft -f

flush ruleset

# Google Public DNs
define dns_google = { 8.8.8.8, 8.8.4.4}

table ip filter {
    
    chain output {
        type filter hook output priority 0
        ip daddr $dns_google counter
        counter
    }
}

Pots veure que els comentaris han desaparegut i enlloc de les variables hi ha els valors definits a les variables:

$ sudo nft list chain filter output
table ip filter {
        chain output {
                type filter hook output priority filter; policy accept;
                ip daddr { 8.8.4.4, 8.8.8.8 } counter packets 0 bytes 0
                counter packets 766 bytes 68036
        }
}

Fes un ping a 8.8.8.8 i un altre a 8.8.4.4, i verifica que la regla ip daddr { … } funciona:

$ ping -c 1 8.8.8.8 > /dev/null
$ sudo nft list chain filter output
table ip filter {
        chain output {
                type filter hook output priority filter; policy accept;
                ip daddr { 8.8.4.4, 8.8.8.8 } counter packets 1 bytes 84
                counter packets 1660 bytes 151800
        }
}
...

ssh

Mira quina és la IP origen de la connexió ssh:

$ sudo tcpdump -i any port 22 
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
22:07:59.385459 enp2s0 Out IP box-1.ssh > 10.0.29.109.50414: Flags [P.], seq 4286926479:4286926555, ack 1031133820, win 436, options [nop,nop,TS val 665503130 ecr 2743213119], length 76
...

En el meu cas la IP és 10.0.29.109.

Modifica l'script per tal de que només permeti connexions ssh desde la màquina WSL:

#!/usr/sbin/nft -f

flush ruleset

define wsl = 10.0.29.109

table ip filter {
    chain input {
        type filter hook input priority 0
        tcp dport 22 ip saddr != $wsl drop
   }
}

La regla ssh funciona, sinó ara mateix hauries perdut la connexió.

Pots veure que la regla té:

  • 2 expressions: tcp dport 22 i ip saddr != 10.0.29.109
  • 1 declaració: drop

Verifica que desde la màquina box-2 ja no et pots conectar per ssh.

box-2$ ssh isard@10.0.0.50
ssh: connect to host 10.0.0.50 port 22: Connection timed out

Avançat

systemd

nftables és un servei del sistema que es controla a través de systemd.

El servei executa l’script /etc/nftables.conf.

Reemplaça el fitexer nftables.conf:

sudo cp test.nft /etc/nftables.conf 

Arrenca el firewall amb systemctl:

sudo systemctl enable --now nftables
Created symlink /etc/systemd/system/sysinit.target.wants/nftables.service → /usr/lib/systemd/system/nftables.service.

A partir d'ara sempre que arrenquis la màquina el firewall estarà actiu amb aquesta configuració.

Per aquest motiu les proves sempre les has de fer amb el fitxer test.nft.

Activitat

1.- Modifica el firewall perquè per defecte només accepti connexions d'entrada ssh:

#!/usr/sbin/nft -f

flush ruleset

define wsl = 10.0.29.109

table ip filter {
    chain input {
        type filter hook input priority 0; policy drop
        iifname "lo" accept
        tcp dport 22 ip saddr == $wsl accept 
   }
}

Has d'afegir la regla iifname "lo" accept perquè alguns serveis bàsics del sistema operatiu funcionen amb la interfície "loopback", com per exemple, la resolució de noms.

2.- Arrenca un servidor nginx i verifica que ni la màquina wsl ni box-2 poden accedir al servidor.

$ curl 10.0.0.50
curl: (28) Failed to connect to 10.0.0.50 port 80 after 134542 ms: Couldn't connect to server

3.- Configura nftables perquè també accepti connexions entrants tcp al port 80:

table ip filter {
    chain input {
        type filter hook input priority 0; policy drop
        iifname "lo" accept
        tcp dport 22 ip saddr == $wsl accept 
        tcp dport 80 accept
   }
}

4.- Verifica que encara que acceptes els paquets destinats al port 80, no et pots connectar als servidors http d'altres màquines:

$ curl google.es
curl: (6) Could not resolve host: google.es

Tracing

Quan tenim un problema amb la configuració de nftables a més d’utilitzar un contador (“counter”) per veure que una regla s’ha activat podem fer un “tracing”.

Afegeix al final de la cadena l’expressió: meta nftrace set 1

table ip filter {
    chain input {
        type filter hook input priority 0; policy drop
        iifname "lo" accept
        tcp dport 22 ip saddr == $wsl accept 
        tcp dport 80 accept
        meta nftrace set 1
   }
}

Torna a carregar nftables i comença a monitoritzar l’entrada de paquets: nft monitor trace.

$ sudo nft monitor trace

Pots veure que la regla no s'activa (els paquest ssh sòn acceptats abans que li toqui el torn a aquesta regla).

Peró si executes curl https://10.0.0.50, llavors el paquet si que arriba a l'última regla:

$ sudo nft monitor trace
trace id 925a4f61 ip filter input packet: iif "enp3s0" ether saddr 52:54:00:6b:76:33 ether daddr 52:54:00:15:2f:a1 ip saddr 10.0.0.100 ip daddr 10.0.0.50 ip dscp cs0 ip ecn not-ect ip ttl 64 ip id 13968 ip length 60 tcp sport 49464 tcp dport 443 tcp flags == syn tcp window 64240 
trace id 925a4f61 ip filter input rule meta nftrace set 1 (verdict continue)
trace id 925a4f61 ip filter input policy drop 
...

Ara podem provar perquè no funciona curl google.es.

Obre una altre terminal:

box-1$ curl google.es
curl: (6) Could not resolve host: google.es

Pots veure que el firewall no accepta el paquet de tornada de saddr 8.8.8.8:

$ sudo nft monitor trace
trace id 2f05a9dc ip filter input packet: iif "enp1s0" ether saddr 52:54:00:47:b3:69 ether daddr 52:54:00:0c:0b:8a ip saddr 8.8.8.8 ip daddr 192.168.122.123 ip dscp cs0 ip ecn not-ect ip ttl 111 ip id 12106 ip length 71 udp sport 53 udp dport 42566 udp length 51 @th,64,96 0xabae81800001000100000000 
trace id 2f05a9dc ip filter input rule meta nftrace set 1 (verdict continue)
trace id 2f05a9dc ip filter input policy drop 
...

conntrack

Fins ara estem aplicant regles sense estat: només tenim en compte el paquet que estem filtrant.

Però hi ha molts paquets que estan relacionats perquè pertanyen a una mateixa connexió.

Recordes que al principi al explicar el hooks hem parlat de la “flowtable”?

Netfilter té un sistema de seguiment de connexions que permet rastrejar quins paquets pertanyen a la mateixa connexió.

Pots afegir aquesta regla a la cadena input: ct state established,related accept

table ip filter {
    chain input {
        type filter hook input priority 0; policy drop
        iifname "lo" accept
        ct state established,related accept
        tcp dport 22 ip saddr == $wsl accept 
        tcp dport 80 accept
        meta nftrace set 1
   }
}

Aquesta regla diu que:

  • ct - És una expressió de tipus “connection tracking”
  • state - Fem un tracking de l'estat de la connexió
  • established, related — Són els estats de conntrack que volem fer “match”

Ara ja funciona:

$ curl -s google.es | head -1
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">

docker

Contiua llegint aquest document

Expressions

En aquesta secció veurem diferents expressions per fet “match” en els paquets que s'estan filtrant.

Metainformació

Un meta-selector et permet fer match respecte informació que el host local té sobre el paquet (com ara com o quan es va rebre) i que no necessàriament està en el mateix paquet.

També en alguns casos pots incorporat metainformació tal com s'explica a Setting packet metainformation.

Nosaltres només veurem uns quants selectors, però en aquest enllaç tens tota la informació al respecte: Matching packet metainformation.

Socket UID / GID

Pots fer un match per uid o gid de l'usuari que ha creat el socket mitjançant les keywords skuid i skgid.

A continuació:

  • Crea un nou usuari alice.
  • Configura el firewall perquè no permeti a l'usuari alice realitzar connexions de sortida:
table ip filter {
    chain output {
        type filter hook output priority 0 
        meta skuid alice drop
   }
}

Verifica que el firewall funciona:

$ ping -c 1 google.es | head -1
PING google.es (142.250.186.35) 56(84) bytes of data.

$ su -c "ping -c 1 google.es" alice
Password: 
ping: google.es: Temporary failure in name resolution

Modifica el firewall perquè faci servir el uid de l'usuari enlloc del seu nom, i verifica que funciona.

time

També pots fer match per time, day o hour.

D'aquesta manera pots controlar l'accés a determinats serveis en hores determinades o generar un log d'accéss en hores o dies no habituals.

A continuació tenim una regla que no permet cap connexió de sortida entre les 00:00 i les 06:00:

table ip filter {
    chain output {
        type filter hook output priority 0
        meta hour 00:00-06:00 drop
   }
}

Modifica l'hora del sistema perquè s'activi la regla:

$ sudo timedatectl set-ntp no
$ sudo timedatectl set-time '05:59:00'

Headers

La informació completa està a Matching packet headers.

Ethernet headers

La informació de nivell 2 només està disponible en el camí d'entrada (ingress, prerouting, input)

Modifica el fitxer index.html del servidor nginx perquè respongui amb "Hello from box-1".

Verifica que box-2 té accés al servidor:

box-2$ curl box-1
Hello from box-1

Mira quina és l'adreça MAC de box-2:

$ cat /sys/class/net/enp3s0/address
52:54:00:6b:76:33

Modifica el firewall perquè no permeti connexions de box-2 mitjançant un filtre MAC:

table ip filter {
    chain input {
        type filter hook input priority 0
        ether saddr 52:54:00:6b:76:33 drop
   }
}

Verifica que la regla funciona:

box-2$ curl box-1
curl: (28) Failed to connect to 10.0.0.50 port 80 after 133377 ms: Couldn't connect to server

IPv4

També pots fer un match segons l'adreça IPv4 origen i destí:

table ip filter {
    chain input {
        type filter hook input priority 0
        ip saddr 10.0.0.100 drop
   }
}

Verifica que box-2 no és pot conectar ni per HTTP ni per ping.

Modifica la IP de box-2 per evitar el firewall:

Enlloc d'eliminar els paquets entrants de la màquina box-1 pots eliminar els de sortida a la màquina box-1 mitjançant una cadena output.

Modifica el firewall i verifica que box-2 tampoc és pot conectar ni per HTTP ni per ping.

ICMP

Si vols pots eliminar totes les sol·licituds d'eco ICMP (conegudes popularment com a pings):

table ip filter {
    chain input {
        type filter hook input priority 0
        icmp type echo-request drop
   }
}

Verifica que funciona:

$ ping 10.10.0.50
PING 10.10.0.50 (10.10.0.50) 56(84) bytes of data.
^C

Pots utilitzar nft describe per trobar les paraules clau de tipus icmp disponibles.

Transport protocol

Per filtrar en un protocol de capa 4 com TCP, pots fer-ho amb aquesta regla:

table ip filter {
    chain input {
        type filter hook input priority 0
        ip protocol tcp drop
   }
}

Verifica que la màquina client pot fer ping, però no curl.

wsl$ ping box-1
PING box-1 (10.2.39.218) 56(84) bytes of data.
64 bytes from box-1 (10.2.39.218): icmp_seq=1 ttl=62 time=36.5 ms
...

Torna a arrencar la màquina box-1 per poder conectar-te amb ssh.

TCP/UDP

Pots crear un regla per eliminar tots el paquets TCP en un rang de ports:

table ip filter {
    chain output {
        type filter hook output priority 0
        tcp dport 100-1024 drop
   }
}

Amb aquesta regla server no pot establir una connexió HTTPS:

$ curl https://www.google.com
^C

També pot utilitzar el conjunt anònim l4proto i un expressió th (transport header) per fer match a la vegada de paquets TCP i UDP dirigits al port 53 (DNS ) - el protocol fa servir els dos ports.

table ip filter {
    chain output {
        type filter hook output priority 0
        meta l4proto { tcp, udp } th dport 53 drop
   }
}

Pots veure que funciona, encara que el que està bloquejant és el local resolver cache:

$ nslookup google.com
;; communications error to 127.0.0.53#53: timed out
...

Connection tracking

La informació completa està a: Matching connection tracking stateful metainformation

Les expressions conntrack (ct) permeten configurar tallafocs amb estat de tal manera que es poden identificar tots els paquets que pertanyen a una mateixa connexió.

Modifica el firewall per fer un tracking de les connexions:

table ip filter {
    chain input {
        type filter hook input priority 0
        ct state established, related accept
   }
}

Pots veure el tracking de la connexió ssh:

$ sudo apt install -y conntrack
$ sudo conntrack -L -o id,extended
ipv4     2 tcp      6 299 ESTABLISHED src=10.0.29.109 dst=10.2.39.218 sport=50454 dport=22 src=10.2.39.218 dst=10.0.29.109 sport=22 dport=50454 [ASSURED] mark=0 use=1 id=1189582752
ipv4     2 udp      17 29 src=127.0.0.1 dst=127.0.0.53 sport=59490 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=59490 mark=0 use=1 id=3172624988
ipv4     2 tcp      6 75 TIME_WAIT src=192.168.122.122 dst=141.30.62.22 sport=38194 dport=80 src=141.30.62.22 dst=192.168.122.122 sport=80 dport=38194 [ASSURED] mark=0 use=1 id=3291912715
conntrack v1.4.8 (conntrack-tools): 3 flow entries have been shown.

Rate limiting

Pots limitar el trànsit per paquets o bytes mitjançant limit.

Per packet

L'exemple següent mostra una regla que només permet com a màxim 1 paquet per segon de sol·licituts HTTP – és un exemple! :

table ip filter {
    chain input {
        type filter hook input priority 0
        ct state established, related accept
        tcp dport 80 limit rate over 1/second counter drop
   }
}

Modifica la pàgina del servidor de box-1:

$ echo "Server box-1" | sudo tee /var/www/html/index.html

Pots verificar que el firewall limita el número de paquets que s’admeten per segon protegint al servidor nginx de que el saturin amb peticions HTTP:

box-2$ TIMEFORMAT=%R
box-2$ $ time curl box-1
Server box-1
0.097        
box-2$ time curl box-1
Server box-1
1.087        
box-2$ time curl box-1
Server box-1
7.298 

Per bytes

També pots limitar pel número de bytes per tal de limitar l’ample de banda del servidor.

Baixa el llibre “War and Peace”:

box-1$ sudo curl https://www.gutenberg.org/files/2600/2600-0.txt -o /var/www/html/war-and-peace.txt

Modifica el firewall:

table ip filter {
    chain input {
        type filter hook input priority 0
        ct state established, related accept
        tcp dport 80 limit rate over 100bytes/second counter drop
   }
}

Verifica que el firewall limita la velocitat de baixada de fitxers:

$ time curl -o /dev/null box-1/war-and-peace.txt

Sentències

A continuació veurem algunes sentències que actuen sobre els paquets que han fet “match” en una regla.

Rejecting traffic

Enlloc d’eliminar un paquet el pots rebutjar:

table ip filter {
    chain input {
        type filter hook input priority 0
        tcp dport 80 reject
   }
}

Pots verificar que el servidor informa al client que rebutja la connexió amb un paquet “port unreachable”:

No funciona amb wireguard

$ curl box-1
curl: (7) Failed to connect to box-1 port 80 after 44 ms: Couldn't connect to server

Si vols pots canviar el motiu pel qual no s’accepta la connexió:

table ip filter {
    chain input {
        type filter hook input priority 0
        tcp dport 80 reject with icmp type host-unreachable
   }
}

Logging traffic

Pots tenir un registre dels paquets que s’han processat mitjançant l'acció log.

Aquesta és la regla més senzilla que pots utilitzar:

Si fas una petició HTTP desde el client pots veure que el paquet queda registrat al syslog:

$ sudo cat /var/log/kern.log | grep 10.0.0.100 | tail -1
2024-12-18T18:26:07.659843+00:00 box-1 kernel: IN=enp3s0 OUT= MAC=52:54:00:15:2f:a1:52:54:00:6b:76:33:08:00 SRC=10.0.0.100 DST=10.0.0.50 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=18982 DF PROTO=TCP SPT=42798 DPT=80 WINDOW=501 RES=0x00 ACK URGP=0

A continuació tens una regla típica de match, log i accept de trànsit ssh entrant per deixar registrades totes les conexions ssh que s’han fet al servidor:

table ip filter {
    chain input {
        type filter hook input priority 0
        tcp dport 22 ct state new log prefix "New SSH connection: "
   }
}

El prefix indica el string inicial que s'utilitza com a prefix per al missatge que es registra.

Si mires el registre pots veure que la teva connexió ssh ha quedat registrada (SRC=10.0.29.109):

$ sudo cat /var/log/kern.log | grep "New SSH"
2024-12-18T18:29:15.165023+00:00 box-1 kernel: New SSH connection: IN=enp2s0 OUT= MAC=52:54:00:50:12:3f:9a:4b:2c:ac:1f:67:08:00 SRC=10.0.29.109 DST=10.2.39.218 LEN=88 TOS=0x10 PREC=0x00 TTL=63 ID=27645 DF PROTO=TCP SPT=44160 DPT=22 WINDOW=15002 RES=0x00 ACK PSH URGP=0 

NAT

Continua llegint aquest document