Report: Added implementation, and experiments 1 and 2

This commit is contained in:
2026-09-27 18:23:43 +03:00
parent ad8414367d
commit 839dc03f84
2 changed files with 853 additions and 2 deletions
+853 -2
View File
@@ -1,4 +1,5 @@
% !TEX TS-program = xelatex
% !TeX document-id = {d31ec456-0f13-4d95-9fc2-bd99b70397a1}
% !TeX TXS-program:compile = txs:///xelatex/[-shell-escape]
% !TEX encoding = UTF-8 Unicode
% !TEX spellcheck = el-GR
%
@@ -75,6 +76,44 @@
\usepackage{footnote}
\usepackage{footmisc}
\usepackage{minted}
\usepackage{xcolor} %
\setminted[python]{
fontsize=\small,
breaklines,
autogobble,
baselinestretch=1.1,
obeytabs=true
tabsize=2,
numbersep=8pt,
startinline,
gobble=0
}
\setminted[json]{
fontsize=\small,
breaklines,
autogobble,
baselinestretch=1.1,
obeytabs=true
tabsize=2,
numbersep=8pt,
startinline,
gobble=0
}
\setminted[bash]{
fontsize=\small,
breaklines,
autogobble,
baselinestretch=1.1,
obeytabs=true
tabsize=2,
numbersep=8pt,
startinline,
gobble=0
}
%\definecolor{red-highlight}{rgb}{1.0, 0.11, 0.0}
\definecolor{red-highlight}{rgb}{1.0, 0.31, 0.0}
@@ -104,6 +143,16 @@
\newcommand{\dslDesignUrl}{\repoGithub/blob/HEAD/Mininet_DSL_Design.md}
% Experiment 1
\newcommand{\dhcpExecutionUrl}{\repoGithub/blob/HEAD/exercise/results/exp1-dhcp/execution.txt}
\newcommand{\dhcpDecodedUrl}{\repoGithub/blob/HEAD/exercise/results/exp1-dhcp/dhcp-decoded.txt}
\newcommand{\dhcpPcapUrl}{\repoGithub/blob/HEAD/exercise/results/exp1-dhcp/dhcp.pcap}
% Experiment 2
\newcommand{\arpExecutionUrl}{\repoGithub/blob/HEAD/exercise/results/exp2-arp/execution.txt}
\newcommand{\arpDecodedUrl}{\repoGithub/blob/HEAD/exercise/results/exp2-arp/arp-decoded.txt}
\newcommand{\arpPcapUrl}{\repoGithub/blob/HEAD/exercise/results/exp2-arp/arp.pcap}
\begin{document}
\InsertTitle
@@ -144,7 +193,10 @@ Language -- DSL}, και δημιουργεί δυναμικά τους αντί
\end{itemize}
\section{Υλοποίηση του Builder}
\section{Υλοποίηση}
% ========================= DSL =========================
\subsection{Δηλωτική γλώσσα περιγραφής τοπολογίας}
@@ -312,6 +364,805 @@ Layer-3 διευθυνσιοδότηση και ενεργοποιημένη π
ο σημασιολογικός έλεγχος πραγματοποιούνται σε \textbf{διαφορετικά στάδια}.
% ========================= Builder =========================
\subsection{Υλοποίηση του Builder}
Η υλοποίηση του builder ακολουθεί τον διαχωρισμό που εισάγει η DSL και οργανώνεται ως μία ακολουθία ανεξάρτητων
σταδίων, καθένα από τα οποία μετασχηματίζει την πληροφορία σε μία περισσότερο συγκεκριμένη αναπαράσταση.
Η δομή αυτή επιτρέπει τον σαφή διαχωρισμό ανάμεσα στην περιγραφή της τοπολογίας, στον έλεγχο της εγκυρότητάς της
και στην τελική δημιουργία και εκτέλεση του δικτύου στο Mininet.
\subsubsection{Δομή των αρχείων}
Ο πηγαίος κώδικας του builder βρίσκεται στον κατάλογο \texttt{source/}, ενώ η περιγραφή και τα αποτελέσματα της
συγκεκριμένης εργαστηριακής άσκησης διατηρούνται χωριστά στον κατάλογο \texttt{exercise/}.
Η βασική δομή του αποθετηρίου είναι η ακόλουθη:
\begin{verbatim}
.
|-- source/
| |-- build_network.py
| |-- topology_loader.py
| |-- topology_parser.py
| |-- topology_validator.py
| |-- topology_plan.py
| `-- topology_renderer.py
|
|-- exercise/
| |-- topology.json
| |-- topology.generated.py
| `-- results/
|
|-- Mininet_DSL_Design.md
`-- README.md
\end{verbatim}
\begin{itemize}
\item Το \texttt{topology\_loader.py} είναι υπεύθυνο αποκλειστικά για τη φόρτωση του αρχείου JSON από το σύστημα
αρχείων και την παραγωγή της αντίστοιχης ακατέργαστης Python δομής.
\item Το \texttt{topology\_parser.py} περιέχει τη γραμματική, τα \texttt{Enum} και τα \texttt{dataclass} αντικείμενα
που αποτελούν το τυποποιημένο σημασιολογικό μοντέλο της DSL.
\item Το \texttt{topology\_validator.py} ελέγχει τη σημασιολογική συνέπεια της τοπολογίας, χωρίς να πραγματοποιεί
οποιαδήποτε μεταβολή στο δίκτυο.
\item Το \texttt{topology\_plan.py} επιλύει τις αφηρημένες επιλογές της DSL και παράγει ένα πλήρως συγκεκριμένο
\texttt{ExecutionPlan}.
\item Το \texttt{topology\_renderer.py} μετατρέπει το σχέδιο αυτό σε εκτελέσιμο Python κώδικα για το Mininet, οποίος
εξάγεται σε ξεχωριστό αρχείο και αποτελεί ένα ιδιότυπο \emph{python-listing} του renderer.
\item Τέλος, το \texttt{build\_network.py} αποτελεί τον \textbf{coordinator} και το μοναδικό user-facing entry point,
συνδέοντας τα επιμέρους στάδια και αναλαμβάνοντας την τελική εκτέλεση του παραγόμενου προγράμματος.
\end{itemize}
\subsubsection{Χρήση}
Η κανονική χρήση του builder απαιτεί μόνο το αρχείο JSON που περιγράφει την επιθυμητή τοπολογία.
Για την τοπολογία της παρούσας εργασίας η πλήρης κατασκευή και εκτέλεση πραγματοποιείται με την εντολή:
\begin{minted}{bash}
sudo python3 source/build_network.py exercise/topology.json
\end{minted}
Η εκτέλεση αυτή μεταγλωττίζει πρώτα την τοπολογία, δημιουργεί το αρχείο
\texttt{exercise/topology.generated.py} και στη συνέχεια εκτελεί \textbf{το ίδιο ακριβώς παραγόμενο αρχείο}.
Μετά την κατασκευή και παραμετροποίηση του δικτύου ανοίγει το Mininet CLI, από το οποίο εκτελούνται τα πειράματα.
Κατά την ανάπτυξη είναι επίσης δυνατό να παραχθεί μόνο το ενδιάμεσο Python πρόγραμμα, χωρίς να δημιουργηθεί
κανένα network namespace ή άλλη κατάσταση στο σύστημα.
Για παράδειγμα:
\begin{minted}{bash}
python3 source/build_network.py exercise/topology.json --emit [Optional file name]
\end{minted}
Η μορφή αυτή δεν απαιτεί δικαιώματα root και είναι χρήσιμη για την επιθεώρηση του αποτελέσματος της
μεταγλώττισης ή για τον εντοπισμό σφαλμάτων πριν από την πραγματική εκτέλεση.
Το \texttt{--emit} μπορεί επιπλέον να δεχθεί συγκεκριμένο path για το παραγόμενο αρχείο.
Πριν από την εκτέλεση μπορεί επίσης να πραγματοποιηθεί έλεγχος του περιβάλλοντος με:
\begin{minted}{bash}
python3 source/build_network.py --env-check
\end{minted}
Ο έλεγχος επιβεβαιώνει την παρουσία του Mininet, του Open vSwitch και των εργαλείων Linux που απαιτούνται από
τον builder και τα πειράματα, όπως τα \texttt{ip}, \texttt{tc}, \texttt{sysctl}, \texttt{dnsmasq},
\texttt{dhclient}, \texttt{tcpdump} και \texttt{ping}.
Ο ίδιος έλεγχος πραγματοποιείται αυτόματα πριν από την πραγματική εκτέλεση μιας παραγόμενης τοπολογίας.
\subsubsection{Διαχωρισμός περιγραφής τοπολογίας και runtime κατάστασης}
Βασική σχεδιαστική επιλογή είναι ο \textbf{διαχωρισμός της δηλωμένης τοπολογίας από την κατάσταση που δημιουργείται
κατά την εκτέλεσή της}.
Το αρχείο JSON περιγράφει την επιθυμητή διαμόρφωση και τις πηγές από τις οποίες θα προκύψει η παραμετροποίηση,
χωρίς να επιχειρεί να αποθηκεύσει την παροδική κατάσταση ενός εκτελούμενου δικτύου.
Για παράδειγμα, η δήλωση:
\begin{minted}{json}
"net0": {
"addressing": "dhcp"
}
\end{minted}
δεν καθορίζει ποια διεύθυνση IP θα έχει τελικά ο host.
Δηλώνει ότι η διεύθυνση πρέπει να αποκτηθεί δυναμικά από μία υπηρεσία DHCP κατά την εκτέλεση της τοπολογίας.
Αντίστοιχα, ένας στατικός σύνδεσμος στο JSON οδηγεί στη δημιουργία πραγματικών virtual Ethernet interfaces,
χωρίς τα τελικά ονόματα αυτών των interfaces να χρειάζεται να εμφανίζονται στην αρχική περιγραφή.
Η ίδια διάκριση ισχύει για τις DHCP leases, τον ARP neighbor table, τους μετρητές πακέτων και τα αρχεία
\texttt{pcap}, τα οποία αποτελούν \emph{runtime state} και δεν ανήκουν στη δηλωτική γλώσσα.
Για τον ίδιο λόγο ο builder δεν εκκινεί αυτόματα το \texttt{dhclient} στους hosts που έχουν δηλωθεί ως DHCP
clients, καθώς η απόκτηση της διεύθυνσης αποτελεί μέρος της πειραματικής διαδικασίας της εργασίας.
Η αρχή αυτή αποτυπώνεται συνοπτικά ως:
\begin{verbatim}
Declarative topology Runtime state
static address -------> configured IP address
static gateway -------> routing-table entry
DHCP addressing -------> dynamically acquired lease
link declaration -------> veth / Mininet interfaces
service declaration -------> running process
ARP entries
packet captures
counters
\end{verbatim}
\subsubsection{Compiler-like αρχιτεκτονική}
Η εσωτερική αρχιτεκτονική του builder σχεδιάστηκε με λογική παρόμοια με αυτή ενός \textbf{μεταγλωττιστή}.
Αντί η εφαρμογή να διαβάζει ένα JSON και να καλεί άμεσα συναρτήσεις του Mininet, η περιγραφή περνά από
διαδοχικά στάδια, καθένα από τα οποία έχει σαφώς περιορισμένη ευθύνη.
Η συνολική ροή είναι:
\begin{verbatim}
topology.json
↓
[load]
↓
raw JSON dictionary
↓
[parse]
↓
typed Topology model
↓
[validate]
↓
validated Topology
↓
[plan]
↓
ExecutionPlan
↓
[render]
↓
topology.generated.py
↓
[execute]
↓
Mininet CLI
↓
[cleanup]
\end{verbatim}
Ο διαχωρισμός αυτός επιτρέπει σε κάθε στάδιο να εξετάζεται ανεξάρτητα και μεταφέρει σταδιακά την περιγραφή από
μία υψηλού επιπέδου δήλωση πρόθεσης σε μία πλήρως συγκεκριμένη ακολουθία ενεργειών Mininet και Linux.
Παράλληλα, κάθε κατηγορία σφάλματος ανήκει στο στάδιο που διαθέτει την απαραίτητη πληροφορία για να την εντοπίσει.
\paragraph{Φόρτωση -- \texttt{topology\_loader.py}.}
Το πρώτο στάδιο αφορά αποκλειστικά την πρόσβαση στο αρχείο και την ανάλυση της σύνταξης JSON.
Η δημόσια συνάρτηση \texttt{load\_topology()} διαβάζει το αρχείο και επιστρέφει ένα
\texttt{Dict[str, Any]}, χωρίς να γνωρίζει οτιδήποτε για τη σημασιολογία της DSL.
\begin{verbatim}
raw = load_topology("exercise/topology.json")
JSON file
|
`--> Dict[str, Any]
\end{verbatim}
Σφάλματα όπως ανύπαρκτο αρχείο, μη έγκυρο JSON ή root αντικείμενο διαφορετικό από JSON object αναφέρονται ως
\texttt{TopologyLoadError}.
Στο σημείο αυτό δεν εξετάζεται ακόμη αν ένα \texttt{type} είναι έγκυρο ή αν ένας σύνδεσμος αναφέρεται σε
υπαρκτό κόμβο.
\paragraph{Parsing -- \texttt{topology\_parser.py}.}
Το δεύτερο στάδιο μετατρέπει την ακατέργαστη δομή JSON στο \textbf{τυποποιημένο σημασιολογικό μοντέλο} της DSL.
Η δημόσια συνάρτηση \texttt{parse\_topology()} κατασκευάζει αντικείμενα όπως \texttt{Topology}, \texttt{Node},
\texttt{Interface}, \texttt{Link} και \texttt{Service}, χρησιμοποιώντας \texttt{Enum} για τα κλειστά σύνολα τιμών.
Για παράδειγμα, ένα τμήμα JSON όπως:
\begin{minted}{json}
{
"node": "s1",
"interface": "auto"
}
\end{minted}
δεν παραμένει ένα γενικό dictionary, αλλά μετατρέπεται εννοιολογικά σε:
\begin{minted}{python}
LinkEndpoint(
node="s1",
interface=LinkInterfaceSelector.AUTO
)
\end{minted}
Το στάδιο αυτό εφαρμόζει επίσης την \emph{αυστηρή γραμματική} της γλώσσας.
Άγνωστα πεδία ή μη υποστηριζόμενες τιμές προκαλούν \texttt{TopologyParseError}, αντί να αγνοούνται σιωπηρά.
\paragraph{Validation -- \texttt{topology\_validator.py}.}
Μετά το parsing η δομή είναι γραμματικά έγκυρη, αυτό όμως δεν συνεπάγεται ότι περιγράφει και συνεπές δίκτυο.
Η δημόσια συνάρτηση \texttt{validate()} πραγματοποιεί τους ελέγχους που απαιτούν γνώση περισσότερων από ενός
αντικειμένων της τοπολογίας.
Για παράδειγμα, το ακόλουθο JSON μπορεί να αναλυθεί συντακτικά, αλλά δεν περιγράφει έγκυρη στατική διεπαφή:
\begin{minted}{json}
"net0": {
"addressing": "static"
}
\end{minted}
Το validation θα απορρίψει τη συγκεκριμένη περιγραφή επειδή απουσιάζει η στατική διεύθυνση.
Αντίστοιχα ελέγχονται αναφορές σε ανύπαρκτους κόμβους, επαναχρησιμοποίηση interfaces, διευθύνσεις και gateways,
διαθεσιμότητα ports για \texttt{auto}, καθώς και η συνέπεια των ρυθμίσεων της υπηρεσίας DHCP.
Σε αντίθεση με ένα απλό fail-fast μοντέλο, ο validator συλλέγει τα προβλήματα σε αντικείμενα
\texttt{ValidationIssue} και τα επιστρέφει συγκεντρωτικά μέσω ενός \texttt{TopologyValidationError}.
Με αυτόν τον τρόπο ο χρήστης μπορεί να διορθώσει περισσότερα από ένα προβλήματα σε έναν κύκλο εκτέλεσης.
\paragraph{Planning -- \texttt{topology\_plan.py}.}
Το planning είναι το σημείο στο οποίο η έγκυρη πλέον περιγραφή μετατρέπεται σε μία
\textbf{πλήρως επιλυμένη ενδιάμεση αναπαράσταση}.
Η δημόσια συνάρτηση \texttt{plan()} παράγει ένα \texttt{ExecutionPlan}, στο οποίο δεν παραμένουν αφηρημένοι
selectors όπως \texttt{auto} ή άλλες αποφάσεις που πρέπει να ληφθούν από τον backend.
Για παράδειγμα, έστω ένας switch(node) με δύο ρητά δηλωμένες λογικές διεπαφές:
\begin{minted}{json}
{
"name": "s1",
"type": "switch",
"interfaces": {
"client": {},
"uplink": {}
}
}
\end{minted}
και δύο σύνδεσμοι, από τους οποίους ο πρώτος χρησιμοποιεί \texttt{auto}, ενώ ο δεύτερος ζητά ρητά τη διεπαφή
\texttt{uplink}:
\begin{minted}{json}
{
"endpoints": [
{"node": "h1", "interface": "net0"},
{"node": "s1", "interface": "auto"}
]
},
{
"endpoints": [
{"node": "r0", "interface": "lan"},
{"node": "s1", "interface": "uplink"}
]
}
\end{minted}
Πριν από την επίλυση των \texttt{auto}, ο planner εντοπίζει όλες τις ρητές αναφορές σε interfaces και δεσμεύει
τη \texttt{uplink}.
Το διαθέσιμο σύνολο διεπαφών του \texttt{s1} για αυτόματη επιλογή περιέχει επομένως μόνο τη \texttt{client}.
\begin{minted}{python}
reserved["s1"] = {"uplink"}
free_interfaces["s1"] = ["client"]
\end{minted}
Κατά το planning, το \texttt{auto} του πρώτου συνδέσμου επιλύεται συνεπώς στη λογική διεπαφή \texttt{client}.
Στο συγκεκριμένο παράδειγμα οι λογικές διεπαφές αντιστοιχίζονται στη συνέχεια και στα συγκεκριμένα ονόματα
διεπαφών που θα χρησιμοποιήσει το Mininet:
\begin{verbatim}
h1:net0 -> h1-eth0
s1:client -> s1-eth0
r0:lan -> r0-eth0
s1:uplink -> s1-eth1
\end{verbatim}
Με αυτόν τον τρόπο μία προηγούμενη αυτόματη επιλογή δεν μπορεί να καταλάβει interface που έχει ζητηθεί ρητά από
μεταγενέστερο link.
Το \texttt{auto} λειτουργεί επομένως ως \emph{επιλογέας διαθέσιμης λογικής διεπαφής} και όχι ως όνομα
διεπαφής.
Η συμπεριφορά είναι διαφορετική όταν το πεδίο \texttt{interfaces} έχει παραλειφθεί πλήρως από έναν κόμβο.
Σε αυτή την περίπτωση ο planner δημιουργεί συνθετικά λογικά ονόματα, όπως \texttt{auto0} και \texttt{auto1},
τα οποία αντιστοιχίζονται στη συνέχεια σε φυσικά ονόματα όπως \texttt{s1-eth0} και \texttt{s1-eth1}.
Στο ίδιο στάδιο αποφασίζονται και backend-specific λεπτομέρειες που δεν πρέπει να εμφανίζονται στη DSL.
Έτσι, η σημασιολογία ενός απλού Layer-2 switch μετατρέπεται από τον planner στην επιλογή
\texttt{switch\_fail\_mode = "standalone"} για το Open vSwitch.
Ένα απλοποιημένο τμήμα του \texttt{ExecutionPlan} για το προηγούμενο παράδειγμα μπορεί επομένως να θεωρηθεί ότι
έχει τη μορφή:
\begin{minted}{python}
PlannedLinkEndpoint(
node="s1",
logical_interface="client",
interface_name="s1-eth0"
)
PlannedLinkEndpoint(
node="s1",
logical_interface="uplink",
interface_name="s1-eth1"
)
PlannedNode(
name="s1",
type=NodeType.SWITCH,
switch_fail_mode="standalone"
)
\end{minted}
Ο planner δεν δημιουργεί interfaces και δεν τα εισάγει στο Mininet.
Η ευθύνη του τελειώνει όταν όλες οι αποφάσεις που απαιτούνται για την εκτέλεση έχουν αποτυπωθεί στο
\texttt{ExecutionPlan}.
\paragraph{Rendering -- \texttt{topology\_renderer.py}.}
Το renderer δέχεται το πλήρως επιλυμένο \texttt{ExecutionPlan} και το μετατρέπει σε ένα ολοκληρωμένο Python
πρόγραμμα μέσω της δημόσιας συνάρτησης \texttt{render\_python()}.
Σε αυτό το στάδιο \textbf{δεν πραγματοποιείται νέα σημασιολογική απόφαση}: ο renderer αποτυπώνει σε κώδικα τις
επιλογές που έχουν ήδη γίνει από τον planner.
Για το προηγούμενο παράδειγμα μπορούν να παραχθούν εντολές της μορφής:
\begin{minted}{python}
nodes['h1'] = net.addHost('h1', ip=None)
nodes['r0'] = net.addHost('r0', ip=None)
nodes['s1'] = net.addSwitch('s1', failMode='standalone')
net.addLink(
nodes['h1'],
nodes['s1'],
intfName1='h1-eth0',
intfName2='s1-eth0',
)
net.addLink(
nodes['r0'],
nodes['s1'],
intfName1='r0-eth0',
intfName2='s1-eth1',
)
\end{minted}
Παρατηρείται ότι στο παραγόμενο πρόγραμμα δεν υπάρχει πλέον ο selector \texttt{auto}.
Η επιλογή \texttt{s1:client} έχει ήδη πραγματοποιηθεί από τον planner και ο renderer γνωρίζει μόνο το πλήρως
επιλυμένο ζεύγος \texttt{s1:client} και \texttt{s1-eth0}.
Η χρήση του \texttt{ip=None} εμποδίζει το Mininet να εκχωρήσει τις δικές του προεπιλεγμένες διευθύνσεις στους
hosts, ώστε η διευθυνσιοδότηση να παραμένει αποκλειστικά υπό τον έλεγχο της DSL.
Ανάλογα με το σχέδιο, ο renderer παράγει επίσης εντολές για IP addresses, routes, sysctls, \texttt{TCLink} και
εκκίνηση του \texttt{dnsmasq}.
Το παραγόμενο πρόγραμμα περιλαμβάνει ακόμη το πλήρες lifecycle του δικτύου:
\begin{minted}{python}
net.build()
net.start()
# interface configuration
# routes
# sysctls
# services
CLI(net)
# finally:
# stop started services
# net.stop()
\end{minted}
\paragraph{Το παραγόμενο Python ως ενδιάμεση ``assembly''.}
Το αρχείο \texttt{topology.generated.py} έχει σκόπιμα κεντρικό ρόλο στην αρχιτεκτονική και δεν αποτελεί απλώς
ένα προσωρινό implementation detail.
Μπορεί να θεωρηθεί ως η \emph{``assembly'' της DSL}: είναι χαμηλότερου επιπέδου από το JSON, πλήρως συγκεκριμένο,
άμεσα εκτελέσιμο και ταυτόχρονα αρκετά αναγνώσιμο ώστε να μπορεί να επιθεωρηθεί από τον χρήστη.
Η αντιστοίχιση μπορεί να συνοψιστεί ως:
\begin{verbatim}
topology.json: declarative source
Topology: typed semantic representation
ExecutionPlan: resolved intermediate representation
topology.generated.py: executable "assembly"
Mininet / Linux: runtime
\end{verbatim}
Ο coordinator αποθηκεύει πάντοτε το παραγόμενο Python πριν από την εκτέλεση και στη συνέχεια εκτελεί ακριβώς
αυτό το αρχείο.
Δεν υπάρχει δεύτερος, ανεξάρτητος executor που να μεταφράζει απευθείας το \texttt{ExecutionPlan} σε Mininet
operations, αποφεύγοντας έτσι τον κίνδυνο διαφορετικής συμπεριφοράς μεταξύ του inspectable artifact και του
πραγματικού runtime.
\paragraph{Εκτέλεση και συντονισμός -- \texttt{build\_network.py}.}
Το \texttt{build\_network.py} συνθέτει τα προηγούμενα στάδια μέσω της συνάρτησης
\texttt{compile\_topology()}:
\begin{minted}{python}
topology = parse_topology(load_topology(path))
validate(topology)
execution_plan = plan(topology)
source = render_python(execution_plan)
\end{minted}
Το παραγόμενο source γράφεται στο \texttt{topology.generated.py} και, στην κανονική λειτουργία, εκτελείται ως
ξεχωριστό Python process με τα ίδια δικαιώματα με τον coordinator.
Η πραγματική εκτέλεση απαιτεί root privileges, ενώ η φόρτωση, το parsing, το validation, το planning και το
rendering μπορούν να πραγματοποιηθούν χωρίς αυτά.
Το generated πρόγραμμα δημιουργεί τους κόμβους και τα links, εφαρμόζει τη δικτυακή παραμετροποίηση, εκκινεί τις
προβλεπόμενες υπηρεσίες και παραδίδει τον έλεγχο στον χρήστη μέσω του Mininet CLI.
Η έξοδος από το CLI οδηγεί σε δομημένο cleanup μέσω \texttt{finally}, όπου τερματίζονται οι service processes που
ξεκίνησε το ίδιο το πρόγραμμα και στη συνέχεια καλείται το \texttt{net.stop()}.
\subsubsection{Έλεγχος των επιμέρους σταδίων}
Η ανεξαρτησία των σταδίων αξιοποιείται και κατά την ανάπτυξη και τον εντοπισμό σφαλμάτων.
Κάθε βασικό module του pipeline διαθέτει ένα μικρό \textbf{self-test του δημόσιου interface του}, το οποίο μπορεί
να εκτελεστεί χωρίς να δημιουργηθεί πραγματικό Mininet δίκτυο.
Για παράδειγμα:
\begin{minted}{bash}
python3 source/topology_loader.py
python3 source/topology_parser.py
python3 source/topology_validator.py
python3 source/topology_plan.py
python3 source/topology_renderer.py
\end{minted}
Μία επιτυχής εκτέλεση καταλήγει σε μήνυμα της μορφής:
\begin{verbatim}
topology_parser: self-test passed
\end{verbatim}
Τα tests δεν περιορίζονται στην απλή εισαγωγή των modules, αλλά ελέγχουν την κύρια συμπεριφορά του αντίστοιχου
δημόσιου API.
Ο parser, για παράδειγμα, ελέγχει τόσο σωστή μετατροπή σε typed objects όσο και απόρριψη άγνωστων πεδίων, ενώ ο
validator εξετάζει τόσο έγκυρες όσο και πολλαπλά λανθασμένες τοπολογίες.
Αντίστοιχα, ο planner ελέγχει την επίλυση του \texttt{auto}, την παραγωγή των physical interface names, την
επιλογή \texttt{standalone} για τους switches και την παραγωγή των απαιτούμενων sysctls.
Ο renderer ελέγχει ότι το παραγόμενο source είναι συντακτικά έγκυρο Python και ότι περιλαμβάνει τις αναμενόμενες
κλήσεις Mininet, τις υπηρεσίες και το cleanup.
Ο διαχωρισμός αυτός αποδείχθηκε ιδιαίτερα χρήσιμος κατά την ανάπτυξη, καθώς ένα σφάλμα μπορεί να απομονωθεί στο
στάδιο στο οποίο εμφανίζεται, χωρίς να απαιτείται κάθε φορά εκκίνηση του Mininet με δικαιώματα root.
Το \texttt{--emit} συμπληρώνει αυτή τη διαδικασία, επιτρέποντας την επιθεώρηση της τελικής
\emph{``assembly''} πριν από οποιαδήποτε μεταβολή της πραγματικής δικτυακής κατάστασης του συστήματος.
% ========================= Πείραμα 1 =========================
\section{Πείραμα 1 -- DHCP και διαδικασία D.O.R.A.}
\subsection{Σκοπός και πειραματική διαδικασία}
Σκοπός του πρώτου πειράματος είναι η παρατήρηση της διαδικασίας μέσω της οποίας ένας host χωρίς στατική
διευθυνσιοδότηση αποκτά τις απαραίτητες ρυθμίσεις δικτύου από έναν DHCP server.
Ειδικότερα, εξετάζεται η ακολουθία \textbf{Discover--Offer--Request--ACK (D.O.R.A.)} και τα αντίστοιχα πεδία των
Ethernet, IP και UDP headers.
Η τοπολογία εκκινήθηκε από τον κατάλογο \texttt{exercise/} με την εντολή:
\begin{minted}{bash}
sudo python3 ../source/build_network.py topology.json
\end{minted}
Πριν από την καταγραφή αφαιρέθηκε το αποθηκευμένο lease του \texttt{dhclient}, ώστε ο \texttt{h1} να ξεκινήσει
τη διαδικασία χωρίς να επιχειρήσει να επαναχρησιμοποιήσει διεύθυνση από προηγούμενη εκτέλεση.
Αφαιρέθηκε επίσης τυχόν προηγούμενο αρχείο καταγραφής και στη συνέχεια ξεκίνησε το \texttt{tcpdump} στη διεπαφή
\texttt{h1-eth0}.
Οι εντολές που εκτελέστηκαν στο Mininet CLI ήταν:
\begin{minted}{bash}
minted> h1 rm -f /var/lib/dhcp/dhclient.leases
minted> h1 rm -f /tmp/dhcp.pcap
minted> h1 tcpdump -i h1-eth0 -n -w /tmp/dhcp.pcap &
minted> h1 ps aux | grep tcpdump
minted> h1 dhclient h1-eth0
minted> h1 pkill -INT tcpdump
minted> h1 tcpdump -n -e -vv -r /tmp/dhcp.pcap
\end{minted}
Η επιλογή \texttt{-e} κατά την ανάγνωση της καταγραφής επιτρέπει την εμφάνιση των Ethernet headers και επομένως
των MAC διευθύνσεων που απαιτούνται για την ανάλυση.
Η καταγραφή ολοκληρώθηκε χωρίς απώλειες από το \texttt{tcpdump}, με 12 πακέτα να έχουν καταγραφεί συνολικά.
Η πλήρης συνεδρία της εκτέλεσης βρίσκεται στο \href{\dhcpExecutionUrl}{\texttt{execution.txt}}.
Το αρχικό αρχείο καταγραφής διατίθεται στο \href{\dhcpPcapUrl}{\texttt{dhcp.pcap}}, ενώ η αναλυτικά
αποκωδικοποιημένη μορφή του βρίσκεται στο \href{\dhcpDecodedUrl}{\texttt{dhcp-decoded.txt}}.
\subsection{Αποτελέσματα καταγραφής -- Ζητούμενο 1.1}
Η καταγραφή περιλαμβάνει την αναμενόμενη ακολουθία
\textbf{Discover $\rightarrow$ Offer $\rightarrow$ Request $\rightarrow$ ACK}, η οποία ολοκληρώθηκε επιτυχώς.
Στην πραγματική εκτέλεση καταγράφηκαν δύο DHCP Discover με το ίδιο transaction ID πριν από το πρώτο Offer.
Η χρονική ακολουθία των σχετικών DHCP μηνυμάτων ήταν:
\begin{verbatim}
10:58:29.655251 DHCP Discover
10:58:32.214530 DHCP Discover
10:58:32.670897 DHCP Offer
10:58:32.671193 DHCP Request
10:58:32.673025 DHCP ACK
10:58:32.678132 DHCP Offer
\end{verbatim}
Μεταξύ του πρώτου Discover και του πρώτου Offer παρατηρήθηκαν τρία ARP probes από τον DHCP server για τη
διεύθυνση \texttt{192.168.1.131} και στη συνέχεια ένα ICMP Echo Request προς την ίδια διεύθυνση.
Ο έλεγχος αυτός εισήγαγε καθυστέρηση στην πρώτη απάντηση του server, με αποτέλεσμα ο \texttt{dhclient} να
επαναμεταδώσει το Discover περίπου τρία δευτερόλεπτα μετά το πρώτο.
Και τα δύο Discover χρησιμοποιούν το ίδιο transaction ID, \texttt{0x0ecb5a2e}, επομένως αποτελούν μέρος της
ίδιας προσπάθειας απόκτησης διεύθυνσης και όχι δύο ανεξάρτητες διαδικασίες DHCP.
Παρά την επαναμετάδοση, η διαδικασία ολοκληρώθηκε κανονικά με Offer, Request και ACK και ο \texttt{h1} έλαβε τη
διεύθυνση \texttt{192.168.1.131}.
Μετά το ACK καταγράφηκε ακόμη ένα Offer, χωρίς αυτό να επηρεάζει την ήδη ολοκληρωμένη παραχώρηση της διεύθυνσης.
Η βασική τετράδα D.O.R.A. δίνει τα ακόλουθα στοιχεία:
\begin{table}[H]
\centering
\small
\begin{tabularx}{\textwidth}{
>{\raggedright\arraybackslash}p{1.3cm}
>{\raggedright\arraybackslash}p{5.2cm}
>{\raggedright\arraybackslash}p{2.0cm}
>{\raggedright\arraybackslash}X
}
\textbf{Μήνυμα} &
\textbf{IP} &
\textbf{UDP port} &
\textbf{MAC} \\ \hline
% \textbf{Μήνυμα} &
% \textbf{από $\rightarrow$ προς} &
% \textbf{UDP port από $\rightarrow$ προς} &
% \textbf{MAC από $\rightarrow$ προς} \\ \hline
Discover &
\texttt{0.0.0.0 $\rightarrow$ 255.255.255.255} &
\texttt{68 $\rightarrow$ 67} &
\texttt{12:07:f8:bf:6e:ff $\rightarrow$ ff:ff:ff:ff:ff:ff} \\
Offer &
\texttt{192.168.1.1 $\rightarrow$ 192.168.1.131} &
\texttt{67 $\rightarrow$ 68} &
\texttt{82:3b:16:e9:bb:17 $\rightarrow$ 12:07:f8:bf:6e:ff} \\
Request &
\texttt{0.0.0.0 $\rightarrow$ 255.255.255.255} &
\texttt{68 $\rightarrow$ 67} &
\texttt{12:07:f8:bf:6e:ff $\rightarrow$ ff:ff:ff:ff:ff:ff} \\
ACK &
\texttt{192.168.1.1 $\rightarrow$ 192.168.1.131} &
\texttt{67 $\rightarrow$ 68} &
\texttt{82:3b:16:e9:bb:17 $\rightarrow$ 12:07:f8:bf:6e:ff} \\
\end{tabularx}
\caption{Διευθύνσεις και UDP θύρες των μηνυμάτων της διαδικασίας D.O.R.A.}
\end{table}
\subsection{Ανάλυση της διαδικασίας D.O.R.A.}
Στο \textbf{Discover} ο \texttt{h1} δεν διαθέτει ακόμη διεύθυνση IPv4 και αναζητά διαθέσιμο DHCP server στο
τοπικό δίκτυο.
Για τον λόγο αυτό χρησιμοποιεί ως source IP την \texttt{0.0.0.0} και μεταδίδει το μήνυμα προς την περιορισμένη
broadcast διεύθυνση \texttt{255.255.255.255}, με Ethernet destination \texttt{ff:ff:ff:ff:ff:ff}.
Στο \textbf{Offer} ο DHCP server του \texttt{r0}, με διεύθυνση \texttt{192.168.1.1}, προσφέρει στον client τη
διεύθυνση \texttt{192.168.1.131}.
Στο ίδιο μήνυμα περιλαμβάνονται επίσης η μάσκα \texttt{255.255.255.0}, η broadcast διεύθυνση
\texttt{192.168.1.255} και η προεπιλεγμένη πύλη \texttt{192.168.1.1}.
Ο \texttt{h1} απαντά με \textbf{Request}, στο οποίο δηλώνει τόσο τον server που επέλεξε όσο και τη διεύθυνση που
ζητά να του εκχωρηθεί.
Τα αντίστοιχα options που καταγράφηκαν είναι:
\begin{minted}{text}
Server-ID Option: 192.168.1.1
Requested-IP Option: 192.168.1.131
\end{minted}
Τέλος, με το \textbf{ACK} ο server επιβεβαιώνει την παραχώρηση της \texttt{192.168.1.131} και αποστέλλει τις
τελικές παραμέτρους του lease.
Στην καταγραφή το lease time είναι \texttt{43200} δευτερόλεπτα και η προεπιλεγμένη πύλη που παρέχεται στον
client είναι η \texttt{192.168.1.1}.
\subsection{Broadcast μετάδοση του DHCP Request -- Ζητούμενο 1.2}
Το DHCP Request της αρχικής διαδικασίας απόκτησης διεύθυνσης μεταδόθηκε ως \textbf{broadcast} τόσο στο επίπεδο
Ethernet όσο και στο επίπεδο IP.
Στην καταγραφή παρατηρούνται συγκεκριμένα:
\begin{minted}{text}
Ethernet destination: ff:ff:ff:ff:ff:ff
IP destination: 255.255.255.255
UDP ports: 68 -> 67
\end{minted}
Κατά τη στιγμή αποστολής του Request η παραχώρηση της διεύθυνσης δεν έχει ακόμη ολοκληρωθεί και ο client
συνεχίζει να χρησιμοποιεί την \texttt{0.0.0.0} ως source IP.
Η broadcast μετάδοση επιτρέπει επιπλέον σε όλους τους DHCP servers του τοπικού δικτύου να παρατηρήσουν ποια
προσφορά επέλεξε ο client.
Ο επιλεγμένος server αναγνωρίζεται από το \texttt{Server-ID} του Request, ενώ τυχόν άλλοι servers μπορούν να
διαπιστώσουν ότι οι δικές τους προσφορές δεν επιλέχθηκαν.
Το ACK του \texttt{192.168.1.1} ολοκληρώνει τελικά τη διαδικασία και επιβεβαιώνει ότι η
\texttt{192.168.1.131} μπορεί πλέον να χρησιμοποιηθεί από τον \texttt{h1}.
% ========================= Πείραμα 2 =========================
\section{Πείραμα 2 -- Πρωτόκολλο ARP}
\subsection{Σκοπός και πειραματική διαδικασία}
Σκοπός του δεύτερου πειράματος είναι η παρατήρηση της διαδικασίας αντιστοίχισης μιας γνωστής διεύθυνσης IPv4
στην αντίστοιχη διεύθυνση MAC μέσω του πρωτοκόλλου \textbf{ARP}.
Ειδικότερα, εξετάζεται η αρχική επίλυση της διεύθυνσης του \texttt{h2} από τον \texttt{h1} και η διαφορετική
μορφή μετάδοσης του ARP Request και του ARP Reply.
Η τοπολογία εκκινήθηκε εκ νέου και οι δύο hosts του Subnet A απέκτησαν διευθύνσεις μέσω DHCP.
Οι διευθύνσεις που εκχωρήθηκαν κατά τη συγκεκριμένη εκτέλεση ήταν:
\begin{itemize}
\item \texttt{\textbf{192.168.1.131/24}} για τον \texttt{\textbf{h1}} και
\item \texttt{\textbf{192.168.1.164/24}} για τον \texttt{\textbf{h2}}.
\end{itemize}
Οι αρχικές εντολές της εκτέλεσης ήταν:
\begin{minted}{bash}
sudo python3 ../source/build_network.py topology.json
minted> h1 ip -4 -br addr
minted> h2 ip -4 -br addr
minted> h1 dhclient h1-eth0
minted> h2 dhclient h2-eth0
minted> h1 ip -4 -br addr
minted> h2 ip -4 -br addr
\end{minted}
Πριν από το ping διαγράφηκε ο neighbor table του \texttt{h1}, ώστε να μην υπάρχει ήδη γνωστή αντιστοίχιση
μεταξύ της IP και της MAC διεύθυνσης του \texttt{h2}.
Η κενή κατάσταση του πίνακα επιβεβαιώθηκε με την εντολή \texttt{ip neigh show}.
\begin{minted}{bash}
minted> h1 ip neigh flush all
minted> h1 ip neigh show
minted> h1 rm -f /tmp/arp.pcap
minted> h1 tcpdump -i h1-eth0 -n arp -w /tmp/arp.pcap &
minted> h1 ping -c 1 192.168.1.164
minted> h1 ip neigh show
minted> h1 pkill -INT tcpdump
minted> h1 tcpdump -n -e -vv -r /tmp/arp.pcap
\end{minted}
Το φίλτρο \texttt{arp} του \texttt{tcpdump} περιορίζει την καταγραφή στα ARP frames, ενώ η επιλογή \texttt{-e}
κατά την ανάγνωση εμφανίζει τα Ethernet headers και επομένως τις MAC διευθύνσεις πηγής και προορισμού.
Η καταγραφή ολοκληρώθηκε χωρίς απώλειες, με τέσσερα ARP frames να έχουν καταγραφεί συνολικά.
Η πλήρης συνεδρία της εκτέλεσης βρίσκεται στο \href{\arpExecutionUrl}{\texttt{execution.txt}}.
Το αρχικό αρχείο καταγραφής διατίθεται στο \href{\arpPcapUrl}{\texttt{arp.pcap}}, ενώ η αποκωδικοποιημένη μορφή του βρίσκεται στο
\href{\arpDecodedUrl}{\texttt{arp-decoded.txt}}.
\subsection{Αποτελέσματα καταγραφής}
Μετά την εκκαθάριση του neighbor table, το πρώτο ping από τον \texttt{h1} προς τον \texttt{h2} απαίτησε την
επίλυση της MAC διεύθυνσης που αντιστοιχεί στην \texttt{192.168.1.164}.
Το πρώτο ζεύγος πακέτων της καταγραφής ήταν:
\begin{minted}{text}
4a:6f:2a:18:f4:80 > ff:ff:ff:ff:ff:ff
Request who-has 192.168.1.164 tell 192.168.1.131
82:c0:6d:ae:3f:56 > 4a:6f:2a:18:f4:80
Reply 192.168.1.164 is-at 82:c0:6d:ae:3f:56
\end{minted}
Από την καταγραφή προκύπτει επομένως ότι στη συγκεκριμένη εκτέλεση οι δύο hosts είχαν τα ακόλουθα στοιχεία:
\begin{table}[H]
\centering
\begin{tabular}{lll}
\textbf{Host} & \textbf{IPv4} & \textbf{MAC} \\ \hline
\texttt{h1} & \texttt{192.168.1.131} & \texttt{4a:6f:2a:18:f4:80} \\
\texttt{h2} & \texttt{192.168.1.164} & \texttt{82:c0:6d:ae:3f:56} \\
\end{tabular}
\caption{Διευθύνσεις των hosts που συμμετέχουν στο πείραμα ARP.}
\end{table}
Το ping ολοκληρώθηκε επιτυχώς και, μετά την ανταλλαγή ARP, ο neighbor table του \texttt{h1} περιείχε (λογικά) την
αντιστοίχιση:
\begin{minted}{text}
192.168.1.164 dev h1-eth0 lladdr 82:c0:6d:ae:3f:56 REACHABLE
\end{minted}
Το αποτέλεσμα αυτό επιβεβαιώνει ότι ο \texttt{h1} έμαθε τη MAC διεύθυνση του \texttt{h2} μέσω της προηγούμενης
ανταλλαγής ARP και μπορεί πλέον να δημιουργεί απευθείας Ethernet frames προς αυτόν.
\subsection{Ανάλυση του ARP Request και Reply -- Ζητούμενο 2.1}
Κατά την αποστολή του αρχικού ARP Request ο \texttt{h1} γνωρίζει τη διεύθυνση IP του προορισμού, αλλά
\textbf{δεν γνωρίζει ακόμη τη MAC διεύθυνσή του}.
Δεν μπορεί επομένως να δημιουργήσει ένα unicast Ethernet frame προς τον \texttt{h2}.
Για τον λόγο αυτό το ARP Request μεταδίδεται προς την Ethernet broadcast διεύθυνση:
\begin{minted}{text}
Source MAC: 4a:6f:2a:18:f4:80
Destination MAC: ff:ff:ff:ff:ff:ff
Who has 192.168.1.164?
Tell 192.168.1.131
\end{minted}
Όλοι οι κόμβοι του ίδιου Layer-2 broadcast domain μπορούν να λάβουν το Request, αλλά μόνο ο host στον οποίο
ανήκει η \texttt{192.168.1.164} πρέπει να απαντήσει.
Στην συγκεκριμένη περίπτωση ο host αυτός είναι ο \texttt{h2}.
Το ARP Reply, αντίθετα, μεταδίδεται ως \textbf{unicast}:
\begin{minted}{text}
Source MAC: 82:c0:6d:ae:3f:56
Destination MAC: 4a:6f:2a:18:f4:80
192.168.1.164 is-at 82:c0:6d:ae:3f:56
\end{minted}
Ο \texttt{h2} μπορεί να στείλει την απάντηση απευθείας στον \texttt{h1}, επειδή το ARP Request περιλαμβάνει τα
στοιχεία του αποστολέα και επομένως παρέχει στον \texttt{h2} τη MAC διεύθυνση στην οποία πρέπει να απαντήσει.
Δεν υπάρχει συνεπώς ανάγκη το Reply να μεταδοθεί σε όλους τους κόμβους του broadcast domain.
\textbf{Η MAC προορισμού του αρχικού ARP Request είναι επομένως \texttt{ff:ff:ff:ff:ff:ff},
ενώ το ARP Reply επιστρέφει ως unicast στη MAC \texttt{4a:6f:2a:18:f4:80} του \texttt{h1}.}
\subsection{Πρόσθετη παρατήρηση}
Στην καταγραφή εμφανίστηκε περίπου πέντε δευτερόλεπτα αργότερα και ένα δεύτερο ζεύγος ARP Request/Reply προς
την αντίθετη κατεύθυνση:
\begin{minted}{text}
82:c0:6d:ae:3f:56 > 4a:6f:2a:18:f4:80
Request who-has 192.168.1.131 tell 192.168.1.164
4a:6f:2a:18:f4:80 > 82:c0:6d:ae:3f:56
Reply 192.168.1.131 is-at 4a:6f:2a:18:f4:80
\end{minted}
Σε αντίθεση με το αρχικό Request, το δεύτερο Request έχει ήδη ως Ethernet destination τη MAC του
\texttt{h1} και επομένως είναι unicast.
Η ανταλλαγή αυτή σχετίζεται με τη διαχείριση και επιβεβαίωση της κατάστασης του neighbor cache από το Linux και
δεν αποτελεί την αρχική επίλυση που προκαλεί το ping.
Για το ζητούμενο του πειράματος εξετάζεται συνεπώς το \textbf{πρώτο ζεύγος Request/Reply}, στο οποίο φαίνεται
καθαρά η μετάβαση από broadcast αναζήτηση σε unicast απάντηση.
Παρόλα αυτά το γεγονός ότι υπήρχε δεύτερο ζεύγος μας προβλημάτισε ιδιαίτερα μέχρι να ανακαλύψουμε τον λόγο ύπαρξής του!
\subsection{Συμπεράσματα}