6884
Comment:
|
39583
|
Deletions are marked like this. | Additions are marked like this. |
Line 2: | Line 2: |
Rileva i nodi vicini raggiungibili attraverso una (o più) interfacce di rete e ne reperisce l'identificativo. Con ogni vicino, poi, si accorda per la creazione (o meno) di un arco. L'accordo potrebbe non essere raggiunto perché uno dei due vertici ha già un numero di archi elevato, tutti con costo inferiore a questo. Oppure, più frequentemente, se i due vertici appartengono a reti distinte. Per ogni arco mantiene l'identificativo del vertice collegato e il costo. Nel tempo, gestisce la costituzione di nuovi archi, la rimozione di archi, i cambiamenti del costo degli archi. === Note === Tra due nodi vicini ci possono essere 2 o più archi, ma solo se sono su diversi domini di collisione. Da questo deriva che i messaggi inviati in UDP Unicast vanno elaborati dal solo nodo destinatario e solo una volta, cioè quando sono ricevuti dall'arco che li invia. Per questo il nodo che li invia su un certo arco compone l'oggetto UnicastID con l'identificativo del nodo destinatario e il MAC del nodo destinatario associato a quell'arco. ---- Quando si crea un arco esso esiste per entrambi i nodi. Quindi ogni nodo può inoltrare i suoi pacchetti all'altro e deve inoltrare i pacchetti che provengono dall'altro. ---- Il costo di un arco in una direzione ( da A verso B ) può essere diverso dal costo nella direzione inversa. Questo non è utile quando il costo rappresenta il round-trip time, ma può esserlo se rappresenta la larghezza di banda in uscita. ---- Quando un nodo rimuove un arco tenta di comunicarlo al vertice collegato perché faccia altrettanto. |
<<TableOfContents(4)>> == Ruolo del modulo == Il ruolo fondamentale del modulo Neighborhood è il rilevamento dei collegamenti (detti ''archi'') che si possono realizzare con altri nodi diretti vicini, la loro realizzazione e la misurazione del costo associato a tali archi. Per l'esattezza, il nodo diretto vicino che viene rilevato è il processo ''ntkd'' che è in esecuzione su tale nodo. Infatti tale processo all'avvio si crea un identificativo univoco, che chiamiamo NeigborhoodNodeID e lo trasmette periodicamente sui domini broadcast a cui il nodo è collegato con le sue interfacce di rete. Ogni nodo crea un solo NeigborhoodNodeID e lo mantiene invariato per tutta la sua vita. Per distinguere un vicino da un altro, il modulo guarda il NeigborhoodNodeID. Inoltre, il modulo distingue anche le singole interfacce di rete che riesce a rilevare di ogni suo vicino. Cioè guarda alla coppia NeigborhoodNodeID + MAC address. Come diretta conseguenza del ruolo di realizzazione e mantenimento degli archi, al modulo Neighborhood è demandato anche il compito di permettere agli altri moduli la comunicazione tra nodi: * Lato client. Il modulo fornisce metodi per produrre gli stub che gli altri moduli dell'applicazione usano per comunicare con i nodi (diretti vicini o con indirizzo IP). * Lato server. Il modulo viene interrogato quando si ascolta una richiesta per sapere se passarla a uno (o piu d'uno) skeleton nel nodo corrente, il quale potrà richiamare metodi anche di altri moduli. == Identità multiple in un nodo == Introduciamo il concetto di ''identità''. In un singolo nodo possono in dati momenti sussistere diverse ''identità''. La ragione di essere di queste identità sarà discussa in dettaglio nella trattazione del modulo QSPN, quando si parla di nodi virtuali. . Per una comprensione delle motivazioni di queste ''identità'' e del loro rapporto con gli ''indirizzi di scheda'' si veda il documento [[Netsukuku/ita/docs/ModuloQSPN/Esempio1/Step1|"Modulo QSPN - Esempio di uso degli indirizzi virtuali"]], in particolare la premessa. Ogni ''identità'' di un nodo ha un suo identificativo. Questo identificativo è distinto dal NeigborhoodNodeID, il quale è un concetto interno al modulo Neighborhood. L'identificativo assegnato ad una ''identità'' di un nodo lo chiamiamo semplicemente NodeID. Il NodeID assegnato a ogni ''identità'' è essenzialmente un intero di 32 bit scelto a caso, assumendo che sia univoco a livello dei domini di collisione in cui il nodo partecipa con le sue interfacce di rete. Questo dettaglio implementativo non è di pertinenza del modulo Neighborhood. Il modulo sa solo che il NodeID può essere usato come identificativo univoco che si riferisce ad una precisa ''identità'' all'interno di un preciso nodo. Il modulo Neighborhood non ha conoscenza diretta di quali siano le ''identità'' che vivono nel suo nodo in un dato momento. Sappiamo solo che ogni nodo in ogni momento ha una e una sola ''identità principale''. Parleremo in seguito delle caratteristiche peculiari di questa particolare identità, ma non sono informazioni di pertinenza del modulo Neighborhood. Abbiamo visto che la presenza di identità multiple in un nodo è supportata dal framework ZCD. Con questo intendiamo evidenziare che è possibile realizzare uno stub che possa essere usato da una precisa identità del nodo ''a'', indichiamola con ''a~-,,0,,-~'', per chiamare un metodo remoto su una precisa identità del nodo ''b'', indichiamola con ''b~-,,0,,-~''. Abbiamo detto inoltre che fra i ruoli del modulo Neighborhood c'è quello di permettere agli altri moduli dell'applicazione la comunicazione con altri nodi. Ora aggiungiamo che un particolare modulo può essere interessato a questo tipo di identificazione precisa della identità all'interno di un nodo. Un altro modulo, invece, potrebbe essere agnostico rispetto a queste identità, e voler semplicemente chiamare un metodo remoto su un particolare nodo. Il modulo Neighborhood, lato client, deve saper produrre uno stub per ogni esigenza. Inoltre, lato server, deve saper individuare un elenco (con zero, uno o più elementi) di root-dispatcher a partire da un messaggio ricevuto (cioè da un UnicastID o un BroadcastID). Nei casi in cui l'identità interna al singolo nodo è rilevante, il NodeID che identifica una ''identità'' di un nodo è una parte essenziale delle classi che si usano come SourceID, UnicastID e BroadcastID nella produzione di stub per chiamare metodi remoti. Facciamo un esempio di un modulo (ad esempio QSPN) in cui una precisa identità del nodo ''a'', indichiamola con ''a~-,,0,,-~'', vuole chiamare un metodo remoto su una precisa identità del nodo diretto vicino ''b'', indichiamola con ''b~-,,0,,-~''. Il nodo ''a'' chiama un metodo di Neighborhood per produrre uno stub di tipo ''identity_aware_unicast''. In questo metodo il nodo passa il NodeID di ''a~-,,0,,-~'' e questo sarà usato dal modulo Neighborhood per produrre un IdentityAwareSourceID. Inoltre in questo metodo il nodo passa il NodeID di ''b~-,,0,,-~'' e questo sarà usato dal modulo Neighborhood per produrre un IdentityAwareUnicastID. Per il lato server vedremo sotto come il nodo ''b'' aveva istruito il suo modulo Neighborhood perché potesse gestire questo tipo di UnicastID. Facciamo un altro esempio di un modulo in cui una precisa identità del nodo ''a'', indichiamola con ''a~-,,0,,-~'', vuole chiamare un metodo remoto su un set di precise identità dei nodi diretti vicini, diciamo ''b~-,,0,,-~'', ''b~-,,1,,-~'', ''c~-,,0,,-~'' e ''d~-,,0,,-~''. Il nodo ''a'' chiama un metodo di Neighborhood per produrre uno stub di tipo ''identity_aware_broadcast''. In questo metodo il nodo passa il NodeID di ''a~-,,0,,-~'' e questo sarà usato dal modulo Neighborhood per produrre un IdentityAwareSourceID. Inoltre in questo metodo il nodo passa i vari NodeID di ''b~-,,0,,-~'', ''b~-,,1,,-~'', ''c~-,,0,,-~'' e ''d~-,,0,,-~'' e questi saranno usati dal modulo Neighborhood per produrre un IdentityAwareBroadcastID. Per il lato server vedremo sotto come i nodi, ad esempio ''b'', ''c'', ''d'' ed anche ''e'', avevano istruito ognuno il suo modulo Neighborhood perché potesse gestire questo tipo di BroadcastID. In altri casi l'identità interna al singolo nodo non è rilevante. In questi casi è il NeigborhoodNodeID che costituisce la parte essenziale delle classi che si usano come SourceID, UnicastID e BroadcastID nella produzione di stub per chiamare metodi remoti. Facciamo un esempio di un modulo in cui un nodo ''a'' vuole chiamare un metodo remoto su un nodo diretto vicino ''b''. Il nodo ''a'' chiama un metodo di Neighborhood per produrre uno stub di tipo ''whole_node_unicast''. Il modulo Neighborhood usa il suo NeigborhoodNodeID per produrre un WholeNodeSourceID. Inoltre in questo metodo il nodo passa il INeighborhoodArc di ''b'' (vedremo sotto come questo oggetto sia fornito dal modulo Neighborhood al suo esterno, mentre il NeigborhoodNodeID è interno al modulo); il relativo NeighborhoodNodeID sarà usato dal modulo Neighborhood per produrre un WholeNodeUnicastID. Per il lato server vedremo sotto come il nodo ''b'' aveva istruito il suo modulo Neighborhood perché potesse gestire questo tipo di UnicastID. Anche il WholeNodeSourceID ricevuto dal nodo ''b'' sarà sufficiente perché il modulo Neighborhood sappia associarlo (anche su richiesta di un altro modulo) ad un particolare INeighborhoodArc. Facciamo un altro esempio di un modulo in cui un nodo ''a'' vuole chiamare un metodo remoto su un set di nodi diretti vicini, diciamo ''b'', ''c'' e ''d''. Il nodo ''a'' chiama un metodo di Neighborhood per produrre uno stub di tipo ''whole_node_broadcast''. Il modulo Neighborhood usa il suo NeigborhoodNodeID per produrre un WholeNodeSourceID. Inoltre in questo metodo il nodo passa i vari INeighborhoodArc di ''b'', ''c'' e ''d''; i relativi NeighborhoodNodeID saranno usati dal modulo Neighborhood per produrre un WholeNodeBroadcastID. Per il lato server vedremo sotto come i nodi, ad esempio ''b'', ''c'', ''d'' ed anche ''e'', avevano istruito ognuno il suo modulo Neighborhood perché potesse gestire questo tipo di BroadcastID. Ovviamente anche lo stesso modulo Neighborhood, il quale nelle sue comunicazioni con i diretti vicini non è interessato a distinguere le ''identità'', può usare in completa autonomia le modalità sopra esposte per effettuare chiamate Unicast e Broadcast. . '''__TODO__: serve questo?''' Alcuni moduli (ad esempio !PeerServices) prevedono la possibilità di chiamare un metodo remoto su un nodo che non è un diretto vicino ma si può raggiungere con un !TcpClient ad un certo indirizzo IP. In questi casi il destinatario del messaggio è sempre uno solo. Anche in questi casi un modulo potrebbe essere consapevole della presenza di più ''identità'' in un nodo e voler individuare una particolare identità sulla quale chiamare il metodo remoto. In questi casi il nodo chiamante deve essere a conoscenza non solo dell'indirizzo IP a cui raggiungere il nodo, ma anche del NodeID della identità su cui operare, oppure l'identità su cui si deve operare è la ''identità principale'' del nodo. All'inizio dovrò dire al Neighborhood: * se ricevi un UnicastID di tipo IdentityAwareUnicastID: * usa questa callback . IAddressManagerSkeleton? xxxxxx(NodeID id) . la quale restituirà <null> oppure un root-dispatcher che potrà accedere solo quei moduli che gestiscono le identità, e in particolare la istanza indicata nell'UnicastID. * se ricevi un BroadcastID di tipo IdentityAwareBroadcastID: * usa questa callback . Gee.List<IAddressManagerSkeleton> xxxxxx(Gee.List<NodeID> id) . la quale restituirà una lista (che può essere vuota) di root-dispatcher ognuno dei quali potrà accedere solo quei moduli che gestiscono le identità, e in particolare una precisa istanza. * se ricevi un UnicastID di tipo WholeNodeUnicastID e ti riconosci come destinatario: * usa questa istanza . IAddressManagerSkeleton xxxxxx . la quale è un root-dispatcher che potrà accedere solo quei moduli che non gestiscono le identità. * se ricevi un BroadcastID di tipo WholeNodeBroadcastID e ti riconosci come destinatario: * usa questa istanza . IAddressManagerSkeleton xxxxxx . la quale è un root-dispatcher che potrà accedere solo quei moduli che non gestiscono le identità. == Operazioni del modulo == Il modulo fa uso delle [[Netsukuku/ita/docs/Librerie/TaskletSystem|tasklet]], un sistema di multithreading cooperativo. Attraverso di esso esegue il monitoraggio delle schede di rete lasciando libero il chiamante di svolgere altri task. Il modulo fa uso del framework [[Netsukuku/ita/docs/Librerie/ZCD|ZCD]], precisamente appoggiandosi ad una libreria intermedia prodotta con questo framework per formalizzare i metodi remoti usati nel demone ''ntkd''. Quando si inizializza, il modulo produce l'identificativo NeigborhoodNodeID del proprio nodo. Il modulo viene subito inizializzato con le callback da chiamare per gestire le chiamate ''!IdentityAware'' e lo skeleton da usare per le chiamate ''!WholeNode''. Di modo che è pronto a gestire le chiamate che il nodo potrà ricevere. Il modulo riceve subito l'elenco delle interfacce di rete che deve gestire. Ad ognuna associa un indirizzo locale detto ''indirizzo di scheda''. Rileva i nodi vicini raggiungibili attraverso una (o più) interfacce di rete e ne reperisce l'identificativo NeigborhoodNodeID + MAC. Si veda sotto la trattazione dell'argomento degli archi multipli con un unico nodo vicino: essi sono ammessi solo se diversi MAC del vicino sono rilevati da diverse interfacce di rete del nodo corrente. Per verificare questo vincolo è necessario identificare un nodo vicino come singola entità (con il NeigborhoodNodeID) e non basarsi soltanto sui distinti MAC address. Con ogni vicino, poi, si accorda per la creazione (o meno) di un arco. L'accordo potrebbe non essere raggiunto perché uno dei due vertici ha già un numero di archi elevato. Se l'accordo viene raggiunto entrambi i nodi vengono anche a conoscenza dell' ''indirizzo di scheda'' del vicino. Sia ''k'' un arco che il modulo nel nodo corrente ''a'' ha creato con un vicino ''b''. La struttura dati che il modulo mantiene per l'arco ''k'' contiene: * L'identificativo di ''b'' (NeighborhoodNodeID). * Il MAC address dell'interfaccia di rete di ''b''. ~-'''Nota fuori luogo''' Si tratta di un'interfaccia reale di ''b'', gestita dalla identità principale di ''b'' nel suo network namespace default.-~ * L' ''indirizzo di scheda'' associato dal nodo ''b'' a detta interfaccia. * L'interfaccia di rete di ''a''. ~-'''Nota fuori luogo''' Si tratta di un'interfaccia reale di ''a'', gestita dalla identità principale di ''a'' nel suo network namespace default.-~ * Il costo dell'arco. . ~-'''Nota fuori luogo''' Una volta che detto arco è stato realizzato, non è di pertinenza del modulo Neighborhood stabilire se il nodo va a popolare gli archi del QSPN. Quindi esso non sa quali ''identità'' l'altro nodo abbia e con quali risulti collegato a noi. Sarà l'utilizzatore del modulo a prendersene cura dopo che gli è stata segnalata dal modulo la costituzione del nuovo arco.-~ Quando il modulo crea un nuovo arco, esso imposta le rotte nelle tabelle del kernel per rendere possibile la comunicazione via TCP con il vicino attraverso gli ''indirizzi di scheda''. Nel tempo, il modulo gestisce la costituzione di nuovi archi, la rimozione di archi, i cambiamenti del costo degli archi. == Creazione di una nuova identità == Abbiamo detto che la creazione di una nuova ''identità'' di un nodo non è una scelta del modulo, ma del suo utilizzatore. Vediamo come questo avviene. Esaminiamo il caso in cui, a fronte di una migrazione, il nodo corrente ''a'' crea una nuova identità ''a~-,,1,,-~'', cioè un nuovo NodeID, basata su una sua precedente identità ''a~-,,0,,-~''. * Di fatto, la creazione di una nuova identità consiste nell'istanziare un nuovo !QspnManager. Cioè: ''a~-,,1,,-~'' è un nuovo NodeID associato ad una nuova istanza di ogni modulo che è "!IdentityAware", in particolare il modulo QSPN. * Se la vecchia identità ''a~-,,0,,-~'' era la principale, allora la nuova identità ''a~-,,1,,-~'' diventa la principale. * L'utilizzatore del modulo manteneva l'associazione ''ns'' tra l'identità ''a~-,,0,,-~'' e un network namespace ''n~-,,old,,-~''. Se ''a~-,,0,,-~'' era l'identità principale, allora ''n~-,,old,,-~'' era il network namespace default. * L'utilizzatore del modulo, in autonomia, crea un network namespace temporaneo ''n~-,,temp,,-~''. Poi aggiorna le sue associazioni: alla vecchia identità ''a~-,,0,,-~'' associa ''n~-,,temp,,-~''. Alla nuova identità ''a~-,,1,,-~'' associa ''n~-,,old,,-~''. D'ora in poi ''a~-,,1,,-~'' gestirà le interfacce di rete (reali o pseudo) che sono in ''n~-,,old,,-~''. * In pseudo codice: * ''n~-,,temp,,-~'' = new network_namespace(). * ''n~-,,old,,-~'' = ''ns(a~-,,0,,-~)''. * ''ns(a~-,,0,,-~)'' = ''n~-,,temp,,-~''. * ''ns(a~-,,1,,-~)'' = ''n~-,,old,,-~''. * L'utilizzatore del modulo manteneva anche l'associazione ''in(a~-,,0,,-~)'' (interfacce gestite da ''a~-,,0,,-~'') tra ogni interfaccia di rete reale gestita dal nodo e l'interfaccia (reale o pseudo) gestita dall'identità. * Per ogni interfaccia di rete reale, crea una pseudo-interfaccia e la sposta su questo namespace: sarà gestita da ''a~-,,0,,-~''. Mentre quelle che prima gestiva ''a~-,,0,,-~'' saranno gestita da ''a~-,,1,,-~''. * Associa un nuovo ''indirizzo di scheda'' ad ogni interfaccia di rete gestita da ''a~-,,0,,-~'' sul nuovo network namespace temporaneo ''n~-,,temp,,-~''. * In pseudo codice: * Per ogni ''r'' in ''list_interfacce_reali'': * ''in(a~-,,1,,-~)(r)'' = ''in(a~-,,0,,-~)(r)''. * ''in(a~-,,0,,-~)(r)'' = new pseudo_interfaccia() basata su ''r'' nel namespace ''ns(a~-,,0,,-~)''. . Questa operazione valorizza anche ''in(a~-,,0,,-~)(r).mac''. * ''in(a~-,,0,,-~)(r).linklocal'' = new indirizzo_di_scheda(). * L'utilizzatore del modulo (forse con l'aiuto del modulo QSPN) manteneva anche una associazione ''f'' tra la coppia ''a~-,,0,,-~-i'' (formata dall'identità ''a~-,,0,,-~'' e un arco ''i'' che il modulo Neighborhood aveva realizzato) e un set di identità nel nodo ''b'' collegato all'arco ''i''. . Entriamo nel dettaglio dell'associazione ''f''. Per ogni arco ''i'': * L'arco ''i'' collega una interfaccia di rete reale di ''a'' con una interfaccia di rete reale di ''b''. Chiamiamo ''a(i)'' l'interfaccia reale di ''a'' e ''b(i)'' l'interfaccia reale di ''b''. * L'identità ''a~-,,0,,-~'' poteva essere la ''principale'' oppure no. Se non lo era, allora non gestiva l'interfaccia reale ''a(i)'' ma una pseudo-interfaccia che si appoggia su essa ma ha un diverso MAC address e un diverso ''indirizzo di scheda''. In generale abbiamo detto che indichiamo con ''in(a~-,,0,,-~)(a(i))'' l'interfaccia (reale o pseudo) gestita da ''a~-,,0,,-~'' che si appoggia all'interfaccia di rete reale ''a(i)''. * Nel nodo ''b'', analogamente, possono esistere più identità. Una di esse, la ''principale'', gestisce l'interfaccia di rete reale ''b(i)''. Le altre gestiscono ognuna una pseudo-interfaccia che si appoggia sulla reale ma ha un diverso MAC address e un diverso ''indirizzo di scheda'' rispetto ai dati riportati dall'arco ''i''. * L'associazione ''f'' nel nodo ''a'', collega la coppia ''a~-,,0,,-~-i'' a zero o una o più di queste identità di ''b''. Supponiamo che siano ''n''. Scriviamo ''f(a~-,,0,,-~-i).len = n''. * Indichiamo la ''k''-esima di esse con la scrittura ''f(a~-,,0,,-~-i)[k]''. Sia ''b~-,,j,,-~'' l'identità di ''b'' a cui si riferisce. * La struttura dati ''f(a~-,,0,,-~-i)[k]'' contiene: * ''ns'' - Il nome del network namespace gestito da ''a~-,,0,,-~''. In realtà no, sarebbe una duplicazione. * ''dev'' - Il nome dell'interfaccia di rete gestita da ''a~-,,0,,-~'' su cui si appoggia l'arco ''i''. In realtà no, sarebbe una duplicazione. * ''mac'' - Il MAC dell'interfaccia di rete gestita da ''a~-,,0,,-~'' su cui si appoggia l'arco ''i''. In realtà no, sarebbe una duplicazione. * ''linklocal'' - L' ''indirizzo di scheda'' dell'interfaccia di rete gestita da ''a~-,,0,,-~'' su cui si appoggia l'arco ''i''. In realtà no, sarebbe una duplicazione. * ''peer_mac'' - Il MAC address dell'interfaccia (reale o pseudo) gestita da ''b~-,,j,,-~''. * ''peer_linklocal'' - L' ''indirizzo di scheda'' dell'interfaccia (reale o pseudo) gestita da ''b~-,,j,,-~''. * Questa struttura dati la chiamiamo per brevità ''arco-identità'' ''a~-,,0,,-~''-''b~-,,j,,-~''. * Ci sono da fare delle operazioni per ogni ''arco-identità'' che parte da ''a~-,,0,,-~'' ora che la nuova identità ''a~-,,1,,-~'' è stata creata basandosi sulla precedente identità ''a~-,,0,,-~''. . Per l'esattezza, quali operazioni vanno fatte dipende anche dal fatto che l'identità nel nodo collegato abbia o meno partecipato anch'essa alla migrazione. * Per ogni ''arco-identità'' ''w~-,,0,,-~'' che parte da ''a~-,,0,,-~'': * Sia ''i'' l'arco su cui si appoggia ''w~-,,0,,-~''. Sia ''b'' il nodo collegato all'arco ''i''. * Sia ''b~-,,j,,-~'' l'identità collegata a ''w~-,,0,,-~''. * Il nodo ''a'' crea un duplicato ''w~-,,1,,-~'' dell'arco per assegnarlo ad ''a~-,,1,,-~''. ''w~-,,1,,-~ = w~-,,0,,-~.copy(); f(a~-,,1,,-~-i).add(w~-,,1,,-~)''. * Oltre a assegnare il nuovo ''arco-identità'' alla nuova identità nell'associazione ''f'', il nodo passa un IQspnArc alla nuova istanza di !QspnManager. * Cambia i dati dell'arco assegnato ad ''a~-,,0,,-~'' relativamente all'interfaccia locale: * ''w~-,,0,,-~.ns = ns(a~-,,0,,-~)''. In realtà questo dato non è nella struttura, sarebbe una duplicazione. * ''w~-,,0,,-~.dev = in(a~-,,0,,-~)(a(i))''. In realtà questo dato non è nella struttura, sarebbe una duplicazione. * ''w~-,,0,,-~.mac = in(a~-,,0,,-~)(a(i)).mac''. In realtà questo dato non è nella struttura, sarebbe una duplicazione. * ''w~-,,0,,-~.linklocal = in(a~-,,0,,-~)(a(i)).linklocal''. In realtà questo dato non è nella struttura, sarebbe una duplicazione. * L'identità ''a~-,,0,,-~'' deve comunicare questi nuovi dati riguardo i suoi estremi (mac e linklocal) all'identità ''b~-,,j,,-~''. Inoltre deve chiedere all'identità ''b~-,,j,,-~'' se ha partecipato anch'essa alla migrazione. Se sì, l'identità ''b~-,,j,,-~'' risponde comunicando i dati ''new_peer_mac'' e ''new_peer_linklocal'' che sono riferiti alla nuova interfaccia che il nodo ''b'' ha creato. * Se ''b~-,,j,,-~'' ha partecipato alla migrazione: * Cambia i dati dell'arco assegnato ad ''a~-,,0,,-~'' relativamente all'interfaccia remota: * ''w~-,,0,,-~.peer_mac = new_peer_mac''. * ''w~-,,0,,-~.peer_linklocal = new_peer_linklocal''. * Altrimenti: * Restano gli stessi. Tutte queste operazioni non coinvolgono direttamente il modulo Neighborhood. Esso resta comunque in grado, ricevendo dall'utilizzatore i NodeID aggiornati, di produrre uno stub per comunicare dalla sua nuova identità ad uno o più diretti vicini. Resta anche in grado, dato un messaggio ricevuto, di identificare, per mezzo delle callback ricevute all'inizio, se è per la sua nuova identità e da parte di chi. Esaminiamo il caso in cui un nodo vicino ''b'' crea una nuova identità ''b~-,,1,,-~'' basata su una sua precedente identità ''b~-,,0,,-~''. La precedente identità ''b~-,,0,,-~'' era collegata attraverso un arco (o più di uno) alla identità del nodo corrente ''a~-,,k,,-~'', la quale non è cambiata. '''TODO''' Tutte queste operazioni non coinvolgono direttamente il modulo Neighborhood. Esso resta comunque in grado, ricevendo dall'utilizzatore i NodeID aggiornati, di produrre uno stub per comunicare dalla sua vecchia identità alla nuova identità del vicino. Resta anche in grado, dato un messaggio ricevuto, di identificare, per mezzo delle callback ricevute all'inizio, se è per una sua identità da parte della nuova identità del vicino. == Rimozione di un arco-identità == Deve essere possibile richiedere al modulo Neighborhood la rimozione di un certo ''arco-identità'' dal set mantenuto in un certo arco. Di norma questo avviene quando l'utilizzatore del modulo decide che una certa ''identità'' del nodo corrente non deve avere più archi verso una certa ''identità'' di un suo vicino; quindi l'operazione va fatta su tutti gli archi che collegano i due nodi. Quindi diciamo anche che l'utilizzatore del modulo deve poter chiedere al modulo l'elenco di tutti i suoi archi e per ogni arco l'elenco degli ''archi-identità'' associati. Così che possa controllarli e poi sapere di quali richiedere la rimozione. Quando il modulo Neighborhood rimuove un ''arco-identità'' (ricordiamo che questo avviene solo su richiesta diretta del suo utilizzatore) si occupa anche di rimuovere la rotta che collegava i relativi ''indirizzi di scheda''. Quindi in precedenza l'utilizzatore del modulo si era dovuto occupare di rimuovere (o cambiare) tutte le rotte che usavano quell'arco come gateway. == Caratteristiche degli archi == Quando si crea un arco esso esiste per entrambi i nodi. ---- Tra due nodi vicini ci possono essere più collegamenti. Ad esempio i nodi A e B possono avere entrambi una interfaccia di rete wireless e una ethernet ed essere collegati sia con un cavo (direttamente o per il tramite di un [[https://en.wikipedia.org/wiki/Ethernet_hub|hub]]) sia con le interfacce wireless (in modalità ad-hoc o per il tramite di un access point). Oppure ancora, un nodo C con due interfacce di rete ethernet può essere collegato tramite due cavi ad un unico [[https://en.wikipedia.org/wiki/Network_switch|switch]] al quale è collegato anche il nodo D. Ci chiediamo: in quali casi può essere utile che i due nodi tengano in considerazione più di un arco? Se i collegamenti sono su distinti [[https://en.wikipedia.org/wiki/Collision_domain|domini di collisione]], è utile considerarli in modo distinto, poiché le trasmissioni fatte su un collegamento non influenzano la capacità dell'altro collegamento. Ad esempio i nodi A e B possono sfruttare in parallelo i due collegamenti (uno via cavo e l'altro via etere). Consideriamo il caso del nodo C che ha le due interfacce ethernet C~-,,0,,-~ e C~-,,1,,-~ collegate tramite due cavi ad uno switch~-^1^-~. Supponiamo che il nodo D sia collegato allo stesso switch con un solo cavo collegato alla sua interfaccia D~-,,0,,-~. Supponiamo che sia in corso una trasmissione di dati da D~-,,0,,-~ a C~-,,0,,-~. Allora questa trasmissione influenza la capacità del collegamento tra D~-,,0,,-~ e C~-,,1,,-~; quindi non potremmo usare questi due collegamenti in parallelo in modo efficiente. Supponiamo, invece, che il nodo D sia collegato allo stesso switch con due cavi collegati alle sue interfacce D~-,,0,,-~ e D~-,,1,,-~. Supponiamo che sia in corso una trasmissione di dati da D~-,,0,,-~ a C~-,,0,,-~. Allora questa trasmissione, per le caratteristiche di uno switch, non influenza la capacità del collegamento tra D~-,,1,,-~ e C~-,,1,,-~; quindi potremmo usare questi due collegamenti in parallelo in modo efficiente. Si consideri inoltre che uno switch, pur realizzando distinti domini di collisione, forma un unico [[https://en.wikipedia.org/wiki/Broadcast_domain|dominio broadcast]]; questo significa che il nodo C tramite la sua interfaccia C~-,,0,,-~ con un messaggio in broadcast rileva la presenza del nodo D anche attraverso la sua interfaccia D~-,,1,,-~. E la stessa cosa vale per la coppia D~-,,0,,-~ e C~-,,1,,-~. Però non ha senso considerare tutti questi 4 possibili archi: lo sfruttamento ottimale si ha con due archi, ad esempio D~-,,0,,-~-C~-,,0,,-~ e D~-,,1,,-~-C~-,,1,,-~, che possono essere usati per due trasmissioni in parallelo che non si disturbano a vicenda. Ovviamente un nodo non è in grado di sapere se il segmento di rete a cui è collegato tramite una sua interfaccia è supportato da uno switch o da un semplice hub. Per avvicinarci allo sfruttamento ottimale descritto sopra, implementiamo questa regola: il modulo consente la costituzione di un secondo arco tra i nodi A e B solo se le interfacce di rete di entrambi i nodi sono diverse da quelle su cui è realizzato il primo arco. . ~-Nota 1: Si consideri la differenza tra uno switch e un hub. Non avrebbe senso collegare due interfacce ethernet di un singolo nodo ad un unico hub, perché in nessun caso si potrebbero sfruttare in parallelo.-~ ---- Il costo di un arco in una direzione (da A verso B) può essere diverso dal costo nella direzione inversa. Cioè ogni vertice effettua una misurazione del costo dell'arco indipendente da quella fatta dall'altro vertice. Questo non è utile quando il costo rappresenta il round-trip time, ma può esserlo se rappresenta la larghezza di banda in uscita. ---- Quando un nodo rimuove un arco tenta di comunicarlo al vertice collegato perché faccia altrettanto. |
Line 44: | Line 187: |
* Implementazione del sistema di tasklet. | |
Line 46: | Line 189: |
* La (o le) interfaccia di rete da monitorare. <<BR>> Una interfaccia nic_xy è un oggetto che comprende: * il nome della interfaccia ( wlan0 ) * il MAC address della interfaccia ( CC:AF:78:2E:C8:B6 ) * Callback per misurare il RTT (ping) con un vicino. |
* La (o le) interfaccia di rete da monitorare. . Durante le operazioni del modulo è possibile aggiungere o rimuovere una interfaccia di rete da monitorare. |
Line 51: | Line 192: |
* Factory per ottenere uno "stub" per invocare metodi remoti nei nodi vicini. Durante le operazioni del modulo è possibile aggiungere o rimuovere una interfaccia di rete da monitorare. Quando si aggiunge una interfaccia al modulo, esso verifica che non sia un duplicato; se nome e MAC coincidono con una già inserita allora ignora la richiesta; se uno coincide e l'altro no, allora errore fatale. Quando si aggiunge una interfaccia di rete da monitorare, il nodo genera un nuovo indirizzo IPv4 locale (vedi appunti) e se lo assegna su questa interfaccia. Quando si rimuove l'interfaccia dal modulo il nodo rimuove anche l'indirizzo locale dalla interfaccia. |
* Factory per creare uno "stub" per invocare metodi remoti nei nodi vicini. * Manager di indirizzi e rotte. |
Line 64: | Line 196: |
Line 66: | Line 197: |
* rilevamento di una collisione con altra rete; questo evento scatenerà la procedura di fusione delle due reti su un altro modulo. * costituzione di un arco. * rimozione di un arco. * variazione del costo di un arco. * Fornisce on demand: * elenco degli archi ora presenti. * dato il nome di una interfaccia di rete (e.g. wlan0) [che di norma si ottiene dal parametro _rpc_caller di un metodo remoto che è in esecuzione, il che fa presumere che la suddetta interfaccia è gestita dal modulo] l'oggetto interfaccia che la rappresenta, se tale interfaccia è al momento gestita dal modulo. Altrimenti un RPCError? * dato un UnicastID (identificativo di un unicast) ricevuto da una data interfaccia di rete, stabilire se è un messaggio da processare. * dato un arco, un oggetto stub utilizzabile per inviare un messaggio tramite questo arco al vicino (chiamare un metodo remoto in unicast) * un oggetto stub utilizzabile per inviare un messaggio (chiamare un metodo remoto in broadcast) a tutti i vicini; oppure, dato un identificativo di un vicino, a tutti i vicini tranne quello; oppure, data una interfaccia di rete, a tutti i vicini raggiungibili tramite quella. <<BR>> Quando si invia un messaggio tramite questo oggetto l'invio del messaggio è asincrono: procederà in una nuova tasklet, mentre il metodo non fornirà alcuna risposta al chiamante. E' possibile fornire una callback che verrà richiamata dopo un certo tempo se per qualcuno degli archi noti al modulo non si avrà ricevuto un messaggio di ACK dal vicino collegato. Questo controllo viene fatto sugli archi che sono esistenti al momento dell'invio AND sono ancora presenti alla scadenza del timeout. La callback viene chiamata una volta per ogni arco che fallisce e avrà quell'arco come argomento, così che il chiamante possa prendere un provvedimento, ad esempio forzando la rimozione dell'arco. <<BR>> Quando si invia un broadcast a più vicini può essere necessario inviarlo in broadcast su una o più interfacce di rete. * Ha un metodo per forzare la rimozione di un arco. |
* costituzione di un arco. * rimozione di un arco. * variazione del costo di un arco. * Fornisce metodi per: * Ottenere l'elenco degli archi ora presenti. * Dato l'identificativo di un messaggio in unicast ricevuto (una istanza di UnicastID) e dato il nome dell'interfaccia di rete da cui è stato ricevuto, stabilire se il messaggio è da processare. * Dato l'identificativo di un messaggio in broadcast ricevuto (una istanza di BroadcastID) e dato il nome dell'interfaccia di rete da cui è stato ricevuto, stabilire se il messaggio è da processare. * Dato un arco, ottenere un oggetto stub utilizzabile per inviare un messaggio (chiamare un metodo remoto) tramite questo arco al vicino ad esso collegato. Il messaggio viene inviato con protocollo reliable (TCP); questo significa che se non vengo notificato di un errore posso essere certo che il vicino ha ricevuto correttamente il mio messaggio e che l'eventuale risposta che ottengo è stata ricevuta correttamente. * Ottenere un oggetto stub utilizzabile per inviare un messaggio in broadcast. Questo tipo di stub si usa per richiamare metodi remoti che non restituiscono risultati (void e senza eccezioni). E' possibile specificare a questo metodo se lo stub sarà utilizzato per inviare messaggi destinati a tutti i vicini; oppure a tutti tranne uno, passandogli l'identificativo del vicino da escludere; oppure, passandogli una interfaccia di rete, a tutti i vicini raggiungibili tramite quella. . Quando non si specifica una interfaccia di rete, il modulo produrrà uno stub che si occuperà di inviare il messaggio in broadcast su tutte le interfacce di rete gestite. . Quando si invia un messaggio tramite questo oggetto l'invio del messaggio è asincrono: procederà in una nuova tasklet, mentre il metodo non fornirà alcuna risposta al chiamante. E' possibile fornire un oggetto in cui un determinato metodo (callback) verrà richiamato dopo un certo tempo se per qualcuno degli archi noti al modulo non si avrà ricevuto un messaggio di ACK dal vicino collegato. Questo controllo viene fatto sugli archi che sono esistenti al momento dell'invio '''e''' sono ancora presenti alla scadenza del timeout. Il metodo callback viene chiamato una volta per ogni arco che fallisce e avrà quell'arco come argomento, così che il chiamante possa prendere un provvedimento, ad esempio forzando la rimozione dell'arco. * Forzare la rimozione di un arco. |
Line 79: | Line 211: |
La stub factory è un oggetto di cui il modulo conosce l'interfaccia IStubFactory. Tramite essa il modulo può: * ottenere uno stub per chiamare un metodo in broadcast; il modulo può specificare l'oggetto BroadcastID per indicare quali vicini sono interessati; può inoltre indicare una callback da richiamare sugli archi per i quali non riceve il messaggio di ACK. * ottenere uno stub per chiamare un metodo su uno specifico vicino; il modulo specifica l'oggetto UnicastID e può specificare se si vuole ricevere una risposta o no. ---- L'identificativo del proprio nodo, come anche l'identificativo di ogni vertice rilevato, è un oggetto il cui contenuto non è noto al modulo neighborhood. L'interfaccia di questo oggetto nota al modulo gli consente di: * verificare se due identificativi sono identici (metodo 'equals'). * verificare se due identificativi appartengono alla stessa rete (metodo 'is_on_same_network'). ---- Un arco è un oggetto noto al modulo. Grazie alle informazioni memorizzate in esso (my_nic) il modulo è in grado di produrre l'oggetto che realizza la chiamata di un metodo remoto in unicast. L'interfaccia dell'oggetto arco nota all'esterno del modulo, invece, permette solo un sottoinsieme di operazioni: * leggere l'identificativo del vicino. * leggere il MAC del vicino. * ottenere l'indirizzo IPv4 di scheda (vedi appunti, metodo 'string get_local_address') * leggere il costo dell'arco. * verificare se due archi sono identici (metodo 'equals'). * verificare se l'arco poggia su una data interfaccia di rete; abbiamo questo metodo, invece di avere direttamente l'interfaccia, per scoraggiare la creazione di un oggetto remoto unicast senza passare dal modulo. ---- Il costo di un arco rilevato è la latenza, cioè il tempo che impiega un messaggio da noi a raggiungere il vertice collegato. In realtà quello che si può misurare, quindi quello che il modulo memorizza come costo, è il round-trip time (RTT). |
L'implementazione del sistema di tasklet è passata al modulo dal suo utilizzatore. Si tratta di una istanza dell'interfaccia INtkdTasklet che è descritta nel relativo [[Netsukuku/ita/docs/Librerie/TaskletSystem#Interfacce|documento]]. ---- Una interfaccia di rete passata al modulo è un oggetto istanza di una classe di cui il modulo conosce l'interfaccia INeighborhoodNetworkInterface. Tramite questa interfaccia il modulo può: * Leggere il nome dell'interfaccia di rete, es: wlan0 (proprietà 'i_neighborhood_dev'). * Leggere il MAC address dell'interfaccia di rete, es: CC:AF:78:2E:C8:B6 (proprietà 'i_neighborhood_mac'). * Misurare il round-trip time (la latenza) con un vicino. L'utilizzatore del modulo Neighborhood può fornire il meccanismo di misurazione della latenza (o in generale del costo associato ad un arco) secondo due modalità. Come primo tentativo il modulo chiama il metodo 'measure_rtt' passando alcune informazioni tra cui l'indirizzo di scheda (del vicino) associato all'arco. Se l'oggetto fornito dall'utilizzatore è in grado di misurare la latenza con accuratezza (ad esempio eseguendo un comando "ping") questo primo tentativo va a buon fine. Altrimenti il modulo procederà con il secondo meccanismo. Il meccanismo alternativo prevede la collaborazione del modulo nel nodo vicino. In questo caso il compito dell'oggetto fornito dall'utilizzatore, sia nel nodo che fa la misurazione sia nel nodo vicino, sarà quello di inviare e ascoltare un messaggio concordato su una porta UDP qualsiasi nella interfaccia di rete interessata. Con questa modalità la misurazione risulterà certamente meno accurata; questo è dovuto al fatto che il modulo utilizza dei thread cooperativi che non offrono tempi di risposta certi. ---- La stub factory è un oggetto di cui il modulo conosce l'interfaccia INeighborhoodStubFactory. Tramite essa il modulo può: * Creare uno stub per chiamare un metodo via UDP in broadcast sui nodi vicini (metodo 'i_neighborhood_get_broadcast'). . Il modulo specifica una o più interfacce di rete sulle quali desidera che lo stub invii il messaggio. . Inoltre il modulo specifica l'oggetto ISourceID che lo stub includerà nel messaggio come identificativo della ''identità'' del mittente. . Inoltre il modulo specifica l'oggetto IBroadcastID che lo stub includerà nel messaggio. In questo modo viene indicato a ogni vicino che riceve il messaggio se debba considerarsi tra i destinatari (con una o più delle sue ''identità''). . Infine il modulo può indicare un'istanza dell'interfaccia !ModRpc.IAckCommunicator se vuole ricevere dopo il timeout la lista dei MAC address che hanno segnalato con un ACK la ricezione del messaggio. * Creare uno stub per chiamare un metodo via UDP su uno specifico vicino (metodo 'i_neighborhood_get_unicast'). . Il modulo specifica l'interfaccia di rete sulla quale desidera che lo stub invii il messaggio. . Inoltre il modulo specifica l'oggetto ISourceID che lo stub includerà nel messaggio come identificativo della ''identità'' del mittente. . Inoltre il modulo specifica l'oggetto IUnicastID che lo stub includerà nel messaggio per indicare a ogni vicino che lo riceve se è lui (una sua ''identità'') il destinatario. . Infine il modulo può specificare se si vuole attendere l'esecuzione del metodo da parte del vicino o no. Se no, allora la corretta ricezione del messaggio da parte del vicino '''non''' è garantita. . Il modulo usa questa modalità per comunicare con un vicino quando ancora non è stata negoziata la creazione dell'arco e quindi non è ancora possibile realizzare la connessione via TCP. * Creare uno stub per chiamare un metodo via TCP su uno specifico indirizzo (metodo 'i_neighborhood_get_tcp'). . Il modulo specifica l'indirizzo di scheda associato all'arco. . Inoltre il modulo specifica l'oggetto ISourceID che lo stub includerà nel messaggio come identificativo della ''identità'' del mittente. . Inoltre il modulo specifica l'oggetto UnicastID che lo stub includerà nel messaggio per indicare al nodo ricevente quale sia (fra le sue ''identità'') il destinatario. . Infine il modulo può specificare se si vuole attendere l'esecuzione del metodo da parte del vicino o no, ma comunque se il metodo ritorna senza l'eccezione !StubError la ricezione da parte del vicino è garantita. . Il modulo usa questa modalità per comunicare in modo reliable con un nodo vicino attraverso un suo arco. ---- Il manager di rotte e indirizzi è un oggetto di cui il modulo conosce l'interfaccia INeighborhoodIPRouteManager. Tramite essa il modulo può: * Dato il nome di una interfaccia di rete e un indirizzo IP [[http://en.wikipedia.org/wiki/Link-local_address|link-local]] nella dotted form, aggiungere l'indirizzo IP all'interfaccia di rete (metodo 'i_neighborhood_add_address'); * Dato il nome di una interfaccia di rete, il suo indirizzo IP link-local associato e un altro indirizzo IP link-local nella dotted form, aggiungere la rotta con scope link verso un vicino sull'interfaccia specificando come src preferito l'indirizzo di scheda (metodo 'i_neighborhood_add_neighbor'); * Dato il nome di una interfaccia di rete, il suo indirizzo IP link-local associato e l'indirizzo IP link-local di un vicino, rimuovere la rotta con scope link verso il vicino dall'interfaccia (metodo 'i_neighborhood_remove_neighbor'); * Dato il nome di una interfaccia di rete e il suo indirizzo IP link-local associato, rimuovere l'indirizzo IP dall'interfaccia (metodo 'i_neighborhood_remove_address'). Il modulo lo usa per rendere possibile la comunicazione via TCP coi vicini tramite un indirizzo fisso. Il modulo associa ad ogni interfaccia di rete che gestisce un indirizzo detto ''indirizzo di scheda''. Per ogni arco che realizza, il modulo aggiunge la rotta con scope link verso l'indirizzo di scheda dell'interfaccia del vicino collegata all'arco. Quando rimuove l'arco rimuove anche la rotta. Quando il modulo cessa di gestire un'interfaccia rimuove il relativo indirizzo. Tramite questo meccanismo il modulo gestisce solo gli indirizzi di scheda della ''identità'' principale del nodo corrente, nel network namespace default. Allo stesso modo, esso imposta le rotte verso gli indirizzi di scheda della ''identità'' principale di ogni nodo vicino, sempre nel network namespace default. Per la gestione delle altre ''identità'', sia come indirizzi propri sia come rotte verso gli indirizzi dei vicini, il nodo le gestisce in autonomia, senza l'intervento del modulo Neighborhood. ---- L'identificativo del proprio nodo, come anche l'identificativo di ogni vertice rilevato, è un oggetto il cui contenuto non è noto al modulo neighborhood. L'interfaccia di questo oggetto nota al modulo (INeighborhoodNodeID) gli consente di: * verificare se due identificativi sono identici (metodo 'i_neighborhood_equals'). ---- Un arco è un oggetto (!NeighborhoodRealArc) noto al modulo. Grazie alle informazioni memorizzate in esso (my_nic, mac) il modulo è in grado di evitare la creazione di ulteriori archi verso lo stesso vicino se non usano interfacce di rete distinte da ambo i lati, ed anche di segnalare che il vicino ad esso collegato non è fra i destinatari di un messaggio inviato in broadcast. Sempre con le informazioni memorizzate in questo oggetto (nic_addr) il modulo è in grado di produrre lo stub che realizza la chiamata di un metodo remoto in TCP (reliable). L'interfaccia dell'oggetto arco nota all'esterno del modulo, INeighborhoodArc, permette solo un sottoinsieme di operazioni: * Leggere l'identificativo del vicino (proprietà 'i_neighborhood_neighbour_id'). * Leggere il MAC dell'interfaccia di rete del vicino collegata su questo arco (proprietà 'i_neighborhood_neighbour_mac'). * Leggere l'indirizzo di scheda dell'interfaccia di rete del vicino collegata su questo arco (proprietà 'i_neighborhood_neighbour_nic_addr'). Da usare ad esempio quando si vuole impostare una rotta nelle tabelle che ha questo arco come gateway. * Leggere il costo dell'arco (proprietà 'i_neighborhood_cost'). * Leggere l'interfaccia di rete dell'arco (proprietà 'i_neighborhood_nic'). * Data una istanza di !CallerInfo, passata all'inizio dell'esecuzione di un metodo remoto (vedi framework [[Netsukuku/ita/docs/Librerie/ZCD|ZCD]]), verificare se la chiamata del metodo è stata ricevuta tramite questo arco (metodo 'i_neighborhood_comes_from'). ---- Quando si chiama il metodo che produce uno stub per l'invio di messaggi in broadcast, può essere passato un oggetto che contiene il codice e i dati necessari a gestire l'evento di 'mancata ricezione di un ACK da un arco entro il timeout'. Tale oggetto implementa l'interfaccia INeighborhoodMissingArcHandler. L'interfaccia permette di: * lanciare il codice che gestisce una arco mancante, passandogli l'arco (metodo 'i_neighborhood_missing'). ---- Il costo di un arco può essere espresso con diverse metriche (latenza, larghezza di banda, ...). Attualmente l'implementazione del modulo misura la latenza e la esprime con un intero in microsecondi. La latenza è il tempo che impiega un messaggio da noi a raggiungere il vertice collegato. In realtà quello che si può misurare, quindi quello che il modulo memorizza come costo, è il round-trip time (RTT). |
Modulo Neighborhood - Analisi Funzionale
Contents
Ruolo del modulo
Il ruolo fondamentale del modulo Neighborhood è il rilevamento dei collegamenti (detti archi) che si possono realizzare con altri nodi diretti vicini, la loro realizzazione e la misurazione del costo associato a tali archi.
Per l'esattezza, il nodo diretto vicino che viene rilevato è il processo ntkd che è in esecuzione su tale nodo. Infatti tale processo all'avvio si crea un identificativo univoco, che chiamiamo NeigborhoodNodeID e lo trasmette periodicamente sui domini broadcast a cui il nodo è collegato con le sue interfacce di rete. Ogni nodo crea un solo NeigborhoodNodeID e lo mantiene invariato per tutta la sua vita.
Per distinguere un vicino da un altro, il modulo guarda il NeigborhoodNodeID. Inoltre, il modulo distingue anche le singole interfacce di rete che riesce a rilevare di ogni suo vicino. Cioè guarda alla coppia NeigborhoodNodeID + MAC address.
Come diretta conseguenza del ruolo di realizzazione e mantenimento degli archi, al modulo Neighborhood è demandato anche il compito di permettere agli altri moduli la comunicazione tra nodi:
- Lato client. Il modulo fornisce metodi per produrre gli stub che gli altri moduli dell'applicazione usano per comunicare con i nodi (diretti vicini o con indirizzo IP).
- Lato server. Il modulo viene interrogato quando si ascolta una richiesta per sapere se passarla a uno (o piu d'uno) skeleton nel nodo corrente, il quale potrà richiamare metodi anche di altri moduli.
Identità multiple in un nodo
Introduciamo il concetto di identità. In un singolo nodo possono in dati momenti sussistere diverse identità. La ragione di essere di queste identità sarà discussa in dettaglio nella trattazione del modulo QSPN, quando si parla di nodi virtuali.
Per una comprensione delle motivazioni di queste identità e del loro rapporto con gli indirizzi di scheda si veda il documento "Modulo QSPN - Esempio di uso degli indirizzi virtuali", in particolare la premessa.
Ogni identità di un nodo ha un suo identificativo. Questo identificativo è distinto dal NeigborhoodNodeID, il quale è un concetto interno al modulo Neighborhood. L'identificativo assegnato ad una identità di un nodo lo chiamiamo semplicemente NodeID.
Il NodeID assegnato a ogni identità è essenzialmente un intero di 32 bit scelto a caso, assumendo che sia univoco a livello dei domini di collisione in cui il nodo partecipa con le sue interfacce di rete. Questo dettaglio implementativo non è di pertinenza del modulo Neighborhood. Il modulo sa solo che il NodeID può essere usato come identificativo univoco che si riferisce ad una precisa identità all'interno di un preciso nodo.
Il modulo Neighborhood non ha conoscenza diretta di quali siano le identità che vivono nel suo nodo in un dato momento. Sappiamo solo che ogni nodo in ogni momento ha una e una sola identità principale. Parleremo in seguito delle caratteristiche peculiari di questa particolare identità, ma non sono informazioni di pertinenza del modulo Neighborhood.
Abbiamo visto che la presenza di identità multiple in un nodo è supportata dal framework ZCD. Con questo intendiamo evidenziare che è possibile realizzare uno stub che possa essere usato da una precisa identità del nodo a, indichiamola con a0, per chiamare un metodo remoto su una precisa identità del nodo b, indichiamola con b0.
Abbiamo detto inoltre che fra i ruoli del modulo Neighborhood c'è quello di permettere agli altri moduli dell'applicazione la comunicazione con altri nodi. Ora aggiungiamo che un particolare modulo può essere interessato a questo tipo di identificazione precisa della identità all'interno di un nodo. Un altro modulo, invece, potrebbe essere agnostico rispetto a queste identità, e voler semplicemente chiamare un metodo remoto su un particolare nodo. Il modulo Neighborhood, lato client, deve saper produrre uno stub per ogni esigenza. Inoltre, lato server, deve saper individuare un elenco (con zero, uno o più elementi) di root-dispatcher a partire da un messaggio ricevuto (cioè da un UnicastID o un BroadcastID).
Nei casi in cui l'identità interna al singolo nodo è rilevante, il NodeID che identifica una identità di un nodo è una parte essenziale delle classi che si usano come SourceID, UnicastID e BroadcastID nella produzione di stub per chiamare metodi remoti.
Facciamo un esempio di un modulo (ad esempio QSPN) in cui una precisa identità del nodo a, indichiamola con a0, vuole chiamare un metodo remoto su una precisa identità del nodo diretto vicino b, indichiamola con b0. Il nodo a chiama un metodo di Neighborhood per produrre uno stub di tipo identity_aware_unicast. In questo metodo il nodo passa il NodeID di a0 e questo sarà usato dal modulo Neighborhood per produrre un IdentityAwareSourceID. Inoltre in questo metodo il nodo passa il NodeID di b0 e questo sarà usato dal modulo Neighborhood per produrre un IdentityAwareUnicastID. Per il lato server vedremo sotto come il nodo b aveva istruito il suo modulo Neighborhood perché potesse gestire questo tipo di UnicastID.
Facciamo un altro esempio di un modulo in cui una precisa identità del nodo a, indichiamola con a0, vuole chiamare un metodo remoto su un set di precise identità dei nodi diretti vicini, diciamo b0, b1, c0 e d0. Il nodo a chiama un metodo di Neighborhood per produrre uno stub di tipo identity_aware_broadcast. In questo metodo il nodo passa il NodeID di a0 e questo sarà usato dal modulo Neighborhood per produrre un IdentityAwareSourceID. Inoltre in questo metodo il nodo passa i vari NodeID di b0, b1, c0 e d0 e questi saranno usati dal modulo Neighborhood per produrre un IdentityAwareBroadcastID. Per il lato server vedremo sotto come i nodi, ad esempio b, c, d ed anche e, avevano istruito ognuno il suo modulo Neighborhood perché potesse gestire questo tipo di BroadcastID.
In altri casi l'identità interna al singolo nodo non è rilevante. In questi casi è il NeigborhoodNodeID che costituisce la parte essenziale delle classi che si usano come SourceID, UnicastID e BroadcastID nella produzione di stub per chiamare metodi remoti.
Facciamo un esempio di un modulo in cui un nodo a vuole chiamare un metodo remoto su un nodo diretto vicino b. Il nodo a chiama un metodo di Neighborhood per produrre uno stub di tipo whole_node_unicast. Il modulo Neighborhood usa il suo NeigborhoodNodeID per produrre un WholeNodeSourceID. Inoltre in questo metodo il nodo passa il INeighborhoodArc di b (vedremo sotto come questo oggetto sia fornito dal modulo Neighborhood al suo esterno, mentre il NeigborhoodNodeID è interno al modulo); il relativo NeighborhoodNodeID sarà usato dal modulo Neighborhood per produrre un WholeNodeUnicastID. Per il lato server vedremo sotto come il nodo b aveva istruito il suo modulo Neighborhood perché potesse gestire questo tipo di UnicastID. Anche il WholeNodeSourceID ricevuto dal nodo b sarà sufficiente perché il modulo Neighborhood sappia associarlo (anche su richiesta di un altro modulo) ad un particolare INeighborhoodArc.
Facciamo un altro esempio di un modulo in cui un nodo a vuole chiamare un metodo remoto su un set di nodi diretti vicini, diciamo b, c e d. Il nodo a chiama un metodo di Neighborhood per produrre uno stub di tipo whole_node_broadcast. Il modulo Neighborhood usa il suo NeigborhoodNodeID per produrre un WholeNodeSourceID. Inoltre in questo metodo il nodo passa i vari INeighborhoodArc di b, c e d; i relativi NeighborhoodNodeID saranno usati dal modulo Neighborhood per produrre un WholeNodeBroadcastID. Per il lato server vedremo sotto come i nodi, ad esempio b, c, d ed anche e, avevano istruito ognuno il suo modulo Neighborhood perché potesse gestire questo tipo di BroadcastID.
Ovviamente anche lo stesso modulo Neighborhood, il quale nelle sue comunicazioni con i diretti vicini non è interessato a distinguere le identità, può usare in completa autonomia le modalità sopra esposte per effettuare chiamate Unicast e Broadcast.
TODO: serve questo? Alcuni moduli (ad esempio PeerServices) prevedono la possibilità di chiamare un metodo remoto su un nodo che non è un diretto vicino ma si può raggiungere con un TcpClient ad un certo indirizzo IP. In questi casi il destinatario del messaggio è sempre uno solo. Anche in questi casi un modulo potrebbe essere consapevole della presenza di più identità in un nodo e voler individuare una particolare identità sulla quale chiamare il metodo remoto. In questi casi il nodo chiamante deve essere a conoscenza non solo dell'indirizzo IP a cui raggiungere il nodo, ma anche del NodeID della identità su cui operare, oppure l'identità su cui si deve operare è la identità principale del nodo.
All'inizio dovrò dire al Neighborhood:
- se ricevi un UnicastID di tipo IdentityAwareUnicastID:
- usa questa callback
- IAddressManagerSkeleton? xxxxxx(NodeID id)
la quale restituirà <null> oppure un root-dispatcher che potrà accedere solo quei moduli che gestiscono le identità, e in particolare la istanza indicata nell'UnicastID.
- usa questa callback
- se ricevi un BroadcastID di tipo IdentityAwareBroadcastID:
- usa questa callback
Gee.List<IAddressManagerSkeleton> xxxxxx(Gee.List<NodeID> id)
- la quale restituirà una lista (che può essere vuota) di root-dispatcher ognuno dei quali potrà accedere solo quei moduli che gestiscono le identità, e in particolare una precisa istanza.
- usa questa callback
- se ricevi un UnicastID di tipo WholeNodeUnicastID e ti riconosci come destinatario:
- usa questa istanza
- IAddressManagerSkeleton xxxxxx
- la quale è un root-dispatcher che potrà accedere solo quei moduli che non gestiscono le identità.
- usa questa istanza
- se ricevi un BroadcastID di tipo WholeNodeBroadcastID e ti riconosci come destinatario:
- usa questa istanza
- IAddressManagerSkeleton xxxxxx
- la quale è un root-dispatcher che potrà accedere solo quei moduli che non gestiscono le identità.
- usa questa istanza
Operazioni del modulo
Il modulo fa uso delle tasklet, un sistema di multithreading cooperativo. Attraverso di esso esegue il monitoraggio delle schede di rete lasciando libero il chiamante di svolgere altri task.
Il modulo fa uso del framework ZCD, precisamente appoggiandosi ad una libreria intermedia prodotta con questo framework per formalizzare i metodi remoti usati nel demone ntkd.
Quando si inizializza, il modulo produce l'identificativo NeigborhoodNodeID del proprio nodo.
Il modulo viene subito inizializzato con le callback da chiamare per gestire le chiamate IdentityAware e lo skeleton da usare per le chiamate WholeNode. Di modo che è pronto a gestire le chiamate che il nodo potrà ricevere.
Il modulo riceve subito l'elenco delle interfacce di rete che deve gestire. Ad ognuna associa un indirizzo locale detto indirizzo di scheda.
Rileva i nodi vicini raggiungibili attraverso una (o più) interfacce di rete e ne reperisce l'identificativo NeigborhoodNodeID + MAC. Si veda sotto la trattazione dell'argomento degli archi multipli con un unico nodo vicino: essi sono ammessi solo se diversi MAC del vicino sono rilevati da diverse interfacce di rete del nodo corrente. Per verificare questo vincolo è necessario identificare un nodo vicino come singola entità (con il NeigborhoodNodeID) e non basarsi soltanto sui distinti MAC address.
Con ogni vicino, poi, si accorda per la creazione (o meno) di un arco. L'accordo potrebbe non essere raggiunto perché uno dei due vertici ha già un numero di archi elevato. Se l'accordo viene raggiunto entrambi i nodi vengono anche a conoscenza dell' indirizzo di scheda del vicino.
Sia k un arco che il modulo nel nodo corrente a ha creato con un vicino b. La struttura dati che il modulo mantiene per l'arco k contiene:
L'identificativo di b (NeighborhoodNodeID).
Il MAC address dell'interfaccia di rete di b. Nota fuori luogo Si tratta di un'interfaccia reale di b, gestita dalla identità principale di b nel suo network namespace default.
L' indirizzo di scheda associato dal nodo b a detta interfaccia.
L'interfaccia di rete di a. Nota fuori luogo Si tratta di un'interfaccia reale di a, gestita dalla identità principale di a nel suo network namespace default.
- Il costo dell'arco.
Nota fuori luogo Una volta che detto arco è stato realizzato, non è di pertinenza del modulo Neighborhood stabilire se il nodo va a popolare gli archi del QSPN. Quindi esso non sa quali identità l'altro nodo abbia e con quali risulti collegato a noi. Sarà l'utilizzatore del modulo a prendersene cura dopo che gli è stata segnalata dal modulo la costituzione del nuovo arco.
Quando il modulo crea un nuovo arco, esso imposta le rotte nelle tabelle del kernel per rendere possibile la comunicazione via TCP con il vicino attraverso gli indirizzi di scheda.
Nel tempo, il modulo gestisce la costituzione di nuovi archi, la rimozione di archi, i cambiamenti del costo degli archi.
Creazione di una nuova identità
Abbiamo detto che la creazione di una nuova identità di un nodo non è una scelta del modulo, ma del suo utilizzatore. Vediamo come questo avviene.
Esaminiamo il caso in cui, a fronte di una migrazione, il nodo corrente a crea una nuova identità a1, cioè un nuovo NodeID, basata su una sua precedente identità a0.
Di fatto, la creazione di una nuova identità consiste nell'istanziare un nuovo QspnManager. Cioè: a1 è un nuovo NodeID associato ad una nuova istanza di ogni modulo che è "IdentityAware", in particolare il modulo QSPN.
Se la vecchia identità a0 era la principale, allora la nuova identità a1 diventa la principale.
L'utilizzatore del modulo manteneva l'associazione ns tra l'identità a0 e un network namespace nold. Se a0 era l'identità principale, allora nold era il network namespace default.
L'utilizzatore del modulo, in autonomia, crea un network namespace temporaneo ntemp. Poi aggiorna le sue associazioni: alla vecchia identità a0 associa ntemp. Alla nuova identità a1 associa nold. D'ora in poi a1 gestirà le interfacce di rete (reali o pseudo) che sono in nold.
- In pseudo codice:
ntemp = new network_namespace().
nold = ns(a0).
ns(a0) = ntemp.
ns(a1) = nold.
L'utilizzatore del modulo manteneva anche l'associazione in(a0) (interfacce gestite da a0) tra ogni interfaccia di rete reale gestita dal nodo e l'interfaccia (reale o pseudo) gestita dall'identità.
Per ogni interfaccia di rete reale, crea una pseudo-interfaccia e la sposta su questo namespace: sarà gestita da a0. Mentre quelle che prima gestiva a0 saranno gestita da a1.
Associa un nuovo indirizzo di scheda ad ogni interfaccia di rete gestita da a0 sul nuovo network namespace temporaneo ntemp.
- In pseudo codice:
Per ogni r in list_interfacce_reali:
in(a1)(r) = in(a0)(r).
in(a0)(r) = new pseudo_interfaccia() basata su r nel namespace ns(a0).
Questa operazione valorizza anche in(a0)(r).mac.
in(a0)(r).linklocal = new indirizzo_di_scheda().
L'utilizzatore del modulo (forse con l'aiuto del modulo QSPN) manteneva anche una associazione f tra la coppia a0-i (formata dall'identità a0 e un arco i che il modulo Neighborhood aveva realizzato) e un set di identità nel nodo b collegato all'arco i.
Entriamo nel dettaglio dell'associazione f. Per ogni arco i:
L'arco i collega una interfaccia di rete reale di a con una interfaccia di rete reale di b. Chiamiamo a(i) l'interfaccia reale di a e b(i) l'interfaccia reale di b.
L'identità a0 poteva essere la principale oppure no. Se non lo era, allora non gestiva l'interfaccia reale a(i) ma una pseudo-interfaccia che si appoggia su essa ma ha un diverso MAC address e un diverso indirizzo di scheda. In generale abbiamo detto che indichiamo con in(a0)(a(i)) l'interfaccia (reale o pseudo) gestita da a0 che si appoggia all'interfaccia di rete reale a(i).
Nel nodo b, analogamente, possono esistere più identità. Una di esse, la principale, gestisce l'interfaccia di rete reale b(i). Le altre gestiscono ognuna una pseudo-interfaccia che si appoggia sulla reale ma ha un diverso MAC address e un diverso indirizzo di scheda rispetto ai dati riportati dall'arco i.
L'associazione f nel nodo a, collega la coppia a0-i a zero o una o più di queste identità di b. Supponiamo che siano n. Scriviamo f(a0-i).len = n.
Indichiamo la k-esima di esse con la scrittura f(a0-i)[k]. Sia bj l'identità di b a cui si riferisce.
La struttura dati f(a0-i)[k] contiene:
ns - Il nome del network namespace gestito da a0. In realtà no, sarebbe una duplicazione.
dev - Il nome dell'interfaccia di rete gestita da a0 su cui si appoggia l'arco i. In realtà no, sarebbe una duplicazione.
mac - Il MAC dell'interfaccia di rete gestita da a0 su cui si appoggia l'arco i. In realtà no, sarebbe una duplicazione.
linklocal - L' indirizzo di scheda dell'interfaccia di rete gestita da a0 su cui si appoggia l'arco i. In realtà no, sarebbe una duplicazione.
peer_mac - Il MAC address dell'interfaccia (reale o pseudo) gestita da bj.
peer_linklocal - L' indirizzo di scheda dell'interfaccia (reale o pseudo) gestita da bj.
Questa struttura dati la chiamiamo per brevità arco-identità a0-bj.
Ci sono da fare delle operazioni per ogni arco-identità che parte da a0 ora che la nuova identità a1 è stata creata basandosi sulla precedente identità a0.
- Per l'esattezza, quali operazioni vanno fatte dipende anche dal fatto che l'identità nel nodo collegato abbia o meno partecipato anch'essa alla migrazione.
Per ogni arco-identità w0 che parte da a0:
Sia i l'arco su cui si appoggia w0. Sia b il nodo collegato all'arco i.
Sia bj l'identità collegata a w0.
Il nodo a crea un duplicato w1 dell'arco per assegnarlo ad a1. w1 = w0.copy(); f(a1-i).add(w1).
Oltre a assegnare il nuovo arco-identità alla nuova identità nell'associazione f, il nodo passa un IQspnArc alla nuova istanza di QspnManager.
Cambia i dati dell'arco assegnato ad a0 relativamente all'interfaccia locale:
w0.ns = ns(a0). In realtà questo dato non è nella struttura, sarebbe una duplicazione.
w0.dev = in(a0)(a(i)). In realtà questo dato non è nella struttura, sarebbe una duplicazione.
w0.mac = in(a0)(a(i)).mac. In realtà questo dato non è nella struttura, sarebbe una duplicazione.
w0.linklocal = in(a0)(a(i)).linklocal. In realtà questo dato non è nella struttura, sarebbe una duplicazione.
L'identità a0 deve comunicare questi nuovi dati riguardo i suoi estremi (mac e linklocal) all'identità bj. Inoltre deve chiedere all'identità bj se ha partecipato anch'essa alla migrazione. Se sì, l'identità bj risponde comunicando i dati new_peer_mac e new_peer_linklocal che sono riferiti alla nuova interfaccia che il nodo b ha creato.
Se bj ha partecipato alla migrazione:
Cambia i dati dell'arco assegnato ad a0 relativamente all'interfaccia remota:
w0.peer_mac = new_peer_mac.
w0.peer_linklocal = new_peer_linklocal.
- Altrimenti:
- Restano gli stessi.
Tutte queste operazioni non coinvolgono direttamente il modulo Neighborhood. Esso resta comunque in grado, ricevendo dall'utilizzatore i NodeID aggiornati, di produrre uno stub per comunicare dalla sua nuova identità ad uno o più diretti vicini. Resta anche in grado, dato un messaggio ricevuto, di identificare, per mezzo delle callback ricevute all'inizio, se è per la sua nuova identità e da parte di chi.
Esaminiamo il caso in cui un nodo vicino b crea una nuova identità b1 basata su una sua precedente identità b0. La precedente identità b0 era collegata attraverso un arco (o più di uno) alla identità del nodo corrente ak, la quale non è cambiata.
TODO
Tutte queste operazioni non coinvolgono direttamente il modulo Neighborhood. Esso resta comunque in grado, ricevendo dall'utilizzatore i NodeID aggiornati, di produrre uno stub per comunicare dalla sua vecchia identità alla nuova identità del vicino. Resta anche in grado, dato un messaggio ricevuto, di identificare, per mezzo delle callback ricevute all'inizio, se è per una sua identità da parte della nuova identità del vicino.
Rimozione di un arco-identità
Deve essere possibile richiedere al modulo Neighborhood la rimozione di un certo arco-identità dal set mantenuto in un certo arco.
Di norma questo avviene quando l'utilizzatore del modulo decide che una certa identità del nodo corrente non deve avere più archi verso una certa identità di un suo vicino; quindi l'operazione va fatta su tutti gli archi che collegano i due nodi. Quindi diciamo anche che l'utilizzatore del modulo deve poter chiedere al modulo l'elenco di tutti i suoi archi e per ogni arco l'elenco degli archi-identità associati. Così che possa controllarli e poi sapere di quali richiedere la rimozione.
Quando il modulo Neighborhood rimuove un arco-identità (ricordiamo che questo avviene solo su richiesta diretta del suo utilizzatore) si occupa anche di rimuovere la rotta che collegava i relativi indirizzi di scheda. Quindi in precedenza l'utilizzatore del modulo si era dovuto occupare di rimuovere (o cambiare) tutte le rotte che usavano quell'arco come gateway.
Caratteristiche degli archi
Quando si crea un arco esso esiste per entrambi i nodi.
Tra due nodi vicini ci possono essere più collegamenti. Ad esempio i nodi A e B possono avere entrambi una interfaccia di rete wireless e una ethernet ed essere collegati sia con un cavo (direttamente o per il tramite di un hub) sia con le interfacce wireless (in modalità ad-hoc o per il tramite di un access point). Oppure ancora, un nodo C con due interfacce di rete ethernet può essere collegato tramite due cavi ad un unico switch al quale è collegato anche il nodo D.
Ci chiediamo: in quali casi può essere utile che i due nodi tengano in considerazione più di un arco? Se i collegamenti sono su distinti domini di collisione, è utile considerarli in modo distinto, poiché le trasmissioni fatte su un collegamento non influenzano la capacità dell'altro collegamento. Ad esempio i nodi A e B possono sfruttare in parallelo i due collegamenti (uno via cavo e l'altro via etere). Consideriamo il caso del nodo C che ha le due interfacce ethernet C0 e C1 collegate tramite due cavi ad uno switch1. Supponiamo che il nodo D sia collegato allo stesso switch con un solo cavo collegato alla sua interfaccia D0. Supponiamo che sia in corso una trasmissione di dati da D0 a C0. Allora questa trasmissione influenza la capacità del collegamento tra D0 e C1; quindi non potremmo usare questi due collegamenti in parallelo in modo efficiente.
Supponiamo, invece, che il nodo D sia collegato allo stesso switch con due cavi collegati alle sue interfacce D0 e D1. Supponiamo che sia in corso una trasmissione di dati da D0 a C0. Allora questa trasmissione, per le caratteristiche di uno switch, non influenza la capacità del collegamento tra D1 e C1; quindi potremmo usare questi due collegamenti in parallelo in modo efficiente.
Si consideri inoltre che uno switch, pur realizzando distinti domini di collisione, forma un unico dominio broadcast; questo significa che il nodo C tramite la sua interfaccia C0 con un messaggio in broadcast rileva la presenza del nodo D anche attraverso la sua interfaccia D1. E la stessa cosa vale per la coppia D0 e C1. Però non ha senso considerare tutti questi 4 possibili archi: lo sfruttamento ottimale si ha con due archi, ad esempio D0-C0 e D1-C1, che possono essere usati per due trasmissioni in parallelo che non si disturbano a vicenda.
Ovviamente un nodo non è in grado di sapere se il segmento di rete a cui è collegato tramite una sua interfaccia è supportato da uno switch o da un semplice hub. Per avvicinarci allo sfruttamento ottimale descritto sopra, implementiamo questa regola: il modulo consente la costituzione di un secondo arco tra i nodi A e B solo se le interfacce di rete di entrambi i nodi sono diverse da quelle su cui è realizzato il primo arco.
Nota 1: Si consideri la differenza tra uno switch e un hub. Non avrebbe senso collegare due interfacce ethernet di un singolo nodo ad un unico hub, perché in nessun caso si potrebbero sfruttare in parallelo.
Il costo di un arco in una direzione (da A verso B) può essere diverso dal costo nella direzione inversa. Cioè ogni vertice effettua una misurazione del costo dell'arco indipendente da quella fatta dall'altro vertice. Questo non è utile quando il costo rappresenta il round-trip time, ma può esserlo se rappresenta la larghezza di banda in uscita.
Quando un nodo rimuove un arco tenta di comunicarlo al vertice collegato perché faccia altrettanto.
Requisiti
- Implementazione del sistema di tasklet.
- Identificativo del proprio nodo.
- La (o le) interfaccia di rete da monitorare.
- Durante le operazioni del modulo è possibile aggiungere o rimuovere una interfaccia di rete da monitorare.
- Numero massimo di archi da realizzare.
- Factory per creare uno "stub" per invocare metodi remoti nei nodi vicini.
- Manager di indirizzi e rotte.
Deliverables
- Emette un segnale per:
- costituzione di un arco.
- rimozione di un arco.
- variazione del costo di un arco.
- Fornisce metodi per:
- Ottenere l'elenco degli archi ora presenti.
- Dato l'identificativo di un messaggio in unicast ricevuto (una istanza di UnicastID) e dato il nome dell'interfaccia di rete da cui è stato ricevuto, stabilire se il messaggio è da processare.
- Dato l'identificativo di un messaggio in broadcast ricevuto (una istanza di BroadcastID) e dato il nome dell'interfaccia di rete da cui è stato ricevuto, stabilire se il messaggio è da processare.
- Dato un arco, ottenere un oggetto stub utilizzabile per inviare un messaggio (chiamare un metodo remoto) tramite questo arco al vicino ad esso collegato. Il messaggio viene inviato con protocollo reliable (TCP); questo significa che se non vengo notificato di un errore posso essere certo che il vicino ha ricevuto correttamente il mio messaggio e che l'eventuale risposta che ottengo è stata ricevuta correttamente.
- Ottenere un oggetto stub utilizzabile per inviare un messaggio in broadcast. Questo tipo di stub si usa per richiamare metodi remoti che non restituiscono risultati (void e senza eccezioni). E' possibile specificare a questo metodo se lo stub sarà utilizzato per inviare messaggi destinati a tutti i vicini; oppure a tutti tranne uno, passandogli l'identificativo del vicino da escludere; oppure, passandogli una interfaccia di rete, a tutti i vicini raggiungibili tramite quella.
- Quando non si specifica una interfaccia di rete, il modulo produrrà uno stub che si occuperà di inviare il messaggio in broadcast su tutte le interfacce di rete gestite.
Quando si invia un messaggio tramite questo oggetto l'invio del messaggio è asincrono: procederà in una nuova tasklet, mentre il metodo non fornirà alcuna risposta al chiamante. E' possibile fornire un oggetto in cui un determinato metodo (callback) verrà richiamato dopo un certo tempo se per qualcuno degli archi noti al modulo non si avrà ricevuto un messaggio di ACK dal vicino collegato. Questo controllo viene fatto sugli archi che sono esistenti al momento dell'invio e sono ancora presenti alla scadenza del timeout. Il metodo callback viene chiamato una volta per ogni arco che fallisce e avrà quell'arco come argomento, così che il chiamante possa prendere un provvedimento, ad esempio forzando la rimozione dell'arco.
- Forzare la rimozione di un arco.
Classi e interfacce
L'implementazione del sistema di tasklet è passata al modulo dal suo utilizzatore. Si tratta di una istanza dell'interfaccia INtkdTasklet che è descritta nel relativo documento.
Una interfaccia di rete passata al modulo è un oggetto istanza di una classe di cui il modulo conosce l'interfaccia INeighborhoodNetworkInterface. Tramite questa interfaccia il modulo può:
- Leggere il nome dell'interfaccia di rete, es: wlan0 (proprietà 'i_neighborhood_dev').
Leggere il MAC address dell'interfaccia di rete, es: CC:AF:78:2E:C8:B6 (proprietà 'i_neighborhood_mac').
- Misurare il round-trip time (la latenza) con un vicino.
L'utilizzatore del modulo Neighborhood può fornire il meccanismo di misurazione della latenza (o in generale del costo associato ad un arco) secondo due modalità. Come primo tentativo il modulo chiama il metodo 'measure_rtt' passando alcune informazioni tra cui l'indirizzo di scheda (del vicino) associato all'arco. Se l'oggetto fornito dall'utilizzatore è in grado di misurare la latenza con accuratezza (ad esempio eseguendo un comando "ping") questo primo tentativo va a buon fine. Altrimenti il modulo procederà con il secondo meccanismo.
Il meccanismo alternativo prevede la collaborazione del modulo nel nodo vicino. In questo caso il compito dell'oggetto fornito dall'utilizzatore, sia nel nodo che fa la misurazione sia nel nodo vicino, sarà quello di inviare e ascoltare un messaggio concordato su una porta UDP qualsiasi nella interfaccia di rete interessata. Con questa modalità la misurazione risulterà certamente meno accurata; questo è dovuto al fatto che il modulo utilizza dei thread cooperativi che non offrono tempi di risposta certi.
La stub factory è un oggetto di cui il modulo conosce l'interfaccia INeighborhoodStubFactory. Tramite essa il modulo può:
- Creare uno stub per chiamare un metodo via UDP in broadcast sui nodi vicini (metodo 'i_neighborhood_get_broadcast').
- Il modulo specifica una o più interfacce di rete sulle quali desidera che lo stub invii il messaggio.
Inoltre il modulo specifica l'oggetto ISourceID che lo stub includerà nel messaggio come identificativo della identità del mittente.
Inoltre il modulo specifica l'oggetto IBroadcastID che lo stub includerà nel messaggio. In questo modo viene indicato a ogni vicino che riceve il messaggio se debba considerarsi tra i destinatari (con una o più delle sue identità).
Infine il modulo può indicare un'istanza dell'interfaccia ModRpc.IAckCommunicator se vuole ricevere dopo il timeout la lista dei MAC address che hanno segnalato con un ACK la ricezione del messaggio.
- Creare uno stub per chiamare un metodo via UDP su uno specifico vicino (metodo 'i_neighborhood_get_unicast').
- Il modulo specifica l'interfaccia di rete sulla quale desidera che lo stub invii il messaggio.
Inoltre il modulo specifica l'oggetto ISourceID che lo stub includerà nel messaggio come identificativo della identità del mittente.
Inoltre il modulo specifica l'oggetto IUnicastID che lo stub includerà nel messaggio per indicare a ogni vicino che lo riceve se è lui (una sua identità) il destinatario.
Infine il modulo può specificare se si vuole attendere l'esecuzione del metodo da parte del vicino o no. Se no, allora la corretta ricezione del messaggio da parte del vicino non è garantita.
- Il modulo usa questa modalità per comunicare con un vicino quando ancora non è stata negoziata la creazione dell'arco e quindi non è ancora possibile realizzare la connessione via TCP.
- Creare uno stub per chiamare un metodo via TCP su uno specifico indirizzo (metodo 'i_neighborhood_get_tcp').
- Il modulo specifica l'indirizzo di scheda associato all'arco.
Inoltre il modulo specifica l'oggetto ISourceID che lo stub includerà nel messaggio come identificativo della identità del mittente.
Inoltre il modulo specifica l'oggetto UnicastID che lo stub includerà nel messaggio per indicare al nodo ricevente quale sia (fra le sue identità) il destinatario.
Infine il modulo può specificare se si vuole attendere l'esecuzione del metodo da parte del vicino o no, ma comunque se il metodo ritorna senza l'eccezione StubError la ricezione da parte del vicino è garantita.
- Il modulo usa questa modalità per comunicare in modo reliable con un nodo vicino attraverso un suo arco.
Il manager di rotte e indirizzi è un oggetto di cui il modulo conosce l'interfaccia INeighborhoodIPRouteManager. Tramite essa il modulo può:
Dato il nome di una interfaccia di rete e un indirizzo IP link-local nella dotted form, aggiungere l'indirizzo IP all'interfaccia di rete (metodo 'i_neighborhood_add_address');
- Dato il nome di una interfaccia di rete, il suo indirizzo IP link-local associato e un altro indirizzo IP link-local nella dotted form, aggiungere la rotta con scope link verso un vicino sull'interfaccia specificando come src preferito l'indirizzo di scheda (metodo 'i_neighborhood_add_neighbor');
- Dato il nome di una interfaccia di rete, il suo indirizzo IP link-local associato e l'indirizzo IP link-local di un vicino, rimuovere la rotta con scope link verso il vicino dall'interfaccia (metodo 'i_neighborhood_remove_neighbor');
- Dato il nome di una interfaccia di rete e il suo indirizzo IP link-local associato, rimuovere l'indirizzo IP dall'interfaccia (metodo 'i_neighborhood_remove_address').
Il modulo lo usa per rendere possibile la comunicazione via TCP coi vicini tramite un indirizzo fisso. Il modulo associa ad ogni interfaccia di rete che gestisce un indirizzo detto indirizzo di scheda. Per ogni arco che realizza, il modulo aggiunge la rotta con scope link verso l'indirizzo di scheda dell'interfaccia del vicino collegata all'arco. Quando rimuove l'arco rimuove anche la rotta. Quando il modulo cessa di gestire un'interfaccia rimuove il relativo indirizzo.
Tramite questo meccanismo il modulo gestisce solo gli indirizzi di scheda della identità principale del nodo corrente, nel network namespace default. Allo stesso modo, esso imposta le rotte verso gli indirizzi di scheda della identità principale di ogni nodo vicino, sempre nel network namespace default. Per la gestione delle altre identità, sia come indirizzi propri sia come rotte verso gli indirizzi dei vicini, il nodo le gestisce in autonomia, senza l'intervento del modulo Neighborhood.
L'identificativo del proprio nodo, come anche l'identificativo di ogni vertice rilevato, è un oggetto il cui contenuto non è noto al modulo neighborhood. L'interfaccia di questo oggetto nota al modulo (INeighborhoodNodeID) gli consente di:
- verificare se due identificativi sono identici (metodo 'i_neighborhood_equals').
Un arco è un oggetto (NeighborhoodRealArc) noto al modulo. Grazie alle informazioni memorizzate in esso (my_nic, mac) il modulo è in grado di evitare la creazione di ulteriori archi verso lo stesso vicino se non usano interfacce di rete distinte da ambo i lati, ed anche di segnalare che il vicino ad esso collegato non è fra i destinatari di un messaggio inviato in broadcast. Sempre con le informazioni memorizzate in questo oggetto (nic_addr) il modulo è in grado di produrre lo stub che realizza la chiamata di un metodo remoto in TCP (reliable).
L'interfaccia dell'oggetto arco nota all'esterno del modulo, INeighborhoodArc, permette solo un sottoinsieme di operazioni:
- Leggere l'identificativo del vicino (proprietà 'i_neighborhood_neighbour_id').
- Leggere il MAC dell'interfaccia di rete del vicino collegata su questo arco (proprietà 'i_neighborhood_neighbour_mac').
- Leggere l'indirizzo di scheda dell'interfaccia di rete del vicino collegata su questo arco (proprietà 'i_neighborhood_neighbour_nic_addr'). Da usare ad esempio quando si vuole impostare una rotta nelle tabelle che ha questo arco come gateway.
- Leggere il costo dell'arco (proprietà 'i_neighborhood_cost').
- Leggere l'interfaccia di rete dell'arco (proprietà 'i_neighborhood_nic').
Data una istanza di CallerInfo, passata all'inizio dell'esecuzione di un metodo remoto (vedi framework ZCD), verificare se la chiamata del metodo è stata ricevuta tramite questo arco (metodo 'i_neighborhood_comes_from').
Quando si chiama il metodo che produce uno stub per l'invio di messaggi in broadcast, può essere passato un oggetto che contiene il codice e i dati necessari a gestire l'evento di 'mancata ricezione di un ACK da un arco entro il timeout'. Tale oggetto implementa l'interfaccia INeighborhoodMissingArcHandler. L'interfaccia permette di:
- lanciare il codice che gestisce una arco mancante, passandogli l'arco (metodo 'i_neighborhood_missing').
Il costo di un arco può essere espresso con diverse metriche (latenza, larghezza di banda, ...). Attualmente l'implementazione del modulo misura la latenza e la esprime con un intero in microsecondi.
La latenza è il tempo che impiega un messaggio da noi a raggiungere il vertice collegato. In realtà quello che si può misurare, quindi quello che il modulo memorizza come costo, è il round-trip time (RTT).