# Freenet - Seminario * [Introduzione a Freenet](#1---introduzione-a-freenet) * [Cenni storici](#cenni-storici) * [Design Goals](#design-goals) * [Architettura di Freenet](#architettura-di-freenet) * [Modello di funzionamento](#modello-di-funzionamento) * [Chiavi e ricerche](#chiavi-e-ricerche-2) * [Ottenere un file](#ottenere-un-file) * [Note riguardo al processo di ricerca di un file](#note-riguardo-al-processo-di-ricerca-di-un-file) * [Decentralized data storage](#decentralized-data-storage-3) * [Inserimento di dati (files)](#inserimento-di-dati-files) * [Data management](#data-management) * [Data encryption](#data-encryption) * [Dettagli sul protocollo](#dettagli-sul-protocollo) * [Small World Network](#small-world-network-4) * [Darknet implementation](#darknet-implementation) * [Il fenomeno del mondo piccolo (Small World Phenomenon)](#il-fenomeno-del-mondo-piccolo-small-world-phenomenon) * [Il Dark Network](#il-dark-network) * [Applicazione del Greedy Routing](#applicazione-del-greedy-routing) * [L'algoritmo Greedy](#lalgoritmo-greedy) * [Bibliografia](#bibliografia) ## Introduzione a Freenet Freenet è una rete peer-to-peer decentralizzata, progettata per resistere alla censura. L'obbiettivo primario di Freenet è quello di costruire una rete in cui sia rispettata la libertà di parola ed espressione tramite una forte preservazione dell'anonimato. ### Cenni storici Freenet è stato ideato da Ian Clarke nel 1999, a seguito di un progetto studentesco all'Università di Edinburgo. Nel 2001, Clarke e altri ricercatori hanno posto le basi del progetto con un paper intitolato *"Freenet: A Distributed Anonymous Information Storage and Retrieval System"* , nel quale teorizzano un modello di anonimato in Internet ottenuto distribuendo blocchi di contenuto di piccole dimensioni, cifrati, su tutti i nodi della rete. Da allora, il progetto Freenet è in continuo sviluppo. Freenet è scritto in Java, di modo da essere compatibile con qualunque sistema in grado di hostare una JVM. Dal 2011, Freenet supporta OpenJDK, un'implementazione open source della piattaforma Java. ### Design Goals Nel paper del 2001 vengono identificati 5 obbiettivi principali della rete: * Anonimato per i produttori e i consumatori di informazioni * Storage deniability * Resistenza a tentativi effettuati da terze parti di negare accesso alle informazioni * Storage e routing delle informazioni dinamici ed efficienti * Decentralizzazione di tutte le funzioni della rete ## Architettura di Freenet Freenet è implementato come un network peer-to-peer **adattivo**, costituito da nodi che effettuano richieste gli uni agli altri per salvare e ottenere files di dati (*data files*). I data files sono indicizzati e nominati con **chiavi indipendenti dalla localizzazione** (*location-independent keys*). Ogni nodo mantiene il suo data storage **locale**, che rende disponibile agli altri nodi per operazioni di **reading and writing**. Nello storage locale dei nodi è salvata una **routing table** che contiene gli indirizzi di altri nodi e le chiavi che si crede essi contengano. Questa routing table permette di fare **routing dinamico**. ### Modello di funzionamento Il modello di funzionamento di base è che le richieste per le chiavi siano passate da nodo a nodo, tramite una catena di **proxy requests**, in cui ogni nodo decide **dove** mandare la richiesta nel successivo step. Questo modello rassomiglia il routing IP classico. I percorsi variano a seconda delle chiavi richieste. Gli algoritmi di routing descritti nelle sezioni successive sono progettati per adattare i percorsi nel tempo, per garantire una performance ottimale. A ogni richiesta è assegnato: * un numero di **hops-to-live**, simili al TTL di IP, che servono per evitare catene infinite di richieste tra i nodi, * un identificatore pseudo-unico casuale, così che i nodi possano evitare i loop, rifiutando richieste che hanno già visto. *Nota: quando questo accade, il nodo che precede sceglie semplicemente un altro nodo a cui fare forwarding*. Il processo di passaggio delle richieste continua finchè la richiesta non è soddisfatta o gli *hops-to-live* si esauriscono. A questo punto, il risultato (*success* o *failure*) viene mandato lungo la catena di nodi mandanti (quelli che hanno passato la richiesta). Nessun nodo è privilegiato rispetto agli altri, quindi **non esistono gerarchie o central points of failure**. Per entrare nel network, è sufficiente scoprire gli indirizzi di uno o più nodi, tramite mezzi *in-band* (Opennet) oppure *out-of-band* (Darknet), quindi cominciare a inviare richieste. ## Chiavi e ricerche In Freenet, i files sono identificati da **chiavi binarie** (*binary file keys*), ottenute applicando una hash function come **SHA**. Si identificano **tre differenti tipi di chiavi**: * **KSK (Keyword-Signed-Key)**: La forma più basica di chiave utilizzata in Freenet. E' derivata da una stringa di testo scelta dall'utente al momento del salvataggio del file nella rete. La stringa viene utilizzata come input per generare una coppia di chiavi **asimmetriche** pubblica/privata e la chiave pubblica viene passata alla funzione di hash per ottenere la **chiave del file** (*file key*). La chiave privata è utilizzata per **firmare il file**, provvedendo un minimo controllo di integrità, ovvero la garanzia che un file corrisponda alla sua file key. Si nota che un sistema simile è soggetto a **dictionary attacks** contro la firma, dato che è possibile ottenere **una lista di stringe descrittive**. Per questa e **altre ragioni (TODO)**, il file è anche crittato utilizzando la stringa descrittiva come chiave. Per permettere ad altri peer di ottenere il file, il nodo deve rendere pubblica la stringa descrittiva. Questo rende le chiavi **keyword-signed** facili da ricordare e comunicare ai peer. Purtroppo però questo sistema non è efficace in quanto le chiavi non sono uniche (è possibile che due utenti scelgano la stessa chiave per file diversi). * **SSK (Signed-Subspace-Key)**: E' pensata per risolvere le debolezze delle KSK. Questo tipo di chiavi abilita i **personal namespaces**, che un utente può creare generando una coppia di chiavi **asimmetriche** che identificheranno il suo namespace. Per inserire un file, è necessario scegliere una stringa descrittiva come per le KSK, ma questa volta **la chiave pubblica del namespace e la stringa descrittiva sono hashate indipendentemente**. Tra le due viene fatto poi un bit-by-bit **XOR** e il risultato è hashato di nuovo per ottenere la chiave del file. Come per le KSK, la chiave privata è utilizzata per firmare il file, e il file è crittato utilizzando la stringa descrittiva come chiave. Questa firma risulta però più sicura in quanto **generata da una coppia di chiavi casuali**. Per permettere ad altri peer di ottenere il file, il nodo deve rendere pubblica la stringa descrittiva **e la sua public subspace key**. In aggiunta, il proprietario del file ora ha la possibilità di gestire il proprio namespace. Per esempio, potrebbe simulare una struttura gerarchica creando files come direttori (*directory*) contenenti hypertext che punta ad altri files. I direttori potrebbero anche puntare ricorsivamente ad altri direttori. * **CHK (Content-Hash-Key)**: Una chiave utile per implementare **update** e **splitting** dei files. Una CHK è semplicemente derivata **hashando direttamente i contenuti del file corrispondente**, ottenendo una chiave pseudo-unica per ogni file. I files sono anche crittati da una chiave di cifratura generata **casualmente**. Per permettere ad altri peer di ottenere il file, il nodo deve rendere pubblica la chiave di hash del contenuto e la chiave di decifratura. Si noti come la chiave di decifratura **non è MAI salvata con il file, ma solo pubblicata con la sua file key**, per ragioni ancora da spiegare **(TODO)**. Le chiavi CHK sono molto utili se usate in parallelo con le SSK, utilizzando un **meccanismo di indirezione**. Per salvare un file di cui si può fare un update, un utente deve prima inserirlo sotto la sua CHK. Successivamente l'utente può inserire un file **indiretto** sotto una SSK il cui contenuto è la CHK. Questo permette ad altri peer di ottenere la chiave in due step, data la SSK. ### Ottenere un file Per ottenere un file, un utente deve per prima cosa calcolare la sua chiave binaria (*file key*). In seguito, l'utente deve mandare una richiesta al proprio nodo specificando **chiave e HTL**. Quando un nodo riceve una richiesta: 1. Controlla se il dato cercato è presente nel suo storage, se sì ritorna il dato lungo la catena di nodi che hanno passato la richiesta. 2. Se il dato non è presente all'interno del suo storage, il nodo passa la richiesta al nodo contenente la **chiave più vicina alla chiave richiesta** presente nel suo key database (*forwarding table*). Se questa richiesta ha successo, il nodo passa il dato indietro al richiedente in *upstream*, fa **caching** del file nel suo storage locale, e crea una nuova *entry* nella sua forwarding table associando la sorgente del dato con la chiave richiesta. Una richiesta successiva della stessa chiave potrà essere soddisfatta dalla cache locale dei nodi per cui è passata la richiesta. Una richiesta per una chiave **simile (vicina)**, determinata come **distanza lessicografica**, è passata alla sorgente del dato precedente. Essendo che mantenere una tabella di sorgenti dei dati è un pericolo per la sicurezza degli utenti, ogni nodo che ha fatto caching può decidere **arbitrariamente** di cambiare il suo messaggio di reply, dichiarando se stesso o un altro nodo lungo il percorso come sorgente. Se un nodo **non è in grado di fare forwarding di una richiesta**, perché il nodo scelto non è presente o perché si verrebbe a creare un loop, allora esso tenta di mandare la richiesta al secondo nodo che ritiene più *vicino*, poi al terzo e così via. Nel caso in cui un nodo **non riesca a mandare la richiesta a nessun altro peer**, esso manda un messaggio di **failure** al suo vicino sull'upstream, che quindi proverà a mandare la richiesta alla *sua* seconda scelta e così via. In questo modo, ogni richiesta agisce come una *steepest-ascent-hill-climbing search* (cit) con **backtracking**. ![](./presentation/requestseq.jpg) ### Note riguardo al processo di ricerca di un file * Se gli HTL scadono, un messaggio di failure è propagato verso l'upstream **senza provare nessun nodo successivo**. Inoltre, se gli HTL sono considerati **troppi** (un numero troppo elevato), i nodi possono decidere di limitali per ridurre il carico sulla rete. * I nodi possono ignorare richieste pendenti dopo un certo periodo di tempo, per liberare memoria dedicata ai messaggi. * Il meccanismo di ricerca ha una serie di implicazioni: 1. Si è visto come la **qualità del routing migliora nel tempo**, per due ragioni: I nodi tendono a specializzarsi nel localizzare set di chiavi simili, essendo che le richieste per chiavi simili sono solitamente mandate verso gli stessi nodi. Inoltre i nodi si specializzano nel salvare **cluster di files con chiavi simili**, essendo che ogni richiesta con successo comporta una serie di nodi che diventano **cache** e quindi ottengono file con chiavi simili. 2. Il meccanismo di richiesta **replica in maniera trasparente dati popolari**, creando mirrors nel sistema vicino alle località con maggiore richiesta. 3. La connettività tende a crescere e favorire i nodi che rispondono con successo a più richieste, essendo che ogni nodo che risponde con successo aggiunge una entry alla sua routing table, e pertanto riesce a scoprire **una parte maggiore della rete**. Si nota però come questo **non aiuti gli altri nodi a scoprirlo**. 4. Si creano link diretti alle sorgenti di dati, bypassando i nodi intermediari. Per questo **i nodi che fanno da sorgente con successo guadagnano entries nella routing table e saranno contattati più spesso**. ## Decentralized data storage ### Inserimento di dati (files) L'inserimento dei files segue una strategia parallela a quella effettuata per le richieste. Per inserire un file, un utente: * Calcola una binary file key corrispondente al file. * Manda un messaggio di **insert** al proprio nodo, specificando la chiave proposta e un valore di HTL, che determina **il numero di nodi in cui il file sarà salvato**. * Quando un nodo riceve una proposta di inserimento controlla il proprio storage per verificare che la chiave non sia già presente. * Se la chiave è presente, il nodo ritorna il file precedentemente salvato come se avesse ricevuto una richiesta. In questo modo l'utente sa che è avvenuta una collisione e può riprovare con un'altra chiave. * Se la chiave non è presente nello storage, il nodo cerca la chiave più vicina ad essa nella sua routing table, e manda la richiesta di insert al nodo corrispondente. Se si verifica una collisione, il nodo passa i dati all'upstream e di nuovo si comporta come se avesse ricevuto una richiesta (caching del file in locale + creazione di entry nella routing table). * Se il limite di HTL è raggiunto **senza alcuna collisione**, il risultato di **all clear** è propagato indietro al nodo che ha effettuato l'inserimento. Questo è un ACK di **success**, al contrario di quanto avviene per le richieste, in cui il raggiungimento degli HTL senza collisione significa *file not found*. * Una volta ricevuto l'**all clear**, l'utente manda il file da inserire al proprio nodo, che lo propaga lungo il percorso stabilito dalla query iniziale, salvandolo in tutti i nodi nel percorso. Ogni nodo creerà quindi una entry nella propria routing table corrispondente al file. Per evitare di salvare l'origine del file (security concern), **ogni nodo può arbitrariamente decidere di cambiare l'insert message, identificandosi come sorgente del nodo**. * Se un nodo non può mandare la richiesta al nodo da lui scelto per malfunzionamenti (target down) oppure perché si creerebbe un *loop*, esso manda la richiesta al secondo nodo più vicino, e così via. Questo meccanismo ha 3 effetti: * I nuovi files inseriti sono posizionati selettivamente in nodi con chiavi vicine (simili) a quella scelta per il nuovo file. Questo rinforza il fenomeno del clustering delle chiavi. * I nuovi nodi **possono usare gli insert come modo supplementare di annunciare la propria presenza nella rete**. * Gli attacker non possono propagare file falsi al posto di quelli originali, anzi, ottengono l'effetto opposto, essendo che file con la stessa chiave non possono essere inseriti. (*Nota: questo è un problema delle KSK, mentre le altre chiavi hanno un meccanismo di verifica molto più forte, utilizzando i namespace dell'inserter*). ### Data management Freenet funziona sul principio del decentralized data storage, ovvero **ogni nodo della rete condivide un certo ammontare di spazio locale, che viene dedicato esclusivamente al data storage**. I proprietari dei singoli nodi possono configurare quanto spazio dedicare alla rete. Lo storage è gestito come una **cache LRU** (Least Recently Used), che ordina i dati salvati in base a un ordine temporale decrescente di ricevimento delle richieste (o tempo di inserimento, se non è mai stata ricevuta una richiesta). Quando un nuovo files è inserito / salvato, se questo causa un superamento dei limiti dello storage, i file usati **meno recentemente** sono cancellati finchè non si raggiunge la disponibilità di memoria sufficiente. Questo comportamento potrebbe impattare pesantemente la disponibilità dei dati, ma è mitigato dal fatto che **le entry nella routing table relative ai file eliminati sono mantenute in memoria per un certo tempo, permettendo al nodo di ottenere nuove copie dalle sorgenti originali.** Tecnicamente, lo storage dei nodi **non è una cache**, dato che **non c'è copia permanente dei dati che sono salvati in una cache**, come invece avviene per il caching classico (un esempio è Free Haven o Eterniti, ma anche il DNS caching, in cui i dati hanno una sorgente che li mantiene in modo permanente). Il meccanismo di *scadenza* dei dati, che ne causa la cancellazione, ha però l'effetto vantaggioso di permettere ai nuovi dati di *aggiornare* e rimpiazzare i dati più vecchi. I documenti presenti da più tempo che però hanno valore sono preservati grazie alle richieste che ricevono, che aggiornano le entry nella cache (e quindi ricevono una posizione migliore nell'ordinamento LRU). ### Data encryption Per ragioni politiche / legali, potrebbe essere desiderabile da parte degli operatori dei nodi di **non conoscere il contenuto del proprio storage locale**. Per questo motivo, tutti i dati salvati sono cifrati, non per renderli più sicuri (il che sarebbe impossibile, dato che chiunque effettui una richiesta è in grado di decifrare il file una volta che lo riceve). L'obbiettivo è invece quello di **poter negare la conoscenza, da parte dei node operators, di qualunque dato salvato nel proprio storage**, dato che tutto ciò che essi conoscono *a priori* è la chiave del file, non la chiave di cifratura del file. Le chiavi di cifratura (per il KSK e l'SSK) sono ottenibili solo **con il processo di invertimento di un hash**, mentre le chiavi di cifratura del Content Hash sono completamente scollegate. Ovviamente **un dictionary attack rivelerebbe quali chiavi sono presenti**, ma la difficoltà e il workload di un attacco simile è sufficientemente alta da permettere una misura di sicurezza agli operatori di nodi. ### Dettagli sul protocollo Il protocollo Freenet: * **E' packet-oriented** * utilizza **self-contained messages** * Ogni messaggio include un ID della transazione, così che i nodi possano tracciare lo stato di insert e request. Questo design permette flessibilità nella scelta del **meccanismo di trasporto**, che quindi supporta **TCP,UDP e altre tecnologie come il packet radio**. Per massimizzare l'efficienza, i nodi che utilizzano un canale **persistente** (come una connessione TCP) possono anche mandare **multipli messaggi lungo la stessa connessione**. Gli indirizzi dei nodi consistono in un **metodo di trasporto** più un identificatore di trasporto specifico, come un'indirizzo IP e una porta, ma **nodi che cambiano indirizzo frequentemente possono utilizzare indirizzi virtuali**, salvati come **Address Resolution Keys** (ARK), che sono SSK aggiornate per contenere l'indirizzo reale corrente. ## Small World Network ### Darknet implementation Si può discriminare tra due tipi di reti Peer-To-Peer: * **Light** P2P network: freenet, gnutella, DHT. Permettono la comunicazione tra **qualunque nodo**. * **Dark** P2P network: Waste. Solo i nodi **fidati**, il cui indirizzo è conosciuto *a priori*, possono comunicare (*friend to friend network*). #### Il fenomeno del mondo piccolo (Small World Phenomenon) Il fenomeno del *mondo piccolo* è il principio grazie al quale un network P2P è in grado di ottenere informazioni in maniera **decentralizzata e scalabile**. Il fenomeno è simile al principio dei **sei gradi di separazione** tra esseri umani, dato che **conoscenze tra esseri umani formano uno small world network**. Quindi: **l'informazione è condivisa solo tra i conoscenti**, intesi come **relazioni fidate**. Si nota come questo rappresenti un algoritmo di routing effettivo, dotato di grande robustezza, essendo che resiste ad attacchi intenzionati a **rompere il routing** (come l'invio di informazioni a qualcuno che non è fidato). Se questo accade, l'algoritmo di routing effettua un **restart**. Il problema principale, stabilita l'esistenza di un percorso tra due nodi fidati, è quello di **trovare il percorso**, il che non è sempre immediato. Nel caso di una persona che vuole mandare una lettera solo tramite conoscenze, il discriminante per stabilire a chi passare la lettera (il **next hop**) è dato da informazioni aggiuntive riguardo al conoscente. In una rete di nodi, questo non è possibile, ma può essere tradotto in un concetto di **similarità** tra i nodi, definita come **closeness** (vicinanza). Questo significa che **peer simili hanno maggiore probabilità di essere connessi**. La scelta che un nodo deve fare diventa quindi: **Quale tra i peer che conosco è più vicino (quindi più simile) al peer che sto cercando di raggiungere?** Questa scelta identifica un algoritmo di **Greedy routing**. #### Il Dark Network Un Dark Network, come la parte di Freenet chiamata **DarkNet** utilizza un algoritmo di greedy routing simile a quello descritto nel fenomeno del mondo piccolo. Per ottenerelo, bisogna definire un modo di accedere alle informazioni della rete. In OpenNet, ogni indirizzo è accessibile a tutti. In Internet, c'è un sistema gerarchico di indirizzi IP, pianificati e assegnati. In Freenet, e specialmente in DarkNet, le informazioni non sono organizzate ma **distribuite caoticamente**, dato che **non esiste un'entità che si occupi di assegnare gli indirizzi di rete**. Ciononostante, l'abilità di mandare un messaggio tra due nodi distanti sulla rete è essenziale. Jon Kleinberg nel 2000 ha spiegato come gli Small World Network possano essere navigati, definendo una proprietà essenziale che permette la riuscita dello Small World Phenomenon: * L'efficacia del routing negli Small World Networks dipende **dalla proporzione delle connessioni che hanno diversa lunghezza, rispetto alla posizione dei nodi**. Questo significa che connessioni di differente *lunghezza* devono essere rappresentate come nodi **più vicini o più lontani**. In una scelta tra uno step più lungo e uno più corto è preferibile quello più corto, dato che quando arriviamo più vicini a qualcuno, c'è una probabilità più alta che quel qualcuno conoscano la persona a cui entrambi sono vicini. Per questo, si richiede: **Il numero di condizioni con determinata lunghezza deve essere inversamente proporzionale alla lunghezza delle stesse**. Se questo requisito è rispettato, l'algoritmo ha una complessita di `O((log(n))^2)` steps, risultando quindi molto performante. In termini pratici, questo significa che l'ideale è avere **poche connessioni lunghe e molte corte**, sapendo che **più lunga è una connessione meno probabilità ha di venire effettuata**. #### Applicazione del Greedy Routing La risoluzione algoritmica alla domanda: *"Quale nodo è il più vicino a quello a cui devo mandare il messaggio?"*, non è sempre possibile senza avere una grande mole di informazioni riguardo alla rete. Freenet e in modo particolare DarkNet è incentrata sulla preservazione dell'anonimato dei suoi utenti, perciò non si hanno a disposizione la posizione dei nodi nella rete e nemmeno le *trusted connections, o "conoscenze"* di ciascun nodo. Per queste ragioni, possiamo dire che è la rete, adottando il modello di Kleinberg, a decidere: * Ad ogni nodo viene assegnata un'identità numerica che è posizionata in una griglia, rispettando il principio di Kleinberg che richiede poche connessioni lunghe e molte corte. Può essere visto come un *reverse engineering* delle posizioni dei nodi, basandosi sulle connessioni nella rete. Questo però necessita una rete decentralizzata e un procedimento distribuito, altrimenti si potrebbe verificare la centralizzazione delle informazioni riguardo alla rete in un singolo nodo (o più nodi). ##### L'algoritmo Greedy L'algoritmo riceve come input un network di nodi connessi casualmente e restituisce uno small-world network in cui le lunghezze tra i nodi sono inversamente proporzionali al numero di nodi della data lunghezza. 1. Un nuovo nodo si inserisce nella rete e sceglie una posizione a caso. 2. Il nodo scambia la propria posizione con quella degli altri nodi, di modo da **minimizzare il prodotto della lunghezza gli edge del grafo**. Questo prodotto rappresenta la **vicinanza** tra i peer nel social space, che è integrata nella loro identità numerica. In una rete generata con questo principio, l'algoritmo greedy lavora molto bene, permettendo a un nodo di connettersi a qualunque altro in un numero **relativamente piccolo** di steps. Bisogna però considerare che ciò non rivela nulla riguardo alla **posizione** del nodo rispetto agli altri. *(dimostrazione grafica)* La dimostrazione grafica mostra che un algoritmo di routing Greedy funziona **se e solo se i nodi vicini hanno più probabilità di conoscersi**. Perciò il processo di riordinamento dei nodi è necessario. L'algoritmo di riordinamento dei nodi è **interamente decentralizzato** e implementato in Freenet. The algorithm for node rearrangement is **fully decentralized** and implemented inside freenet. *(simulazione)* ## Bibliografia * Paper di Ian Clarke che descrive le basi di Freenet [link](https://www.cs.cornell.edu/people/egs/615/freenet.pdf) * Slides della presentazione di Darknet a Berlino, 2005, [link](https://freenetproject.org/assets/papers/ccc-slideshow.pdf.bz2) * Simulazione in Java della presentazione di Darknet a Berlino, 2005, [link](https://freenetproject.org/assets/papers/ccc-freenet-demo.tar.bz2) * Paper *Distributed routing in Small World Networks*, [link](http://freenetproject.org/assets/papers/swroute.pdf) * [Sito ufficiale Freenet](https://freenetproject.org/pages/about.html)