Created
April 12, 2016 14:43
-
-
Save maxux/b8e8baeac1ac3478e3abcd68b2cfca7b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
\documentclass[twoside,openright]{article} | |
\usepackage[top=2cm, bottom=2cm, left=2cm, right=2cm]{geometry} | |
\usepackage[francais]{babel} | |
\usepackage{titling} | |
\usepackage{graphicx} | |
\usepackage[usenames,dvipsnames]{xcolor} | |
\usepackage{scrextend} | |
\changefontsizes[20pt]{12pt} | |
\usepackage{floatrow} | |
\usepackage{minted} | |
\usepackage{courier} | |
\usepackage{caption} | |
\usepackage{fontspec} | |
\setmonofont{DejaVu Sans Mono} | |
\definecolor{mintedbg}{rgb}{0.9, 0.9, 0.9} | |
\newminted{c}{ | |
bgcolor=mintedbg, | |
tabsize=8, | |
fontsize=\footnotesize | |
} | |
\newminted{text}{ | |
bgcolor=mintedbg, | |
tabsize=8, | |
fontsize=\footnotesize | |
} | |
\newminted{bash}{ | |
bgcolor=mintedbg, | |
tabsize=8, | |
fontsize=\footnotesize | |
} | |
\floatsetup[listing]{style=Plaintop} | |
\floatsetup[figure]{style=Plaintop} | |
\setlength{\intextsep}{20pt plus 1.0pt minus 2.0pt} | |
\let\stdsection\section | |
\renewcommand*\section{\cleardoublepage\stdsection} | |
\title{Isolation de processus par virtualisation de la couche réseau dans un système open source compatible Windows.} | |
\author{Daniel Maxime} | |
\date{Mai 2014} | |
\graphicspath{{content/}} | |
\newcommand\call[1]{{\mbox{\color{MidnightBlue}#1}}} | |
\newcommand\hexa[1]{{\color{OliveGreen}#1}} | |
\newcommand\file[1]{{\color{BrickRed}\thinspace#1\thinspace}} | |
\renewcommand\listoflistingscaption{Table des extraits de code} | |
\begin{document} | |
\maketitle | |
\newpage | |
\tableofcontents | |
\clearpage | |
\listoffigures | |
\newpage | |
\listoflistings | |
\newpage | |
\section{Introduction} | |
\subsection{Présentation de l'entreprise} | |
La société Level IT a été fondée en décembre 2000 par deux ingénieurs civils (Hault Olivier et Laconte Luc) | |
de l'Université de Liège. Son activitée principale est axée sur le développement et la commercialisation | |
de solutions informatiques permettant de répondre aux challenges et attentes de ses clients en termes | |
d'excellence dans des domaines multiples comme la gestion de production, les | |
Systèmes de Management Intégrés (SMI), la Total Productive Maintenance (TPM), | |
ainsi que la gestion au quotidien des plans d'actions, d'exigences légales et de certifications | |
(ISO, OSHAS, REACH, ...). | |
\newline | |
Avec ses produits phares (tel que Practeos, SAMS, Phone2Blog, ...) et grâce à ses principaux clients | |
(ArcelorMittal, Tecteo, GlaxoSmithKline, Université de Liège, Nomacorc, ...), | |
Level IT a su s'imposer comme référence en termes de développements de logiciels de qualité en Wallonie. | |
\newline | |
Son implantation stratégique au coeur du « Liège Science Park » vise à consolider ses activités de | |
Recherches et Développement (R\&D), tout en maintenant des relations étroites avec des acteurs | |
de référence dans les domaines académiques, institutionnels, industriels et de services. | |
\newline | |
Leur principale technologie de développement repose sur Microsoft .NET mais ils ne se limitent | |
pas à ça. En effet, en Recherche et Développement, une multitude de choix de langage de programmation | |
différentes peuvent être utilisé pour répondre au mieux aux attentes des clients. | |
\newline | |
\begin{center} | |
\includegraphics[scale=1.8]{logo-levelit.eps} | |
\end{center} | |
\newpage | |
\subsection{Cahier des charges} | |
Certains systèmes d'exploitation, à l'heure actuelle, n'utilisent plus exclusivement | |
la « virtualisation » à proprement parler, comme moyen d'isolement d'applications sur une même machine physique. | |
Certains systèmes (comme Linux par exemple) permettent de lancer des applications dans | |
différents namespaces, qui sont en fait une instanciation de différentes parties du système, | |
permettant l'isolement de l'application. Pour ce qui est de la partie réseau, cela permet | |
à différentes applications d'avoir différentes stack TCP/IP indépendantes l'une de l'autre. | |
Pour l'instant, cela n'est pas possible nativement sur les systèmes Microsoft Windows. | |
\newline | |
Ce stage/TFE consiste à implémenter ce système de namespace réseau dans le kernel opensource de | |
ReactOS qui fonctionne de la même façon que le kernel Microsoft Windows NT. | |
Par exemple, on doit pouvoir lancer une application dans un namespace spécifique | |
qui serait totalement indépendant des autres, pour avoir un isolement réseau pour l'application. | |
Il faut que cet isolement puisse accéder au monde extérieur sans interférer avec les autres parties | |
isolées. Il faut également pouvoir connecter plusieurs parties isolées ensemble. | |
\newline | |
Cet isolement permet d'avoir une table de routage, des interfaces réseaux, des règles de firewalling, etc. | |
totalement indépendants vis-à-vis des autres namespaces sur le même système hôte. | |
\newline | |
Le cadre de ce projet sera une implémentation opensource. L'ajout de cette option doit ne pas | |
interférer avec l'utilisation actuelle du système (il doit être optionnel et transparent pour l'application). | |
Les applications déjà existantes doivent toujours pouvoir fonctionner de la même façon. | |
\newline | |
Actuellement, une solution propriétaire existe sous Microsoft Windows pour ça: Parallels Virtuozzo Containers | |
(ou Parallels Containers for Windows). Cette solution requiert l'utilisation de | |
la suite Parallels pour fonctionner. Pour les autres systèmes, des solutions | |
tels que : OpenVZ, Linux-VServer, Solaris Containers, FreeBDS Jail, sysjail, HP-UX Containers,... | |
sont des implémentations de virtualisation au niveau OS, mais présentent toutes différents | |
avantages et inconvénients. Exemple : à l'heure actuelle sous Linux, à part OpenVZ, aucune solution | |
ne propose une isolation totale des privilèges root. | |
\newline | |
Sous Windows, excepté « Parallels Containers », aucun système de virtualisation au | |
niveau OS (iCore Virtual Accounts ou Sandboxie) ne supporte l'isolement de la stack réseau. | |
\newpage | |
\section{ReactOS} | |
ReactOS est un système d'exploitation libre visant à être binairement compatible | |
avec les systèmes d'exploitations propriétaires Microsoft Windows NT. | |
\subsection{Historique} | |
En 1996, des personnes ont formé un groupe appelé « FreeWin95 », visant à implémenter | |
un clône de Windows 95. Cependant, suite à de trop longues discussions à propos de | |
l'implémentation du système, le projet n'a absolument pas abouti. | |
\newline | |
Fin 1997, Jason Filby, un développeur d'Oracle (Durban, Afrique du Sud) | |
alors chef coordinateur du projet FreeWin95, fait une demande générale sur la mailing list | |
pour demander de relancer le projet. Il a été décidé de cibler Windows NT et d'obtenir des résultats | |
plutôt que de parler indéfiniment sur comment faire le système. | |
Leur but était réellement d'écrire du code pour avoir quelque chose de fonctionnel. | |
\newline | |
Le projet a été renommé en « ReactOS » suite à l'insatisfaction du monopole de Microsoft sur le marché | |
des systèmes d'exploitation. | |
\newline | |
En février 1998, ReactOS est né. La première version disponible dans le svn est la 0.0.1, elle comporte | |
12000 lignes de code C (et 30000 lignes de headers). On y retrouve un noyau très léger mais avec déjà | |
une arborescence similaire à l'actuelle. Cette version ne contient qu'un noyau et un boot loader. | |
\newline | |
Par la suite, les débuts de ReactOS ont été difficiles et lents. Seuls quelques personnes savaient | |
comment écrire un noyau (en effet, ce n'est pas à la portée de tout le monde d'écrire un scheduler, un | |
boot loader, etc.). | |
Une fois le noyau un peu plus complet et stable, des drivers de base tels | |
que les drivers IDE ou simplement un driver clavier ont pu être écrits et de plus en plus de personnes ont | |
pu contribuer et commencer à coder à grande échelle pour le système. | |
\subsection{A propos de ReactOS et idéologie} | |
ReactOS est un système d'exploitation libre et open source basé sur l'architecture de Windows NT, supportant | |
les logiciels et pilotes existant pour Windows. | |
\newline | |
ReactOS n'est pas basé sur Linux (comme l'est le projet WINE), cependant WINE est également implémenté | |
dans ReactOS et constitue même la majorité du mode utilisateur du système. A l'heure actuelle, ReactOS est | |
destiné aux développeurs plutôt qu'à des utilisateurs finaux, mais à terme, le système devrait être utilisable | |
par n'importe qui, de la même façon qu'un Windows actuel. Leur but est de pouvoir faire un système binairement | |
compatible avec Windows, sans être forcé de suivre la démarche commercial de Microsoft, ainsi que leurs idées | |
(par exemple, ne pas forcer l'utilisation de Metro). | |
\subsubsection{Compatibilité} | |
« Change your OS, not your software! » est un des slogans de ReactOS. L'idéologie du groupe est qu'on ne | |
devrait pas changer de logiciels sous prétexte qu'on change de système d'exploitation. C'est pourquoi ils | |
veulent faire fonctionner les logiciels et pilotes Windows sur leur système libre. | |
\subsubsection{Sécurité} | |
Contrairement à ce qu'on pourrait penser, le noyau NT a une très bonne sécurité de par sa conception. | |
Il utilise des listes de contrôle d'accès évolués et est flexible. La mauvaise réputation qui a été apportée | |
avec Windows XP principalement est due au fait que pour garder une rétro-compatibilité et pour aider les gens | |
à migrer de Windows 9x vers Windows XP, il a fallu désactiver par défaut une grosse partie de la sécurité. | |
\newline | |
ReactOS a été prévu pour prendre directement l'aspect sécurisé de Windows NT. | |
\subsubsection{Légèreté} | |
ReactOS est conçu pour être léger et puissant. L'interface graphique se veut simple et légère comme celle | |
de Windows 95 tout en gardant l'efficacité et les nouvelles fonctionnalités d'un Windows récent. | |
\subsubsection{Libre} | |
Le code source de ReactOS est disponible sous licence GNU GPL et sous BSD. La possibilité de voir le code | |
source du noyau est facilement accessible via l'SVN et sa modification l'est aussi une fois inscrit sur | |
le site principal de ReactOS. | |
\subsubsection{Confiance} | |
Depuis 1996, ReactOS a été écrit « from scratch », c'est à dire depuis rien. | |
C'est une réimplémentation solide du noyau NT, prévu aussi bien pour l'embarqué que pour les | |
ordinateurs personnels et serveurs. | |
De plus, ReactOS prend en charge plusieurs méthodes d'implémentations basées sur d'autres familles d'OS tel | |
que UNIX, VMS et OS/2 (exemple, il y'a une implémentation de la couche IP qui prend en charge | |
les sockets BSD, ça veut dire qu'une application codée pour utiliser les sockets BSD peut potentiellement | |
tourner sous ReactOS). | |
\subsubsection{Orienté objet} | |
ReactOS n'est pas un système orienté objet au sens propre du terme, mais il utilise des objets pour la | |
représentation interne du système (comme Windows NT, ce concept sera expliqué plus loin lors de la | |
communication entre le driver et le kernel). ReactOS n'est pas « pour » l'idéologie UNIX qui dit que | |
« tout est fichier ». Bien que cela fasse la force de UNIX, c'est également le goulot d'étranglement | |
d'après les développeurs et l'idéologie du groupe ReactOS (je n'ai pas trouvé de source à ce propos | |
mais le code source ne montre en effet pas beaucoup d'allusion au fonctionnement de UNIX). | |
\newline | |
Pour ma part, je ne partage pas cet avis à propos du goulot sous UNIX. | |
\begin{figure}[!h] | |
\caption{Capture d'écran de ReactOS 0.3.16 LiveCD dans VirtualBox} | |
\centering | |
\includegraphics[scale=0.6]{reactos-livecd.eps} | |
\end{figure} | |
\subsection{Technique} | |
ReactOS respecte assez bien, d'un point de vue logique, le fonctionnement d'un noyau NT, mais l'implémentation | |
qui en est faite n'est pas toujours identique ou optimale. L'un des message important que les développeurs font | |
passer lors d'un développement dans le système est: faire les choses bien est un bon début, mais les résultats | |
sont plus importants que les performances. Le côté optimisation pourra être fait après par quelqu'un d'autre, | |
le but premier est d'avoir quelque chose de fonctionnel. | |
\newline | |
Une fois qu'on commence à regarder le code source, on comprend que c'est clairement ça. Le code est loin d'être | |
optimisé, mais il est fonctionnel. Une grosse partie n'est pas commentée et la documentation n'est pas forcément | |
facile à trouver non plus. Un « doxygen » (générateur de documentation C++) est disponible pour le code, mais | |
il n'est pas assez bien utilisé pour pouvoir s'en servir de référence pour le code. | |
\newline | |
\newpage | |
\section{Le réseau dans ReactOS} | |
La partie réseau de ReactOS n'est pas la partie du système la plus mise à jour. La couche TCP/IP | |
\footnote{Par abus de langage, on emploie le terme « stack TCP/IP » pour l'entièreté de la couche réseau | |
dans un système d'exploitation, mais en réalité dans le cadre de ce travail, seul la « stack IP » est mise | |
en cause, en effet nous n'irons pas plus haut que la couche 3 du modèle OSI. Nous verrons cela plus tard.} | |
a initialement été écrite en 2000 (en se référant aux dates de modifications en commentaires dans le code) | |
et n'a pas vraiment été mise à jour depuis. On se retrouve donc avec du vieux code mais fonctionnel. A noter | |
aussi que seul le strict minimum a été implémenté comme cela sera montré plus loin. | |
\newline | |
ReactOS utilise le même fonctionnement global que Windows XP. Le principal repose sur NDIS. | |
Ensuite, on retrouve une bibliothèque IP (couche 3) et une implémentation de lwIP (utilisée partiellement). | |
\subsection{NDIS (Network Driver Interface Specification)} | |
NDIS est une API pour les cartes réseaux écrite conjointement par Microsoft et 3Com et principalement | |
utilisée dans les systèmes Microsoft Windows. Le projet est propriétaire et fermé, nous n'avons donc pas | |
accès aux sources de l'implémentation, mais des versions opensource ont été faites par Reverse Engineering | |
et il est donc possible d'utiliser des drivers NDIS sous Linux, FreeBSD, etc. | |
\newline | |
NDIS dispose de trois grandes parties: | |
\begin{itemize} | |
\itemsep0em | |
\item Miniport Drivers: ces drivers implémentent le fonctionnement hardware (ou logique en cas | |
de cartes virtuelles) des cartes réseaux. C'est là que les interruptions matérielles, etc. sont gérées. | |
\item ProtocolDrivers: ces drivers peuvent se binder à un ou des Miniports et communiquer avec eux | |
de façon standardisée avec l'API. | |
\item Intermediate Drivers: ces drivers se situent entre les Miniports et les Protocols Drivers. Ils | |
permettent de filtrer le contenu qui y transite. | |
\end{itemize} | |
Typiquement, les drivers réseaux sont donc des Miniports, le driver TCP/IP est un ProtocolDriver et | |
les Intermediates Drivers font office de Firewall, de Load Balancing Failover ou encore de translation | |
entre des vieux transports et un driver Miniport qui ne le supporterait pas. | |
\newline | |
ReactOS utilise bien ce système de fonctionnement et implémente NDIS 5 (les versions 6 et supérieures ne sont | |
pas supportées). Le système dispose de 3 drivers par | |
défaut: ne2000, pcnet et rtl8193. Un grand nombre de drivers binaire faits pour Windows sont | |
fonctionnels dans ReactOS, une liste des drivers testés est disponible sur le Wiki | |
\footnote{https://www.reactos.org/wiki/Supported\_Hardware/Network\_cards}. | |
\begin{figure}[!h] | |
\caption{Couches NDIS} | |
\centering | |
\includegraphics[scale=0.15]{ndis-layers.eps} | |
\end{figure} | |
\newpage | |
\subsection{Implémentation de la couche 3 (tcpip.sys)} | |
Le driver \file{tcpip.sys} (dont les sources se trouvent dans \file{/drivers/network/tcpip/}) contient tout ce qui | |
est nécessaire à la réception d'un paquet NDIS et la gestion interne des différentes tables | |
(routing (RIB), forwarding (FIB), ARP, ...). Le driver TCP/IP à proprement parler est un ProtocolDriver NDIS. | |
\file{tcpip.sys} est linké (statiquement\footnote{Cette précision est importante pour la suite}) | |
à une bibliothèque logicielle qui s'appelle « ip » (source \file{/lib/network/ip}). | |
Cette bibliothèque s'occupe de garder en mémoire toutes les informations de couche 3 pour le système | |
(listes des adresses ip, les interfaces, leur binding avec NDIS, etc.). | |
Des précisions l'implémentation de la \call{libip} sont à la section \ref{sub:libip} page \pageref{sub:libip}. | |
Pour faire communiquer le ProtocolDriver avec le user-mode plus haut, le système utilise « TDI » pour | |
faire le lien entre les deux. TDI est expliqué section \ref{sub:tdi} page \pageref{sub:tdi}. | |
\begin{figure}[!h] | |
\caption{Couches TDI - NDIS } | |
\centering | |
\includegraphics[scale=0.15]{ndis-tdi.eps} | |
\end{figure} | |
\newpage | |
\section{Les namespaces} | |
Dans le sens large du terme, un « namespace » (espace de nom) est l'isolation d'un élément ou d'un | |
groupe d'éléments, par un nom. On retrouve cette notion aussi bien dans la programmation qu'ailleurs, | |
comme ici, dans une isolation système. | |
\subsection{Virtualisation} | |
Si la virtualisation est très à la mode ces temps ci, c'est parce qu'elle apporte un réel gain de rentabilité | |
du matériel dont on dispose. En effet, la virtualisation permet de partager | |
les ressources d'une machine pour plusieurs utilisateurs, programmes, systèmes d'exploitation, ... | |
\newline | |
Cependant le terme « Virtualisation » est très vague. Il existe plusieurs techniques de | |
virtualisation: | |
\subsubsection{Virtualisation noyau user-space} | |
Ce procédé permet de lancer un noyau par dessus un noyau existant, sans | |
l'isoler. Chaque noyau a son propre espace utilisateur, comme une application | |
classique, la seule isolation est celle qui se trouve à l'intérieur de cet espace | |
utilisateur. Cette solution n'est vraiment utile que lors du développement | |
d'un noyau pour pouvoir le tester plus facilement (jusqu'à une certaine | |
limite, en effet vu comme ça, le noyau ne dispose pas d'un accès matériel comme | |
un vrai noyau l'aurait). | |
\begin{figure}[!h] | |
\caption{Diagramme d'une virtualisation en noyau user-mode} | |
\centering | |
\includegraphics{Diagramme_ArchiKernelUserSpace.eps} | |
\end{figure} | |
\subsubsection{Hyperviseur de type 2} | |
Un hyperviseur de type 2 est un logiciel qui tourne sur le système d'exploitation | |
hôte et permet de lancer un ou plusieurs systèmes d'exploitation invités. | |
Le logiciel virtualise (ou émule) le matériel pour les systèmes invités. Ces | |
systèmes pensent alors dialoguer directement avec le matériel alors qu'en | |
réalité il y a une couche entre les deux. | |
Les solutions les plus connues sont VMware Player/Workstation, | |
Oracle VirtualBox ou encore Parallels Desktop. | |
\newline | |
La différence entre l'émulation et la virtualisation est l'accessibilité | |
du système hôte. Un émulateur s'occupe de transférer les demandes du système | |
invité vers le système hôte. L'avantage est côté performances vu qu'il ne s'agit | |
que d'une translation de données, le problème est que le système invité doit | |
être compatible avec le matériel du système hôte. | |
Pour faire tourner des systèmes totalement différents et même non compatibles | |
avec le matériel hôte (exemple faire tourner un système embarqué ARM ou MIPS | |
sur du x86), il faut passer par un émulateur. Son job consiste, lui, à traduire | |
tout ce qui passe entre le système invité et hôte. L'impacte sur les performances | |
est très important au vu du nombre d'opérations qu'il faut faire. Un émulateur | |
libre très connu est \call{qemu}. Cet émulateur (qui fait aussi de la virtualisation) | |
a été utilisé dans ce projet car il propose des outils de débogage très avancés. | |
\begin{figure}[!h] | |
\caption{Diagramme de virtualisation avec un hyperviseur de type 2} | |
\centering | |
\includegraphics{Diagramme_ArchiEmulateur.eps} | |
\end{figure} | |
\subsubsection{Hyperviseur de type 1} | |
Un hyperviseur de type 1 est comme un noyau très léger et dédié pour faire | |
tourner des machines virtuelles dessus. L'avantage d'un système dédié est | |
qu'il est prévu et optimisé pour, la gestion des ressources est grandement | |
mieux gérée par ce système et permet des performances très élevées. Ce type | |
d'hyperviseur n'est du coup pas prévu pour tourner sur une machine end-user | |
mais bien dédié sur un serveur qui ne fait que ça. C'est actuellement la | |
solution la plus aboutie pour virtualiser entièrement un système d'exploitation | |
complet en dégradant le moins les performances. | |
\newline | |
Cependant, l'utilisation d'un noyau très léger a pour contrainte de n'être | |
compatible qu'avec un nombre restreint de matériel et ces solutions | |
sont généralement onéreuses. Heureusement, des solutions libres existent | |
comme Citrix Xen, KVM, ... Côté propriétaire, les plus connus sont | |
Microsoft Hyper-V, VMware ESX. | |
\begin{figure}[!h] | |
\caption{Diagramme de virtualisation avec un hyperviseur de type 1} | |
\centering | |
\includegraphics{Diagramme_ArchiHyperviseur.eps} | |
\end{figure} | |
\subsubsection{Virtualisation par Isolateur} | |
Un isolateur est un logiciel qui isole l'exécution d'un programme dans un | |
contexte bien précis. L'isolateur permet de ne montrer que ce qu'il veut | |
à l'application qui tourne derrière, c'est lui qui s'occupe de filtrer | |
le monde réel à l'application qu'il englobe. De ce fait on peut faire tourner | |
plusieurs instances d'une application sans que l'une n'interfère avec l'autre. | |
\newline | |
Niveau performance, cette technique est très rentable car l'overhead (le | |
temps perdu à se gérer soi-même) est très faible. Il n'y a qu'un seul noyau | |
qui tourne, qu'une seule gestion de la machine globale et des petites parties | |
qui s'occupent d'isoler. | |
\newline | |
Sous Linux, c'est effectué via les Namespaces Linux. Il dispose de plusieurs | |
formes et permet d'isoler différentes parties du système indépendamment. On | |
peut par exemple n'isoler que le réseau, que les processus, que les systèmes | |
de fichiers, etc. | |
\newline | |
Sous BSD, ce procédé s'appelle BSD Jail, sous Solaris, ce sont des « zones ». | |
A l'heure actuelle, sous Windows il n'y a qu'une solution propriétaire | |
développée par Parallels qui s'applique comme surcouche sur le système | |
d'exploitation. | |
\begin{figure}[!h] | |
\caption{Diagramme d'une virtualisation par Isolateur} | |
\centering | |
\includegraphics{Diagramme_ArchiIsolateur.eps} | |
\end{figure} | |
\subsection{ReactOS} | |
C'est cette dernière technique qui a été retenue pour mon implémentation dans ReactOS. | |
De la même façon que sous Linux, nous allons isoler la partie réseau du système pour permettre | |
de lancer plusieurs applications (identiques ou différentes) dans des contextes distincts. | |
\newline | |
\newline | |
\newline | |
\subsection{Les network namespaces} | |
Un « namespace réseau » est un contexte réseau complet isolé. Un contexte réseau complet prend réellement | |
toutes les notions de réseaux d'une stack TCP/IP générale: table de routage, table ARP, interfaces IP, | |
cartes réseaux, couche ethernet, etc. | |
\begin{figure}[!h] | |
\caption{Schéma de deux namespaces réseau dans un même système} | |
\centering | |
\includegraphics[scale=0.25]{network-namespaces.eps} | |
\end{figure} | |
En d'autres termes, avoir plusieurs namespaces réseau dans un même système consiste à instancier plusieurs | |
fois la stack TCP/IP sans aucun liens (au départ en tout cas, rien n'empêche la connexion des namespaces | |
entre eux, comme on verra plus loin) entre ces différentes instances. | |
\subsection{Alternatives à l'isolation réseau sous d'autres systèmes} | |
Sous Windows, la seule solution qui permette cette approche est la solution propriétaire | |
« Parallels Virtuozzo Containers », hélas il n'y a que très peu d'informations sur le fonctionnement de son | |
implémentation. | |
\newline | |
Sous Cisco, ce principe est appelé « VRF » (Virtual Routing and Forwarding), il consiste à avoir | |
plusieurs tables de routage différentes qui co-existent sur un même routeur. | |
\newline | |
Sous Solaris (depuis Solaris 10, 2005) on retrouve une isolation « Solaris Containers » qui inclut | |
ce qu'ils appellent des « Zones ». Chaque zone a son propre nom (node name), sa propre stack réseau | |
et son propre stockage. | |
\newline | |
Sous FreeBSD, la technique d'isolation se nomme « FreeBSD Jails », cependant cette isolation | |
ne crée pas une nouvelle stack réseau. Il n'y a qu'une protection (isolation) aux niveaux processus, fichier | |
et des sessions utilisateur/administrateur. | |
\newline | |
%% FIXME: network namespace Linux | |
Pour expliquer comment j'ai implémenté l'isolation de la stack réseau dans ReactOS, je vais expliquer | |
le fonctionnement interne de la stack et comment j'ai isolé les différentes parties qui en dépendent (processus, etc.). | |
Je vais commencer par les processus, puis les différentes couches OSI intéressantes (layer 3 puis layer 2) | |
pour enfin finir sur l'implémentation du lien entre les couches supérieures du système (le user-mode) et la stack | |
réseau. | |
\newpage | |
\section{Implémentation des namespaces réseau dans ReactOS} | |
L'un des points les plus importants est qu'il ne faut absolument pas casser la compatibilité des applications | |
déjà existantes avec le système, en y incluant les namespaces. Tout doit être transparent. Pour ce qui est du code, | |
pour rendre sa maintenance la plus facile possible, il est préférable de ne modifier qu'une petite partie du système | |
et d'y centraliser toutes les modifications plutôt que de faire des changements plic-ploc un peu partout dans le kernel. | |
\subsubsection{Gestion des Processus} | |
Sous Linux, la création d'un namespace se fait via un flag dans le syscall \call{clone()}. L'apparition | |
des différents namespaces sous Linux ont ajouté les flags suivant: | |
\begin{itemize} | |
\item \textbf{CLONE\_NEWNS}\newline | |
Isolation des points de montages | |
\item \textbf{CLONE\_NEWUTS}\newline | |
Isolation du hostname et domain name | |
\item \textbf{CLONE\_NEWIPC}\newline | |
Isolation des communications inter-processus | |
\item \textbf{CLONE\_NEWPID}\newline | |
Isolation des PID, cela a pour effet de pouvoir avoir plusieurs | |
processus avec le même PID et avoir différents \call{init} comme parent (PID 1) | |
\item \textbf{CLONE\_NEWNET}\newline | |
Isolation de la couche réseau (routes, ARP, etc.) | |
\item \textbf{CLONE\_NEWUSER}\newline | |
Isolation des user-id et group-id ce qui a pour effet de pouvoir séparer | |
les permissions et les privilèges entre le namespace et l'extérieur (par exemple être root | |
dans le namespace mais ne pas l'être dans l'environnement extérieur) | |
\newline | |
\end{itemize} | |
L'idée que j'ai retenue pour implémenter une isolation réseau sous ReactOS est de faire un procédé similaire. | |
Sous Windows il n'existe pas de syscall POSIX \call{clone()} (ce syscall est utilisé par \call{fork()} | |
et \call{pthread()}, par exemple). Pour lancer un nouveau processus, Windows dispose du syscall | |
\call{CreateProcess()}. J'ai donc ajouté un flag que l'on peut passer en paramètre à \call{CreateProcess()}. | |
Les flags existant sont (par exemple): | |
\begin{itemize} | |
\item \textbf{CREATE\_NEW\_CONSOLE}\newline | |
Le nouveau processus est ouvert dans une nouvelle console au lieu d'hériter de la | |
console parente (par défaut) | |
\item \textbf{CREATE\_SUSPENDED}\newline | |
Le thread principal est lancé en mode suspendu et ne sera actif que lors d'un | |
appel à \call{ResumeThread()} | |
\item \textbf{DEBUG\_PROCESS}\newline | |
Active le mode de debug d'un processus (et de ses fils), ce qui permet l'utilisation | |
de \call{WaitForDebugEvent()} entre autres | |
\item ... | |
\newline | |
\end{itemize} | |
Pour garder une cohérence entre Linux et Windows, j'ai fait un flag | |
\linebreak\call{PROCESS\_CREATE\_FLAGS\_NEWNET}. | |
Ce flag permet de lancer une application (console par exemple, mais n'importe quelle application | |
peut être exécutée par là. L'avantage de lancer une console est qu'on dispose | |
alors d'un « launcher » pour lancer d'autres application dans le même namespace). Les namespaces sont | |
automatiquement hérités de leurs parents, donc tout ce qui sera lancé depuis le namespace 1 (par exemple), à | |
moins de n'avoir le flag qui crée un nouveau namespace spécifié, sera également attaché au namespace 1. | |
\newline | |
Tant qu'il existe au moins un processus dans un namespace, celui-ci est considéré comme actif. Une fois | |
qu'il ne reste plus de processus attaché à un namespace, ce dernier est détruit et son ID sera le prochain | |
utilisé. | |
\newline | |
% | |
% process.c - kill.c | |
% | |
\subsubsection{Implémentation du gestionnaire de namespaces réseau} | |
Pour ce qui est de l'implémentation, la création d'un namespace se fait dans | |
\linebreak\file{/ntoskrnl/ps/process.c} | |
dans la fonction \call{PspCreateProcess()}. J'ai également créé un nouveau fichier dans | |
\file{/ntoskrnl/netns/} qui se nomme \file{networknamespace.c} pour gérer les appels | |
de drivers et la communication avec le kernel. | |
Dans \file{/ntoskrnl/ps/kill.c} se trouve le code qui gère la fin d'un processus. Quand un processus est fermé (ou tué), | |
le gestionnaire de namespace est appelé pour vérifier si il existe encore un processus dans le namespace dans | |
lequel se trouvait le processus, si ce n'est pas le cas, le namespace est supprimé et ce qui y était lié est déchargé. | |
\newline | |
Pour stocker (faire le lien) entre un namespace et un processus, une bonne solution serait d'implémenter | |
un système permettant d'isoler les processus entre eux, mais cette solution consiste plus à implémenter | |
l'équivalent d'un \call{CLONE\_NEWPID}, ce qui n'est pas le but recherché. La méthode la plus simple que j'ai | |
trouvée a été d'attribuer un ID (champ \call{NetNs}) dans la table principale qui gère les processus, à savoir | |
la table globale de structure \call{EPROCESS}. Cette table est la table utilisée par le système pour stocker | |
le PID et tout le contexte d'utilisation d'un processus, ça me semble un bon endroit pour y stocker le namespace | |
qui lui est lié, le seul souci qu'il peut y avoir avec cette solution est que la taille de la structure | |
\call{EPROCESS} est modifiée. Cette table est utilisée entre autres par \textit{WinDBG} (le débuggeur Microsoft) | |
et certains développeurs de ReactOS m'ont averti sur le channel IRC de \#reactos que ça \textbf{pourrait} casser | |
le fonctionnement de \textit{WinDBG}. Je n'ai remarqué aucun problème avec celui-ci durant mon travail. | |
\newline | |
Dans mon implémentation, seul le namespace réseau est stocké en dur dans la table, pour étendre cette feature, | |
remplacer ce champ par une structure pour y stocker l'ID des différents namespaces peut être une solution | |
intéressante. | |
\newline | |
Pour ne pas devoir accéder à cette table depuis un driver/le kernel (tout d'abord parce que cette structure est | |
opaque d'après la MSDN, et puis surtout par propreté et logique d'utilisation), j'ai fait une fonction qui permet | |
de récupérer le numéro du namespace réseau courant via \call{IoGetCurrentProcessNs()}. Cet appel est utilisé un peu | |
partout dans la lib ip et dans le protocol driver tcpip pour prendre une décision sur quelle table (routage, ARP, ...) | |
utiliser. | |
\newline | |
La communication entre le kernel et la stack TCP/IP se fait via l'objet | |
\linebreak\call{\textbackslash{}Device\textbackslash{}NamespaceNetwork}. | |
Cet objet est initialisé dans le driver \file{tcpip.sys} | |
ce qui est loin d'être la meilleure solution, en effet cela implique que le gestionnaire de namespaces réseau | |
doit attendre que \file{tcpip.sys} soit chargé pour pouvoir dispatcher les opérations du kernel. Dans le cas présent, | |
c'était le moyen le plus facile de faire communiquer \file{tcpip.sys} et le kernel sans rien modifier d'autre dans | |
le kernel. | |
\newline | |
Pour ne pas devoir créer un syscall par opération, la méthode la plus évidente est de faire un syscall | |
qui prend une structure en paramètre et une opération tirée d'une liste. Côté kernel, la | |
liste des opérations (opcode) que j'ai implémentées se trouve dans l'enum suivant: | |
\begin{listing}[H] | |
\caption{Extrait de /drivers/network/tcpip/include/dispatch.h} | |
\begin{ccode} | |
enum NETNSOPCODE { | |
KCREATE_NAMESPACE, | |
KREMOVE_NAMESPACE, | |
KATTACH_INTERFACE, | |
KDETACH_INTERFACE, | |
KSET_MACADDRESS, | |
KENABLE_SWITCH, | |
KDISABLE_SWITCH, | |
}; | |
\end{ccode} | |
\end{listing} | |
Cette liste contient le minimum nécessaire pour créer et supprimer un namespace, mais en plus elle permet de | |
gérer les « virtuals ethernet » (et les interfaces physiques également en réalité) car pour l'instant il n'y avait | |
aucune commande implémentée pour attribuer une adresse IP ou MAC à une interface depuis la ligne de commande. | |
Actuellement l'enum est copié/collé à plusieurs endroits dans le code car les dépendances d'includes sont assez | |
tordues pour pouvoir définir tout comme il faut partout. Il faut faire attention lors de la modification de l'enum | |
de modifier les fichiers: | |
\begin{itemize} | |
\item \file{/drivers/network/tcpip/include/dispatch.h} | |
\item \file{/include/ndk/netns.h} | |
\item \file{/include/psdk/netns.h} | |
\item \file{/ntoskrnl/include/internal/networknamespace.h} | |
\newline | |
\end{itemize} | |
Cet enum contient également la gestion du switch virtuel que j'ai implémenté pour gérer la couche 2 du modèle OSI | |
(le switch est expliqué section \ref{sub:switch} page \pageref{sub:switch}). | |
\newline | |
Le syscall kernel (qui se trouve dans namespace.c) est \call{NtSetNetworkNamespace()}. Il prend en paramètre une | |
structure \call{NETNSOP} qui contient un ID de namespace, un opcode (l'enum plus haut), une valeur (un \call{int} | |
utilisé différemment en fonction de l'opcode) et une adresse MAC (qui n'est utilisée qu'avec l'opcode KSET\_MACADDRESS). | |
Toutes les options sont préfixées par « K » pour différencier facilement la partie kernel-mode | |
de la partie user-mode. | |
\begin{figure}[!h] | |
\caption{Diagramme de création d'un namespace en kernel-space} | |
\centering | |
\includegraphics[scale=0.6]{namespace-processing.eps} | |
\end{figure} | |
\newpage | |
(Notez que la partie « Create new TDI Entity List » sera expliquée plus loin). | |
Pour ce qui est de la partie usermode, les opcode sont définis: | |
\begin{listing}[H] | |
\caption{Extrait de /include/psdk/winbase.h} | |
\begin{ccode} | |
enum UNETNSOPCODE { | |
ATTACH_INTERFACE, | |
DETACH_INTERFACE, | |
SET_MACADDRESS, | |
ENABLE_SWITCH, | |
DISABLE_SWITCH, | |
}; | |
\end{ccode} | |
\end{listing} | |
Comme on peut le voir, on ne peut pas créer de namespace via cet appel, il n'y a que via le \call{CreateProcess()} | |
qu'on peut créer un nouveau namespace dans l'implémentation actuelle. Ces opcodes sont surtout faits pour interagir | |
avec les namespaces existants. Les structures et les fonctions ont été crées, | |
maintenant il reste à faire le lien entre le kernel et le usermode. | |
% | |
% SYSCALL | |
% | |
\subsubsection{Ajout et linkage d'un syscall dans le kernel} | |
Lors de la compilation, pour appeler le kernel depuis le usermode, il faut pouvoir compiler et linker | |
l'exécutable correctement. Pour ça, il faut qu'à la compilation, le compilateur sache où se trouve | |
l'appel système, comment l'appeler, etc. | |
\newline | |
Pour respecter la programmation Windows classique, pour faire un appel du user-mode au kernel-mode, il faut | |
passer par la Win32 API. La Win32 API se trouve (en partie) dans \file{kernel32.dll} qui lui même appelle | |
des fonctions de la Native API \file{ntdll.dll}. | |
\newline | |
\file{ntdll.dll} est la partie du système qui exporte les appels publics du kernel Windows (\file{ntoskrnl.exe}), | |
appelés des « Native API ». Le code linké avec \call{ntdll.dll} est appelé « Native Application » et ne dépend | |
pas de Win32 (ce code peut donc être appelé au début du chargement du système, avant que l'API utilisateur classique | |
(Win32) soit utilisable, comme par exemple, autochk.exe qui s'occupe de lancer CheckDisk au démarrage). | |
Kernel32.dll est donc une « Native Application » qui exporte au reste du système des appels | |
de plus haut niveau (gestion de threads, mémoire, etc.). | |
\begin{figure}[!h] | |
\caption{Diagramme de la couche d'appel du kernel à la Win32 API} | |
\centering | |
\includegraphics[scale=0.2]{kernel32.eps} | |
\end{figure} | |
Concrètement, dans ReactOS, \file{ntdll.dll} et \file{kernel32.dll} ont dans leurs dossiers sources, un | |
fichier \file{kernel32.spec} et \file{ntdll.spec} qui contient la liste des fonctions à exporter ainsi | |
que la taille et le nombre de paramètres que la fonction prend. | |
\begin{listing}[H] | |
\caption{Code ajouté à ntdll.spec, ntoskrnl.spec et kernel32.spec} | |
\begin{textcode} | |
@ stdcall NtSetNetworkNamespace(ptr) ; ntdll.spec | |
@ stdcall IoGetCurrentProcessNs() ; ntoskrnl.spec | |
@ stdcall SetNetworkNamespace(ptr) ; kernel32.spec | |
\end{textcode} | |
\end{listing} | |
Il ne reste plus qu'à ajouter les prototypes dans un \file{.h} global (comme \file{winbase.h} par exemple), et | |
le tour est joué. Maintenant que le syscall est fait et exporté, il reste à l'utiliser. | |
Tout ces appels ont été utilisés en user-mode avec l'utilitaire en ligne de commande « \file{unshare.exe} » | |
que j'ai spécialement développé dans le cadre de ce travail. | |
% | |
% unshare.exe usermode | |
% | |
\subsubsection{Unshare} | |
Sous Linux, l'application « unshare » permet de lancer une application dans un nouveau namespace (au choix). | |
Pour l'exemple ci-dessous, on va lancer un \call{bash} dans un nouveau namespace réseau et on va voir que la | |
liste des interfaces disponibles sur le système ne sera pas la même qu'avant. | |
\begin{listing}[H] | |
\caption{Utilisation de « unshare » sous Linux} | |
\begin{bashcode} | |
# unshare --help | |
-m, --mount unshare mounts namespace | |
-u, --uts unshare UTS namespace (hostname etc) | |
-i, --ipc unshare System V IPC namespace | |
-n, --net unshare network namespace | |
-p, --pid unshare pid namespace | |
-U, --user unshare user namespace | |
-f, --fork fork before launching <program> | |
--mount-proc[=<dir>] mount proc filesystem first (implies --mount) | |
# ip link | |
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode [...] | |
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 | |
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP [...] | |
link/ether 8c:89:a5:c8:8a:84 brd ff:ff:ff:ff:ff:ff | |
\end{bashcode} | |
\end{listing} | |
\begin{listing}[H] | |
\caption{Nouveau namespace avec « unshare » sous Linux} | |
\begin{bashcode} | |
# unshare --net bash | |
# ip link | |
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default | |
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 | |
\end{bashcode} | |
\end{listing} | |
Le nouveau namespace réseau est volatile (le namespace est détruit une fois que bash est quitté). | |
J'ai voulu reprendre cette idée de pouvoir créer un namespace via une commande \call{unshare}, | |
mais en plus de ça, cette commande est devenue la commande principale | |
de gestion des namespaces, des ip, des adresses mac, etc. | |
Par facilité, l'implémentation de \file{unshare.exe} est clairement un « fourre-tout », l'idée est de supporter | |
pleinement l'aspect kernel de l'isolation, le nom du programme qui va changer l'adresse IP ou l'adresse MAC de | |
l'interface a peu d'importance vis-à-vis de l'implémentation dans le système. Pour plus de précisions sur les | |
utilitaires qui devraient être utilisés, voir section \ref{next:utils} page \pageref{next:utils}. | |
\newline | |
Pour ce qui est de la partie volatile, j'ai gardé ce système pour le portage. Cela facilite le développement sans pour | |
autant enlever des fonctionnalités. Faire du code persistant nécessiterait de sauver les ID des namespaces et les | |
interfaces qui y sont attachées. Pour garder une persistance, à l'heure actuelle, il faut créer un script | |
de démarrage qui lance à la manière d'un batch, toutes les commandes | |
\file{unshare.exe} nécessaires, un peu à la façon d'\call{iptables} sous Linux. | |
\newline | |
Le code d'\file{unshare.exe} se trouve dans \file{/base/applications/network/unshare/} | |
et sa syntaxe est relativement simple, on la verra au cours du développement qui suit | |
\footnote{Un résumé de la syntaxe se trouve en annexe, page \pageref{a:unshare}}. | |
Maintenant que les processus peuvent utiliser différents namespaces, voyons comment nous allons exploiter ça dans | |
le driver \file{tcpip.sys} et comment nous allons trainer les I/O réseaux pour avoir un fonctionnement cohérent et isolé. | |
\subsection{Implémentation des namespaces dans la stack réseau (tcpip.sys)} | |
Le fichier \file{tcpip.sys} s'occupe entièrement du traitement réseau dans le système. Même si le résultat | |
final est un gros fichier binaire qui contient tout, le code, lui, est bien segmenté suivant les différents traitements | |
(couche OSI 2, 3 et 4). | |
\newline | |
Le principe à suivre est simple. Côté usermode, on demande quelque chose au kernel (à un driver) et le système nous | |
donne une réponse. Cette réponse est généralement une liste d'éléments (liste d'interfaces, routes, adresses, ...). | |
Il « suffit » donc de trouver où cette liste est générée et d'y ajouter un matching sur le namespace en plus que | |
le traitement qui est actuellement fait. L'idée est là. Pour ce faire, on va analyser le code qui s'occupe du layer 3. | |
% | |
% /lib/drivers/ip/ | |
% | |
\label{sub:libip} | |
\subsubsection{Bibliothèque IP} | |
Comme dit plus haut, la partie TCP/IP du système utilise 2 grandes parties, le code de \file{tcpip.sys} et la bibliothèque | |
ip qui est statiquement linkée. | |
\newline | |
La lib ip qui se trouve dans \file{/lib/drivers/ip/} est structurée ainsi (je n'ai gardé que les fichiers qui ont été utilisés | |
dans ce travail): | |
\begin{itemize} | |
\item \file{/lib/drivers/ip/network}: | |
\begin{itemize} | |
\item \textbf{\file{address.c}}: | |
permet de stocker une adresse ip (ipv4 et ipv6) et de manipuler, | |
tester et les comparer. Il y a également des fonctions de debug intéressantes dedans | |
dont une qui permet de formater une adresse pour pouvoir l'afficher en tant que string. | |
\item \textbf{\file{arp.c}}: | |
son nom peut être trompeur, il s'agit du handler de paquets ARP et non | |
de la gestion de la table ARP du système. Les fonctions contenues dans ce fichier s'occupent | |
soit de forger une requête ARP et de l'envoyer sur le réseau, soit de lire un paquet ARP | |
et d'en analyser le contenu et traiter (ou pas) la requête. | |
\item \textbf{\file{icmp.c}}: | |
son nom l'indique clairement, ce fichier contient les fonctions pour envoyer | |
ou recevoir un paquet ICMP. | |
\item \textbf{\file{interface.c}}: | |
permet de manipuler l'état et des informations sur une interface ip. | |
\item \textbf{\file{ip.c}}: | |
la partie principale de la bibliothèque, ce fichier contient ce qu'il faut pour | |
initialiser la bibliothèque, enregistrer des protocoles, des interfaces et communiquer avec | |
\file{tcpip.sys} | |
\item \textbf{\file{loopback.c}}: | |
tout le code relatif aux interfaces de loopback. L'idée que j'avais du loopback sous Windows | |
était qu'une interface NDIS (comme tout le reste du système) s'occupait de gérer ça, mais en fait | |
non, c'est une interface virtuelle de niveau 3 qui s'occupe de tout gérer (en tout cas dans ReactOS). | |
\item \textbf{\file{neighbor.c}}: | |
la gestion des voisins (les adresses MAC des voisins, donc la table ARP en définitive). | |
\item \textbf{\file{receive.c}}: | |
dispatcheur de paquets reçus. | |
\item \textbf{\file{router.c}}: | |
s'occupe de la gestion (ajout, suppression, recherche) de route dans la table de routage. | |
\newline | |
\end{itemize} | |
\item \file{/lib/drivers/ip/transport}:\newline | |
contient des sous-dossiers (tcp, udp, rawip) contenant la gestion des différents protocoles de couche 4. Toute l'isolation | |
étant faite après la couche 3, rien de cette partie n'a été modifié, il n'y avait aucune raison d'y toucher. | |
\newline | |
\end{itemize} | |
Bien que ça soit une bibliothèque (en tout cas d'après le path du code), une chose étonnante est que tout les includes se trouvent dans | |
le dossier de tcpip (dans \file{/drivers/}) et non dans la lib avec le reste. On voit donc très clairement qu'il y a une dépendance mutuelle | |
entre les deux parties. | |
% | |
% IP Address struct | |
% | |
\subsubsection{Adresses} | |
Les adresses IP sont stockées sous forme d'une structure pouvant aussi bien accepter une IPv4 qu'une IPv6. | |
\begin{listing}[H] | |
\caption{struct IP\_ADDRESS} | |
\begin{ccode} | |
typedef struct IP_ADDRESS { | |
UCHAR Type; | |
union { | |
IPv4_RAW_ADDRESS IPv4Address; | |
IPv6_RAW_ADDRESS IPv6Address; | |
} Address; | |
} IP_ADDRESS, *PIP_ADDRESS; | |
\end{ccode} | |
\end{listing} | |
Le Type défini si il s'agit d'une IPv4 ou une IPv6 (\hexa{0x04} ou \hexa{0x06}, défini par les deux defines \call{IP\_ADDRESS\_V4} | |
et \call{IP\_ADDRESS\_V6}) puis l'union contient l'adresse en question. | |
% | |
% IP Interface struct | |
% | |
\subsubsection{Interfaces} | |
Les adresses IP ne sont pas attribuées à des interfaces NDIS ou des interfaces layer 2 directement. La bibliothèque contient | |
ses propres interfaces (liées à l'interface de la couche inférieure par un pointeur) et crée donc une indépendance à ses couches inférieures. | |
Une interface IP est définie comme telle (seul les champs intéressants pour ce travail ont été gardés): | |
\begin{listing}[H] | |
\caption{struct IP\_INTERFACE} | |
\begin{ccode} | |
typedef struct _IP_INTERFACE { | |
// [...] | |
LIST_ENTRY ListEntry; | |
PVOID Context; | |
IP_ADDRESS Unicast; | |
IP_ADDRESS Netmask; | |
IP_ADDRESS Broadcast; | |
UNICODE_STRING Name; | |
UINT Index; | |
LL_TRANSMIT_ROUTINE Transmit; | |
// [...] | |
} IP_INTERFACE, *PIP_INTERFACE; | |
\end{ccode} | |
\end{listing} | |
Les noms de variables sont globalement clairs, mais quelques précisions: | |
\begin{itemize} | |
\item \textbf{ListEntry}: champ utilisé par les listes chainées implémentées dans le code | |
\item \textbf{Context}: un pointeur void vers quelque chose d'intéressant de niveau inférieur. | |
Dans notre cas, ce pointeur sera le lien vers l'interface layer 2 à laquelle l'interface IP | |
est attachée. | |
\item \textbf{Index}: numéro unique d'identification de l'interface. C'est cet index qu'on retrouvera comme | |
numéro d'interface lors d'un route print par exemple. Pour communiquer avec le usermode, c'est cet index | |
qui sera utilisé pour spécifier une interface, c'est plus facile que de matcher des noms. | |
\item \textbf{Transmit}: pointeur de fonction vers un handler qui va dispatcher un paquet pour l'envoyer | |
vers la couche inférieure. | |
\newline | |
\end{itemize} | |
Cette structure a été adaptée en fonction des différents besoins qui sont expliqués dans ce chapitre. | |
% | |
% IP Library Main | |
% | |
\subsubsection{IP} | |
Le point d'entrée et le coeur de la bibliothèque IP. Bien que cette partie n'ait presque pas été modifiée, il est important de | |
comprendre son fonctionnement pour savoir comment tout est lié ensemble. | |
C'est dans la routine d'initialisation du driver TCPIP que l'initialisation de la bibliothèque est appelée. Elle met en place | |
ce qu'il faut pour gérer les listes d'interface et prépare l'enregistrement des protocoles. | |
\newline | |
Les protocoles ICMP, TCP et UDP s'enregistrent comme handlers et lors de la réception d'un paquet, une itération sur les handlers | |
s'effectue pour savoir à qui envoyer le paquet pour le traiter. De cette façon il est très facile d'implémenter un nouveau protocol | |
car il s'agit de créer un nouveau traitement et l'enregistrer comme les autres sans modifier le code déjà existant. | |
L'avantage pour moi, c'est qu'il suffit d'adapter le coeur pour que tout ce qui s'y attache puisse | |
bénéficier de l'isolation de manière transparente, c'est pour ça que la couche 4 n'a pas à être modifiée. | |
\newline | |
C'est également dans cette partie de code qu'on ajoute une interface IP. Pour ce faire, il suffit de remplir une structure \call{PLLIP\_BIND\_INFO} | |
et de la passer à la fonction \call{IPCreateInterface} qui va s'occuper d'ajouter une nouvelle interface à la liste globale. La structure | |
\call{PLLIP\_BIND\_INFO} est en fait une \call{IP\_INTERFACE} très simplifiée qui ne contient que le minimum pour la lier au reste du système: | |
le contexte de la couche inférieure, la taille du header de la couche inférieure (pour savoir ce qu'il faut ignorer lors de la réception d'un paquet) et | |
un pointeur vers une fonction qui permet de préparer un paquet à envoyer. | |
% | |
% Loopback Interface | |
% | |
\subsubsection{Loopback} | |
La partie loopback a été un point maître dans l'implémentation car c'est la façon la plus simple de pouvoir tester l'isolation d'interface. | |
Sous Linux, quand on crée un nouveau namespace, automatiquement une interface de loopback (à l'état down) est ajoutée à ce namespace. Linux | |
permet une utilisation du réseau sans interface de loopback ce qui n'est pas le cas sous Windows. En effet, pour ce qui est de ReactOS, toutes | |
les manipulations de recherche d'interface, etc., partent du principe qu'il y a \textbf{TOUJOURS} une interface existante, la loopback. Cela | |
simplifie le code et le principe, étant donné qu'il ne faut pas prendre en compte le cas où aucune interface n'existe. Le revers de la médaille est | |
qu'on ne donne pas un accès complet à la couche réseau à l'utilisateur. Il est (sans hack du kernel) impossible de changer l'adresse ip de l'interface | |
de loopback ni même de désactiver l'interface. | |
\newline | |
En partant de ce principe, leur idée (que je trouve justifiée) a été de mettre une variable globale dans la bibliothèque qui contient les informations | |
à propos de la loopback (un pointeur vers sa structure \call{IP\_INTERFACE} en d'autres termes). C'est là que ça se corse. L'implémentation n'est | |
\textbf{ABSOLUMENT} pas prévue pour supporter plusieurs interfaces de loopback. Pour avoir une isolation correcte des namespaces, il est normal | |
d'avoir des loopbacks indépendantes qui peuvent toutes avoir la même adresse IP (typiquement 127.0.0.1/8). Il a donc fallu trouver un moyen | |
de gérer plusieurs loopbacks (si possible sans réécrire tout ce qui existe déjà...) | |
\newline | |
La méthode qui me semble la plus logique est de commencer par remplacer la variable globale par une liste de loopbacks et non par une loopback unique. | |
En utilisant le système de liste chainée du système, pas besoin de faire grand chose: | |
\newline | |
\begin{listing}[H] | |
\caption{struct LOOPLIST} | |
\begin{ccode} | |
typedef struct _LOOPLIST { | |
LIST_ENTRY ListEntry; | |
PIP_INTERFACE Loopback; | |
} LOOPLIST, *PLOOPLIST; | |
\end{ccode} | |
\end{listing} | |
Cette méthode sert à isoler la liste des loopbacks indépendamment du reste du code, mais plus loin, on se rend compte que pour savoir si une interface | |
précise est une loopback, ils comparent l'adresse de la variable globale à l'interface. Cette méthode (un peu trop naïve) est adaptable en remplacant | |
la comparaison par un appel de fonction qui itère la liste des loopbacks pour savoir si elle est présente dans la liste ou pas. Cette méthode n'est pas | |
idéale quand on peut faire beaucoup plus simple: ajouter un flag à la structure \call{IP\_INTERFACE} pour savoir si c'est une loopback ou pas. | |
De cette façon, on sait directement si une interface précise est une loopback. C'est beaucoup plus facile et efficace qu'une liste. A noter que | |
j'ai quand même laissé la liste des loopbacks dans le code, ça laisse une abstraction de dépendances (liste de loopbacks, liste d'interfaces). | |
\newline | |
Lors de la création d'une loopback, elle s'identifie comme étant dans le namespace 0 par défaut. C'est au code qui a appelé la création de la loopback | |
de changer son namespace pour la mettre dans le bon lui-même. | |
\subsubsection{Voisinage} | |
Le voisinage (la table ARP en d'autres termes) est une table temporaire (les champs qui y sont stockés, y sont en | |
cache avec un timeout, une fois le timeout dépassé, l'entrée est supprimée) qui fait la correspondance entre | |
un adresse IP et une adresse MAC (qui a été résolue par le protocol ARP). Cette table est nécessaire pour | |
envoyer une trame ethernet vu qu'on doit placer l'adresse MAC de destination dans le header ethernet. | |
\newline | |
La table de voisinage est faite comme suit: | |
\begin{listing}[H] | |
\caption{struct NEIGHBOR\_CACHE\_ENTRY} | |
\begin{ccode} | |
typedef struct NEIGHBOR_CACHE_ENTRY { | |
// [...] | |
UCHAR State; | |
UINT EventTimer; | |
UINT EventCount; | |
PIP_INTERFACE Interface; | |
PVOID LinkAddress; | |
IP_ADDRESS Address; | |
// [...] | |
} NEIGHBOR_CACHE_ENTRY, *PNEIGHBOR_CACHE_ENTRY; | |
\end{ccode} | |
\end{listing} | |
\begin{itemize} | |
\item \textbf{State}: état de la résolution (reachable, stale, delay, incomplete, failed, ...) | |
\footnote{Voir le fonctionnement du protocole ARP, bon exemple: http://linux-ip.net/html/ether-ARP.html} | |
\item \textbf{EventTimer}: durée (ticks) depuis le dernier événement | |
\item \textbf{EventCount}: nombre d'événements | |
\item \textbf{Interface}: interface depuis laquelle l'entrée ARP a été ajoutée | |
(donc l'interface par laquelle il faut sortir un paquet vers cette destination) | |
\item \textbf{LinkAddress}: pointeur vers l'adresse de layer 2 (dans notre cas, une adresse MAC) | |
\item \textbf{Address}: adresse IP de destination | |
\newline | |
\end{itemize} | |
Pour avoir une idée de comment c'est représenté en mémoire, voilà un diagramme des éléments accessibles depuis | |
une entrée dans la table ARP du système: | |
\begin{figure}[!h] | |
\caption{Diagramme de relation avec une entrée dans la table ARP} | |
\centering | |
\includegraphics[scale=0.3]{neighbor-diagram.eps} | |
\end{figure} | |
\subsubsection{Routeur} | |
\label{sub:router} | |
Pour ce qui est du routage, le système garde une liste de destination (la FIB pour Forwarding Information Base). Cette table contient: | |
\begin{listing}[H] | |
\caption{struct FIB\_ENTRY} | |
\begin{ccode} | |
typedef struct _FIB_ENTRY { | |
LIST_ENTRY ListEntry; | |
// [...] | |
IP_ADDRESS NetworkAddress; | |
IP_ADDRESS Netmask; | |
PNEIGHBOR_CACHE_ENTRY Router; | |
UINT Metric; | |
} FIB_ENTRY, *PFIB_ENTRY; | |
\end{ccode} | |
\end{listing} | |
On y a toutes les informations nécessaires: l'adresse de destination avec son masque, l'entrée dans la cache ARP pour y accéder et son metric. | |
L'entrée dans la table ARP contient un pointeur vers l'interface de sortie. Une fois qu'on a accès à l'interface on a donc accès à son namespace. | |
Tout ce qu'il nous faut donc. Il reste à modifier le code pour qu'une recherche de route tienne compte du namespace en cours (ou un namespace voulu). | |
\newline | |
\label{sub:routerpid} | |
La fonction \call{RouterGetRoute()} s'occupe de rechercher dans la table de routage, une route vers une destination donnée. Hélas, on ne peut pas juste | |
s'occuper de comparer les namespaces puis renvoyer ce qu'on a trouvé. Durant le développement, j'ai découvert que lors de l'émission d'un paquet, tout se passe | |
bien (le namespace étant donné par le processus en cours, ça correspond), mais lors de la réception d'un paquet, le processus en cours d'exécution est \call{SYSTEM} | |
(pid 4 et de même sous Windows). Du coup, tous les processus de base étant par défaut dans le namespace 0, la réception d'un paquet ne se routait pas correctement | |
et le paquet se retrouvait drop. Il a donc fallu vérifier si c'est le procesus \call{SYSTEM} qui fait la demande ou pas. Si oui, le système a une vision | |
globale de la table et non pas uniquement son namespace. | |
\newline | |
Pour comparer le processus \call{SYSTEM}, j'ai hard-codé le numéro du PID \hexa{0x04}, ce n'est pas idéale mais je ne sais pas trop comment faire ça de façon plus générique. | |
J'ai vu dans la doc MSDN qu'il y avait une routine qui s'appelle \call{PsIsSystemThread()} qui pourrait résoudre le problème, mais je n'ai pas eu l'occasion de la tester. | |
\newline | |
Lors de la recherche d'une route, il y a un cas particulier qui est traité, c'est le cas où l'adresse de destination | |
se trouve sur une interface locale. Lors d'une recherche de route vers une destination, une première étape est | |
de voir si il existe une interface qui contient la destination, si oui on utilise la cache ARP sinon on regarde dans | |
la table de routage classique. | |
\begin{figure}[!h] | |
\caption{Diagramme de recherche d'une route} | |
\centering | |
\includegraphics[scale=0.6]{routing-diagram.eps} | |
\end{figure} | |
Lors de la recherche OnLink, j'ai dû adapter un peu la recherche en faisant une nouvelle fonction qui prend, en plus, | |
comme argument l'interface source de la requête. En fait, lors d'une communication inter-namespace (qui sera expliquée | |
plus en détail dans la partie Switching (section \ref{sub:switch} page \pageref{sub:switch})), la requête source | |
se fait depuis le PID \hexa{0x04} aussi, alors que la source ne vient pas du monde extérieur mais bien du système. | |
Il est donc possible de connaître la source et donc de pouvoir préciser la recherche à faire. En effet, sinon | |
c'est sur toutes les routes et interfaces que la recherche se fait et ça pose problème en cas d'overlapping: quelle | |
interface choisir vu qu'elles ont la même ip ? En se basant sur l'interface source, il est possible de faire | |
correspondre les namespaces en sachant lesquels sont inter-connectés (via le contexte Layer 2, expliqué | |
également plus loin). | |
\newline | |
La couche 3 (le fonctionnement et l'utilisation du protocol IP) est maintenant adaptée pour pouvoir gérer | |
du trafic depuis des namespaces isolés. | |
\newline | |
Concrêtement, on a modifié le fonctionnement de la table de routage et adapté les interfaces de loopback. | |
\newline | |
Lors du développement, après être arrivé à faire de l'isolation de ping | |
entre 2 loopbacks dans 2 namespaces différents (les loopbacks n'ont pas d'adresse MAC, il s'agit donc bien de | |
couche 3 uniquement), une grande étape a été franchie. Cependant, isoler en couche 3 n'est pas suffisant, en effet | |
pour l'instant, on est capable d'avoir du routage IP isolé, mais le broadcast ethernet ne l'est pas du tout et | |
le protocole ARP (par exemple) utilise du broadcast ethernet pour pouvoir résoudre les adresses MAC en fonction | |
des adresses IP. Sans isolation de couche 2, cette communication ne peut se faire correctement. | |
\newline | |
Voyons voir comment traiter la couche 2. | |
% | |
% lan.c | |
% | |
\subsection{Implémentation layer 2 et connexion au layer 3} | |
Pour poursuivre le processus d'isolation, il faut pouvoir différencier les différents LAN (des Virtuals LAN) | |
qui sont utilisés. Un namespace correspond à un LAN (avec un domaine de broadcast qui lui est propre). Pour | |
pouvoir isoler un LAN, il faut pouvoir isoler et dispatcher une trame ethernet vers le bon endroit | |
(un « VLAN 1 » ne doit pas recevoir des trames de broadcast du \linebreak« VLAN 2 » par exemple). | |
\newline | |
Pour y parvenir, on va attaquer une partie plus bas niveau au niveau driver, car on ne va plus travailler uniquement | |
sur une couche software type bibliothèque de gestion de paquets, mais bien d'un driver qui va communiquer avec | |
des cartes réseaux, des adresses MAC, etc. (bien entendu, la partie hardware n'entre pas en jeu, tout a été écrit | |
pour ne pas nécessiter de modification extérieure... si il avait fallu réécrire les drivers des cartes réseaux pour | |
implémenter les namespaces, l'intérêt serait bien moindre). | |
\newline | |
Comme expliqué lors du début de cet ouvrage, Windows et ReactOS utilisent NDIS pour communiquer avec le matériel | |
réseau. Les drivers Miniport NDIS s'occupent de la partie hardware et font le lien avec la partie NDIS | |
ProtocolDriver (dans notre cas, TCP/IP). | |
\newline | |
Le code du ProtocolDriver TCP/IP se trouve dans le fichier \file{/drivers/network/lan/lan/lan.c} et son point | |
d'entrée est la fonction \call{LANRegisterProtocol()}. Pendant le chargement du driver \file{tcpip.sys}, | |
cette fonction est appelée et le driver se lie (bind) avec NDIS. | |
Travailler avec NDIS, globalement, c'est appeler des \call{XxxxRegister} | |
avec comme paramètre une structure de données qui ne contient que des pointeurs de fonctions avec des prototypes | |
bien précis, qui sont décrits dans une documentation officielle\footnote{http://www.ndis.com/}. | |
C'est le cas que ça soit du Miniport ou des ProtocolDriver. | |
Une fois le ProtocolDriver correctement attaché, NDIS va appeler le callback | |
\call{LANRegisterAdapter()} pour chaque interface (Miniport) existante (si une nouvelle interface | |
Plug'n'Play vient à arriver pendant l'exécution du système, cette fonction sera également appelée | |
avec la nouvelle interface). | |
\newline | |
Pour représenter une interface layer 2, au même titre que pour la partie IP, une structure propre aux interfaces | |
layer 2 est utilisée, nommée \call{LAN\_ADAPTER}: | |
\begin{listing}[H] | |
\caption{struct LAN\_ADAPTER} | |
\begin{ccode} | |
typedef struct LAN_ADAPTER { | |
LIST_ENTRY ListEntry; | |
// [...] | |
PVOID Context; | |
NDIS_HANDLE NdisHandle; | |
NDIS_STATUS NdisStatus; | |
NDIS_MEDIUM Media; | |
UCHAR HWAddress[IEEE_802_ADDR_LENGTH]; | |
// [...] | |
UCHAR HeaderSize; | |
USHORT MTU; | |
UINT Speed; | |
UINT PacketFilter; | |
// [...] | |
} LAN_ADAPTER *PLAN_ADAPTER; | |
\end{ccode} | |
\end{listing} | |
Encore une fois, seuls les champs qui sont utilisés dans le cadre de ce développement ont été notés. | |
Pour ce qui est des précisions: | |
\begin{itemize} | |
\item \textbf{Context}: pointeur vers la structure \call{IP\_INTERFACE} qui lui est attachée | |
(voir section \ref{fig:iplanlink} page \pageref{fig:iplanlink} pour avoir un diagramme | |
plus clair). | |
\item \textbf{NdisHandle}: un identificateur unique utilisé par NDIS pour savoir où dispatcher | |
les demandes à propos de l'interface | |
\item \textbf{Media}: contient un ID sur le type d'interface dont il s'agit. Seul le média ethernet (802.3) | |
est supporté dans ReactOS. | |
\item \textbf{HWAddress}: sans doute le champ le plus important et le plus intéressant: l'adresse MAC de | |
l'interface | |
\item \textbf{PacketFilter}: flags NDIS pour filtrer les paquets qu'on va recevoir. | |
\newline | |
\end{itemize} | |
\begin{figure}[!h] | |
\caption{Diagramme de lien entre LAN\_ADAPTER et NDIS} | |
\centering | |
\includegraphics[scale=0.3]{lan_adapter-ndis.eps} | |
\end{figure} | |
Petite parenthèse: étant donné que plus loin on va devoir recevoir des trames dont l'adresse | |
MAC sera l'adresse d'une interface virtuelle, il va falloir activer le mode promiscuous de la carte réseau pour | |
ne pas que la carte (de façon hardware) drop les trames qui ne lui sont pas directement adressées. Pour cela, il | |
suffit d'ajouter le flag \call{NDIS\_PACKET\_TYPE\_PROMISCUOUS} au filtre de paquet de l'interface (PacketFilter). | |
\footnote{Le driver ethernet \textit{pcnet} qui a été implémenté dans ReactOS ne semble pas avoir de handler | |
permettant d'activer ou pas le mode promiscuous. Voir section \ref{sub:extrapcnet} page \pageref{sub:extrapcnet}} | |
\newline | |
Lorsque l'interface Miniport reçoit une trame, elle la forward à NDIS qui s'occupe de forwarder le paquet aux | |
ProtocolDrivers. Le \call{ReceiveHandler} est alors appelé avec la trame et l'interface source en paramètre. | |
% | |
% ReceivePacketHandler | |
% | |
\subsubsection{Réception d'une trame dans le driver TCP/IP} | |
La réception d'une trame est plutôt basique, le driver se charge simplement de regarder le type de data dans le | |
payload puis de retirer le header ethernet et forwarder le paquet au handler de paquets. | |
Seul les types IPv4, IPv6 et ARP sont implémentés dans ReactOS. | |
Une fois cette opération terminée, le driver notifie NDIS en disant que le traitement de trame est terminé et | |
demande de forwarder le paquet résultant au handler de paquets (\call{ReceivePacketHandler}). | |
\begin{figure}[!h] | |
\caption{Diagramme de réception d'un paquet depuis NDIS} | |
\centering | |
\includegraphics[scale=0.3]{ip-reception-stage1.eps} | |
\end{figure} | |
Une fois que le handler de paquets est appelé, le traitement du paquet est ajouté dans une queue de traitements. | |
Cette queue est gérée par une implémentation de « Chew » (\call{ChewCreate}), qui prend en paramètres une fonction | |
et un paramètre (dans ce cas ci, une structure qui contient tout ce qui est nécessaire: paquet, interface, etc.). | |
\newline | |
\begin{figure}[!h] | |
\caption{Diagramme de préparation d'un paquet NDIS reçu} | |
\centering | |
\includegraphics[scale=0.3]{ip-reception-stage2.eps} | |
\end{figure} | |
Une fois que la queue arrive au paquet en question, le contenu est analysé et extrait du paquet NDIS (qui utilise | |
un format propre au protocole pour stocker les données dans leur structure, ce n'est pas un simple buffer). | |
Les statistiques de l'interface sont mises à jour (nombre de paquets/bytes reçus) puis selon le type de payload, | |
le handler correspondant est appelé (\call{IPReceive} ou \call{ARPReceive}, le reste est droppé). | |
C'est à partir de ce moment là que la bibliothèque IP prend le relais. | |
\newline | |
\begin{figure}[!h] | |
\caption{Diagramme de réception d'un paquet depuis NDIS vers TCP/IP} | |
\centering | |
\includegraphics[scale=0.3]{ip-reception-stage3.eps} | |
\end{figure} | |
Ce ProtocolDriver est le premier à avoir un paquet entrant après NDIS, il est du coup également le dernier | |
à recevoir un paquet sortant. | |
\newline | |
% | |
% LANTransmit | |
% | |
\subsubsection{Emission d'une trame depuis le driver TCP/IP} | |
Chaque interface IP (donc de couche 3) dispose d'un pointeur de fonction vers un callback de transmission. Excepté | |
pour la loopback dont le code de transmission se trouve dans la bibliothèque IP (il n'a pas de passage via NDIS | |
pour la loopback dans ReactOS), la fonction de transmission est \call{LANTransmit} qui se trouve dans le | |
ProtocolDriver TCP/IP. | |
\newline | |
Une fois que cette fonction est appelée (elle dispose du paquet et de l'interface source qui veut émettre), elle | |
se contente de créer une nouvelle trame ethernet, d'y placer le paquet (IP) en payload et de construire l'en-tête | |
ethernet (MAC source et MAC destination) en fonction des paramètres fournis. Elle met également à jour les | |
statistiques de l'interface (nombre de paquets/bytes émis) puis forward la trame nouvellement créée à NDIS. | |
\newline | |
\begin{figure}[!h] | |
\caption{Diagramme d'émission d'un paquet depuis TCP/IP vers NDIS} | |
\centering | |
\includegraphics[scale=0.3]{LANTransmit.eps} | |
\end{figure} | |
Jusque là, il s'agit du fonctionnement d'origine du driver TCP/IP et aucune modification n'y a été vraiment apportée | |
pour y supporter les namespaces... mais il faut savoir comment le protocole fonctionne avant de le triturer pour y | |
inclure quelque chose de nouveau. L'étape suivante, qui est l'étape clé dans les containers (et donc les namespaces), | |
est l'utilisation d'interfaces virtuelles pour faire un pont entre l'intérieur isolé du namespace et l'extérieur. | |
% | |
% Virtual Ethernet | |
% | |
\subsubsection{Virtual Ethernet} | |
L'interface virtuelle que j'ai implémentée est une interface de couche 3 qui se trouve dans la bibliothèque IP | |
et qui se base sur l'interface de loopback (étant donné que la loopback est également une interface virtuelle | |
où l'émission et la réception de paquet sont communes). Cependant, elle nécessite également d'être liée à la | |
couche 2 pour disposer d'une adresse MAC. | |
\newline | |
Son implémentation se trouve dans \file{/lib/drivers/ip/network/veth.c} et va toucher aussi bien à la couche 3 que | |
la couche 2. | |
\newline | |
Je pars du principe qu'une interface virtuelle, dans le cas dont j'ai besoin, doit être liée à une interface | |
déjà existante (sinon ça revient au même que de faire une loopback). La première étape est donc d'identifier | |
l'interface demandée. Vu qu'on ne dispose que de listes linéaires, on parcourt la liste des interfaces existantes | |
et on compare l'index de l'interface avec l'index passé en paramètre (typiquement, l'index donné lors | |
de la commande \call{unshare attach <ID>}). | |
\newline | |
Ensuite, tout comme une loopback, on demande à la bibliothèque IP d'instancier une | |
\linebreak\call{IP\_INTERFACE} qu'on va ensuite configurer. | |
\newline | |
Le but d'une virtual ethernet est de s'attacher à une interface physique | |
\footnote{Rien n'empêche le code de s'attacher à une loopback ou même à une | |
autre virtual ethernet, je ne vois pas dans quel cas cela ne serait pas possible, cependant le code n'a pas été | |
testé pour, cette utilisation serait donc expérimentale.} (ou plutôt de manière générale à un NDIS Miniport). | |
Cela veut dire que pour communiquer avec NDIS, il nous faut récupérer le NdisHandler de l'interface à laquelle | |
on veut se connecter. En fait pour faire encore plus simple, on va tout simplement dupliquer la structure | |
\call{LAN\_ADAPTER} de l'interface à laquelle on s'attache, et s'y attacher. | |
L'avantage de ce cas est qu'on dispose exactement du même traitement que l'interface source (émission, réception | |
de paquets, etc.), et ayant dupliqué la structure, on peut également la modifier... donc changer l'adresse | |
MAC de l'interface ! Allons-y. Une fois la structure dupliquée, on génère une adresse MAC aléatoire (voir | |
section \ref{item:macrandom} page \pageref{item:macrandom}) puis on lie la nouvelle \call{LAN\_ADAPTER} | |
au contexte de notre nouvelle \call{IP\_INTERFACE}. | |
\label{fig:iplanlink} | |
\begin{figure}[!h] | |
\caption{Diagramme d'un lien classique entre IP\_INTERFACE, LAN\_ADAPTER et NDIS} | |
\centering | |
\includegraphics[scale=0.3]{ip_interface-lan_adapter-single.eps} | |
\end{figure} | |
\begin{figure}[!h] | |
\caption{Diagramme de lien avec une Virtual Ethernet (instance d'une LAN\_ADAPTER + IP\_INTERFACE)} | |
\centering | |
\includegraphics[scale=0.3]{ip_interface-lan_adapter.eps} | |
\end{figure} | |
L'interface virtuelle étant attachée à une interface physique, ça veut dire que d'un point de vue | |
réception de paquets, l'interface source qu'on va recevoir sera l'interface physique et non l'interface | |
virtuelle (vu que NDIS ne connait pas l'existence de \textbf{nos} interfaces virtuelles). | |
Il va donc falloir ajouter une couche entre la réception du paquet et son traitement, une couche qui | |
va consister à aiguiller le paquet vers la bonne direction | |
\footnote{Attention, la Virtual Ethernet qui a été créée ici n'est pas réellement comparable à une | |
« veth » sous Linux. Sous Linux, ce type d'interface fonctionne comme une pipe (tout ce qui entre | |
d'un côté, sort de l'autre), dans notre cas, une Virtual Ethernet nécessite un switch virtuel pour | |
fonctionner correctement}. | |
\clearpage | |
% | |
% Virtual Switch (switch.sys) | |
% | |
\subsubsection{Implémentation d'un switch pour connecter les namespaces} | |
\label{sub:switch} | |
Pour pouvoir rediriger une trame dans son namespace adéquat, il va falloir switcher les trames logiciellement. | |
En effet, chaque namespace ayant des interfaces de niveau 2 (avec des adresses MAC uniques), l'aiguillage va | |
se faire à ce niveau là. | |
\newline | |
\begin{figure}[!h] | |
\caption{Switching de trames} | |
\centering | |
\includegraphics[scale=0.3]{switching.eps} | |
\end{figure} | |
Cette implémentation est l'équivalant sous Linux de la commande: | |
\begin{center} | |
« \call{ip link add veth0 type veth peer name veth1} » | |
\end{center} | |
Sous linux, cela crée une paire d'interface (une pipe, donc tout ce qui entre d'un côté en ressort de l'autre). | |
Pour connecter un namespace avec le monde extérieur, on place un bout du pipe dans le namespace, et l'autre | |
bout du pipe dans la partie réseau globale (physique). | |
\newline | |
Dans notre cas, on va connecter une interface virtuelle (qui elle est attaché à un namespace) à un switch | |
virtuel qui va s'occuper de switcher les trames vers différents endroits (un autre namespace ou le monde | |
extérieur). Le switch n'est réellement qu'un seul gros switch mais il connait les différents | |
namespaces (« Virtual LAN ») existants et peut donc faire la part des choses. | |
Voyons maintenant l'implémentation dans le code. | |
\newline | |
La première implémentation qui a été faite est d'intégrer un switch virtuel dans la plus basse couche | |
de la réception de trames dans \file{tcpip.sys} (ReceiveHandler). Le travail du switch était simplement | |
de comparer l'adresse MAC de destination de la trame et d'adapter l'interface logique source | |
dans le code pour que les traitements soient cohérents pour la suite. L'idée de base était là, mais | |
l'implémenter dans \file{tcpip.sys} n'est pas la solution. A terme, l'idée est d'intégrer | |
un « vrai » (plus complet) switch virtuel (comme par exemple Open vSwitch) qui lui, dispose | |
de beaucoup plus d'options (802.1Q, etc.). Le but n'étant pas de réécrire un vrai switch, | |
on va uniquement supporter le switching de base, il y aura donc un certain manque de support plus loin, nous | |
allons voir ça. | |
\newline | |
\begin{listing}[H] | |
\caption{Prototype de la fonction NDIS s'occupant de la réception d'un paquet} | |
\begin{ccode} | |
INT NTAPI ProtocolReceivePacket( | |
NDIS_HANDLE BindingContext, // LAN_ADAPTER | |
PNDIS_PACKET NdisPacket // NDIS_PACKET | |
); | |
\end{ccode} | |
\end{listing} | |
Pour déplacer l'implémentation hors de \file{tcpip.sys} (pour pouvoir changer le switch facilement sans | |
devoir modifier le coeur du code), l'idée que j'ai retenue est de faire un deuxième ProtocolDriver qui | |
s'occuperait de switcher puis de renvoyer le résultat à \file{tcpip.sys} comme si de rien n'était pour | |
lui. C'est ce procédé qui est utilisé sous Windows par les switchs virtuels les plus utilisés, comme | |
le « VirtualBox Bridged Networking Driver » qui permet d'utiliser des interfaces virtuelles dans VirtualBox. | |
\newline | |
\begin{figure}[!h] | |
\caption{Diagramme montrant la nécessité du switch} | |
\centering | |
\includegraphics[scale=0.3]{why-switching.eps} | |
\end{figure} | |
Etant donné que tous les ProtocolDrivers bindés à une interface reçoivent la même trame | |
\footnote{C'est comme ça que fonctionne le driver WinPcap qui permet de sniffer le réseau sous Windows | |
(utilisé par Wireshark, par exemple). En s'installant comme ProtocolDriver, le driver reçoit une copie | |
de tout le trafic indépendamment du fonctionnement de TCP/IP. Cela permet également au driver de pouvoir | |
injecter des paquets aux interfaces auxquelles le ProtocolDriver est bindé}, sous Windows, il suffit | |
de décocher le « Protocol TCP/IP » de l'interface physique et de laisser le « Protocol Switch » pour arriver | |
à un switching de plus bas niveau. Cependant, cette partie n'est pas implémentée dans ReactOS. C'est un vrai | |
casse tête pour pouvoir gérer facilement le binding d'une interface réseau (l'interface graphique | |
ne supporte même pas la possibilité de cocher ou décocher un ProtocolDriver) | |
\footnote{Voir screenshots section \ref{an:tcp} page \pageref{an:tcp}}. | |
\begin{figure}[!h] | |
\caption{Fonctionnement d'un ProtocolDriver NDIS} | |
\centering | |
\includegraphics[scale=0.2]{ndis-protocol-copy.eps} | |
\end{figure} | |
Pour pallier à ce problème, je suis parti sur une implémentation de switch qu'on peut activer et désactiver | |
à la volée (via \call{unshare enable switch} et \call{unshare disable switch}). | |
\newline | |
Pour connecter le switch avec la pile TCP/IP existante, il faut faire communiquer les deux drivers entre eux. | |
L'idée la plus simple et propre est de transférer le paquet entre l'un et l'autre via un | |
\call{IoCallDriver}, cependant ça veut dire qu'à \textbf{CHAQUE} trame reçue, un appel à l'Object Manager | |
du kernel va être fait, créer une interruption logiciel, ajouter une entrée dans une queue d'exécution, etc. | |
Bref, quelque chose de très lourd pour simplement forwarder un paquet entre deux drivers. | |
\begin{figure}[!h] | |
\caption{Diagramme d'utilisation de switch.sys} | |
\centering | |
\includegraphics[scale=0.3]{enable-switch.eps} | |
\end{figure} | |
\newpage | |
Une autre idée pour implémenter une communication entre les deux est de compiler et linker le switch | |
avec la bibliothèque IP du système. De cette façon, non seulement je peux utiliser les mêmes structures de données, | |
mais en plus je pourrais partager des variables globales entre \file{tcpip.sys} et \file{switch.sys} par | |
le biais de la bibliothèque... mais ce n'est malheureusement pas le cas. | |
En effet, la bibliothèque est linkée statiquement aux drivers, donc chaque driver à sa propre instance. | |
Une autre solution est donc à trouver. | |
\newline | |
L'un des avantages de se trouver en kernel space c'est qu'on est beaucoup moins restreint quant à | |
l'utilisation de la mémoire (il n'y a pas cette protection du heap comme en user-mode qui empêche | |
au code d'accéder à du contenu qui ne lui appartient pas). Du coup, avec une méthode peu commode, | |
j'ai créé une structure qui va stocker des pointeurs de tcpip.sys et les envoyer à switch.sys via une communication | |
drivers classique. | |
\begin{listing}[H] | |
\caption{struct SWITCH\_NOTIFY} | |
\begin{ccode} | |
typedef struct _SWITCH_NOTIFIY { | |
PCHAR TCPIPEnabled; | |
VOID (*LANTransmit)(PVOID, PNDIS_PACKET, UINT, PVOID, USHORT, [...]); | |
NDIS_STATUS NTAPI (*ProtocolReceive)(NDIS_HANDLE, NDIS_HANDLE, [...]); | |
INT NTAPI (*ProtocolReceivePacket)(NDIS_HANDLE, PNDIS_PACKET); | |
PLIST_ENTRY AdapterListHead; | |
PKSPIN_LOCK AdapterListLock; | |
NDIS_HANDLE GlobalPacketPool; | |
NDIS_HANDLE GlobalBufferPool; | |
} SWITCH_NOTIFY, *PSWITCH_NOTIFY; | |
\end{ccode} | |
\end{listing} | |
On voit directement à la longueur du code, qu'il s'agit de pointeurs de fonctions. Le reste, ce sont | |
des pointeurs classiques vers des variables ou même une copie de certaines variables | |
(comme les \call{NDIS\_HANDLE} qui ne sont pas nécessaires à déréférencer car fixes). | |
Pour ce qui est de l'utilisation: | |
\begin{itemize} | |
\item \textbf{TCPIPEnabled}: un pointeur vers un flag qui active (ou pas) \file{tcpip.sys}. | |
Ce flag est utilisé conjointement avec SwitchEnabled qui se trouve dans le code de switch.c | |
\item \textbf{ProtocolReceive}: pointeur vers la fonction \call{ProtocolReceive} de \file{tcpip.sys} | |
\item \textbf{ProtocolReceivePacket}: pointeur vers la fonction \call{ProtocolReceivePacket} de \file{tcpip.sys} | |
\item \textbf{AdapterListHead}: pointeur vers la liste chainée des \call{LAN\_ADAPTER} de \file{tcpip.sys} | |
\item \textbf{AdapterListLock}: pointeur vers le verrou (mutex) de la liste chainée | |
\item \textbf{GlobalPacketPool}: copie du handle \call{PacketPool} de \file{tcpip.sys} utilisé par NDIS | |
(pour émettre un paquet) | |
\item \textbf{GlobalBufferPool}: copie du handle \call{BufferPool} de \file{tcpip.sys} utilisé par NDIS | |
(également pour émettre un paquet) | |
\end{itemize} | |
Le driver \file{tcpip.sys} est adapté pour que dès qu'une modification d'un de ces champs est provoquée, le | |
driver recrée cette structure et la ré-envoie au switch pour le tenir informé des nouvelles modifications. | |
\newline | |
Une fois que le switch reçoit cette structure, il s'occupe de copier le contenu dans une variable globale. En plus | |
de ça, il est possible que la mise à jour ait été déclenchée par l'ajout d'une nouvelle interface. Dans ce cas, | |
par sécurité, si le switch est activé (si le flag \call{SwitchEnabled} est à \call{TRUE}), on va itérer sur | |
toutes les interfaces et "rerouter" las fonctions de réception et d'émission de l'interface vers le switch. | |
\begin{listing}[H] | |
\caption{Reroutage des fonctions de réception et de transmission de paquets de tcpip.sys vers le switch virtuel} | |
\begin{ccode} | |
if(SwitchEnabled == TRUE) { | |
DbgPrint("Switch: rerouting interfaces to switch\n"); | |
CurrentEntry = Linker.AdapterListHead->Flink; | |
while (CurrentEntry != Linker.AdapterListHead) { | |
Current = CONTAINING_RECORD(CurrentEntry, LAN_ADAPTER, ListEntry); | |
((PIP_INTERFACE) Current->Context)->Fallback = | |
((PIP_INTERFACE) Current->Context)->Transmit; | |
((PIP_INTERFACE) Current->Context)->Transmit = SwitchTransmit; | |
CurrentEntry = CurrentEntry->Flink; | |
} | |
} | |
\end{ccode} | |
\end{listing} | |
A partir de ce moment là, tout le trafic sortant va passer d'abord par le switch avant de passer par TCP/IP | |
et le trafic entrant passera par le switch avant de passer dans TCP/IP. Il ne reste plus qu'à faire le traitement | |
des paquets. | |
\newline | |
A noter: même si il n'y a qu'une seule instance du switch virtuel, d'un point de vue logique, nous disposons | |
d'un switch par « Virtual LAN ». En effet, dès que deux interfaces (virtuelles ou pas) sont connectées/attachées | |
l'une à l'autre, un point de switching entre les deux s'établit (le switch fait le rapprochement en comparant | |
le handle NDIS)\footnote{La bonne méthode serait de d'abord créer un Virtual LAN, puis d'y attacher des | |
interfaces et non pas de créer un Virtual LAN implicitement lors d'une connexion}. | |
\newline | |
Lors de la demande de transmission d'un paquet, on reçoit en paramètres le paquet IP et un pointeur | |
vers une adresse (MAC) de destination. | |
% | |
% User-mode linking (ipconfig -> iphlpapi -> tdi -> tcpip.sys) | |
% | |
\subsection{Lien entre la couche utilisateur et le driver TCP/IP} | |
Maintenant que les couches 2 et 3 sont isolées dans le kernel, il faut voir comment faire le lien avec la partie utilisateur. | |
Je suis parti d'ipconfig (la commande donne l'adresse IP et MAC des interfaces installées sur le système, donc je | |
suppose que les appels que ce programme fait, sont directement liés au code que j'ai modifié plus haut). | |
% | |
% ipconfig | |
% | |
\subsubsection{Utilitaires de gestion: ipconfig.exe, route.exe, arp.exe, ping.exe, ...} | |
Le code source de « ipconfig » se trouve dans \file{/base/applications/network/ipconfig}. Le code semble avoir | |
été réécrit from scratch mais utilise bien « iphlpapi » (comme sous Windows), pour communiquer avec la couche | |
réseau du système. | |
\newline | |
Dans le header du code, un commentaire dit directement que « renew » et « release » ne sont pas implémentés. | |
Nous sommes au sommet de la couche d'appel, et on se rend compte d'une chose: | |
il manque des fonctionnalités, et même de base, mais bon, le principal semble fonctionner. | |
\newline | |
Globalement, l'affichage des interfaces ainsi que leurs état, adresse, etc. se font via l'appel | |
de la fonction \call{GetAdaptersInfo} qui se trouve dans la \call{iphlpapi}. D'après MSDN, cette appel | |
retourne une liste chainée de structures \call{IP\_ADAPTER\_INFO}. | |
\newline | |
Le contenu de la structure de IP\_ADAPTER\_INFO comparé à ce qu'on vient de voir (la séparation layer 3 et 2) | |
n'est plus respecté, c'est une structure prévue pour répondre à l'utilisateur le plus de choses possible, du | |
coup on se retrouve avec un mix de tout: | |
\begin{listing}[H] | |
\caption{struct IP\_ADAPTER\_INFO (repris de la MSDN)} | |
\begin{ccode} | |
typedef struct _IP_ADAPTER_INFO { | |
struct _IP_ADAPTER_INFO *Next; | |
char AdapterName[MAX_ADAPTER_NAME_LENGTH + 4]; | |
char Description[MAX_ADAPTER_DESCRIPTION_LENGTH + 4]; | |
BYTE Address[MAX_ADAPTER_ADDRESS_LENGTH]; | |
DWORD Index; | |
UINT Type; | |
UINT DhcpEnabled; | |
PIP_ADDR_STRING CurrentIpAddress; | |
IP_ADDR_STRING IpAddressList; | |
IP_ADDR_STRING GatewayList; | |
IP_ADDR_STRING DhcpServer; | |
BOOL HaveWins; | |
IP_ADDR_STRING PrimaryWinsServer; | |
time_t LeaseExpires; | |
// [...] | |
} IP_ADAPTER_INFO, *PIP_ADAPTER_INFO; | |
\end{ccode} | |
\end{listing} | |
On voit bien qu'on dispose d'un tas de choses totalement hétérogène d'un point de vue isolation: | |
MAC, IP, DHCP, DNS, Gateway, etc. On a donc une concentration et une centralisation des appels | |
qui se font via \call{iphlpapi}. | |
% | |
% iphlpapi | |
% | |
\subsubsection{IP Helper API (iphlpapi)} | |
Cette API permet de questionner le système à propos de sa configuration réseau. Elle ne permet pas d'envoyer | |
ou recevoir des paquets IP, mais bien uniquement de récupérer (ou configurer) des informations. | |
\newline | |
Par exemple, la fonction \call{getInterfaceInfoSet} permet d'établir une liste des interfaces réseaux | |
disponibles. En réalité, il s'agit d'un helper pour l'utilisation de TDI. Pour établir la liste des interfaces, | |
iphlpapi va demander à TDI de retourner une liste d'entités puis cette liste va être parcourue et analysée. | |
C'est également dans cette partie du code que l'interface de loopback va être ignorée, c'est pourquoi | |
elle n'apparaît pas dans ipconfig (alors qu'elle existe bel et bien). | |
\newline | |
Quelle que soit la partie du code de iphlpapi, on remarque un appel récurrent qui est | |
\linebreak\call{tdiGetEntityIDSet}. | |
% | |
% TDI | |
% | |
\label{sub:tdi} | |
\subsubsection{TDI (Transport Dispatch Interface)} | |
TDI est un protocole utilisé pour communiquer avec la couche de transport de la stack réseau de Windows. | |
Les « Transport Providers » sont l'implémentation de protocoles réseau tel que TCP/IP, NetBIOS, et AppleTalk. | |
Durant la compilation et le linkage d'une application user-mode, un client TDI est intégré dans le code. | |
Ce client permet de faire une passerelle pour les API user-mode des ProtocolDrivers. Typiquement, les | |
commandes TDI sont: TDI\_SEND, TDI\_CONNECT, TDI\_RECEIVE. | |
\newline | |
On trouve TDI à trois endroits dans l'arborescence: | |
\begin{itemize} | |
\item \file{/drivers/network/tdi/tdi/} | |
\item \file{/drivers/network/tcpip/} (avec le reste de TCP/IP) | |
\item \file{/lib/tdilib/} | |
\newline | |
\end{itemize} | |
La partie qui se trouve dans \file{drivers/tdi} ne contient que du code non-implémenté, certainement pour garder | |
une compatibilité de symbols avec des applications qui l'utilisent (la partie NetBios n'est absolument | |
pas gérée par exemple). | |
\newline | |
La \call{tdilib} par contre est très petite mais implémentée. Il n'y a pas grand chose derrière. | |
Lors d'une interrogation via TDI, les étapes suivantes sont effectuées: | |
\begin{itemize} | |
\item Ouverture de l'objet \call{\textbackslash{}device\textbackslash{}Xxxx} | |
\footnote{Variable en fonction de la destination: Tcp, Udp, Raw, ...} | |
\item Appel de \call{IOCTL\_TCP\_QUERY\_INFORMATION\_EX} sur le driver avec les paramètres | |
qui ont été fournis à l'appel de TDI (le type d'entité demandé, etc.) | |
\item Retour de la réponse du driver directement à l'appelant (sans même lire la réponse ou la | |
formater) | |
\newline | |
\end{itemize} | |
L'objet \call{\textbackslash{}device\textbackslash{}tcp} est géré dans \call{tcpip} | |
et la liste des entités (TDI Entities) aussi. | |
Étant donné que toutes les informations sont transmises via ces entités, pour isoler la réponse côté | |
user-mode, il suffit de modifier la liste d'entités retournée. | |
\newline | |
Malheureusement, encore une fois, dans le code de \file{tcpip.sys}, la liste des \call{TDIEntityID} se retrouve | |
dans une variable globale au code, et cette variable est utilisée pas mal de fois dans le code. | |
De plus, la structure \call{TDIEntityID} n'est pas un type défini par ReactOS, mais bien par l'implémentation | |
de TDI, plus d'informations à propos de cette structure se retrouvent dans la documentation MSDN | |
\footnote{http://msdn.microsoft.com/en-us/library/bb432492(v=vs.85).aspx}. Par sécurité (et propreté, bien que | |
la propreté puisse être subjective vu ce qui arrive...) j'ai décidé de ne pas modifier cette structure, étant | |
donné qu'elle est explicitement décrite (ce n'est pas le cas de la structure \call{EPROCESS} | |
que j'ai modifiée, car elle n'est que partiellement décrite dans la documentation, certaines parties sont cachées). | |
\newline | |
Pour pouvoir disposer de plusieurs listes d'entités, j'ai tout simplement créé une liste qui contient des listes d'entités. | |
Il y a deux variables globales utilisées: \call{EntityList} et \call{EntityCount}. La première variable est | |
un vecteur d'entités, la deuxième est un entier qui contient le nombre d'éléments du vecteur. Pour ne pas modifier | |
le code existant, j'ai redéfini ces deux variables par un appel de fonction qui va retourner la liste | |
correspondante au namespace en cours. | |
\begin{listing}[H] | |
\caption{Adaptation de la liste d'entités TDI} | |
\begin{ccode} | |
/* Override original global names with functions */ | |
#define EntityList GetEntityList(IoGetCurrentProcessNs()) | |
#define EntityCount GetEntityCount(IoGetCurrentProcessNs()) | |
TDIEntityInfo *GetEntityList(ULONG NamespaceId); | |
ULONG GetEntityCount(ULONG NamespaceId); | |
ULONG SetEntityCount(ULONG Count); | |
NTSTATUS TdiNewNamespace(ULONG NamespaceId); | |
NTSTATUS TdiRemoveNamespace(ULONG NamespaceId); | |
\end{ccode} | |
\end{listing} | |
Cette méthode (bien que très agressive) fonctionne très bien et propose plusieurs avantages: | |
\begin{itemize} | |
\item Les appels déjà existants à \call{EntityList} sont toujours fonctionnels | |
\item On peut débugger l'appel de la liste vu qu'à chaque accès, une fonction est appelée | |
\item La liste peut être adaptée plus tard, en changeant juste la fonction appelée | |
ou en modifiant la fonction \call{GetXxxxxx} directement. | |
\newline | |
\end{itemize} | |
Pour stocker ces différentes listes, on va créer deux nouvelles structures qui vont contenir les informations | |
nécessaires pour garder différentes listes de TDIEntityInfo. | |
\begin{listing}[H] | |
\caption{Structure de données pour stocker plusieurs listes d'entités TDI} | |
\begin{ccode} | |
typedef struct _TDINamespaceInfo { | |
LIST_ENTRY ListEntry; /* Linked list Entry */ | |
ULONG NamespaceId; /* Namespace ID */ | |
TDIEntityInfo *tdiEntry; /* Real EntityList */ | |
} TDINamespaceInfo; | |
typedef struct _TDINamespaceCount { | |
LIST_ENTRY ListEntry; /* Linked list Entry */ | |
ULONG NamespaceId; /* Namespace ID */ | |
ULONG CountValue; /* Real EntityCount */ | |
} TDINamespaceCount; | |
\end{ccode} | |
\end{listing} | |
Grâce à ces deux structures, on peut identifier chaque \call{EntityList} et \call{EntityCount} par un ID | |
qui sera le numéro du namespace. | |
\newline | |
Pour ce qui est des deux fonctions (qui remplacent les variables) \call{GetEntityList}, il ne s'agit plus | |
que d'une itération sur la liste des \call{EntityList} et de retourner celui qui correspond au namespace demandé. | |
\begin{listing}[H] | |
\caption{Fonctionnement de GetEntityList} | |
\begin{ccode} | |
TDIEntityInfo *GetEntityList(ULONG NamespaceId) | |
{ | |
TDINamespaceInfo *EntryInfo; | |
PLIST_ENTRY Entry = __EntityListNS.Flink; | |
while (Entry != &__EntityListNS) { | |
EntryInfo = CONTAINING_RECORD(Entry, TDINamespaceInfo, ListEntry); | |
if (EntryInfo->NamespaceId == NamespaceId) | |
return EntryInfo->tdiEntry; | |
Entry = Entry->Flink; | |
} | |
return NULL; | |
} | |
\end{ccode} | |
\end{listing} | |
Pour ce qui est de \call{TdiNewNamespace} et \call{TdiRemoveNamespace}, rappelez-vous, ces fonctions sont | |
appelées lors de la création d'un namespace (depuis process.c). | |
\newline | |
Nous avons le point clé de l'isolation: les applications de configuration et de management | |
en user-mode (ipconfig, arp, route, ...) utilisent tous \call{iphlpapi} (qui utilise TDI). En réalité, tout | |
le user-mode utilise TDI pour communiquer avec la couche réseau du système (également Winsock), c'est | |
grâce à cette modification dans TDI qu'on peut isoler une stack TCP/IP sans aller plus loin que la couche 3. | |
Grâce à ça, l'implémentation actuelle de UDP, TCP, etc. n'a pas à être modifié. | |
\begin{figure}[!h] | |
\caption{Pile d'appels depuis ipconfig jusqu'à tcpip.sys} | |
\centering | |
\includegraphics[scale=0.2]{ipconfig-tdi.eps} | |
\end{figure} | |
\newpage | |
\section{Topologies de test} | |
\subsection{Overlapping d'adresses IP de destination} | |
Une première démonstration du fonctionnement de l'isolation est un exemple avec de l'overlapping d'IP | |
de destination. En effet dans cet exemple on va avoir deux processus (ex: deux \file{cmd.exe}) dans deux namespaces | |
différents qui seront tous les deux attachés à deux interfaces physiques différentes. Chacune des interfaces | |
physiques est connectée à un routeur derrière, qui ont tout les deux l'adresse IP \call{172.16.0.254/24}. Dans un | |
environnement classique, il ne peut y avoir qu'une seule route vers \call{172.16.0.254}, cet exemple | |
montre bien qu'on dispose de deux tables de routage et deux tables ARP différentes pour les deux processus. | |
\begin{figure}[!h] | |
\caption{Topologie de test avec overlapping d'adresses IP de destination} | |
\centering | |
\includegraphics[scale=0.3]{topology-overlap.eps} | |
\end{figure} | |
\subsection{Overlapping d'adresses IP en communication inter-namespaces} | |
Une autre démonstration est l'isolement inter-namespaces. Dans cet exemple on va faire 4 namespaces qu'on va | |
regrouper en 2 isolations logiques. On va inter-connecter 2 namespaces entre eux, et les deux autres entre eux. | |
Pour complexifier le tout, on va faire de l'overlapping dans les deux namespaces logiques. | |
Le diagramme est plus que nécessaire pour comprendre le principe: | |
\begin{figure}[!h] | |
\caption{Topologie de test avec overlapping d'IP inter-namespaces} | |
\centering | |
\includegraphics[scale=0.3]{internamespace.eps} | |
\end{figure} | |
\newpage | |
\section{Conclusion} | |
\subsection{Technique} | |
En partant d'un OS écrit « from scratch », en analysant le fonctionnement et en lisant le code source | |
du système, je suis parvenu à implémenter un Proof-Of-Concept relativement poussé d'une isolation | |
complète d'une stack TCP/IP: les interfaces réseaux, la table de routage et la table ARP sont isolées. | |
\newline | |
Du côté du user-mode, les réponses des applications \file{ipconfig.exe}, \file{route.exe}, | |
\file{arp.exe} sont bien isolées également. Un « ipconfig » ne donnera que la liste des interfaces | |
réseaux de son namespace (et leurs adresses IP et MAC). Un « route print » n'affiche que les | |
routes de son namespace. Pareil pour la commande « arp -a » qui donnera la table ARP de son namespace, sans | |
interférer avec les autres. | |
\newline | |
En plus de ça, l'isolation va à un tel point que je peux faire une topologie qui supporte de l'overlapping | |
d'adresses aussi bien sources que de destinations. En effet, dans deux namespaces différents, je peux | |
pinger la même ip (destination) et voir que le traitement du paquet ne se fait pas de la même façon (routage | |
précis sur base de l'application source). | |
\newline | |
J'ai écrit un simple (mais suffisant pour l'instant) switch virtuel qui s'occupe d'aiguiller les trames | |
que le système reçoit, de façon à avoir différents domaines de broadcast (sur une certaine limitation: il n'est | |
pas possible d'avoir plusieurs domaines de broadcast sur une interface physique, cela demande un support | |
de tagging de paquet, comme fait le 802.1Q). | |
\newline | |
Pour finir, j'ai établi un canal de communication entre les drivers (nouveaux et existants) et le user-mode. | |
J'ai pu traverser toute la couche d'un OS (user-mode vers le kernel-mode, en passant par un driver) et toute | |
la stack IP d'un système (en se limitant aux couches de liaison et transport). | |
\newline | |
J'ai pu intégrer un principe fonctionnel et utilisable dans une topologie de test, il reste cependant pas mal | |
de travail à faire pour rendre ce système parfaitement fonctionnel et utilisable à grande échelle (à commencer | |
par remplacer mon pauvre switch de base par un switch plus évolué). | |
\newpage | |
\label{next:utils} | |
\subsection{Recommandations pour la suite} | |
Un grand nombre de points sont à améliorer ou corriger, certains sont dûs à une limitation d'implémentation, | |
d'autres à des proof-of-concept qui sont restés dans le code et n'ont jamais été améliorés. | |
\begin{itemize} | |
\item Switch virtuel: le \file{switch.sys} qui a été écrit spécialement pour les namespaces n'est pas à garder. | |
En effet il ne permet qu'un switching très sommaire (sur base d'adresses MAC uniquement) et est | |
très loin d'être optimisé. De plus il a une forte dépendance à \file{tcpip.sys} et à la lib ip du système. | |
L'idée qui a été suggérée par mon maitre de stage est d'utiliser un portage de openvswitch pour | |
Windows. Cependant à l'heure actuelle, l'implémentation est partielle. Pour profiter pleinement | |
des namespaces, un switch supportant des VLAN semble indispensable pour pouvoir isoler correctement | |
la couche 2 et 3. | |
\item Le système utilise globalement des listes linéaires pour stocker toutes les tables existantes. Dans beaucoup | |
de cas liés aux namespaces, ces listes sont itérées à chaques réception de paquets. L'utilisation d'un meilleur | |
algorithme ou tout simplement de hashtable pour stocker ces tables semble incontournable pour avoir de meilleures | |
performances. | |
\item La communication entre le kernel et le driver réseau se fait par l'objet | |
\linebreak\call{\textbackslash{}Device\textbackslash{}NamespaceNetwork}, qui est initialisé dans le driver | |
{tcpip.sys}. Cette pratique est une relique d'implémentation. Mon but a été de faire communiquer | |
le kernel avec \file{tcpip.sys} rapidement et c'est la première solution qui m'est venue à l'esprit, | |
cependant maintenant que l'implémentation est plus aboutie, on se rend compte que cette méthode | |
n'est pas optimale. Seul \file{tcpip.sys} peut bénéficier d'une mise à jour du kernel (être appelé | |
depuis le kernel). | |
La solution serait de créer l'objet | |
\call{\textbackslash{}Device\textbackslash{}NamespaceNetwork} | |
dans \file{ntoskrnl.exe} (dans la partie \file{networknamespace.c}) et d'y ajouter une | |
liste de Listeners. Chaque partie du système (driver ou autre) ayant un rapport avec le réseau | |
pourrait alors s'enregistrer comme « client network namespace » au-près du kernel. De ce fait, | |
lors d'une demande de création d'un namespace (par exemple), les différents drivers qui ont | |
une part à jouer dans le namespace (\file{tcpip.sys} doit allouer de nouvelles listes | |
dans son code par exemple) seraient notifiés depuis le kernel. | |
\newpage | |
\begin{figure}[!h] | |
\caption{Enregistrement des drivers dans le kernel et dispatching d'actions} | |
\centering | |
\includegraphics[scale=0.2]{kernel-dispatch-nsop.eps} | |
\end{figure} | |
\item Remplacer le hard-code de détection du PID \hexa{0x04} par une fonction qui vérifierait de façon plus générique si | |
le processus en cours d'exécution est le processus \call{SYSTEM} ou pas. | |
Voir section \ref{sub:routerpid} page \pageref{sub:routerpid}. | |
\label{item:macrandom} | |
\item Faire un tirage aléatoire de l'adresse MAC de la Virtual Ethernet autrement qu'en tirage totalement | |
aléatoire sur les 48 bits. Par exemple, VirtualBox a son propre OUI et donc toutes leurs adresses | |
MAC ont le même préfixe. Il est possible qu'un préfixe existe pour ce genre d'utilisation | |
mais je n'en ai pas trouvé (certaines implémentations d'interfaces TAP font aussi un tirage aléatoire | |
sur les 48 bits, c'est pourquoi j'ai laissé ça ainsi). | |
\item Actuellement, toute la configuration et le fonctionnement des namespaces est volatile. | |
Windows utilise sa base de registre pour y sauver toutes sortes de configurations, etc. Au lieux | |
de faire un script batch au démarrage de la machine, une implémentation de la sauvegarde de | |
la configuration (et son chargement au boot) dans la base de registre pourrait être un point | |
intéressant comparé au fonctionnement général de Windows. | |
\item Implémenter complètement le support de l'isolation des processus (supporter le fait | |
d'avoir plusieurs PID les mêmes simultanément, ...) | |
\label{next:utils} | |
\item Le portage de la commande « ip » sous Linux, du package « iproute2 ». Ce package et la commande « ip » | |
supporte presque tout l'aspect de gestion réseau sous Linux. (ip address, ip link, ip route, | |
ip rule, ip neighbor, ip tunnel, etc.) | |
\item Faire une extension pour « netsh » qui permettrait de gérer les namespaces depuis la ligne | |
de commande. En effet, « netsh » est prévu pour supporter des extensions par l'ajout de | |
fichiers \file{.dll}, un module qui s'occuperait de l'attachement/détachement des interfaces | |
à un namespace depuis « netsh » semble un choix intéressant. | |
\item Porter le code et le fonctionnement de ReactOS vers Windows pour en faire une solution | |
comme « Parallels Containers for Windows », mais avec du code Open Source. Il ne sera plus question | |
de modifier le fonctionnement du kernel par contre, mais bien d'adapter le fonctionnement | |
en faisant une sur-couche au système existant. | |
\item Développer un plugin pour GNS3. Ce programme offre une GUI qui permet de faire une topologie | |
très simplement. A la base, la GUI sert à configurer des routeurs Cisco pour configurer | |
une topologie avec l'émulateur \call{dynamips}, mais leur interface graphique est adaptable. | |
On peut imaginer utiliser l'interface de GNS3 pour configurer les namespaces de la machine et | |
ainsi construire la topologie et la connexion des containers graphiquement. | |
\item Hyper-V utilise un switch virtuel qui a été développé par Microsoft. Ce switch est utilisé partout | |
dans l'hyperviseur de Microsoft et supporte déjà pas mal de fonctionnalités (VLAN, etc.). Il | |
serait intéressant de remplacer mon implémentation du switch virtuel par la leur et donc | |
offrir une couche de virtualisation (containers) par dessus leur switch. | |
\end{itemize} | |
\newpage | |
\section{Conclusion personnelle} | |
\subsection{A propos de mes études} | |
Étant en option réseau à la Haute École de la Province de Liège, ce sujet est directement lié a mes études. | |
Bien que la majorité des cours soient basés sur des langages de hauts niveaux, les cours de C et \textbf{surtout} | |
de réseau m'ont permis d'arriver plus ou moins facilement à bout de ce travail. | |
\newline | |
La programmation kernel n'est pas vraiment abordée dans mon cursus, j'ai donc dû trouver et apprendre par moi-même | |
une grande partie du fonctionnement de Windows. J'ai toujours été intéressé et passionné par Linux (le kernel | |
et le système GNU/Linux en général), j'ai donc pu me baser sur ce que je connaissais pour le transposer dans une | |
logique Windows. | |
\newline | |
Concrètement, mon développement dans le kernel s'achève avec | |
\textbf{84} fichiers modifiés, | |
\textbf{4130} lignes ajoutées et | |
\textbf{550} lignes supprimées | |
(il s'agit d'un \call{git diff} entre mon checkout du SVN ReactOS et mon dernier commit dans mon git local) | |
% | |
% | |
% TFE | |
\subsection{Sujet du TFE} | |
Ce sujet est particulièrement intéressant car il n'existe aucune alternative libre ou même native à ce | |
que j'ai conçu/écrit. Le fait de faire quelque chose de neuf et dans un domaine très recherché à l'heure | |
actuelle (la virtualisation) m'a vraiment plu et m'a appris beaucoup de choses, principalement à propos | |
du kernel Windows NT. | |
\newline | |
Ca me rassure réellement de voir qu'il reste encore des domaines et des sujets où les langages de hauts niveaux | |
n'ont pas vraiment leurs places. Il n'est pas vraiment concevable à l'heure actuelle de faire un hyperviseur | |
(faible en demande de ressources) en Java par exemple. | |
\newline | |
Je n'avais jamais eu l'occasion de tester réellement les namespaces sous Linux avant de commencer ce stage | |
et je suis très content d'avoir pu découvrir cette partie de la virtualisation qui est pour moi un concurrent | |
sérieux aux hyperviseurs de type 1 et 2 à l'heure actuelle où le partage de ressources et la rentabilité | |
du matériel deviennent un point critique. | |
\newline | |
A l'heure actuelle où le « cloud » devient un mot à la mode et que tout le monde s'empresse d'utiliser, | |
cette technologie demande justement énormément de virtualisation (mettre en ligne du contenu dans un | |
environnement sécurisé (et donc isolé)). Si il fallait faire une machine virtuelle par utilisateur | |
d'un système, l'overhead global serait énorme et la rentabilité des ressource ne serait pas optimale. | |
% | |
% Retour | |
% | |
\subsection{Retour sur le stage} | |
Je me suis vraiment bien amusé durant mon stage. L'ambiance générale de la boite et dans l'openspace où | |
je codais était vraiment sympa. De plus, je ne suis absolument pas quelqu'un « du matin », et Level IT | |
est le genre de société qui offre une certaine flexibilité d'horaire qui me permet, je trouve, | |
de bien travailler. | |
\newline | |
Le courant est (vite) très bien passé avec mon entourage. Je me suis intégré facilement à l'équipe | |
et j'ai vraiment bien aimé développer dans des conditions qui me permettent d'utiliser mon propre | |
laptop avec mon système d'exploitation et mon environnement de développement, sans être obligé et | |
confiné à utiliser des logiciels précis. | |
% | |
% Thanks | |
% | |
\newpage | |
\section{Remerciement} | |
Je souhaite tout d'abord remercier Pierre De Fooz pour m'avoir proposé ce stage. En effet, j'avais demandé | |
si il était possible de trouver un stage qui impliquait d'écrire du C (n'étant vraiment pas intéressé | |
par des langages de haut niveau comme C\# et encore moins Java) dans le domaine du réseau. | |
Il m'a trouvé ce sujet qui ne demande qu'à faire du C, dans un kernel et qui de plus est Open Source. | |
C'est là exactement tout ce que je recherchais. | |
\newline | |
Je tiens à remercier également Olivier Hault, patron de Level IT, qui a toujours été derrière moi, m'a soutenu | |
et donné des pistes à suivre pour arriver au but. | |
\newline | |
Le channel \#reactos du serveur IRC irc.freenode.net, sur lequel j'ai pu avoir de l'aide des développeurs du projet ainsi | |
que des contacts avec le coordinateur de projet actuel. | |
\newline | |
Denis Jasselette, Cyril Paulus et Christine Daniel pour la relecture de cet ouvrage. | |
\newline | |
Le channel \#inpres du serveur IRC irc.maxux.net pour ses résidents, toujours prêts à aider et à troller: | |
Raphael Javaux (RaphaelJ), Lionel Ancia (liBdot4), Benoît Dardenne (morte), Sacha Sokoloff (Darky), | |
Laurent Doms (Pichet), Ghilan Onkelinxou (ghilan), Zoé (z03), Mathieu Lobet (omlet), | |
Christophe De Carvalho (Zaibon), Kimberly Scott (woop), William Gathoye (wget), Anthony Binnici (Nemo), | |
Thomas Van Gysegem (T4g1), Loïc Coenen (jt), Corentin Pazdera (nado), Sarah Sabry (Paglops). | |
\newline | |
Tous ceux que je n'aurais pas cité et qui m'ont permis d'arriver au bout de ce projet ! | |
\newpage | |
\section{Informations complémentaires} | |
\begin{itemize} | |
\item Ce document a été rédigé en LaTeX, compilé en PDF avec XeLaTeX sous GNU/Linux (Gentoo). | |
\item Les diagrammes que j'ai réalisés ont été faits avec https://www.draw.io | |
\item La coloration syntaxique de ce document est faite via \textit{pygmentize} et \textit{minted}. | |
Pour bénéficier d'une coloration correcte des types, j'ai dû en ajouter un grand nombre | |
au fichier source car Microsoft a eu l'excellente idée (je ne sais pas pourquoi à vrai dire) de | |
redéfinir tous les types du C en majuscule et de ne respecter aucune convention vis-à-vis | |
des customs types (par exemple faire des typedef type\_t). | |
\item Source des diagrammes de virtualisation: https://fr.wikipedia.org/wiki/Virtualisation | |
\end{itemize} | |
\section{Annexes} | |
\subsection{Environnement de développement et compilation} | |
La première chose à faire pour pouvoir utiliser ReactOS depuis les sources, est de cloner le SVN puis compiler | |
le système complet. Un environnement de compilation (nommé RosBE) a été fait sur mesure pour compiler le système from | |
scratch. Cet environnement comprend des scripts pour configurer des variables d'environnement et une toolchain | |
complète (gcc, binutils, ...). | |
\newline | |
L'arborescence (principale) du code source de ReactOS se compose ainsi: | |
\begin{itemize} | |
\item \file{/base}: | |
contient la source des applications userspace (ipconfig, calc, autochk, ...) | |
\item \file{/boot}: | |
contient le code du bootloader et les fichiers \file{.inf} permettant de construire un registre | |
de base (pour le livecd ou le bootcd) | |
\item \file{/cmake}: | |
contient les fichiers de configuration pour la compilation du système complet | |
\item \file{/dll}: | |
contient le code des bibliothèques partagées de Windows (ntdll, directx, le control panel, ...) | |
ansi que des portages du monde libre (libpng, libjpeg, ...) | |
\item \file{/drivers}: | |
contient le code source des drivers qui ont été réécrits pour ReactOS (drivers IDE, USB, Serial, ...) | |
dont notamment le driver TCP/IP et NDIS. | |
\item \file{/include}: | |
contient les .h du ddk, nds, kernel et tous les headers globaux du système | |
\item \file{/lib}: | |
contient la source de la RTL (RunTime Library) de Windows et des libs écrites spécialement | |
pour ReactOS, dont la « libip » | |
\item \file{/media}: | |
contient tout ce qui ne se compile pas et qui est nécessaire au système (fichiers inf, fonts, | |
wallpapers, ...) | |
\item \file{/ntoskrnl}: | |
contient le code de \file{ntoskrnl.exe} qui est réellement le coeur du système, c'est le kernel | |
de Windows. Le contenu est structuré pour séparer la gestion de la mémoire, des processus, etc. | |
\newline | |
\end{itemize} | |
Le système se compile avec cmake et dispose d'une target \textit{bootcd} ou \textit{livecd}. | |
\begin{itemize} | |
\item \textit{\call{bootcd}}: | |
compile tout le système et crée un ISO bootable qui lance une installation | |
du système (Windows XP like). Installer le système nécessite un disque dur, partitionner, | |
un bootloader, etc. mais cette version permet d'utiliser le système en read-write | |
\item \textit{\call{livecd}}: | |
compile tout le système et crée un ISO bootable qui lance le système sans | |
l'installer. Cet ISO n'inclut pas l'installeur et n'est utilisable qu'en read-only. Cette | |
version ne nécessite pas de disque dur. Le livecd est très pratique pour tester des modifications | |
côté kernel et drivers car durant le boot, le matériel est probé puis installé. Ca | |
permet de partir d'un système fraîchement installé rapidement. | |
\end{itemize} | |
Tout mon travail s'est effectué sur la révision 62083 du SVN. Le code fait un peu plus de 3 millions de lignes | |
de C et un peu moins de 2 millions de lignes de header. | |
Ma machine de développement a été un laptop Acer 7730G, avec un Core 2 Duo à 2.2 Ghz et 4 Go de ram. | |
La compilation du live cd met une grosse demi-heure à compiler. | |
\subsection{Exécution et débogage} | |
Pour l'exécution du système, j'ai utilisé la virtualisation et l'émulation. Mon laptop | |
n'ayant pas de technologie de virtualisation vu l'âge du CPU, les solutions Xen, etc. n'étaient pas | |
possibles (vu que c'est du Windows qui est virtualisé). | |
Mon choix s'est limité à VirtualBox et qemu: | |
\begin{itemize} | |
\item VirtualBox: hyperviseur de type 2, il m'a surtout permis d'utiliser le système avec | |
plusieurs cartes réseaux virtuelles bindées à des Debian virtuels pour pouvoir | |
analyser le trafic entrant et sortant. | |
\item qemu: je n'ai pas réussi à virtualiser plusieurs interfaces réseaux bindées aux Debian | |
mais l'utilisation de qemu permet un déboggage du kernel avec l'utilitaire \call{gdb} | |
à distance. Breakpoint, backtrace, chargements de symboles, etc. tout ce qu'il faut | |
pour pouvoir avoir la main sur le kernel. | |
\newline | |
\end{itemize} | |
Dans le fichier de configuration \file{/cmake/config.cmake}, j'ai ajouté l'option \call{SEPARATE\_DBG} qui permet | |
de séparer les symboles de débogage des binaires, ce qui m'a permis de charger les symboles dans \call{gdb} et pouvoir | |
faire du backtrace, des breakpoints, etc. dans le kernel et les drivers. Cette option ne se trouvait pas dans | |
la config mais le flag \call{SEPARATE\_DBG} existe et est implémenté dans le code qui s'occupe de compiler | |
le projet. | |
\newpage | |
\subsection{lwIP (A Lightweight TCP/IP stack)} | |
Ce projet\footnote{https://savannah.nongnu.org/projects/lwip/} | |
est particulièrement intéressant car il s'agit d'une implémentation d'une stack TCP/IP simple | |
mais complète, très légère et vraiment indépendante du système derrière, le code pouvant même tourner | |
sur du matériel embarqué sans OS. | |
\newline | |
Le but de lwIP est d'avoir une implémentation la plus complète possible d'une stack TCP/IP tout en | |
gardant une très faible consommation de ressources. | |
\newline | |
Les fonctionnalités principales de lwIP sont: | |
\begin{itemize} | |
\item Protocoles: IP, ICMP, UDP, TCP, IGMP, ARP, PPPoS, PPPoE | |
\item Client DHCP, client DNS, AutoIP/APIPA (Zeroconf), agent SNMP | |
\item APIs: APIs spécialisés pour systèmes avancés | |
\item ... | |
\newline | |
\end{itemize} | |
Dans la mailing list de ReactOS\footnote{https://lists.gnu.org/archive/html/lwip-devel/2011-08/msg00010.html}, | |
on peut voir une discussion récente à propos de l'implémentation de | |
lwIP dans ReactOS comme étant stable et devenue la stack primaire du système, cependant durant tous mes | |
tests, je n'ai jamais vu cette partie de code être utilisée (leur stack IP par contre bien). | |
Seule la partie TCP du système utilise lwIP en réalité. Etant en layer 4, ce point n'a pas impacté | |
mon développement au final. | |
\newpage | |
\begin{figure}[!h] | |
\caption{Couche réseau et utilisation de lwIP} | |
\centering | |
\includegraphics[scale=0.3]{diagramme-lwip.eps} | |
\end{figure} | |
\newpage | |
\label{a:unshare} | |
\subsection{Syntaxe de la commande unshare.exe} | |
\begin{listing}[H] | |
\caption{Utilisation de unshare.exe} | |
\begin{ccode} | |
unshare // Lance un cmd.exe dans un nouveau namespace | |
unshare attach <id> // Ajoute une veth liée à l'interface <id> | |
unshare detach <id> // Détache une interface virtuelle (déchargement) | |
unshare ip <id> <ipv4> <mask> // Attribue une adresse IPv4 à une interface | |
unshare mac <id> <mac> // Change l'adresse MAC d'une interface | |
unshare enable switch // Active le switch layer 2 virtuel | |
unshare disable switch // Désactive le switch layer 2 virtuel | |
\end{ccode} | |
\end{listing} | |
% | |
% NDIS Miniport newdev | |
% | |
\subsection{Bloquage d'implémentation par le Plug'n'Play manager et NDIS Miniport} | |
Jusqu'à présent, tout le système a été basé pour attacher une interface virtuelle à une interface | |
physique\footnote{Concrètement, il faut une interface « physique » (pour ReactOS) par Virtual LAN | |
que l'ont veut créer.}. | |
Pour bien faire, il faudrait l'attacher à une interface de type TAP plutôt. | |
\subsubsection{Interfaces type TAP} | |
Les interfaces TAP sont des interfaces layer 2 dont la fonction n'est que de forwarder le trafic du | |
kernel-mode en user-mode. | |
Ces interfaces sont utilisées par OpenVPN ou encore le réseau TOR. En effet, la partie | |
intéressante du code se trouve en user-mode (le chiffrement, la négociation de connexions, la gestion | |
du trafic, le décodage des trames, etc.). L'interface TAP est très simple vu que le driver ne s'occupe que | |
de faire du passthrough entre le kernel et le user-mode. | |
\newline | |
L'avantage d'utiliser ce type d'interface est qu'on dispose d'une carte réseau virtuelle layer 2 | |
sans dépendance hardware. | |
Il existe un portage pour Windows de l'interface TAP et elle semble fonctionner dans ReactOS, mais | |
le système ne gère pas pleinement le Plug'n'Play à ce niveau là. En effet, pour pouvoir installer et | |
instancier une interface TAP dans ReactOS, un reboot du système est nécessaire... autant dire que | |
ce n'est absolument pas envisageable pour les namespaces qui sont totalement dynamiques et, qui plus | |
est dans mon implémentation, volatiles. | |
\newline | |
J'ai passé beaucoup (trop) de temps sur la tentative de faire fonctionner l'interface TAP pour pouvoir | |
l'utiliser comme pièce maîtresse du mécanisme d'interface virtuelle. | |
\newline | |
\subsubsection{Interfaces virtuelles NDIS} | |
Les interfaces virtuelles dans NDIS sont des pilotes Miniport qui ne font pas d'appels au hardware | |
(pas de handlers d'interruption, etc.). Tout s'enregistre de la même façon, excepté que l'appel | |
à l'instanciation du driver ne se fait pas quand le hardware est détecté (vu qu'il n'y en a pas) mais | |
bien manuellement ou automatiquement quand NDIS est prêt de son côté. | |
\newline | |
Lors de l'enregistrement d'une interface Miniport, la structure de configuration contient | |
un champ \call{InitializeHandler}. Le problème actuellement dans l'implémentation de NDIS est que | |
ce handler n'est appelé \textbf{que} lors d'un appel du Plug'n'Play manager « hardware ». Du coup | |
l'interface TAP (ou n'importe quelle interface sans correspondance hardware) n'est jamais | |
instanciée (à noter que dans le gestionnaire de périphériques, l'interface apparaît bien et est | |
notée comme fonctionnelle (mais encore une fois, cette partie n'est sans doute pas bien implémentée)). | |
\newpage | |
\subsection{Modifications collatérales} | |
Durant le développement général, je suis tombé plusieurs fois sur des cas qui ne sont pas directement liés | |
à l'implémentation des namespaces. Certains cas m'ont bloqué, d'autres sont totalement inutiles mais | |
intéressants d'un point de vue technique. | |
\subsubsection{Mode Promiscuous dans le driver pcnet} | |
\label{sub:extrapcnet} | |
En regardant le code du driver pcnet, le mode promiscuous est bien défini dans le header du driver | |
(\file{/drivers/network/dd/pcnet/pcnethw.h}) mais le define \call{CSR15\_PROM} n'est utilisé | |
nulle part dans le code. Du coup, j'ai forcé son utilisation durant | |
l'initialisation de l'interface, cela se passe dans la fonction \call{MiInitChip} dans | |
\file{/drivers/network/dd/pcnet/pcnet.c} ligne 742. | |
\begin{listing}[H] | |
\caption{/drivers/network/dd/pcnet/} | |
\begin{ccode} | |
// | |
// pcnethw.h | |
// | |
#define CSR15_DRX 0x1 /* disable receiver */ | |
#define CSR15_DTX 0x2 /* disable transmitter */ | |
#define CSR15_LOOP 0x4 /* loopback enable */ | |
// [...] | |
#define CSR15_DRCVBC 0x4000 /* disable receive broadcast */ | |
#define CSR15_PROM 0x8000 /* promiscuous mode */ | |
// | |
// pcnet.c MiInitChip(): le CSR15 étant initialisé à 0x00 | |
// | |
NdisRawWritePortUshort(Adapter->PortOffset + RAP, CSR15); | |
NdisRawWritePortUshort(Adapter->PortOffset + RDP, CSR15_PROM); | |
\end{ccode} | |
\end{listing} | |
\subsubsection{Changer la couleur du Blue Screen} | |
Quand on développe dans le kernel, on fini toujours par tomber au moins une (ou 10... ou 50...) fois sur un | |
blue screen parce qu'on a déréférencé un pointeur NULL (ou qu'on a essayé de charger une font TrueType dans le | |
kernel par exemple). A force, le bleu du BSOD ça lasse un peu. Puis, n'importe quel geek a toujours voulu | |
au moins une fois dans sa vie changer la couleur de fond d'un Blue Screen, c'est tellement fun. | |
\newline | |
On a l'avantage ici d'avoir le code source du kernel, alors ça doit être facile de changer ça. Suffit de chercher | |
un peu comment le Blue Screen est généré. Dans le debugger kernel intégré au système, une commande permet de tester | |
si l'équivalent du \call{panic()} sous Linux fonctionne. C'est \call{bugcheck}. Cette commande génère un Blue Screen | |
tout simplement. De là, facile de savoir où le code se trouve. | |
\newline | |
L'affichage du Blue Screen se trouve dans \file{/ntoskrnl/ke/bug.c}, j'ai dû parcourir le fichier et lire un | |
peu ce qui s'y trouve pour tomber sur un appel à \call{InbvSolidColorFill}. En changeant un peu les valeurs en | |
paramètres aléatoirement, j'ai fini par trouver que le dernier paramètre est la couleur de fond. J'ai changé le | |
\hexa{0x02} en \hexa{0x04} et je me suis retrouvé avec un Blue Screen... vert. | |
\begin{figure}[!h] | |
\caption{Green Screen of Death} | |
\centering | |
\includegraphics[scale=0.6]{GSOD.eps} | |
\end{figure} | |
\newpage | |
\label{an:tcp} | |
\subsection{Comparaison des interfaces graphiques de gestion des protocoles} | |
Comme dit dans le développement, la gestion (activation/désactivation) d'un ProtocolDriver | |
n'est pas finie et l'interface graphique (par exemple) de ReactOS manque cruellement de possibilités. | |
\begin{figure}[!h] | |
\caption{VirtualBox Bridged Networking Driver (NDIS ProtocolDriver)} | |
\centering | |
\includegraphics[scale=0.8]{vbox-bridged.eps} | |
\end{figure} | |
\begin{figure}[!h] | |
\caption{Interface graphique non finalisée dans ReactOS} | |
\centering | |
\includegraphics[scale=0.6]{reactos-ncpacpl.eps} | |
\end{figure} | |
\end{document} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment