Jekyll2024-03-13T17:39:41+00:00https://blog.planete-kraus.eu/feed.xmlLa vie trépidante d’un chat au foyerUn peu de code, beaucoup de sieste.L’opportunité2024-01-15T00:00:00+00:002024-01-15T00:00:00+00:00https://blog.planete-kraus.eu/2024/01/15/l-opportunite<div id="outline-container-org9a902be" class="outline-2">
<h2 id="org9a902be">L’opportunité</h2>
<div class="outline-text-2" id="text-org9a902be">
<p>
Ouvrez grand vos oreilles ! Oyez, oyez, bon peuple !<br />
Vous devez distinguer le son mélodieux<br />
Du riche influenceur qui n’en croit pas ses yeux :<br />
Le prix a décuplé, on promet le centuple.<br />
</p>
<p>
« Pourquoi ? Comment ça marche ? — Voyez-vous, c’est très simple. »<br />
S’ensuit alors un charabia bien ennuyeux.<br />
Vous n’avez rien compris. On pourrait faire mieux,<br />
Mais en l’état, qui peut sonder ce vide ample ?<br />
</p>
<p>
La documentation technique du projet<br />
Est un fil sur Twitter, du moins un premier jet,<br />
Le reste viendra vite si le jeton décolle.<br />
</p>
<p>
Faites donc vos recherches, mais si vous cherchez trop,<br />
Vous laisserez filer l’opportunité folle :<br />
Vous risquez d’échapper aux griffes de l’escroc.<br />
</p>
</div>
</div>L’opportunité Ouvrez grand vos oreilles ! Oyez, oyez, bon peuple ! Vous devez distinguer le son mélodieux Du riche influenceur qui n’en croit pas ses yeux : Le prix a décuplé, on promet le centuple.Mon trousseau2023-10-07T00:00:00+00:002023-10-07T00:00:00+00:00https://blog.planete-kraus.eu/2023/10/07/mon-trousseau<p>
Si vous voulez me contacter, vous pouvez passer par mon
intendant. Veuillez utiliser la clé GPG suivante :
</p>
<blockquote>
<p>
AB8C 881E 332F 9258 8FA7 0427 3BB0 BC12 32D8 B82C
</p>
</blockquote>
<p>
Vous pouvez télécharger la clé <a href="../../../code/images/vivien.gpg">ici</a>.
</p>
<p>
Si vous voulez me donner accès par SSH, utilisez la clé <a href="../../../code/images/id_vivien.pub">SSH</a>:
</p>
<blockquote>
<p>
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDW+me/1YxfYQ84IImD8tyihHbEWPqBhwQ0NkkNRZzuJkY/8Z3Y7lTD2qXRF8DFOKsgYQwZij1iemf8Kdla4Kb8loqJW0bLI+pQBRgkF8cMCMr6QaU443Fcou3Tutnu3hv6w4nV3aPWi7/eZDqWoqsVT35P5kylApG9AGwLa0F2mhDeFyKKbswrrHZftjpSqa2ei25SiMZubaCSw2eDJ0Uc+KjSRwwtz5804kk4ojAvIw+SaiI/wQGLsZIt1doUlshAtGrfH7Dt/Qq6J3gKd8Gtpg2s3ReKHhux6//pO3aIIEYTe7ac77Hu5oYAsdqPJkjB1r5Gv8AKE7+/RaDfEh1+U2IbaVW9z9xgBGgBcS9lLJ+e7qNmYwxii18cPp9bkfcFBH9m5brzMwnRyuo+/ck9zOeIOEQJfrCDypWEWq8jaPXLXDC53XN2SPiYFIEKIPNQLrjTO97pQUAXUU8Osu7zkUEfZyKbEqCGdEHA1SB6HLtVU8Te41Nu8ZqpJdMdGdE= openpgp:0x27EB6AD0
</p>
</blockquote>Je vous indique la clé GPG et la clé SSH à utiliser pour identifier mon humain.Premier point de croix2023-07-08T00:00:00+00:002023-07-08T00:00:00+00:00https://blog.planete-kraus.eu/2023/07/08/premier-point-de-croix<p>
L’informatique, c’est bien, mais j’ai toujours été passionné par les
pelotes et les fils de couleurs. C’est très amusant ! Mais on peut
aussi s’en servir pour faire des réalisations au point de croix. Ici,
sur cette toile Aïda, j’ai fait un petit collègue qui prédate des
fleurs. Les points sont faits avec 3 brins, et les broderies
supplémentaires avec 2 brins (1 pour les contours en noir).
</p>
<div id="org09caa5e" class="figure">
<p><img src="../../../code/images/chaton.png" alt="chaton.png" />
</p>
</div>
<p>
Je ne vous montre pas l’envers !
</p>Non content d’être un chat informaticien, je fais aussi de la broderie au point de croix !On peut maintenant hacher des données avec GnuTLS + Guile !2022-12-29T00:00:00+00:002022-12-29T00:00:00+00:00https://blog.planete-kraus.eu/2022/12/29/on-peut-maintenant-hacher-des-donn%C3%A9es-avec-gnutls-et-guile<p>
Chers amis des chats et du lisp, bonjour. Je me présente : Twinky. Je
reprends ce blog afin de continuer le travail de mon prédécesseur :
vous transmettre la passion des chats et du développement
informatique.
</p>
<div id="orgc347f0b" class="figure">
<p><img src="../../../code/images/twinky.jpg" alt="twinky.jpg" />
</p>
</div>
<p>
Aujourd’hui, je vous informe que l’humain a agrémenté le code de
gnutls/guile <a href="https://gitlab.com/gnutls/guile/-/merge_requests/5">avec les fonctions de hachage</a><sup><a id="fnr.1" class="footref" href="#fn.1" role="doc-backlink">1</a></sup>. Ou devrais-je dire, les fonctions de
chachage ? À vous de juger. Le programme <a href="#org228ec50">1</a> montre ce qu’on
peut faire avec ! Sympa, non ?
</p>
<div class="org-src-container">
<label class="org-src-name"><span class="listing-number">Listing 1: </span>Exemple d’utilisation de la fonction de hachage</label><pre class="src src-scheme" id="org228ec50">(use-modules (gnutls) (ice-9 match) (rnrs bytevectors)
(srfi srfi-26))
(<span class="org-keyword">define</span> <span class="org-function-name">chiffre->hex</span>
(cute string-ref <span class="org-string">"0123456789abcdef"</span> <>))
(<span class="org-keyword">define</span> (<span class="org-function-name">chiffres->hex</span> . chiffres)
(list->string (<span class="org-keyword">map</span> chiffre->hex chiffres)))
(<span class="org-keyword">define</span> (<span class="org-function-name">octet->hex</span> octet)
(call-with-values
(cute euclidean/ octet 16)
chiffres->hex))
(<span class="org-keyword">define</span> <span class="org-function-name">octets->hex</span>
(cute map octet->hex <>))
(<span class="org-keyword">define</span> (<span class="org-function-name">->hex</span> octets)
(string-join (octets->hex (bytevector->u8-list octets)) <span class="org-string">""</span>))
(<span class="org-keyword">let*</span> ((message <span class="org-string">"Bonjour les amis des chats !"</span>)
(condensat (hash-direct digest/sha256 (string->utf8 message))))
(format #t <span class="org-string">"Quand je hache « ~a », j’obtiens :</span>
<span class="org-string">#+begin_example</span>
<span class="org-string"> ~a</span>
<span class="org-string">#+end_example"</span>
message (->hex condensat)))
</pre>
</div>
<p>
Quand je hache « Bonjour les amis des chats ! », j’obtiens :
</p>
<pre class="example" id="orgaf7cbed">
37ab62b986405a49b0ac6849afb8db7fa9193814c4693bf1ff30e6803b2ed406
</pre>
<div id="footnotes">
<h2 class="footnotes">Footnotes: </h2>
<div id="text-footnotes">
<div class="footdef"><sup><a id="fn.1" class="footnum" href="#fnr.1" role="doc-backlink">1</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara">Notez que depuis
l’écriture de cet article, la nouvelle version 3.7.12 a été publiée
avec ces changements.</p></div></div>
</div>
</div>La cryptographie, c’est compliqué. En Guile Scheme, ça l’est encore plus ! Heureusement, on peut utiliser GnuTLS en Guile.Démarrer un serveur avant de connaître son adresse IP2022-01-07T00:00:00+00:002022-01-07T00:00:00+00:00https://blog.planete-kraus.eu/2022/01/07/d%C3%A9marrer-un-serveur-avant-de-conna%C3%AEtre-son-adresse-IP<p>
C’est un problème qui impacte tous les bidouilleurs à domicile. C’est
déjà suffisamment complexe de configurer sa Bidule Box, mais un
nouveau problème intervient. À la maison, vous avez sans doute une
adresse IP dynamique. C’est-à-dire que si vous débranchez votre
routeur et que vous le rebranchez, vous obtiendrez une nouvelle
adresse IP. Sur ma connexion Escrorange, toutefois, il semble que ce
ne soit le cas que pour IPv4. Malheureusement, cela ne change rien au
problème finalement : lorsque mon ordi démarre, il y a un court laps
de temps pendant lequel le réseau est initialisé, mais je n’ai pas
encore d’adresse IP. Ou alors, je ne les ai pas encore toutes.
</p>
<p>
Si mon service réseau décide de démarrer à ce moment-là, je me
retrouve bien embarassé : il ne sera tout simplement pas accessible
(ou seulement partiellement), et le problème ne sera peut-être jamais
détecté.
</p>
<p>
La solution retenue par systemd <a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/systemd-network-targets-and-services_configuring-and-managing-networking">consiste à définir une cible
<code>network-online.target</code></a>, qui ne sera activée que quand la négociation
des adresses sera terminée. Comme noté sur le site de Red Hat, cette
solution n’est utile que pour le démarrage du système. En cas de panne
temporaire du réseau sans redémarrage de la machine, il n’y aura pas
de redémarrage du service. Malgré cette limitation, la solution
s’applique à tous les services nécessitant de se lier au réseau. Mais
peut-être est-il possible de concevoir mieux chaque service, afin que
le problème ne se pose pas ?
</p>
<p>
Nous allons aujourd’hui voir comment il est possible de concevoir le
service réseau de sorte à ce qu’il puisse réagir à la présence d’une
nouvelle adresse IP à lier, ou à la disparition d’une adresse
utilisée.
</p>
<div id="outline-container-org608aa50" class="outline-2">
<h2 id="org608aa50"><span class="section-number-2">1.</span> Dites bonjour :)</h2>
<div class="outline-text-2" id="text-1">
<p>
Nous allons concevoir un serveur très simple : il lui est donné comme
configuration un nom de domaine et un nom de service, et il se liera à
toutes les adresses connues de ce domaine, selon le protocole TCP,
dont le port correspond au service. Lorsqu’un client se connectera, on
enverra "Bonjour :)" et on terminera la connexion.
</p>
<p>
Comme on s’attend à ce qu’il y ait plusieurs adresses IP à lier, il y
aura donc plusieurs sockets à surveiller en même temps. Il y a de
bonnes API pour faire cela, comme par exemple <a href="https://en.wikipedia.org/wiki/Epoll">epoll</a>, mais nous allons
nous contenter de la fonction <code>select</code> car elle est disponible
directement en guile.
</p>
<div class="org-src-container">
<label class="org-src-name"><span class="listing-number">Listing 1: </span>classe-serveur : définition de la classe serveur</label><pre class="src src-scheme" id="org985359e">(<span class="org-keyword">define-class</span> <span class="org-type"><serveur></span> ()
(sockets <span class="org-builtin">#:init-value</span> '())
(hôte <span class="org-builtin">#:init-keyword</span> <span class="org-builtin">#:hôte</span> <span class="org-builtin">#:init-thunk</span> gethostname)
(service <span class="org-builtin">#:init-keyword</span> <span class="org-builtin">#:service</span> <span class="org-builtin">#:init-value</span> <span class="org-string">"12345"</span>))
</pre>
</div>
<p>
Le nom de l’hôte et le nom du service sont des constantes pour cette
classe. En effet, il serait trop complexe de gérer plusieurs noms
d’hôtes différents. La valeur par défaut du nom d’hôte est le nom de
l’ordinateur (que l’on considère comme dynamique, même s’il serait
assez incongru qu’il change sans que les adresses IP associées
suivent). La valeur par défaut du service, en revanche, est
arbitraire, et c’est au développeur de la fixer.
</p>
</div>
</div>
<div id="outline-container-org02d4a4d" class="outline-2">
<h2 id="org02d4a4d"><span class="section-number-2">2.</span> Relier le serveur en cas de besoin</h2>
<div class="outline-text-2" id="text-2">
<p>
Nous pouvons définir une méthode, <code>relier</code>, qui s’occupera de
récupérer la liste des adresses IP pour l’hôte, afin de fermer les
sockets obsolètes et d’en lier de nouvelles pour les nouvelles
adresses. Il ne faudrait pas fermer les sockets qui sont toujours
valides, autrement les connexions en attente seraient rejetées.
</p>
<p>
Par choix, nous allons éviter de faire trop de mutations dans notre
API. La fonction retournera donc deux valeurs : un nouveau serveur, et
la liste des sockets obsolètes. Charge à l’utilisateur de l’API de les
fermer.
</p>
<div class="org-src-container">
<label class="org-src-name"><span class="listing-number">Listing 2: </span>fonction-relier : mettre à jour la liste des sockets à lier</label><pre class="src src-scheme" id="org04914a6">(<span class="org-keyword">define-method</span> (<span class="org-function-name">relier</span> (serveur <span class="org-type"><serveur></span>))
(<span class="org-keyword">define</span> (<span class="org-function-name">index</span> adresse)
<span class="org-comment-delimiter">;; </span><span class="org-comment">retourne une valeur d’index pour adresse, comparable avec</span>
<span class="org-comment-delimiter">;; </span><span class="org-comment">equal?</span>
(list
(sockaddr:fam adresse)
(sockaddr:path adresse)
(sockaddr:addr adresse)
(sockaddr:port adresse)))
(<span class="org-keyword">define</span> <span class="org-function-name">tcp</span>
(protoent:proto (getprotobyname <span class="org-string">"tcp"</span>)))
(<span class="org-keyword">let</span> ((sockets-existantes
(make-hash-table))
(nouveau-serveur (shallow-clone serveur)))
(<span class="org-keyword">for-each</span>
(<span class="org-keyword">lambda</span> (socket)
(<span class="org-keyword">let</span> ((adresse (getsockname socket)))
(hash-set! sockets-existantes
<span class="org-comment-delimiter">;; </span><span class="org-comment">On ne peut pas utiliser les adresses telles</span>
<span class="org-comment-delimiter">;; </span><span class="org-comment">quelles, sinon equal? ne fonctionnera pas.</span>
(index adresse)
socket)))
(slot-ref serveur 'sockets))
(slot-set!
nouveau-serveur 'sockets
(<span class="org-keyword">map</span>
(<span class="org-keyword">lambda</span> (info-adresse)
(<span class="org-keyword">let</span> ((adresse (addrinfo:addr info-adresse)))
(<span class="org-keyword">let</span> ((existante
(hash-ref sockets-existantes
(index adresse))))
(<span class="org-keyword">if</span> existante
(<span class="org-keyword">begin</span>
(hash-remove! sockets-existantes (index adresse))
existante)
<span class="org-comment-delimiter">;; </span><span class="org-comment">Nouvelle adresse</span>
(<span class="org-keyword">let</span> ((s (socket (addrinfo:fam info-adresse)
(addrinfo:socktype info-adresse)
(addrinfo:protocol info-adresse))))
(bind s adresse)
(listen s 10)
s)))))
(getaddrinfo (slot-ref serveur 'hôte)
(slot-ref serveur 'service)
(logior AI_PASSIVE)
0 <span class="org-comment-delimiter">;; </span><span class="org-comment">Famille</span>
SOCK_STREAM <span class="org-comment-delimiter">;; </span><span class="org-comment">Avec connexion</span>
tcp)))
(values nouveau-serveur
(<span class="org-keyword">map</span> cdr (hash-map->list cons sockets-existantes)))))
</pre>
</div>
<p>
La fonction construit une table de hachage qui retient la liste des
sockets, indexée par l’adresse de la socket. Puisque les adresses IP
sont converties en nombre par guile, la fonction de comparaison
<code>equal?</code> permet de retrouver les adresses nécessaires pour satisfaire
<code>getaddrinfo</code>.
</p>
<p>
Par souci de facilité, nous allons appeler directement la fonction
<code>relier</code> dans la méthode d’initialisation de la classe de serveur.
</p>
<div class="org-src-container">
<label class="org-src-name"><span class="listing-number">Listing 3: </span>initialisation-serveur : initialisation de la classe <serveur></label><pre class="src src-scheme" id="orga5e9b5b">(<span class="org-keyword">define-method</span> (<span class="org-function-name">initialize</span> (object <span class="org-type"><serveur></span>) initargs)
(next-method)
(<span class="org-keyword">receive</span> (serveur _)
(relier object)
(slot-set! object 'sockets
(slot-ref serveur 'sockets))))
</pre>
</div>
<p>
On commence bien sûr par appeler <code>(next-method)</code>, de sorte à définir
les champs hôte et service grâce à la méthode d’initialisation par
défaut, qui ne se fonde que sur la définition des slots. Il est
possible de détourner cette garantie en utilisant de l’héritage
multiple, mais nous supposerons que l’initialisation pour la classe
fille d’un héritage multiple ne fait pas directement appel à
<code>(next-method)</code>, ce qui fera disparaître le problème.
</p>
</div>
</div>
<div id="outline-container-org6546348" class="outline-2">
<h2 id="org6546348"><span class="section-number-2">3.</span> Utilisons <code>select</code></h2>
<div class="outline-text-2" id="text-3">
<p>
Comme nous allons utiliser la fonction select, nous devons être en
mesure d’obtenir la liste des ports ou descripteurs de fichiers à
surveiller. On peut simplement retourner la valeur du champ sockets
ici.
</p>
<div class="org-src-container">
<label class="org-src-name"><span class="listing-number">Listing 4: </span>fonction-ports : retourne la liste des ports à surveiller</label><pre class="src src-scheme" id="org34ed747">(<span class="org-keyword">define-method</span> (<span class="org-function-name">ports</span> (serveur <span class="org-type"><serveur></span>))
(values
<span class="org-comment-delimiter">;; </span><span class="org-comment">Liste des ports dont on doit surveiller la lecture</span>
(slot-ref serveur 'sockets)
<span class="org-comment-delimiter">;; </span><span class="org-comment">Liste des ports dont on attend l’écriture</span>
'()
<span class="org-comment-delimiter">;; </span><span class="org-comment">Liste des ports dont on attend une erreur</span>
'()))
</pre>
</div>
<p>
Enfin, lorsque la fonction select aura indiqué un port disponible en
lecture, nous devons agir. En l’occurence, il faut accepter le nouveau
client, lui envoyer le bonjour, et fermer la socket du client. En
revanche, si la socket ne fait pas partie du serveur, il ne faut rien
faire.
</p>
<div class="org-src-container">
<label class="org-src-name"><span class="listing-number">Listing 5: </span>fonction-prête : agit lorsqu’un port est prêt</label><pre class="src src-scheme" id="org1511da4">(<span class="org-keyword">define-method</span> (<span class="org-function-name">prête</span> (serveur <span class="org-type"><serveur></span>) socket direction)
(<span class="org-keyword">when</span> (port? socket)
(set! socket (port->fdes socket)))
(<span class="org-keyword">for-each</span>
(<span class="org-keyword">lambda</span> (socket-serveur)
(<span class="org-keyword">when</span> (<span class="org-keyword">and</span> (eq? direction 'lire)
(eqv? (port->fdes socket-serveur) socket))
(<span class="org-keyword">let</span> ((client (accept socket-serveur)))
(<span class="org-keyword">let</span> ((port (car client))
(adresse (cdr client)))
(format port <span class="org-string">"Bonjour ~a :)\n"</span>
(inet-ntop (sockaddr:fam adresse)
(sockaddr:addr adresse)))
(close-port port)))))
(slot-ref serveur 'sockets))
serveur)
</pre>
</div>
</div>
</div>
<div id="outline-container-org8921c10" class="outline-2">
<h2 id="org8921c10"><span class="section-number-2">4.</span> Le programme à exécuter</h2>
<div class="outline-text-2" id="text-4">
<p>
Nous pouvons maintenant exécuter <a href="../../../code/bonjour.scm">../../../code/bonjour.scm</a> :
</p>
<div class="org-src-container">
<pre class="src src-scheme">(use-modules (oop goops) (ice-9 receive) (ice-9 match)
(srfi srfi-19))
<span class="org-type"><<classe-serveur>></span>
<span class="org-type"><<fonction-relier>></span>
<span class="org-type"><<initialisation-serveur>></span>
<span class="org-type"><<fonction-ports>></span>
<span class="org-type"><<fonction-prête>></span>
(<span class="org-keyword">define</span> (<span class="org-function-name">main</span> serveur)
(format #t <span class="org-string">"~a : le serveur lie les adresses suivantes : ~a\n"</span>
(date->string (current-date))
(<span class="org-keyword">map</span>
(<span class="org-keyword">lambda</span> (socket)
(<span class="org-keyword">let</span> ((adresse (getsockname socket)))
(<span class="org-keyword">let</span> ((adresse
(inet-ntop (sockaddr:fam adresse) (sockaddr:addr adresse)))
(port
(sockaddr:port adresse)))
(format #f <span class="org-string">"[~a]:~a"</span> adresse port))))
(slot-ref serveur 'sockets)))
(<span class="org-keyword">receive</span> (read write except) (ports serveur)
(match (select read write except 60)
((read write except)
(<span class="org-keyword">let</span> <span class="org-function-name">traiter</span> ((serveur serveur)
(tâches
(append
(<span class="org-keyword">map</span> (<span class="org-keyword">lambda</span> (socket)
`(,socket lire))
read)
(<span class="org-keyword">map</span> (<span class="org-keyword">lambda</span> (socket)
`(,socket écrire))
write)
(<span class="org-keyword">map</span> (<span class="org-keyword">lambda</span> (socket)
`(,socket exception))
except))))
(match tâches
(()
(<span class="org-keyword">receive</span> (serveur à-fermer) (relier serveur)
(<span class="org-keyword">for-each</span> close-port à-fermer)
(main serveur)))
(((socket direction) tâches-restantes ...)
(traiter (prête serveur socket direction)
tâches-restantes))))))))
(main (make <span class="org-type"><serveur></span>))
</pre>
</div>
<p>
Ce programme vérifie toutes les 60 secondes (dans le pire des cas) que
les adresses à lier sont à jour, et sert tous les clients qui se
présentent en leur répondant « Bonjour :) ».
</p>
<p>
Il y a beaucoup de choses à améliorer. Pour commencer, il faudrait
utiliser autre chose que <code>select</code>, qui a une complexité temporelle
linéaire et qui ne gère qu’un nombre limité de clients. Nous avons
aussi une complexité linéaire pour vérifier si la socket est rattachée
au serveur, et donc quadratique pour un appel récursif de <code>main</code>. Il
faudrait utiliser <code>epoll</code>, ou une bibliothèque de gestion du réseau
plus haut niveau comme la GLib, et revoir notre code. Il faudrait
également utiliser getopt-long (en internationalisant les noms longs
d’options) pour choisir le nom d’hôte à lier. Enfin, exécuter
<code>getaddrinfo</code> à chaque fois que l’on sert un groupe de clients n’est
pas très pertinent, et attendre 60 secondes peut être long. Pour
détecter les situations où nous devrions relier le serveur, il va
falloir utiliser l’API Netlink, ce que nous ferons une prochaine fois.
</p>
</div>
</div>Lorsque vous démarrez votre serveur sur un réseau dynamique, il est possible qu’il démarre avant que l’adresse IP soit connue. Que faire ?