Avanti Indietro Indice

4. Informazioni per i programmatori

Voglio confidarvi un segreto: il mio criceto preferito ha realizzato tutto il codice. Io sono solo un tramite, una facciata se si vuole, appartenente al grande piano del mio animale. Perciò, non mi si rimproveri se ci sono dei bachi. Incolpate lo svelto, l'impellicciato.

4.1 Comprendere ip_tables

iptables provvede semplicemente un array di regole in memoria (da qui il nome `iptables') e informazioni su dove i pacchetti da ciascun hook dovrebbero cominciare la traversata. Dopo che una tabella è stata registrata, lo userspace può leggere e sostituirne il contenuto utilizzando getsockopt() e setsockopt().

iptables non si registra con nessun hook di netfilter: rilascia ad altri moduli questo compito, provvede quindi solo a passare i pacchetti in modo appropriato; un modulo deve registrare hook e ip_tables separatamente, e fornire il meccanismo per chiamare ip_tables quando viene raggiunto l'hook.

Strutture dati ip_tables

Per convenienza viene utilizzata, per rappresentare una regola sia nello userspace sia nel kernel, la stessa struttura dati, sebbene qualche campo sia utilizzato solo nel kernel.

Ogni regola consiste delle seguenti parti:

  1. Una `struct ipt_entry'.
  2. Zero o più strutture `struct ipt_entry_match', ognuna con un ammontare variabile (0 o più byte) di dati allegati.
  3. Una struttura `struct ipt_entry_target' con un ammontare variabile (0 o più byte) di dati allegati.

La natura variabile della regola dà un'enorme disponibilità di flessibilità per le estensioni, come si vedrà, in particolare su come ciascun match (corrispondenza) oppure target (obiettivo) può trasportare un quantitativo arbitrario di dati. Ciò comporta comunque alcune trappole: è necessario prestare attenzione all'allineamento. Ciò avviene assicurandosi che le strutture `ipt_entry', `ipt_entry_match' e `ipt_entry_target' siano convenientemente dimensionate, e che tutti i pacchetti siano confinati nell'allineamento massimo della macchina utilizzando la macro IPT_ALIGN().

La `struct ipt_entry' ha i seguenti campi:

  1. Una `struct ipt_ip', contenente la specificazione dell'intestazione IP che deve essere soddisfatta.
  2. Un bitfield `nf_cache' che mostra quali parti del pacchetto questa regola ha esaminato.
  3. Un campo `target_offset' che indica l'offset da cui, a partire dall'inizio di questa regola, la struttura ipt_entry_target comincia. Questo dovrebbe essere sempre allineato correttamente (attraverso la macro IPT_ALIGN).
  4. Un campo `next_offset' che indica la dimensione totale di questa regola, inclusi i match e i target. Anche questo campo dovrebbe essere allineato correttamente utilizzando la macro IPT_ALIGN.
  5. Un campo `comefrom' utilizzato dal kernel per tracciare la traversata dei pacchetti.
  6. Un campo `struct ipt_counters' contenente i contatori del numero e dei byte riguardanti i pacchetti che hanno soddisfatto questa regola.

Le strutture `struct ipt_entry_match' e `struct ipt_entry_target' sono molto simili, in quanto contengono un campo di lunghezza totale (IPT_ALIGN'alizzato) (rispettivamente `match_size' e `target_size') e una union che mantiene il nome del match o del target (per userspace) e un puntatore (per il kernel).

A causa della complicata natura della struttura dati delle regole sono state previste alcune routine di aiuto:

ipt_get_target()

Questa funzione inline restituisce un puntatore al target di una regola.

IPT_MATCH_ITERATE()

Questa macro invoca la funzione specificata per ogni soddisfazione della regola data. Il primo argomento della funzione è la `struct ipt_match_entry', altri argomenti (se presenti) sono quelli forniti alla macro IPT_MATCH_ITERATE(). Questa funzione ritorna zero in caso di iterazione, o un valore diverso da zero in caso di stop.

IPT_ENTRY_ITERATE()

Questa funzione richiede un puntatore ad una entry, la dimensione totale della tabella delle entry e una funzione da invocare. Il primo argomento della funzione è la `struct ipt_entry', altri argomenti (se presenti) sono quelli forniti alla macro IPT_ENTRY_ITERATE(). Questa funzione ritorna zero in caso di iterazione, o un valore diverso da zero in caso di stop.

ip_tables dallo Userspace

Lo userspace ha quattro funzioni: può leggere la tabella corrente, leggere le informazioni (posizione degli hook e dimensione della tabella), sostituire la tabella (ed ottenere i vecchi contatori) e aggiungere nuovi contatori.

Ciò permette la simulazione, attraverso lo userspace, di qualsiasi operazione atomica: ciò è ottenuto attraverso la libreria libiptc, la quale provvede per i programmi una comoda semantica "add/delete/replace".

Siccome queste tabelle sono trasferite nello spazio del kernel, l'allineamento diventa un problema per quelle macchine che possiedono tipi di regole userspace e kernelspace differenti (es. Sparc64 con userland a 32-bit). Questi casi sono gestiti sovrascrivendo, per queste piattaforme, la definizione di IPT_ALIGN nel file `libiptc.h'.

Uso di ip_tables e traversata

Il kernel comincia ad esaminare dalla locazione indicata dal particolare hook. La regola è esaminata se gli elementi della `struct ipt_ip' sono soddisfatti, ciascuna `struct ipt_entry_match' è poi controllata a turno (la funzione associata con quella soddisfatta è invocata). Se la funzione corrispondente ritorna 0, le iterazioni sono fermate su questa regola. Se il parametro `hotdrop' è impostato a 1, il pacchetto sarà immediatamente scartato (è utilizzata per alcuni pacchetti sospetti, come nella funzione match tcp).

Se l'iterazione continua verso la fine, i contatori sono incrementati e la `struct ipt_entry_target' è esaminata: se è un target (obiettivo) standard allora viene letto il campo `verdict' (valore negativo indica verdetto del pacchetto, positivo indica un offset a cui saltare). Se la risposta è positiva e l'offset non corrisponde a quello della regola successiva, la variabile `back' è impostata, e il valore `back' precedente è collocato nel campo `comefrom' di questa regola.

Per i target non-standard viene chiamata la funzione target: essa restituisce un verdetto (target non standard non possono saltare, in quanto si potrebbe infrangere il codice statico di determinazione dei loop). Il verdetto può corrispondere anche a IPT_CONTINUE per continuare con la regola successiva.

4.2 Estendere iptables

Siccome sono pigro, iptables è abbastanza estendibile. Questo è sostanzialmente un tentativo di passare il lavoro ad altre persone, e rappresenta proprio ciò che è l'Open Source dopo tutto (vedi Free Software, ciò che RMS dichiara a riguardo della parola "freedom", e io ero presente ad uno di questi discorsi quando ho scritto ciò).

Estendere iptables potenzialmente coinvolge due parti: estensione del kernel, con la scrittura di un nuovo modulo, e possibilmente estensione del programma userspace iptables, con la realizzazione di una nuova libreria condivisa.

Il Kernel

Realizzare un modulo per il kernel è di per sé abbastanza semplice, come si può notare dagli esempi. Una cosa da sapere è che il codice deve essere rientrante: ci può essere un solo pacchetto in arrivo dallo userspace mentre un altro giunge su un interrupt. Di fatto in SMP ci può essere un pacchetto su un interrupt per CPU in 2.3.4 e oltre.

Le funzioni che è necessario conoscere sono:

init_module()

Questa funzione è il punto di entrata del modulo. Restituisce un numero di errore negativo, oppure 0 se riesce a registrarsi con successo con netfilter.

cleanup_module()

Questo è il punto di uscita del modulo; dovrebbe eliminare la registrazione con netfilter.

ipt_register_match()

Utilizzata per registrare un nuovo tipo di match (corrispondenza). Si passerà ad essa una `struct ipt_match' di solito dichiarata come variabile statica (file-scope).

ipt_register_target()

Utilizzata per registrare un nuovo tipo. Si passerà ad essa una `struct ipt_target' di solito dichiarata come variabile statica (file-scope).

ipt_unregister_target()

Utilizzata per rimuovere la registrazione del proprio target.

ipt_unregister_match()

Utilizzata per rimuovere la registrazione del proprio match.

Un avvertimento riguardo la realizzazione di cose complicate (come ad esempio provvedere dei contatori) nello spazio extra del proprio match o del proprio target. Sulle macchine SMP, l'intera tabella è duplicata usando memcpy per ciascuna CPU: se davvero si desidera mantenere informazioni in modo centralizzato, si dovrebbe dare un'occhiata al metodo utilizzato con il match `limit'.

Nuove funzioni match

Nuove funzione match sono di solito realizzate come moduli a sé stanti. E' possibile estendere questi moduli successivamente, sebbene solitamente non necessario. Un modo potrebbe essere quello di utilizzare la funzione `nf_register_sockopt' del framework netfilter, per consentire agli utenti di comunicare direttamente con i propri moduli. Un'altra soluzione potrebbe essere quella di esportare i simboli per altri moduli affinché si registrino allo stesso modo di netfilter e iptables.

Il nocciolo della propria funzione match sarà la struttura ipt_match che sarà passata a `ipt_register_match()'. Questa struttura ha i seguenti campi:

list

Questo campo è impostabile con qualsiasi robaccia, facciamo `{ NULL, NULL }'.

name

Questo campo specifica il nome della funzione match, come riferito allo userspace. Il nome, affinché l'auto-caricamento funzioni, deve corrispondere al nome del modulo (ossia, se il nome è "mac", il modulo dovrà essere "ipt_mac.o").

match

Questo campo è un puntatore ad una funzione match che prende skb, i puntatori ai dispositivi in ed out (uno dei quali potrebbe essere NULL, a seconda dell'hook), un puntatore ai dati match della regola che è risultata soddisfatta, la dimensione della regola, l'offset IP (non zero significa un frammento non di testa), un puntatore all'intestazione del protocollo (ossia, giusto l'intestazione IP), la lunghezza dei dati (ossia la dimensione del pacchetto meno l'intestazione IP) e infine un puntatore ad una variabile `hotdrop'. Dovrebbe restituire un valore non-zero se il pacchetto la soddisfa, e può impostare `hotdrop' a 1 se restituisce 0, per segnalare che questo pacchetto deve essere scartato immediatamente.

checkentry

Questo campo è un puntatore ad una funzione la quale controlla le specificazioni di una regola; se restituisce 0, allora la regola dell'utente non sarà accettata. Ad esempio, il tipo match "tcp" accetterà solo pacchetti tcp, quindi se la `struct ipt_ip', parte della regola, non specifica che il protocollo è tcp, uno zero è restituito. L'argomento tablename consente al match di controllare con quali tabelle può essere utilizzato, mentre la `hook_mask' è una bitmask di hook da cui questa regola può essere chiamata: se il match non ha senso per qualche hook di netfilter, si può evitare ciò in questo punto.

destroy

Questo campo è un puntatore ad una funzione la quale è invocata quando una entry, che utilizza questo match, è cancellata. Ciò consente di allocare dinamicamente delle risorse nella checkentry e di rilasciarle qui.

me

Questo campo è da impostare a `THIS_MODULE', il quale fornisce un puntatore al modulo. Esso comporta l'aumento e la diminuzione dell'usage-count a seconda che le regole di questo tipo siano create o distrutte. Ciò previene che un utente rimuova il modulo (e che quindi cleanup_module() sia invocata) quando esiste una regola riferita ad esso.

Nuovi target

Se il proprio target modifica il pacchetto (es. l'intestazione o il corpo), bisogna chiamare la funzione skb_unshare() per copiare il pacchetto nel caso che sia clonato: altrimenti qualsiasi raw socket che ha un clone di skbuff vedrà le modifiche (es. le persone potrebbero vedere cose arcane in tcpdump).

Nuovi target sono di solito realizzati a loro volta come moduli a sé stanti. La discussione riguardante il capitolo `Nuove funzioni match' può essere ugualmente utilizzata anche qui.

Il nocciolo del proprio nuovo target è la struct ipt_target che sarà poi passata alla ipt_register_target(). La struttura ha i seguenti campi:

list

Questo campo è impostabile con qualsiasi robaccia, facciamo `{ NULL, NULL }'.

name

Questo campo specifica il nome della funzione target, come riferito allo userspace. Il nome, affinché l'auto-caricamento funzioni, deve corrispondere al nome del modulo (ossia, se il nome è "REJECT", il modulo dovrà essere "ipt_REJECT.o").

target

Questo è un puntatore alla funzione target, la quale richiede skbuff, il numero di hook, i puntatori ai device input ed output (uno di essi potrebbe essere NULL), un puntatore ai dati target, la dimensione dei dati target, e la posizione della regola nella tabella. La funzione target restituisce o IPT_CONTINUE (-1) se la traversata deve continuare, oppure uno dei verdetti di netfilter verdict (NF_DROP, NF_ACCEPT, NF_STOLEN etc.).

checkentry

Questo campo è un puntatore ad una funzione la quale controlla le specificazioni di una regola; se restituisce 0, allora la regola dell'utente non sarà accettata.

destroy

Questo campo è un puntatore ad una funzione che è invocata quando una entry con questo target è cancellata. Ciò consente di allocare dinamicamente le risorse nella checkentry e di rilasciarle qui.

me

Questo campo è da impostare a `THIS_MODULE', il quale fornisce un puntatore al modulo. Esso comporta l'aumento e la diminuzione dell'usage-count a seconda che le regole di questo tipo siano create o distrutte. Ciò previene che un utente rimuova il modulo (e che quindi cleanup_module() sia invocata) quando esiste una regola riferita ad esso.

Nuove tabelle

Se desiderato si può creare una nuova tabella con scopi specifici. Per crearla, si deve chiamare `ipt_register_table()' fornendo una `struct ipt_table', la quale ha i seguenti campi:

list

Questo campo è impostabile con qualsiasi robaccia, facciamo `{ NULL, NULL }'.

name

Questo campo specifica il nome della funzione target, come riferito allo userspace. Il nome, affinché l'auto-caricamento funzioni, deve corrispondere al nome del modulo (ossia, se il nome è "nat", il modulo dovrà essere "ipt_nat.o").

table

Questa è una `struct ipt_replace' completamente popolata, proprio come utilizzata nello userspace per sostituire una tabella. Il puntatore `counters' può essere impostato a NULL. Questa struttura dati può essere dichiarata `__initdata', in questo modo dopo il boot sarà eliminata.

valid_hooks

Questa è una bitmask di hook IPv4 di netfilter, con cui si accederà alla tabella: questa è usata per verificare che quelle entry point siano valide, e per calcolare i possibili hook per le funzioni `checkentry()' di ipt_match e ipt_target.

lock

Questo campo è l'interruttore lettura-scrittura dell'intera tabella; la si inizializzi a RW_LOCK_UNLOCKED.

private

Questo campo è utilizzato internamente dal codice di ip_tables.

Tool userspace

Ora dopo aver realizzato uno splendente modulo per il kernel, si potrebbe desiderare di controllare le opzioni dallo userspace. Piuttosto che avere una versione derivata di iptables per ogni estensione, io utilizzo l'ultimissima tecnologia degli anni 90: i furbies. Scusate, intendevo le librerie condivise.

Nuove tabelle generalmente non richiedono alcuna estensione di iptables: l'utente può utilizzare l'opzione `-t' per far sì che sia possibile utilizzare la nuova tabella.

La libreria condivisa dovrebbe avere una funzione `_init()', la quale sarà chiamata automaticamente appena caricata: è l'equivalente della funzione `init_module()' per i moduli del kernel. Questa dovrebbe poi chiamare `register_match()' o `register_target()', a seconda che la libreria provveda un nuovo match o un nuovo target.

E' necessario fornire una libreria condivisa: può essere usata per inizializzare parte della struttura, o fornire opzioni addizionali. Insisto nel consigliare la creazione della libreria condivisa anche se non deve fare niente, ma solo per ridurre i problemi che si verificano quando le librerie sono mancanti.

Esistono funzioni molto utili descritte nel file header `iptables.h', in particolare:

check_inverse()

controlla se un argomento è attualmente `!', e in tal caso imposta il flag `invert' se non già impostato. Se restituisce vero, si può incrementare optind, come fatto negli esempi.

string_to_number()

converte una stringa in un numero dell'intervallo dato, restituisce -1 se malformato o fuori intervallo. `string_to_number' si appoggia a `strtol' (vedi la pagina del manuale), questo significa che uno "0x" in testa indica che il numero è in base esadecimale, e uno "0" indica che è in base ottale.

exit_error()

dovrebbe essere invocata se si incontra un errore. Di solito il primo argomento è `PARAMETER_PROBLEM', il quale specifica che l'utente non ha utilizzato correttamente la linea comando.

Nuove funzioni match

La funzione _init() della libreria condivisa passa a `register_match()' un puntatore ad una `struct iptables_match' statica, che ha i seguenti campi:

next

Questo puntatore è utilizzato per realizzare una lista linkata di match (come quelle utilizzate per visualizzare le regole). Dovrebbe essere inizialmente impostata a NULL.

name

Nome della funzione match. Questa dovrebbe corrispondere al nome della libreria (es. "tcp" per `libipt_tcp.so').

version

Di solito impostata con la macro IPTABLES_VERSION: questa è utilizzata per assicurarsi che l'eseguibile iptables non utilizzi per sbaglio le librerie condivise errate.

size

Dimensione dei dati match per questo match; si dovrebbe utilizzare la macro IPT_ALIGN() per assicurarsi che sia correttamente allineato.

userspacesize

Per alcuni match, il kernel modifica alcuni campi internamente (il target `limit' è uno di questi casi). Ciò significa che una semplice `memcmp()' è insufficiente per comparare due regole (richiesto per la funzionalità delete-matching-rule). Se questo è il caso, si sistemino tutti i campi che non cambiano all'inizio della struttura, e qui si metta la loro dimensione. Di solito questa ha lo stesso valore del campo `size'.

help

Funzione che visualizza le informazioni sull'uso delle opzioni.

init

Questa può essere utilizzata per inizializzare lo spazio extra (se presente) della struttura ipt_entry_match, e per impostare qualsiasi bit nfcache; se si sta esaminando qualcosa non esprimibile utilizzando il contenuto di `linux/include/netfilter_ipv4.h', allora si faccia semplicemente un OR con i bit NFC_UNKNOWN. Sarà chiamata prima di `parse()'

parse

Questa funzione è chiamata quando un'opzione non conosciuta è presente nella linea comando: dovrebbe restituire non-zero se l'opzione è effettivamente della propria libreria. `invert' è vera se un `!' è già stato incontrato. Il puntatore `flags' è di esclusivo utilizzo per la propria libreria match, e di solito è utilizzato per memorizzare una bitmask di opzioni che sono state specificate. Ci si assicuri di aver aggiustato il campo nfcache. Riallocando si può estendere, se necessario, la dimensione della struttura `ipt_entry_match', ma poi è necessario assicurarsi che la dimensione sia passata attraverso la macro IPT_ALIGN.

final_check

Questa è chiamata dopo che la linea di comando è stata analizzata, inoltre viene passato l'intero `flags' riservato per la propria libreria. Ciò dà la possibilità di controllare che tutte le opzione obbligatorie siano state specificate, quindi si invochi `exit_error()' se è il caso.

print

Utilizzata dal codice di visualizzazione della catena per stampare (sullo standard output) le informazioni match extra (se presenti) di una regola. L'opzione numeric viene impostata se l'utente specifica il flag `-n'.

save

Questa funzione è il contrario della parse: è utilizzata da `iptables-save' per riprodurre le opzioni usate per creare la regola.

extra_opts

Questa è una lista di opzioni extra, terminata con un NULL, offerte dalla propria libreria. E' fusa con le opzioni correnti e passata alla getopt_long; consultare le pagine del manuale per i dettagli. Il codice di ritorno della getopt_long diventa poi il primo argomento (`c') della funzione `parse()'.

Ci sono altri elementi extra alla fine di questa struttura, utilizzati internamente da iptables: non è necessario impostarli.

Nuovi target

La funzione _init() della propria libreria condivisa passa a `register_target()' un puntatore ad una `struct iptables_target' statica, la quale ha campi simili alla struttura iptables_match vista prima.

Utilizzare `libiptc'

libiptc è la libreria di controllo di iptables, progettata per visualizzare e manipolare le regole nel modulo iptables del kernel. Anche se il suo utilizzo corrente riguarda il programma iptables, consente di scrivere altri tool in modo molto semplice. E' necessario essere root per utilizzare queste funzioni.

Le tabelle del kernel sono semplici tabelle di regole e un insieme di numeri che rappresentano gli entry point. I nomi delle catene ("INPUT", ecc.) sono forniti come astrazioni dalla libreria. Le catene definite dall'utente sono etichettate inserendo un nodo di errore prima dell'inizio della catena definita dall'utente, la quale contiene nella sezione dei dati extra del target, il nome della catena (le posizioni delle catene incorporate sono definite attraverso gli entry point delle tre tabelle).

Sono forniti i seguenti target standard: ACCEPT, DROP, QUEUE (che sono tradotti rispettivamente in NF_ACCEPT, NF_DROP, e NF_QUEUE,), RETURN (che è tradotto in un valore speciale IPT_RETURN gestito da ip_tables), e JUMP (che è tradotto dal nome della catena nel reale offset all'interno della tabella).

Quando `iptc_init()' è invocata, la tabella, inclusi i contatori, è letta. Questa tabella è manipolabile attraverso le funzioni `iptc_insert_entry()', `iptc_replace_entry()', `iptc_append_entry()', `iptc_delete_entry()', `iptc_delete_num_entry()', `iptc_flush_entries()', `iptc_zero_entries()', `iptc_create_chain()' `iptc_delete_chain()', e `iptc_set_policy()'.

I cambiamenti alla tabella non saranno apportati fino a quando non sarà chiamata la funzione `iptc_commit()'. Ciò significa che è possibile che due librerie utenti, operanti sulla stessa catena, concorrano una con l'altra; per prevenire queste situazioni sarebbe necessario il locking, al momento non effettuabile.

Non esiste concorrenza per quanto riguarda i contatori; i contatori sono sommati dopo nel kernel in un modo tale che i loro incrementi, tra il tempo di lettura e scrittura della tabella, siano ancora visibili nella nuova tabella.

Ci sono diverse funzioni di aiuto:

iptc_first_chain()

Questa funzione restituisce il nome della prima catena della tabella.

iptc_next_chain()

Questa funzione restituisce il nome della catena successiva della tabella: NULL indica che non ci sono altre catene.

iptc_builtin()

Restituisce true (vero) se il nome della catena fornito corrisponde al nome di una catena definita da iptables.

iptc_first_rule()

Questa funzione restituisce un puntatore alla prima regola della catena avente il nome dato: NULL indica catena vuota.

iptc_next_rule()

Questa restituisce un puntatore alla regola successiva della catena: NULL indica fine della catena.

iptc_get_target()

Questa funzione permette di ottenere il target di una data regola. Se si tratta di un target estensione viene restituito il nome del target. Se corrisponde ad un salto ad un'altra catena viene restituito il nome della catena. Se è un verdetto (es. DROP) ne viene restituito il nome. Se non ha un target (regola accounting-style) viene restituita una stringa vuota.

Si noti che questa funzione dovrebbe essere utilizzata al posto della consultazione diretta del valore del campo `verdict' nella struttura ipt_entry, dato che offre le sopraindicate ulteriori interpretazioni del verdetto standard.

iptc_get_policy()

Questa consente di ottenere la policy (tattica) di una catena incorporata, e la sua statistica di utilizzo attraverso l'argomento `counters'.

iptc_strerror()

Questa funzione restituisce una ancor più eloquente spiegazione riguardo un codice di fallimento della libreria iptc. Se una funzione fallisce, essa imposta sempre errno: questo valore può essere passato a iptc_strerror() per generare un messaggio di errore.

4.3 Comprendere il NAT

Benvenuti al Network Address Translation presente nel kernel. Si noti che l'infrastruttura offerta è stata progettata più con l'obiettivo della completezza piuttosto che della bruta efficienza, interventi futuri potranno incrementare sensibilmente le prestazioni. Al momento sono contento che funzioni.

NAT è suddiviso in connection tracking (il quale non manipola affatto i pacchetti), e il codice di NAT stesso. Il connection tracking è stato progettato per essere utilizzato come modulo di iptables, ed effettua sottili distinzioni riguardanti lo stato, che generalmente il NAT non considera proprio.

Connection Tracking

Il connection tracking (tracciamento delle connessioni) si aggancia agli hook di alta priorità NF_IP_LOCAL_OUT e NF_IP_PRE_ROUTING, in ordine, per vedere i pacchetti prima che entrino nel sistema.

Il campo nfct della skb è un puntatore ad uno degli array infos[], presenti all'interno della struct ip_conntrack. Quindi si può ricavare lo stato della skb in base a quale elemento di questo array esso sta puntando: questo puntatore codifica sia la struttura state sia la relazione di skb con questo stato.

Il modo migliore per estratte il campo `nfct' consiste nel chiamare `ip_conntrack_get()', la quale restituisce NULL se non è impostato, oppure il puntatore alla connessione, inoltre "compila" ctinfo che descrive la relazione del pacchetto con questa connessione. Questo tipo enumerato può assumere diversi valori:

IP_CT_ESTABLISHED

Il pacchetto è parte di una connessione stabilita, nella direzione originale.

IP_CT_RELATED

Il pacchetto è correlato ad una connessione, ed è passato nella direzione originale.

IP_CT_NEW

Il pacchetto sta cercando di creare una nuova connessione (ovviamente, è nella direzione originale).

IP_CT_ESTABLISHED + IP_CT_IS_REPLY

Il pacchetto è parte di una connessione stabilita, nella direzione di risposta.

IP_CT_RELATED + IP_CT_IS_REPLY

Il pacchetto è correlato ad una connessione, ed è passato nella direzione di risposta.

Quindi un pacchetto in risposta può essere identificato effettuando un test di tipo >= IP_CT_IS_REPLY.

4.4 Estendere il Connection tracking/NAT

Questi framework sono stati progettati per essere adattati a qualsiasi tipo di protocollo e tipo differente di mapping. Alcuni di questi tipi di mapping potrebbero essere piuttosto specifici, per esempio mapping load-balancing/fail-over.

Internamente, il connection tracking, prima di cercare connessioni o regole che siano soddisfatte, converte un pacchetto in una "tupla", che consiste nella parte interessante del pacchetto. Questa tupla ha una parte manipolabile e una parte non manipolabile; chiamate "src" e "dst", almeno come appaiono nel mondo del SNAT durante l'ispezione del primo pacchetto (nel caso del mondo del Destination NAT corrisponderebbero ad un pacchetto di risposta). La tupla per ogni pacchetto dello stesso stream, nella stessa direzione, è sempre uguale.

Ad esempio, una tupla di un pacchetto TCP contiene la parte manipolabile: indirizzo IP sorgente e porta sorgente, la parte non manipolabile: indirizzo IP destinazione e porta destinazione. Tuttavia non è necessario che la parte manipolabile e la parte non manipolabile siano dello stesso tipo; ad esempio, una tupla di un pacchetto ICMP contiene la parte manipolabile: indirizzo IP sorgente e l'id ICMP, e la parte non manipolabile: indirizzo IP destinazione, tipo e codice ICMP.

Ogni tupla ha un inverso, il quale corrisponde alla tupla relativa ai pacchetti dello stream che arrivano in risposta. Ad esempio, l'inverso di un pacchetto ICMP ping, icmp id 12345, da 192.168.1.1 a 1.2.3.4, è un pacchetto ping-reply, icmp id 12345, da 1.2.3.4 a 192.168.1.1.

Queste tuple, rappresentate dalla `struct ip_conntrack_tuple', sono ampiamente utilizzate. Di fatto, assieme con l'hook da cui il pacchetto arriva (il quale ha effetto sul tipo di manipolazione), e il dispositivo coinvolto, questa corrisponde all'informazione completa del pacchetto.

La maggior parte delle tuple sono contenute entro la `struct ip_conntrack_tuple_hash', che aggiunge una entry alla lista doppiamente linkata, e un puntatore alla connessione a cui la tupla appartiene.

Una connessione è rappresentata dalla `struct ip_conntrack' la quale ha due campi `struct ip_conntrack_tuple_hash': uno riguardante la direzione del pacchetto originale (tuplehash[IP_CT_DIR_ORIGINAL]), e uno riguardante la direzione dei pacchetti in risposta (tuplehash[IP_CT_DIR_REPLY]).

Comunque, la prima cosa che il NAT fa è di verificare se il codice del connection tracking è riuscito ad estrarre una tupla e a trovare una connessione esistente, controllando il campo nfct della skbuff; ciò permette di conoscere se è un tentativo di nuova connessione, o in caso contrario, quale direzione ha; nell'ultimo caso inoltre sono poi effettuate le manipolazioni stabilite precedentemente per questa connessione.

Se corrisponde invece all'inizio di una nuova connessione, si cercherà una regola per questa tupla, utilizzando il meccanismo standard di attraversamento di iptables. Se una regola viene soddisfatta, è utilizzata per inizializzare le manipolazioni, sia per quella direzione sia per la risposta; il codice del connection tracking ci farà notare che la risposta, come aspettato, è stata cambiata. Quindi sarà manipolata come sopra.

Se non c'é nessuna regola, viene creato un collegamento `null': questo di solito non mappa il pacchetto, ma esiste per assicurare che non si mappi un altro stream sopra uno esistente. Qualche volta, il collegamento null non può essere creato, in quanto si è già mappato sopra uno stream, in questo caso la manipolazione per protocollo potrebbe provare a rimapparla, anche se è nominalmente un collegamento `null'.

Target NAT standard

I target NAT sono simili ai target estensione di iptables, eccetto per il fatto che sono utilizzati solo con la tabella `nat'. Sia i target SNAT che DNAT prendono una `struct ip_nat_multi_range' come dato extra; ciò serve per specificare l'intervallo di indirizzi che è consentito utilizzare per un mapping. Un elemento di intervallo `struct ip_nat_range' consiste in un indirizzo IP minimo e massimo inclusi, e in un valore massimo e minimo inclusi specifici del protocollo (es. porte TCP). C'è inoltre spazio per i flag, i quali specificano se l'indirizzo IP può essere mappato (qualche volta si desidera mappare solo la parte specifica del protocollo di una tupla, non l'IP), e un altro per indicare che la parte specifica del protocollo dell'intervallo è valida.

Un intervallo multiplo consiste in un array di elementi `struct ip_nat_range'; ciò significa che un intervallo potrebbe essere "1.1.1.1-1.1.1.2 porte 50-55 E 1.1.1.3 porta 80". Ogni elemento dell'intervallo viene aggiunto all'intervallo (una unione, per chi ama la teoria degli insiemi).

Nuovi protocolli

All'interno del kernel

Implementare un nuovo protocollo prima di tutto significa decidere quale parte di una tupla deve essere manipolabile e quale no. Qualsiasi cosa nella tupla deve avere la proprietà di identificare univocamente lo stream. La parte manipolabile della tupla è poi la parte su cui si può effettuare il NAT: per il caso TCP questa è la porta sorgente, per ICMP è l'ID; insomma qualcosa utilizzabile come "identificatore dello stream". La parte non manipolabile consiste invece nella parte restante del pacchetto, che identifica univocamente lo stream, ma con cui non si può "giocare" (es. porta destinazione TCP, tipo ICMP).

Una volta prese queste decisioni, si può scrivere un'estensione al codice del connection-tracking nella directory, e proseguire popolando la struttura `ip_conntrack_protocol' che è necessario poi passare alla funzione `ip_conntrack_register_protocol()'.

I campi della `struct ip_conntrack_protocol' sono:

list

Da impostare a '{ NULL, NULL }'; utilizzata per unirsi alla lista.

proto

Il numero del protocollo; vedere `/etc/protocols'.

name

Nome del protocollo. Questo è il nome che l'utente vedrà; in genere è meglio se corrisponde ad uno dei nomi canonici presenti in `/etc/protocols'.

pkt_to_tuple

Funzione che, dato il pacchetto, riempie le parti specifiche della tupla riguardanti il protocollo. Il puntatore `datah' punta all'inizio dell'intestazione (giusto dopo l'intestazione IP), mentre datalen è la lunghezza del pacchetto. Se il pacchetto non è abbastanza lungo per contenere le informazioni dell'intestazione, restituisce 0; datalen sarà comunque sempre di almeno 8 byte (imposto dal framework).

invert_tuple

Questa funzione è utilizzata semplicemente per cambiare la parte specifica del protocollo della tupla in modo tale che appaia come quella di un pacchetto di risposta.

print_tuple

Questa funzione è utilizzata per stampare la parte specifica del protocollo di una tupla; di solito è usata la funzione sprintf() con il buffer fornito. Restituisce il numero di caratteri utilizzati. Questa è utilizzata per stampare gli stati per la /proc.

print_conntrack

Questa funzione è utilizzata per stampare la parte privata della struttura conntrack, se presente, usata inoltre anche per stampare gli stati in /proc.

packet

Questa funzione è chiamata quando un pacchetto è visto quale parte di una connessione stabilita. Si ottiene un puntatore alla struttura conntrack, l'intestazione IP, la lunghezza, e la ctinfo. Ritorna un verdetto per il pacchetto (normalmente NF_ACCEPT), oppure -1 se il pacchetto non è parte valida di una connessione. Si può cancellare la connessione dall'interno di questa funzione se desiderato, ma è d'obbligo utilizzare la seguente forma per evitare concorrenze (vedere ip_conntrack_proto_icmp.c):

if (del_timer(&ct->timeout))
        ct->timeout.function((unsigned long)ct);

new

Questa funzione è chiamata quando un pacchetto crea una connessione per la prima volta; non c'è un argomento ctinfo, dato che il primo pacchetto è ctinfo IP_CT_NEW per definizione. Restituisce 0 se fallisce nella creazione della connessione, o un immediato timeout di connessione.

Una volta scritto e testato ciò è possibile tracciare il proprio nuovo protocollo, ora è tempo di istruire NAT su come interpretarlo. Ciò significa realizzare un nuovo modulo; un'estensione al codice NAT e di andare a popolare la struttura `ip_nat_protocol' che sarà necessario passare a `ip_nat_protocol_register()'.

list

Da impostare a '{ NULL, NULL }'; utilizzata per unirsi alla lista.

name

Nome del protocollo. Questo è il nome che l'utente conoscerà; in genere, per l'auto-caricamento nello userspace, è meglio se corrisponde ad uno dei nomi canonici di `/etc/protocols', come vedremo più avanti.

protonum

Numero del protocollo; vedere `/etc/protocols'.

manip_pkt

Questa è l'altra metà della funzione pkt_to_tuple del connection tracking: si può pensare ad essa come a "tuple_to_pkt". Ci sono comunque alcune differenze: si ottiene un puntatore all'inizio dell'intestazione IP e la lunghezza totale del pacchetto. Ciò perché alcuni protocolli (UDP, TCP) necessitano di conoscere l'intestazione IP. Si fornirà il campo ip_nat_tuple_manip della tupla (ossia, il campo "src"), piuttosto che l'intera tupla, e il tipo di manipolazione che si sta per effettuare.

in_range

Questa funzione è utilizzata per indicare se la parte manipolabile della tupla fornita appartiene all'intervallo dato. Questa funzione è un po' complicata: si sta per fornire il tipo di manipolazione che è stata applicata alla tupla, la quale ci dice come interpretare l'intervallo (ci stiamo rivolgendo all'intervallo sorgente o a quello destinazione?).

Questa funzione è utilizzata per controllare se un mapping esistente ci colloca nell'intervallo corretto, e inoltre per controllare se non è necessaria una manipolazione.

unique_tuple

Questa funzione è il nocciolo del NAT: data una tupla e un intervallo, si sta per alterare la parte della tupla relativa al protocollo per sistemarla nell'intervallo, e renderla unica. Se non si riesce a trovare una tupla non utilizzata nell'intervallo, deve restituire 0. Si ottiene inoltre un puntatore alla struttura conntrack, richiesta dalla ip_nat_used_tuple().

L'approccio comune è di iterare semplicemente la parte della tupla relativa al protocollo attraverso l'intervallo, utilizzando `ip_nat_used_tuple()' fino a quando una non restituisce false.

Si noti che il caso mapping nullo è già stato controllato: o è esterno all'intervallo dato o è già occupato.

Se IP_NAT_RANGE_PROTO_SPECIFIED non è impostato, ciò significa che l'utente sta effettuando il NAT, non il NAPT: sta facendo qualcosa di ragionevole con l'intervallo. Se il mapping non è desiderabile (per esempio, entro TCP, un mapping sulla destinazione non dovrebbe modificare la porta TCP a meno che non sia ordinato) deve restituire 0.

print

Dato un buffer di caratteri, una tupla match e una maschera, mostra per esteso le parti relative al protocollo e ritorna la lunghezza del buffer utilizzato.

print_range

Dato un buffer di caratteri e un intervallo, stampa per esteso la parte relativa al protocollo dell'intervallo e restituisce la lunghezza del protocollo utilizzato. Questa non sarà chiamata se il flag IP_NAT_RANGE_PROTO_SPECIFIED non è stato impostato per l'intervallo.

Nuovi target NAT

Questa è la parte davvero interessante. Si possono scrivere nuovi target NAT che provvedano un nuovo tipo di mapping. Due extra target sono forniti nel pacchetto di default: MASQUERADE e REDIRECT. Questi sono abbastanza semplici per illustrare il potenziale e la capacità di realizzare un nuovo target NAT.

Queste sono realizzate come qualsiasi altro target di iptables, internamente essi estraggono la connessione e chiamano `ip_nat_setup_info()'.

Protocol helper

I protocol helper per il connection tracking permettono al codice del connection tracking di comprendere i protocolli che usano connessioni multiple (es. FTP). Inoltre segnano le connessioni `figlie' come correlate alla connessione iniziale, solitamente leggendo il relativo indirizzo del flusso di dati.

I protocol helper per il NAT fanno due cose: innanzitutto permettono al NAT di manipolare il flusso di dati per cambiarne l'indirizzo, ed in secondo luogo di eseguire il NAT sulle connessioni correlate all'originale.

Moduli helper per il connection tracking

Descrizione

Il compito del modulo per il connection tracking è quello di stabilire quali pacchetti appartengono ad una connessione già stabilita. Per farlo il modulo esegue i seguenti passi:

Se ci sono azioni aggiuntive che devono essere fatte la prima volta che un pacchetto, appartenente ad una connessione prevista, arriva, il modulo può registrare una funzione callback che sarà chiamata in quel momento.

Strutture e funzioni disponibili

La funzione init del proprio modulo deve chiamare `ip_conntrack_helper_register()' con un puntatore a `struct ip_conntrack_helper'. Questa struttura ha i seguenti campi:

list

Questa è la testa di una lista linkata. Netfilter gestisce questa lista internamente. Da inizializzare con `{ NULL, NULL }'.

name

Questo è un puntatore ad una stringa costante che specifica il nome del protocollo. ("ftp", "irc", ...)

flags

Un gruppo con una o più delle seguenti flag:

me

Un puntatore al modulo dell'helper. Da inizializzare con la macro `THIS_MODULE'.

max_expected

Massimo numero di expectation non confermate (in sospeso).

timeout

Timeout (in secondi) per ogni expectation non confermata. Una expectation è cancellata `timeout' secondi dopo che l'expectation identificata con la funzione `ip_conntrack_expect_related()'.

tuple

Questa è una `struct ip_conntrack_tuple' che specifica a quali pacchetti il modulo per il conntrack helper è interessato.

mask

Maschera riferita a `struct ip_conntrack_tuple'. Questa maschera specifica quali bit di tuple sono validi.

help

La funzione che netfilter dovrebbe chiamare per ogni pacchetto che verifica tuple+mask

Esempio schematico di un conntrack helper module


#define FOO_PORT        111

static int foo_expectfn(struct ip_conntrack *new)
{
        /* funzione chiamata quando il primo pacchetto di una connessione
           prevista arriva */

        return 0;
}

static int foo_help(const struct iphdr *iph, size_t len,
                struct ip_conntrack *ct,
                enum ip_conntrack_info ctinfo)
{
        /* analizza i dati passati con questa connessione e
           decide come saranno i pacchetti correlati */

        /* aggiorna i dati privati della connessione master
           (session state, ...) */
        ct->help.ct_foo_info = ...

        if (there_will_be_new_packets_related_to_this_connection)
        {
                struct ip_conntrack_expect exp;

                memset(&exp, 0, sizeof(exp));
                exp.t = tuple_specifying_related_packets;
                exp.mask = mask_for_above_tuple;
                exp.expectfn = foo_expectfn;
                exp.seq = tcp_sequence_number_of_expectation_cause;

                /* dati privati della connessione slave */
                exp.help.exp_foo_info = ...

                ip_conntrack_expect_related(ct, &exp);
        }
        return NF_ACCEPT;
}

static struct ip_conntrack_helper foo;

static int __init init(void)
{
        memset(&foo, 0, sizeof(struct ip_conntrack_helper);

        foo.name = "foo";
        foo.flags = IP_CT_HELPER_F_REUSE_EXPECT;
        foo.me = THIS_MODULE;
        foo.max_expected = 1;   /* una expectation alla volta */
        foo.timeout = 0;        /* le expectation non terminano */

        /* siamo interessati a tutti i pacchetti TCP con porta di destinazione 111 */
        foo.tuple.dst.protonum = IPPROTO_TCP;
        foo.tuple.dst.u.tcp.port = htons(FOO_PORT);
        foo.mask.dst.protonum = 0xFFFF;
        foo.mask.dst.u.tcp.port = 0xFFFF;
        foo.help = foo_help;

        return ip_conntrack_helper_register(&foo);
}

static void __exit fini(void)
{
        ip_conntrack_helper_unregister(&foo);
}

Moduli helper per il NAT

Descrizione

I moduli helper per il NAT, gestiscono il NAT per alcune specifiche applicazioni. Di solito questo include la manipolazione al volo dei dati: si pensi al comando PORT dell'FTP, dove il client comunica al server a quale IP/porta connettersi. Per questo un modulo helper per l'FTP deve sostituire l'IP/porta dopo che il comando PORT è stato eseguito in una connessione FTP.

Se stiamo trattando con il protocollo TCP, la faccenda si complica leggermente. La ragione sta nella possibile variazione delle dimensioni del pacchetto (esempio FTP: la lunghezza della stringa che rappresenta la tupla IP/porta dopo che il comando PORT l'ha modificata). Se cambiamo le dimensioni del pacchetto, abbiamo un syn/ack di differenza tra la parte sinistra e destra del NAT. (ad esempio se estendiamo un pacchetto di 4 ottetti, bisogna aggiungere questo offset al numero di sequenza TCP di ogni pacchetto che seguirà).

E' anche richiesta la gestione del NAT di tutti i pacchetti correlati. Prendiamo come esempio ancora FTP, dove tutti i pacchetti in ingresso di una connessione DATA devono essere NATtati verso l'IP/porta specificati dal client con il comando PORT nella connessione di controllo, piuttosto che passare attraverso la normale tabella lookup.

Strutture e funzioni disponibili

La funzione `init()' del modulo helper chiama `ip_nat_helper_register()' con un puntatore ad una `struct ip_nat_helper'. Questa struttura ha i seguenti elementi:

list

Ancora un altro header di una lista usata internamente da netfilter. Da inizializzare con { NULL, NULL }.

name

Un puntatore ad una stringa costante con il nome del protocollo

flags

Un gruppo di zero, una o più delle seguenti flag:

me

Un puntatore al modulo dell'helper. Da inizializzare usando la macro `THIS_MODULE'.

tuple

una `struct ip_conntrack_tuple' che descrive a quali pacchetti in NAT helper è interessato.

mask

maschera una `struct ip_conntrack_tuple', che dice a netfilter quali bit di tuple sono validi.

help

La funzione che è chiamata per ogni pacchetto che verifica tuple+mask.

expect

La funzione che è chiamata per ogni primo pacchetto di una connessione prevista.

E' molto simile alla scrittura di un connection tracking helper.

Esempio di un modulo helper per il NAT


#define FOO_PORT        111

static int foo_nat_expected(struct sk_buff **pksb,
                        unsigned int hooknum,
                        struct ip_conntrack *ct,
                        struct ip_nat_info *info)
/* chiamata ogni volta che arriva il primo pacchetto di una connessione correlata.
   parametri:   pksb    packet buffer
                hooknum HOOK di provenienza (POST_ROUTING, PRE_ROUTING)
                ct      informazioni su questa connessione (correlata)
                info    &ct->nat.info
   valore di ritorno: verdetto (NF_ACCEPT, ...) */
{
        /* Cambio dei valori ip/porta del pacchetto con i valori
           letti da master->tuplehash, per mapparlo allo stesso modo,
           viene chiamata ip_nat_setup_info e restituito NF_ACCEPT. */

}

static int foo_help(struct ip_conntrack *ct,
                    struct ip_conntrack_expect *exp,
                    struct ip_nat_info *info,
                    enum ip_conntrack_info ctinfo,
                    unsigned int hooknum,
                    struct sk_buff  **pksb)
/* chiamata per ogni pacchetto riconosciuto dal conntrack come expectation-cause
   parametri:   ct      struct ip_conntrack della connessione principale
                exp     struct ip_conntrack_expect dell'expectation
                        determinata dal conntrack helper per questo protocollo
                info    (STATO: related, new, established, ... )
                hooknum HOOK di provenienza (POST_ROUTING, PRE_ROUTING)
                pksb    packet buffer
*/
{

        /* estrazione di informazioni sui futuri pacchetti correlati
           (è possibile condividere informazioni con la funzione foo_help
           del connection tracking).
           Scambio di indirizzo/porta con i valori per il masquerading,
           inserimento della tupla dei pacchetti correlati */
}

static struct ip_nat_helper hlpr;

static int __init(void)
{
        int ret;

        memset(&hlpr, 0, sizeof(struct ip_nat_helper));
        hlpr.list = { NULL, NULL };
        hlpr.tuple.dst.protonum = IPPROTO_TCP;
        hlpr.tuple.dst.u.tcp.port = htons(FOO_PORT);
        hlpr.mask.dst.protonum = 0xFFFF;
        hlpr.mask.dst.u.tcp.port = 0xFFFF;
        hlpr.help = foo_help;
        hlpr.expect = foo_nat_expect;

        ret = ip_nat_helper_register(hlpr);

        return ret;
}

static void __exit(void)
{
        ip_nat_helper_unregister(&hlpr);
}

4.5 Comprendere Netfilter

Netfilter è piacevolmente semplice, ed è stato descritto in modo abbastanza esauriente nei capitoli precedenti. Tuttavia, qualche volta è necessario andare oltre a ciò che l'infrastruttura NAT o ip_tables offrono, oppure si potrebbe desiderare di sostituirle interamente.

Un importante problema per netfilter (beh, in futuro) è il caching. Ogni skb ha un campo `nfcache': una bitmask che indica quali campi dell'intestazione sono stati esaminati e se i pacchetti sono stati alterati o no. L'idea è che ciascun hook fuori di netfilter effettui un OR con i bit rilevanti, in questo modo si potrà successivamente realizzare un sistema cache sufficientemente intelligente da comprendere quando i pacchetti non necessitano di essere passati attraverso netfilter.

I bit più importanti sono NFC_ALTERED, che specifica che il pacchetto è stato alterato (questo è già utilizzato per l'hook IPv4 NF_IP_LOCAL_OUT, per re-instradare i pacchetti alterati), e NFC_UNKNOWN, che indica che il caching non dovrebbe essere effettuato in quanto sono state esaminate alcune proprietà non esprimibili. Se incerti, semplicemente si imposti il flag NFC_UNKNOWN nel campo nfcache della skb all'interno del proprio hook.

4.6 Realizzare nuovi moduli Netfilter

Introduzione agli hook di Netfilter

Per ricevere/manipolare i pacchetti nel kernel, si può semplicemente scrivere un modulo che registri un "hook netfilter". Questa è sostanzialmente un'espressione di interesse per alcuni determinati punti; gli attuali punti sono specifici per protocollo, e sono definiti nelle intestazioni di netfilter specifiche per i protocolli, ad esempio "netfilter_ipv4.h".

Per registrare e rimuovere le registrazioni di hook di netfilter, si utilizzeranno le funzioni `nf_register_hook' e `nf_unregister_hook'. Ciascuna di queste richiede un puntatore ad una `struct nf_hook_ops' che si dovrà popolare come segue:

list

Utilizzata per unirsi alla lista linkata: impostare a '{ NULL, NULL }'

hook

Funzione invocata quando un pacchetto "colpisce" questo hook. La funzione deve restituire NF_ACCEPT, NF_DROP oppure NF_QUEUE. Nel caso NF_ACCEPT, sarà chiamato il successivo hook agganciato a questo punto. Nel caso NF_DROP, il pacchetto sarà scartato. Nel caso NF_QUEUE, sarà accodato. Si riceverà inoltre un puntatore ad un puntatore skb, perciò si può sostituire completamente la skb, se desiderato.

flush

Al momento non utilizzata: progettata per far passare i pacchetti giunti quando la cache viene svuotata. Forse non sarà mai implementata: impostare a NULL.

pf

La famiglia del protocollo, es. nel caso IPv4, `PF_INET'.

hooknum

Numero dell'hook a cui si è interessati, es. `NF_IP_LOCAL_OUT'.

Processare i pacchetti accodati

Questa interfaccia è al momento utilizzata da ip_queue; ci si può registrare per gestire, per un dato protocollo, i pacchetti accodati. Ha una semantica simile a quella delle registrazioni di un hook, eccetto il fatto che è possibile bloccare il trattamento del pacchetto, inoltre si vedranno solo i pacchetti per i quali un hook ha risposto con un `NF_QUEUE'.

Le due funzioni utilizzate per registrare l'interesse ai pacchetti accodati sono `nf_register_queue_handler()' e `nf_unregister_queue_handler()'. La funzione che si registrerà sarà chiamata con il puntatore `void *' che poi si passerà alla `nf_register_queue_handler()'.

Se nessun altro è registrato per gestire un protocollo, restituire NF_QUEUE è equivalente a restituire NF_DROP.

Una volta registrato l'interesse ai pacchetti accodati, essi cominciano ad essere accodati. Si può fare qualsiasi cosa con essi, ma è obbligatorio chiamare `nf_reinject()' una volta terminato (non si effettui semplicemente un kfree_skb()). Quando si effettua il "reinject" di skb, si passi la skb, la `struct nf_info' gestore della queue fornita e un verdetto: con NF_DROP i pacchetti vengono scartati, NF_ACCEPT fa sì che continuino ad iterare attraverso gli hook, NF_QUEUE che siano nuovamente accodati, e NF_REPEAT che l'hook che ha accodato i pacchetti sia nuovamente consultato (si evitino i loop infiniti).

Si può guardare all'interno della `struct nf_info' per ottenere informazioni ausiliarie sul pacchetto, quali ad esempio interfacce e hook.

Ricevere comandi dallo Userspace

E' cosa comune che componenti di netfilter vogliano interagire con lo userspace. Il metodo affinché ciò avvenga richiede il meccanismo setsockopt. Nota che ogni protocollo deve essere modificato per poter chiamare nf_setsockopt() per i numeri setsockopt che non comprende (e nf_getsockopt() per i numeri getsockopt), finora solo IPv4, IPv6 e DECnet sono stati modificati.

Utilizzando una tecnica ora familiare, si registrerà una `struct nf_sockopt_ops' utilizzando nf_register_sockopt(). I campi di questa struttura sono i seguenti:

list

Utilizzata per unirsi alla lista. Impostare a '{ NULL, NULL }'.

pf

La famiglia del protocollo che si gestisce, es. PF_INET.

set_optmin

e

set_optmax

Questi specificano l'intervallo (esclusivo) di numeri setsockopt gestiti. Quindi utilizzare 0 e 0 significa non avere numeri setsockopt.

set

Questa è la funzione chiamata quando l'utente richiama una delle setsockopts. Si dovrebbe controllare che esse abbiano capacità NET_ADMIN entro questa funzione.

get_optmin

e

get_optmax

Questi specificano l'intervallo (esclusivo) dei numeri setsockopt gestiti. Quindi utilizzare 0 e 0 significa non avere numeri setsockopt.

get

Questa è la funzione chiamata quando l'utente richiama una delle getsockopts. Si dovrebbe controllare che esse abbiano capacità NET_ADMIN entro questa funzione.

Gli ultimi due campi sono utilizzati internamente.

4.7 Gestione del pacchetto nello userspace

Utilizzando la libreria libipq e il modulo `ip_queue', quasi tutto ciò che può essere fatto nel kernel può ora essere effettuato nello userspace. Ciò significa che, con qualche penalità nella velocità, si può sviluppare il proprio codice interamente nello userspace. A meno che non si stia provando a filtrare bande larghe, si dovrebbe trovare questo approccio superiore al manipolamento del pacchetto nel kernel.

Nei primi giorni di vita di netfilter ho constatato ciò portando una versione embrionale di iptables nello userspace. Netfilter apre le porte a tutte le persone che vogliono scrivere per conto proprio moduli efficienti di manipolazione della rete, e in qualsiasi linguaggio desiderato.


Avanti Indietro Indice