La redondance avec OpenVPN a l'air tellement simple quand on lit la doc' mais ça ne l'est pas, comme toujours. Il y a un point problématique et des directives de config qui peuvent entrer en conflit et faire tout foirer.
Contexte : j'utilise un VPN fourni par FDN (
https://www.fdn.fr/-VPN-.html). Depuis quelques jours, ils ont monté un deuxième serveur OpenVPN dans l'optique de répartir la charge et surtout, de pouvoir couper un serveur sans impact le temps d'une maintenance. Les RRset A/AAAA vpn.fdn.fr contiennent les IPs des deux serveurs. vpn(1|2).fdn.fr sont des RR qui pointent directement sur un des serveurs OpenVPN. Je n'ai pas la main sur les serveurs OpenVPN : je suis un simple client OpenVPN.
Point problématique : la doc' d'OpenVPN nous dit : « OpenVPN also supports the remote directive referring to a DNS name which has multiple A records in the zone configuration for the domain. In this case, the OpenVPN client will randomly choose one of the A records every time the domain is resolved. »
La deuxième phrase semble au moins partiellement inexacte. J'ai testé avec la version 2.2.1 d'OpenVPN packagée dans Debian stable et avec la version 2.3.2 packagée dans les backports mais rien à faire, dans ce type de setup OpenVPN utilise toujours la première IP du RRset (première s'entend dans l'ordre dans lequel le serveur qui fait autorité sur la zone fdn.fr. et qui a répondu à la requête de mon récursif-cache local a classé les RR de ce set et que le récursif-cache utilisé a mémorisé le set en l'état).
Voici comment je mets ce dysfonctionnement en évidence :
1) je lance OpenVPN, il se connecte à vpn2
2) dig vpn.fdn.fr retourne vpn2 puis vpn1. Donc OpenVPN a bien pris la première IP du set. Mais c'est peut-être le résultat du tirage imprévisible.
3) Sur le routeur de sortie du réseau, je drop tout ce qui vient du client OpenVPN à destination de vpn2.
4) Je regarde OpenVPN réessayer 5 fois de suite de se reconnecter uniquement à vpn2.
5) Je flush le cache de mon Unbound local, je résous avec dig/flush le cache jusqu'à obtenir vpn1 en premier dans le RRset.
6) Au prochain essai, OpenVPN se connecte à vpn1 ...
7) Sur le routeur de sortie du réseau, je drop tout ce qui vient du client OpenVPN à destination de vpn1 et je débloque vpn2.
8) OpenVPN persiste à vouloir se connecter aveuglément à vpn1 ...
J'ai testé « remote-random » à tout hasard, même si la doc' indique que c'est uniquement pour choisir de manière imprévisible où commencer dans une liste de plusieurs « remote » indiqués dans la conf': sans succès, of course.
Donc, pour moi, le balancing DNS-based ne fonctionne pas dans l'état actuel. Il dépend du TTL du RRset et de la réponse d'un des serveurs DNS qui font autorité (pour l'ordre dans le RRset). Donc en cas de maintenance sur un serveur, tous ceux qui ont un VPN monté avec ce serveur continueront à s'y connecter pendant *au moins* le TTL restant.
Je n'ai aucune piste pour comprendre ce dysfonctionnement ... Peut-être qu'une simulation avec iptables n'est pas suffisante et qu'OpenVPN change de serveur uniquement pour des erreurs plus graves (pas de route, destination/port unreachable, ...).
ÉDIT du 02/06/2014 à 12h05 : il y a un ticket clôturé à ce sujet sur le bugtracker OpenVPN :
https://community.openvpn.net/openvpn/query?status=closed&summary=~Do+not+randomize+resolving+of+IP+addresses+in+getaddr%28%29&col=id&col=summary&col=status&col=owner&col=type&col=priority&col=milestone&order=priority .
Vu le type, « FRP - Feature Removal Process », cette fonctionnalité a été retirée. La description indique : « Based on a discussion on the mailing list and in the IRC meeting Feb 18, it was decided to remove get_random() from the getaddr() function as that can conflict with round-robin/randomization done by DNS servers.
http://thread.gmane.org/gmane.network.openvpn.devel/3104/ »
La discussion pointée n'aide pas beaucoup. En revanche, on peut retrouver ceci : <
http://article.gmane.org/gmane.network.openvpn.devel/3080> -> « Agreed that this randomization is not probably required in most scenarios and as such should be removed or deprecated. For more thorough discussion see the full chatlog. ».
Le thread de la ML auquel ils font référence : <
http://thread.gmane.org/gmane.network.openvpn.devel/3048/>. Je n'y lis rien d'intéressant.
Le backlog IRC auquel ils font référence : <
http://secure-computing.net/logs/%23%23openvpn-discussion.log> (inaccessible à l'heure actuelle -> solution = cache ->
https://webcache.googleusercontent.com/search?q=cache:qtFCxHFX15UJ:secure-computing.net/logs/%2523%2523openvpn-discussion.log+&cd=1&hl=fr&ct=clnk&gl=fr. Chercher « get_random() », la première occurrence est la bonne (discussion du 18 février 2012). Ce backlog est beaucoup plus intéressant.
Donc la motivation de la suppression de cette fonctionnalité c'est "presque tous les serveurs DNS font le taff, pourquoi le refaire au risque de casser ce que le serveur DNS a déjà fait sachant qu'il n'y a que le cas /etc/hosts où cette fonctionnalité est utile".
Évidemment moi j'ai testé avec le seul récursif-cache qui ne fait pas du round robin sur les RRset par défaut (directive « rrset-roundrobin » à positionner à yes depuis la version 1.4.17) ... FIN DE L'ÉDIT
On tente alors d'utiliser le deuxième mode de redondance proposé par la doc' OpenVPN : plusieurs directives « remote » dans le fichier de configuration. On y ajoute les lignes suivantes :
remote vpn2.fdn.fr 1194
remote vpn1.fdn.fr 1194
OpenVPN boucle à l'infini sur cette liste jusqu'à trouver un serveur fonctionnel dans l'ordre donné: vpn2 -> vpn1 -> vpn2 -> vpn1 ... Comportement identique lorsqu'il détecte qu'un serveur ne répond plus (ping-restart). On peut utiliser la directive « remote-random » pour indiquer de commencer le parcours de cette liste de manière imprévisible, sans suivre l'ordre dans le fichier de conf. Ici il n'y a que deux combinaisons : soit vpn2 -> vpn1 -> vpn2 -> vpn1 ... soit vpn1 -> vpn2 -> vpn1 -> vpn2 ...
On notera que cette méthode est moins flexible que celle de la redondance basée sur le DNS : si FDN veut ajouter ou retirer un serveur VPN, il faut adapter la conf' client. Bon, pour le retrait, il y a toujours moyen de faire pointer le nom sur l'IP d'un des serveurs restants mais bon ...
On reload. OpenVPN se connecte à vpn2. Sur la gw du réseau local, on drop tout ce qui vient de la machine qui exécute le client OpenVPN et qui est à destination de vpn2. Après le délai convenu avec ping-restart, le client OpenVPN, conformément à ce qu'indique la doc, tente de se reconnecter à vpn2, échoue puis tente vpn1 ... et échoue ! Voici un extrait de log :
openvpn[16231]: [_.fdn.fr] Peer Connection Initiated with [AF_INET]80.67.169.57:1194
[...]
openvpn[16231]: [_.fdn.fr] Inactivity timeout (--ping-restart), restarting
openvpn[16231]: Re-using SSL/TLS context
openvpn[16231]: UDPv4 link remote: [AF_INET]80.67.169.45:1194 <-- tiens, on ne cause plus avec 80.67.169.57 (vpn2) avec qui on a monté la session ?
openvpn[16231]: [UNDEF] Inactivity timeout (--ping-restart), restarting <-- tiens, il n'y a pas l'habituel « TLS: Initial packet from »
Le log OpenVPN est trompeur car il laisse croire à un refus de .45 (vpn1) dû à la réutilisation du contexte TLS négocié avec le premier serveur (.57/vpn2) alors qu'il n'en est rien comme nous allons le voir !
Ce problème se produit aussi avec un balancing DNS-based que nous avons vu plus haut. Mais ça n'arrive qu'à deux conditions :
1) expiration du TTL du RRset
2) le nouvel ordre du RRset est différent de l'ancien ordre
OpenVPN part alors dans une boucle infinie de tentative de connexion -> détection d'inactivité -> tentative de connexion -> détection d'inactivité. Le seul moyen de casser la boucle est de restart OpenVPN à la main.
On remarque qu'utiliser les directives « remap-usr1 SIGHUP » et « remote-random » fait que ça fonctionne (ping-restart -> sigusr1 -> sighup) ... Donc il y a quelque chose qui cloche avec le restart soft (sigusr1) comparé au restart hard (sighup). C'est ce qui m'a mis la puce à l'oreille ... Qu'est-ce qui n'est pas fait lors d'un restart soft ? persist-tun/key bien sûr puisqu'on le lui demande dans la conf !
Le problème vient du cumul des directives « redirect-gateway def1 » et « persist-tun » dans la conf' client. En effet, « redirect-gateway def1 » crée une route /32 vers le serveur VPN actuel via la default gw déjà en place ainsi que deux routes /1 afin de rediriger tout le trafic v4 dans le VPN. « persist-tun » indique à OpenVPN de ne pas détruire la tun lors d'un ping-restart donc le noyau ne flush pas les routes associées à cette interface comme il le ferait sinon. Donc quand OpenVPN essaie le serveur suivant dans la liste des « remote » ... les paquets sont envoyés sur la tun (à cause des 2 * /1 et l'absence de route plus spécifique) et sont donc perdus. D'où l'échec de la tentative de connexion.
Solution pour que le balancing entre plusieurs serveurs indiqués par plusieurs « remote » fonctionne dans ce cas de figure : soit on n'utilise pas la directive « persist-tun », soit on configure OpenVPN pour ajouter autant de routes qu'il y a de serveurs définis (« route 80.67.169.57 255.255.255.255 192.168.1.254 » et « route 80.67.169.45 255.255.255.255 192.168.1.254 » dans le cas de FDN). Testé et approuvé dans les deux cas.
ÉDIT du 02/06/2014 à 12h05 : ticket ouvert par un adminsys de FDN dans le bugtracker OpenVPN :
https://community.openvpn.net/openvpn/ticket/412 . Affaire à suivre. FIN DE L'ÉDIT