RNDMISC-441: Second Milestone
This commit is contained in:
BIN
TINF-Praxis-Bestaetigung.docx
Normal file
BIN
TINF-Praxis-Bestaetigung.docx
Normal file
Binary file not shown.
5
abk.tex
5
abk.tex
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
\chapter*{Abkürzungsverzeichnis} % chapter*{..} --> keine Nummer, kein "Kapitel"
|
\chapter*{Abkürzungsverzeichnis} % chapter*{..} --> keine Nummer, kein "Kapitel"
|
||||||
% Nicht ins Inhaltsverzeichnis
|
% Nicht ins Inhaltsverzeichnis
|
||||||
% \addcontentsline{toc}{chapter}{Akürzungsverzeichnis} % Damit das doch ins Inhaltsverzeichnis kommt
|
\addcontentsline{toc}{chapter}{Akürzungsverzeichnis} % Damit das doch ins Inhaltsverzeichnis kommt
|
||||||
|
|
||||||
% Hier werden die Abkürzungen definiert
|
% Hier werden die Abkürzungen definiert
|
||||||
\begin{acronym}[DHBW]
|
\begin{acronym}[DHBW]
|
||||||
@@ -40,6 +40,9 @@
|
|||||||
\acro{OSPF}[OSPF]{Open Shortest Path First}
|
\acro{OSPF}[OSPF]{Open Shortest Path First}
|
||||||
\acro{ASN}[ASN]{Autonome System Nummer}
|
\acro{ASN}[ASN]{Autonome System Nummer}
|
||||||
\acro{JSON}[JSON]{JavaScript Object Notation}
|
\acro{JSON}[JSON]{JavaScript Object Notation}
|
||||||
|
\acro{SSH}[SSH]{Secure Shell}
|
||||||
|
\acro{TTL}[TTL]{Time to live}
|
||||||
|
\acro{DRY}[DRY]{Don't repeat yourself}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
BIN
bericht.pdf
BIN
bericht.pdf
Binary file not shown.
@@ -179,7 +179,7 @@ Vgl.\cite{\thefield{entrykey}}}
|
|||||||
commentstyle=\color{olive},
|
commentstyle=\color{olive},
|
||||||
keywordstyle=\color{magenta},
|
keywordstyle=\color{magenta},
|
||||||
numberstyle=\tiny\color{lightgray},
|
numberstyle=\tiny\color{lightgray},
|
||||||
stringstyle=\color{violet},
|
stringstyle=\color{orange},
|
||||||
basicstyle=\ttfamily\footnotesize,
|
basicstyle=\ttfamily\footnotesize,
|
||||||
breakatwhitespace=false,
|
breakatwhitespace=false,
|
||||||
breaklines=true,
|
breaklines=true,
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ Gutachter der Studienakademie & \BetreuerDHBW \\
|
|||||||
\end{titlepage}
|
\end{titlepage}
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
\pagenumbering{Roman}
|
||||||
\input{erklaerung.tex}
|
\input{erklaerung.tex}
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
@@ -163,12 +163,16 @@ Gutachter der Studienakademie & \BetreuerDHBW \\
|
|||||||
\newpage
|
\newpage
|
||||||
\tableofcontents % Inhaltsverzeichnis hier ausgeben
|
\tableofcontents % Inhaltsverzeichnis hier ausgeben
|
||||||
\listoffigures % Liste der Abbildungen
|
\listoffigures % Liste der Abbildungen
|
||||||
|
\addcontentsline{toc}{chapter}{Abbildungsverzeichnis}
|
||||||
\listoftables % Liste der Tabellen
|
\listoftables % Liste der Tabellen
|
||||||
|
\addcontentsline{toc}{chapter}{Tabellenverzeichnis}
|
||||||
\lstlistoflistings % Liste der Listings
|
\lstlistoflistings % Liste der Listings
|
||||||
\listofequations % Liste der Formeln
|
\addcontentsline{toc}{chapter}{Liste der Code Snippets}
|
||||||
|
|
||||||
% Jetzt kommt der "eigentliche" Text
|
% Jetzt kommt der "eigentliche" Text
|
||||||
\include{abk} % Abkürzungsverzeichnis
|
\include{abk} % Abkürzungsverzeichnis
|
||||||
|
|
||||||
|
\pagenumbering{arabic}
|
||||||
\include{kapitel1}
|
\include{kapitel1}
|
||||||
\include{kapitel2}
|
\include{kapitel2}
|
||||||
\include{kapitel3}
|
\include{kapitel3}
|
||||||
|
|||||||
628
kapitel4.tex
628
kapitel4.tex
@@ -122,20 +122,638 @@ Mit seiner intelligenten Konvertierung und Verwaltung von Routen durch \ac{BGP}
|
|||||||
|
|
||||||
\subsection{Generieren der Config Files für Bird} %TODO RNDMISC-410
|
\subsection{Generieren der Config Files für Bird} %TODO RNDMISC-410
|
||||||
|
|
||||||
Um die Routen an den Bird Routing Daemon übermitteln zu können, müssen diese erst in eine für Bird verständliche Konfigurationsdatei umgewandelt werden.
|
Für den einfachen Umgang mit Routen wurde für die \verb|routes| Variable eine Python Dataclass angelegt, welche das IP-Präfix, die IP-Version und eine Liste der gesetzen \ac{BGP}-Communities enthält.
|
||||||
%Warum verwenden wir Jinja und wie funktionierts?
|
|
||||||
|
|
||||||
\subsubsection{Integrität der Konfigurationsdatei sicherstellen} %TODO RNDMISC-420
|
\begin{lstlisting}[language=python,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:route-dataclass},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={Python Dataclass für Routen Objekte}]
|
||||||
|
@dataclass
|
||||||
|
class Route:
|
||||||
|
prefix: str
|
||||||
|
encode: str
|
||||||
|
communities: list[str]
|
||||||
|
|
||||||
|
def __init__(self, prefix="", encode="", communities=[]):
|
||||||
|
self.prefix = prefix
|
||||||
|
self.encode = encode
|
||||||
|
self.communities = communities
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.encode} {self.prefix} {self.communities}"
|
||||||
|
|
||||||
|
def decode_prefix(self):
|
||||||
|
self.prefix = self.prefix.replace("_", "/")
|
||||||
|
|
||||||
|
def encode_prefix(self):
|
||||||
|
self.prefix = self.prefix.replace("/", "_")
|
||||||
|
|
||||||
|
def get_communities(self):
|
||||||
|
communities = self.communities
|
||||||
|
return list(map(lambda com: com.replace(":", ","), communities))
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
Neben den genannten Feldern, kann die Dataclass auch noch Methoden zur verarbeitung der Felder, ähnlich wie eine normale Klasse beinhalten.
|
||||||
|
Besonders zu betonen ist hier die \verb|get_communities| Methode, welche die Communities in ein von Bird akzeptiertes Format umwandelt.
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
Die in Consul gespeicherten Routen werden dann periodisch von Consul über dessen eigene \ac{API} abgefragt.
|
||||||
|
Hierfür ist die \verb|parse_consul_values| Methode zuständig.
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=python,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:parse-consul-values},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={parse\_consul\_values Methode}]
|
||||||
|
def parse_consul_values(values, watched_prefix) -> (list[Route], list[Route]):
|
||||||
|
if not values:
|
||||||
|
return [], []
|
||||||
|
|
||||||
|
v4_routes = []
|
||||||
|
v6_routes = []
|
||||||
|
|
||||||
|
for entry in values:
|
||||||
|
route_entry = Route()
|
||||||
|
route_entry.prefix = entry["Key"].split(watched_prefix)[1]
|
||||||
|
json_communities = entry["Value"].decode("utf-8")
|
||||||
|
route_entry.communities = json.loads(json_communities)["communities"]
|
||||||
|
|
||||||
|
route_entry.encode, route_entry.prefix = route_entry.prefix.split("/")
|
||||||
|
route_entry.decode_prefix()
|
||||||
|
|
||||||
|
if route_entry.encode == "IPv4":
|
||||||
|
v4_routes.append(route_entry)
|
||||||
|
|
||||||
|
elif route_entry.encode == "IPv6":
|
||||||
|
v6_routes.append(route_entry)
|
||||||
|
return v4_routes, v6_routes
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
Im Laufe der Methode werden die abgefragten Einträge in Routenobjekte umgewandelt.
|
||||||
|
Um herauszufinden um welche IP Version es sich bei der Route handelt, wird der entsprechende Key des Pfades ausgelesen, da dieser die IP Version mit im Namen trägt.
|
||||||
|
Als Resultat gibt die Methode ein Tupel zurück, wobei eines die IPv4 Routen und das andere die IPv6 Routen sind.
|
||||||
|
Diese Aufteilung ist notwendig, da die in Debian 11 mitgelieferte Version von Bird eine klare Auftrennung dieser fordert.
|
||||||
|
Neuere Versionen von Bird können auch mit beiden IP Versionen gleichzeitig umgehen.
|
||||||
|
|
||||||
|
Durch die Auftrennung der beiden IP-Versionen, muss auch die Konfigurationsdatei für Bird, zweimal generiert werden.
|
||||||
|
Mit der Methode \verb|generate_bird_files| werden Umgebungsvariablen geladen, welche für die Generierung der Konfiguration benötigt werden.
|
||||||
|
Neben diesen übergibt die Methode auch die Routen als Parameter weiter.
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=python,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:generate-bird-files},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={generate\_bird\_files Methode}]
|
||||||
|
def generate_bird_files(v4_routes, v6_routes, pybird, pybird6):
|
||||||
|
gen = BirdConfigGenerator()
|
||||||
|
click.echo("Generating and committing config files")
|
||||||
|
gen.bird_config(
|
||||||
|
v4_routes,
|
||||||
|
"route_template.j2",
|
||||||
|
"../config/bird",
|
||||||
|
"v4.conf",
|
||||||
|
os.getenv("ROUTER_ID"),
|
||||||
|
os.getenv("LOCAL_AS"),
|
||||||
|
os.getenv("REMOTE_AS"),
|
||||||
|
os.getenv("BGP_NEIGHBOR"),
|
||||||
|
"ANEXIA Route Injection v4",
|
||||||
|
)
|
||||||
|
|
||||||
|
gen.bird_config(
|
||||||
|
v6_routes,
|
||||||
|
"route_template.j2",
|
||||||
|
"../config/bird",
|
||||||
|
"v6.conf",
|
||||||
|
os.getenv("ROUTER_IDv6"),
|
||||||
|
os.getenv("LOCAL_AS"),
|
||||||
|
os.getenv("REMOTE_AS"),
|
||||||
|
os.getenv("BGP_NEIGHBORv6"),
|
||||||
|
"ANEXIA Route Injection v6",
|
||||||
|
)
|
||||||
|
|
||||||
|
pybird.commit_config()
|
||||||
|
pybird6.commit_config()
|
||||||
|
click.echo("Done")
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
Die Methode \verb|bird_config|, welche um eine gute Struktur zu wahren zu einer gesonderten Datei und Klasse angehört, ruft die \verb|get_communities| Methode der Route Dataclass auf, um die Routen in einer von Bird lesbares Format zu wandeln.
|
||||||
|
Des Weiteren wird hier die tatsächliche Konfiguration auf das Dateisystem geschrieben.
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=python,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:bird-config},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={bird\_config Methode}]
|
||||||
|
def bird_config(
|
||||||
|
self,
|
||||||
|
unconverted_routes,
|
||||||
|
route_template,
|
||||||
|
config_path,
|
||||||
|
config_name,
|
||||||
|
router_id=None,
|
||||||
|
local_as=None,
|
||||||
|
remote_as=None,
|
||||||
|
bgb_neighbor=None,
|
||||||
|
description=None,
|
||||||
|
):
|
||||||
|
converted_routes = []
|
||||||
|
for route in unconverted_routes:
|
||||||
|
route.communities = route.get_communities()
|
||||||
|
converted_routes.append(route)
|
||||||
|
|
||||||
|
target_file = self.__prepare_config_path(config_path, config_name)
|
||||||
|
self.__write_config_file(
|
||||||
|
target_file,
|
||||||
|
converted_routes,
|
||||||
|
route_template,
|
||||||
|
router_id,
|
||||||
|
local_as,
|
||||||
|
remote_as,
|
||||||
|
bgb_neighbor,
|
||||||
|
description,
|
||||||
|
)
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
Um die Routen an den Bird Routing Daemon übermitteln zu können, müssen diese erst in eine für Bird verständliche Konfigurationsdatei umgewandelt werden.
|
||||||
|
Zur Realisierung wird die Jinja2 Templating Engine verwendet, da diese die Möglichkeit schafft, alle Eigenschaften des Injectors dynamisch zu konfigurieren.
|
||||||
|
Somit kann der tatsächliche Code aller Injectoren identisch sein, und Variable Eigenschaften wie z.B. die \verb|router_id| oder der \ac{BGP}-Peering Nachbar können beim Ausrollen festgelegt werden.
|
||||||
|
|
||||||
|
\begin{lstlisting}[%language=tex,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:route-template},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={Jinja Template zur Konfigurationsgenerierung}]
|
||||||
|
protocol static injected_routes {
|
||||||
|
{% for route in routes %}
|
||||||
|
route {{route.prefix}} via {{router_id}} {
|
||||||
|
{% for community in route.communities %}
|
||||||
|
bgp_community.add(({{community}}));
|
||||||
|
{% endfor %}
|
||||||
|
};
|
||||||
|
{% endfor %}
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol bgp Route_Injection {
|
||||||
|
description "{{description}}";
|
||||||
|
local as {{local_as}};
|
||||||
|
neighbor {{bgp_neighbor}} as {{remote_as}};
|
||||||
|
next hop self;
|
||||||
|
export filter {
|
||||||
|
if proto = "injected_routes" then accept;
|
||||||
|
reject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
In diesem Template finden sich einige Variablen
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item \verb|routes| (Python Liste mit Routen Elementen)
|
||||||
|
\item \verb|router_id| (IPv4 Addresse des Injectors)
|
||||||
|
\item \verb|local_as| (Lokales \ac{ASN})
|
||||||
|
\item \verb|remote_as| (Nachbar \ac{ASN})
|
||||||
|
\item \verb|bgp_neighbor| (Nachbar \ac{BGP}-Router IPv4 Adresse)
|
||||||
|
\item \verb|description| (Beschreibung des Protokolls)
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
wieder, welche entweder im Code oder dynamisch beim Ausrollen, also ausrollen des Injectors gesetzt werden müssen.
|
||||||
|
Jinja kann auch mit Listen und verschachtelten Listen umgehen, was bei der \verb|routes| Variable zum Einsatz kommt.
|
||||||
|
Jinja kann dann über die Liste der Routenobjekte iterieren und für jede Route einen gesonderten Eintrag mit den jeweiligen \ac{BGP}-Communities erstellen.
|
||||||
|
Folglich ein Beispiel einer möglichen Konfiguration. \acp{ASN} und \verb|router_id|s können hier entweder als Umgebungsvariable oder von einer .env Datei geladen werden.
|
||||||
|
Die Routen werden dynamisch während der Programmlaufzeit angegeben, konvertiert und konfiguriert.
|
||||||
|
|
||||||
|
\begin{lstlisting}[%language=tex,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:bird-configuration-example},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={Beispiel einer Konfigurationsdatei}]
|
||||||
|
protocol static injected_routes {
|
||||||
|
route 1.1.1.1/32 via 172.20.0.5 {
|
||||||
|
bgp_community.add((47147,3200));
|
||||||
|
bgp_community.add((12345,12345));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol bgp Route_Injection {
|
||||||
|
description "ANEXIA Route Injection v4";
|
||||||
|
local as 64701;
|
||||||
|
neighbor 172.20.0.6 as 65001;
|
||||||
|
next hop self;
|
||||||
|
export filter {
|
||||||
|
if proto = "injected_routes" then accept;
|
||||||
|
reject;
|
||||||
|
};
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
Somit können nun Konfigurationsdateien für Bird erstellt werden.
|
||||||
|
Sollte jedoch während dem Rendern des Templates ein Fehler auftreten, kann es passieren, dass eine inkorrekte oder gar keine Konfigurationsdatei generiert wird.
|
||||||
|
Dies könnte einen negativen Einfluss auf die Operation des Service haben und muss somit verhindert werden.
|
||||||
|
|
||||||
|
Um das Problem zu verhindern, wird die Konfiguration erst in eine temporäre Datei geschrieben.
|
||||||
|
Wenn dies erfolgreich war, dann wird die temporäre Datei umbenannt und in das echte Konfigurationsverzeichnis geschoben.
|
||||||
|
Da hier eine Datei überschrieben statt angepasst wird, gehen Dateiberechtigungen verloren und müssen neu gesetzt werden.
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=python,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:write-config-file},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={\_\_write\_config\_file Methode}]
|
||||||
|
def __write_config_file(
|
||||||
|
self,
|
||||||
|
target_path,
|
||||||
|
routes,
|
||||||
|
template,
|
||||||
|
router_id=None,
|
||||||
|
local_as=None,
|
||||||
|
remote_as=None,
|
||||||
|
bgp_neighbor=None,
|
||||||
|
description=None,
|
||||||
|
):
|
||||||
|
template = self.env.get_template(template)
|
||||||
|
with NamedTemporaryFile(delete=False, mode="w") as conf:
|
||||||
|
conf.write(
|
||||||
|
template.render(
|
||||||
|
routes=routes,
|
||||||
|
router_id=router_id,
|
||||||
|
local_as=local_as,
|
||||||
|
remote_as=remote_as,
|
||||||
|
bgp_neighbor=bgp_neighbor,
|
||||||
|
description=description,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
os.chmod(conf.name, 0o660)
|
||||||
|
shutil.move(conf.name, target_path)
|
||||||
|
except Exception as e:
|
||||||
|
echo(e)
|
||||||
|
os.remove(conf.name)
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
|
||||||
\subsection{Status der Routen von Bird abfragen} %TODO RNDMISC-361
|
\subsection{Status der Routen von Bird abfragen} %TODO RNDMISC-361
|
||||||
|
|
||||||
\subsubsection{Evaluation der pybird Bibliothek}
|
\subsubsection{Evaluation der pybird Bibliothek}
|
||||||
|
|
||||||
\subsection{Bird und Bird6 aufteilen} %TODO RNDMISC-362
|
Da die entwickelte \ac{API} über einen Status Endpunkt verfügt, welcher letztendlich von der Anexia Engine abgerufen wird, muss auch der Injektor die benötigten Statusinformationen zur Verfügung stellen.
|
||||||
|
Hierfür wurde evaluiert, welche Python Bibliothek sich am besten zu diesem Zwecke eignet.
|
||||||
|
|
||||||
|
Die Entscheidung für die Verwendung der `pybird` Bibliothek wurde aus folgenden Gründen getroffen:
|
||||||
|
|
||||||
|
\begin{enumerate}
|
||||||
|
\item Funktionalität: Die `pybird` Bibliothek wurde speziell dafür entwickelt mit dem Bird Routing Daemon zu interagieren.
|
||||||
|
\item Direkte Socket Anbindung: `pybird` unterstützt die direkte Kommunikation mit dem Bird Control Socket, was eine erleichterte Kommunikation ermöglicht.
|
||||||
|
\item Aktualisierung und Wartung: Da die `pybird` Bibliothek aktiv gepflegt wird, kann sichergestellt werden, dass sie auch mit zukünftigen Versionen des Bird Routing Daemons kompatibel sein wird.
|
||||||
|
Des Weiteren kann so auch sichergestellt werden, dass das Route Injection Project sich auch in der Zukunft noch auf diese Bibliothek verlassen kann.
|
||||||
|
\item Open-Source: Durch den offenen Quellcode, kann sichergestellt werden, dass der Code keine Malware/Spyware enthält.
|
||||||
|
Sollte es nötig sein, kann der Quellcode der Bibliothek geforked, und auf die Bedürfnisse der Anexia angepasst werden.
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
Über die Methode \verb|get_routes| der \verb|PyBird| Klasse können die von Bird übernommenen Routen abgefragt werden.
|
||||||
|
Als Parameter kann das Präfix der Route angegeben werden, sodass die Ausgabe auf nur dieses Präfix beschränkt wird.
|
||||||
|
Pybird gibt die Ausgabe dann in folgendem Format zurück:
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=tex,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:pybird-raw-output},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={Unverarbeitete Ausgabe von Pybird}]
|
||||||
|
[{'community': '65535:65281',
|
||||||
|
'prefix': '1.2.3.4/32',
|
||||||
|
'peer': '172.20.0.3',
|
||||||
|
'interface': 'eth0',
|
||||||
|
'source': 'injected_routes',
|
||||||
|
'time': '13:37:47'}]
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
Von dieser Ausgabe wird jedoch nur der Teil, welcher die Communities betrifft benötigt.
|
||||||
|
Folglich muss die Ausgabe noch im Code angepasst werden.
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=python,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:respond-state-to-consul},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={respond\_state\_to\_consul Methode}]
|
||||||
|
def respond_state_to_consul(
|
||||||
|
consul: ckv, pybird: PyBird, route: Route, injector_id: str
|
||||||
|
) -> None:
|
||||||
|
state = pybird.get_routes(prefix=route.prefix)
|
||||||
|
route.encode_prefix()
|
||||||
|
try:
|
||||||
|
actual_communities = state[0].get("community", "").split(" ")
|
||||||
|
except IndexError:
|
||||||
|
actual_communities = []
|
||||||
|
expected_communities = list(route.communities)
|
||||||
|
state = get_bird_communities(expected_communities, actual_communities)
|
||||||
|
state = json.dumps({"communities": state})
|
||||||
|
try:
|
||||||
|
consul.kv.put(
|
||||||
|
f"v1/state/{injector_id}/{route.encode}/{route.prefix}",
|
||||||
|
state,
|
||||||
|
)
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
click.echo("Lost consul while reporting route :c")
|
||||||
|
return
|
||||||
|
route.decode_prefix()
|
||||||
|
click.echo(f"Route {route.prefix} with state {actual_communities} pushed to consul")
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
Um den Status zu bestimmen, werden die Communities, welche im Routenobjekt abgespeichert sind, mit den Communities welche von Bird zurückgegeben wurde verglichen.
|
||||||
|
Stimmen diese überein, so kann davon ausgegangen werden, dass Bird alle Communities akzeptiert hat und an den Nachbar Router übermitteln kann.
|
||||||
|
Sollte es Abweichungen zwischen den Communities geben, bedeutet dies, dass noch nicht alle Communities von Bird akzeptiert wurden.
|
||||||
|
Als Folge dessen werden auch nur die aktuell in Bird eingetragen Communities zurück an Consul übermittelt.
|
||||||
|
Die \ac{API}-Komponente des Route Injection Service fragt dann den in Consul eingetragenen Status ab und bestimmt dann selbst, ob der gesamte Prozess erfolgreich, noch im Gange oder fehlerhaft war.
|
||||||
|
Dies wird dann von der Anexia Engine interpretiert und ist für den Nutzer sichtbar.
|
||||||
|
|
||||||
\subsection{Realisierung des Heartbeats}
|
\subsection{Realisierung des Heartbeats}
|
||||||
|
|
||||||
\subsection{Emergency-Mode implementieren} %TODO RNDMISC-363
|
Um sicherzustellen, dass die API den aktuellen Status der online verfügbaren Injektoren erfassen kann, verwenden die Injektoren ein sogenanntes `Heartbeat`-System, das seine Aktivität in Consul signalisiert.
|
||||||
|
Dieses Heartbeat wird in Form eines Wertes (Value) in Consul gemeldet.
|
||||||
|
Dieser Prozess ermöglicht es der API, den Zustand der einzelnen Injektoren zu überwachen und sicherzustellen, dass sie ordnungsgemäß funktionieren.
|
||||||
|
|
||||||
|
Jeder Injektor meldet seinen Status durch das Schreiben eines Wertes (Value) in einen spezifischen Schlüssel-Wert-Pfad in Consul.
|
||||||
|
Dieser Pfad lautet: \verb|v1/state/<injector_id>/heartbeat|.
|
||||||
|
Hierbei steht \verb|<injector_id>| für die eindeutige Kennung des Injektors.
|
||||||
|
Der Wert (Value), der in den oben genannten Schlüssel-Wert-Pfad geschrieben wird, hat den Inhalt `\{\}`, was auf ein leeres JSON-Objekt hinweist.
|
||||||
|
Dieses leere Objekt dient als Platzhalter und signalisiert der API, dass der Injektor aktiv ist und seinen Heartbeat meldet.
|
||||||
|
Der gemeldete Wert (Value) hat eine \ac{TTL} von 10 Sekunden.
|
||||||
|
Dies bedeutet, dass nachdem der Injektor seinen Heartbeat gemeldet hat, der Wert für 10 Sekunden in Consul bestehen bleibt.
|
||||||
|
Wenn innerhalb dieses Zeitraums keine weiteren Heartbeats gemeldet werden, wird der Wert automatisch aus Consul entfernt.
|
||||||
|
|
||||||
|
Durch das Heartbeat-System kann die API regelmäßig aktualisierte Informationen erhalten, welche Injektoren online und funktionsfähig sind.
|
||||||
|
|
||||||
|
\vspace{1cm}
|
||||||
|
Um den Heartbeat im Programmcode möglichst modular zu realisieren wurde hierfür eine eigene Methode erstellt.
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=python,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:create-heartbeat},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={create\_heartbeat Methode}]
|
||||||
|
def create_heartbeat(consul, injector_id):
|
||||||
|
session_id = consul.session.create(behavior="delete", ttl=10)
|
||||||
|
consul.kv.put(
|
||||||
|
key=f"v1/state/{injector_id}/heartbeat", value="{}", acquire=session_id
|
||||||
|
)
|
||||||
|
return session_id
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
Nach dem initialen Anlegen des Heartbeateintrages wird dieser alle fünf Sekunden erneuert und sicherzustellen, dass die \ac{TTL} des Eintrages nicht abläuft.
|
||||||
|
|
||||||
|
\subsection{Emergency-Mode} %TODO RNDMISC-363
|
||||||
|
|
||||||
|
|
||||||
|
Um sicherzustellen, dass der Route Injection Service auch in Szenarien von Netzwerkproblemen zwischen der \ac{API} und den Injektoren effizient arbeiten kann, sei es für das Hinzufügen, Ändern oder Löschen von Routen, wurde eine maßgebliche Funktion eingeführt, die als Emergency-Mode, bzw. Notfallmodus bekannt ist.
|
||||||
|
Diese Funktion wurde entwickelt, um direkten Zugriff auf die Injektoren zu ermöglichen und Routenverwaltungsvorgänge über die Kommandozeile durchzuführen.
|
||||||
|
Der Emergency-Mode fungiert als eine Art Sicherheitsvorkehrung, die sicherstellt, dass die Verfügbarkeit und Funktionalität des Dienstes aufrechterhalten werden kann, selbst wenn die übliche Kommunikation zwischen der \ac{API} und den Injektoren temporär gestört ist.
|
||||||
|
Der Namensteil `Mode` lässt vermuten, dass es sich um einen tatsächlichen Operationsmodus handelt.
|
||||||
|
Dies ist allerdings nicht ganz korrekt.
|
||||||
|
Der Emergency-Mode ist eher als Funktionalitätserweiterung zu sehen und kann selbst dann aktiviert werden, wenn die Kommunikation zwischen \ac{API} und Injector intakt ist.
|
||||||
|
Dies ist insbesondere nützlich, wenn dringende Änderungen an den Routingeinstellungen erforderlich sind, die nicht auf die normale Kommunikation warten können.
|
||||||
|
Um Zugriff auf die Kommandozeile zu erhalten, muss ein Nutzer sich über \ac{SSH} auf den Injector einloggen.
|
||||||
|
Firmeninterne Automatismen stellen sicher, dass nur befugte Nutzer Zugriff auf das System haben.
|
||||||
|
|
||||||
|
|
||||||
|
Zur Gewährleistung der Integrität des Service müssen die vom Nutzer eingegebene Routen validiert und auf Ihre Korrektheit überprüft werden.
|
||||||
|
In der Regel wird dies von der \ac{API} übernommen, jedoch werden im Notfallmodus die Routen direkt in den Injector eingespeist, und die Validierung der \ac{API} wird umgangen.
|
||||||
|
Daher muss diese vom Injector selbst durchgeführt werden.
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=python,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:validate-route},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={Methode zur Validierung von Routen}]
|
||||||
|
def validate_route(prefix: str, communities=None) -> Route:
|
||||||
|
route = Route()
|
||||||
|
try:
|
||||||
|
route.prefix = str(ipaddress.ip_network(prefix))
|
||||||
|
except ValueError:
|
||||||
|
raise click.exceptions.BadParameter("Route prefix is invalid")
|
||||||
|
if ":" in route.prefix:
|
||||||
|
route.encode = "IPv6"
|
||||||
|
else:
|
||||||
|
route.encode = "IPv4"
|
||||||
|
if communities:
|
||||||
|
communities = communities.split(",")
|
||||||
|
for community in communities:
|
||||||
|
community_parts = community.split(":")
|
||||||
|
if len(community_parts) != 2:
|
||||||
|
raise click.exceptions.BadParameter(
|
||||||
|
f"{community} is not a valid BGP community"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
if not int(community_parts[0]) in range(1, 65535) or not int(
|
||||||
|
community_parts[1]
|
||||||
|
) in range(1, 65535):
|
||||||
|
raise click.exceptions.BadParameter(
|
||||||
|
f"{community} is not a valid BGP community"
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
raise click.exceptions.BadParameter(
|
||||||
|
f"{community} contains invalid integer value"
|
||||||
|
)
|
||||||
|
route.communities = list(communities)
|
||||||
|
return route
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
Der Zweck ist, BGP-Routen, primär in Bezug auf deren Präfixe und Communities zu validieren.
|
||||||
|
Die Methode akzeptiert ein Präfix als obligatorisches Argument und optional eine Liste von Communities als Zeichenfolge.
|
||||||
|
Das Hauptziel dieser Funktion ist es, sicherzustellen, dass die angegebenen Informationen den \ac{BGP}-Anforderungen entsprechen und gültig sind.
|
||||||
|
|
||||||
|
Zuerst wird ein neues Routenobjekt erstellt, das als Container für die validierten Daten dient.
|
||||||
|
Die Funktion versucht dann, den übergebenen Präfix als IP-Netzwerk zu interpretieren.
|
||||||
|
Bei einer ungültigen Eingabe wird eine `BadParameter`-Exception ausgelöst.
|
||||||
|
|
||||||
|
Das Präfix wird analysiert, um festzustellen, ob es sich um ein IPv4- oder IPv6-Präfix handelt.
|
||||||
|
Dies wird im `encode`-Attribut des Routenobjekts vermerkt.
|
||||||
|
Im Fall von übergebenen Communities werden diese analysiert und validiert.
|
||||||
|
Jede Community wird auf ihre Struktur überprüft, und die einzelnen Teile werden auf ihre Gültigkeit im Hinblick auf \ac{ASN} und Wertigkeit geprüft.
|
||||||
|
Fehlerhafte Communities führen zu entsprechenden `BadParameter`-Exceptions.
|
||||||
|
|
||||||
|
Abschließend werden die validierten Informationen, einschließlich Präfix und Communities, im Routenobjekt gespeichert.
|
||||||
|
Die Funktion gibt dieses Objekt zurück, das nun die validierten Daten enthält.
|
||||||
|
|
||||||
|
Zur Vereinfachung der Interaktionen mit der Kommandozeile wird die Bibliothek `click` verwendet.
|
||||||
|
Durch diese können Exceptions leicht an den Benutzer übermittelt werden, und Tests können einfach gestaltet werden.
|
||||||
|
|
||||||
|
Der Operator welcher letztendlich den Emergency Mode bedienen wird, hat zwei Eingabemöglichkeiten:
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item add-route <prefix> <communities>
|
||||||
|
\item delete-route <prefix>
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
Wobei `<>` für Platzhalter des entsprechenden Parameters stehen.
|
||||||
|
Eine Möglichkeit, schon existierende Routen zu updaten bietet der Emergency Mode nicht.
|
||||||
|
Routen welche über den Emergency Mode hinzugefügt wurde, haben immer Vorrang gegenüber Routen, welche über Consul geladen wurden.
|
||||||
|
Eine weitere Anforderung an den Emergency Mode war, dass Routen auch nach Reboot des Injectors erhalten bleiben.
|
||||||
|
Dies forderte, dass Routen auf einer Weise im Dateisystem erhalten werden.
|
||||||
|
Um dies zu Realisieren bestünde die Möglichkeit eine Datenbank wie `sqlite` zu nutzen.
|
||||||
|
Eine einfachere Lösung dieses Problems war es jedoch, die Routen als \ac{JSON} in eine Datei zu schreiben.
|
||||||
|
Die schon bei den Konfigurationsdateien für Bird, wurden IPv4 und IPv6 aus demselben Grund getrennt.
|
||||||
|
|
||||||
|
Um die Konsistenz und Integrität dieser Dateien, auch `Emergency Files` genannt zu gewährleisten, wurde ein Filelock gesetzt.
|
||||||
|
Zur Vermeidung des Dirty read Problems, welches in der Vorlesung Datenbanken erläutert wurde, wurde das Filelock sowohl für Schreib- als auch für Lesevorgänge gesetzt.
|
||||||
|
So kann ein zweiter Prozess das Emergency File erst lesen, wenn der erste Prozess den Schreibvorgang abgeschlossen hat.
|
||||||
|
Dies dient nicht nur zur Mehrbenutzersynchronisation von mehreren Menschen, sondern hauptsächlich, dass der Hauptprozess nicht versucht das Emergency File zu lesen, während ein Operator mittels des Emergency Mode Änderungen vornimmt.
|
||||||
|
|
||||||
|
Da Python, beziehungsweise die benutze \ac{JSON} Bibliothek Probleme damit hatte verschachtelte \ac{JSON}s zu de- und enkodieren, wurde eine weitere Dataclass angelegt.
|
||||||
|
Diese Dataclass dient nur als Container, um eine Liste an Routenobjekten anzulegen.
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=python,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:route-container},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={Route Container Dataclass}]
|
||||||
|
@dataclass_json
|
||||||
|
@dataclass
|
||||||
|
class RouteContainer:
|
||||||
|
routes: list[Route]
|
||||||
|
|
||||||
|
def __init__(self, routes):
|
||||||
|
self.routes = routes
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.routes}"
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
Zu Beginn des Programmstarts werden die Pfade der Lockfiles und Emergency Files innerhalb des Docker Containers festgelegt:
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=python,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:path-declaration},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={Deklaration der Dateipfade}]
|
||||||
|
emergency_file_v4 = "/var/lib/route_injector/emergency_route_v4.json"
|
||||||
|
emergency_file_v6 = "/var/lib/route_injector/emergency_route_v6.json"
|
||||||
|
lock_file_v4 = "/var/lib/route_injector/emergency_route_v4.lock"
|
||||||
|
lock_file_v6 = "/var/lib/route_injector/emergency_route_v6.lock"
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
Die Methode welche beim Aufruf von \verb|add-route| über die Kommandozeile aufgerufen wird lässt sich wie folgt darstellen:
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=python,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:add-route},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={add\_route Methode}]
|
||||||
|
@click.argument("communities")
|
||||||
|
@click.argument("prefix")
|
||||||
|
@cli.command()
|
||||||
|
def add_route(prefix, communities):
|
||||||
|
route = validate_route(prefix, communities)
|
||||||
|
emergency_file = emergency_file_v4
|
||||||
|
lockfile = FileLock(lock_file_v4)
|
||||||
|
if route.encode == "IPv6":
|
||||||
|
emergency_file = emergency_file_v6
|
||||||
|
lockfile = FileLock(lock_file_v6)
|
||||||
|
with lockfile.acquire():
|
||||||
|
current_routes = read_emergency_file(emergency_file)
|
||||||
|
new_routes = find_and_remove_in_list(current_routes, route)
|
||||||
|
new_routes.append(route)
|
||||||
|
route_container = RouteContainer(new_routes)
|
||||||
|
write_emergency_file(route_container, emergency_file)
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
Zuerst wird über den erwähnten Validierungsprozess sichergestellt, dass die vom Nutzer eingegeben Route eine valide Route ist.
|
||||||
|
Über eine if Abfrage wird geprüft, ob das Präfix der eingegebenen Route ein IPv6 Präfix ist.
|
||||||
|
Ist das der Fall, dann wird das entsprechende Emergency File und Lockfile einer Variablen zugewiesen.
|
||||||
|
Anschließend wird das Filelock auf das entsprechende Emergency File gesetzt, um sicherzustellen, dass keine weiteren Prozesse auf das File zugreifen können.
|
||||||
|
Im Folgenden werden die schon im Emergency File enthaltenen Routen mit den neu hinzugefügten verglichen.
|
||||||
|
Sollte eine Route hinzugefügt werden, wessen Präfix schon im aktuellen Emergency File enthalten ist, wird diese über die \verb|find_and_remove_in_list| entfernt.
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=python,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:find-and-remove-in-list},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={find\_and\_remove\_in\_list Methode}]
|
||||||
|
def find_and_remove_in_list(route_list: list, list_element: Route):
|
||||||
|
for element in route_list:
|
||||||
|
if element.prefix == list_element.prefix:
|
||||||
|
route_list.remove(element)
|
||||||
|
return route_list
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
Die neue Route wird danach der Liste von Routen hinzugefügt, und über die RouteContainer Dataclass wieder zu einer verschachtelten \ac{JSON} konvertiert.
|
||||||
|
Zum Löschen von Routen aus den Emergency Files, gibt es die \verb|delete_route| Methode, welche sich maßgeblich dadurch unterscheidet, dass sie keine \ac{BGP}-Communities als Parameter benötigt, sondern lediglich das Routenpräfix.
|
||||||
|
Infolgedessen, fehlt in dieser Methode auch der Teil, welcher die neue Route der Routenliste hinzufügt, da hier nur die Route entfernt werden muss.
|
||||||
|
|
||||||
|
Da das Lesen und Schreiben der Files mehrmals im Programmcode geschieht, wurde hierfür jeweils eine Methode geschrieben um Codeduplizierung möglichst zu vermeiden und das \ac{DRY} Prinzip einzuhalten.
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=python,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:read-emergency-file},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={read\_emergency\_file Methode}]
|
||||||
|
def read_emergency_file(emergency_route_file: str) -> list:
|
||||||
|
if not os.path.exists(emergency_route_file):
|
||||||
|
return []
|
||||||
|
with open(emergency_route_file, "r") as emergency_route:
|
||||||
|
json_routes = emergency_route.read()
|
||||||
|
routes_from_file = RouteContainer.from_json(json_routes).routes
|
||||||
|
return routes_from_file
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=python,
|
||||||
|
frame=single, % Ein Rahmen um den Code
|
||||||
|
framexleftmargin=15pt, % Rahmen link von den Zahlen
|
||||||
|
style=algoBericht,
|
||||||
|
label={lst:write-emergency-file},
|
||||||
|
captionpos=b, % Caption unter den Code setzen
|
||||||
|
caption={write\_emergency\_file Methode}]
|
||||||
|
def write_emergency_file(routes: RouteContainer, emergency_route_file: str):
|
||||||
|
with NamedTemporaryFile(delete=False, mode="w") as tmp_emergency_route_file:
|
||||||
|
tmp_emergency_route_file.write(routes.to_json())
|
||||||
|
try:
|
||||||
|
shutil.move(tmp_emergency_route_file.name, emergency_route_file)
|
||||||
|
except Exception as e:
|
||||||
|
click.echo(e)
|
||||||
|
os.remove(tmp_emergency_route_file.name)
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
|
||||||
\section{Testen}
|
\section{Testen}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user