Linux netfilter Hacking HOWTO
  Rusty Russell, mailing list netfilter@lists.samba.org
  $Revision: 1.10 $ $Date: 2001/05/04 20:58:43 $

  Questo documento descrive l'architettura netfilter presente in Linux,
  come sfruttarla, ed alcuni dei più importanti sistemi collocati al di
  sopra, ossia il filtraggio dei pacchetti, il connection tracking
  (tracciamento delle connessioni), il Network Address Translation.
  Traduzione a cura di Masetti Marco marcomas@libero.it <mailto:marco­
  mas@libero.it>
  ______________________________________________________________________

  Indice Generale





















































  1. Introduzione

     1.1 Che cos'è netfilter?
     1.2 Che cos'è che non va con la 2.0 e la 2.2?
     1.3 Chi sei?
     1.4 Perché si pianta?

  2. Dove si può reperire l'ultima versione?

  3. L'architettura di netfilter

     3.1 Fondamenti di Netfilter
     3.2 Selezione dei pacchetti: IP Tables
        3.2.1 Filtraggio dei pacchetti
        3.2.2 NAT
           3.2.2.1 Mascheramento, Port Forwarding, Proxy Trasparente
        3.2.3 Manipolamento dei pacchetti
     3.3 Connection Tracking
     3.4 Altre aggiunte

  4. Informazioni per i programmatori

     4.1 Comprendere ip_tables
        4.1.1 Strutture dati ip_tables
        4.1.2 ip_tables dallo Userspace
        4.1.3 Uso di ip_tables e traversata
     4.2 Estendere iptables
        4.2.1 Il Kernel
           4.2.1.1 Nuove funzioni match
           4.2.1.2 Nuovi target
           4.2.1.3 Nuove tabelle
        4.2.2 Tool userspace
           4.2.2.1 Nuove funzioni match
           4.2.2.2 Nuovi target
        4.2.3 Utilizzare `libiptc'
     4.3 Comprendere il NAT
        4.3.1 Connection Tracking
     4.4 Estendere il Connection tracking/NAT
        4.4.1 Target NAT standard
        4.4.2 Nuovi protocolli
           4.4.2.1 All'interno del kernel
        4.4.3 Nuovi target NAT
        4.4.4 Aiutanti dei protocolli
        4.4.5 Moduli di aiuto del connection tracking
           4.4.5.1 Descrizione
           4.4.5.2 Strutture e funzioni disponibili
           4.4.5.3 Esempio della struttura di un modulo di aiuto del conntrack
        4.4.6 Moduli di aiuto NAT
           4.4.6.1 Descrizione
           4.4.6.2 Strutture e funzioni disponibili
           4.4.6.3 Esempio di un modulo NAT di aiuto
     4.5 Comprendere Netfilter
     4.6 Realizzare nuovi moduli Netfilter
        4.6.1 Introduzione agli hook di Netfilter
        4.6.2 Processare i pacchetti accodati
        4.6.3 Ricevere comandi dallo Userspace
     4.7 Gestione del pacchetto nello userspace

  5. Portare moduli di filtraggio dei pacchetti da 2.0 e 2.2

  6. La suite per il test

     6.1 Realizzare un test
     6.2 Variabili e ambiente
     6.3 Tool utili
        6.3.1 gen_ip
        6.3.2 rcv_ip
        6.3.3 gen_err
        6.3.4 local_ip
     6.4 Consigli vari

  7. Motivazione

  8. Ringraziamenti



  ______________________________________________________________________

  1.  Introduzione

  Salve ragazzi.


  Questo documento è un viaggio che in alcune parti sarà molto comodo
  mentre in altre vi farà sentire abbandonati a voi stessi.  Il miglior
  consiglio che vi posso dare è di prendere una grossa, intima tazza di
  caffè o di cioccolata calda, di procurarvi una confortevole sedia e,
  prima di avventurarvi nel mondo a volte pericoloso del network
  hacking, di assorbirne il contenuto.


  Per comprendere meglio come utilizzare l'infrastruttura presente al di
  sopra del framework netfilter, raccomando di leggere il Packet
  Filtering HOWTO e il NAT HOWTO.  Per informazioni riguardanti la
  programmazione del kernel suggerisco la Rusty's Unreliable Guide to
  Kernel Hacking e la Rusty's Unreliable Guide to Kernel Locking.


  (C) 2000 Paul `Rusty' Russell.  Licenza GNU GPL.


  1.1.  Che cos'è netfilter?

  netfilter è un framework per il manipolamento dei pacchetti, esterno
  alla normale interfaccia socket Berkeley.  Consta di 4 parti. Prima
  parte, ogni protocollo definisce degli "hook" (IPv4 ne definisce 5) i
  quali sono punti ben definiti in una traversata dei pacchetti nel
  protocol stack.  In ciascuno di questi punti, il protocollo richiamerà
  il framework netfilter fornendo il pacchetto e il numero dell'hook.


  Seconda parte, porzioni del kernel possono registrarsi per
  "ascoltare", per ogni protocollo, differenti hook.  Perciò quando un
  pacchetto è passato al framework netfilter, esso controlla se qualcuno
  si è registrato per quel determinato protocollo e hook; se sì, a
  ciascuno di essi è data, in ordine, una chance per esaminare (e
  possibilmente alterare) il pacchetto, eliminarlo (NF_DROP),
  consentirgli di proseguire (NF_ACCEPT), indicare a netfilter di
  dimenticarsi di esso (NF_STOLEN), oppure ancora di chiedere a
  netfilter di accodarlo per lo userspace (NF_QUEUE).


  Terza parte, i pacchetti che sono stati accodati sono sistemati (dal
  driver ip_queue) per essere inviati allo userspace; questi pacchetti
  sono gestiti in modo asincrono.


  La parte finale consiste in splendidi commenti sul codice e nella
  documentazione.  Questa è strumentale per ogni progetto sperimentale.
  Il motto di netfilter (rubato spudoratamente a Cort Dougan) è:

               ``Orbene... quanto è meglio di KDE?''





  (Questo motto si affianca a `Frustami, colpiscimi, fammi utilizzare
  ipchains').


  In aggiunta a questo framework grezzo sono stati realizzati vari
  moduli che forniscono funzionalità simili ai kernel precedenti (pre-
  netfilter), in particolare un sistema NAT e uno di filtraggio dei
  pacchetti (iptables) entrambi estendibili.


  1.2.  Che cos'è che non va con la 2.0 e la 2.2?


  1. infrastruttura per il passaggio dei pacchetti allo userspace non
     radicata:

  ·  Programmare il kernel è complicato

  ·  Il codice per il kernel deve essere sviluppato in C/C++

  ·  Tattiche di filtraggio dinamico non sono collocate nel kernel

  ·  2.2 ha introdotto la copia dei pacchetti verso lo userspace
     attraverso netlink, ma la reintroduzione è lenta e soggetta a
     controlli di `sanità'.  Ad esempio, non è possibile avere pacchetti
     reimmessi che sostengono di arrivare da un'interfaccia esistente.

  2. Il proxy trasparente è un "accrocchio":

  ·  ogni pacchetto è controllato per stabilire se esiste un legame
     socket con questo indirizzo

  ·  Al root è consentito di collegarsi ad indirizzi estranei

  ·  Non è possibile redirigere i pacchetti generati localmente

  ·  REDIRECT non gestisce le risposte UDP: redirigere pacchetti UDP
     named verso 1153 non funziona in quanto alcuni client non
     gradiscono risposte provenienti da una porta che non sia la 53.

  ·  REDIRECT non è coordinata con l'allocazione delle porte tcp/udp: un
     utente può ottenere una porta shadow attraverso una regola
     REDIRECT.

  ·  E' risultato corrotto almeno due volte durante la serie 2.1.

  ·  Il codice è estremamente intrusivo. Si considerino le statistiche
     del numero di #ifdef CONFIG_IP_TRANSPARENT_PROXY presenti nella
     2.2.1: 34 occorrenze in 11 file.  Le si confrontino con
     CONFIG_IP_FIREWALL, il quale ha 10 occorrenze in 5 file.

  3. Creare regole di filtraggio dei pacchetti indipendenti dagli
     indirizzi delle interfacce non è possibile:

  ·  E' necessario conoscere gli indirizzi locali delle interfacce per
     distinguere pacchetti generati localmente o destinati localmente,
     da quelli in transito.

  ·  Non è sufficiente però nei casi di redirezione o mascheramento.

  ·  La catena forward ha solo l'informazione riguardante l'interfaccia
     di uscita, ciò significa che è necessario immaginare da dove arriva
     un pacchetto in base alle conoscenze sulla topografia della rete.


  4. Il mascheramento è incluso nel filtraggio dei pacchetti:

     interazioni tra filtraggio dei pacchetti e mascheramento rendono
     complessa la realizzazione del firewall:

  ·  Al filtraggio in input, i pacchetti in risposta appaiono destinati
     alla box stessa

  ·  Al filtraggio nella forward, i pacchetti demascherati non sono
     visti del tutto

  ·  Al filtraggio in output, i pacchetti appaiono provenienti dalla box
     locale


  5. manipolazione TOS, redirezione, ICMP unreachable e marcamento (che
     riguarda port forwarding, instradamento e QoS) sono collocati
     assieme al codice di filtraggio.

  6. il codice di ipchains non è né modulare, né estendibile (per
     esempio filtraggio per indirizzo, filtraggio in base alle opzioni,
     ecc.).

  7. La mancanza di un'infrastruttura adeguata ha portato alla
     proliferazione di differenti tecniche:

  ·  Mascheramento, più moduli per protocollo

  ·  Fast static NAT attraverso il codice di instradamento (senza
     gestione per protocollo)

  ·  Port forwarding, redirezione, auto forwarding

  ·  Linux NAT e progetti Server Virtuale.



  8. Incompatibilità tra CONFIG_NET_FASTROUTE e filtraggio dei
     pacchetti:

  ·  Pacchetti in transito attraversano in ogni caso tre catene

  ·  Nessun modo per segnalare se queste catene possono essere
     oltrepassate.


  9. L'ispezione dei pacchetti scartati a causa della protezione di
     instradamento (es. Source Address Verification) non è possibile.

  10.
     Nessun modo per leggere atomicamente i contatori delle regole di
     filtraggio dei pacchetti.

  11.
     L'opzione CONFIG_IP_ALWAYS_DEFRAG è da selezionare in fase di
     compilazione, ciò rende difficile la vita alle distribuzioni che
     desiderano un kernel con funzionalità generiche.




  1.3.  Chi sei?

  Sono l'unico tanto insensato disponibile a farlo.  Come coautore di
  ipchains e attuale manutentore del Linux Kernel IP Firewall ho
  conosciuto molti dei problemi incontrati dalle persone con l'attuale
  sistema, in aggiunta mi sono anche esposto a cercare di comprendere
  cosa cercavano di fare.


  1.4.  Perché si pianta?

  Woah! Avreste dovuto vederlo la scorsa settimana!


  Siccome non sono un così grande programmatore, come noi tutti potremmo
  desiderare, sicuramente non ho esaminato tutti gli scenari, a causa di
  mancanza di tempo, attrezzatura e/o ispirazione.


  2.  Dove si può reperire l'ultima versione?

  Esiste un server CVS su samba.org che contiene gli ultimi HOWTO, i
  tool userspace e la testsuite. Per visite saltuarie si può utilizzare
  l'interfaccia web <http://cvs.samba.org/cgi-bin/cvsweb/netfilter/>.

  Per prelevare gli ultimi sorgenti, si possono seguire le seguenti
  fasi:

  1. Accedere al server SAMBA CVS come anonimo:


       cvs -d :pserver:cvs@cvs.samba.org:/cvsroot login




  2. Quando viene richiesta la password digitare `cvs'.

  3. Controllare il codice che si utilizza:


       cvs -d :pserver:cvs@cvs.samba.org:/cvsroot co netfilter




  4. Per aggiornare all'ultima versione utilizzare


       cvs update -d -P





  3.  L'architettura di netfilter

  Netfilter consiste semplicemente in una serie di hook collocati in
  vari punti del protocol stack (in questo momento, IPv4, IPv6 e
  DECnet).  Il diagramma (idealizzato) di attraversamento nel caso
  dell'IPv4 assomiglia al seguente:





  Pacchetto che attraversa il sistema Netfilter:

     --->[1]--->[Instradamento]--->[3]--->[4]--->
                      |                 ^
                      |                 |
                      |           [Instradamento]
                      v                 |
                     [2]               [5]
                      |                 ^
                      |                 |
                      v                 |




  I pacchetti arrivano sulla sinistra: dopo aver passato il semplice
  controllo di sanità (ossia, no troncature, IP checksum OK, ricezione
  non confusa) sono passati all'hook NF_IP_PRE_ROUTING [1] del framework
  netfilter.


  Successivamente entrano nel codice di routing, il quale decide se il
  pacchetto è destinato ad un'altra interfaccia o ad un processo locale.
  Il codice di routing potrebbe scartare i pacchetti non instradabili.


  Se è destinato alla box stessa, il framework netfilter, prima che il
  pacchetto sia passato al processo (se presente), è chiamato nuovamente
  per l'hook NF_IP_LOCAL_IN [2].

  Se è invece destinato ad un'altra interfaccia il framework netfilter è
  chiamato per l'hook NF_IP_FORWARD [3].


  Il pacchetto poi, prima di essere immesso nuovamente nel cavo, passa
  all'hook finale, l'hook NF_IP_POST_ROUTING [4].


  L'hook NF_IP_LOCAL_OUT [5] è chiamato per i pacchetti creati
  localmente.  Si può qui notare che il codice di routing avviene dopo
  che questo hook è stato chiamato: di fatto, il codice di routing è
  chiamato prima (per comprendere l'indirizzo IP sorgente e alcune
  opzioni IP) e richiamato nuovamente se il pacchetto è stato alterato.


  3.1.  Fondamenti di Netfilter

  Ora segue un esempio riguardante netfilter per IPv4, si potrà notare
  quando ciascun hook è attivato. Questa è l'essenza di netfilter.


  I moduli del kernel possono registrarsi per "ascoltare" qualsiasi
  hook.  Un modulo che registra una funzione deve specificare anche la
  priorità che essa deve avere nell'ambito dell'hook.  Quando l'hook di
  netfilter sarà invocato dal codice del nucleo di rete, ciascun modulo
  registrato per questo punto sarà richiamato secondo l'ordine di
  priorità e sarà libero di manipolare il pacchetto.  Il modulo potrà
  inoltre specificare a netfilter di effettuare una delle seguenti
  cinque cose:


  1. NF_ACCEPT: continua la traversata normalmente.

  2. NF_DROP: scarta il pacchetto; non continuare la traversata.


  3. NF_STOLEN: ho prelevato il pacchetto; non continuare la traversata.

  4. NF_QUEUE: accoda il pacchetto (di solito per gestione userspace).

  5. NF_REPEAT: chiama di nuovo questo hook.


  Le altre parti di netfilter (gestione dei pacchetti accodati,
  commenti) saranno trattate più avanti nella sezione riguardante il
  kernel.


  Subito dopo questi concetti base, si possono realizzare complesse
  manipolazioni dei pacchetti, come descritto nei prossimi due
  paragrafi.


  3.2.  Selezione dei pacchetti: IP Tables

  Un sistema di selezione dei pacchetti, denominato IP Tables, è stato
  realizzato al di sopra del framework netfilter.  E' un diretto
  discendente di ipchains (che proviene da ipfwadm, che a sua volta
  deriva da ipfw IIRC della BSD) con in più l'estendibilità.  I moduli
  del kernel possono registrare una nuova tabella e richiedere che un
  determinato pacchetto attraversi la tabella indicata.  Questo metodo
  di selezione dei pacchetti è utilizzato per il filtraggio dei
  pacchetti (tabella `filter'), per il Network Address Translation
  (tabella `nat'), e per il manipolamento generico dei pacchetti prima
  dell'instradamento (tabella `mangle').


  Gli hook registrati con netfilter sono (con le funzioni di ciascun
  hook disposte secondo l'ordine con cui sono attualmente richiamate):





           --->PRE------>[ROUTE]--->FWD---------->POST------>
               Conntrack    |       Filter   ^    NAT (Src)
               Mangle       |                |    Conntrack
               NAT (Dst)    |             [ROUTE]
               (QDisc)      v                |
                            IN Filter       OUT Conntrack
                            |  Conntrack     ^  Mangle
                            |                |  NAT (Dst)
                            v                |  Filter







  3.2.1.  Filtraggio dei pacchetti

  Questa tabella, `filter', non deve mai alterare i pacchetti: solo
  filtrarli.

  Uno dei vantaggi del filtro iptables rispetto a ipchains consiste nel
  fatto che è più compatto e veloce, inoltre si aggancia in netfilter
  nei punti NF_IP_LOCAL_IN, NF_IP_FORWARD e NF_IP_LOCAL_OUT.  Ciò
  significa che per ogni pacchetto c'è un (e solo un) punto possibile
  per il filtraggio.  Le cose in questo modo sono per l'utente molto più
  semplici rispetto a ipchains.  Inoltre, il framework netfilter
  provvede sia un'interfaccia di input che di output per l'hook
  NF_IP_FORWARD, ciò implica la possibilità di avere diversi tipi, e
  anche piuttosto semplici, di filtraggio.


  Nota: ho implementato le porzione del kernel riguardanti ipchains e
  ipfwadm come moduli sopra netfilter, consentendo così di poter
  utilizzare i vecchi tool userspace ipfwadm e ipchains senza la
  necessità di un aggiornamento.


  3.2.2.  NAT

  Questo è il regno della tabella `nat', che riceve i pacchetti da due
  hook di netfilter: per i pacchetti non locali NF_IP_PRE_ROUTING e
  NF_IP_POST_ROUTING sono perfetti per la modifica rispettivamente della
  destinazione e della sorgente.  Se CONFIG_IP_NF_NAT_LOCAL è definito,
  gli hook NF_IP_LOCAL_OUT e NF_IP_LOCAL_IN sono utilizzabili per
  alterare la destinazione dei pacchetti locali.


  Questa tabella è leggermente differente rispetto alla tabella
  `filter', in questa solo il primo pacchetto di una nuova connessione
  attraversa la tabella: il risultato di questa traversata sarà poi
  applicata a tutti i pacchetti futuri appartenenti alla stessa
  connessione.


  3.2.2.1.  Mascheramento, Port Forwarding, Proxy Trasparente

  Ho suddiviso il NAT in Source NAT (dove al primo pacchetto viene
  alterata la sorgente) e Destination NAT (al primo pacchetto viene
  alterata la destinazione).


  Il mascheramento è una forma speciale di Source NAT: port forwarding e
  il proxy trasparente sono invece forme speciali di Destination NAT.
  Queste ora utilizzano tutte il framework NAT, invece di essere entità
  indipendenti.


  3.2.3.  Manipolamento dei pacchetti

  La tabella di manipolamento dei pacchetti (tabella `mangle') è
  utilizzata per il cambiamento delle informazioni dei pacchetti.  Si
  "aggancia" a netfilter nei punti NF_IP_PRE_ROUTING e NF_IP_LOCAL_OUT.



  3.3.  Connection Tracking

  Il Connection tracking (tracciamento delle connessioni) è fondamentale
  per il NAT, tuttavia è implementato come modulo; ciò per consentire di
  estendere il codice di filtraggio dei pacchetti, permettendo
  l'utilizzo semplice e pulito del connection tracking (modulo `state').


  3.4.  Altre aggiunte

  La nuova flessibilità fornisce l'opportunità di realizzare cose
  davvero incredibili, oltre che di apportare miglioramenti o di
  realizzare completi rimpiazzi da combinare e adattare.





  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.


  4.1.1.  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().
        La funzione deve ritornare uno zero affinché l'iterazione
        continui oppure un valore diverso da zero per interromperla.


     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().  La funzione deve ritornare uno
        zero affinché l'iterazioni continui oppure un valore diverso da
        zero per interromperla.


  4.1.2.  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'.


  4.1.3.  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.


  4.2.1.  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'.


  4.2.1.1.  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 ip_tables.


  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 attiva (la struttura che è stata preparata
        nello userspace), 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.


  4.2.1.2.  Nuovi target

  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, i puntatori ai device input ed output (uno di essi
        potrebbe essere NULL), un puntatore ai dati target e la
        posizione della regola nella tabella.  La funzione target può
        restituire IPT_CONTINUE (-1) se la traversata deve continuare
        oppure un verdetto (NF_ACCEPT, NF_STOLEN ecc).


     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.


  4.2.1.3.  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.



  4.2.2.  Tool userspace

  Ora realizzato il proprio scintillante 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 (shared libraries).


  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: essa può essere
  utilizzata per inizializzare parte della struttura oppure per fornire
  ulteriori opzioni. Insisto ora sull'utilizzo delle librerie condivise
  anche quando non devono fare nulla, in quanto ciò riduce i problemi
  che si riscontrano quando le librerie risultano mancanti.


  Esistono funzioni molto utili descritte nell'intestazione di
  `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 basa su `strtol' (si vedano le manpage),
        ciò significa che un "0x" iniziale indicherà un numero in base
        esadecimale e uno "0" iniziale un numero 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.



  4.2.2.1.  Nuove funzioni match

  La funzione _init() della libreria 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 NETFILTER_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 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 (allo 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,
        offerta dalla propria libreria. E' fusa con le opzioni correnti
        e passata alla getopt_long; consultare le man page 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.


  4.2.2.2.  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.


  4.2.3.  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 fornite come astrazioni della libreria. Le catene
  definite dall'utente sono etichettate inserendo un nodo di errore
  prima dell'inizio della catena dell'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).



  I target standard supportati sono: ACCEPT, DROP, QUEUE i quali sono
  tradotti rispettivamente in NF_ACCEPT, NF_DROP, e NF_QUEUE, RETURN (il
  quale è tradotto con un valore speciale IPT_RETURN gestito da
  ip_tables), e JUMP (il quale viene tradotto a partire dal nome della
  catena con un offset reale 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 presente.


     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.


  4.3.1.  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 infos[] array,
  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 risposta.


     IP_CT_RELATED + IP_CT_IS_REPLY
        Il pacchetto è correlato ad una connessione, ed è passato nella
        direzione 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 sulla tabella `nat'.  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'.





  4.4.1.  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).


  4.4.2.  Nuovi protocolli

  4.4.2.1.  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.  Si 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 il
        seguente idioma 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 relativa al protocollo
        della tupla 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 sarà stato impostato
        per l'intervallo.



  4.4.3.  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()'.


  4.4.4.  Aiutanti dei protocolli

  Gli "aiutanti" dei protocolli (protocol helper) riguardanti il
  connection tracking consentono al codice del connection tracking di
  comprendere quei protocolli che utilizzano connessioni di rete
  multiple (es. FTP) marcando le connessioni `figlie' come correlate,
  relative alla connessione iniziale, di solito leggendo gli indirizzi
  relativi al di fuori dal data stream.


  Gli "aiutanti" per il NAT effettuano invece due cose: prima di tutto
  consentono al codice NAT di manipolare il data stream in modo da poter
  cambiare l'indirizzo contenuto al suo interno, e secondo di permettere
  il NAT sulla connessione relativa basata sulla connessione originale.


  4.4.5.  Moduli di aiuto del connection tracking

  4.4.5.1.  Descrizione


  Il dovere di un modulo del connection tracking è quello di specificare
  quali pacchetti appartengono ad una connessione già stabilita. Il
  modulo, per raggiungere lo scopo, ha i seguenti mezzi:


  ·  Segnalare a netfilter a quali pacchetti è interessato (la
     maggioranza degli "aiutanti" operano su una particolare porta).

  ·  Registrare una funzione con netfilter. Questa funzione sarà
     chiamata per ciascun pacchetto che soddisferà i suddetti criteri.


  ·  Una funzione `ip_conntrack_expect_related()' che potrà essere
     invocata da lì per segnalare a netfilter di attendere una
     connessione relativa.



  4.4.5.2.  Strutture e funzioni disponibili

  La propria funzione di inizializzazione del modulo del kernel deve
  chiamare `ip_conntrack_helper_register()' fornendo un puntatore ad una
  `struct ip_conntrack_helper'.  Questa struttura ha i seguenti campi:


     list
        Questa è la testa della lista linkata.  Netfilter gestisce
        questa lista internamente. La si inizializzi a `{ NULL, NULL }'.


     tuple
        Questa è una `struct ip_conntrack_tuple' che specifica a quali
        pacchetti è interessato il modulo di aiuto.


     mask
        Ancora una `struct ip_conntrack_tuple'.  Questa maschera
        specifica quali bit della tupla sono validi.


     help
        La funzione che netfilter dovrebbe chiamare per ogni accoppiata
        tuple+mask di un pacchetto.



  4.4.5.3.  Esempio della struttura di un modulo di aiuto del conntrack





































  ______________________________________________________________________

  #define FOO_PORT        111

     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
                decidi come i relativi pacchetti appariranno */

             if (there_will_be_new_packets_related_to_this_connection)
             {
                     t = new_tuple_specifying_related_packets;
                     ip_conntrack_expect_related(ct, &t);

                     /* salva importanti informazioni per il nat in
                             ct->help.ct_foo_info;        */

             }
             return NF_ACCEPT;
     }

     static struct ip_conntrack_helper foo;

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

             /* Siamo interessati a tutti i pacchetti TCP aventi porta 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);
     }

  ______________________________________________________________________







  4.4.6.  Moduli di aiuto NAT

  4.4.6.1.  Descrizione


  I moduli di aiuto per il NAT effettuano alcune gestioni NAT
  specifiche.  Di solito queste includono manipolazioni al volo dei
  dati: si pensi al comando PORT in FTP, dove il client segnala al
  server a quale IP/porta connettersi.  Per questo motivo il modulo di
  aiuto deve sostituire la IP/porta nella connessione di controllo FTP
  solo dopo il comando PORT.



  Se si è impegnati con il TCP allora le cose sono leggermente più
  complicate.  La ragione è un possibile cambiamento della dimensione
  dei pacchetti (esempio FTP: la lunghezza di una stringa rappresentante
  una tupla IP/porta cambiata dopo il comando PORT) Se si cambia la
  dimensione del pacchetto si otterrà una differenza syn/ack tra i
  versanti sinistro e destro della NAT box (ossia se si è esteso un
  pacchetto di 4 otteti, si dovrà aggiungere questo offset al sequence
  number del protocollo TCP di ciascun pacchetto che seguirà).



  Gestione particolare del NAT sarà necessaria anche su tutti i
  pacchetti relativi.  Si prenda ancora come esempio FTP dove tutti i
  pacchetti in arrivo attraverso la connessione DATA dovranno subire il
  NAT con la IP/porta fornita dal client con il comando PORT, attraverso
  la connessione di controllo, anziché passare attraverso la normale
  consultazione della tabella.


  ·  callback per i pacchetti che causano la connessione relativa
     (foo_help)

  ·  callback per tutti i pacchetti relativi (foo_nat_expected)



  4.4.6.2.  Strutture e funzioni disponibili

  La propria funzione `init()' del modulo nat di aiuto richiama
  `ip_nat_helper_register()' fornendo un puntatore alla `struct
  ip_nat_helper'.  Questa struttura ha i seguenti membri:


     list
        Ancora la testa della lista utilizzata internamente da
        netfilter.  La si inizializzi a { NULL, NULL }.


     tuple
        una `struct ip_conntrack_tuple' che descrive a quali pacchetti
        il proprio aiutante NAT è interessato.


     mask
        una `struct ip_conntrack_tuple', che segnala a netfilter quali
        bit della tupla sono validi.


     help
        La funzione di aiuto invocata per ciascuna coppia tuple+mask.


     name
        Il nome univoco che identifica questo "aiutante" del NAT.


  Questa è esattamente uguale alla scrittura di un "aiutante" per il
  connection tracking.

  Si può anche specificare che il proprio modulo è pronto a gestire il
  NAT di qualsiasi connessione attesa (presumibilmente impostata da un
  modulo del connection tracking), attraverso la funzione
  `ip_nat_expect_register()', la quale richiede una `struct
  ip_nat_expect'.  Questa struttura ha i seguenti membri:


     list
        Ancora una volta la testa della lista utilizzata internamente da
        netfilter.  La si inizializzi a { NULL, NULL }.


     expect
        funzione che effettua il NAT sulle connessioni attese.
        Restituisce vero se essa gestisce la connessione, altrimenti
        viene chiamata la funzione registrata successiva per vedere se
        essa è in grado di gestire il pacchetto.  Se restituisce vero,
        la funzione deve sancire il verdetto.




  4.4.6.3.

  Esempio di un modulo NAT di aiuto
















































  ______________________________________________________________________

     #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,
                             struct ip_conntrack *master,
                             struct ip_nat_info *masterinfo,
                             unsigned int *verdict)

     /* chiamata ogni qual volta arriva un pacchetto relativo
        come specificato nel modulo del connection tracking

        params:      pksb        packet buffer
                     hooknum        HOOK da cui arriva la chiamata (POST_ROUTING, PRE_ROUTING)
                     ct        informazioni riguardanti questa connessione (relativa)
                     info        &ct->nat.info
                     master     informazioni riguardanti la connessione principale
                     masterinfo &master->nat.info
                     verdict cosa fare del pacchetto se si restituisce 1.
     {
             /* Verifica che questa sia proveniente da foo_expect, non da ftp_expect, ecc */
             /* Quindi cambia giusto ip/porta del pacchetto con i valori mascherati
                (letti da master->tuplehash) per mapparla nello stesso modo,
                chiama ip_nat_setup_info, imposta *verdict, return 1. */

     }

     static int foo_help(struct ip_conntrack *ct,
                     struct ip_nat_info *info,
                     enum ip_conntrack_info ctinfo,
                     unsigned int hooknum,
                     struct sk_buff  **pksb)
     /* chiamata per il pacchetto che causa i pacchetti relativi
        params:      ct        informazioni relative alla connessione tracciata
                     info        (STATO: related, new, established, ... )
                     hooknum        HOOK da cui proviene la chiamata (POST_ROUTING, PRE_ROUTING)
                     pksb        packet buffer
     */
     {

             /* estrai informazioni riguardanti i pacchetti relativi in arrivo (
                si possono condividere informazioni con la foo_help del connection tracking).
                Scambia indirizzo/porta con i valori mascherati, inserisci tupla riguardante
                i pacchetti relativi  */
     }

     static struct ip_nat_expect foo_expect = {
             { NULL, NULL },
             foo_nat_expected };

     static struct ip_nat_helper hlpr;

     static int __init(void)
     {
             int ret;

             if ((ret = ip_nat_expect_register(&foo_expect)) == 0) {
                     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;

                     ret = ip_nat_helper_register(hlpr);
                     if (ret != 0)
                             ip_nat_expect_unregister(&foo_expect);
             }
             return ret;
     }

     static void __exit(void)
     {
             ip_nat_expect_unregister(&foo_expect);
             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

  4.6.1.  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'.



  4.6.2.  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: NF_DROP causa il loro scartamento,
  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.


  4.6.3.  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.


  5.  Portare moduli di filtraggio dei pacchetti da 2.0 e 2.2

  Si dia un'occhiata al file ip_fw_compat.c per un semplice esempio che
  dovrebbe rendere i porting piuttosto semplici.


  6.  La suite per il test

  Nella CVS è presente una suite per i test: più test la suite gestisce,
  e maggiore sarà la certezza che dei cambiamenti al codice non abbiano
  silenziosamente corrotto qualcosa. Test banali sono importanti quanto
  quelli più ingegnosi: sono i test banali che semplificano i test
  complessi (ci si assicuri che le basi funzionino correttamente prima
  di eseguire i test complessi).


  I test sono semplici: sono giusto degli script shell presenti nella
  sotto-directory testsuite/ che si suppone abbiano successo.  Gli
  script sono eseguiti in ordine alfabetico, quindi `01test' sarà
  eseguito prima di `02test'.  Correntemente ci sono 5 directory di
  test:


     00netfilter/
        test generici riguardanti il framework netfilter

     01iptables/
        test riguardanti iptables

     02conntrack/
        test riguardanti il connection tracking

     03NAT/
        test riguardanti il NAT

     04ipchains-compat/
        test riguardanti la compatibilità ipchains/ipfwadm

  All'interno della directory testsuite/ è presente uno script
  `test.sh'.  Esso configura due semplici interfacce (tap0 e tap1),
  abilita il forwarding, e rimuove tutti i moduli di netfilter.  Quindi
  esegue da ciascuna directory ogni script test.sh fino a quando uno
  fallisce.  Questo script ha due argomenti opzionali: `-v' che
  specifica di visualizzare ogni test processato e un nome opzionale di
  test: se è fornito, lo script salterà tutti i test fino a trovare
  quello specificato.


  6.1.  Realizzare un test

  Si crei un nuovo file in una directory appropriata: si provi a
  numerare il proprio test così sarà eseguito al momento opportuno.  Ad
  esempio, allo scopo di effettuare il test del tracciamento delle
  risposte ICMP (02conntrack/02reply.sh) è necessario innanzitutto
  controllare che i pacchetti ICMP uscenti siano tracciati correttamente
  (02conntrack/01simple.sh).


  Solitamente è meglio creare più file di piccole dimensioni, ciascuno
  dei quali si occupi di una sola area, ciò aiuta le persone che
  eseguono la testsuite ad isolare immediatamente i problemi.


  Se qualcosa non funziona durante il test, semplicemente si effettui un
  `exit 1', il quale causa un fallimento; se riguarda qualcosa che si
  aspettava fallisse, si potrebbe stampare un messaggio unico.  I propri
  test dovrebbero concludersi con `exit 0' se tutto è stato eseguito
  correttamente.  E' necessario controllare che tutti i comandi siano
  stati eseguiti con successo, utilizzando `set -e' all'inizio dello
  script oppure appendendo `|| exit 1' alla fine di ciascun comando.


  Le funzioni di aiuto `load_module' e `remove_module' possono essere
  utilizzate per caricare i moduli: con la testsuite non si dovrebbe mai
  contare sull'auto-caricamento a meno che non sia proprio quello che si
  desidera specificatamente verificare.


  6.2.  Variabili e ambiente

  Si hanno due interfacce in gioco: tap0 e tap1.  I loro indirizzi sono
  rispettivamente nelle variabili $TAP0 e $TAP1.  Entrambe hanno netmask
  255.255.255.0; le loro reti sono rispettivamente in $TAP0NET e
  $TAP1NET.


  E' presente un file temporaneo vuoto in $TMPFILE.  Esso è cancellato
  al termine del proprio test.


  Lo script sarà eseguito dalla directory testsuite/, se presente.
  Quindi si può accedere ai tool (quali iptables) utilizzando un path
  che cominci con `../userspace'.


  Lo script può visualizzare maggiori informazioni se $VERBOSE è
  impostata (si intende che l'utente specifichi `-v' dalla linea
  comandi).


  6.3.  Tool utili

  Ci sono parecchi tool utili nella sotto-directory "tools": ciascuno
  esce ritornando uno stato non zero se ha riscontrato un problema.


  6.3.1.  gen_ip

  Si possono generare pacchetti IP utilizzando `gen_ip', il quale emette
  un pacchetto IP verso lo standard input.  Si possono alimentare di
  pacchetti tap0 e tap1 inviando lo standard output verso /dev/tap0 e
  /dev/tap1 (questi sono creati subito dopo la prima esecuzione della
  testsuite, se non già esistenti).


  gen_ip è un programma semplice che è al momento piuttosto pignolo
  riguardo l'ordine degli argomenti.  Prima di tutto richiede gli
  argomenti generali opzionali:


     FRAG=offset,length
        Genera il pacchetto, quindi lo converte in un frammento
        utilizzando i parametri offset e lenght forniti.


     MF Imposta il bit `More Fragments'.


     MAC=xx:xx:xx:xx:xx:xx
        Imposta l'indirizzo sorgente MAC.


     TOS=tos
        Imposta il campo TOS del pacchetto (da 0 a 255).


  Seguono gli argomenti obbligatori:


     source ip
        Indirizzo IP sorgente del pacchetto.


     dest ip
        Indirizzo IP destinazione del pacchetto.


     length
        Lunghezza totale del pacchetto, intestazioni incluse.


     protocol
        Numero del protocollo del pacchetto, es. 17 = UDP.


  Poi gli argomenti dipendono dal protocollo: nel caso UDP (17), essi
  consistono nei numeri di porta sorgente e destinazione.  Nel caso ICMP
  (1), essi consistono nel tipo e nel codice del messaggio ICMP: se il
  tipo è 0 oppure 8 (ping-reply o ping) allora sono richiesti altri due
  argomenti (i campi ID e sequence).  Nel caso TCP sono richiesti la
  porta sorgente, la porta destinazione e i flag ("SYN", "SYN/ACK",
  "ACK", "RST" oppure "FIN").  Ci sono tre argomenti opzionali: "OPT="
  seguito da una lista di opzioni separate da virgole, "SYN=" seguito da
  un numero di sequenza e "ACK" seguito anch'esso da un numero di
  sequenza.  Infine, l'argomento opzionale "DATA" specifica che il
  "carico" del pacchetto TCP è da riempire con il contenuto dello
  standard input.


  6.3.2.  rcv_ip

  Si possono vedere i pacchetti IP utilizzando `rcv_ip', il quale
  visualizza la linea comandi il più possibile corrispondente con i
  valori originali dati a gen_ip (i frammenti sono l'eccezione).


  Ciò è estremamente utile per l'analisi dei pacchetti.  Richiede due
  argomenti obbligatori:


     wait time
        Il tempo massimo di attesa, espresso in secondi, per un
        pacchetto proveniente dallo standard input.



     iterations
        Numero di pacchetti da ricevere.


  C'è inoltre un argomento opzionale "DATA" che causa la
  visualizzazione, dopo l'intestazione del pacchetto, del contenuto di
  un pacchetto TCP sullo standard output.


  La modalità di utilizzo di `rcv_ip' in uno script shell è la seguente:


  # Imposta il controllo, in questo modo si può utilizzare & negli script shell
  set -m

  # Attendi due secondi per un pacchetto proveniente da tap0
  ../tools/rcv_ip 2 1 < /dev/tap0 > $TMPFILE &

  # Assicurati che rcv_ip sia in funzione
  sleep 1

  # Invia un ping
  ../tools/gen_ip $TAP1NET.2 $TAP0NET.2 100 1 8 0 55 57 > /dev/tap1 || exit 1

  # Attendi rcv_ip,
  if wait %../tools/rcv_ip; then :
  else
      echo rcv_ip failed:
      cat $TMPFILE
      exit 1
  fi




  6.3.3.  gen_err

  Questo programma prende un pacchetto (come generato da gen_ip, ad
  esempio) dallo standard input e lo rigira in un errore ICMP.


  Richiede tre argomenti: un indirizzo IP sorgente, un tipo e un codice.
  L'IP di destinazione sarà impostato utilizzando l'indirizzo IP
  sorgente del pacchetto dato allo standard input.


  6.3.4.  local_ip

  Questo prende un pacchetto dallo standard input e lo immette nel
  sistema da un raw socket. Ciò consente di dare l'apparenza di un
  pacchetto generato localmente (come separato dal pacchetto fornito ad
  uno dei dispositivi ethertap, sembra quindi un pacchetto generato in
  remoto).


  6.4.  Consigli vari

  Tutti i tool assumono di poter fare qualsiasi cosa in una lettura o
  scrittura: ciò è vero per i dispositivi ethertap, ma potrebbe non
  essere vero se si sta facendo qualcosa di complicato con le pipe.


  dd può essere utilizzato per "tagliare" i pacchetti: dd ha un'opzione
  obs (output block size) che può essere usata per produrre in output il
  pacchetto in una singola scrittura.

  Si effettui prima di tutto il test per "successo": ad esempio per
  verificare se i pacchetti sono bloccati con successo, prima si testi
  se i pacchetti passano normalmente poi che alcuni siano bloccati. In
  caso contrario un problema non correlato potrebbe fermare i pacchetti
  ...


  Si cerchi di scrivere test corretti, non del tipo `provare in modo
  casuale e vedere cosa accade'.  Se un test corretto fallisce, ciò
  rappresenta un'ottima cosa da sapere.  Se invece un test casuale
  fallisce non è di grande aiuto.


  Se un test fallisce senza ritornare un messaggio, si può aggiungere un
  `-x' alla prima riga dello script (es. `#! /bin/sh -x') per vedere
  quali comandi sono stati eseguiti.


  Se un test fallisce di tanto in tanto, si controllino eventuali
  interferenze casuali nel traffico di rete (si provi a "disabilitare"
  tutte le proprie interfacce esterne).  Stando nella stessa rete di
  Andrew Tridgell, ad esempio, tendo ad essere assillato dai broadcast
  di Windows.


  7.  Motivazione

  Come sviluppatore di ipchains ho realizzato (in uno di quei momenti di
  flash-abbaglianti-mentre-attendi-di-entrare in un ristorante cinese a
  Sidney) che il filtraggio dei pacchetti era effettuato nel posto
  sbagliato.  Non riesco a trovarla ora, ma ricordo una lettera inviata
  ad Alan Cox, che gentilmente rispondeva `perché prima di tutto non
  termini quello che stai facendo, probabilmente è la cosa giusta'.  In
  parole povere, pragmatismo doveva prevalere su "La Cosa Giusta".


  Dopo aver terminato ipchains, che inizialmente doveva essere una
  modifica minore della parte del kernel riguardante ipfwadm, diventata
  poi una consistente riscrittura, e aver scritto l'HOWTO, mi sono reso
  conto di quanta confusione esistesse nella vasta comunità di Linux a
  riguardo delle questioni quali filtraggio dei pacchetti,
  mascheramento, port forwarding e così via.


  Questa è la soddisfazione di fornire il proprio supporto: ottieni una
  stretta percezione su cosa gli utenti cercano di fare, e con che cosa
  si trovano a lottare.  Il software free per lo più è ricompensato
  quando è nelle mani della maggior parte degli utenti (questo è il
  punto, giusto?), e ciò consente poi di poterlo rendere migliore.
  L'architettura, non la documentazione, è la chiave per risolvere i
  problemi.


  Quindi avevo esperienza, per quanto riguardava il codice di ipchains,
  e una buona idea su cosa le persone volevano fare. Esistevano solo due
  problemi.


  Primo, non volevo tornare indietro sulla sicurezza. Essere un
  consulente sulla sicurezza è un tiro alla fune costante e morale tra
  la coscienza e il portafogli. Ad un livello di principio si vende la
  percezione della sicurezza, la quale è in discordia con l'attuale
  sicurezza.  Forse lavorare nel campo militare, dove si comprende la
  sicurezza, potrebbe essere differente.


  Il secondo problema è che i nuovi utenti non sono l'unica
  preoccupazione; un numero crescente di compagnie e ISP utilizzano
  queste funzionalità.  C'era quindi la necessità di un input fidato
  proveniente da queste classi di utenti se si desiderava poi scalare
  verso gli utenti "casalinghi".


  Questi problemi sono stati risolti quando mi sono imbattuto in David
  Bonn, di fama WatchGuard, allo Usenix nel Luglio 1998.  Stavano
  cercando un coder del kernel Linux; alla fine concordarono di
  indirizzarmi per un mese ai loro uffici di Seattle per vedere se si
  poteva elaborare un accordo in cui loro si sarebbero impegnati a
  sponsorizzare il mio nuovo codice e il mio sforzo per il supporto.  La
  cifra concordata fu maggiore di quanto aspettato, perciò non ottenni
  un taglio dello stipendio.  Ciò significa che non ho più da pensare a
  consulenze esterne per un po'.


  L'esposizione alla WatchGuard mi portava all'esposizione a quei grandi
  clienti di cui avevo bisogno, e l'indipendenza da loro mi permetteva
  di supportare tutti gli utenti (es. concorrenti della WatchGuard) in
  modo eguale.


  Avrei potuto quindi sviluppare netfilter con comodità, portare
  ipchains al di sopra, ed essere soddisfatto. Sfortunatamente, il
  codice di masquerading sarebbe comunque rimasto nel kernel: rendere il
  masquerading indipendente dal filtraggio è uno dei punti più
  importanti nel momento in cui si sposta il filtro dei pacchetti, ma
  per fare ciò è necessario portare anche il masquerading al di sopra
  del framework netfilter.


  La mia esperienza con la funzionalità `interface-address' di ipfwadm
  (rimossa con ipchains) mi aveva insegnato che non c'era alcuna
  speranza di togliere il codice del masquerading e di attendere che
  qualcuno, che ne avesse bisogno, realizzasse un porting al di sopra di
  netfilter al posto mio.


  Perciò avevo bisogno di avere almeno tante funzionalità quante il
  codice corrente; preferibilmente qualcuna in più, per incoraggiare
  utenti di nicchia ad adottarlo. Ciò significava rimpiazzare il proxy
  trasparente (volentieri!), masquerading e port forwarding. In altre
  parole, un completo strato NAT.


  Anche se avevo deciso di portare lo strato esistente del masquerading,
  invece di scrivere un sistema NAT generico, il codice del masquerading
  ormai mostrava già i segni dell'età, e mancanza di manutenzione.  Non
  c'era un manutentore del masquerading e si vedeva.  Sembra che gli
  utenti più "seri" non utilizzino affatto il masquerading, e inoltre
  non ci sono molti utenti "casalinghi" disponibili alla manutenzione.
  Persone ottime come Juan Ciarlante avevano apportato correzioni, ma
  ormai si era arrivati ad uno stadio (essendo stato esteso più e più
  volte) che una riscrittura era davvero necessaria.


  Prego notare che non ero la persona adatta ad effettuare una
  riscrittura del NAT: non utilizzavo più il masquerading, e non avevo
  studiato il codice esistente a suo tempo.  Forse è questa la ragione
  per cui mi ha impegnato più a lungo di quanto previsto.  Il risultato
  è comunque abbastanza buono, secondo la mia opinione, e assicuro che
  ho imparato davvero molto. Non dubito comunque che una seconda
  versione sarà migliore, una volta constatato come le persone la
  utilizzano.
  8.  Ringraziamenti

  Grazie a tutti coloro che sono stati di aiuto, in modo particolare
  Harald Welte per aver realizzato la sezione riguardante gli "aiutanti"
  dei protocolli.