6620
Comment:
|
54899
|
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. Un arco associa una specifica interfaccia di rete del nodo corrente con una specifica interfaccia di rete di un altro nodo diretto vicino. Tecnicamente, con il termine ''diretto vicino'' si intende che le due interfacce di rete suddette sono collegate ad un comune [[https://en.wikipedia.org/wiki/Broadcast_domain|dominio broadcast]]. Come vedremo in seguito, non sono ammessi due archi che partendo da una stessa interfaccia di rete del nodo corrente colleghino a due distinte interfacce di rete di uno stesso nodo diretto vicino. Per questo ogni arco deve identificare univocamente non soltanto la specifica interfaccia di rete di un vicino, ma anche il nodo vicino come entità nel suo insieme. L'identificativo di un nodo vicino è chiamato NeigborhoodNodeID. Esso è un concetto interno al modulo Neighborhood. Riassumendo, ogni arco associa una interfaccia di rete del nodo corrente ad una coppia composta dal NeigborhoodNodeID del vicino e dal MAC address della sua interfaccia. 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. == Operazioni di base == 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 alla libreria di livello intermedio ''ntkdrpc'' 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 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''. * L' ''indirizzo di scheda'' associato dal nodo ''b'' a detta interfaccia. * L'interfaccia di rete di ''a''. * Il costo dell'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. == 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. 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. 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. Quando un nodo gestisce più di una interfaccia di rete si ipotizza sempre che siano collegate a diversi domini di collisione o per lo meno ad uno switch. 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. == 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. 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. Ogni nodo ha sempre una e una sola ''identità principale''. L'identità principale del nodo non è sempre la stessa: il nodo può in un certo momento creare una nuova identità e farla diventare la sua principale. L'identità principale (i concetti espressi in questa frase verranno chiariti in seguito nel documento) è quella che gestisce le interfacce di rete ''reali'' del nodo nel ''network namespace default'', ognuna con il suo indirizzo di scheda. Il modulo Neighborhood non ha conoscenza diretta di quali siano le ''identità'' che vivono nel suo nodo in un dato momento. 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. Chiamiamo il primo tipo un ''modulo consapevole di identità'' o ''modulo di identità'' o ''identity-aware''. Il secondo tipo è un ''modulo di nodo'' o ''whole-node''. Di norma in un modulo ''di identità'' c'è una classe di cui viene creata una specifica istanza per ogni ''identità'' che il nodo assume. 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 dal discriminatore contenuto in un messaggio ricevuto (cioè dall'oggetto !CallerInfo); questi dispatcher, se il modulo da chiamare è un ''modulo di identità'', devono saper indirizzare ognuno una precisa istanza del modulo da chiamare. Ricordiamo che l'oggetto !CallerInfo è fornito dalla libreria di livello intermedio del framework ZCD prodotta con "rpcdesign" e che contiene queste informazioni: * Un ISourceID. * Un IUnicastID o un IBroadcastID. * L'indirizzo IP che ha trasmesso il pacchetto. Il modulo Neighborhood lato client farà in modo che nei messaggi trasmessi ai diretti vicini (quindi sempre per i messaggi trasmessi in UDP), questo indirizzo identifichi univocamente una interfaccia di rete del nodo che trasmette. Il nodo che riceve riconosce questo indirizzo se ha già realizzato un arco con quella interfaccia. * Nel caso di pacchetti TCP, l'indirizzo IP usato come destinazione. Il modulo Neighborhood lato client farà in modo che nei messaggi trasmessi ai diretti vicini, questo indirizzo identifichi univocamente una interfaccia di rete del nodo destinatario. * Nel caso di pacchetti UDP, l'interfaccia di rete del nodo da cui è stato ricevuto il messaggio. Nei casi in cui il modulo che vuole comunicare è ''di identità'', il NodeID che identifica una ''identità'' di un nodo è una parte essenziale delle classi che si usano come ISourceID, IUnicastID e IBroadcastID nella produzione di stub per chiamare metodi remoti. Facciamo un esempio di un modulo ''di identità'' (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,,-~'', passando attraverso l'arco ''x''. Il nodo ''a'' chiama un metodo di Neighborhood per produrre uno stub di tipo ''identity_aware_unicast''. In questo metodo il nodo passa un INeighborhoodArc che identifica l'arco ''x'' (questo è l'oggetto fornito dal modulo Neighborhood al suo esterno per rappresentare uno specifico arco formato con un diretto vicino). In questo oggetto è identificata l'interfaccia di rete di ''a'' e il MAC address dell'interfaccia di rete di ''b''. Inoltre in questo metodo il nodo passa il NodeID di ''a~-,,0,,-~'' e questo sarà usato dal modulo Neighborhood per produrre un IdentityAwareSourceID. Infine in questo metodo il nodo passa il NodeID di ''b~-,,0,,-~'' e questo sarà usato dal modulo Neighborhood insieme al MAC address dell'interfaccia di rete di ''b'' per produrre un IdentityAwareUnicastID. Lo stub prodotto in questo modo trasmetterà su una sola interfaccia di rete, univocamente individuata dal INeighborhoodArc passato al metodo. Per il lato server vedremo sotto come il nodo ''b'' aveva istruito il suo modulo Neighborhood perché potesse gestire questo tipo di IUnicastID. Nelle trasmissioni ''unicast'' è sempre individuato un solo arco tra ''a'' e ''b''. Infatti il messaggio è trasmesso solo su una interfaccia di ''a'' e contiene come IUnicastID un oggetto che individua uno specifico MAC address di ''b''. Quindi il nodo ''b'' esamina il messaggio solo quando lo legge da quella particolare interfaccia e non dalle altre. In particolare, nelle trasmissioni ''identity_aware_unicast'' abbiamo aggiunto come informazioni le identità ''a~-,,0,,-~'' e ''b~-,,0,,-~''; grazie a queste il nodo ricevente ''b'' (non il modulo Neighborhood, bensì il suo utilizzatore) è in grado di identificare la specifica istanza del ''modulo di identità'' da coinvolgere e anche di sapere (grazie a delle associazioni che vedremo meglio sotto) se esiste quello specifico ''arco-identità'' così individuato. Facciamo un altro esempio di un modulo ''di identità'' 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. Lo stub prodotto in questo modo trasmetterà su tutte le interfacce di rete gestite dal nodo ''a''. 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 IBroadcastID. Nelle trasmissioni ''broadcast'' non sono individuati specifici archi tra ''a'' e gli altri nodi. Infatti il messaggio è trasmesso su tutte le interfacce di ''a'' e contiene come IBroadcastID un oggetto che non individua gli specifici MAC address dei destinatari. Guardiamo cosa succede alla ricezione da parte di uno dei nodi, diciamo ''b''. Il nodo ''b'' quando riceve il messaggio trasmesso dalla interfaccia ''eth0(a)'' del nodo ''a'' leggendolo dalla sua interfaccia ''eth0(b)'', se esiste l'arco ''eth0(a)-eth0(b)'', non può fare altro che esaminarlo. In particolare, nelle trasmissioni ''identity_aware_broadcast'' abbiamo aggiunto come informazioni le identità ''a~-,,0,,-~'' e ''b~-,,0,,-~'' e ''b~-,,1,,-~''. Il nodo ''b'' se l'arco ''eth0(a)-eth0(b)'' è associato a un ''arco-identità'' ''a~-,,0,,-~-b~-,,0,,-~'' deve processarlo su questo ''arco-identità''. Allo stesso modo se l'arco ''eth0(a)-eth0(b)'' è associato anche su un ''arco-identità'' ''a~-,,0,,-~-b~-,,1,,-~'' deve processarlo anche su questo ''arco-identità''. Se i nodi ''a'' e ''b'' hanno più di un arco che li collega, questa cosa può ripetersi per diverse volte a seguito di un solo messaggio broadcast, ad esempio anche con ''wlan0(a)'' e ''wlan0(b)''. Nei casi in cui il modulo che vuole comunicare è ''di nodo'', il NeigborhoodNodeID costituisce la parte essenziale delle classi che si usano come ISourceID, IUnicastID e IBroadcastID nella produzione di stub per chiamare metodi remoti. Facciamo un esempio di un modulo ''di nodo'' in cui un nodo ''a'' vuole chiamare un metodo remoto su un nodo diretto vicino ''b'', passando attraverso l'arco ''x''. Il nodo ''a'' chiama un metodo di Neighborhood per produrre uno stub di tipo ''whole_node_unicast''. In questo metodo il nodo passa un INeighborhoodArc che identifica l'arco ''x'' in cui è identificata l'interfaccia di rete di ''a'' e il MAC address dell'interfaccia di rete di ''b''. Il modulo Neighborhood usa il suo NeigborhoodNodeID per produrre un WholeNodeSourceID. Inoltre dal INeighborhoodArc ''x'' si può reperire il NeighborhoodNodeID di ''b''; questo sarà usato dal modulo Neighborhood insieme al MAC address dell'interfaccia di rete di ''b'' per produrre un WholeNodeUnicastID. Lo stub prodotto in questo modo trasmetterà su una sola interfaccia di rete, univocamente individuata dal INeighborhoodArc passato al metodo. Per il lato server vedremo sotto come il nodo ''b'' aveva istruito il suo modulo Neighborhood perché potesse gestire questo tipo di IUnicastID. Come detto prima, nelle trasmissioni ''unicast'' è sempre individuato un solo arco tra ''a'' e ''b''. Quindi il nodo ''b'' esamina il messaggio solo una volta. Facciamo un altro esempio di un modulo ''di nodo'' 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 (senza guardare i MAC address) saranno usati dal modulo Neighborhood per produrre un WholeNodeBroadcastID. Lo stub prodotto in questo modo trasmetterà su tutte le interfacce di rete gestite dal nodo ''a''. 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 IBroadcastID. Come detto prima, nelle trasmissioni ''broadcast'' non sono individuati specifici archi tra ''a'' e gli altri nodi. Quindi ogni nodo che riceve il messaggio potrebbe esaminarlo diverse volte. Diciamo quindi che, soprattutto per i ''moduli di nodo'', le trasmissioni broadcast verso un set di nodi avvengono sempre su tutti gli archi esistenti. Ovviamente anche lo stesso modulo Neighborhood, il quale è di per se un modulo ''di nodo'', 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. Esaminiamo cosa avviene lato server. Una descrizione passo passo è presente in questo [[Netsukuku/ita/docs/ModuloNeighborhood/ChiamateLatoServer|documento]]. Abbiamo anticipato che, per gestire le comunicazioni lato server, all'inizio della sua attività il modulo Neighborhood deve essere istruito su come gestire le chiamate che riceve. L'utilizzatore del modulo dovrà dire al Neighborhood: * se ricevi un IUnicastID di tipo IdentityAwareUnicastID: * usa questa callback . IAddressManagerSkeleton? ''get_identity_skeleton(NodeID source_id, NodeID unicast_id, string peer_address, string? dev)'' . la quale restituirà <null> oppure un root-dispatcher che potrà accedere solo i moduli ''di identità'', e in particolare la istanza indicata nell'IdentityAwareUnicastID. * se ricevi un IBroadcastID di tipo IdentityAwareBroadcastID: * usa questa callback . Gee.List<IAddressManagerSkeleton> ''get_identity_skeleton_set(NodeID source_id, Gee.List<NodeID> broadcast_set, string peer_address, string dev)'' . la quale restituirà una lista (che può essere vuota) di root-dispatcher ognuno dei quali potrà accedere solo i moduli ''di identità'', e in particolare una precisa istanza. Nell'insieme questi dispatcher copriranno tutte le ''identità'' che sono presenti nel IdentityAwareBroadcastID e vivono in questo nodo. * se ricevi un IUnicastID di tipo WholeNodeUnicastID e ti riconosci come destinatario: * usa questa istanza . IAddressManagerSkeleton ''node_skeleton'' . la quale è un root-dispatcher che potrà accedere solo i moduli ''di nodo''. * se ricevi un IBroadcastID di tipo WholeNodeBroadcastID e ti riconosci fra i destinatari: * usa ancora l'istanza ''node_skeleton''. 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. Grazie al framework ZCD, quando un metodo remoto viene invocato, l'implementazione del metodo riceve un oggetto chiamato ''!CallerInfo''. Con tale oggetto esso può identificare attraverso quale ''arco-nodo'' o ''arco-identità'' tale messaggio è pervenuto. Per realizzare questa associazione il modulo Neighborhood deve fornire essenzialmente due metodi: * ''NodeID get_identity(ISourceID source_id)'' - Solo per i messaggi destinati a moduli ''di identità'', dato l'identificativo del mittente di un messaggio ricevuto restituisce il NodeID dell' ''identità'' del mittente. Tale dato è contenuto nella classe IdentityAwareSourceID, che è interna al modulo Neighborhood. * ''INeighborhoodArc get_node_arc(ISourceID source_id, string dev)'' - Solo per i messaggi destinati a moduli ''di nodo'', dato l'identificativo del mittente di un messaggio ricevuto e il nome dell'interfaccia di rete su cui il messaggio è stato ricevuto, restituisce l'istanza di arco (INeighborhoodArc). Nella classe WholeNodeSourceID, che è interna al modulo Neighborhood, è contenuto il NeighborhoodNodeID. . Se non è possibile associare questi dati ad un arco, il modulo Neighborhood restituisce null. In teoria questo caso si può verificare solo se un ''arco-nodo'' prima valido (altrimenti il metodo remoto non doveva essere affatto invocato) è stato rimosso pochi istanti prima. In questo caso l'esecuzione del metodo andrebbe "probabilmente" interrotta, ma questo è di pertinenza del codice che implementa il metodo remoto. === Costituzione della prima identità - Associazioni mantenute dal nodo === Con il termine nodo indichiamo l'utilizzatore del modulo Neighborhood. Quando il nodo ''a'' inizia l'attività, assume una ''identità'' che è la sua ''identità principale''. Chiamiamola ''a~-,,0,,-~''. Di fatto questo significa che crea un NodeID e crea una prima istanza di ogni modulo ''di identità''. Il nodo mantiene una associazione ''ns'' tra questa identità e il network namespace default. Lo indichiamo dicendo ''ns(a~-,,0,,-~) = ""''. La stringa vuota rappresenta il network namespace default, altrimenti avremmo il nome del network namespace. Il nodo mantiene una associazione ''in'' tra questa identità e un'altra associazione. Questa associazione interna ''in(a~-,,0,,-~)'' (interfacce gestite da ''a~-,,0,,-~'') è tra ogni interfaccia di rete reale gestita dal nodo e una struttura dati che rappresenta l'interfaccia gestita dall'identità. Ad esempio: costruiamo la struttura dati ''n'' che identifica l'interfaccia "eth0". Abbiamo questi membri: * ''n.dev = "eth0"''. * ''n.mac = "02:AF:78:2E:C8:B6"''. * ''n.linklocal = "169.254.201.13"''. e poi diciamo che questa struttura è associata all'interfaccia reale "eth0" come viene gestita da ''a~-,,0,,-~'': * ''in(a~-,,0,,-~)("eth0") = n''. Per la ''identità principale a~-,,p,,-~'' abbiamo sempre che ''in(a~-,,p,,-~)("xyz").dev = "xyz"''. Cioè la ''identità principale'' gestisce le interfacce reali che sono nel network namespace default. Quando il modulo Neighborhood forma un arco ''i'', questo rappresenta un collegamento tra una interfaccia di rete reale di ''a'' e una interfaccia di rete reale del nodo collegato, chiamiamolo ''b''. Da questo momento il nodo mantiene una associazione ''f'' tra la coppia ''a~-,,0,,-~-i'' e un set di identità nel nodo ''b''. Dopo aver formato l'arco ''i'' il nodo viene a conoscenza (come lo fa non è di pertinenza del modulo Neighborhood) che sopra questo arco devono appoggiarsi ''n'' ''archi-identità'' tra ''a~-,,0,,-~'' e le identità ''b~-,,0,,-~'' ... ''b~-,,n-1,,-~''. Viene a conoscere inoltre per ognuna di queste identità di ''b'', sempre relativamente all'arco ''i'', un MAC-address e un indirizzo di scheda. Memorizza tutti questi dati nell'associazione ''f''. Riassumendo, ''f(a~-,,0,,-~-i)'' è un set di oggetti che chiamiamo ''arco-identità''. Un suo elemento, diciamo ''w'', rappresenta l'arco ''a~-,,0,,-~-b~-,,j,,-~'' che poggia sull'arco ''i''. L'elemento ''w'' contiene: * ''w.peer_nodeid'' - Il NodeID di ''b~-,,j,,-~''. * ''w.peer_mac'' - Il MAC address dell'interfaccia gestita da ''b~-,,j,,-~''. Se ''b~-,,j,,-~'' è la ''identità principale'' del nodo ''b'' allora questo MAC address risulta essere lo stesso che si può reperire dall'oggetto arco ''i''. * ''w.peer_linklocal'' - L' ''indirizzo di scheda'' dell'interfaccia gestita da ''b~-,,j,,-~''. Se ''b~-,,j,,-~'' è la ''identità principale'' del nodo ''b'' allora questo ''indirizzo di scheda'' risulta essere lo stesso che si può reperire dall'oggetto arco ''i''. === 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. . 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. 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 ''di identità'', in particolare il modulo QSPN. * Se ''a~-,,0,,-~'' era la ''identità principale'', allora ''a~-,,1,,-~'' diventa la ''identità principale''. * L'utilizzatore del modulo manteneva l'associazione ''ns'' tra l'identità ''a~-,,0,,-~'' e un network namespace ''n~-,,old,,-~''. Se ''a~-,,0,,-~'' era la ''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 gestite 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)''. * Crea una nuova pseudo-interfaccia ''pr'' basata su ''r'' nel namespace ''ns(a~-,,0,,-~)''. * Crea un nuovo ''indirizzo di scheda'' ''addr_pr'' e assegnalo a ''pr'' * ''in(a~-,,0,,-~)(r)'' = new ''struttura_dati()''. * ''in(a~-,,0,,-~)(r).dev'' = ''pr''. * ''in(a~-,,0,,-~)(r).mac'' = ''pr.mac''. * ''in(a~-,,0,,-~)(r).linklocal'' = ''addr_pr''. * 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 tra il nodo ''a'' e un altro nodo) e un set di identità nel nodo 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 del nodo collegato. Sia ''b'' questo nodo. 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)'' bensì una pseudo-interfaccia. Questa si appoggia su ''a(i)'' 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_nodeid'' - Il NodeID di ''b~-,,j,,-~''. * ''peer_mac'' - Il MAC address dell'interfaccia (''b(i)'' o una pseudo che si appoggia su ''b(i)'') gestita da ''b~-,,j,,-~''. * ''peer_linklocal'' - L' ''indirizzo di scheda'' dell'interfaccia (''b(i)'' o una pseudo che si appoggia su ''b(i)'') 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. Quindi abbiamo una stretta relazione tra questa struttura dati ''w~-,,1,,-~'' e l'oggetto IQspnArc. Si potrebbe ipotizzare che è questa struttura a implementare l'interfaccia IQspnArc e ad essere passata al !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. * Il nodo ''a'' deve comunicare al nodo ''b'' questi nuovi dati riguardo gli estremi (mac e linklocal) dell' ''arco-identità'' ''a~-,,0,,-~''-''b~-,,j,,-~''. * Il nodo ''a'' deve chiedere al nodo ''b'' se l'identità ''b~-,,j,,-~'' ha partecipato anch'essa alla migrazione. Se sì, il nodo ''b'' risponde comunicando i dati ''new_peer_mac'' e ''new_peer_linklocal'' che sono riferiti alla nuova interfaccia che il nodo ''b'' ha creato e che è gestita ora da ''b~-,,j,,-~''. Comunica anche il NodeID della nuova identità frutto della migrazione di ''b~-,,j,,-~'', chiamiamola ''b~-,,k,,-~'', che ora gestisce la vecchia interfaccia del nodo ''b'' che prima era gestita da ''b~-,,j,,-~''. * Se ''b~-,,j,,-~'' ha partecipato alla migrazione: * Il nodo ''a'' deve richiedere al nodo ''b'' di formare un nuovo ''arco-identità'' ''a~-,,1,,-~''-''b~-,,k,,-~''. Le informazioni che passa sono: * Il NodeID di ''a~-,,1,,-~''. * Il NodeID di ''b~-,,k,,-~''. * I dati ''in(a~-,,1,,-~)(a(i)).mac'' e ''in(a~-,,1,,-~)(a(i)).linklocal''. * 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''. * Cambia i dati dell'arco assegnato ad ''a~-,,1,,-~'' relativamente alla ''identità'' remota: * ''w~-,,1,,-~.peer_nodeid'' = Il NodeID di ''b~-,,k,,-~''. * Altrimenti: * Il nodo ''a'' deve richiedere al nodo ''b'' di formare un nuovo ''arco-identità'' ''a~-,,1,,-~''-''b~-,,j,,-~''. Le informazioni che passa sono: * Il NodeID di ''a~-,,1,,-~''. * Il NodeID di ''b~-,,j,,-~''. * I dati ''in(a~-,,1,,-~)(a(i)).mac'' e ''in(a~-,,1,,-~)(a(i)).linklocal''. 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 moduli ''di identità'' per comunicare dalla sua nuova identità ad uno o più diretti vicini. Resta anche in grado, dato un messaggio ricevuto che è per moduli ''di identità'', 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 ''i'' (o più di uno) alla identità del nodo corrente ''a~-,,k,,-~'', la quale non è cambiata. Sia ''w'' l' ''arco-identità'' ''a~-,,k,,-~''-''b~-,,0,,-~'' che si appoggia su ''i''. * Il nodo ''a'' riceve sull'arco ''i'' la comunicazione da parte del nodo ''b'' che sono cambiati gli estremi (''new_peer_mac'' e ''new_peer_linklocal'') dell' ''arco-identità'' ''a~-,,k,,-~''-''b~-,,0,,-~''. * Cerca nell'elenco che ha nell'associazione ''f(a~-,,k,,-~-i)'' l' ''arco-identità'' ''w'' che lo collega a ''b~-,,0,,-~''. * Memorizza ''old_peer_linklocal = w.peer_linklocal''. * Aggiunge sulle tabelle di routing di ''ns(a~-,,k,,-~)'' la rotta che collega ''in(a~-,,k,,-~)(i).linklocal'' a ''new_peer_linklocal''. * Aggiorna tutte le rotte sulle tabelle di routing di ''ns(a~-,,k,,-~)'' che usano ''w'' come gateway. Ora dovranno avere ''new_peer_linklocal''. * Elimina dalle tabelle di routing di ''ns(a~-,,k,,-~)'' la rotta che collegava ''in(a~-,,k,,-~)(i).linklocal'' a ''old_peer_linklocal''. * Aggiorna i dati: * ''w.peer_mac = new_peer_mac''. * ''w.peer_linklocal = new_peer_linklocal''. * Il nodo ''a'' riceve la domanda da parte del nodo ''b'' se l'identità ''a~-,,k,,-~'' ha partecipato ad una migrazione. Risponde di no. * Il nodo ''a'' riceve la richiesta da parte del nodo ''b'' di formare un nuovo ''arco-identità'' ''a~-,,k,,-~''-''b~-,,1,,-~''. Le informazioni che riceve sono: * Il NodeID di ''a~-,,k,,-~''. * Il NodeID di ''b~-,,1,,-~''. * I dati ''peer_mac'' e ''peer_linklocal'' che sono riferiti alla interfaccia che il nodo ''b'' ha assegnato a ''b~-,,1,,-~'' per l'arco ''i''. * L'utilizzatore del modulo Neighborhood crea un nuovo ''arco-identità'' con questi dati e lo aggiunge all'elenco che ha nell'associazione ''f(a~-,,k,,-~-i)''. 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 moduli ''di identità'' per comunicare da una sua identità alla nuova identità del vicino ''b''. Resta anche in grado, dato un messaggio ricevuto che è per moduli ''di identità'', di identificare, per mezzo delle callback ricevute all'inizio, se è per una sua identità da parte della nuova identità del vicino ''b''. === Rimozione di un arco-identità === In un certo momento, l'utilizzatore del modulo decide che una certa ''identità'' del nodo corrente ''a~-,,k,,-~'' non deve avere più archi verso una certa ''identità'' di un suo vicino ''b~-,,j,,-~''; quindi per ogni arco che il modulo Neighborhood aveva creato tra ''a'' e ''b'', l'utilizzatore del modulo fa alcune operazioni per rimuovere gli ''archi-identità'' ''a~-,,k,,-~''-''b~-,,j,,-~''. In precedenza l'utilizzatore del modulo si era dovuto occupare di rimuovere (o cambiare) tutte le rotte che usavano quell'arco come gateway. * Per ogni arco ''i'': * Il nodo ''a'' comunica sull'arco ''i'' al nodo ''b'' che sta rimuovendo il suo ''arco-identità'' ''a~-,,k,,-~''-''b~-,,j,,-~'' sull'arco ''i'' affinché anche il nodo ''b'' apporti le modifiche alle sue associazioni. * Cerca nell'elenco che ha nell'associazione ''f(a~-,,k,,-~-i)'' l' ''arco-identità'' ''w'' che lo collega a ''b~-,,j,,-~''. * Elimina dalle tabelle di routing di ''ns(a~-,,k,,-~)'' la rotta che collegava ''in(a~-,,k,,-~)(i).linklocal'' a ''w.peer_linklocal''. * Elimina ''w'' dall'elenco ''f(a~-,,k,,-~-i)''. Tutte queste operazioni non coinvolgono direttamente il modulo Neighborhood. |
Line 44: | Line 270: |
* Identificativo del proprio nodo. * 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. |
* Implementazione del sistema di tasklet. * Delegati per la gestione delle chiamate ricevute: * Una callback per gestire gli IdentityAwareUnicastID. * Una callback per gestire gli IdentityAwareBroadcastID. * Uno skeleton del root-dispatcher per gestire gli WholeNodeUnicastID e gli WholeNodeBroadcastID. * 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 278: |
* 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. |
* Factory per creare uno "stub" per invocare metodi remoti nei nodi vicini. * Manager di indirizzi e rotte. |
Line 61: | Line 282: |
Line 63: | Line 283: |
* 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. |
* Avvenuta assegnazione dell' ''indirizzo di scheda'' ad una interfaccia di rete gestita. * Costituzione di un arco. Significa anche avvenuto inserimento della rotta nelle tabelle. * Rimozione di un arco. Significa anche avvenuta rimozione della rotta nelle tabelle. * Variazione del costo di un arco. * Avvenuta rimozione dell' ''indirizzo di scheda'' ad una interfaccia di rete che non si gestisce più. * Fornisce metodi per: * ''current_arcs'' - Ottenere l'elenco degli archi ora presenti. * ''get_dispatcher'' - Dato l'identificativo del destinatario di un messaggio in unicast ricevuto, cioè una istanza di IUnicastID, e dato il nome dell'interfaccia su cui è stato ricevuto (oppure null se il protocollo era TCP) ottenere una istanza di skeleton del root-dispatcher se il messaggio è da processare, oppure null. Nel caso di protocollo UDP, il nome dell'interfaccia è necessario perché il messaggio sia processato solo quando è ricevuto dalla corretta interfaccia. Infatti in questo caso l'oggetto IUnicastID contiene anche il MAC address a cui il messaggio è destinato. * ''get_dispatcher_set'' - Dato l'identificativo dei destinatari di un messaggio in broadcast ricevuto, cioè una istanza di IBroadcastID, ottenere una lista (che può essere vuota) di istanze di skeleton del root-dispatcher: il messaggio è da processare su ognuna di queste istanze. * ''get_identity'' - Solo per i messaggi destinati a moduli ''di identità'', dato l'identificativo del mittente di un messaggio ricevuto, cioè una istanza di ISourceID, ottenere il NodeID dell' ''identità'' del mittente. Se non è possibile ottenerlo, restituisce null. * ''get_node_arc'' - Solo per i messaggi destinati a moduli ''di nodo'', dato l'identificativo del mittente di un messaggio ricevuto, cioè una istanza di ISourceID, e il nome dell'interfaccia di rete su cui il messaggio è stato ricevuto, ottenere l'istanza di arco (INeighborhoodArc). Se non è possibile ottenerlo, restituisce null. * ''get_stub_identity_aware_unicast'' - Dato un arco che collega questo nodo, chiamiamolo ''a'', ad un altro nodo vicino, chiamiamolo ''b'', dato l'identificativo di una ''identità'' che risiede in ''a'', chiamiamola ''a~-,,k,,-~'', dato l'identificativo di una ''identità'' che risiede in ''b'', chiamiamola ''b~-,,j,,-~'', ottenere un oggetto stub utilizzabile per chiamare un metodo remoto su un modulo ''di identità'' da ''a~-,,k,,-~'' a ''b~-,,j,,-~''. Questo stub dialoga con il nodo remoto con protocollo reliable. * ''get_stub_whole_node_unicast'' - Dato un arco che collega questo nodo, chiamiamolo ''a'', ad un altro nodo vicino, chiamiamolo ''b'', ottenere un oggetto stub utilizzabile per chiamare un metodo remoto su un modulo ''di nodo'' da ''a'' a ''b''. Questo stub dialoga con il nodo remoto con protocollo reliable. * ''get_stub_identity_aware_broadcast'' - Dato l'identificativo di una ''identità'' che risiede in ''a'', chiamiamola ''a~-,,k,,-~'', dato un set di identificativi di ''identità'' che risiedono in alcuni nodi vicini, ottenere un oggetto stub utilizzabile per inviare un messaggio in broadcast destinato a un modulo ''di identità'' di queste ''identità''. . 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 riprovando con diverse chiamate unicast reliable. Si noti che in questo caso ad un arco passato alla callback possono corrispondere diversi ''archi-identità''. * ''get_stub_whole_node_broadcast'' - Dato un set di archi che collegano ad alcuni nodi vicini, ottenere un oggetto stub utilizzabile per inviare un messaggio in broadcast destinato a un modulo ''di nodo'' di questi nodi vicini. . Il modulo produrrà una istanza di IBroadcastID che indicherà come destinatari i nodi che sono identificati dagli archi che sono stati passati a questo metodo. . Il modulo poi 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 e non reliable: si veda la spiegazione del metodo precedente. Tuttavia in questo caso ad un arco passato alla callback non corrispondono diversi ''archi-identità''. * Forzare la rimozione di un arco. |
Line 76: | Line 306: |
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 ITasklet 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à ''dev''). * Leggere il MAC address dell'interfaccia di rete, es: CC:AF:78:2E:C8:B6 (proprietà ''mac''). * Misurare il round-trip time (la latenza) con un vicino (metodo ''measure_rtt''). ---- 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 IAckCommunicator se vuole ricevere dopo il timeout la lista dei MAC address che hanno segnalato con un ACK la ricezione del messaggio. Tale interfaccia è fornita dalla libreria di livello intermedio ''ntkdrpc'' prodotta per usare il framework ZCD. * 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 IUnicastID 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. ---- La classe usata per l'identificativo di una identità, cioè NodeID, è definita nella libreria [[Netsukuku/ita/docs/Librerie/Common|Common]]. Il modulo Neighborhood ha una dipendenza su questa libreria, quindi conosce tale classe. La conoscenza del modulo Neighborhood relativamente a tale classe si limita al fatto di sapere che essa è serializzabile secondo la modalità usata in !JsonGlib. ---- La classe usata per l'identificativo di un nodo, cioè NeighborhoodNodeID, è interna al modulo Neighborhood. Anche essa è serializzabile secondo la modalità usata in !JsonGlib. ---- 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. 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 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'). * 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
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.
Un arco associa una specifica interfaccia di rete del nodo corrente con una specifica interfaccia di rete di un altro nodo diretto vicino. Tecnicamente, con il termine diretto vicino si intende che le due interfacce di rete suddette sono collegate ad un comune dominio broadcast.
Come vedremo in seguito, non sono ammessi due archi che partendo da una stessa interfaccia di rete del nodo corrente colleghino a due distinte interfacce di rete di uno stesso nodo diretto vicino. Per questo ogni arco deve identificare univocamente non soltanto la specifica interfaccia di rete di un vicino, ma anche il nodo vicino come entità nel suo insieme.
L'identificativo di un nodo vicino è chiamato NeigborhoodNodeID. Esso è un concetto interno al modulo Neighborhood. Riassumendo, ogni arco associa una interfaccia di rete del nodo corrente ad una coppia composta dal NeigborhoodNodeID del vicino e dal MAC address della sua interfaccia.
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.
Operazioni di base
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 alla libreria di livello intermedio ntkdrpc 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 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.
L' indirizzo di scheda associato dal nodo b a detta interfaccia.
L'interfaccia di rete di a.
- Il costo dell'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.
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.
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. 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. Quando un nodo gestisce più di una interfaccia di rete si ipotizza sempre che siano collegate a diversi domini di collisione o per lo meno ad uno switch. 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.
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.
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.
Ogni nodo ha sempre una e una sola identità principale. L'identità principale del nodo non è sempre la stessa: il nodo può in un certo momento creare una nuova identità e farla diventare la sua principale. L'identità principale (i concetti espressi in questa frase verranno chiariti in seguito nel documento) è quella che gestisce le interfacce di rete reali del nodo nel network namespace default, ognuna con il suo indirizzo di scheda.
Il modulo Neighborhood non ha conoscenza diretta di quali siano le identità che vivono nel suo nodo in un dato momento.
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. Chiamiamo il primo tipo un modulo consapevole di identità o modulo di identità o identity-aware. Il secondo tipo è un modulo di nodo o whole-node. Di norma in un modulo di identità c'è una classe di cui viene creata una specifica istanza per ogni identità che il nodo assume.
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 dal discriminatore contenuto in un messaggio ricevuto (cioè dall'oggetto CallerInfo); questi dispatcher, se il modulo da chiamare è un modulo di identità, devono saper indirizzare ognuno una precisa istanza del modulo da chiamare. Ricordiamo che l'oggetto CallerInfo è fornito dalla libreria di livello intermedio del framework ZCD prodotta con "rpcdesign" e che contiene queste informazioni:
- Un ISourceID.
- Un IUnicastID o un IBroadcastID.
- L'indirizzo IP che ha trasmesso il pacchetto. Il modulo Neighborhood lato client farà in modo che nei messaggi trasmessi ai diretti vicini (quindi sempre per i messaggi trasmessi in UDP), questo indirizzo identifichi univocamente una interfaccia di rete del nodo che trasmette. Il nodo che riceve riconosce questo indirizzo se ha già realizzato un arco con quella interfaccia.
- Nel caso di pacchetti TCP, l'indirizzo IP usato come destinazione. Il modulo Neighborhood lato client farà in modo che nei messaggi trasmessi ai diretti vicini, questo indirizzo identifichi univocamente una interfaccia di rete del nodo destinatario.
- Nel caso di pacchetti UDP, l'interfaccia di rete del nodo da cui è stato ricevuto il messaggio.
Nei casi in cui il modulo che vuole comunicare è di identità, il NodeID che identifica una identità di un nodo è una parte essenziale delle classi che si usano come ISourceID, IUnicastID e IBroadcastID nella produzione di stub per chiamare metodi remoti.
Facciamo un esempio di un modulo di identità (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, passando attraverso l'arco x. Il nodo a chiama un metodo di Neighborhood per produrre uno stub di tipo identity_aware_unicast. In questo metodo il nodo passa un INeighborhoodArc che identifica l'arco x (questo è l'oggetto fornito dal modulo Neighborhood al suo esterno per rappresentare uno specifico arco formato con un diretto vicino). In questo oggetto è identificata l'interfaccia di rete di a e il MAC address dell'interfaccia di rete di b. Inoltre in questo metodo il nodo passa il NodeID di a0 e questo sarà usato dal modulo Neighborhood per produrre un IdentityAwareSourceID. Infine in questo metodo il nodo passa il NodeID di b0 e questo sarà usato dal modulo Neighborhood insieme al MAC address dell'interfaccia di rete di b per produrre un IdentityAwareUnicastID. Lo stub prodotto in questo modo trasmetterà su una sola interfaccia di rete, univocamente individuata dal INeighborhoodArc passato al metodo. Per il lato server vedremo sotto come il nodo b aveva istruito il suo modulo Neighborhood perché potesse gestire questo tipo di IUnicastID.
Nelle trasmissioni unicast è sempre individuato un solo arco tra a e b. Infatti il messaggio è trasmesso solo su una interfaccia di a e contiene come IUnicastID un oggetto che individua uno specifico MAC address di b. Quindi il nodo b esamina il messaggio solo quando lo legge da quella particolare interfaccia e non dalle altre. In particolare, nelle trasmissioni identity_aware_unicast abbiamo aggiunto come informazioni le identità a0 e b0; grazie a queste il nodo ricevente b (non il modulo Neighborhood, bensì il suo utilizzatore) è in grado di identificare la specifica istanza del modulo di identità da coinvolgere e anche di sapere (grazie a delle associazioni che vedremo meglio sotto) se esiste quello specifico arco-identità così individuato.
Facciamo un altro esempio di un modulo di identità 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. Lo stub prodotto in questo modo trasmetterà su tutte le interfacce di rete gestite dal nodo a. 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 IBroadcastID.
Nelle trasmissioni broadcast non sono individuati specifici archi tra a e gli altri nodi. Infatti il messaggio è trasmesso su tutte le interfacce di a e contiene come IBroadcastID un oggetto che non individua gli specifici MAC address dei destinatari. Guardiamo cosa succede alla ricezione da parte di uno dei nodi, diciamo b. Il nodo b quando riceve il messaggio trasmesso dalla interfaccia eth0(a) del nodo a leggendolo dalla sua interfaccia eth0(b), se esiste l'arco eth0(a)-eth0(b), non può fare altro che esaminarlo. In particolare, nelle trasmissioni identity_aware_broadcast abbiamo aggiunto come informazioni le identità a0 e b0 e b1. Il nodo b se l'arco eth0(a)-eth0(b) è associato a un arco-identità a0-b0 deve processarlo su questo arco-identità. Allo stesso modo se l'arco eth0(a)-eth0(b) è associato anche su un arco-identità a0-b1 deve processarlo anche su questo arco-identità. Se i nodi a e b hanno più di un arco che li collega, questa cosa può ripetersi per diverse volte a seguito di un solo messaggio broadcast, ad esempio anche con wlan0(a) e wlan0(b).
Nei casi in cui il modulo che vuole comunicare è di nodo, il NeigborhoodNodeID costituisce la parte essenziale delle classi che si usano come ISourceID, IUnicastID e IBroadcastID nella produzione di stub per chiamare metodi remoti.
Facciamo un esempio di un modulo di nodo in cui un nodo a vuole chiamare un metodo remoto su un nodo diretto vicino b, passando attraverso l'arco x. Il nodo a chiama un metodo di Neighborhood per produrre uno stub di tipo whole_node_unicast. In questo metodo il nodo passa un INeighborhoodArc che identifica l'arco x in cui è identificata l'interfaccia di rete di a e il MAC address dell'interfaccia di rete di b. Il modulo Neighborhood usa il suo NeigborhoodNodeID per produrre un WholeNodeSourceID. Inoltre dal INeighborhoodArc x si può reperire il NeighborhoodNodeID di b; questo sarà usato dal modulo Neighborhood insieme al MAC address dell'interfaccia di rete di b per produrre un WholeNodeUnicastID. Lo stub prodotto in questo modo trasmetterà su una sola interfaccia di rete, univocamente individuata dal INeighborhoodArc passato al metodo. Per il lato server vedremo sotto come il nodo b aveva istruito il suo modulo Neighborhood perché potesse gestire questo tipo di IUnicastID.
Come detto prima, nelle trasmissioni unicast è sempre individuato un solo arco tra a e b. Quindi il nodo b esamina il messaggio solo una volta.
Facciamo un altro esempio di un modulo di nodo 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 (senza guardare i MAC address) saranno usati dal modulo Neighborhood per produrre un WholeNodeBroadcastID. Lo stub prodotto in questo modo trasmetterà su tutte le interfacce di rete gestite dal nodo a. 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 IBroadcastID.
Come detto prima, nelle trasmissioni broadcast non sono individuati specifici archi tra a e gli altri nodi. Quindi ogni nodo che riceve il messaggio potrebbe esaminarlo diverse volte. Diciamo quindi che, soprattutto per i moduli di nodo, le trasmissioni broadcast verso un set di nodi avvengono sempre su tutti gli archi esistenti.
Ovviamente anche lo stesso modulo Neighborhood, il quale è di per se un modulo di nodo, 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.
Esaminiamo cosa avviene lato server. Una descrizione passo passo è presente in questo documento.
Abbiamo anticipato che, per gestire le comunicazioni lato server, all'inizio della sua attività il modulo Neighborhood deve essere istruito su come gestire le chiamate che riceve. L'utilizzatore del modulo dovrà dire al Neighborhood:
- se ricevi un IUnicastID di tipo IdentityAwareUnicastID:
- usa questa callback
IAddressManagerSkeleton? get_identity_skeleton(NodeID source_id, NodeID unicast_id, string peer_address, string? dev)
la quale restituirà <null> oppure un root-dispatcher che potrà accedere solo i moduli di identità, e in particolare la istanza indicata nell'IdentityAwareUnicastID.
- usa questa callback
- se ricevi un IBroadcastID di tipo IdentityAwareBroadcastID:
- usa questa callback
Gee.List<IAddressManagerSkeleton> get_identity_skeleton_set(NodeID source_id, Gee.List<NodeID> broadcast_set, string peer_address, string dev)
la quale restituirà una lista (che può essere vuota) di root-dispatcher ognuno dei quali potrà accedere solo i moduli di identità, e in particolare una precisa istanza. Nell'insieme questi dispatcher copriranno tutte le identità che sono presenti nel IdentityAwareBroadcastID e vivono in questo nodo.
- usa questa callback
- se ricevi un IUnicastID di tipo WholeNodeUnicastID e ti riconosci come destinatario:
- usa questa istanza
IAddressManagerSkeleton node_skeleton
la quale è un root-dispatcher che potrà accedere solo i moduli di nodo.
- usa questa istanza
- se ricevi un IBroadcastID di tipo WholeNodeBroadcastID e ti riconosci fra i destinatari:
usa ancora l'istanza node_skeleton.
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.
Grazie al framework ZCD, quando un metodo remoto viene invocato, l'implementazione del metodo riceve un oggetto chiamato CallerInfo. Con tale oggetto esso può identificare attraverso quale arco-nodo o arco-identità tale messaggio è pervenuto. Per realizzare questa associazione il modulo Neighborhood deve fornire essenzialmente due metodi:
NodeID get_identity(ISourceID source_id) - Solo per i messaggi destinati a moduli di identità, dato l'identificativo del mittente di un messaggio ricevuto restituisce il NodeID dell' identità del mittente. Tale dato è contenuto nella classe IdentityAwareSourceID, che è interna al modulo Neighborhood.
INeighborhoodArc get_node_arc(ISourceID source_id, string dev) - Solo per i messaggi destinati a moduli di nodo, dato l'identificativo del mittente di un messaggio ricevuto e il nome dell'interfaccia di rete su cui il messaggio è stato ricevuto, restituisce l'istanza di arco (INeighborhoodArc). Nella classe WholeNodeSourceID, che è interna al modulo Neighborhood, è contenuto il NeighborhoodNodeID.
Se non è possibile associare questi dati ad un arco, il modulo Neighborhood restituisce null. In teoria questo caso si può verificare solo se un arco-nodo prima valido (altrimenti il metodo remoto non doveva essere affatto invocato) è stato rimosso pochi istanti prima. In questo caso l'esecuzione del metodo andrebbe "probabilmente" interrotta, ma questo è di pertinenza del codice che implementa il metodo remoto.
Costituzione della prima identità - Associazioni mantenute dal nodo
Con il termine nodo indichiamo l'utilizzatore del modulo Neighborhood.
Quando il nodo a inizia l'attività, assume una identità che è la sua identità principale. Chiamiamola a0. Di fatto questo significa che crea un NodeID e crea una prima istanza di ogni modulo di identità.
Il nodo mantiene una associazione ns tra questa identità e il network namespace default. Lo indichiamo dicendo ns(a0) = "". La stringa vuota rappresenta il network namespace default, altrimenti avremmo il nome del network namespace.
Il nodo mantiene una associazione in tra questa identità e un'altra associazione. Questa associazione interna in(a0) (interfacce gestite da a0) è tra ogni interfaccia di rete reale gestita dal nodo e una struttura dati che rappresenta l'interfaccia gestita dall'identità.
Ad esempio: costruiamo la struttura dati n che identifica l'interfaccia "eth0". Abbiamo questi membri:
n.dev = "eth0".
n.mac = "02:AF:78:2E:C8:B6".
n.linklocal = "169.254.201.13".
e poi diciamo che questa struttura è associata all'interfaccia reale "eth0" come viene gestita da a0:
in(a0)("eth0") = n.
Per la identità principale ap abbiamo sempre che in(ap)("xyz").dev = "xyz". Cioè la identità principale gestisce le interfacce reali che sono nel network namespace default.
Quando il modulo Neighborhood forma un arco i, questo rappresenta un collegamento tra una interfaccia di rete reale di a e una interfaccia di rete reale del nodo collegato, chiamiamolo b. Da questo momento il nodo mantiene una associazione f tra la coppia a0-i e un set di identità nel nodo b.
Dopo aver formato l'arco i il nodo viene a conoscenza (come lo fa non è di pertinenza del modulo Neighborhood) che sopra questo arco devono appoggiarsi n archi-identità tra a0 e le identità b0 ... bn-1. Viene a conoscere inoltre per ognuna di queste identità di b, sempre relativamente all'arco i, un MAC-address e un indirizzo di scheda. Memorizza tutti questi dati nell'associazione f.
Riassumendo, f(a0-i) è un set di oggetti che chiamiamo arco-identità. Un suo elemento, diciamo w, rappresenta l'arco a0-bj che poggia sull'arco i. L'elemento w contiene:
w.peer_nodeid - Il NodeID di bj.
w.peer_mac - Il MAC address dell'interfaccia gestita da bj. Se bj è la identità principale del nodo b allora questo MAC address risulta essere lo stesso che si può reperire dall'oggetto arco i.
w.peer_linklocal - L' indirizzo di scheda dell'interfaccia gestita da bj. Se bj è la identità principale del nodo b allora questo indirizzo di scheda risulta essere lo stesso che si può reperire dall'oggetto arco i.
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.
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.
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 di identità, in particolare il modulo QSPN.
Se a0 era la identità principale, allora a1 diventa la identità principale.
L'utilizzatore del modulo manteneva l'associazione ns tra l'identità a0 e un network namespace nold. Se a0 era la 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 gestite 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).
Crea una nuova pseudo-interfaccia pr basata su r nel namespace ns(a0).
Crea un nuovo indirizzo di scheda addr_pr e assegnalo a pr
in(a0)(r) = new struttura_dati().
in(a0)(r).dev = pr.
in(a0)(r).mac = pr.mac.
in(a0)(r).linklocal = addr_pr.
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 tra il nodo a e un altro nodo) e un set di identità nel nodo 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 del nodo collegato. Sia b questo nodo. 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) bensì una pseudo-interfaccia. Questa si appoggia su a(i) 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_nodeid - Il NodeID di bj.
peer_mac - Il MAC address dell'interfaccia (b(i) o una pseudo che si appoggia su b(i)) gestita da bj.
peer_linklocal - L' indirizzo di scheda dell'interfaccia (b(i) o una pseudo che si appoggia su b(i)) 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. Quindi abbiamo una stretta relazione tra questa struttura dati w1 e l'oggetto IQspnArc. Si potrebbe ipotizzare che è questa struttura a implementare l'interfaccia IQspnArc e ad essere passata al 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.
Il nodo a deve comunicare al nodo b questi nuovi dati riguardo gli estremi (mac e linklocal) dell' arco-identità a0-bj.
Il nodo a deve chiedere al nodo b se l'identità bj ha partecipato anch'essa alla migrazione. Se sì, il nodo b risponde comunicando i dati new_peer_mac e new_peer_linklocal che sono riferiti alla nuova interfaccia che il nodo b ha creato e che è gestita ora da bj. Comunica anche il NodeID della nuova identità frutto della migrazione di bj, chiamiamola bk, che ora gestisce la vecchia interfaccia del nodo b che prima era gestita da bj.
Se bj ha partecipato alla migrazione:
Il nodo a deve richiedere al nodo b di formare un nuovo arco-identità a1-bk. Le informazioni che passa sono:
Il NodeID di a1.
Il NodeID di bk.
I dati in(a1)(a(i)).mac e in(a1)(a(i)).linklocal.
Cambia i dati dell'arco assegnato ad a0 relativamente all'interfaccia remota:
w0.peer_mac = new_peer_mac.
w0.peer_linklocal = new_peer_linklocal.
Cambia i dati dell'arco assegnato ad a1 relativamente alla identità remota:
w1.peer_nodeid = Il NodeID di bk.
- Altrimenti:
Il nodo a deve richiedere al nodo b di formare un nuovo arco-identità a1-bj. Le informazioni che passa sono:
Il NodeID di a1.
Il NodeID di bj.
I dati in(a1)(a(i)).mac e in(a1)(a(i)).linklocal.
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 moduli di identità per comunicare dalla sua nuova identità ad uno o più diretti vicini. Resta anche in grado, dato un messaggio ricevuto che è per moduli di identità, 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 i (o più di uno) alla identità del nodo corrente ak, la quale non è cambiata. Sia w l' arco-identità ak-b0 che si appoggia su i.
Il nodo a riceve sull'arco i la comunicazione da parte del nodo b che sono cambiati gli estremi (new_peer_mac e new_peer_linklocal) dell' arco-identità ak-b0.
Cerca nell'elenco che ha nell'associazione f(ak-i) l' arco-identità w che lo collega a b0.
Memorizza old_peer_linklocal = w.peer_linklocal.
Aggiunge sulle tabelle di routing di ns(ak) la rotta che collega in(ak)(i).linklocal a new_peer_linklocal.
Aggiorna tutte le rotte sulle tabelle di routing di ns(ak) che usano w come gateway. Ora dovranno avere new_peer_linklocal.
Elimina dalle tabelle di routing di ns(ak) la rotta che collegava in(ak)(i).linklocal a old_peer_linklocal.
- Aggiorna i dati:
w.peer_mac = new_peer_mac.
w.peer_linklocal = new_peer_linklocal.
Il nodo a riceve la domanda da parte del nodo b se l'identità ak ha partecipato ad una migrazione. Risponde di no.
Il nodo a riceve la richiesta da parte del nodo b di formare un nuovo arco-identità ak-b1. Le informazioni che riceve sono:
Il NodeID di ak.
Il NodeID di b1.
I dati peer_mac e peer_linklocal che sono riferiti alla interfaccia che il nodo b ha assegnato a b1 per l'arco i.
L'utilizzatore del modulo Neighborhood crea un nuovo arco-identità con questi dati e lo aggiunge all'elenco che ha nell'associazione f(ak-i).
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 moduli di identità per comunicare da una sua identità alla nuova identità del vicino b. Resta anche in grado, dato un messaggio ricevuto che è per moduli di identità, di identificare, per mezzo delle callback ricevute all'inizio, se è per una sua identità da parte della nuova identità del vicino b.
Rimozione di un arco-identità
In un certo momento, l'utilizzatore del modulo decide che una certa identità del nodo corrente ak non deve avere più archi verso una certa identità di un suo vicino bj; quindi per ogni arco che il modulo Neighborhood aveva creato tra a e b, l'utilizzatore del modulo fa alcune operazioni per rimuovere gli archi-identità ak-bj.
In precedenza l'utilizzatore del modulo si era dovuto occupare di rimuovere (o cambiare) tutte le rotte che usavano quell'arco come gateway.
Per ogni arco i:
Il nodo a comunica sull'arco i al nodo b che sta rimuovendo il suo arco-identità ak-bj sull'arco i affinché anche il nodo b apporti le modifiche alle sue associazioni.
Cerca nell'elenco che ha nell'associazione f(ak-i) l' arco-identità w che lo collega a bj.
Elimina dalle tabelle di routing di ns(ak) la rotta che collegava in(ak)(i).linklocal a w.peer_linklocal.
Elimina w dall'elenco f(ak-i).
Tutte queste operazioni non coinvolgono direttamente il modulo Neighborhood.
Requisiti
- Implementazione del sistema di tasklet.
- Delegati per la gestione delle chiamate ricevute:
- Una callback per gestire gli IdentityAwareUnicastID.
- Una callback per gestire gli IdentityAwareBroadcastID.
- Uno skeleton del root-dispatcher per gestire gli WholeNodeUnicastID e gli WholeNodeBroadcastID.
- 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:
Avvenuta assegnazione dell' indirizzo di scheda ad una interfaccia di rete gestita.
- Costituzione di un arco. Significa anche avvenuto inserimento della rotta nelle tabelle.
- Rimozione di un arco. Significa anche avvenuta rimozione della rotta nelle tabelle.
- Variazione del costo di un arco.
Avvenuta rimozione dell' indirizzo di scheda ad una interfaccia di rete che non si gestisce più.
- Fornisce metodi per:
current_arcs - Ottenere l'elenco degli archi ora presenti.
get_dispatcher - Dato l'identificativo del destinatario di un messaggio in unicast ricevuto, cioè una istanza di IUnicastID, e dato il nome dell'interfaccia su cui è stato ricevuto (oppure null se il protocollo era TCP) ottenere una istanza di skeleton del root-dispatcher se il messaggio è da processare, oppure null. Nel caso di protocollo UDP, il nome dell'interfaccia è necessario perché il messaggio sia processato solo quando è ricevuto dalla corretta interfaccia. Infatti in questo caso l'oggetto IUnicastID contiene anche il MAC address a cui il messaggio è destinato.
get_dispatcher_set - Dato l'identificativo dei destinatari di un messaggio in broadcast ricevuto, cioè una istanza di IBroadcastID, ottenere una lista (che può essere vuota) di istanze di skeleton del root-dispatcher: il messaggio è da processare su ognuna di queste istanze.
get_identity - Solo per i messaggi destinati a moduli di identità, dato l'identificativo del mittente di un messaggio ricevuto, cioè una istanza di ISourceID, ottenere il NodeID dell' identità del mittente. Se non è possibile ottenerlo, restituisce null.
get_node_arc - Solo per i messaggi destinati a moduli di nodo, dato l'identificativo del mittente di un messaggio ricevuto, cioè una istanza di ISourceID, e il nome dell'interfaccia di rete su cui il messaggio è stato ricevuto, ottenere l'istanza di arco (INeighborhoodArc). Se non è possibile ottenerlo, restituisce null.
get_stub_identity_aware_unicast - Dato un arco che collega questo nodo, chiamiamolo a, ad un altro nodo vicino, chiamiamolo b, dato l'identificativo di una identità che risiede in a, chiamiamola ak, dato l'identificativo di una identità che risiede in b, chiamiamola bj, ottenere un oggetto stub utilizzabile per chiamare un metodo remoto su un modulo di identità da ak a bj. Questo stub dialoga con il nodo remoto con protocollo reliable.
get_stub_whole_node_unicast - Dato un arco che collega questo nodo, chiamiamolo a, ad un altro nodo vicino, chiamiamolo b, ottenere un oggetto stub utilizzabile per chiamare un metodo remoto su un modulo di nodo da a a b. Questo stub dialoga con il nodo remoto con protocollo reliable.
get_stub_identity_aware_broadcast - Dato l'identificativo di una identità che risiede in a, chiamiamola ak, dato un set di identificativi di identità che risiedono in alcuni nodi vicini, ottenere un oggetto stub utilizzabile per inviare un messaggio in broadcast destinato a un modulo di identità di queste identità.
- 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 riprovando con diverse chiamate unicast reliable. Si noti che in questo caso ad un arco passato alla callback possono corrispondere diversi archi-identità.
get_stub_whole_node_broadcast - Dato un set di archi che collegano ad alcuni nodi vicini, ottenere un oggetto stub utilizzabile per inviare un messaggio in broadcast destinato a un modulo di nodo di questi nodi vicini.
- Il modulo produrrà una istanza di IBroadcastID che indicherà come destinatari i nodi che sono identificati dagli archi che sono stati passati a questo metodo.
- Il modulo poi 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 e non reliable: si veda la spiegazione del metodo precedente. Tuttavia in questo caso ad un arco passato alla callback non corrispondono diversi archi-identità.
- 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 ITasklet 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à dev).
Leggere il MAC address dell'interfaccia di rete, es: CC:AF:78:2E:C8:B6 (proprietà mac).
Misurare il round-trip time (la latenza) con un vicino (metodo measure_rtt).
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 IAckCommunicator se vuole ricevere dopo il timeout la lista dei MAC address che hanno segnalato con un ACK la ricezione del messaggio. Tale interfaccia è fornita dalla libreria di livello intermedio ntkdrpc prodotta per usare il framework ZCD.
- 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 IUnicastID 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.
La classe usata per l'identificativo di una identità, cioè NodeID, è definita nella libreria Common. Il modulo Neighborhood ha una dipendenza su questa libreria, quindi conosce tale classe.
La conoscenza del modulo Neighborhood relativamente a tale classe si limita al fatto di sapere che essa è serializzabile secondo la modalità usata in JsonGlib.
La classe usata per l'identificativo di un nodo, cioè NeighborhoodNodeID, è interna al modulo Neighborhood. Anche essa è serializzabile secondo la modalità usata in JsonGlib.
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. 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 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').
- 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).