Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ipv6_conf/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.pyc
246 changes: 246 additions & 0 deletions ipv6_conf/Report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
# Introduzione e scopo del progetto
Il presente lavoro ha l'obiettivo di fare il design, implementare e testare un'applicazione
su ambiente OSHI che permetta la configurazione automatica di indirizzi ipv6 su una topologia
relativamente semplice. A questo scopo è stata utilizzata la tecnologia [gRPC](grpc.io).
Essenzialmente è un framework multipiattaforma che permette di eseguire procedure su un server
remoto in modo scalabile e veloce.

Nel seguito analizzeremo i passaggi che ci hanno permesso di raggiungere l'obbiettivo.

# Deploy
In questa sezione analizziamo in dettaglio i principali passaggi che hanno
permesso l'inizializzazione dei server gRPC in ogni nodo della rete.

Prima di tutto avevamo la necessità di comunicare al *deployer* della topologia
l'utilizzo dei server gRPC. A tale scopo è stata creata una semplice struttura
dati da inserire manualmente nell'output json della topologia in questione:

"grpc_ipv6": {
"path": "/path/del/server/grpc.py"
}

L'unico attributo **"path"** indica il percorso del file python dove è
implementato il server gRPC.

In seguito abbiamo apportato delle modifiche al file python relativo al parsing
del codice JSON generato nell'interfaccia web:

*~/workspace/Dreamer-Topology-Parser-and-Validator/topo_parser.py*:

def parse_data(self):
self.load_advanced()
self.load_vertex()
self.load_links()
self.load_vss()
self.create_subnet()
self.load_grpc() # load gRPC path
self.parsed = True
...
def load_grpc(self):
if self.verbose:
print "*** Retrieve Grpc Options"
grpc_ipv6 = self.json_data['grpc_ipv6'] if 'grpc_ipv6' in self.json_data else []
self.path_grpc = grpc_ipv6['path']

La classe *TopoParser* viene istanziata nel file *~/workspace/Dreamer-Mininet-Extensions/mininet_deployer.py* che si occupa del
deploy della rete creata nell'interfaccia web, ovvero dell'instaurazione dei nodi e
dei links nell'emulatore Mininet. In particolare sono state aggiunte queste righe
nella creazione della rete:

*mininet_deployer.py*

if parser.path_grpc != "":
net.grpc_path = parser.path_grpc

Come spiegato nel dettaglio a seguire, grazie a questa modifica assegnamo al campo
*grpc_path* del costruttore dell'estensione custom della classe Mininet, la stringa
contenente il path del server gRPC.

La definizione dell'estensione della classe Mininet si trova nel file *~/workspace/Dreamer-Mininet-Extensions/mininet_extensions.py* dove sono definiti
anche tutti i nodi personalizzati dell'ambiente OSHI. In questa classe, in
particolare nel metodo *start()*, avviene la vera e propria instaurazione dei
server gRPC in tutti i nodi:

*mininet_extensions.py*

if self.grpc_path != "":
all_nodes = self.cr_oshis + self.pe_oshis + self.ce_routers
start_server = ["python",self.grpc_path,"&"]
print "Starting grpc servers with %s" % start_server
for node in all_nodes:
node_name = node.name
node.popen(start_server) # with cmd (for some unsolved reason) we were not able to run python
print "*** Starting grpc server on %s at %s" % (node_name, node.IP())

Inizialmente nell'array *all_nodes* vengono salvati i riferimenti agli
oggetti "nodo" (da notare che abbiamo volutamente escluso i controllers in
quato questi sarebbero i client del sistema gRPC) e salviamo in *start_server*
il comando bash per eseguire i server. In seguito nel costrutto *foreach*
cicliamo su tutti i nodi e in questi facciamo partire il server con il comando
**node.popen(start_server)**.

A questo proposito è doveroso aprire una parentesi:
il comando che viene utilizzato solitamente nei nodi mininet **node.cmd("")**,
non funzionava per il comando assegnato. Facendo diversi test ci siamo accorti
che, per qualche motivo che ancora non ci è noto, questo comando non esegue
applicativi python, ma tutto il resto sì. La soluzione più veloce è stata quella
di utilizzare per l'appunto il metodo sopracitato che, ai nostri fini, era
equivalente.

A questo punto, una volta effettuato il deploy della topologia, tutti i nodi
hanno il proprio server gRPC avviato, il cui funzionamento verrà esposto nella
sezione seguente.

# gRPC

Il motivo per cui usiamo gRPC è molto semplice: estendere le funzionalità di openflow tramite la tecnologia rpc.
Una soluzione alternativa sarebbe stata quella di usare le RESTapi ma in termini di prestazioni gRPC risulta nettamente superiore.

Al momento del deploy della rete ogni nodo, ad esclusione del controller, apre un socket sulla porta scelta per far comunicare server e client grpc (si è scelta la porta 50001). Precisamente il controller svolgerà la funzione di client mentre tutti gli altri nodi avranno la funzione di server. Questo perchè il controller vuole effettuare delle operazioni sui nodi che gestisce normalmente tramite openflow estendendo così le sue funzionalità con una tecnologia altrettanto prestazionale.

Un ulteriore vantaggio riscontrato è la semplicità del codice.
gRPC infatti, oltre ad avere una documentazione molto dettagliata, permette con facilità di realizzare un servizio utilizzando il linguaggio di programmazione che si desidera con poche righe e molto intuitivamente. La nostrà scelta è ricaduta su python. Vediamone i dettagli:

1) Si parte da un file con estensione .proto (qui si utilizza il linguaggio proto3). In questo andranno esplicitati sia i metodi di richiesta e risposta tra server e client sia i parametri che si vogliono controllare durante la comunicazione. Nel nostro caso, dato che abbiamo la necessità di configurare indirizzi ipv6 e le rotte sui nodi, si controlleranno i parametri **ipv6, subnet, interface** per ipv6 e **r_subnets, routes, r_devs** per le rotte. Il campo **mode** verrà utilizzato dal client e dal server per scegliere il tipo di servizio da utilizzare, o meglio scegliere se settare: indirizzi ipv6, rotte ipv6, indirizzi e rotte ipv6.

*ipset.proto*

...
message Request {
repeated string ipv6 = 1;
string subnet = 2;
repeated string iface = 3;
repeated string r_subnets = 4;
repeated string routes = 5;
repeated string r_devs = 6;
int32 mode = 7;
}

message Reply {
string message = 1;
}
...

Successivamente il file viene compilato con gli appositi tool che python mette a disposizione, nel caso specifico:

python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ipset.proto

Da qui vengono generati 2 file: **ipset_pb2.py** e **ipset_pb2_grpc.py** che contengono le classi e le funzioni necessarie per il passo successivo.

2) Ora dobbiamo creare il client ed il server utilizzando le 2 librerie generate al passo precendente.

*client.py*

def run(remote_ipv4,ipv6,subnet,iface,r_subnets,routes,r_devs,mode):
ok = False
while not ok:
try:
channel = grpc.insecure_channel(remote_ipv4+":50001")
stub = ipset_pb2_grpc.IpSetStub(channel)
response = stub.Set(ipset_pb2.Request(ipv6=ipv6,subnet=subnet,iface=iface,
r_subnets=r_subnets, routes=routes, r_devs=r_devs,
mode=mode))
except grpc._channel._Rendezvous as e:
print e
time.sleep(1)
print "Retrying..."
else:
print "Server at {} says: {}".format(remote_ipv4,response.message)
ok = True

Questo è la funzione chiave presente nel client. Volendo comunicare con i server ha bisogno dei loro indirizzi ipv4 e la porta scelta per la comunicazione gRPC. Da qui, se la richiesta va a buon fine, viene ricevuta una risposta di avvenuta comunicazione con l'ulteriore conferma che l'operazione che si voleva svolgere è andata a buon fine.

*server.py*

...
class GiveMe(ipset_pb2_grpc.IpSetServicer):

def Set(self, request, context):
if request.mode == MODE_ADDR:
set_addrs(request.ipv6, request.subnet, request.iface)
return ipset_pb2.Reply(message='I have set {} on subnet /{} and interface {}'
.format(request.ipv6,request.subnet,request.iface))

elif request.mode == MODE_ROUTES:
set_routes(request.r_subnets, request.routes, request.r_devs)
return ipset_pb2.Reply(message='I have set the routes')

elif request.mode == MODE_ADDR_ROUTES:
set_addrs(request.ipv6, request.subnet, request.iface)
set_routes(request.r_subnets, request.routes, request.r_devs)
return ipset_pb2.Reply(message='I have set {} on subnet /{} and interface {} and set the routes'
.format(request.ipv6,request.subnet,request.iface))

else:
return ipset_pb2.Reply(message="Unrecognized mode")


def set_addrs(ipv6, subnet, iface):
for i in range(len(ipv6)):
print "Command: sudo ip -6 addr add "+ipv6[i]+"/"+subnet+" dev "+iface[i]
os.system("sudo ip -6 addr add "+ipv6[i]+"/"+subnet+" dev "+iface[i])

def set_routes(r_subnets, routes, r_devs):
os.system('sudo sysctl -w net.ipv6.conf.all.forwarding=1')
for i in range(len(r_subnets)):
print "Command: sudo ip -6 r add "+r_subnets[i]+" via "+routes[i]+" dev "+r_devs[i]
os.system("sudo ip -6 r add "+r_subnet[i]+" via "+routes[i]+" dev "+r_devs[i])

def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=20))
ipset_pb2_grpc.add_IpSetServicer_to_server(GiveMe(), server)
server.add_insecure_port('[::]:50001')
server.start()
...

Il server è un tantino più complicato, ma nulla se si pensa che con poche righe abbiamo fatto svolgere una comunicazione molto efficente e, volendo, scalabile ad ogni servizio ci venga in mente.
Viene aperto un socket sulla porta 50001 in attesa di richieste provenienti dal client. Se si riceve una richiesta si controlla la variabile **mode** per interpretare quali operazioni è necessario svolgere, ossia: assegnazione di indirizzi ipv6, assegnazione di rotte o entrambe.
Vengono a questo punto lanciate le funzioni **set_addrs()** e/o **set_routes** in accordo con la variabile **mode** e successivamente recuperate le variabili spedite tramite richiesta grpc dal client. A questo punto si possono eseguire i comandi:

...
os.system("sudo ip -6 addr add "+ipv6[i]+"/"+subnet+" dev "+iface[i])
...
...
os.system("sudo ip -6 r add "+r_subnet[i]+" via "+routes[i]+" dev "+r_devs[i])
...

Viene poi inviata, tramite il metodo **Reply()**, la risposta al mittente dell'avvenuta configurazione richiesta.


# DEMO

In questa sezione descriviamo brevemente il procedimento per testare il lavoro esposto sopra.

- Una volta eseguita la macchina virtuale OSHI (in particolare abbiamo usato la versione 8),
avviare lo script **GO** sul desktop. Il sistema procederà ad avviare il server e
l'interfaccia web del framework OSHI.
- Bisogna creare un nuovo progetto e utilizzare la topologia d'esempio chiamata *example_network_3cr_2pe_2ce*
- Adesso è necessario apportare una modifica al codice json che descrive la topogia:
1. Selezionare *edit* nel menu a tendina;
2. Accedere al codice json;
3. Aggiungere l'oggetto json come proposto sopra;
4. Salvare.
- Lanciare un *deployment* della topologia.
- Eseguire un terminale e accedere al controllore attraverso ssh, la password è **root**:

ssh root@10.255.248.1

- Avviare il client gRPC:

cd /root/ipv6-oshi-grpc/grpc
python client.py

- Se tutto è andato bene per ogni nodo della rete si dovrebbero ricevere dei messaggi di
avvenuta configurazione
- Eseguire lo script contenuto nel repository per configurare ipv6 sul controllore:

./ctrl_ipv6_conf.sh

- Eseguire dei ping di prova che verifichino il corretto funzionamento della rete ipv6:

ping6 $node_ip





98 changes: 98 additions & 0 deletions ipv6_conf/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import grpc
import ipset_pb2
import ipset_pb2_grpc
import time

MODE_ADDR = 1;
MODE_ROUTES = 2;
MODE_ADDR_ROUTES = 3;

def run(remote_ipv4,ipv6,subnet,iface,r_subnets,routes,r_devs,mode):
ok = False
while not ok:
try:
channel = grpc.insecure_channel(remote_ipv4+":50001")
stub = ipset_pb2_grpc.IpSetStub(channel)
response = stub.Set(ipset_pb2.Request(ipv6=ipv6,subnet=subnet,iface=iface,
r_subnets=r_subnets, routes=routes, r_devs=r_devs,
mode=mode))
except grpc._channel._Rendezvous as e:
print e
time.sleep(1)
print "Retrying..."
else:
print "Server at {} says: {}".format(remote_ipv4,response.message)
ok = True


if __name__ == '__main__':
remote_ipv4_cro3 = "10.0.2.1" # used to contact a node
ipv6_cro3 = ["fd3c:9f20:5d73:03::01","fd3c:9f20:5d73:05::02","fd3c:9f20:5d73:06::02","fd3c:9f20:5d73:07::01","fd3c:9f20:5d73:0b::02"]
subnet_cro3 = "64"
iface_cro3 = ["vi1","vi2","vi3","vi4","vi5"]
r_subnets_cro3 = ["default","fd3c:9f20:5d73:01::/64","fd3c:9f20:5d73:0a::/64"]
routes_cro3 = ["fd3c:9f20:5d73:03::02","fd3c:9f20:5d73:05::01","fd3c:9f20:5d73:06::01"]
r_devs_cro3 = ["vi1","vi2","vi3"]

remote_ipv4_cro4 = "10.0.1.1" # used to contact a node
ipv6_cro4 = ["fd3c:9f20:5d73:02::02","fd3c:9f20:5d73:07::02","fd3c:9f20:5d73:08::02"]
subnet_cro4 = "64"
iface_cro4 = ["vi1","vi2","vi3"]
r_subnets_cro4 = ["default","fd3c:9f20:5d73:04::/64","fd3c:9f20:5d73:01::/64","fd3c:9f20:5d73:0b::/64"]
routes_cro4 = ["fd3c:9f20:5d73:08::01","fd3c:9f20:5d73:02::01","fd3c:9f20:5d73:02::01","fd3c:9f20:5d73:07::01"]
r_devs_cro4 = ["vi3","vi1","vi1","vi2"]

remote_ipv4_cro5 = "10.0.2.2" # used to contact a node
ipv6_cro5 = ["fd3c:9f20:5d73:03::02","fd3c:9f20:5d73:04::02","fd3c:9f20:5d73:09::02"]
subnet_cro5 = "64"
iface_cro5 = ["vi1","vi2","vi3"]
r_subnets_cro5 = ["default", "fd3c:9f20:5d73:01::/64", "fd3c:9f20:5d73:0a::/64", "fd3c:9f20:5d73:02::/64", "fd3c:9f20:5d73:08::/64"]
routes_cro5 = ["fd3c:9f20:5d73:03::01", "fd3c:9f20:5d73:04::01", "fd3c:9f20:5d73:09::01", "fd3c:9f20:5d73:04::01","fd3c:9f20:5d73:09::01"]
r_devs_cro5 = ["vi1","vi2", "vi3", "vi2", "vi3"]

remote_ipv4_peo2 = "10.0.0.2" # used to contact a node
ipv6_peo2 = ["fd3c:9f20:5d73:01::01","fd3c:9f20:5d73:02::01","fd3c:9f20:5d73:04::01","fd3c:9f20:5d73:05::01"]
subnet_peo2 = "64"
iface_peo2 = ["vi1","vi2","vi3","vi4"]
r_subnets_peo2 = ["default","fd3c:9f20:5d73:0b::/64"]
routes_peo2 = ["fd3c:9f20:5d73:04::02","fd3c:9f20:5d73:05::02"]
r_devs_peo2 = ["vi1","vi2","vi3","vi2","vi3"]

remote_ipv4_peo6 = "10.0.5.1" # used to contact a node
ipv6_peo6 = ["fd3c:9f20:5d73:06::01","fd3c:9f20:5d73:08::01","fd3c:9f20:5d73:09::01","fd3c:9f20:5d73:0a::01"]
subnet_peo6 = "64"
iface_peo6 = ["vi1","vi2","vi3","vi4"]
r_subnets_peo6 = ["default","fd3c:9f20:5d73:0b::/64"]
routes_peo6 = ["fd3c:9f20:5d73:09::02","fd3c:9f20:5d73:06::02"]
r_devs_peo6 = ["vi3","vi1"]

remote_ipv4_cer1 = "10.0.0.1" # used to contact a node
ipv6_cer1 = ["fd3c:9f20:5d73:01::02"]
subnet_cer1 = "64"
iface_cer1 = ["cer1-eth1"]
r_subnets_cer1 = ["default"]
routes_cer1 = ["fd3c:9f20:5d73:01::01"]
r_devs_cer1 = ["cer1-eth1"]

remote_ipv4_cer7 = "10.0.9.1" # used to contact a node
ipv6_cer7 = ["fd3c:9f20:5d73:0a::02"]
subnet_cer7 = "64"
iface_cer7 = ["cer7-eth1"]
r_subnets_cer7 = ["default"]
routes_cer7 = ["fd3c:9f20:5d73:0a::01"]
r_devs_cer7 = ["cer7-eth1"]

print "Trying with cro3"
run(remote_ipv4_cro3,ipv6_cro3,subnet_cro3,iface_cro3,r_subnets_cro3,routes_cro3,r_devs_cro3, MODE_ADDR_ROUTES)
print "Trying with cro4"
run(remote_ipv4_cro4,ipv6_cro4,subnet_cro4,iface_cro4,r_subnets_cro4,routes_cro4,r_devs_cro4, MODE_ADDR_ROUTES)
print "Trying with cro5"
run(remote_ipv4_cro5,ipv6_cro5,subnet_cro5,iface_cro5,r_subnets_cro5,routes_cro5,r_devs_cro5, MODE_ADDR_ROUTES)
print "Trying with peo2"
run(remote_ipv4_peo2,ipv6_peo2,subnet_peo2,iface_peo2,r_subnets_peo2,routes_peo2,r_devs_peo2, MODE_ADDR_ROUTES)
print "Trying with peo6"
run(remote_ipv4_peo6,ipv6_peo6,subnet_peo6,iface_peo6,r_subnets_peo6,routes_peo6,r_devs_peo6, MODE_ADDR_ROUTES)
print "Trying with cer1"
run(remote_ipv4_cer1,ipv6_cer1,subnet_cer1,iface_cer1,r_subnets_cer1,routes_cer1,r_devs_cer1, MODE_ADDR_ROUTES)
print "Trying with cer7"
run(remote_ipv4_cer7,ipv6_cer7,subnet_cer7,iface_cer7,r_subnets_cer7,routes_cer7,r_devs_cer7, MODE_ADDR_ROUTES)
7 changes: 7 additions & 0 deletions ipv6_conf/ctrl_ipv6_conf.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

sudo ip -6 addr add fd3c:9f20:5d73:0b::01/64 dev ctr8-eth1

sudo sysctl -w net.ipv6.conf.all.forwarding=1

sudo ip -6 route add default via fd3c:9f20:5d73:0b::02 dev ctr8-eth1
23 changes: 23 additions & 0 deletions ipv6_conf/ipset.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
syntax = "proto3";

option java_multiple_files = true;

package ipset;

service IpSet {
rpc Set (Request) returns (Reply) {}
}

message Request {
repeated string ipv6 = 1;
string subnet = 2;
repeated string iface = 3;
repeated string r_subnets = 4;
repeated string routes = 5;
repeated string r_devs = 6;
int32 mode = 7;
}

message Reply {
string message = 1;
}
Loading