Shell distant pour connexion instable

Pour administrer mes serveurs en déplacement rien de plus facile avec SSH et les réseaux mobiles. J’utilisais ainsi SSH via l’application ConnectBot sur Android. Si cette solution permet de dépanner elle présente quelques défauts.

Limites du SSH

Tout d’abord elle est très sensible à la qualité de la connexion. En effet le terminal est rapidement bloqué si le retour (echo) du serveur tarde à venir. Cela peut même provoquer un plantage de ConnectBot.

Ensuite quand la commande est longue comme par exemple un download ou une commande de compilation ou bien une commande de transcodage vidéo ou même une installation de paquet, une interruption de la connexion lui est fatal. On peut y remédier par l’utilisation d’un nohup mais ce dernier fait perdre la main sur la commande. De plus la commande qui part en nohup n’est plus accrochée à la connexion ssh quand le shell se termine.

La perte de connexion réseau est un réel problème pour les longues sessions de shell distant. C’est encore plus vrai même pour les courtes sessions quane la connectivité est mauvaise ou par intermittence comme dans le métro parisien !

Mosh le shell déconnecté

J’ai alors découvert Mosh. C’est un shell qui au lieu d’être connecté à un serveur ssh en TCP, envoie et reçoit ses paquets en UDP. La phase de connexion initiale se fait en SSH classique pour démarrer ensuite une session en UDP . Cette session en UDP survit aux interruptions réseaux !

Pour l’utilisateur client, mosh se presente comme un shell ssh. Quand il y a une perte de connexion, mosh indique par un message que la connexion réseaux est perdue, le terminal est alors bloqué puis quand le réseau revient, le terminal retrouve sont fonctionnement normal. Les commandes lancées sur le serveur continuent pendant la perte de connexion.

Installation

Mosh est un programme client/serveur. Il faut donc l’installer sur le serveur que l’on veut pouvoir atteindre vie un réseau intermittant. En root sur ma Debian

# apt-get install mosh

Termux le client Android le plus simple

Sur le client, il faut également installer mosh. Avec Android le plus simple est d’installer Termux. Cet emulateur de terminal fournit le paquet mosh.

pkg install mosh

L’utilisation de mosh est comme ssh

mosh user@server.net

On pourra avantageusement passer unr authentification par clé via une commande du type

mosh –ssh=”ssh -i /data/data/com.termux/files/usr/etc/ssh/ssh_host_ed25519_key” user@server.net

UserLand pour un client Linux sur Android

Si on veut rester dans un environnement familier en l’occurence Debian pour moi, le logiciel UserLand pour Android permet d’avoir une distribution Linux complète. A partir de là on installe mosh de manière classique. Pour Debian

sudo apt-get install mosh

UserLand permettra également de partager l’installation de mosh avec une session UserLand graphique. Mais le grand intérêt pour moi est de bénéficier de l’émulateur de terminal de UserLand que je trouve plus abouti que celui de Termux.

Paramétrage avec UserLand

Néanmoins les sessions Debian de UserLand sont un peu brutes. Il faut les personnaliser un peu. En effet mosh nécessite une locales UTF-8.

sudo apt-get install locale

Choisir alors une locales UTF-8, pour moi fr_FR.UTF-8

sudo dpkg-reconfigure locales

Mettre à la fin de .bashrc

LANG=”fr_FR.UTF-8″
export LANG

Sourcer le fichier

source .bashrc

Mosh est désormais utilisable.

Tubes nommés et redirections en parallèle

La ligne de commande est bien faite. Cette phrase est souvent entendue dans la bouche des informaticiens. J’en ai encore vérifié la véracité ses jours derniers.

Ma problèmatique était de constituer des statistiques sur des accès HTTP en consultant un fichier de log. La volumétrie des log étant de l’ordre de plusieurs gigaoctets et je ne disposais que de ma ligne de commande en bash.

Je me suis alors dirigé vers une serie de grep avec les bons motifs de recherche couplé à un bon vieux wc:

grep motif fichier.log | wc -l

Pour compliquer il y avait plusieurs motifs differents à compter et il fallait produire un fichier par jour avec le comptage de chaques motifs.

ACCES_COUNT=`grep -e motif_date -e motif_acces fichier.log | wc -l`

Autant de ligne de ce genre que de motif. L’inconvenient est que l’on parcourt la log autant de fois qu’il y a de motif d’acces pour une date donnée. Dans mon cas j’avais 15 motifs soit 1h30 pour obtenir les statistiques pour un jour donné.

Or, une fois que l’on est sur la bonne date dans le fichier de log c’est dommage de le reparcourir depuis le debut pour le motif d’accès suivant. D’où l’idée d’externaliser le parcours du fichier à la recherche de la bonne date puis de dispatcher la ligne aux grep qui compte les motifs d’accès.

tee

On a donc envie de faire un premier grep dont on va diriger la sortie sur d’autres grep en arborscence. La première pièce du puzzle est la commande tee. Voici l’idée :

grep -e motif_date fichier.log | tee -a entree_proc_grep1 entree_proc_grep2 … > /dev/null

Il manque la seconde piece qui est celle qui permet de definir un tube entre le premier grep et les autres qui doivent s’exécuter en parallèle. C’est là que les tube nommés interviennent.

named pipe

La commande mkfifo crée un tube nommé que l’on donne en entrée au grep de motif d’accès. Ces grep seront alors bloqués en attente de lire des donnés dans leurs tubes respectifs.

mkfifo entree_proc_grep1

grep -e motif_acces < entree_proc_grep1 | wc -l > fichier_temp1` &

PID_LIST+=” $!”

Autant de lignes de ce genre qu’il y a de motif d’accès à compter. Remarquons qu’à ce stade on à seulement créé des processus pour faire les grep de comptage et qu’ils sont tous bloqués en attentes sur leur tube. On alimente ensuite les tubes. Voici l’ordre

  1. créer les tube
  2. lancer en arrière plan les grep prenant les tubes en entrée
  3. lancer le grep sur la date pipé avec le tee vers les tubes

Subtilites

On ne peut plus récupérer la valeur des wc car c’est un sous shell qui l’exécute. On passe par un fichier temporaire.

On stocke la liste des pid des grep de comptage car le shell courant qui est leur parent doit attendre qu’ils soient tous finis avant d’aller lire les resultats dans les fichiers temporaires.

wait $PID_LIST

cat fichier_temp1

cat fichier_temp2

….

Dans mon cas particulier je suis passé de 1h30 de traitement pour avoir les statistiques d’un jour donné à seulement 3 minutes !

Finitions

Pour être propre il faut terminer en supprimant les fichiers temporaires (de comptage et les tubes).

Pour pouvoir lancer plusieurs instance du script sur des dates differentes en parallèle, ii faut ajouter le pid du script dans les noms des fichiers temporaires ( tubes nommés et comptage).

Dans mon cas particulier il me fallait les statistiques de plusieurs jours: chaque jour ayant sa propre stat. On peut lancer le script manuellement pour chaque jour ou bien intérer sur les jours, ce qui donne a peu près ceci:

for jour in 1 2 3 4

do

mkfifo entree_proc_grep1

grep -e motif_acces < entree_proc_grep1 | wc -l > fichier_temp1` &

PID_LIST+=” $!”

…..

grep -e motif_date fichier.log | tee -a entre_proc_grep1 entree_proc_grep2 … > /dev/null

wait $PID_LIST

cat fichier_temp1 >> fichier_stat_$jour

echo “” >> fichier_stat_$jour

…..

rm entree_proc_grep1

….

done

On remarque encore que pour chaque jour en parcourt l’ensemble de la log d’accès. Or dès le premier passage on a deja passé sur toutes les dates possibles. Une seule passe doit suffire ! L’idée est que le grep sur le motif de la date ne prenne plus le fichier de log en entrée mais un tube nommé…

for jour in 1 2 3 4

do

mkfifo entree_proc_grep1_$jour

grep -e motif_acces < entree_proc_grep1_$jour | wc -l > fichier_temp1_$jour` &

PID_LIST+=” $!”

…..

mkfifo entree_proc_$jour

PROC_JOUR_LIST+=” entree_proc_$jour”

grep -e motif_date entree_proc_$jour| tee -a entre_proc_grep1_$jour entree_proc_grep2_$jour … > /dev/null &

PID_LIST+=” $!”

done

cat fichier_log | tee -a $PROC_JOUR_LIST > /dev/null

wait $PID_LIST

for jour in 1 2 3 4

do

cat fichier_temp1_$jour >> fichier_stat_$jour

echo “” >> fichier_stat_$jour

…..

rm entree_proc_grep1_$jour

….

rm entree_proc_$jour

done

C’est un peu plus compliqué car pour distinguer tous ces tubes nommés on doit ajouter le numero de jour à leur nom.

Eviter ainsi de reparcourir la log depuis le début pour chaque jour permet d’aller plus vite. Dans mon cas particulier pour les statistiques de 3 jours je suis passé de 2mn50 à 0mn45s et pour les statistiques de 14 journées je suis passé de 22mn à 5mn !

Moralité de l’histoire, l’algorithmie alliée à la ligne de commande et la puissance du shell ça fait des étincelles.

Du bonne usage de la commande LS

Sous Linux, le ls est sans doute une des commandes les plus utilisées. En effet une fois arrivé dans un répertoire, qui ne lance pas un ls pour visualiser son contenu ? Cette commande est un peu le coup d’oeil qu’on lance autour pour juger son environnement.

Usage du ls

La pluspart du temps l’utilisateur fait

ls

ou alors pour avoir plus d’information sur les fichiers

ls -l

Cela correspond à un usage normal de la commande ls qui est d’obtenir la liste du contenu d’un répertoire.

Détournement du ls

Malheureusement cela se gâte quand on est dans un répertoire avec de très nombreux fichiers et que l’on veut juste des information sur certains d’entre eux. On utilise alors un motif de complétion en * pour effectuer ce genre de commande:

ls -l toto*

On obtient ainsi la liste des fichiers (i.e.les informations sur) commençant par toto. L’utilisateur a alors l’impression que ls fait une recherche des fichiers commençant par toto. C’est faux. Pour s’en convaincre il suffit d’activer le débug du shell par

set -x

Cela permet de voir ce que le shell exécute, puis lancez la commande:

ls *

On constate que l’expansion de shell remplace l’étoile par tous les fichiers présents dans le répertoire avant d’evaluer la commande ls suivie des paramètres ainsi constitués. Donc a en fait lancé la commande :

ls fichier1 fichier2 fichier3

C’est l’expansion de shell qui fait la soit disant recherche et ls ne fait que donner des informations sur les fichiers trouvés. Comme on ne passe pas de commutateur tel que -l, on n’obtient ni plus ni mpoins confirmation que les fichiers existent bien (travail effectué par l’expansion de shell).

Mauvais usage du ls

De la se servir de ls pour tester l’existense de fichier, il n’y a qu’un pas qu’il ne faut pas franchir. En effet on vu que c’est l’expansion de shell qui fait le boulot. Cela conduit à un effet désagréable quand l’expansion de shell ramène beaucoup trop de fichiers si bien que le shell ne peut exécuter la commande ls avec tant d’argument. Sur un linux récent faire n ls * dans un répertoire avec 100000 fichiers dont le nom fait 20 caractères chacun, conduit à:

ls *
bash: /bin/ls: Liste d'arguments trop longue

Laissons à ls ce qui appartient à ls: donner des informations sur des fichiers (taille, droits etc.). Pour la recherche de fichier il y a find

find -name "toto*"

En mettant entre guillemet le motif du nom de fichier, on empêche l’expansion de shell. On garde une ligne de commande courte. find interprête le meta-caractère étoile pour faire la recherche.

Existence de fichier selon un motif

Si on veut juste vérifier l’existence de fichier ayant un certain motif, on peut faire sur un Linux récent:

find -name "toto*" -print -quit | wc -l

Cela permet de chercher en s’arrêtant sur le premier trouvé puis de compter le nombre de fichier trouvé: soit 0 soit 1.

Si findutils n’est pas au moins en version 4.2.3 sur votre système alors l’option -quit n’est pas disponible. Son absence aura pour effet de vous priver d’un arrêt de la commande au premier fichier trouvé…. et donc obligera de faire une recherche complète.

Si l’on veut récupérer l’information d’existence où non de fichier via un code retour, on pourra avantageusement utiliser grep combiné à un ls comme ceci

if (ls | grep "2014*" 1>/dev/null 2>&1) then echo "trouve"; else echo "pas trouvé"; fi