Modulo Neighborhood - Dettagli Tecnici

Requisiti

L'utilizzatore del modulo Neighborhood per prima cosa inizializza il modulo richiamando il metodo statico init di NeighborhoodManager. In tale metodo viene anche passata l'istanza di ITasklet per fornire l'implementazione del sistema di tasklet.

Dopo istanzia il suo NeighborhoodManager passando al costruttore:

Dopo, per ogni interfaccia di rete che intende gestire, richiama sul NeighborhoodManager il metodo start_monitor(nic), dove nic è una INeighborhoodNetworkInterface.

Deliverables

Il modulo segnala l'avvenuta assegnazione dell'indirizzo di scheda ad una interfaccia di rete gestita attraverso il segnale nic_address_set di NeighborhoodManager.

In tale segnale viene riportato:


Il modulo segnala la costituzione di un arco attraverso il segnale arc_added di NeighborhoodManager.

In tale segnale viene riportato:

Il segnale significa anche che è stata aggiunta nelle tabelle (nel network namespace default) la rotta verso quell'indirizzo di scheda che è riportato nell'istanza di INeighborhoodArc.


Il modulo segnala la rimozione di un arco attraverso il segnale arc_removed di NeighborhoodManager.

In tale segnale viene riportato:

Il segnale significa anche che è stata rimossa dalle tabelle (nel network namespace default) la rotta verso quell'indirizzo di scheda che è riportato nell'istanza di INeighborhoodArc.


Il modulo segnala la variazione del costo di un arco attraverso il segnale arc_changed di NeighborhoodManager.

In tale segnale viene riportato:


Il modulo segnala l'avvenuta rimozione dell'indirizzo di scheda ad una interfaccia di rete che non si gestisce più attraverso il segnale nic_address_unset di NeighborhoodManager.

In tale segnale viene riportato:


Il modulo permette di ottenere l'elenco degli archi ora presenti con il metodo current_arcs di NeighborhoodManager.


Il modulo fornisce i metodi get_dispatcher e get_dispatcher_set di NeighborhoodManager per permettere al nodo di gestire i messaggi unicast e broadcast ricevuti.


Il modulo fornisce i metodi get_identity e get_node_arc di NeighborhoodManager per permettere al nodo di identificare il vicino (identità o nodo) che ha inviato un messaggio.


Il modulo permette di ottenere uno stub per inviare un messaggio reliable ad un vicino tramite un arco (o un arco-identità) con il metodo get_stub_whole_node_unicast (o get_stub_identity_aware_unicast) di NeighborhoodManager.


Il modulo permette di ottenere uno stub per inviare un messaggio in broadcast con i metodi get_stub_whole_node_broadcast (e get_stub_identity_aware_broadcast) di NeighborhoodManager.


Il modulo permette di forzare la rimozione di un arco con il metodo remove_my_arc di NeighborhoodManager.

Rilevamento dei vicini, costituzione degli archi, misurazione dei costi

Quando viene istanziato il NeighborhoodManager il modulo genera il suo NeighborhoodNodeID.

Quando viene chiamato il metodo start_monitor sulla istanza di NeighborhoodManager, il modulo inizia a gestire una interfaccia di rete. Di essa riceve il nome e il MAC address. Per prima cosa verifica che questi valori siano univoci. Cicla le interfacce che ha già in gestione; se una ha gli stessi valori per nome e MAC allora ignora questa richiesta che è un duplicato; se una ha lo stesso MAC e nome diverso oppure lo stesso nome e MAC diverso allora il modulo va in errore fatale.

Il modulo, per ogni interfaccia che inizia a gestire, memorizza il suo nome e il suo MAC address e genera e memorizza un indirizzo locale di scheda. Usa l'oggetto INeighborhoodIPRouteManager per impostare l'indirizzo generato. Poi emette il segnale nic_address_set.

Poi avvia una tasklet nella quale invia un broadcast_to_dev (cioè un broadcast solo su quella interfaccia di rete) con il messaggio "ci sono io" indicando il suo NeighborhoodNodeID, il MAC della interfaccia e il suo indirizzo locale di scheda. Poi ripete lo stesso messaggio con bassa frequenza, ogni minuto.

I nodi che sono nel dominio broadcast vedono questo messaggio e possono fin da subito accordarsi per un arco. Quelli che hanno già costituito un arco verso quel MAC address ignorano il nuovo messaggio.

Un nodo che vuole accordarsi per un arco con un vicino di cui ha notato la presenza gli invia un messaggio "facciamo un arco" in UDP unicast, indicando anche esso il suo NeighborhoodNodeID e il MAC e l'indirizzo locale della interfaccia di rete, e riceverà la risposta. Dopo entrambi i nodi avranno un arco il cui costo è ancora non misurato.

Dopo essersi accordati per l'arco entrambi i nodi usano l'oggetto INeighborhoodIPRouteManager per impostare la rotta verso l'indirizzo di scheda del nuovo vicino. Quindi da subito è possibile realizzare connessioni reliable (con protocollo TCP) tra i due nodi passanti per questo nuovo arco.

Un arco il cui costo non è ancora stato misurato non va a far parte della lista ufficiale che il modulo Neighborhood espone all'applicazione.

Dopo aver realizzato l'arco entrambi i nodi avviano una tasklet che si occuperà della monitorazione dell'arco e della misurazione del costo ad esso associato. In questa tasklet viene realizzata subito una prima misurazione e in seguito ogni 30 secondi si ripete. Nell'effettuare la misurazione si verifica anche il funzionamento stesso dell'arco e se non funziona viene rimosso.

La misurazione del costo espresso come RTT avviene attraverso l'uso dell'oggetto INeighborhoodNetworkInterface, come indicato nel relativo documento.

Dopo che è stata fatta la prima misurazione, il modulo emette il segnale arc_added e l'arco va a far parte della lista ufficiale.

Per quanto riguarda la memorizzazione del costo, dopo aver rilevato la prima misurazione l'algoritmo è il seguente:

. costo_nuovo = <costo appena rilevato>
. costo_memorizzato = costo_nuovo
. costo_ufficiale = costo_memorizzato

Dopo ogni successiva misurazione l'algoritmo è il seguente:

. costo_nuovo = <costo appena rilevato>
. costo_delta = costo_nuovo - costo_memorizzato
. se (costo_delta > 0) costo_delta = costo_delta / 10
. se (costo_delta < 0) costo_delta = costo_delta / 3
. costo_memorizzato = costo_memorizzato + costo_delta
. se (costo_memorizzato < costo_ufficiale*0.5 OR costo_memorizzato > costo_ufficiale*2)
    . costo_ufficiale = costo_memorizzato

Questo algoritmo fa in modo che lievi variazioni non scatenino pesanti aggiornamenti e traffico di rete. Inoltre tiene conto della seguente osservazione: se ci sono misurazioni ravvicinate della latenza che differiscono quella più bassa è quella più vicina alla latenza puramente dovuta alla distanza dei due nodi, mentre quella più alta è stata probabilmente maggiormente influenzata dal carico del nodo o dal bufferbloat. Nonostante questa osservazione l'algoritmo pian piano si adegua se le misurazioni si mantengono su un valore elevato.

Produzione di uno stub per inviare un messaggio in broadcast

Il modulo Neighborhood riceve dal suo utilizzatore (nel costruttore di NeighborhoodManager) un'istanza di un oggetto di cui conosce l'interfaccia INeighborhoodStubFactory. Questa ha il metodo get_broadcast che restituisce un oggetto stub di tipo radice (un IAddressManagerStub) che effettua chiamate broadcast.

Quando vuole trasmettere un messaggio in broadcast il modulo chiama sempre subito prima questo metodo per ottenere uno stub nuovo. In questo metodo può indicare una istanza dell'interfaccia IAckCommunicator per ricevere dopo il timeout la lista dei MAC address che hanno segnalato con un ACK la ricezione del messaggio.

Il metodo riceve questi parametri:

Un IBroadcastID può avere diverse forme per identificare quali nodi diretti vicini (o quali loro identità) siano i destinatari del messaggio. Individuiamo queste possibili classi che implementano IBroadcastID:

Quando il modulo neighborhood (NeighborhoodManager) vuole inviare un messaggio in broadcast:

Se si è passata comm, l'istanza di IAckCommunicator opzionale, allora la chiamata del metodo remoto deve seguire immediatamente la creazione dello stub, perché la prima chiamata a current_arcs_for_broadcast viene eseguita subito e la seconda viene eseguita dopo il timeout che parte al momento della chiamata del metodo remoto.


Il modulo può aver bisogno internamente di comunicare con i suoi vicini e per questo di produrre uno stub. Oppure lo stub gli può essere richiesto dall'esterno. In entrambi i casi questo algoritmo sopra delineato viene usato. La differenza sta nella costruzione della istanza di IBroadcastID.

Uno stub è necessario al modulo Neighborhood quando deve trasmettere un messaggio in broadcast per rilevare nuovi archi o nodi. In questa occasione quello che serve è un EveryWholeNodeBroadcastID. E non serve un INeighborhoodMissingArcHandler.

Quando uno stub è necessario ad un modulo di nodo (che non sia il Neighborhood) quel modulo (o comunque l'utilizzatore del modulo Neighborhood) deve indicare quali nodi vanno raggiunti. Deve cioè fornire un set di INeighborhoodArc da cui il modulo Neighborhood estrapola un set di NeighborhoodNodeID con cui compone un WholeNodeBroadcastID. Può inoltre fornire un INeighborhoodMissingArcHandler. Il metodo pubblico di NeighborhoodManager a questo scopo è get_stub_whole_node_broadcast.

Quando uno stub è necessario ad un modulo di identità quel modulo (o comunque l'utilizzatore del modulo Neighborhood) deve indicare quali identità vanno raggiunte. Deve cioè fornire un set di NodeID con cui il modulo Neighborhood compone un IdentityAwareBroadcastID. Può inoltre fornire un INeighborhoodMissingArcHandler. Il metodo pubblico di NeighborhoodManager a questo scopo è get_stub_identity_aware_broadcast.

Produzione di uno stub per inviare un messaggio UDP in unicast

L'interfaccia INeighborhoodStubFactory permette permette con il metodo get_unnicast al modulo di ottenere uno stub per inviare un messaggio UDP (non reliable) ad un particolare vicino tramite una sua particolare interfaccia di rete, dati questi parametri:

Un IUnicastID prodotto per un messaggio da trasmettere in UDP ha solo una forma:

Il modulo Neighborhood può aver bisogno internamente di comunicare con un suo vicino prima di aver realizzato un arco; in questo caso produce questo tipo di stub con questo tipo di IUnicastID. Come istanza di ISourceID viene usata una istanza della classe WholeNodeSourceID.

In seguito, quando vi è un arco tra due nodi vicini, il modulo userà solo comunicazioni con protocollo reliable per inviare messaggi ad uno specifico vicino. Questa modalità non reliable non viene fornita all'esterno dal modulo.

Produzione di uno stub per inviare un messaggio reliable ad un vicino tramite un arco

L'interfaccia INeighborhoodStubFactory permette con il metodo get_tcp al modulo anche di ottenere uno stub per inviare un messaggio reliable ad un particolare vicino tramite un particolare arco, dati questi parametri:

Un IUnicastID prodotto per un messaggio da trasmettere in TCP può avere diverse forme per identificare quale nodo diretto vicino (o quale sua identità) sia il destinatario del messaggio. Individuiamo queste possibili classi che implementano IUnicastID:

Chiamando un metodo su questo stub viene inviato un messaggio con il protocollo TCP, quindi reliable. Il metodo attende che il messaggio raggiunga il vicino, ma al momento della produzione dello stub si può specificare se si intende attendere l'esecuzione del metodo nel nodo vicino oppure no.


Il modulo può aver bisogno internamente di comunicare con un suo vicino passando per un arco; in questo caso produce questo tipo di stub. Oppure lo stub gli può essere richiesto dall'esterno.

Indirizzo IPv4 di scheda

La comunicazione tra due nodi collegati da un arco deve poter avvenire in modo reliable. Infatti se la comunicazione risulta impossibile si deve procedere alla rimozione dell'arco stesso. Per implementare la comunicazione reliable si può far uso del protocollo TCP, ma per questo occorre poter associare ad ogni vertice dell'arco un indirizzo IP fisso.

Si assegna subito un indirizzo "locale" distinto ad ogni scheda di rete del nodo e lo si mantiene sempre, anche quando il nodo fa l'ingresso in una nuova rete, o migra in un diverso g-nodo, ecc.

L'unico requisito da soddisfare è che tale indirizzo IPv4 sia univoco tra tutti i vicini. Lo scegliamo in modo random nella classe di indirizzi 169.254.0.0/16.

Il caso in cui due nodi vicini si scelgano lo stesso indirizzo è ignorabile.

Quando si aggiunge una interfaccia di rete da monitorare, il modulo genera un nuovo indirizzo IPv4 locale e lo aggiunge ai suoi indirizzi su questa interfaccia di rete. Per farlo usa l'oggetto passatogli che implementa l'interfaccia INeighborhoodIPRouteManager, precisamente con il metodo 'add_address'.

Quando si smette di gestire l'interfaccia, il modulo rimuove anche l'indirizzo locale dalla interfaccia, con il metodo 'remove_address'.

Quando un nodo gestisce una interfaccia di rete, nel messaggio broadcast_to_nic "ci sono io" comunica il suo indirizzo scelto per quella scheda.

Quando viene realizzato un arco ciascuno dei due nodi:

Alla rimozione di un arco il nodo:

Quando si vuole inviare un messaggio ad un vicino attraverso un determinato arco si può usare il TCP verso l'indirizzo di scheda del vicino memorizzato su questo arco, quindi si ha un collegamento reliable.

Proof of concept

Come proof of concept è stato realizzato un programma, neighborhoodclient, che si avvale del modulo Neighborhood per:

Si tratta di un programma specifico per un sistema Linux.

Durante l'esecuzione del programma, l'utente può verificare che le rotte verso i diretti vicini vengano correttamente e tempestivamente realizzate e rimosse (ad esempio se un nodo muore o diventa irragiungibile tramite l'arco).

Il programma consente anche di verificare il modus operandi che è stato individuato (e descritto nel documento di analisi) per realizzare:

Questo programma crea e rimuove anche i network namespace e le pseudo-interfacce da assegnare alle identità che il nodo assume, distinte dalla principale.

Il programma fornisce anche una classe che prevede dei metodi remoti come modulo di identità e una che prevede dei metodi remoti come modulo di nodo. Tali classi usano il modulo Neighborhood per gestire le comunicazioni lato client e lato server, L'utente può verificare che un metodo remoto può essere chiamato da un nodo su un suo diretto vicino. Nel caso del modulo di identità può verificare che la giusta identità reagisce sul nodo vicino, ma solo se il corrispettivo arco-identità è presente.

Inoltre il programma interattivamente consente di chiedere al modulo Neighborhood le informazioni che ha raccolto e mostrarle all'utente.

Di seguito descriviamo le operazioni svolte da questo programma. Sarà interessante anche perché alcuni aspetti analizzati qui saranno ripresi nella realizzazione del demone ntkd completo.

Interazione del programma con l'utente

Il programma neighborhoodclient prevede che l'utente immetta, come argomenti della riga di comando e in modo interattivo dalla console durante la sua esecuzione, tutti i requisiti del modulo. Anche i parametri che normalmente sarebbero individuati in modo autonomo dal demone ntkd (per esempio l'identificativo delle identità) nel caso del neighborhoodclient sono espressamente indicati dall'utente, questo per rendere più facilmente riproducibili gli ambienti di test.

I parametri passati all'avvio del programma neighborhoodclient sono:

I comandi che l'utente può dare interattivamente sulla console del programma neighborhoodclient sono:

Il significato di comandi e parametri sarà chiarito in seguito.

Creazione della identità principale

La identità principale del nodo viene creata automaticamente dal demone ntkd all'inizio della sua attività. Nel caso del programma neighborhoodclient, l'utente specifica sulla linea di comando l'identificativo (un intero) da assegnare a tale prima identità del nodo.

Codice id-arco

Quando il modulo Neighborhood segnala al programma neighborhoodclient che ha realizzato un arco, questo programma gli assegna un numero identificativo che chiamiamo id-arco e lo mostra sulla console all'utente. Questo numero serve solo all'utente per indicare nei suoi comandi quel particolare arco. Non ha un suo corrispettivo in nessun concetto applicabile al demone ntkd.

Archi identità

Quando un nodo rileva un diretto vicino tramite una sua interfaccia e forma con esso un arco, su quell'arco non appoggia immediatamente nessun arco-identità. Su quell'arco possono da subito passare delle comunicazioni del modulo di nodo.

È l'utilizzatore del modulo Neighborhood, in questo caso il programma neighborhoodclient su istruzione dell'utente, a decidere quali archi-identità formare.

Ad esempio, supponiamo che il programma neighborhoodclient è stato avviato sul nodo a indicando come identificativo della sua identità principale il numero 123. Sul nodo b è stato avviato indicando come identificativo della sua identità principale il numero 456. Dopo un po' il programma neighborhoodclient segnala sulla console del nodo a di aver realizzato un arco id-arco=1 con il nodo b (sulla console compariranno i MAC address delle interfacce di rete end-point dell'arco e l'utente saprà riconoscere il nodo b).

A questo punto l'utente in via interattiva sulla console del programma in esecuzione sul nodo a chiederà di formare sull'arco id-arco=1 un arco-identità tra 123 e 456. Cosa analoga sulla console del nodo b che avrà anch'esso indicato la formazione dell'arco.

A questo punto su questo arco-identità potranno passare anche delle comunicazioni del modulo di identità.

Creazione di nuove identità, rimozione di vecchie identità

Vi sono alcuni eventi che nel demone ntkd potranno portare alla creazione di una nuova identità: migrazione, costituzione di un percorso fisso per sfruttare i percorsi disgiunti, ... In questo proof of concept ci soffermeremo solo sul caso della migrazione, simulando cioè le operazioni che il demone ntkd dovrà fare in questi scenari.

A fronte di una migrazione, una nuova identità del nodo, chiamiamola ak, viene creata partendo da una delle sue identità, chiamiamola aj, la quale può essere la principale o anche no. Tutti gli archi-identità che partivano dalla ak vengono duplicati automaticamente sulla aj.

Inoltre, un arco-identità che collegava ak ad una identità bi del nodo diretto vicino b, quando viene duplicato sulla aj la collegherà a bi solo se bi non ha partecipato alla stessa migrazione. Se invece bi ha partecipato alla stessa migrazione, allora il nuovo arco-identità collegherà aj a una nuova identità bh che è stata creata sul nodo b partendo dalla bi.

In seguito alcuni archi-identità verranno rimossi dalla ak.

Nel programma neighborhoodclient questo evento viene simulato quando l'utente ne fa richiesta sulla console. Vediamo in che modo l'utente che interagisce con il programma neighborhoodclient può dare tutte le indicazioni per simulare questo scenario.

Supponiamo di avere i nodi a, b, c, d, e sui quali è in esecuzione il programma neighborhoodclient. Questi nodi sono disposti secondo il disegno:

grafo1.adraw

Su ogni nodo viene avviato il programma neighborhoodclient, specificando sulla linea di comando l'identificativo numerico da assegnare alla prima identità principale. Indichiamo queste identità (e il relativo numero identificativo) con a0, b0, c0, d0, e0.

Dopo un certo tempo, il modulo Neigorhood avrà formato degli archi in base alla topologia rappresentata nel disegno sopra. Questi li indichiamo con a-b, b-c, a-c, b-e, a-d.

Poi l'utente interagisce dalla console con il programma neighborhoodclient in ogni nodo e lo istruisce riguardo la creazione degli archi-identità:

grafo2.adraw

Adesso le identità a0 e b0 migrano dando luogo alle nuove identità a1 e b1 che diventano le identità principali dei nodi a e b.

Per simulare questo evento, l'utente effettua queste operazioni:

grafo3.adraw

In seguito viene rimosso l' arco-identità b0-e0. Il motivo di questa rimozione è spiegato nel documento del modulo QSPN, riguarda i cluster di nodi e le identità di connettività.

Per simulare questo evento, l'utente effettua queste operazioni:

Nel disegno evidenziamo i cluster. Risulta più chiaro che la migrazione delle identità principali di a e b da un cluster all'altro ha reso necessaria la presenza delle identità di connettività di a e b nel primo cluster per mantenerlo internamente connesso. Risulta altresì chiaro perché è stato rimosso un arco-identità: perché una identità di connettività mantiene collegamenti solo all'interno del cluster che essa supporta. Evidenziamo anche le identità di connettività con un asterisco.

grafo4.adraw

Infine l'identità b0 si accorge di non essere necessaria alla connettività interna del primo cluster, quindi si auto-distrugge.

Per simulare questo evento, l'utente effettua queste operazioni:

grafo5.adraw