Modulo QSPN - Analisi Funzionale

Ruolo del modulo

Il modulo realizza lo scambio di messaggi con i vicini del nodo al fine di esplorare la rete. Tali messaggi sono chiamati ETP, acronimo di Extended Tracer Packet. In questo documento non illustriamo nel dettaglio come sono fatti questi messaggi. Rimandiamo per questo al documento esplorazione ma consigliamo di leggerlo solo dopo aver letto il presente documento. Per ora basta considerare che ogni nodo usa questi messaggi per comunicare ai suoi vicini le informazioni riguardanti i percorsi della rete che sono a lui noti.

Il modulo fa uso delle tasklet, un sistema di multithreading cooperativo.

Il modulo fa uso del framework ZCD, precisamente appoggiandosi ad una libreria intermedia prodotta con questo framework per formalizzare i metodi remoti usati nel demone ntkd.

Conoscenze obiettivo del nodo

L'obiettivo di ogni nodo n è di reperire e mantenere per ogni destinazione dst fino a max_paths percorsi disgiunti, che siano i percorsi con costo minore.

Il concetto di costo di un percorso può essere implementato con una qualsiasi metrica. Si può ad esempio usare la latenza di un messaggio che attraversa questo percorso. Oppure la larghezza di banda, cioè la quantità di informazioni per unità di tempo che possono attraversare questo percorso. E' sufficiente che siano implementate queste operazioni:

Per una prima lettura di questo documento si consiglia di identificare il termine "costo" con il concetto di latenza di un messaggio che attraversa il percorso.

Inoltre n vuole mantenere per ogni destinazione dst e per ogni proprio vicino v, almeno 1 percorso, se esiste, indipendentemente dal valore di max_paths e dalle regole di disgiunzione, verso dst che non contiene v tra i suoi passaggi.

Inoltre n vuole mantenere per ogni destinazione dst almeno 1 percorso per ogni diverso fingerprint di dst che gli viene segnalato. Il concetto di fingerprint sarà chiarito in seguito.

Inoltre n vuole mantenere per ogni destinazione dst, per ogni livello l da 0 a dst.lvl, per ogni g-nodo p di livello l diretto vicino del mio g-nodo di livello l, almeno 1 percorso, se esiste, per dst che passa per p. I concetti qui enunciati di g-nodi e livelli verranno chiariti in seguito.

Vicini del nodo

Il modulo QSPN nel nodo n considera vicino di n un nodo v se è a conoscenza di uno o più archi che collegano n a v. La conoscenza degli archi a disposizione del nodo n viene passata al modulo come requisito. Il modulo assume che i nodi collegati ai suoi archi appartengano alla sua stessa rete.

Per ogni arco di cui il modulo QSPN viene portato a conoscenza, esso genera un identificativo che possa essere considerato univoco. Si adotta un numero random in uno spazio sufficientemente grande. Questo identificativo dell'arco andrà a far parte, come vedremo in seguito, delle proprietà di un percorso, che lo rendono distinguibile da un altro. L'identificativo dell'arco è un concetto interno al modulo QSPN, di cui l'utilizzatore non si deve occupare.

La trasmissione di messaggi ETP e la loro ricezione sono subordinate agli archi noti al modulo. Un nodo trasmette ETP soltanto tramite gli archi che conosce (oppure in broadcast). Analogamente, un nodo accetta un ETP solo se proviene da un vicino tramite un arco che conosce. Questo fatto comporta il seguente problema.

Siano n e v due nodi appartenenti alla stessa rete che non hanno alcun arco tra di loro, cioè non sono vicini. Supponiamo che viene a formarsi un collegamento diretto tra di essi, che quindi diventano vicini. Il nodo n si avvede per primo della presenza di v e lo contatta per formare un arco (questo avviene al di fuori del modulo QSPN). Il nodo v conferma. Di seguito entrambi avviano una prima misurazione del costo dell'arco. Soltanto a misurazione avvenuta il modulo QSPN viene informato della presenza di un arco. Sia n il nodo che per primo completa la misurazione. Il modulo QSPN del nodo n riceve la notifica di un nuovo arco e cerca di contattare il nodo v per richiedere un ETP. Il nodo v non ha ancora completato la misurazione, quindi il modulo QSPN del nodo v non trova l'arco nella sua lista.

Questo problema rende necessario che quando il modulo QSPN in un nodo v riceve una richiesta da un vicino e non trova l'arco relativo nella sua lista, prima aspetti un certo tempo e verifichi di nuovo la presenza dell'arco. Dopo questa attesa, che chiamiamo tempo di rilevamento dell'arco, se l'arco risulta ancora sconosciuto, il modulo potrà ignorare la richiesta o rifiutarla con una eccezione. La durata di questa attesa deve essere passata al modulo QSPN dal suo utilizzatore, in quanto è esso a conoscere i dettagli dell'iter di misurazione del costo dell'arco.

Struttura gerarchica della rete

L'assegnazione degli indirizzi ai nodi della rete avviene sulla base di una struttura gerarchica imposta alla rete. Tale gerarchia è composta da un numero fisso di livelli: l.

Al livello 0 ci sono i singoli nodi. Ogni nodo da solo costituisce un vertice.

Al livello 1 i singoli nodi sono raggruppati a costituire gruppi (o cluster) che chiamiamo g-nodi di livello 1. Un g-nodo di livello 1 può essere costituito anche da un solo nodo, oppure da più nodi fino ad un numero massimo fissato. Ogni g-nodo di livello 1 può essere considerato come un singolo vertice.

Al livello 2 i g-nodi di livello 1 sono raggruppati a costituire g-nodi di livello 2. Un g-nodo di livello 2 può essere costituito anche da un solo g-nodo di livello 1, oppure da più g-nodi fino ad un numero massimo fissato. Ogni g-nodo di livello 2 può essere considerato come un singolo vertice. Si noti che i singoli nodi che fanno parte di un g-nodo di livello 1 che fa parte a sua volta di un g-nodo di livello 2, ognuno di questi singoli nodi fa parte allo stesso tempo anche del g-nodo di livello 2.

E così via. Nel livello più alto l è presente un solo gruppo che costituisce tutta la rete.

Anche il singolo nodo a volte viene chiamato (impropriamente) un g-nodo di livello 0.

Abbiamo detto che ogni g-nodo di livello i+1 contiene un numero massimo fissato di g-nodi di livello i. Il numero massimo di elementi di un g-nodo è detto gsize. Ogni livello da 0 a l-1 può avere un valore gsize diverso. Chiamiamo gsize(i) il numero massimo di g-nodi di livello i in un g-nodo di livello i+1.

Ogni g-nodo di livello i ha un identificativo che lo individua univocamente all'interno del suo g-nodo di livello i+1. Tale identificativo è un numero intero da 0 a gsize(i) - 1.

L'indirizzo del singolo nodo va ad essere composto mettendo in sequenza gli identificativi di tutti i g-nodi a cui esso appartiene, a partire da quello di livello l-1 fino a quello di livello 0. Si noti che ogni singolo nodo, dal momento che conosce il proprio indirizzo, sa di far parte di un preciso g-nodo di livello 1, di un preciso g-nodo di livello 2, e così via, fino al livello più alto.

Questa strutturazione gerarchica è adottata per evitare che la mappa della rete che ogni nodo tiene in memoria diventi troppo grande.

Partizionamento della rete

Come detto, ogni g-nodo di qualsiasi livello i può essere considerato come se fosse un unico vertice. Si forma cioè per ogni livello i una sorta di partizionamento del grafo che costituisce l'intera rete.

Indichiamo con G il grafo che rappresenta una rete. Sia x un nodo in G. Indichiamo con la scrittura gi(x) il g-nodo di livello i a cui appartiene x.

Se prendiamo tutti i g-nodi di livello i del grafo originale G e li consideriamo come singoli vertici, otteniamo un altro grafo che rappresenta tutta la rete come composta da vertici di livello i. Questo grafo lo indichiamo così: [G]i.

Sia g un g-nodo di livello i. Indichiamo con 𝛤i(g) l'insieme dei g-nodi di livello i che sono vicini (vertici direttamente collegati) di g nel grafo [G]i.

Mappa gerarchica della rete

In ogni singolo nodo, il modulo QSPN ha il compito di costruire e tenere in memoria una mappa a topologia gerarchica della rete.

Per ogni livello i della rete, da 0 a l-1, un nodo n deve memorizzare in tale mappa tutti i g-nodi di livello i appartenenti al suo stesso g-nodo di livello i+1 e dei quali n conosce l'esistenza, cioè conosce almeno un percorso per raggiungerli. Per ognuno vanno memorizzati tutti i percorsi noti e per ogni percorso alcune informazioni che elencheremo più sotto.

Ad esempio, sia il nodo n con indirizzo nl-1·...·n1·n0. Vale a dire che n ha identificativo n0 all'interno del suo g-nodo di livello 1, il quale ha identificativo n1 all'interno del suo g-nodo di livello 2, ... fino a nl-1. Per il livello 0 il nodo n dovrà memorizzare nella mappa tutti i nodi (detti g-nodi di livello 0) che conosce che appartengono al g-nodo di livello 1 n1. Il nodo n non memorizzerà nella sua mappa n0 perché è esso stesso. Per il livello 1 il nodo n dovrà memorizzare nella mappa tutti i g-nodi di livello 1 che conosce che appartengono al g-nodo di livello 2 n2 come se fossero singoli vertici. Il nodo n non memorizzerà n1 come un singolo vertice perché di esso ha già memorizzato tutti i vertici di cui è composto. Per il livello 2 il nodo n dovrà memorizzare nella mappa tutti i g-nodi di livello 2 che conosce che appartengono al g-nodo di livello 3 n3 come se fossero singoli vertici. Il nodo n non memorizzerà n2 come un singolo vertice perché di esso ha già memorizzato tutti i vertici di cui è composto. E così via fino a memorizzare come fossero singoli vertici anche tutti i g-nodi di livello l-1 che conosce, tranne nl-1.

Affinché questa mappa gerarchica sia sufficiente al nodo per raggiungere ogni singolo nodo esistente nella rete, ogni g-nodo deve essere internamente connesso. E' compito del modulo QSPN scoprire e segnalare se un g-nodo di cui si conosce l'esistenza (e almeno 2 diversi percorsi) è divenuto disconnesso. Eventuali successive azioni volte a porre rimedio non sono di competenza del modulo.

A questo scopo ogni g-nodo ha anche un altro identificativo chiamato fingerprint. Vediamo come si genera un fingerprint e come viene "assegnato" ad un g-nodo.

Fingerprint

A livello 0, il fingerprint di un nodo è composto da un identificativo del nodo, univoco a livello di rete, e da una lista di valori che rappresentano l'anzianità ai vari livelli dal livello 0 al livello l-1. L'anzianità a livello 0 indica quanto è vecchio il nodo rispetto agli altri nodi del suo stesso g-nodo di livello 1; a livello i indica quanto è vecchio il suo g-nodo di livello i rispetto agli altri g-nodi di livello i del suo stesso g-nodo di livello i+1. L'oggetto fingerprint del nodo viene passato al modulo QSPN dal suo utilizzatore; quindi come vengano generati o recuperati i dati in esso contenuti non è di pertinenza del modulo, e nemmeno in che modo sia implementato il confronto fra due valori di anzianità.

Il fingerprint di un g-nodo di livello 1 ha come identificativo l'identificativo del nodo più anziano in esso contenuto e i suoi stessi valori di anzianità dal livello 1 in su. Questi valori dal livello 1 in su risultano gli stessi per tutti i nodi che fanno parte di un medesimo g-nodo di livello 1; questa proprietà è assicurata con una modalità che non è di pertinenza del modulo QSPN, come è stato implicitamente detto prima. Inoltre, il fingerprint di un g-nodo di livello 1 ricorda anche l'anzianità del nodo in esso contenuto che gli ha dato il suo identificativo. Come vedremo subito, quando un nodo viene a conoscenza dell'esistenza di un altro nodo di livello 0 nel suo g-nodo di livello 1, cioè viene a conoscenza di un percorso per raggiungerlo, viene anche portato a conoscenza del fingerprint di quel nodo. Di conseguenza ogni nodo, potendo confrontare le rispettive anzianità di tali nodi, è in grado di computare il fingerprint del suo g-nodo di livello 1.

Il fingerprint di un g-nodo di livello i ha come identificativo l'identificativo del g-nodo di livello i-1 più anziano in esso contenuto e i suoi stessi valori di anzianità dal livello i in su (valori che risultano gli stessi per tutti i g-nodi di livello i-1 del g-nodo). Inoltre, il fingerprint di un g-nodo di livello i ricorda anche l'anzianità del g-nodo di livello i-1 in esso contenuto che gli ha dato il suo identificativo, l'anzianità del g-nodo di livello i-2 in esso contenuto che gli ha dato il suo identificativo, e così via fino al livello 0. Anche a livello i abbiamo che quando un nodo viene a conoscenza dell'esistenza di un altro g-nodo di livello i-1 nel suo g-nodo di livello i, cioè viene a conoscenza di un percorso per raggiungerlo, viene anche portato a conoscenza del fingerprint di quel g-nodo. Di conseguenza ogni nodo è in grado di computare il fingerprint del suo g-nodo di livello i.

Il fingerprint a livello l non ha valori di anzianità e nemmeno necessita di ricordare l'anzianità del g-nodo di livello l-1 in esso contenuto che gli ha dato il suo identificativo. Il fingerprint a livello l ha solo un identificativo. Questo è l'identificativo della rete.

Questo meccanismo di costruzione del fingerprint di un g-nodo a partire da quelli dei g-nodi in esso contenuti (sulla base della conoscenza del nodo corrente) fa in modo che al variare della rete ogni nodo rilevi immediatamente il verificarsi dello split di un g-nodo (o dell'intera rete). Con questo termine indichiamo che il g-nodo non è più internamente connesso, ma si sono formate 2 o più isole.

Vediamo con un esempio come avviene questo rilevamento.

Sia g un g-nodo di livello 1; sia f il nodo più anziano in esso. Siano v e w due border-nodi appartenenti al g-nodo g di livello 1; il termine border-nodo di g indica un nodo appartenente a g che ha almeno un vicino che non appartiene a g. Sia x il vicino di v esterno a g; sia y il vicino di w esterno a g; supponiamo che entrambi siano appartenenti allo stesso g-nodo a di livello 2 in cui si trova anche tutto il g-nodo g.

I nodi v e w sono entrambi a conoscenza di alcuni percorsi per raggiungere f. Quindi entrambi hanno calcolato il fingerprint del g-nodo g ottenendo un fingerprint che ha lo stesso identificativo di f e ricorda l'anzianità di f.

Supponiamo che g diventi disconnesso, per esempio per via della rimozione di un arco; che si siano formate due isole; che v ed f si trovino nella prima isola; che w si trovi nella seconda isola. Supponiamo inoltre che il g-nodo a sia ancora internamente connesso. Quindi esiste un percorso che collega x ad y senza passare per g.

Quando w scopre di non avere più alcun percorso verso f lo considera morto, e ricalcola il fingerprint del g-nodo g ottenendo un diverso fingerprint che ha l'identificativo di un altro nodo h e ricorda la sua anzianità. Per via di questa variazione il nodo w trasmette un ETP al nodo y. Se siamo fortunati, il nodo y aveva memoria anche di un percorso verso g che passava per x e questo ha ancora il vecchio fingerprint: significa quindi che il nodo y ha già rilevato un potenziale1 split del g-nodo g. Ma supponiamo che, per via del massimo numero di percorsi disgiunti verso una destinazione, il nodo y non aveva memoria del percorso verso g che passava per x.

Allora l'ETP ricevuto da y si propagherà e raggiungerà x.2 Ora x sarà a conoscenza di 2 percorsi verso la destinazione g che hanno informazioni diverse riguardo il fingerprint di g.

Il nodo che rileva questa situazione, nel nostro esempio x, è un border-nodo dell'isola che contiene il nodo più anziano. Invece noi vogliamo che sia l'altra isola (o le altre isole) ad essere avvertita. Occorre quindi che si generi il flood di un nuovo ETP che porti questa informazione indietro al nodo y. Aggiungiamo quindi una regola (all'insieme delle regole che disciplinano l'algoritmo di esplorazione della rete) che chiamiamo regola di primo rilevamento di split : quando un nodo n vede un percorso p1 verso un g-nodo g con un fingerprint fp1, se prima n non conosceva fp1 e se conosceva g attraverso un diverso percorso p2 con un diverso fingerprint fp2, allora n produce un nuovo ETP con tutti i percorsi verso g e lo invia a tutti i suoi vicini.

Alla fine anche il nodo y verrà a conoscenza di 2 (o più) percorsi verso la destinazione g che hanno informazioni diverse riguardo il fingerprint di g.

Dopo aver atteso un certo tempo, il nodo y verifica se questa situazione è rimasta invariata. Se i percorsi che riportavano distinti fingerprint hanno mantenuto gli stessi distinti fingerprint, allora y avrà rilevato lo split del g-nodo g.

Basandosi sul fatto che un fingerprint di un g-nogo di livello maggiore di 0 ha memoria dell'anzianità del nodo che gli ha dato il suo identificativo, il nodo y può individuare quale dei due fingerprint sia stato assegnato a g dal nodo più anziano in g. In altre parole, quale isola formatasi con il potenziale split sia da considerarsi più anziana. Siccome y ha fra i suoi diretti vicini un border-nodo di un'isola di g con un fingerprint diverso da quello dell'isola più anziana, il modulo QSPN segnalerà al suo esterno questo rilevamento. Al ricevere tale segnalazione, l'utilizzatore del modulo potrà fare in modo che l'isola scollegata venga avvertita.

Si intuisce che questo meccanismo si ripresenta in maniera analoga qualsiasi sia il livello del g-nodo che diventa disconnesso, basta che il g-nodo di livello superiore sia ancora connesso. Se invece lo split avviene sul livello più alto, cioè se si divide tutta la rete, quello che si ottiene è che le 2 isole diventano reti distinte con identificativi di rete distinti. Per entrambe le situazioni, come detto in precedenza, il compito del modulo QSPN è solo quello di permetterne il rilevamento. In particolare nel caso di split di un g-nodo, è necessario che lo rilevi almeno un nodo diretto vicino di un border-nodo dell'isola (o delle isole) che non contiene il nodo più anziano.

Infine, una nota sui segnali emessi dal modulo QSPN verso l'esterno. Osserviamo cosa succede quando un nodo generico n (non solo x e y) scopre che due percorsi p1 e p2 che conosce verso un g-nodo g riportano due diversi fingerprint. Supponiamo che il nodo n individui nel fingerprint riportato dal percorso p1 quello dell'isola più anziana. Il nodo n nel suo modulo QSPN mantiene entrambi i percorsi in memoria con i loro fingerprint. Quando deve pubblicizzare i suoi percorsi attraverso un ETP il nodo n pubblicizza entrambi. Invece, quando il modulo QSPN del nodo n deve esporre all'esterno i percorsi noti verso la destinazione g, allora solo i percorsi con il fingerprint dell'isola più anziana vengono esposti. Vale a dire che il percorso p1 viene esposto e il percorso p2 no.

Se il percorso p2 era precedentemente valido, quindi in precedenza il modulo aveva segnalato la sua presenza, adesso il modulo deve segnalare la rimozione del percorso p2 (sebbene in memoria rimanga ancora). In futuro, inoltre, se p2 tornasse ad essere valido, il modulo dovrà segnalare nuovamente la sua presenza.


Nota 1: Potenziale. Il nodo y per avere la definitiva certezza che il g-nodo g si è disconnesso in due isole, deve attendere un certo lasso temporale. Durante questo tempo, infatti, y potrebbe venire informato anche dal percorso che passa per x che il fingerprint di g è cambiato. In questo caso è possibile che il vecchio capostipite f sia morto ma il g-nodo g sia ancora del tutto connesso. Se invece dopo l'attesa i due percorsi verso g (quello diretto e quello che passa per x) riportano ancora gli stessi diversi fingerprint, allora si ha la certezza che il g-nodo g è diventato disconnesso.

Nota 2: Per questo è importante che un nodo sempre mantenga e trasmetta per ogni destinazione d almeno un percorso per ogni diverso fingerprint di d che gli viene segnalato attraverso gli ETP ricevuti.

Elementi memorizzati nella mappa

Riassumendo, per ogni g-nodo nella topologia gerarchica del nodo corrente, la mappa mantiene queste informazioni:

Netsukuku Address

Il Netsukuku address è l'indirizzo di una risorsa all'interno della rete, ad esempio un nodo o un g-nodo. Devono essere noti i parametri della topologia gerarchica della rete:

Dati questi parametri, un indirizzo di nodo è composto da un identificativo per ogni livello da l-1 a 0. Invece, un indirizzo di g-nodo di livello i è composto da un identificativo per ogni livello da l-1 a i.

Per convenienza, diciamo che i parametri della topologia fanno parte dell'indirizzo.

Per rappresentare gli indirizzi di nodi verrà definita la classe Naddr, che il modulo QSPN non conosce del tutto, ma solo la sua interfaccia IQspnNaddr, come verrà dettagliato sotto.

Il modulo QSPN in un nodo n conosce, per requisito, il suo indirizzo. Per il tramite di messaggi ETP, esso viene a conoscenza degli indirizzi dei suoi vicini e di altri g-nodi che può raggiungere, ma solo di g-nodi che hanno in comune il loro livello direttamente superiore con uno dei g-nodi di cui il nodo n è membro. Cioè, solo i g-nodi di livello i che appartengono allo stesso g-nodo di livello i+1 a cui appartiene il nodo n saranno individuati e memorizzati. Per questo per rappresentare gli indirizzi di g-nodi il modulo QSPN usa la classe HCoord (coordinate gerarchiche).

Nodi virtuali

Abbiamo detto in precedenza che ogni g-nodo di livello i ha un identificativo che lo individua univocamente all'interno del suo g-nodo di livello i+1 e che tale identificativo è un numero intero da 0 a gsize(i) - 1.

In realtà possono esserci casi in cui si vuole temporaneamente includere in un g-nodo di livello i+1 un numero di g-nodi di livello i superiore a gsize(i). In questi casi si vuole dare ad uno specifico g-nodo di livello i un identificativo che è un numero maggiore di gsize(i) - 1.

Si noti che il g-nodo, o meglio i nodi al suo interno, comporranno ciascuno il proprio Netsukuku address mettendo in sequenza gli identificativi dei vari g-nodi a cui appartengono. Ma se la topologia della rete è stata costruita sulla base del numero di bit a disposizione per gli indirizzi univoci nella rete TCP/IP, come di norma succede, questi non potranno avere un corrispettivo indirizzo di rete univoco.

Spieghiamo i motivi che ci spingono ad ammettere questa situazione e le relative conseguenze assumendo i uguale a 0, per semplicità, ma vedremo poi che i ragionamenti si possono estendere ai vari livelli.

Sia un nodo n border-nodo di un g-nodo g di livello 1 verso un altro g-nodo h. Supponiamo che n voglia migrare da g ad h. Sia j il più alto livello tale che g ed h non appartengono allo stesso g-nodo di livello j, con 0 < j < l. Sia U(g) il g-nodo di livello j a cui appartiene g. Sia U(h) il g-nodo di livello j a cui appartiene h. Il g-nodo g, o uno dei suoi g-nodi superiori fino a U(g) compreso, può risultare disconnesso (split) a causa della migrazione di n. Per evitare questo effetto collaterale, quando n migra dal g-nodo g al g-nodo h, oltre ad assumere un identificativo in h, mantiene temporaneamente un identificativo virtuale in g. Un identificativo virtuale all'interno di un g-nodo di livello i + 1 è un numero maggiore di gsize(i) - 1.

Avendo assunto tale identificativo virtuale, il nodo n ha reso libero il suo precedente identificativo reale in g: questo fatto era probabilmente il motivo della sua migrazione. Nello stesso tempo questo permette a g e ai suoi g-nodi superiori fino a U(g) di non violare il vincolo di connettività interna. Infatti il nodo n, come membro di g, continua a partecipare alle trasmissioni di ETP all'interno di U(g).

Il nodo n, come membro di g, ha ora un indirizzo con la componente al livello 0 virtuale. Usiamo questo aggettivo anche per l'indirizzo, diciamo che ha un indirizzo virtuale al livello 0.

Si noti che il modulo QSPN al suo interno non ha alcuna difficoltà a gestire tuple n0·...·nl-1 i cui membri non rispettano il limite gsize(i).

Riguardo le mansioni svolte dal modulo QSPN, il nodo n adotta un comportamento lievemente differente come membro di g rispetto a quello che avrebbe normalmente e che adotta come membro di h. La differenza sta nel fatto che n come membro di g non mantiene nessun arco con nodi esterni al g-nodo U(g).

Diciamo che l'indirizzo che il nodo n detiene in g, oltre ad essere virtuale al livello 0, è anche interno ai livelli da 1 a j, nel senso che supporta la connettività interna dei suoi g-nodi di livello tra 1 e j.

Il nodo n ha ora anche un identificativo nella posizione 0 in h. Assumiamo che potrebbe essere anche esso temporaneamente un identificativo virtuale, cioè un numero maggiore di gsize(0) - 1. In questo caso anche l'indirizzo che il nodo n detiene in h sarebbe virtuale al livello 0. Però in questo caso il nodo n è autorizzato a mantenere archi con qualunque nodo appartenente a g-nodi esterni di qualunque livello. Tale indirizzo quindi non è interno, bensì globale.

Un'altra differenza tra l'indirizzo assunto in h (che è globale) e quello assunto in g (che è interno) è che l'indirizzo assunto in g è destinato a sparire. Questo avviene nel momento in cui la connettività interna di g e dei suoi g-nodi superiori fino a U(g), cioè ai livelli da 1 a j, risulta garantita da altri collegamenti. Invece l'indirizzo assunto in h è destinato a rimanere e, se era virtuale, ad assumere una componente reale al livello 0.

Un nodo, quindi, per quanto concerne il modulo QSPN, ha in ogni momento uno e un solo indirizzo globale. Può avere, in un particolare momento, 0 o 1 o più indirizzi interni ai livelli da 1 a x. Inoltre, un indirizzo interno ai livelli da 1 a x è sempre anche virtuale poiché la sua componente a livello 0 è virtuale; mentre uno globale può essere virtuale o reale.

Il nodo n insieme ai due distinti indirizzi detiene due distinti fingerprint a livello 0, due distinti set di archi e due distinte mappe di percorsi.

Chiamiamo f0(g), map(g) e arcs(g) il fingerprint a livello 0, la mappa e il set di archi associati all'indirizzo interno in g; chiamiamo f0(h), map(h) e arcs(h) il fingerprint a livello 0, la mappa e il set di archi associati all'indirizzo globale in h.

Il fingerprint f0(g) è quello che il nodo aveva in precedenza in g. Il fingerprint f0(h) è una nuova istanza. Esso è identico al primo per quanto riguarda i valori di anzianità di livello inferiore a i e quelli di livello superiore a j. Differisce per l'identificativo del nodo e i valori di anzianità dal livello i al livello j. Siccome nel g-nodo h di livello i+1 questo nodo è l'ultimo arrivato, il fingerprint dei g-nodi da h a U(h) non avrà sicuramente l'identificativo di n.

Il set arcs(g) è il set di archi che il nodo aveva in g. Il set arcs(h) è una nuova istanza.

Inizialmente il nodo duplica tutti gli archi di arcs(g) in arcs(h). Questa operazione di duplicazione degli archi è di competenza dell'utilizzatore del modulo. Del resto è esso stesso che prende l'iniziativa di istruire il modulo relativamente ai nuovi indirizzi. Il modulo Qspn si limita a ricevere un nuovo set di archi insieme agli altri dati necessari a gestire un indirizzo.

La mappa map(g) è quella che il nodo aveva in g. La mappa map(h) è una nuova istanza.

La mappa map(g) è quella che era stata popolata con gli ETP ricevuti quando n era in g. Sebbene il nodo abbia cambiato il suo identificativo in g, tutti i percorsi che conosceva verso le destinazioni di tutti i livelli restano validi. Anche i percorsi che ha reso pubblici ai suoi vicini non hanno subito alcuna variazione, quindi non ha bisogno di inviare ETP correttivi per essi. Ci sono due informazioni soltanto che deve propagare:

La mappa map(h) viene inizializzata vuota, in attesa di processare nuovi ETP sulla base del nuovo indirizzo globale. Le attività del nodo come detentore di questo nuovo indirizzo e della relativa mappa procedono come per qualsiasi indirizzo con cui un nodo entra in una rete esistente. Si vedrà nel documento di dettaglio esplorazione che questo significa iniziare la fase di bootstrap e richiedere ETP ai propri vicini.

In seguito il nodo rimuoverà da arcs(g) tutti gli archi con vicini che non appartengono al g-nodo U(g). Ma questo solo dopo che il nuovo indirizzo in h ha completato la fase di bootstrap e ha segnalato con un ETP la sua presenza e i suoi percorsi verso g a quei stessi vicini che ora si vedranno mancare un arco con il vecchio indirizzo in g.

Se l'identificativo assunto in h era virtuale, ad esempio in attesa del completamento delle operazioni di una migration path, abbiamo detto che è destinato a divenire reale. Quando questa transizione si rende possibile il nodo n dovrà produrre degli ETP. Per questo dovrà attendere anche di aver completato la fase di bootstrap con l'indirizzo virtuale temporaneo. In seguito dovrà:

Per quanto riguarda la produzione e la processazione di messaggi ETP, il modulo gestisce allo stesso modo l'indirizzo globale insieme alla sua mappa e tutti gli indirizzi interni assieme alla loro mappa. Non fa alcuna differenza a questo proposito che un indirizzo sia virtuale o reale.

Per quanto riguarda i percorsi che il nodo conosce, acquisiti in precedenza o in seguito, come vengono esposti all'utilizzatore del modulo? Il nodo n ottiene come sempre percorsi da tutti i vicini con cui ha un arco. Quando li tratta con il suo indirizzo in g e li mette in map(g), scarta i percorsi che sono pubblicizzati da vicini esterni a U(g) e hanno destinazioni di livello minore o uguale a j. Quando li tratta con il suo indirizzo in h e li mette in map(h), scarta i percorsi che sono pubblicizzati da vicini esterni a U(h) e hanno destinazioni di livello minore o uguale a j. Alla fine abbiamo diversi casi in base al livello k delle destinazioni dei percorsi:

Per ogni percorso che espone al suo utilizzatore il modulo indica anche quale sia il Netsukuku address che lo gestisce.

Ovviamente l'utilizzatore dovrà far attenzione a non utilizzare le destinazioni che hanno una componente virtuale nel popolamento delle rotte nelle tabelle di routing del kernel.

Stando alle regole suddette per l'esposizione dei percorsi all'utilizzatore del modulo, c'è ancora una operazione che il modulo del nodo n deve fare subito: segnalare all'utilizzatore che non espone più alcun percorso verso U(h).

Ritorniamo sul discorso della connettività interna dei g-nodi. Esaminiamo i nodi in U(g) che sono diretti vicini di n:

Esaminiamo i nodi in U(h) che sono diretti vicini di n:

Esaminiamo i nodi in w, con wU(g) e wU(h), che sono diretti vicini di n:


Come abbiamo detto sopra, questi concetti valgono anche per un livello i maggiore di 0.

Sia un g-nodo n’ di livello i border-nodo di un g-nodo g’ di livello i + 1. Supponiamo che n’ voglia migrare da g’ ad un diverso g-nodo h’, sempre di livello i + 1. Sia j il più alto livello tale che g’ ed h’ non appartengono allo stesso g-nodo di livello j, con i < j < l. Sia U(g’) il g-nodo di livello j a cui appartiene g’. Sia U(h’) il g-nodo di livello j a cui appartiene h’.

Riguardo la migrazione di n’, diciamo che essa può essere considerata come una operazione atomica, sebbene avvenga per forza di cose in modo graduale. Si considera atomica nel senso che quando un singolo nodo al suo interno concretizza la sua migrazione si comporta come se sapesse per certo che tutti gli altri singoli nodi lo stanno facendo nello stesso istante. Possiamo farlo perché all'interno di n’ il grafo dei nodi e g-nodi che lo costituiscono non cambia.

Consideriamo ogni singolo nodo n all'interno di n’.

Il nodo n trasforma il suo precedente indirizzo che aveva in g’ (che poteva essere reale o virtuale ad un livello diverso da i) in un indirizzo virtuale al livello i.

Da questo si deduce che un indirizzo virtuale può avere uno o più componenti (identificativi) virtuali.

L'indirizzo precedente, inoltre, poteva essere globale o interno ai livelli da k1 a k2; se era globale diventa interno ai livelli da i + 1 a j, se era interno diventa ora interno ai livelli da 𝜇1 a 𝜇2, con 𝜇1 = min(k1,i+1) e 𝜇1 = max(k2,j).

Ad eccezione della componente al livello i tutte le altre componenti restano invariate.

Il nodo n assume anche un nuovo indirizzo in h’. Le componenti ai livelli superiori a i sono ovviamente quelle di h’. La componente al livello i è quella scelta dal g-nodo per la migrazione, che può essere reale o temporaneamente virtuale. Le componenti ai livelli inferiori sono le stesse che il nodo aveva in g’.

Inoltre, l'indirizzo precedente di n in g’ poteva essere globale o interno ai livelli da k1 a k2; se era globale allora questo nuovo indirizzo in h’ sarà globale, se era interno allora questo nuovo indirizzo in h’ sarà interno ai livelli da k1 a k2.

Infine il nodo n insieme ai due distinti indirizzi detiene due distinti fingerprint a livello 0, due distinti set di archi e due distinte mappe di percorsi. Valgono tutte le osservazioni viste prima a tal riguardo. Rimane da precisare per la mappa map(h’) che tutti i percorsi che hanno per destinazione un livello inferiore a i, cioè un g-nodo interno a n’, rimangono validi, quindi il nodo n li copia da map(g’) in map(h’). In seguito il nodo n con il suo nuovo indirizzo in h’ inizia la fase di bootstrap e richiede ETP ai suoi vicini. Saranno dapprima i border-nodi di n’ ad ottenere le informazioni relative a destinazioni esterne allo stesso n’, ma il fatto che i nodi più interni le riceveranno più tardi non rappresenta un problema.

Si può vedere questo esempio a titolo chiarificativo.


Siccome questi concetti valgono anche per un livello i maggiore di 0, cioè un g-nodo può migrare in blocco e mantenere uno o più indirizzi interni e avere anche un indirizzo globale virtuale, può verificarsi anche il caso in cui un nodo che fa ingresso in una rete non abbia altro modo che entrare in uno di questi g-nodi. Cioè, un nodo n può trovarsi ad avere fin dal suo primo ingresso nella rete uno o più indirizzi interni e avere anche un indirizzo globale virtuale. Queste informazioni il nodo n le riceve con modalità che non sono di competenza del modulo Qspn. Analogamente a altri requisiti, queste informazioni sono comunicate al modulo Qspn nella sua inizializzazione.

Rimozione di g-nodi per migrazione

Un singolo nodo n vuole valutare se gi(n) (il suo g-nodo di livello i) può migrare da gi+1(n) in un altro g-nodo h di livello i+1. O meglio completare la sua migrazione e rimuovere l'indirizzo virtuale interno che detiene in gi+1(n). Sia j il più alto livello tale che gi+1(n) ed h non appartengono allo stesso g-nodo di livello j, con j da i+1 a l-1. Quindi n vuole stabilire se la rimozione di gi(n) provoca o non provoca lo split di uno dei g-nodi da gi+1(n) a gj(n). Non servirà mai verificare in gl(n), vale a dire sapere se provoca uno split di tutta la rete, perché la rimozione è fatta per migrare ad un altro g-nodo pur rimanendo nella stessa rete.

Mentre valuta la risposta con le modalità che illustreremo sotto, deve assicurarsi che un altro g-nodo dentro i g-nodi interessati non faccia le stesse considerazioni. Cioè, prima di iniziare deve acquisire un lock su tutti i g-nodi a cui ora appartiene di livello da i+1 a j.

Questa possibilità gli è fornita dal suo utilizzatore. Come questo avviene non è di competenza del modulo Qspn. Il modulo Qspn riceve una istanza di IQspnGNodeLock a tale scopo.

Dopo aver completato, se la valutazione ammette la rimozione del g-nodo, deve richiedere ai vari g-nodi interessati di attendere alcuni istanti e poi rimuovere il lock. Intanto, il nodo provvede a realizzare la rimozione del vecchio indirizzo del suo g-nodo. L'attesa serve a far sì che, quando il lock sarà rimosso, le trasmissioni di ETP avranno portato tutti i nodi nei g-nodi interessati a conoscere che il vecchio indirizzo è ora libero (oppure allocato di nuovo, ma altrove nel grafo).

Se invece la valutazione non ammette la rimozione del g-nodo, allora n può richiedere ai vari g-nodi interessati di rimuovere immediatamente il lock.

Il lock suddetto deve comunque avere un suo tempo di vita massimo, di modo che se il nodo che ha impostato il lock non ottempera alla sua rimozione, comunque il g-nodo non resta bloccato per sempre.

Implementazione

Se il g-nodo gi(n) non ha alcun vicino in gi+1(n), cioè gi+1(n) contiene solo gi(n), allora la domanda da porre è direttamente se la rimozione di gi+1(n) provoca o non provoca lo split di uno dei g-nodi da gi+2(n) a gl-1(n).

La rimozione di gi(n) non provoca lo split di gi+1(n) se il g-nodo gi(n) ha un solo vicino di livello i in gi+1(n). Infatti tutti i g-nodi di livello i in gi+1(n) venivano raggiunti da gi(n) unicamente tramite quel vicino.

Bisogna analizzare solo i casi in cui il g-nodo gi(n) ha due o più vicini in gi+1(n).

La rimozione di gi(n) non provoca lo split di gi+1(n) se:

Sicuramente, se y ha nella sua mappa dei percorsi per x che non passano per n, allora li ha comunicati a n. Però n potrebbe non averli inclusi nella sua mappa perché aveva raggiunto il limite max_paths o perché non erano sufficientemente disgiunti da altri.

Però abbiamo detto all'inizio che ogni nodo si pone anche l'obiettivo di mantenere per ogni destinazione dst, per ogni livello l da 0 a dst.lvl, per ogni g-nodo p di livello l diretto vicino del mio g-nodo di livello l, almeno 1 percorso, se esiste, per dst che passa per p.

Allora se la condizione detta sopra non è soddisfatta, questo è sufficiente ad affermare che la rimozione di gi(n) provoca lo split di gi+1(n).

Abbiamo dimostrato come il nodo n è in grado di dire se la rimozione di gi(n) provoca o non provoca lo split di gi+1(n). Se sappiamo che non la provoca, allora passiamo, uno alla volta, ai livelli superiori. Altrimenti non serve.

Con i valori di k tali che 2 ≤ kj-i, affermiamo quanto segue:

La rimozione di gi(n) non provoca lo split di gi+k(n) se il g-nodo gi+k-1(n) non ha alcun vicino di livello i+k-1 in gi+k(n). Infatti abbiamo già detto che il g-nodo gi+k-1(n) non ha subito lo split.

Bisogna analizzare solo i casi in cui il g-nodo gi+k-1(n) ha almeno un vicino in gi+k(n).

La rimozione di gi(n) non provoca lo split di gi+k(n) se:

Requisiti

Quando il nodo cambia il proprio identificativo a livello i richiede al modulo di apportare tale variazione modificare il proprio indirizzo globale, modificando il suo identificativo a livello i.

Deliverables

Classi e interfacce

L'oggetto che rappresenta gli indirizzi dei nodi non è del tutto noto a questo modulo, che conosce alcune sue interfacce a seconda dell'uso che può farne.

Per il proprio indirizzo il nodo conosce l'interfaccia IQspnMyNaddr, per gli indirizzi di altri nodi conosce l'interfaccia IQspnNaddr.

Questi i metodi delle interfacce note al modulo:


La classe HCoord è una classe comune nota a questo modulo. E' una classe serializzabile, cioè le cui istanze sono adatte al passaggio di dati a metodi remoti (vedi framework ZCD). Una sua istanza contiene le coordinate gerarchiche di un g-nodo nella mappa del nodo: livello e identificativo nel livello.


I passi che costituiscono un percorso noto verso una destinazione sono rappresentati da una doppia sequenza di k elementi:

E' inclusa in testa la coordinata che rappresenta il vicino che usiamo come gateway (o meglio il suo massimo distinto g-nodo rispetto a noi) e in coda la coordinata che rappresenta la destinazione.

La sequenza hops è sempre in termini di g-nodi che hanno il g-nodo superiore in comune con il nodo corrente.

Non viene definita una classe per contenere questa informazione: si usa una lista di HCoord e una lista di int.


I passi che costituiscono il percorso fatto da un ETP sono rappresentati da una singola sequenza di istanze di HCoord.

Per questa informazione si usa una lista di HCoord.


Il fingerprint di un g-nodo è un oggetto che il modulo non istanzia da solo; gli viene passato il suo fingerprint di nodo (a livello 0) e il modulo ne conosce l'interfaccia IQspnFingerprint. Il modulo inoltre assume che sia un oggetto serializzabile.

L'interfaccia IQspnFingerprint consente di:


Il costo di un arco e il costo di un percorso sono rappresentati da istanze di una classe non del tutto nota a questo modulo. La sua interfaccia nota al modulo (IQspnCost) gli consente di:

Il costo di un percorso, che viene pubblicizzato al modulo QSPN da un vicino, può essere un costo fittizio per indicare una certa situazione – come null per indicare che la destinazione è proprio il vicino, o dead per indicare che il percorso non è più funzionante. Invece il costo di un arco, che viene passato al modulo QSPN dal suo utilizzatore, è sempre un valore frutto di una reale misurazione. Infatti non ha alcun significato un arco verso me stesso, e un arco non funzionante viene semplicemente rimosso.

Quindi l'interfaccia IQspnCost consente anche di:

Un'altra caratteristica importante di questi due costi fittizzi (null e dead) è che essi rappresentano il minimo assoluto e il massimo assoluto. Come tali, essi possono essere sommati o confrontati con qualsiasi altro costo indipendentemente dalla metrica a cui si riferisce (latenza, larghezza di banda, ecc.). Infatti, sia cn la costante costo null, sia cd la costante costo dead e sia w un costo che rappresenta una effettiva misurazione di una qualsiasi metrica. Abbiamo queste proprietà:


Un arco è un oggetto il cui contenuto non è del tutto noto al modulo QSPN. L'interfaccia di questo oggetto nota al modulo (IQspnArc) gli consente di:


Un ETP è una istanza della classe EtpMessage che è interna al modulo. Essa è serializzabile. Un ETP contiene:


Un percorso p scritto nella lista P di un ETP è una istanza della classe EtpPath che è interna al modulo. Essa è serializzabile. Contiene:


La classe NodePath è interna al modulo. Una sua istanza rappresenta un percorso da questo nodo alla destinazione comprensivo dell'arco dal nodo al vicino che ha pubblicizzato il percorso. Contiene:


Il percorso fornito dal metodo pubblico get_paths_to del modulo non ha le stesse informazioni usate internamente al modulo e presenti nella classe NodePath. Si usa un'altra classe, RetPath. Anche questa è una classe interna al modulo. La sua interfaccia nota all'esterno del modulo (IQspnNodePath) consente di:


L'oggetto che rappresenta un passo dentro un IQspnNodePath è una istanza della della classe RetHop. Tale classe è interna al modulo. La sua interfaccia nota all'esterno del modulo (IQspnHop) consente di:


Se per un g-nodo g vengono rilevati due percorsi (istanze di IQspnNodePath) che differiscono per il loro fingerprint e se questa situazione si mantiene per un certo lasso di tempo, questo è sintomo dello split del g-nodo g.

Per valutare quanto deve attendere prima di segnalare lo split del g-nodo, al modulo viene fornito un oggetto dal suo utilizzatore, che implementa l'interfaccia IQspnThresholdCalculator. Tramite essa il modulo può:


Per ottenere un lock su uno o più dei suoi g-nodi di appartenenza, al modulo viene fornito un oggetto dal suo utilizzatore, che implementa l'interfaccia IQspnGNodeLock. Tramite essa il modulo può:


La stub factory è un oggetto di cui il modulo conosce l'interfaccia IQspnStubFactory. Tramite essa il modulo può:


Il gestore per gli archi che non segnalano la ricezione di un messaggio in broadcast è una istanza di una classe interna al modulo, che implementa l'interfaccia IQspnMissingArcHandler. Tramite essa: