Abbiamo esposto il problema, dove si deve intervenire, e quali strumenti si hanno a disposizione: al lavoro, dunque.
4.1 Ingannare gli endpoint
Come abbiamo detto in un capitolo precedente, la presenza di un ALG deve essere trasparente agli utenti. Per rispettare questo requisito dobbiamo "ingannare" gli endpoint: ogni terminale deve "convincersi" di comunicare con un terminale attestato sulla sua stessa rete.
L'ambiente di test è quello illustrato in figura 4.1:

Figura 4.1 Ambiente di test
Supponiamo già effettuate le fasi iniziali della chiamata; lo scambio dei messaggi H.245 segue lo schema illustrato in figura 4.2

Figura 4.2 Fallimento apertura dei canali logici
Nell' OLC mandato da Alice a Bob c'è l'indirizzo RTCP di Alice, ad esempio:
RTCP Alice= 10.244.116.107 : 12001
La prima parte è l'indirizzo IP dell' host Alice, mentre 12001 è un numero di porta UDP, sulla quale Alice dichiara di essere pronta a ricevere messaggi del protocollo RTCP.
L'MC inoltra il messaggio a Bob, che immagazzina l'informazione, e restituisce un messaggio OLC ACK che contiene il proprio indirizzo RTCP e quello RTP, ad esempio:
RTCP Bob= 192.168.0.2: 5001
RTP Bob = 192.168.0.2: 5000
Al solito, la prima parte è l'indirizzo IP di Bob, mentre 5000 e 5001 sono le porte adiacenti UDP sulle quali Bob si dichiara pronto a ricevere, rispettivamente, controllo RTCP e dati multimediali codificati RTP.
L'MC inoltra il messaggio ad Alice, che ora è pronta ad aprire un canale logico unidirezionale verso la porta UDP 5000 di Bob;
I pacchetti contenenti i dati multimediali vengono confezionati, ma conterranno nel campo destination dell'intestazione IP un indirizzo IP non appartenente alla rete visibile da Alice. Essi verranno allora spediti verso un gateway di default eventualmente presente sulla rete 10.244.116.x, e da questo scartati.
Lo scambio OLC-OLC ACK nella direzione opposta segue esattamente la stessa sorte, e nessuno dei due endpoint riesce a "sentire" l'altro.
L'inganno
Ogni volta che all' MC arriva un messaggio, esso ne estrae informazioni, che va a memorizzare in apposite strutture dati. Attraverso le API fornite possiamo variare il valore di alcuni campi di queste strutture prima che il Media Controller, a partire da queste, confezioni il messaggio uscente.
La decisione sull'intervenire o meno è data dallo stato di un flag, settato dal gatekeeper in base al contenuto del messaggio ARQ, dal quale si può desumere l'indirizzo IP di chiamante e chiamato. Ovviamente, se il chiamante e il chiamato appartengono alla stessa rete, non c'e bisogno di darsi tanta pena.
Supponiamo d'ora in poi di essere nella situazione in cui il chiamante e il chiamato non si trovano sulla stessa rete.
Riferendoci alla figura 4.3, che illustra il diagramma di stato relativo all'apertura di un canale:
Apertura canale

Figura 4.3Intervento sui messaggi OLC e OLC ACK
Il primo campo è ora un indirizzo della rete di Alice, e precisamente l'indirizzo dell'interfaccia eth0 di Urania. Sarà a quest'indirizzo che Alice spedirà i pacchetti RTP contenenti i dati multimediali.
Ripetendo il procedimento per il canale aperto da Bob verso Alice, siamo giunti alla situazione in figura 4.4.
La combinazione GK+MC fa routing dei flussi H.225 e H.245 , mentre agli endpoint è stato "detto" di spedire i pacchetti RTCP e RTP a indirizzi che possono raggiungere, e cioè quelli delle interfacce di rete di Urania: qui i pacchetti si fermano, dato che non c'è un'applicazione H.323 che sia pronta a riceverli. La chiamata continua a non funzionare.

Figura 4.4 I pacchetti RTP si fermano sulle interfacce Ethernet della
macchina a cavallo tra le due reti
4.2 Far arrivare i pacchetti a destinazione
I pacchetti si fermano sulle interfacce di rete eth0 e eth1 perché nel loro header UDP è quello l'indirizzo di destinazione specificato. Se vogliamo che vadano da qualche altra parte dobbiamo intervenire su quel campo: dobbiamo fare Destination NAT.
Per fare sì che le due interfacce di rete sull'host Urania si possano "vedere", vale a dire che i pacchetti possano passare dall'una all'altra, dobbiamo abilitare su tale host una funzione detta IP forwarding. Questo viene fatto mediante il comando (inserito nel codice C originale)
$
echo 1 > /proc/sys/net/ipv4/ip_forwardUna volta utilizzato questo accorgimento siamo pronti a modificare i pacchetti a mano a mano che arrivano sulle interfacce.
La tabella di NAT di Netfilter
Riprendiamo lo schema esposto nel capitolo 3 che descriveva l'attraversamento dei pacchetti.

Figura 4.5 Attraversamento dei pacchetti
Se consideriamo anche le "catene" della tabella di NAT, inizialmente vuota, modifichiamo lo schema nel modo seguente:

Figura 4.6 Inserimento delle catene Per e Post Routing della tabella NAT
Esiste la possibilità di intervenire sui pacchetti prima ancora che su di essi venga fatta la decisione di routing, quando attraversano la "catena" detta appunto di PREROUTING, e appena prima che vadano in uscita, nella catena detta POSTROUTING.
Quando abbiamo "ingannato" gli endpoint, li abbiamo costretti a cambiare l'indirizzo cui spedire il flusso RTP/RTCP da quello irraggiungibile dell'endpoint remoto col quale la comunicazione deve essere instaurata a quello raggiungibile di una macchina attestata sulla stessa rete. Per ogni connessione teniamo traccia dell'indirizzo "soppresso", e lo restituiamo ai pacchetti con un'operazione di Destination NAT. Chiariamo il procedimento con un esempio. Il generico pacchetto UDP proveniente da Alice avrà nella propria intestazione:
Figura 4.7 Pacchetto UDP
Come già detto, nella fase in cui il codice C aggiunto a quello originale operava le sostituzioni di indirizzo, abbiamo immagazzinato indirizzo IP e porte UDP del reale destinatario dei pacchetti in strutture dati appositamente create. Nella catena di PREROUTING aggiungiamo delle regole costruite a partire da tali informazioni, che restituiranno ai pacchetti la reale destinazione; i pacchetti così modificati verranno indirizzati alla catena FORWARD, attraverso la quale usciranno sull'interfaccia di rete "giusta", raggiungendo finalmente il destinatario.

Figura 4.8 Destination NAT per i pacchetti da Alice verso Bob
In figura 4.8 è illustrato il risultato della sostituzione per i soli pacchetti che viaggiano da Alice verso Bob; nell'altra direzione la situazione è analoga.
In realtà il processing dei pacchetti non si ferma qui: nella catena di POSTROUTING applicheremo una regola di Source NAT, in modo che il pacchetto uscente sembri provenire da una delle due interfacce Ethernet di Urania. La doppia sostituzione è illustrata in figura 4.9, sempre per una sola direzione.

Figura 4.9 DNAT e SNAT
Riassumendo la situazione:
La chiamata (finalmente) funziona: i due utenti si sentono, e, dato che la procedura descritta viene ripetuta per ogni canale logico, si possono anche vedere, se dispongono del necessario hardware.

Figura 4.10 Chiamata a buon fine
Fino ad ora ci siamo occupati della sola parte relativa all'apertura dei canali logici, intercettando i messaggi H.245, deviando i pacchetti, facendo NAT.
Nella fase di chiusura interverremo in maniera analoga, dato che la chiusura di un canale logico è regolata da uno scambio di messaggi molto simile.

Figura 4.11 Scambio di messaggi per la chiusura di un canale
In questa fase dobbiamo fare meno cose; infatti il messaggio Close Logical Channel indica che il mittente vuole chiudere un canale logico, e basta inoltrarlo così com'è, poiche il destinatario non trae dal messaggio informazioni che ne influenzano il comportamento. La stessa cosa vale per il messaggio di conferma.
La cosa che non dobbiamo dimenticare è di cancellare le regole di NAT aggiunte nelle catene di PRE e POST-ROUTING per il canale logico in questione: come detto in precedenza, si è stabilito di tenere traccia di ogni chiamata mediante strutture logiche create appositamente. Alla fine di una chiamata siamo in grado di risalire a tutti i canali logici aperti per essa, e di cancellare tutte le regole relative nelle catene del Netfilter.
Figura 4.11 Chiusura di un canale logico
Le modifiche fatte al codice permettono a questo punto di portare a buon fine una chiamata a cavallo tra una rete pubblica e una privata: dato che il Media Controller originale supporta fino a 100 chiamate contemporanee, estendiamo la possibilità di più chiamate contemporanee anche al proxy realizzato.
Il codice originale offre la possibilità di ottenere (mediante l'uso delle solite API) un'identificativo numerico per ogni conferenza, uno per ogni "leg" di una conferenza (gamba, come comunemente detto in riferimento a una parte di una chiamata multi-parte), e uno per ogni canale logico.
In figura 4.12 è ilustrata una chiamata vista secondo quest'ottica.

Figura 4.12 Terminologia usata dall' MC per descrivere una conferenza
Per immagazzinare queste ed altre informazioni relative alle conferenze in corso, e per tenere traccia delle modifiche fatte alla tabella di routing della macchina che ospita il proxy, in modo da poterle eliminare una volta finite le conferenze, abbiamo usato delle strutture dati del linguaggio C "personalizzate" per i nostri scopi. Inoltre sono state scritte varie funzioni C, che sono servite per effettuare i controlli sull'appartenenza degli host all'una o all'altra sottorete, per la conversione dei formati degli indirizzi, e per permettere al codice di conoscere l'indirizzo IP associato alle interfacce Ethernet della macchina sulla quale gira.
"Indirizzo"
struct indirizzo { char ip[100];
int port;
bool scritto;
}
La struttura indirizzo serve, come appare chiaro, a immagazzinare uno TSAPIdentifier, costituito da un indirizzo IP e da un numero di porta. Il flag "scritto" è una variabile booleana che ci ricorda se nel corso della chiamata abbiamo scritto qualcosa di significativo nella struttura: senza il controllo esercitato da tale flag il codice potrebbe pescare dai due campi precedenti dei numeri a caso.
"Leg"
struct leg { mciCallHandleType numchiamata;
bool attiva;
}
Una call leg è una delle due parti di una conferenza, considerata come l'unione di una parte che va dal primo endpoint al proxy, e da un'altra parte che va dal proxy al secondo endpoint. Il primo campo della struttura non è altro che un identificativo numerico, espresso però secondo un tipo definito dal fornitore dell'MC.
Il flag "attiva" ha la stessa funzione che aveva "scritto" nel caso precedente, e interviene anche nella chiusura di una conferenza.
Quando entrambe le "leg" di una conferenza sono disattivate (vale a dire quando tutti i canali sono stati chiusi), allora la chiamata può considerarsi chiusa: è allora e solo allora che andiamo a cancellare le regole di NAT dalle catene del Netfilter.
"Conferenza"
struct conferenza {
mciConfHandleType id;
leg leg1;
leg leg2;
indirizzo RTCP_IN;
indirizzo RTCP_OUT;
indirizzo AUDIO_IN;
indirizzo AUDIO_OUT;
indirizzo DATA_IN;
indirizzo DATA_OUT;
indirizzo VIDEO_IN;
indirizzo VIDEO_OUT;
bool attiva;
bool inoltratoRTCP;
bool inoltratoAUDIO;
bool inoltratoVIDEO;
bool inoltratoDATA;
}
Questa struct contiene tutti i campi di interesse relativi a una singola conferenza:
Dato che viene aperto un canale logico per ogni tipo di media possibile in una chiamata H.323, sono stati previsti tutti i casi. Nei messaggi H.245 relativi ai canali logici è specificato anche il tipo di media che deve viaggiare sul canale: questa informazione viene letta dai messaggi e gli indirizzi immagazzinati nei campi corretti
4.4.2 Funzioni C
Riportiamo solo l'intestazione delle funzioni scritte e il loro impiego, rimandando all'appendice A per il codice completo.
Getaddress
unsigned char *getaddress( char *interfaccia)
Rileva l'indirizzo ip assegnato all'interfaccia che prende come argomento e restituisce un puntatore a un unsigned char array
Duealla
long int duealla ( int esponente)
Funzione che riceve in ingresso l'esponente e dà in uscita la potenza di base due di quell'esponente come long int
Bintodec
int bintodec( int array[8])
Prende un numero binario espresso come stringa di 8 caratteri e restituisce un intero
Bintodec32
long int bintodec32( char array[32])
Prende un numero binario espresso come stringa di 32 caratteri e restituisce un intero
Numtostring
char *numtostring( long int n)
Converte gli indirizzi dati nel formato long int in indirizzi espressi in dotted notation, in una stringa. Richiede "duealla"
Dottobininv
char *dottobininv (char *dot)
Converte un indirizzo dotted in una stringa di bit, invertendo i quattro ottetti risultanti. Richiede "duealla"
Dottobin
char *dottobin (char *dot)
Converte un indirizzo dotted in una stringa di bit. Richiede "duealla"
Inttobin32
char *inttobin32 ( long int numero)
Converte un indirizzo in formato long int in un array di 32 bit. Richiede "numtostring" e "dottobininv"
Strrev
char *strrev (char *string)
Inverte i caratteri di una stringa. E' una funzione di libreria, non l'ho scritta io.
Same
bool same( char *indirizzo1, long int indirizzo2, int netmask)
Dice se due indirizzi appartengono alla stessa sottorete: il primo è una stringa, il secondo un long int. Il terzo è la maschera di sottorete espressa come numero di bit. Richiede "dottobininv", "inttobin32", "strrev"
4.5 Funziona! Esempi in vari scenari di test
Il software realizzato è stato testato in varie situazioni, nelle quali sono stati usati, di volta in volta, endpoint H.323 di diversi fornitori, e operanti su sistemi operativi differenti. Diamo un elenco degli strumenti usati:
|
Endpoint |
Sistema operativo |
Tipo di endpoint |
Stack H.323 usato |
|
Ohphone |
Linux |
Applicazione software |
Open-source |
|
Openphone |
Windows |
Applicazione software |
Open-source |
|
Netmeeting |
Windows |
Applicazione software |
Proprietario |
|
Innovaphone Tiptel |
Proprietario |
Telefono IP |
Proprietario |
|
GnomeMeeting |
Linux |
Applicazione software |
Open-source |
Il gatekeeper e il Media Controller a partire dai quali è stato realizzato il software sono prodotti da Radvision, un vendor che ha implementato uno stack H.323 proprietario, e le versioni di Linux sulle quali "gira" il software sono quelle che montano un Kernel 2.4.7-10 (quale può essere trovato nella distribuzione Red Hat 7.1.
I risultati ottenuti sono confortanti, ma anticipiamo subito che in alcuni casi le chiamate non sono andate a buon fine. Sono stati allora ripetuti i test attestando i due endpoint sulla stessa rete e provando a effettuare delle chiamate con o senza gatekeeper (senza usare l'MC). Quello che si è trovato è che alcuni endpoint avevano dei problemi di interoperabilità anche nel caso di chiamate dirette, dovuti a implementazioni dello stack H.323 non compatibili (vedi Capitolo 1, sezione pro e contro), mentre in un numero piccolo, ma pur sempre presente, di casi, i problemi sono da ricercarsi nel codice scritto.
Per ciascun test sono state effettuate 4 prove: ogni terminale è stato attestato, di volta in volta, sulla rete privata o su quella pubblica, ed ha chiamato o è stato chiamato dall'altro terminale.
Test 1: Ohphone (Linux)ß -à Tiptel (Innovaphone)

|
Chiamante |
Chiamato |
Esito |
|
Ohphone |
Tiptel |
Il chiamante non riceve l'audio |
|
Tiptel |
Ohphone |
Tutto OK! |
Test 2 : Ohphone (Linux) ß -à Netmeeting (Windows)

|
Chiamante |
Chiamato |
Esito |
|
Ohphone |
Netmeeting |
Chiamata non a buon fine |
|
Netmeeting |
Ohphone |
Chiamata non a buon fine |
Test 3 : Tiptel (Innovaphone) ß -à Tiptel (Innovaphone)

|
Chiamante |
Chiamato |
Esito |
|
Tiptel |
Tiptel |
Tutto OK! |
|
Tiptel |
Tiptel |
Tutto OK! |
Test 4 : Tiptel (Innovaphone) ß -à Netmeeting (Windows)

|
Chiamante |
Chiamato |
Esito |
|
Tiptel |
Netmeeting |
Tutto OK! |
|
Netmeeting |
Tiptel |
Tutto OK! |
Test 5 : Netmeeting (Windows) ß -à Netmeeting (Windows)

|
Chiamante |
Chiamato |
Esito |
|
Netmeeting |
Netmeeting |
Tutto OK! |
|
Netmeeting |
Netmeeting |
Tutto OK! |
Test 6: Ohphone (Linux)ß -à Ohphone (Linux)

|
Chiamante |
Chiamato |
Esito |
|
Ohphone |
Ohphone |
Tutto OK! |
|
Ohphone |
Ohphone |
Tutto OK! |