un wiki, on peut y écrire, non?
oui , on va même dire que c'est un peu fait pour cela
Apriori, le docbook créé par MoinMoin passe bien les scripts (ceux du LDP, en tout cas pour l'instant)
Il passe bien les scripts mais il génére du code degueulasse, qu'il va me falloir deux heures pour déboguer
-rajouter les en-têtes -rajouter les tags docbook omis -faire une mise en page clarifiée
voici d'ailleurs le code généré pour que chacun (mais il y a pas vraiment pas grand monde, donc ca devrait être rapide) puisse se faire une idée du travail à effectuer
je vais sur le wiki je trouve la page envoyé par le traducteur je fais afficher en docbook et ensuite je récupère le fichier généré et attention les yeux,le voici le voila
(et j'envoie ci-joint un exemple de fichier "propre", que je dois tirer de ce torchon )
Il n'y a pas un problème, la ?
<?xml version='1.0' encoding='UTF-8'?><!DOCTYPE article PUBLIC
"-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/xml/4.4/docbookx.dtd%22%3E<article><articleinfo><title>Gazette Linux/Brouillons/lg149-C VPN Networking</title><revhistory><revision><revnumber>19</revnumber><date>2009-03-02 18:51:33</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>18</revnumber><date>2009-03-02 10:53:00</date><authorinitials>KamalSawalToure</authorinitials><revremark>Relecture faite. Traduction finie. Merci pour votre patience</revremark></revision><revision><revnumber>17</revnumber><date>2009-03-02 01:08:45</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>16</revnumber><date>2009-03-02 01:07:57</date><authorinitials>KamalSawalToure</authorinitials><revremark>j'ai fini. Il reste la relecture</revremark></revision><revision><revnumber>15</revnumber><date>2009-02-23 15:28:50</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>14</revnumber><date>2009-02-23 13:35:28</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>13</revnumber><date>2009-02-23 11:26:29</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>12</revnumber><date>2009-02-10 18:51:54</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>11</revnumber><date>2009-02-10 14:57:25</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>10</revnumber><date>2009-02-10 13:33:23</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>9</revnumber><date>2009-02-10 12:41:40</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>8</revnumber><date>2008-12-07 08:27:06</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>7</revnumber><date>2008-10-17 17:02:24</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>6</revnumber><date>2008-10-17 17:00:13</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>5</revnumber><date>2008-10-17 16:28:34</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>4</revnumber><date>2008-10-14 12:58:08</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>3</revnumber><date>2008-10-14 11:47:13</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>2</revnumber><date>2008-10-01 08:50:27</date><authorinitials>KamalSawalToure</authorinitials></revision><revision><revnumber>1</revnumber><date>2008-09-30 15:51:29</date><authorinitials>KamalSawalToure</authorinitials></revision></revhistory></articleinfo><section><title>Configuration d'un Réseau Privé Virtuel (VPN)</title><para>Créer son propre Réseau Virtuel Privé (VPN) est assez facile sur des plateformes qui sont livrées avec un pilote tun: cela permet de traiter le flux des paquets réseaux au niveau de l'espace utilisateur. Bien que cela soit considérablement plus facile que de faire de la programmation réseau dans le noyau, il reste cependant quelques détails à prendre en compte. Cet article vous guidera à travers mes trouvailles. </para><para><emphasis role='strong'>IFF_TUN Versus IFF_TAP</emphasis> </para><para>Le Pilote tun est un périphérique deux-en-un: </para><itemizedlist><listitem><para>Un Périphérique point à point (IFF_TUN). Le périphérique TUN permet le traitement de paquets IP. </para></listitem><listitem><para>Un périphérique Ethernet (IFF_TAP). Le périphérique TAP traite les trames Ethernet. </para></listitem></itemizedlist><para>Cet article parle du code à écrire pour le périphérique Ethernet.Si vous choisissez le périphérique IP, alors vous génèrerez 18 octets par paquet traité; le trafic est certes moins important (l'en-tête et la queue du paquet Ethernet), mais vous aurez à coder un peu plus pour mettre en place votre réseau. </para><para><emphasis role='strong'>Activation du pilote</emphasis> </para><para>Premièrement, on doit s'assurer que le pilote tun est actif.Sur mon système Debian, je dois tout simplement le charger: </para><para># /sbin/modprobe tun # /sbin/lsmod | grep tun tun 10208 0 </para><para># /bin/ls -l /dev/net/tun crw-rw-rw- 1 root root 10, 200 2008-02-10 11:30 /dev/net/tun </para><para><emphasis role='strong'>La Configuration</emphasis> </para><para>Pour des besoins de démonstration, on mettra en place un réseau virtuel de deux hôtes. Une fois qu'on a mis la main sur les paquets Ethernet, nous utiliserons l'encapsulation UDP pour les faire transiter d'une interface virtuelle sur l'hôte A vers une interface virtuelle sur l'hôte B et vice-versa. Le socket UDP sera utilisé en mode non-connecté; cela a l'avantage de nous permettre d'utiliser le même socket pour envoyer et recevoir des paquets depuis n'importe quel autre hôte de notre réseau virtuel. Toutefois, la nature non-connectée de notre socket UDP, complique la réception du chemin MTU (plus de détails dessus dans les prochaines lignes). </para><para>Chaque hôte de notre réseau virtuel exécutera une instance du programme de démonstration. Pour l'illustrer, le trafic d'une application (telnet dans le cas présent) sur l'hôte A vers l'application correspondante (inetd/telnetd) vers l'hôte B, utilisera le chemin suivant: </para><para><emphasis role='strong'>Le mécanisme de découverte</emphasis> </para><para>En pratique, nous avons besoin d'un mécanisme pour faire correspondre les adresses IP virtuelles aux adresses IP réelles. Il nous appartient de bidouiller une méthode de découverte pour résoudre ce problème de correspondance; cependant comme cela n'est pas en rapport direct avec notre sujet du jour, ni nécessaire pour les besoins de notre petite démonstration décrite ici, nous allons tricher et laisser le programme de tunnelling établir les correspondances par des lignes de commande: </para><itemizedlist><listitem override='none'><para>Host A# ./udptun Usage: ./udptun local-tun-ip remote-physical-ip Host A# ./udptun 172.16.0.1 192.168.2.103 Host B# ./udptun 172.16.0.111 192.168.2.113 </para></listitem></itemizedlist><para><emphasis role='strong'>Mise en place de l'interface</emphasis> </para><para>La première chose à faire est de créer l'interface Ethernet virtuelle (tap). Cela se fait avec un simple appel à la fonction open(): </para><screen><![CDATA[struct ifreq ifr_tun; int fd;
if ((fd = open("/dev/net/tun", O_RDWR)) < 0) { /*Erreur de traitement, retour.*/; } memset( &ifr_tun, 0, sizeof(ifr_tun) ); ifr_tun.ifr_flags = IFF_TAP | IFF_NO_PI; if ((ioctl(fd, TUNSETIFF, (void *)&ifr_tun)) < 0) { /*Erreur de traitement, retour.*/; } /*Configurer l'interface: Définir l'adresse IP, le MTU,
etc*/]]></screen><para>Ici, l'indicateur IFF_NO_PI requiert qu'on manipule de trames brutes. Si on ne fait pas cela, les trames se verront ajouter un en-tête de 4 octets. </para><para><emphasis role='strong'>Interface setup: the IP address</emphasis> </para><para>L'interface virtuelle doit être identifiée par une adresse IP. On la définira avec un appel de la fonction ioctl(): </para><screen><![CDATA[/* Définir l'IP de cette terminaison du tunnel */ int set_ip(struct ifreq *ifr_tun, unsigned long ip4) { struct sockaddr_in addr; int sock = -1;
sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { /*Erreur de traitement, retour*/ } memset(&addr, 0, sizeof(addr)); addr.sin_addr.s_addr = ip; /*network byte order*/ addr.sin_family = AF_INET; memcpy(&ifr_tun->ifr_addr, &addr, sizeof(struct sockaddr)); if (ioctl(sock, SIOCSIFADDR, ifr_tun) < 0) { /*Erreur de traitement, retour*/ } /*Sera Utilisé plus tard pour définir le MTU.*/ return sock; }]]></screen><para><emphasis role='strong'>Le PMTU (Path Maximum
Transmission Unit)</emphasis> </para><para>La seul élément que nous avons à configurer est le MTU (Maximum Transmit Unit) de l'interface. Pour notre interface pseudo-Ethernet, le MTU est la charge la plus importante que les trames Ethernet peuvent transporter. On définira le PMTU en fonction du MTU. </para><para>Pour faire simple, on dira que le PMTU est le paquet le plus large qui peut traverser le chemin allant d'un hôte à sa destination sans subir de fragmentation. </para><para>Le PMTU est un paramètre important à prendre en compte pour que tout se passe bien. Considérez ceci: en (ré)injectant vos trames dans le noyau, elles se verront attribuer des en-têtes (IP, UDP et Ethernet) supplémentaires. Si la taille de la trame que vous avez envoyée au noyau est trop proche du PMTU, la trame finale qui sera envoyée à partir de l'interface réelle pourrait être plus grande que le PMTU. Au pire des cas, une telle trame sera perdue quelque part en route. Au meilleur des cas, la trame sera fragmentée en deux et la première partie sera traitée à 100%, ce qui ne sera pas le cas pour la seconde partie. </para><para>Pour éviter cela, on doit découvrir la valeur du PMTU et s'assurer que la nouvelle trame Ethernet sera correctement dimensionnée pour le PMTU. Ensuite, on soustraira du PMTU la taille des nouvelles en-têtes et on donnera cette valeur au MTU de l'interface virtuelle. </para><para>La tâche est facile sous Linux, pour un socket TCP: on doit juste s'assurer que les mécanismes de découverte du PMTU par le noyau sont configurés, et c'est tout. </para><para>Par contre pour les sockets UDP, nous les utilisateurs, avons la responsabilité de nous assurer que les datagrammes UDP sont de taille correcte. Si le socket UDP est connecté à l'hôte correspondant, un simple appel appel de la fonction getsockopt()avec l'indicateur IP_MTU positionné, nous donnera le PMTU </para><para>En ce qui concerne les sockets non-connectés, on doit analyser le PMTU. Premièrement, le socket doit être configuré de façon à ce que les datagrammes ne soient pas fragmentés (positionner l'indicateur DF); ensuite, on doit configurer de façon à être averti des erreurs ICMP que cela pourrait générer. Si un hôte ne peut pas gérer la taille du datagramme sans le fragmenter, alors il devra nous en informer (enfin, on l'espère): </para><screen><![CDATA[int sock; int on;
sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { /*Erreur de traitement, retour*/; } on = IP_PMTUDISC_DO; if (setsockopt(sock, SOL_IP, IP_MTU_DISCOVER, &on, sizeof(on))) { /*Erreur de traitement, retour*/; } on = 1; if (setsockopt(sock, SOL_IP, IP_RECVERR, &on, sizeof(on))) { /*Erreur de traitement, retour*/; } /*Utiliser sock pour la découverte du
PMTU.*/]]></screen><para>Ensuite, on envoie des datagrammes analysés de diverses tailles: </para><screen><![CDATA[ int wrote = rsendto(sock, buf, len, 0, (struct sockaddr*)target, sizeof(struct sockaddr_in));]]></screen><para>Pour finir, on farfouille parmi les erreurs jusqu'à trouver le bon PMTU. Si on a une erreur de PMTU, on ajuste la taille du datagramme et on recommence à envoyer les datagrammes jusqu'à ce que la destination soit atteinte: </para><screen><![CDATA[ char sndbuf[VPN_MAX_MTU] = {0}; struct iovec iov; struct msghdr msg; struct cmsghdr *cmsg = NULL; struct sock_extended_err *err = NULL; struct sockaddr_in addr; int res; int mtu;
if (recv(sock, sndbuf, sizeof(sndbuf), MSG_DONTWAIT) > 0) { /* Réponse reçue. Fin de la découverte du PMTU. Retour.*/ } msg.msg_name = (unsigned char*)&addr; msg.msg_namelen = sizeof(addr); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_flags = 0; msg.msg_control = cbuf; msg.msg_controllen = sizeof(cbuf); res = recvmsg(sock, &msg, MSG_ERRQUEUE); if (res < 0) { if (errno != EAGAIN) perror("recvmsg"); /*Nothing for now, return.*/ } for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg,
cmsg)) { if (cmsg->cmsg_level == SOL_IP) { if (cmsg->cmsg_type == IP_RECVERR) { err = (struct sock_extended_err *) CMSG_DATA(cmsg); } } } if (err == NULL) { /*Découvrte du PMTU: pas d'info jusque là. Retour, mais continue à analyser.*/ }
mtu = 0; switch (err->ee_errno) { ... case EMSGSIZE: debug(" EMSGSIZE pmtu %d\n", err->ee_info); mtu = err->ee_info; break; ... } /*end switch*/ return mtu; /*Mais continue à analyser jusqu'à ce que l'hôte distant
soit atteint!*/]]></screen><para>Une dernière chose: le PMTU est tenu de changer dans le temps. On devra donc le tester de temps en temps et ensuite configurer le MTU de l'interface virtuelle en fonction des résultats du test. Si on veut éviter toute cette gymnastique, on peut configurer le MTU de façon sécurisée mais moins optimale : choisir la valeur la plus petit entre 576 et le MTU de l'interface physique (moins l'en-tête que nous avons mentionné, bien entendu.) </para><para><emphasis role='strong'>Configuration de l'interface: le MTU</emphasis> </para><para>Après avoir finalement obtenu cette valeur magique qu'est le PMTU, nous pouvons correctement configurer le MTU de notre interface: </para><screen><![CDATA[ struct ifreq *ifr_tun; ... ifr_tun->ifr_mtu = mtu; if (ioctl(sock, SIOCSIFMTU, ifr_tun) < 0) { /*Erreur de Traitement*/ }]]></screen><para><emphasis role='strong'>Encapsulation UDP</emphasis> </para><para>Nous avons maintenant une interface virtuelle bien configurée et qui fonctionne. Tout ce que nous avons à faire est de relayer les trames dans les deux directions. Premièrement, il faut ouvrir un socket UDP non-connecté (Je vous épargne les détails), et ensuite: </para><orderedlist numeration='arabic'><listitem><para>Lire les paquets du fichier descriptif du tap et les envoyer à l'adresse IP physique distante de notre hôte correspondant; ceci enverra les paquets dans une seule direction. </para></listitem></orderedlist><screen><![CDATA[ char buf[VPN_MAX_MTU] = {0}; struct sockaddr_in cliaddr = {0}; int recvlen = -1; socklen_t clilen = sizeof(cliaddr);
recvlen = read(_tun_fd, buf, sizeof(buf)); if (recvlen > 0) sendto(_udp_fd, buf, recvlen, 0, (struct
sockaddr*)&cliaddr, clilen); ]]></screen><itemizedlist><listitem override='none'><para><emphasis role='strong'>Avertissement</emphasis>: La lecture du fichier descriptif du tap va provoquer un blocage. Cela veut dire que la fonction read() ne va pas être interrompue même si on ferme le descripteur sous-jacent du fichier. On est donc obligé d'utiliser les fonctions poll()/select() sur ce descripteur de fichier avant d'executer la fonction read() dessus si on veut pouvoir terminer ce thread proprement. </para></listitem></itemizedlist><orderedlist><listitem><para>lire les datagrammes du socket UDP et les envoyer à travers le descripteur de fichier tap : les données circuleront maintenant dans la direction opposée. </para></listitem></orderedlist><screen><![CDATA[ recvlen = recvfrom(_udp_fd, buf, sizeof(buf), 0, (struct sockaddr*)&cliaddr, &clilen); if (recvlen > 0) write(_tun_fd, buf, recvlen); ]]></screen><para>Notez qu'en pratique, si on a plus de deux hôtes dans son réseau virtuel, on doit regarder le contenu des trames pour vérifier les adresses IP source et destination avant de décider où envoyer la trame. </para><para>On peut télécharger les sources des fichiers udptun.c, ttools.c, ttools.h et pathmtu.c avec le Makefile; tout ce dont on parlé ci-dessus est aussi téléchargeable sous la forme d'une archive tar. </para><para><emphasis role='strong'>P comme Privé</emphasis> </para><para>Comme on a le contrôle total du trafic de notre réseau virtuel, on peut le crypter dans l'espace utilisateur. Pour les besoins de cette démonstration, nous allons construire un VPN complet, que nous allons crypter avec IPSEC (remarque: IPSEC a aussi une fonctionnalité intégrée de tunnelling). </para><para>Sous Debian, il faut juste installer le paquetage ipsec-tools et utiliser ces fichiers pour des manipulations manuelles: </para><para>Pour l'hôte A: </para><itemizedlist><listitem override='none'><para>## Vider le SAD et SPD flush; spdflush; </para><para># A & B add 172.16.0.1 172.16.0.111 ah 15700 -A hmac-md5 "123456789.123456"; add 172.16.0.111 172.16.0.1 ah 24500 -A hmac-md5 "123456789.123456"; add 172.16.0.1 172.16.0.111 esp 15701 -E 3des-cbc "123456789.123456789.1234"; add 172.16.0.111 172.16.0.1 esp 24501 -E 3des-cbc "123456789.123456789.1234"; # A spdadd 172.16.0.1 172.16.0.111 any -P out ipsec </para><itemizedlist><listitem override='none'><para>esp/transport//require ah/transport//require; </para></listitem></itemizedlist><para>spdadd 172.16.0.111 172.16.0.1 any -P in ipsec </para><itemizedlist><listitem override='none'><para>esp/transport//require ah/transport//require; </para></listitem></itemizedlist></listitem></itemizedlist><para>Pour l'hôte B: </para><itemizedlist><listitem override='none'><para>## Vider le SAD et SPD flush; spdflush; </para><para># A & B add 172.16.0.1 172.16.0.111 ah 15700 -A hmac-md5 "123456789.123456"; add 172.16.0.111 172.16.0.1 ah 24500 -A hmac-md5 "123456789.123456"; add 172.16.0.1 172.16.0.111 esp 15701 -E 3des-cbc </para><itemizedlist><listitem override='none'><para>"123456789.123456789.1234"; </para></listitem></itemizedlist><para>add 172.16.0.111 172.16.0.1 esp 24501 -E 3des-cbc </para><itemizedlist><listitem override='none'><para>"123456789.123456789.1234"; </para></listitem></itemizedlist><para>#dump ah; #dump esp; # B spdadd 172.16.0.111 172.16.0.1 any -P out ipsec </para><itemizedlist><listitem override='none'><para>esp/transport//require ah/transport//require; </para></listitem></itemizedlist><para>spdadd 172.16.0.1 172.16.0.111 any -P in ipsec </para><itemizedlist><listitem override='none'><para>esp/transport//require ah/transport//require; </para></listitem></itemizedlist></listitem></itemizedlist><para>Remarquez comme le mécanisme de cryptage tout entier est lié aux adresses virtuelles et comme il vous isole des réseaux physiques sur lesquels vos hôtes se trouvent. Vous pouvez directement télécharger le fichier ipsec-tools.conf. </para><para><emphasis role='strong'>Le VPN en marche</emphasis> </para><para>C'est parti! Faisons un ping de 100 octets sur l'interface virtuelle de l'autre hôte: </para><itemizedlist><listitem override='none'><para>Host A$ ping -s 100 172.16.0.111 </para></listitem></itemizedlist><para>Regardons le trafic avec tcpdump sur l'interface virtuelle:: </para><itemizedlist><listitem override='none'><para>#tcpdump -i tap0 </para></listitem><listitem override='none'><para>.. </para><para>15:43:27.739218 IP 172.16.0.1 > 172.16.0.111: AH(spi=0x00003d54,seq=0x1d): </para><itemizedlist><listitem override='none'><para>ESP(spi=0x00003d55,seq=0x1d), length <emphasis role='strong'>128</emphasis> </para></listitem></itemizedlist><para>15:43:27.740673 IP 172.16.0.111 > 172.16.0.1: AH(spi=0x00005fb4,seq=0x1d): </para><itemizedlist><listitem override='none'><para>ESP(spi=0x00005fb5,seq=0x1d), length 128 </para></listitem></itemizedlist><para>15:43:28.738741 IP 172.16.0.1 > 172.16.0.111: AH(spi=0x00003d54,seq=0x1e): </para><itemizedlist><listitem override='none'><para>ESP(spi=0x00003d55,seq=0x1e), length 128 </para></listitem></itemizedlist><para>15:43:28.740170 IP 172.16.0.111 > 172.16.0.1: AH(spi=0x00005fb4,seq=0x1e): </para><itemizedlist><listitem override='none'><para>ESP(spi=0x00005fb5,seq=0x1e), length 128 </para></listitem></itemizedlist><para>15:43:39.494298 IP 172.16.0.1 > 172.16.0.111: AH(spi=0x00003d54,seq=0x1f): </para><itemizedlist><listitem override='none'><para>ESP(spi=0x00003d55,seq=0x1f), length 64 </para></listitem></itemizedlist><para>15:43:39.496818 IP 172.16.0.111 > 172.16.0.1: AH(spi=0x00005fb4,seq=0x1f): </para><itemizedlist><listitem override='none'><para>ESP(spi=0x00005fb5,seq=0x1f), length 40 </para></listitem></itemizedlist></listitem></itemizedlist><para>Sur l'interface physique: </para><itemizedlist><listitem override='none'><para># tcpdump -i eth2 </para></listitem><listitem override='none'><para>.. </para><para>15:45:46.878156 IP 192.168.40.128.11223 > 192.168.40.129.11223: UDP, </para><itemizedlist><listitem override='none'><para>length <emphasis role='strong'>186</emphasis> </para></listitem></itemizedlist><para>15:45:46.879021 IP 192.168.40.129.11223 > 192.168.40.128.11223: UDP, </para><itemizedlist><listitem override='none'><para>length 186 </para></listitem></itemizedlist><para>15:45:47.879479 IP 192.168.40.128.11223 > 192.168.40.129.11223: UDP, </para><itemizedlist><listitem override='none'><para>length 186 </para></listitem></itemizedlist><para>15:45:47.887054 IP 192.168.40.129.11223 > 192.168.40.128.11223: UDP, </para><itemizedlist><listitem override='none'><para>length 186 </para></listitem></itemizedlist><para>15:45:48.880268 IP 192.168.40.128.11223 > 192.168.40.129.11223: UDP, </para><itemizedlist><listitem override='none'><para>length 186 </para></listitem></itemizedlist><para>15:45:48.882738 IP 192.168.40.129.11223 > 192.168.40.128.11223: UDP, </para><itemizedlist><listitem override='none'><para>length 186 </para></listitem></itemizedlist></listitem></itemizedlist><para>Tous les chiffres en gras sont des charges utiles. Quand il sort de l'interface virtuelle, le datagramme crypté est de 186 octets: 14 octets pour l'en-tête Ethernet, 20 pour l'en-tête IP, un en-tête AH de 24 octets et un ESP pour les 128 octets restants. </para><para>Quand il sort de l'interface physique, le datagramme est de 232 octets: 14 octets pour l'en-tête Ethernet, 20 octets pour l'en-tête IP, 8 pour l'en-tête UDP, 186 octets de charge physique et 4 octets pour le bloc de fin de la trame Ethernet. Nous avons donc rajouté 46 octets par datagramme. </para><para><emphasis role='strong'>Ressources</emphasis> </para><itemizedlist><listitem><para><ulink url='http://www.mjmwired.net/kernel/Documentation/networking/tuntap.txt'>The TUN/TAP kernel documentation</ulink> </para></listitem><listitem><para><ulink url='http://www.faqs.org/rfcs/rfc1191.html'>The Path MTU Discovery RFC</ulink> </para></listitem><listitem><para><ulink url='http://linux.die.net/man/7/ip'>The Linux IPv4 man page</ulink> </para></listitem><listitem><para><ulink url='http://www.networksorcery.com/enp/Protocol.htm'>The RFC Sourcebook</ulink> </para></listitem></itemizedlist></section></arti
--