OsGate.org Logo

OpenBSD's pf - openbsd packetfilter firewall

Networks Networks

Date 30.04.2010

Visits 3512

"OpenBSD offre nativamente un ottima soluzione per garantire il giusto grado di sicurezza della propria rete, pf o packet filter, il firewall incluso di default, é in grado di assicurare flessibilità ed affidabilità in maniera pratica e immediata. In questo OpenCont vedremo alcune operazioni basilari effetuabili con questo firewall."

Introduzione

Pf (pf - packet filter) è un firewall rilasciato sotto licenza BSD. Originariamente fu sviluppato per OpenBSD dove fece la sua prima comparsa nella versione 3.0 rilasciata il 1 dicembre 2001. Nel frattempo ne è stato effettuato il porting su altri sistemi operativi (FreeBSD, NetBSD, DragonFlyBSD e core force firewall per Windows basato su pf). Rientra nella tipologia di firewall packet filtering/stateful inspection ed è comparabile a iptables per linux.

Controlli di base

Esistono un paio di cose da controllare per far funzionare pf. Controllare per prima cosa che sia abilitato l'IP forwarding nel file /etc/sysctl.conf e decommentate/aggiungete la linea:

net.inet.ip.forwarding=1

pf.conf

pf.conf è il file di configurazione principale del firewall dove sono contenute tutte le regole scritte da noi per gestire il traffico. Questo file esiste già sul sistema e si consiglia di crearne uno nuovo e rinominare quello esistente in pf.conf.old, anche perchè quest'ultimo contiene già una configurazione base da cui poter iniziare. Errori di configurazione o di ortografia possono compromettere il funzionamento del firewall quindi controllate sempre che tutto sia corretto.

Il file contiene una serie di elementi per il corretto funzionamento del firewall:

  • macro e list
  • table
  • option
  • scrub
  • queueing
  • translation rules
  • filtering rules

Tutte le sezioni, eccetto macros e tables, devono apparire nell'ordine descritto sopra all'interno di pf.conf. Le linee bianche all'interno del file verranno ignorate mentre quelle che iniziano con "#" rappresentano i commenti.

Macros e Lists

Macros

Le macros non sono nient'altro che delle variabili utilizzabili all'interno delle regole. Un classico esempio d'utilizzo di una macro è quello riguardante le interfacce interna ed esterna:

int_if = "fxp0"
ext_if= "fxp1"

Per richiamare la variabile all'interno di una regola si usa il simbolo del dollaro "$":

block in on $ext_if from any to $int_if.

Possiamo usare le macro anche per gli indirizzi IP:

subnet_one = "192.168.1.0/24"
pass in on $ext_if from $subnet_one port 143

Lists

Le list servono a semplificare il compito dell'amministratore di sistema evitando di riscrivere regole uguali con indirizzi IP diversi.

Per creare una lista si usano le macro abbinate alle parentesi graffe:

enemy = { 198.45.21.11, 13.168.2.1, 201.18.0.5 }
block in on $ext_if from $enemy port 80

Oppure possiamo usarla direttamente nella regola:

block in on $ext_if from { 198.45.21.11, 13.168.2.1, !201.18.0.5 } port 80

Nota: il "!" agisce come un NOT, quindi in questo caso lascerà passare il 201.18.0.5.

Nota: al momento del parsing della regola con una lists, il firewall creerà tante regole uguali quanti sono i valori separati da virgole nelle parentesi graffe.

Tables

Le tables servono per gestire gruppi di indirizzi IP e assomigliano molto alle liste. Rispetto a queste ultime però consumano meno memoria e tendono a usare meno il processore garantendo ottime prestazioni anche in caso dovessimo gestire tables con enormi quantità di indirizzi. Le tables si dichiarano con l'attributo "table".

Esempio:

table { 172.16.0.0/16 }
pass in on $ext_if from port 80

Esistono due attributi per le tables:

  • const: il contenuto della tabella non può essere modificato una volta che quest'ultima è stata creata. Se non viene specificato "const" è possibile aggiungere e rimuovere indirizzi alla tabella con pfctl on the fly
  • persist: forza il kernel a mantenere la tables in memoria anche se non ci sono più regole che vi fanno riferimento. Se non specificato il kernel rimuove la tables al momento del parsing dell'ultima regola in cui è contenuta.

Possiamo anche usare le tables con dei file:

table persist file "/etc/spammers"
block in on $ext_it from to 192.168.1.0/24

Il file /etc/spammers contiene una lista di indirizzi da bloccare.

Options

Le options servono per controllare i parametri di pf, per dichiararle si usa l'attributo "set". Se non vengono specificate assumeranno i valori di default. Ecco alcuni esempi.

Nome Descrizione Opzioni
set block-policy option Imposta il comportamento delle regole di tipo bloc.
  • drop: il pacchetto viene scartato in maniera silenziosa e trasparente
  • return: un pacchettp TCP RST viene inviato per i pacchetti TCP bloccati, mentre per tutti gli altri tipi di pacchetti verrà inviato un ICMP unreachable packet.
  • Default option: drop.
set loginterface option Imposta l'interfaccia di rete da dove bisogna raccogliere i log. Può essere usata solo un interfaccia alla volta.
  • Il nome dell'interfaccia
  • Default option: none
set optimization option Ottimizza pf per i vari ambienti di rete
  • normal: adatto per circostanze normali
  • high-latency: per reti con latenza alta
  • aggressive: fa scadere le connessioni dalla tabella di stato in maniera aggressiva. Può ridurre il consumo di memoria ma tuttavia si corre il rischio che vengano eliminate connessioni troppo presto.
  • conservative: è il contrario di aggressive. Mantiene le connessioni occupate. Occupa la CPU e la RAM.
set skip on interface Ignora completamente tutte le operazioni (filtering, queueing, ecc..) sull'interfaccia specificata. Solitamente si imposta sull'interfaccia di loopback. È possibile dichiarare questa option più volte.
  • Default option: none

Scrub

Tramite lo scrubbing dei pacchetti non si fa altro che normalizzare il traffico dei pacchetti in modo da evitare che vi siano dei problemi di interpretazione per il ricevente. Oltre a fare questo scrub riassembla pacchetti frammentati e scarta quelli con flag TCP non validi.

È solito usare scrub in questo modo:

scrub in all

Che esegue lo scrubbing sul traffico entrante su tutte le interfacce.

Scrub possiede delle opzioni come per esempio:

  • no-df: cancella il bit don't fragment dall'header del pacchetto IP. Da utilizzare per esempio quando si utilizzano sistemi non-OpenBSD che generano pacchetti con il bit don't fragment, che vengono opportunamente scartati da scrub, come quando si utilizza NFS.
  • min-ttl num: forza un minimo TTL nell'header del pacchetto IP.

Queueing e priorization

Tramite il queueing è possibile gestire il controllo della banda e dare priorità a determinati tipi di pacchetti. Attraverso uno schedulers è possibile gestire le code di pacchetti in entrata e uscita. Lo scheduler di default in OpenBSD è di tipo FIFO, ovvero processa i pacchetti in questo ordine: il primo che entra è il primo che esce.

Esistono altri due schedulers per gestire le code:

  • Class Based Queueing(CBQ)
  • Priority Based Queueing(PRIQ)

Class Based Queueing

Una Class Based Queueing è una coda basata sulla classe dove la banda disponibile viene divisa tra le classi assegnate alla coda. Il traffico viene assegnato ad ogni coda in base a parametri come la porta, il protocollo o l'indirizzo sorgente/destinazione. La struttura di una coda CBQ è la seguente:

Root (4Mbps)
Uno (2Mbps)
Due (1Mbps)
Tre (1Mbps)

All'interno della gerarchia è presenta la coda madre (root) che definisce la banda totale da utilizzare mentre al disotto troviamo le altre code. Tra parentesi è specificato l'ammontare di banda da utilizzare per quanto riguarda le altre code che, la somma della banda delle code figlie non supera mai quello della coda madre.

È possibile suddividere ulteriormente una coda:

Root (4Mbps)
SubnetA (3Mbps)
utenteA(1Mbps)
http(500Kbps)
ssh(500Kbps)
utenteB(2Mbps)
SubnetB (1Mbps)
secure (100Kbps)
ftp (900Kbps, borrow)

Qui viene introdotta anche l'opzione borrow. Questa opzione serve per prendere in prestito la banda non utilizzata nella coda subnetB. Se il traffico non raggiungesse 1Mbps e contemporaneamente la coda ftp eccedesse i 900kbps, la banda inutilizzata in subnetB sarebbe alloccata alla coda ftp. Al momento che la coda secure dovesse incrementare il traffico la banda presa in prestito sarà restituita. Esiste anche un sistema di priorità in caso di traffico congestionato nel CBQ:

Root (2Mbps)
Uno (1Mbps, priority 1)
http(400kbps, priority 2)
ftp(600kbps, priority 1)
Due (1Mbps, priority 1)

L'opzione priority imposta la priorità della coda, più il suo valore è basso prima verrà processata. In caso di priorità uguale come per la coda Uno e Due, verrà utilizzato il metodo round-robin per processare la coda, ovvero prima una e poi l'altra in modo alternato. La priorità degli oggetti più alti nella gerarchia verrà presa in considerazione prima.

Priority Based Queueing

Un sistema di code basato su priorità gestisce queste ultime tenendo in considerazione solamente la priorità assegnategli. A una coda madre vengono assegnate delle code indicandone le loro priorità di esecuzione.

Root (2Mbps)
Uno (priority 1)
Due (priority 2)
Tre (priority 3)

La coda con priorità più alta viene gestita per prima e cosi via. La banda assegnata viene utilizzata sia dalla coda madre che dalle code sottostanti. Non è possibile creare ulteriori code all'interno di altre code. PRIQ gestisce i pacchetti col metodo FIFO, il primo pacchetto che entra è quello che esce e anche qui code con lo stesso livello di priorità verranno gestite tramite round-robin.

Tabella comparativa

Ecco una tabella comparativa tra le code CBQ e PRIQ:

Class Based Queueing (CBQ) Prioriy Based Queueing (PRIQ)
  • basato sulle classi
  • code annidate
  • banda coda madre condivisibile
  • priorità per le code
  • metodo round-robin
  • basato sulle priorità
  • code non annidate
  • banda coda madre non condivisibile
  • priorità per le code
  • metodo round-robin

RED e ECN

Random Early Detection(RED) e Explicit Congestion Notification(ECN) sono due meccanismi per evitare la congestione di rete. Possono essere utilizzati su una coda nella direttiva queue.

Per maggiori informazioni su questi due interessanti sistemi di congestione del traffico ecco due link. http://en.wikipedia.org/wiki/Random_early_detection, http://en.wikipedia.org/wiki/Explicit_Congestion_Notification.

altq e queue

ALTernate Queueing è il framework principale che gestisce gli schedulers e il Qos delle code ed è incluso di default in pf.

Grazie ad altq e queue, le direttive incaricate di gestire le code nel file di configurazione, possiamo definire i parametri delle code.

altq attiva il sistema di code e schedulers. Inoltre rappresenta la coda root.

altq on interface scheduler bandwitdh bw qlimit limit tbrsize size queue { list }
  • interface: interfaccia sulla quale attivare il sistema di code
  • scheduler: lo scheduler da utilizzare. Sono disponibili solo crb e priq.
  • bw: la banda utilizzabile dallo scheduler.
  • È possibile specificare valori in percentuale, oppure tramite b, Kb, Mb, Gb.
  • limit: parametro opzionale. Il numero di pacchetti da conservare nella coda. Default 50.
  • size: l'ampiezza del regolatore in byte.
  • list: la lista delle code figlie sotto la coda root.

Tramite queue invece definiamo le code figlie da agganciare alla coda root.

queue name [on interface] bandwidth bw [priority pri] [qlimit limit] scheduler ( sched_options ) { queue_list } 
  • name: nome della coda. Deve corrispondere ad un nome definito in list della direttiva altq. Può essere lungo al massimo 15 caratteri.
  • interface: l'interfaccia dove ce una coda valida. Parametro opzionale, se non specificato sarà valida su tutte le interfacce.
  • bw: la banda disponibile sulla coda. Può essere indicato in b, Kb, Mb, Gb o con un valore percentuale.
  • Questo parametro è disponibile solo con lo scheduler cbq. In caso non venga indicato il suo valore sarà 100% della banda della coda madre.
  • pri: priorità della coda. Con cbq può assumere valori da 0 e 7, con priq tra 0 e 15. Di default assume il valore 1.
  • scheduler: cbq o priq. Deve essere lo stesso della coda root.
  • sched_options: le opzioni passate allo scheduler:
    • default: definisce una coda dove i pacchetti che non hanno corrispondenza con le altre code possono fluire. È richiesta una coda di default.
    • red: abilita l'algoritmo RED sulla coda.
    • rio: abilita RED con IN/OUT.
    • ecn: abilita ECN sulla coda. Questo parametro necessita red.
    • borrow: la coda prenderà in prestito banda dalla coda madre. Si può usare solo con cbq.
  • queue_list: una lista di code figlie da creare al di sotto. Funziona solo con cbq.

Esempi di code

Ecco degli esempi di code cbq e priq:

altq on bge0 cbq 1Mb queue { default_out, https_out }

queue default bandwidth 400Kb priority 3 cbq(default)
queue https_out bandwidth 600Kb priority 2

pass out on bge0 inet proto tcp from (bge0) to any keep state queue default
pass out on bge0 inet proto tcp from (bge0) to any port 443 keep state queue https_out

Creiamo la coda root con 1Mb di banda. Successivamente vengono definite le code figlie con i vari parametri. Le code vengono assegnate al traffico, la queue default servirà il traffico tcp in uscita, mentre https_out verrà utilizzata per il traffico avente la porta 443.

altq on fxp1 priq 1Mb { default_in, ssh }

queue default_in priority 2 priq(default)
queue ssh priority 1

pass out on fxp1 proto tcp from any to 192.168.1.0/24 queue default
pass out on fxp1 proto tcp from any port 22 to 19.168.1.0/24 queue ssh

Utilizzando lo scheduler priqcreiamo la coda root e le due code figlie. La coda di default gestisce il traffico in entrata verso la rete 192.168.1.0/24. La seconda coda è per il traffico che utilizza la porta 22 verso la rete 192.168.1.0/24.

Translation rules

Le regole NAT servono per permettere ad una rete di client privata che possiede solo un indirizzo IP pubblico di poter funzionare in tutta tranquillità. Le richieste verso internet sembreranno uscire tutte dal gateway OpenBSD ma ogni richiesta in verità appartiene ad un client all'interno della LAN e solo il firewall è a conoscenza di chi ha richiesto cosa.

Nota: questa regola è per le versioni di OpenBSD che precedono la versione 4.7.

Esiste una regola per attivare il nat:

nat [pass] [log] on interface [af] from src_addr [port src_port] to dst_addr [port dst_port] -> ext_addr [pool_type] [static-port]
  • pass: i pacchetti interessati dal NAT bypasseranno le regole del firewall
  • log: esegue il loggin dei pacchetti
  • interface: il nome dell'interfaccia di rete su cui attivare il NAT
  • af: la famiglia di indirizzi da usare, se inet (IPv4) o inet6 (IPv6). Opzionale.
  • src_addr: l'indirizzo sorgente lato LAN dei pacchetti che verranno translati. Può essere indicato per esempio tramite degli indirizzi IPv4 o IPv6, un nome di dominio, un blocco CIDR o il nome di un interfaccia di rete.
  • src_port: la porta sorgente.Questo parametro non viene utilizzato solitamente perché l'obbiettivo del NAT è di translare tutti collegamenti indipendentemente dalla porta.
  • dst_addr: l'indirizzo di destinazione dei pacchetti affetti da NAT.
  • dst_port: la porta di destinazione.
  • ext_addr: l'indirizzo esterno verso quale effettuare il NAT. Questo è l'indirizzo pubblico del firewall, quello che viene sostituito nei pacchetti all'indirizzo privato. Viene solitamente indicato per esempio tramite un indirizzo Ipv4 o IPv6, un nome di dominio, il nome di un  interfaccia.
  • pool_type: indica il pool d'indirizzi da usare
  • static-port: dice a PF di non modificare la porta sorgente nei pacchetti TCP e UDP.

Nel pf.conf di default è presente una regola NAT globale:

nat on $ext_if from !($ext_if) -> ($ext_if:0)

Questa regola abilita il NAT sull'interfaccia esterna($ext_if) da tutte le interfaccie che non sono (!) quella esterna verso l'interfaccia esterna. :0 indica a PF di non includere indirizzi alias nella translazione. Questa regola NAT è un buon inizio se si vuole effettuare un NAT base.

Filtering rules

La creazione di regole su pf segue un "approccio linguistico". Ovvero le regole sembrano delle frasi di senso compiuto quindi è più facile sia crearle che leggerle in un secondo momento. La creazione di una regola comprende per prima cosa l'azione da eseguire (pass/block) seguita successivamente da tutte le opzioni che si desiderano.

Esempio:

pass in on fxp0 proto tcp from any to 192.168.1.0/24

In questo esempio, viene usata la parola pass per permettere al traffico in entrata (in) verso l'interfaccia fxp0 di passare. "Proto" definisce il protocollo da usare (tcp) e le keyword from e to rappresentano la direzione del traffico, da dove a dove. Ovviamente si possono creare moltissime regole differenti a dipendenza delle proprie esigenze, ecco una panoramica delle opzioni disponibili:

action [direction] [log] [quick] [on interface] [af] [proto protocol] [from src_addr [port src_port]] 
[to dst_addr [port dst_port]] [flags tcp_flags] [state]
  • action: l'azione da intraprendere. Può essere block che blocca i pacchetti o pass che permette a questi ultimi di proseguire verso il loro obbiettivo.
  • direction: la direzione del pacchetto verso una data interfaccia. in per il traffico in entrata, out per quello in uscita.
  • log: abilita il logging
  • quick: se specificata in una regola, permette a quest'ultima di ignorare le regole successive. Per esempio una regola block con la keyword quick, ignorerà un eventuale pass in all inserito subito dopo.
  • interface: il nome dell'interfaccia.
  • af: la famiglia di indirizzi usata, inet(Ipv4) o inet6(IPv6). PF è in grado di determinare questo parametro automaticamente.
  • protocol: il protocollo dei pacchetti in transito. Può essere per esempio tcp/udp oppure un protocollo da /etc/protocols.
  • src_addr: l'indirizzo sorgente. Può essere indicato in molte forme, per esempio come un singolo IP,tramite la keyword any (tutti gli indirizzi) oppure tramite l'indirizzo di una rete seguito per esempio da /24.
  • src_port: la porta che i pacchetti dell'indirizzo src_addr. Possiamo indicarla tramite un numero di porta (21, 22, 443,ecc) oppure consultare /etc/services e inserire il nome di un servizio.
  • dst_addr: indirizzo di destinazione. Valgono le stesse regole di src_addr.
  • dst_port: porta di destinazione dei pacchetti. Valgono le stesse regole di src_port.
  • tcp_flags: in caso venga utilizzato proto tcp, si possono specificare i flags da applicare al pacchetto. Flags S/SA significa che pf controllerà il flag S (syn) e A(ack) del pacchetto ma solo in caso di corrispondenza di SYN il pacchetto sarà valido.
  • state: conserva le informazioni di stato del pacchetto in transito attraverso una regola. Keep state significa mantenere una traccia di ogni connessione passante per permette di avere maggiori performance evitando di controllare ogni volta pacchetti che appartengono alla stessa connessione e di cui è gia stato eseguito il match con una regola. keep state, funziona con TCP, UDP e ICMP è anche il valore di default. Modulate state e synproxy state hanno funzionalità più avanzate usate in casi non comuni.

pfctl

"Pfctl" è il comando che ci permette di controllare pf. Tramite esso possiamo avviare/arrestare il firewall come pure consultarne statistiche riguardanti il traffico.

Ecco alcuni esempi utili:

  • pfctl -e => abilita pf
  • pfctl -d => disabilita pf
  • pfctl -f /etc/pf.conf => carica pf.conf e ne controlla l'integrità
  • pfctl -ss => mostra la tabella di stato in questo momento
  • pfctl -sa => mostra tutto il possibile
  • pfctl -sn => mostra le regole NAT
  • pfctl -sr => mostra le regole di filtering
  • pfctl -sq => mostra le queue