virli/tutorial/4/networkns.md

480 lines
15 KiB
Markdown
Raw Normal View History

2017-11-09 00:30:41 +00:00
Le *namespace* `network` {#net-ns}
2021-10-31 19:51:17 +00:00
------------------------
2016-10-19 03:24:05 +00:00
2022-11-11 09:14:16 +00:00
Voyons maintenant plus en détail les différents espaces de nom, leurs
caractéristiques et leurs usages ; en commençant par le *namespace* `network`.
2021-10-31 19:51:17 +00:00
### Introduction
2016-10-19 03:24:05 +00:00
L'espace de noms `network`, comme son nom l'indique permet de virtualiser tout
2022-02-24 19:43:43 +00:00
ce qui est en lien avec le réseau : les interfaces, les ports, les routes, les
2016-10-19 03:24:05 +00:00
règles de filtrage, etc.
2021-10-31 19:51:17 +00:00
En entrant dans un nouvel espace de noms `network`, on se retrouve dans un
2022-02-24 19:43:43 +00:00
environnement qui n'a plus qu'une interface de *loopback* :
2016-10-19 03:24:05 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```
2022-11-11 09:14:16 +00:00
42sh# unshare --net ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2016-10-19 03:24:05 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-19 03:24:05 +00:00
2022-11-11 09:14:16 +00:00
::::: {.warning}
2017-11-09 00:30:41 +00:00
Bien que portant le même nom que l'interface de *loopback* de notre
environnement principal, il s'agit bien de deux interfaces isolées l'une de
l'autre.
2022-11-11 09:14:16 +00:00
:::::
Afin d'amener du réseau à notre nouvel espace de nom, il va falloir lui
attribuer des interface. En fait, nous allons pouvoir déplacer nos interfaces
réseaux, dans le *namespace* vers lequel elle doit être accessible. Une
interface donnée ne peut se trouver que dans un seul *namespace* à la fois.
2017-11-09 00:30:41 +00:00
Qui dit nouvelle pile réseau, dit également que les ports qui sont assignés
2022-02-24 19:43:43 +00:00
dans l'espace principal, ne le sont plus dans le conteneur : il est donc
2017-11-09 00:30:41 +00:00
possible de lancer un serveur web sans qu'il n'entre en conflit avec celui d'un
2016-10-19 03:24:05 +00:00
autre espace de noms.
2021-10-31 19:51:17 +00:00
### Premiers pas avec `ip netns`
2016-10-19 03:24:05 +00:00
La suite d'outils `iproute2` propose une interface simplifiée pour utiliser le
2022-02-24 19:43:43 +00:00
*namespace* `network` : `ip netns`.
2016-10-19 03:24:05 +00:00
2022-02-24 19:43:43 +00:00
Nous pouvons tout d'abord créer un nouvel espace de noms :
2016-10-19 03:24:05 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
2022-11-11 09:14:16 +00:00
```
42sh# ip netns add virli
2016-10-19 03:24:05 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-19 03:24:05 +00:00
2022-11-11 09:14:16 +00:00
::::: {.code}
2016-10-19 03:24:05 +00:00
La technique utilisée ici pour avoir des *namespaces* nommés est la même que
2017-11-09 00:30:41 +00:00
celle que nous avons vue dans
2022-02-24 19:43:43 +00:00
[la première partie sur les *namespaces*](#ns-lifetime) : via un `mount --bind`
2017-11-09 00:30:41 +00:00
dans le dossier `/var/run/netns/`. Cela permet de faire persister le namespace
malgré le fait que plus aucun processus ne s'y exécute.
2016-10-19 03:24:05 +00:00
2022-11-11 09:14:16 +00:00
Nous pouvons créer artificiellement des entrées pour `ip netns` avec les
quelques commandes suivantes :
<div lang="en-US">
```
# On affiche la liste des netns déjà créés
42sh# ip netns
virli
# On crée un fichier pour servir de réceptacle au bind mount
42sh# touch /var/run/netns/foo
# On crée un nouveau namespace net, puis on bind tout de suite le
# fichier de namespace vers le fichier que l'on vient de créer
42sh# unshare --net \
mount --bind /proc/self/ns/net /var/run/netns/foo
# Testons si cela a bien marché
42sh# ip netns
foo virli
42sh# ip netns exec foo ip link
1: lo: <LOOPBACK> mut 65536 qdisc noop state DOWN mode DEFAULT group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
```
</div>
Les fichiers utilisés par `ip netns` ne sont donc rien de plus que des
*bind-mount*. Ce qui explique qu'ils soient persistant même sans processus
s'exécutant à l'intérieur.
:::::
2017-11-09 00:30:41 +00:00
Maintenant que notre *namespace* est créé, nous pouvons regarder s'il contient
2022-02-24 19:43:43 +00:00
des interfaces :
2016-10-19 03:24:05 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```
42sh# ip netns exec virli ip link
2022-02-24 19:43:43 +00:00
1: lo: <LOOPBACK> mut 65536 qdisc noop state DOWN mode DEFAULT group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2016-10-19 03:24:05 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-19 03:24:05 +00:00
2017-11-09 00:30:41 +00:00
Cette commande ne nous montre que l'interface de *loopback*, car nous n'avons
pour l'instant pas encore attaché la moindre interface.
2016-10-19 03:24:05 +00:00
D'ailleurs, cette interface est rapportée comme étant désactivée, activons-la
2022-02-24 19:43:43 +00:00
via la commande :
2016-10-19 03:24:05 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
2022-11-11 09:14:16 +00:00
```
42sh# ip netns exec virli ip link set dev lo up
2016-10-19 03:24:05 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-19 03:24:05 +00:00
À ce stade, nous pouvons déjà commencer à lancer un `ping` sur cette interface :
2016-10-19 03:24:05 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```
42sh# ip netns exec virli ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.038 ms
...
2016-10-19 03:24:05 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-19 03:24:05 +00:00
2021-10-31 19:51:17 +00:00
### *Virtual Ethernet*
2016-10-19 03:24:05 +00:00
Étant donné qu'une interface réseau ne peut être présente que dans un seul
espace de noms à la fois, il n'est pas bien pratique d'imposer d'avoir une
interface physique par conteneur, d'autant plus si l'on a plusieurs
centaines de conteneurs à gérer.
Une technique couramment employée consiste à créer une interface virtuelle de
2022-02-24 19:43:43 +00:00
type `veth` :
2016-10-19 03:24:05 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
2016-10-19 03:24:05 +00:00
```
42sh# ip link add veth0 type veth peer name veth1
2016-10-19 03:24:05 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-19 03:24:05 +00:00
2022-02-24 19:43:43 +00:00
Une interface `veth` se comporte comme un tube bidirectionnel : tout ce qui
2016-10-19 03:24:05 +00:00
entre d'un côté sort de l'autre et inversement. La commande précédente a donc
2022-02-24 19:43:43 +00:00
créé deux interfaces `veth0` et `veth1` : les paquets envoyés sur `veth0` sont
2017-11-09 00:30:41 +00:00
donc reçus par `veth1` et les paquets envoyés à `veth1` sont reçus par `veth0`.
2016-10-19 03:24:05 +00:00
2022-11-11 09:14:16 +00:00
::::: {.code}
Pour réaliser ces étapes dans un langage de programmation, nous allons passer
par Netlink. Il s'agit d'une interface de communication entre le noyau et
l'espace utilisateur. Il faut commencer par créer une `socket` pour avoir accès
à l'API Netlink, nous pourrons ensuite envoyer des requêtes, comme celle nous
permettant de créer notre interface `veth`.
Un message Netlink a une structure, alignée sur 4 octets, contenant un en-tête
et des données. Le format de l'en-tête est décrit dans le RFC
3549[^RFC3549] :
[^RFC3549]: <https://tools.ietf.org/html/rfc3549>
<div lang="en-US">
```c
struct nlmsghdr {
uint32_t nlmsg_len; // Length of message including header
uint16_t nlmsg_type; // Type of message content
uint16_t nlmsg_flags; // Additional flags
uint32_t nlmsg_seq; // Sequence number
uint32_t nlmsg_pid; // Sender port ID
};
```
</div>
Parmi les fonctionnalités de Netlink, nous allons utiliser le module NIS
2022-11-14 12:52:40 +00:00
(Network Interface Service)[^RFC3549NIS]. Il spécifie le format par lequel
2022-11-11 09:14:16 +00:00
doivent commencer les données liées à l'administration d'interfaces réseau.
[^RFC3549NIS]: Network Interface Service Module <https://tools.ietf.org/html/rfc3549#section-2.3.3.1>
<div lang="en-US">
```c
struct ifinfomsg {
uint8_t ifi_family; // AF_UNSPEC
// uint8_t Reserved
uint16_t ifi_type; // Device type
int32_t ifi_index; // Interface index
uint32_t ifi_flags; // Device flags
uint32_t ifi_change; // change mask
};
```
</div>
Le module NIS a besoin que les données soient transmises sous forme d'*attributs
Netlink*. Ces attributs fournissent un moyen de segmenter la charge utile en
sous-sections. Un attribut a une taille et un type, en plus de sa charge utile.
<div lang="en-US">
```c
struct rtattr {
uint16_t rta_len; // Length of option
uint16_t rta_type; // Type of option
// Data follows
};
```
</div>
Le *payload* du message Netlink sera donc transmis comme une liste d'attributs (où
chaque attribut peut à son tour avoir des attributs imbriqués).
<div lang="en-US">
```c
struct rtattr {
unsigned short rta_len;
unsigned short rta_type;
};
```
</div>
En se basant sur du code d'`ip link`[^IPLINK], on peut reconstituer la
communication suivante :
[^IPLINK]: <https://github.com/shemminger/iproute2/blob/main/ip/link_veth.c>
<div lang="en-US">
```c
#define MAX_PAYLOAD 1024
struct nlreq {
struct nlmsghdr hdr; // Netlink message header
struct ifinfomsg msg; // First data filled with NIS module info
char buf[MAX_PAYLOAD]; // Remaining payload
};
int
create_veth(char *ifname, char *peername)
{
// Create the netlink socket
int sock_fd = socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
if (sock_fd < 0)
return -1;
uint16_t flags =
NLM_F_REQUEST // We build a request message
| NLM_F_CREATE // Create a device if it doesn't exist
| NLM_F_EXCL // Do nothing if it already exists
| NLM_F_ACK; // Except an ack as reply or an error
// Initialise request message
struct nl_req req = {
.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
.hdr.nlmsg_flags = flags,
.hdr.nlmsg_type = RTM_NEWLINK,
.msg.ifi_family = PF_NETLINK,
};
struct nlmsghdr *hdr = &req.hdr;
int maxlen = sizeof(req);
// Attribute r0 with veth info
addattr_l(hdr, maxlen, IFLA_IFNAME, ifname, strlen(ifname) + 1);
// Attribute r1 nested within r1, containing iface info
struct rtattr *linfo =
addattr_nest(n, maxlen, IFLA_LINKINFO);
// Specify the device type is veth
addattr_l(hdr, maxlen, IFLA_INFO_KIND, "veth", 5);
// r2: another nested attribute
struct rtattr *linfodata =
addattr_nest(hdr, maxlen, IFLA_INFO_DATA);
// r3: nested attribute, contains the peer name
struct rtattr *peerinfo =
addattr_nest(n, maxlen, VETH_INFO_PEER);
n->nlmsg_len += sizeof(struct ifinfomsg);
addattr_l(n, maxlen, IFLA_IFNAME, peername, strlen(peername) + 1);
addattr_nest_end(hdr, peerinfo);
addattr_nest_end(hdr, linfodata);
addattr_nest_end(hdr, linfo);
// Send the message
sendmsg(sock_fd, &req, 0);
}
```
</div>
Maintenant que l'on a notre interface virtuelle, nous pouvons envoyer un second
message pour la déplacer dans un nouveau *namespace*[^IPSETNS] :
[^IPSETNS]: <https://github.com/shemminger/iproute2/blob/main/ip/iplink.c#L677>
<div lang="en-US">
```c
// Initialise request message
struct nl_req req = {
.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
.hdr.nlmsg_type = RTM_NEWLINK,
.msg.ifi_family = PF_NETLINK,
};
addattr_l(&req.hdr, sizeof(req), IFLA_NET_NS_FD, &netns, 4);
addattr_l(&req.hdr, sizeof(req), IFLA_IFNAME,
ifname, strlen(ifname) + 1);
sendmsg(sock_fd, &req, 0);
```
</div>
:::::
2016-10-19 03:24:05 +00:00
Dans cette configuration, ces deux interfaces ne sont pas très utiles, mais si
2021-10-31 19:51:17 +00:00
l'on place l'une des deux extrémités dans un autre *namespace* `network`, il
2017-11-09 00:30:41 +00:00
devient alors possible de réaliser un échange de paquets entre les deux.
2016-10-19 03:24:05 +00:00
2022-02-24 19:43:43 +00:00
Pour déplacer `veth1` dans notre *namespace* `virli` :
2016-10-19 03:24:05 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
2022-11-11 09:14:16 +00:00
```
42sh# ip link set veth1 netns virli
2016-10-19 03:24:05 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-19 03:24:05 +00:00
2022-02-24 19:43:43 +00:00
Il ne reste maintenant plus qu'à assigner une IP à chacune des interfaces :
2016-10-19 03:24:05 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
2022-11-11 09:14:16 +00:00
```
42sh# ip netns exec virli ip a add 10.10.10.42/24 dev veth1
42sh# ip a add 10.10.10.41/24 dev veth0
2016-10-19 03:24:05 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-19 03:24:05 +00:00
2022-02-24 19:43:43 +00:00
Dès lors[^linkdown], nous pouvons `ping`er chaque extrêmité :
2017-11-09 00:30:41 +00:00
[^linkdown]: Il peut être nécessaire d'activer chaque lien, via `ip link set
vethX up`.
2016-10-19 03:24:05 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```
42sh# ping 10.10.10.42
- et -
42sh# ip netns exec virli ping 10.10.10.41
2016-10-19 03:24:05 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-19 03:24:05 +00:00
Il ne reste donc pas grand chose à faire pour fournir Internet à notre
2022-02-24 19:43:43 +00:00
conteneur : via un peu de NAT ou grâce à un pont Ethernet.
2016-10-19 03:24:05 +00:00
2021-10-31 19:51:17 +00:00
### Les autres types d'interfaces
2016-10-19 03:24:05 +00:00
Le bridge ou le NAT obligera tous les paquets à passer à travers de nombreuses
2017-11-09 00:30:41 +00:00
couches du noyau. Utiliser les interfaces *veth* est plutôt simple et disponible
2016-10-19 03:24:05 +00:00
partout, mais c'est loin d'être la technique la plus rapide ou la moins
gourmande.
2022-11-11 09:14:16 +00:00
Voyons ensemble les autres possibilités à notre disposition.
2016-10-19 03:24:05 +00:00
2022-11-11 09:14:16 +00:00
#### VLAN \
2016-10-19 03:24:05 +00:00
2018-11-06 13:44:59 +00:00
Il est possible d'attribuer juste une interface de VLAN, si l'on a un switch
2019-11-03 17:54:22 +00:00
supportant la technologie [802.1q](https://fr.wikipedia.org/wiki/IEEE_802.1Q)
derrière notre machine.
2016-10-19 03:24:05 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
2016-10-19 03:24:05 +00:00
```
42sh# ip link add link eth0 name eth0.100 type vlan id 100
42sh# ip link set dev eth0.100 up
42sh# ip link set eth0.100 netns virli
2016-10-19 03:24:05 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-19 03:24:05 +00:00
2022-11-11 09:14:16 +00:00
On attribuera alors à chaque conteneur une interface de VLAN différente. Cela
peut donner lieu à une configuration de switch(s) assez complexe.
2016-10-19 03:24:05 +00:00
2022-11-11 09:14:16 +00:00
#### MACVLAN \
2016-10-19 03:24:05 +00:00
2017-11-09 00:30:41 +00:00
<!-- https://hicu.be/bridge-vs-macvlan -->
Lorsque l'on n'a pas assez de carte ethernet et que le switch ne supporte pas
2022-02-24 19:43:43 +00:00
les VLAN, le noyau met à disposition un routage basé sur les adresses MAC : le
2016-10-19 03:24:05 +00:00
MACVLAN. S'il est activé dans votre noyau, vous allez avoir le choix entre l'un
2022-02-24 19:43:43 +00:00
des quatre modes : *private*, VEPA, *bridge* ou *passthru*.
2017-11-09 00:30:41 +00:00
2021-10-31 19:51:17 +00:00
Quel que soit le mode choisi, les paquets en provenance d'autres machines et à
2022-11-11 09:14:16 +00:00
destination d'une MAC seront délivrés à l'interface possédant ladite MAC. Les
2017-11-09 00:30:41 +00:00
différences entre les modes se trouvent au niveau de la communication entre les
interfaces.
2016-10-19 03:24:05 +00:00
2022-11-11 09:14:16 +00:00
##### VEPA \
2016-10-19 03:24:05 +00:00
2017-11-09 00:30:41 +00:00
Dans ce mode, tous les paquets sortants sont directement envoyés sur
l'interface Ethernet de sortie, sans qu'aucun routage préalable n'ait été
effectué. Ainsi, si un paquet est à destination d'un des autres conteneurs de
la machine, c'est à l'équipement réseau derrière la machine de rerouter le
paquet vers la machine émettrice (par exemple un switch
[802.1Qbg](http://www.ieee802.org/1/pages/802.1bg.html)).
2016-10-19 03:24:05 +00:00
2022-02-24 19:43:43 +00:00
Pour construire une nouvelle interface de ce type :
2016-10-19 03:24:05 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
2016-10-19 03:24:05 +00:00
```
42sh# ip link add link eth0 mac0 type macvlan mode vepa
2017-11-09 00:30:41 +00:00
```
</div>
2022-11-11 09:14:16 +00:00
##### *Private* \
2017-11-09 00:30:41 +00:00
À la différence du mode *VEPA*, si un paquet émis par un conteneur à
destination d'un autre conteneur est réfléchi par un switch, le paquet ne sera
pas délivré.
2019-11-03 17:54:22 +00:00
Dans ce mode, on est donc assuré qu'aucun conteneur ne pourra parler à un autre
2017-11-09 00:30:41 +00:00
conteneur de la même machine.
<div lang="en-US">
```
42sh# ip link add link eth0 mac1 type macvlan mode private
2016-10-19 03:24:05 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-19 03:24:05 +00:00
2022-11-11 09:14:16 +00:00
##### *Bridge* \
2016-10-19 03:24:05 +00:00
2022-11-11 09:14:16 +00:00
En mode *Bridge*, les paquets sont routés selon leur adresse MAC : si jamais
une adresse MAC est connue, le paquet est délivré à l'interface MACVLAN
correspondante ; dans le cas contraire, le paquet est envoyé sur l'interface de
sortie.
2016-10-19 03:24:05 +00:00
2022-02-24 19:43:43 +00:00
Pour construire une nouvelle interface de ce type :
2016-10-19 03:24:05 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
2016-10-19 03:24:05 +00:00
```
42sh# ip link add link eth0 mac2 type macvlan mode bridge
2016-10-19 03:24:05 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-19 03:24:05 +00:00
2022-11-11 09:14:16 +00:00
##### *passthru* \
Enfin, le mode *passthru* permet de récupérer le contrôle sur tout ce qu'il
reste du périphérique initial (notamment pour lui changer sa MAC propre, ou
pour activer le mode de promiscuité).
L'intérêt est surtout de pouvoir donner cette interface à un conteneur ou une
machine virtuelle, sans lui donner un accès complet à l'interface physique (et
notamment aux autres MACVLAN).
On construit l'interface en mode *passthru* de cette façon :
<div lang="en-US">
```
42sh# ip link add link eth0 mac3 type macvlan mode passthru
```
</div>
Une seule interface MACVLAN peut être en mode *passthru* par interface
physique.
2021-10-31 19:51:17 +00:00
### Aller plus loin {-}
2016-10-19 03:24:05 +00:00
Pour approfondir les différentes techniques de routage, je vous
2022-02-24 19:43:43 +00:00
recommande cet article :
[Linux Containers and Networking](https://blog.flameeyes.eu/2010/09/linux-containers-and-networking)[^netnsmore1].
2021-09-11 12:41:43 +00:00
2022-02-24 19:43:43 +00:00
Appliqué à Docker, vous apprécierez cet article : [Understanding Docker
2021-09-11 12:41:43 +00:00
Networking Drivers and their use
cases](https://www.docker.com/blog/understanding-docker-networking-drivers-use-cases/)[^netnsmore2].
[^netnsmore1]: <https://blog.flameeyes.eu/2010/09/linux-containers-and-networking>
[^netnsmore2]: <https://www.docker.com/blog/understanding-docker-networking-drivers-use-cases/>