Tags

arm64 bhyve bit blacklistd bluepill bluetooth cloudinit contabo cu dns dovecot elf encrypt envsubst esp8266 filter freebsd iot ipfw lets lowdown meross openocd opensmtpd openwrt orke perl prises python shell ssh ssl stm32 template tls unbound vps wifi yubikey

2026-08-12

[ ssh ]

alias, tag et match

Imaginons le scénario suivant:

Un extrait des connexions possibles:

$ ssh vm-http.dev.foo.fr
$ ssh pouet@vm-cache.prod.bar.org
$ ssh root@vm-db.dev.baz.com

La complétion

On peut se simplifier la vie en créant des fichiers correspondant aux connexions:

$ mkdir .ssh/completion/ && cd .ssh/completion
$ touch vm-http.dev.foo.fr pouet@vm-cache.prod.bar.org root@vm-db.dev.baz.com ...

et compléter la commande ssh avec la liste de ces fichiers. Inconvénient: la touche Tab va passer un sale quart d’heure.

Mon idéal

Il faudrait avoir une “commande” par domaine: foo pour se connecter à foo.fr, bar pour bar.org et baz pour baz.com . En rajoutant une lettre (minuscule et/ou majuscule ?) pour le sous-domaine, on aurait Pfoo pour se connecter à .prod.foo.fr et dbaz pour .dev.baz.com .

Pour les logins et les machines, il ne faudrait préciser que le service et utiliser une lettre pour le login: Rdb pour root@vm-db, pcache pour pouet@vm-cache, http pour vm-http .

En assemblant le tout, l’extrait des connexions possibles deviendrait:

$ dfoo http
$ Pbar pcache
$ dbaz Rdb

Le mixte minuscule/majuscule n’est là que pour illustrer le propos et pourrait ici se traduire par “si c’est important/risqué/dangereux alors c’est en majuscule”.

Match

Une de mes utilisations de cette directive de mon .ssh/config est de définir des “raccourcis”:

Match originalhost owrt
    HostName openwrt.mon.domaine.que.j.ai

mais si je veux pouvoir préciser un éventuel login, je dois être un peu plus générique:

Match originalhost *db
    HostName vm-db

qu’on traduira par “si le nom de machine de la commande ssh se termine par db alors le nom de machine à utiliser est vm-db”. J’utilise la même chose pour les logins:

Match originalhost p*
    User pouet

Match originalhost R*
    User root

À noter que le “p” passerait en majuscule (pour bien le distinguer du nom de machine) dans le cas où un service commencerait par “p” (proxy, pgsql …).

Tag

C’est avec cette fonctionnalité que je vais résoudre en partie la simplification domaine / sous-domaine. Il est possible de passer un “tag” à une connexion à l’aide du paramètre -P tag et de définir des valeurs par “tag”:

Match tagged old
    KexAlgorithms diffie-hellman-group1-sha1

Pour vérifier si la directive est prise en compte:

$ ssh -P old -G pouet | grep ^kexalgorithms
kexalgorithms diffie-hellman-group1-sha1

Chaque domaine / sous-domaine va correspondre à un “tag” qui va définir le nom de domaine:

Match tagged devfoo
    CanonicalDomains .dev.foo.fr
...
Match tagged prodbaz
    CanonicalDomains .prod.baz.com

Je dois aussi forcer les machines commençant par “vm-” à utiliser un nom de domaine:

Match host vm-*
    CanonicalizeHostname yes

Je vérifie mes combinaisons “tag” / “host”:

$ ssh -G -P devfoo Rdb | grep -e '^user ' -e '^hostname '
user root
hostname vm-db.dev.foo.fr

Attention, une connexion ne peut avoir qu’un seul “tag” et l’ordre des directives est important !

Alias

Je n’ai plus qu’à définir les alias:

alias dfoo ssh -P devfoo
alias Pfoo ssh -P prodfoo
...
alias Pbaz ssh -P prodbaz

et attendre que mes doigts mémorisent tout ça.

Un petit pour la route

Parce que la vie n’est qu’une longue suite d’exceptions, imaginons qu’une machine d’un sous-domaine ne respecte pas la convention de nommage:

# putain de vs-database de mes couilles
Match tagged devbar originalhost *db
    HostName vs-database

Match originalhost *db
    HostName vm-db

Tiré d’un exemple @TAF (et oui, le commentaire est d’origine).

Commentaires: https://github.com/bsdsx/blog_posts/issues/24

Lien vers ce billet


2026-08-08

[ orke lowdown ]

Bienvenue à lowdown !

Mon générateur de blog statique utilisait jusqu’ici un script awk pour transformer du markdown en html. Cela fait quelques temps déjà que j’utilise l’excellent lowdown et son option -tterm pour documenter des projets. Ainsi mes Makefile contiennent désormais une cible doc qui exécute

lowdown -tterm README.md | less -R

lowdown ne manque pas de fonctionnalités parmis lesquelles une sortie html bien plus complète que mon script awk. Métadonnées et gabarits vont clouer définitivement le cercueil de mon générateur.

Générer du html

lowdown a le bon goût d’utiliser l’entrée standard (bien utile pour vérifier le comportement d’une option):

$ echo '## titre de niveau 2' | lowdown -thtml
<h2 id="titre-de-niveau-2">titre de niveau 2</h2>
$ echo '## titre de niveau 2' | lowdown -thtml --html-no-head-ids 
<h2>titre de niveau 2</h2>

Générer une page html

Pour obtenir une page, il faut utiliser l’option -s (ou --out-standalone) qui va utiliser un gabarit par défaut:

$ printf '## titre de niveau 2\nbla bla\nbla bla' | lowdown -thtml --html-no-head-ids -s
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title></title>
</head>
<body>
<h2>titre de niveau 2</h2>
<p>bla bla
bla bla</p>
</body>
</html>

On peut préciser un autre gabarit à l’aide de l’option --template:

$ cat /tmp/tmpl.tmpl
<!DOCTYPE html>
<html>
<body>
$body$
</body>
</html>
$ printf '## titre de niveau 2\nbla bla\nbla bla' | lowdown -thtml --html-no-head-ids -s --template /tmp/tmpl.tmpl
<!DOCTYPE html>
<html>
<body>
<h2>titre de niveau 2</h2>
<p>bla bla
bla bla</p>

</body>
</html>

La magie opère avec les métadonnées:

$ cat /tmp/tmpl.tmpl
<!DOCTYPE html>
<html>
<head><title>$title$</title></head>
<body>
$body$
</body>
</html>
$ cat /tmp/md.md
---
title: mon super titre
---

## Niveau 2

bla bla bla
$ lowdown -thtml --html-no-head-ids -s --template /tmp/tmpl.tmpl /tmp/md.md
<!DOCTYPE html>
<html>
<head><title>mon super titre</title></head>
<body>
<h2>Niveau 2</h2>
<p>bla bla bla</p>

</body>
</html>

Mon blog

Une page du blog est composée de 3 parties:

Le blog est physiquement organisé de la façon suivante:

Un fichier blog.md contient les métadonnées du blog:

$ cat blog.md
---
blog_title: Mon ch'ti blog
blog_description: Unix, libre, toussa ...
blog_url: http://blog.bsdsx.fr
blog_author: bsdsx
blog_root: /
---

Ajouter un billet

L’ajout d’un billet va:

Le rendu du billet

Le rendu du billet src/2026-08-08_orke.md sera stocké dans www/2026/08/2026-08-08_orke.md.htm et généré avec la commande:

lowdown -thtml $LOWDOWN_OPTS -M "date: $YYYY-$MM-$DD" -M "post_url: /$YYYY/$MM/$SRC.html" -s --template tmpl/post.tmpl src/$SRC > $ARCHIVES/$SRC.htm

Le gabarit est très simple:

$ cat tmp.post.tmpl
<p class="date">$date$</p>
<p class="tags">[$for(tags.split)$ <a href="/tags/$this$">$this$</a>$endfor$ ]</p>
<h2>$title$</h2>
$body$
<p>Commentaires: <a target="_blank" href="https://github.com/bsdsx/blog_posts/issues/$id_issue$">https://github.com/bsdsx/blog_posts/issues/$id_issue$</a></p>
<p class="post"><a href="$post_url$" target="_blank">Lien vers ce billet</a></p>
<hr>

Je distingue le rendu du billet (.htm) de la page du billet (.html). Faire cette distinction va me permettre de concaténer plusieurs rendus de billet au sein d’une même page.

Les mots clefs

Pour chaque mot clef, créer un répertoire et un lien vers le rendu du billet:

TAGS=$(lowdown -X tags src/$SRC)
for tag in $TAGS; do
    mkdir -p www/tags/$tag
    (cd www/tags/$tag; ln -fs ../../$YYYY/$MM/$SRC.htm .)
done

Le rendu des mots clefs (partie gauche d’une page):

OUT=www/.tags.htm
echo '<div id="left"><div id="cloud_tags"><p>Tags</p>' > $OUT
for tag in $(find www/tags -type d -mindepth 1 -printf '%f\n' | sort); do
    printf '<a href="/tags/%s/index.html">%s</a>\n' $tag $tag >> $OUT
done
echo '</div>' >> $OUT
lowdown -thtml -s --template tmpl/left_links.tmpl blog.md >> $OUT
echo '</div><!-- end left -->' >> $OUT

Les archives

Le rendu de la partie droite:

OUT=www/.archives.htm
echo '<div id="right"><div id="archives"><p>Archives</p>' > $OUT
for yyyy_mm in $(find www/2* -type d -mindepth 1 | sort -r); do
    yyyy_mm=${yyyy_mm#www/}
    printf '<a href="/%s/index.html">%s</a>\n' $yyyy_mm $yyyy_mm >> $OUT
done
echo '</div>' >> $OUT
lowdown -thtml -s --template tmpl/right_links.tmpl blog.md >> $OUT
echo '</div><!-- end right -->' >> $OUT

Début et fin de page

Génération du début d’une page à l’aide des métadonnées du blog:

OUT=www/.before_main.htm
lowdown -thtml -s --template tmpl/top.tmpl blog.md > $OUT
cat www/.tags.htm www/.archives.htm >> $OUT
echo '<div id="main">' >> $OUT

OUT=www/.after_main.htm
echo '</div><!-- end main -->' > $OUT
lowdown -thtml -s --template tmpl/bottom.tmpl blog.md >> $OUT

Arrivé ici le rendu des différentes parties est terminé, reste à générer les pages html complètes.

Page des mots clefs

Concaténer le contenu des liens de chaque mot clef:

for tag in $TAGS; do
    OUT=www/tags/$tag/.index.html
    HTMS=$(find www/tags/$tag/ -type l | sort -r | tr '\n' ' ')
    cat www/.before_main.htm $HTMS www/.after_main.htm > $OUT
    mv $OUT www/tags/$tag/index.html
done

Page d’archive

Concaténer les rendus de billet:

OUT=$ARCHIVES/.index.html
cat www/.before_main.htm $ARCHIVES/*.htm www/.after_main.htm > $OUT
mv $OUT $ARCHIVES/index.html

Page d’un billet

Identique à une page d’archive mais avec un seul billet:

OUT=$ARCHIVES/.$SRC.html
cat www/.before_main.htm $ARCHIVES/$SRC.htm www/.after_main.htm > $OUT
mv $OUT $ARCHIVES/$SRC.html

Page d’accueil

Concaténer les derniers billets:

[ -f www/.posts ] || touch www/.posts
grep -q --max-count 1 $ARCHIVES/$SRC.htm www/.posts || echo $ARCHIVES/$SRC.htm >> www/.posts
...
latest_posts=$(tail -n $POSTS_PER_PAGE www/.posts | sort -r | tr '\n' ' ')

OUT=www/.index.html
cat www/.before_main.htm $latest_posts www/.after_main.htm > $OUT
mv $OUT www/index.html

Les flux atom et rss

Pour générer ces fichers, j’ai besoin des métadonnées du blog et des billets. Je ne peux pas simplement faire:

$ cat blog.md src/billet.md | lowdown ...

car lowdown ne prendra en compte que les métadonnées du premier fichier comme le montre la commande suivante:

$ cat blog.md src/2026-08-08_orke.md | lowdown -L
blog_title
blog_description
blog_url
blog_author
blog_root

Je ruse en supprimant les marqueurs (“---”) en trop (le dernier du premier fichier et le premier du deuxième fichier) à l’aide d’un script awk:

BEGIN { ff = 1 } # First File
NF == 1 && $1 == "---" {
  if (ff && FNR != 1) { ff = 0; nextfile } # skip last mark
  if (!ff && FNR == 1) { getline } # skip first mark
}

{ print }

Chaque flux utilise 2 gabarits: un pour le début de fichier et un par “entrée”:

awk -f concat.awk blog.md src/$SRC | lowdown -thtml $LOWDOWN_OPTS -M "date: $YYYY-$MM-$DD" -M "post_url: /$YYYY/$MM/$SRC.html" -s --template tmpl/atom_entry.tmpl > $ARCHIVES/$SRC.htm.atom

OUT=www/.feed.atom
[ -f www/.atom ] || lowdown -thtml -s --template tmpl/atom.tmpl blog.md > www/.atom
cat www/.atom > $OUT
echo "<updated>${YYYY}-${MM}-${DD}T08:00:00Z</updated>" >> $OUT
for post in $latest_posts; do
    cat $post.atom >> $OUT
done
echo '</feed>' >> $OUT
mv $OUT www/feed.atom

Le blog

Je peux facilement repartir de zéro:

$ cat reset.sh
#!/bin/sh
set -eu
rm -rf www
for src in src/2*.md; do
./orke.sh $src
done
cp assets/* www/

ou prévisualiser un billet:

$ cat preview.sh
#!/bin/sh
set -eu
cat www/.before_main.htm
lowdown -thtml --html-no-head-ids --parse-no-intraemph -s --template tmpl/post.tmpl $1
cat www/.after_main.htm

$ ./preview.sh src/2026-08-08_orke.md > www/preview.html

Cette nouvelle version d’**orke** méritait bien un petit quelque chose, j’ai donc pris le temps de faire (enfin !) un thème sombre.

Commentaires: https://github.com/bsdsx/blog_posts/issues/23

Lien vers ce billet


2025-04-16

[ cloudinit contabo vps ]

VPS chez Contabo

Pour commencer:

Virtual Private Server

Multi-coeurs, gavés de RAM, un vps d’aujourd’hui n’a rien à envier à un serveur dédié. Pour peu que l’on sache être économe sur l’espace de stockage, un vps peut rendre de multiples services:

et j’en passe. Mes pré-requis concernant un serveur n’ont pas changé d’un iota: FreeBSD et ipv6. FreeBSD parce que je le vaux bien et l’ipv6 parce que l’ipv4 c’est pour les gueux. Il ne doit pas être simple de trouver un founisseur de vps qui ne propose pas de connectivité ipv6 par contre, question BSD, c’est pas gagné. C’est pourquoi j’ai choisi il y a quelques temps déjà Contabo qui malheureusement ne propose plus l’option FreeBSD. Mais tout n’est pas perdu.

API

Le principe est simple: commander un vps sans se soucier du système d’exploitation et le réinstaller à l’aide de l’api. La documentation est claire et une fois mes identifiants récupérés j’ai pû commencer l’écriture de quelques lignes de code Python. On peut trouver sur les nains ternets moultes implémentations autrement plus complètes que mon code mais mes besoins étant simples j’ai préféré “faire à ma sauce” (qui a dit NIH ?).

Le script

Le script contabo.py utilise le module api_contabo.py qui permet de:

Je suis un adepte du “–run”, lancer le script sans aucun argument revient à afficher l’aide:

$ python contabo.py
usage: contabo.py [-h] [--debug] [--run] [--config CONFIG] methode [args ...]

Contabo - api

positional arguments:
  methode
  args

options:
  -h, --help       show this help message and exit
  --debug          debug requests
  --run            run this script
  --config CONFIG  .ini config file

Exemple:

contabo.py --run [--config {config.ini}] images [query]
contabo.py --run [--config {config.ini}] image {imageId}

contabo.py --run [--config {config.ini}] instances
contabo.py --run [--config {config.ini}] instance {instanceId}
contabo.py --run  --config {config.ini}  reset_instance {instanceId} user_data.yml [{imageId} [secretId [secretId...]]

contabo.py --run [--config {config.ini}] secrets
contabo.py --run [--config {config.ini}] secret {secretId}
contabo.py --run [--config {config.ini}] create_secret {secret} {name}
contabo.py --run [--config {config.ini}] delete_secret {secretId}

contabo.py --run [--config {config.ini}] token

Réinstaller un vps

Avant de réinstaller un vps, il faut commencer par s’occuper du “secret”: clef ssh plutôt que maux de passe (maux, pas mot). Pour se faire, placer les identifiants dans des variables d’environnement ou dans un fichier “.ini” (on peut se baser sur le fichier example.ini fourni) :

$ cat contabo.ini
[Api]
CLIENT_ID     =
CLIENT_SECRET = 
API_USERNAME  = 
API_PASSWORD  = 

L’accès à l’api requiert un token:

$ python --run --config contabo.ini token
[ snip token ]

Pour ne pas générer un token à chaque appel, je l’enregistre dans une variable API_CONTABO_TOKEN. Je crée une clef ssh:

$ ssh-keygen -t ed25519 -N '' -f contabo_ed25519

et j’ajoute mon secret (le nom du secret permet de l’identifier plus facilement) :

$ python contabo.py --run --config contabo.ini create_secret "`cat contabo_ed25519.pub|tr -d '\n'`" nom_de_mon_secret
<Response [201]>

Je peux maintenant lister mes secrets:

$ python contabo.py --run --config contabo.ini secrets

et afficher un secret

$ python contabo.py --run --config contabo.ini secret 123456

Je liste les images disponibles (FreeBSD par défaut) :

$ python contabo.py --run --config contabo.ini images

La version la plus récente proposée (14.0) n’est pas vraiment la plus à jour. Si cela pose problème il reste l’option (payante) ‘custom image’ qui propose un espace de 25 Go pour stocker ses images “aux petits oignons”.

Il est temps de lister les instances:

$ python contabo.py --run --config contabo.ini instances

Pour me simplifier la vie, je rajoute une section ‘DEFAULT’ à mon fichier “.ini"

[DEFAULT]
imageId   = 67a63682-6817-41a1-9b87-c8adb4072f27 # freebsd-14.0
sshKeys   = 123456                               # contabo_ed25519

et une section pour mon instance:

[123456789]
productId = V45 # VPS 1 SSD (no setup)

et je réinstalle mon vps:

$ python3 reset_instance {instanceId} user_data.yml

cloudinit

Il n’y a pas très longtemps que j’ai compris l’usage de ce machin nuagique pour enfin y trouver un intéret. Pour les ceussent qui sachent, je ne vais rien vous apprendre. Pour les autres, un exemple de fichier que j’utilise:

#cloud-config
ssh_pwauth: false
user: CHANGE_THIS
packages:
  - net/rsync
  - security/doas
  - sysutils/tmux
write_files:
  - path: /etc/rc.conf.d/background_fsck
    content: |
      background_fsck_delay="10"
  - path: /etc/rc.conf.d/hostname
    content: |
      hostname="CHANGE_THIS"
  - path: /etc/rc.conf.d/local_unbound
    content: |
      local_unbound_enable="YES"
  - path: /etc/rc.conf.d/network
    content: |
      ifconfig_vtnet0='inet CHANGE_THIS netmask 255.255.255.0'
      ifconfig_vtnet0_ipv6="inet6 CHANGE_THIS prefixlen 64"
  - path: /etc/rc.conf.d/ntpd
    content: |
      ntpd_enable="YES"
      ntpd_sync_on_start="YES"
      ntpd_flags="--novirtualips"
      ntp_leapfile_sources="https://hpiers.obspm.fr/iers/bul/bulc/ntp/leap-seconds.list"
  - path: /etc/rc.conf.d/routing
    content: |
      defaultrouter=CHANGE_THIS
      ipv6_defaultrouter=fe80::1%vtnet0
  - path: /etc/rc.conf.d/sshd
    content: |
      sshd_enable="YES"
      sshd_flags="-6 -o UseDNS=yes -o UsePAM=no -o AllowGroups=wheel -o PermitRootLogin=without-password"
  - path: /etc/rc.conf.d/syslogd
    content: |
      syslogd_flags="-ss"
  - path: /usr/local/etc/doas.conf
    content: |
      permit nopass setenv { -ENV PS1=$DOAS_PS1 SSH_AUTH_SOCK } :wheel
runcmd:
  - pw groupmod unbound -m CHANGE_THIS
  - chmod -R g+w /etc/rc.conf.d /etc/unbound/conf.d
  - rm /etc/rc.conf
  - rm -rf /usr/lib/debug /usr/lib32
  - chflags -R 0 /usr/lib32
  - rm -rf /usr/lib32
power_state:
  mode: reboot

Et parce que l’ipv6 n’est pas un long fleuve tranquille, on peut avoir besoin de rajouter:

  - path: /etc/sysctl.conf.local
    content: |
      net.inet6.icmp6.nd6_onlink_ns_rfc4861=1 # because fe80::1 don't respond to neighbor solicitation
  - path: /etc/rc.conf.d/netwait
    content: |
      netwait_enable="YES"
      netwait_ip="2a02:c207::1:53"
      netwait_timeout="5"

Conclusion

Je suis très satisfait de mes 3 vps et si la version (pas très fraiche) de FreeBSD peut en rebuter certains, je suis persuadé que les images BASIC-CLOUDINIT (un énorme merci à bapt@ pour nuageinit ) seront bientôt disponibles.

Commentaires: https://github.com/bsdsx/blog_posts/issues/22

Lien vers ce billet


2024-10-30

[ iot prises meross python ]

Prises connectées

Alors que je baguenaudais sur un site de vente en ligne à une période propice à moultes réductions et dans un énième moment d’égarement j’ai fait l’acquisition de “prises connectées”. Par ‘égarement’ j’entends qu’aucune recherche préalable de compatibilité avec mon environnement n’a été faite et que le besoin à ce moment était loin d’être justifié. Bref, j’ai craqué.

Le matos

Lot de 2 prises Meross MSS305ZF Smart Wi-Fi Plug 16A

Première impression

Autant le dire tout de suite: port série, Tasmota, firmware alternatif et autres bidouilleries ne seront pas de la partie. Et comme ce genre de bestiole s’accompagne toujours d’une application pour débilophone, l’oubli au fond du tiroir ‘okazou’ semblait être la destinée finale de cet achat compulsif. Après quelques (tardives) recherches, je m’aperçois que tout n’est pas perdu.

Configuration initiale

Je ne suis pas spécialiste de ce genre de gadget mais la séquence suivante me semble la façon usuelle de procéder:

Problème: mon débilophone est un Samsung S9 sous LineageOS (ce qui le rend tout de suite un peu moins con) qui n’a pas accès aux magasins d’applications.

Solution: Grâces soient rendues à Fabi019 et l’apk qu’il met à disposition depuis https://github.com/Fabi019/MerossBLE et qui s’installe sans problème. 2 minutes plus tard, une première prise est connectée. Le truc à retenir qui servira plus tard: la valeur du champ ‘Key’ du cartouche ‘1. Server Configuration’ .

Faire mumuse

Je n’ai pas (encore ?) de solution domotique, j’ai vaguement lancé un serveur mqtt (la prise envoie bien des données dessus) mais ce qui m’intérresse c’est d’interagir directement avec la prise. Pour se faire j’ai trouvé des projets en go, nodejs et python. Comme chacun sait, le plus important c’est la doc. Et la doc, pour mon cas, elle est là: https://github.com/arandall/meross/blob/main/doc/protocol.md .

Le protocole n’est pas très compliqué et consiste à l’envoi d’un JSON à une url fixe. J’ai longtemps buté sur un élément:

sign == Signing value equal to md5(messageId + key + timestamp)

car, de ce que j’avais lu, cette ‘key’ semblait être fournie après l’ouverture d’un compte chez meross. J’ai finalement compris que je pouvais fixer cette valeur depuis l’application du débilophone, comme vu un peu plus haut (le truc à retenir).

Je me suis fendu de quelques lignes de python (car on n’est jamais aussi bien servi que par soi-même et non c’est pas du nih) et je peux désormais contrôler mes prises depuis la VM FreeBSD de ma Freebox Delta.

Reste à faire

La prise en mode “appairage” propose un point d’accès wifi à partir duquel il est possible de pousser la configuration. Je n’ai pas encore testé cette possibilité. Le point d’accès auquel se connectent les prises ne propose pas d’ipv6 et je ne trouve pas trace des adresses MAC des prises dans mon voisinage ipv6. Je pense qu’on peut faire des trucs rigolos avec les ‘.TriggerX’ . Quant à ‘Appliance.Control.Upgrade’, si bucket-meross-static.meross.com existe, je n’ai pas encore trouvé le moyen d’obtenir le chemin vers une éventuelle mise à jour du firmware.

Conclusion

Est-ce que je regrette mon achat ? Non. Est-ce que je vais surveiller ma consommation électrique afin de la faire baisser ? Je ne crois pas. Est-ce que je vais faire du mqtt ? Je ne sais pas. Est-ce que je vais finir par installer une solution de domotique ? J’en doute mais si j’ai bien compris c’est un engrenage. Et comme j’ai aussi 2 prises ‘Amazon smart plug’, le mal est peut-être déjà fait.

Commentaires: https://github.com/bsdsx/blog_posts/issues/21

Lien vers ce billet


2024-02-29

[ bhyve wifi openwrt ]

wifibox à la sauce OpenWrt

J’ai pendant très longtemps utilisé un mini routeur sous OpenWrt car le wifi de mon ancien nuc n’était ni stable ni performant. Les ressources de ma nouvelle machine de travail (AMD Ryzen 7 3750H) étant bien supérieures, l’idée m’est venue de mimer FreeBSD wifibox à la sauce OpenWrt. Un portable Lenovo T450 en a aussi fait les frais.

wifibox

Le principe est assez simple: si la plateforme matérielle le permet, attribuer le périphérique wifi à une machine virtuelle dont le pilote a une meilleure prise en charge que celui de FreeBSD. Le projet a fait le choix l’Alpine Linux.

wifi et bhyve

Je commence par identifier le périphérique:

$ pciconf -lv
...
iwm0@pci0:2:0:0:        class=0x028000 rev=0x59 hdr=0x00 vendor=0x8086 device=0x095a subvendor=0x8086 subdevice=0x5010
    vendor     = 'Intel Corporation'
    device     = 'Wireless 7265'
    class      = network
...

Il ne doit plus être pris en charge par le pilote iwm mais par le Pci PassThru (ppt). Je dois aussi charger le module vmm et parce que j’ai un processeur AMD activer un sysctl comme indiqué sur le wiki FreeBSD)

$ cat /boot/loader.conf.d/vmm.conf
vmm_load="YES"
pptdevs="2/0/0" # à adapter suivant pciconf -lv
hw.vmm.amdvi.enable=1

Un redémarrage plus tard, on obtient:

$ pciconf -lv
...
ppt0@pci0:2:0:0:    class=0x028000 rev=0x59 hdr=0x00 vendor=0x8086 device=0x095a subvendor=0x8086 subdevice=0x5010
    vendor     = 'Intel Corporation'
    device     = 'Wireless 7265'
    class      = network
...

Machine virtuelle

Je récupère la dernière image d’OpenWrt:

$ fetch https://downloads.openwrt.org/releases/23.05.2/targets/x86/64/openwrt-23.05.2-x86-64-generic-ext4-combined-efi.img.gz
$ gunzip openwrt-23.05.2-x86-64-generic-ext4-combined-efi.img.gz
gunzip: openwrt-23.05.2-x86-64-generic-ext4-combined-efi.img.gz: trailing garbage ignored

Pour que bhyve puisse utiliser une image efi je dois ajouter le paquet suivant:

$ doas pkg install bhyve-firmware

et je vérifie que la machine virtuelle démarre bien:

$ doas bhyve -H -P -m 192M -s 0:0,hostbridge -s 1:0,lpc -s 2:0,virtio-net,tap0 -s 3:0,virtio-blk,./openwrt-23.05.2-x86-64-generic-ext4-combined-efi.img -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd -l com1,stdio -W vm0

Un halt plus tard je peux détruire cette machine virtuelle:

$ doas bhyvectl --destroy --vm=vm0

Utile quand on joue au plus malin et qu’avec 128 Mo de mémoire on obtient:

error: out of memory.
error: out of memory.


Press any key to continue...Press any key to continue...

OpenWrt

Léger problème avec l’image fournie: elle ne contient absolument rien (module, pilote, outil …) concernant le wifi (c’est voulu par le projet pour la plate-forme x86). Je prépare un disque à ajouter à mon image avec le contenu nécessaire:

$ truncate -s 5M pkgs.img # oui, 5 Mo c'est bien assez
$ newfs_msdos ./pkgs.img
newfs_msdos: warning, ./pkgs.img is not a character device
./pkgs.img: 10192 sectors in 1274 FAT12 clusters (4096 bytes/cluster)
BytesPerSec=512 SecPerClust=8 ResSectors=1 FATs=2 RootDirEnts=512 Sectors=10240 Media=0xf0 FATsecs=4 SecPerTrack=63 Heads=255 HiddenSecs=0
$ doas mdconfig ./pkgs.img 
md0
$ doas mount -t msdosfs /dev/md0 /media

Depuis https://downloads.openwrt.org/releases/23.05.2/packages/x86_64/base/ je récupère les paquets:

hostapd-common_2023-09-08-e5ccbfc6-6_x86_64.ipk
iw_4.14-1_x86_64.ipk
iwinfo_2023-07-01-ca79f641-1_x86_64.ipk
iwlwifi-firmware-iwl7265d_20230804-1_x86_64.ipk
libnl-tiny1_2023-07-27-bc92a280-1_x86_64.ipk
ucode-mod-nl80211_2023-11-07-a6e75e02-1_x86_64.ipk
ucode-mod-rtnl_2023-11-07-a6e75e02-1_x86_64.ipk
ucode-mod-uloop_2023-11-07-a6e75e02-1_x86_64.ipk
wireless-regdb_2024.01.23-1_all.ipk
wpa-cli_2023-09-08-e5ccbfc6-6_x86_64.ipk
wpa-supplicant-mini_2023-09-08-e5ccbfc6-6_x86_64.ipk

et depuis https://downloads.openwrt.org/releases/23.05.2/targets/x86/64/packages/ les modules:

kmod-cfg80211_5.15.137+6.1.24-3_x86_64.ipk
kmod-crypto-aead_5.15.137-1_x86_64.ipk
kmod-crypto-cbc_5.15.137-1_x86_64.ipk
kmod-crypto-ccm_5.15.137-1_x86_64.ipk
kmod-crypto-cmac_5.15.137-1_x86_64.ipk
kmod-crypto-ctr_5.15.137-1_x86_64.ipk
kmod-crypto-gcm_5.15.137-1_x86_64.ipk
kmod-crypto-gf128_5.15.137-1_x86_64.ipk
kmod-crypto-ghash_5.15.137-1_x86_64.ipk
kmod-crypto-hmac_5.15.137-1_x86_64.ipk
kmod-crypto-manager_5.15.137-1_x86_64.ipk
kmod-crypto-null_5.15.137-1_x86_64.ipk
kmod-crypto-rng_5.15.137-1_x86_64.ipk
kmod-crypto-seqiv_5.15.137-1_x86_64.ipk
kmod-crypto-sha512_5.15.137-1_x86_64.ipk
kmod-iwlwifi_5.15.137+6.1.24-3_x86_64.ipk
kmod-mac80211-hwsim_5.15.137+6.1.24-3_x86_64.ipk
kmod-mac80211_5.15.137+6.1.24-3_x86_64.ipk

Je démarre la machine virtuelle avec ce nouveau disque:

$ doas umount /media
$ doas mdconfig -du md0
$ doas bhyve -H -P -m 192M -s 0:0,hostbridge -s 1:0,lpc -s 2:0,virtio-net,tap0 -s 3:0,virtio-blk,./openwrt-23.05.2-x86-64-generic-ext4-combined-efi.img -s 4:0,virtio-blk,./pkgs.img -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd -l com1,stdio -W vm0

et j’installe les paquets:

root@OpenWrt:/# mount -t vfat /dev/vdb /mnt/
root@OpenWrt:/# opkg install /mnt/libnl-tiny1_2023-07-27-bc92a280-1_x86_64.ipk
Package libnl-tiny1 (2023-07-27-bc92a280-1) installed in root is up to date.
root@OpenWrt:/# ln /usr/lib/libnl-tiny.so.1 /usr/lib/libnl-tiny.so # sinon iw va râler

root@OpenWrt:/# opkg install /mnt/hostapd-common_2023-09-08-e5ccbfc6-6_x86_64.ipk /mnt/iw_4.14-1_x86_64.ipk /mnt/iwinfo_2023-07-01-ca79f641-1_x86_64.ipk /mnt/iwlwifi-firmware-iwl7265d_20230804-1_x86_64.ipk /mnt/ucode-mod-* /mnt/wireless-regdb_2024.01.23-1_all.ipk /mnt/wpa-cli_2023-09-08-e5ccbfc6-6_x86_64.ipk /mnt/wpa-supplicant-mini_2023-09-08-e5ccbfc6-6_x86_64.ipk
Installing hostapd-common (2023-09-08-e5ccbfc6-6) to root...
Installing iw (4.14-1) to root...
Installing iwinfo (2023-07-01-ca79f641-1) to root...
Installing iwlwifi-firmware-iwl7265d (20230804-1) to root...
Installing ucode-mod-nl80211 (2023-11-07-a6e75e02-1) to root...
Installing ucode-mod-rtnl (2023-11-07-a6e75e02-1) to root...
Installing ucode-mod-uloop (2023-11-07-a6e75e02-1) to root...
Installing wireless-regdb (2024.01.23-1) to root...
Installing wpa-cli (2023-09-08-e5ccbfc6-6) to root...
Installing wpa-supplicant-mini (2023-09-08-e5ccbfc6-6) to root...
Configuring iwinfo.
Configuring iw.
Configuring ucode-mod-uloop.
Configuring hostapd-common.
Configuring ucode-mod-nl80211.
Configuring ucode-mod-rtnl.
Configuring wpa-supplicant-mini.
Configuring iwlwifi-firmware-iwl7265d.
Configuring wpa-cli.
Configuring wireless-regdb.
root@OpenWrt:/# opkg install /mnt/kmod-*
...
root@OpenWrt:/# umount /mnt
root@OpenWrt:/# halt

Il est temps d’affecter le périphérique wifi à la machine virtuelle (adapter 2/0/0 au pciconf -lv et ne pas oublier l’option -S):

$ doas bhyve -H -P -S -m 192M -s 0:0,hostbridge -s 1:0,lpc -s 2:0,virtio-net,tap0 -s 3:0,virtio-blk,./openwrt-23.05.2-x86-64-generic-ext4-combined-efi.img -s 7:0,passthru,2/0/0 -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd -l com1,stdio -W vm0

Configuration OpenWrt

Pas besoin du délai de grub:

root@OpenWrt:/# grep timeout /boot/grub/grub.cfg 
set timeout="0"

Quelques modules sont chargés au démarrage, je ne garde que le minimum (et encore, je pense que seul 30-fs-vfat est utile pour récupérer la configuration de grub):

root@OpenWrt:/# ls -1 /etc/modules-boot.d/
02-crypto-hash
04-crypto-crc32c
09-crypto-aead
09-crypto-manager
30-fs-vfat

Pareil pour les modules chargés après le démarrage (je doit pourvoir encore en supprimer quelques uns):

root@OpenWrt:/# ls -1 /etc/modules.d/
02-crypto-hash
04-crypto-crc32c
09-crypto-acompress
09-crypto-aead
09-crypto-cbc
09-crypto-ccm
09-crypto-cmac
09-crypto-ctr
09-crypto-gcm
09-crypto-gf128
09-crypto-ghash
09-crypto-hmac
09-crypto-manager
09-crypto-null
09-crypto-rng
09-crypto-seqiv
09-crypto-sha512
25-nls-cp437
25-nls-iso8859-1
25-nls-utf8
30-fs-vfat
iwlwifi
lib-crc-ccitt
lib-crc32c
lib-lzo
mac80211-hwsim

Je désactive les services inutiles:

root@OpenWrt:/# /etc/init.d/dnsmasq     stop && /etc/init.d/dnsmasq     disable
root@OpenWrt:/# /etc/init.d/dropbear    stop && /etc/init.d/dropbear    disable
root@OpenWrt:/# /etc/init.d/firewall    stop && /etc/init.d/firewall    disable
root@OpenWrt:/# /etc/init.d/gpio_switch stop && /etc/init.d/gpio_switch disable
root@OpenWrt:/# /etc/init.d/led         stop && /etc/init.d/led         disable
root@OpenWrt:/# /etc/init.d/sysntpd     stop && /etc/init.d/sysntpd     disable
root@OpenWrt:/# /etc/init.d/uhttpd      stop && /etc/init.d/uhttpd      disable

Je configure le wifi:

root@OpenWrt:/# cat /etc/config/wireless
...
config wifi-device 'radio2'
    option type 'mac80211'
    option path 'pci0000:00/0000:00:07.0'
    option channel '36'
    option band '5g'
    option htmode 'VHT80'
    option disabled '0'

config wifi-iface 'default_radio2'
    option device 'radio2'
    option network 'wan wan6'
    option mode 'sta'
    option ssid 'mon_super_ssid'
    option encryption 'psk2'
    option key 'ma_super_key'

et le réseau:

root@OpenWrt:/# cat /etc/config/network
config interface 'loopback'
    option device 'lo'
    option proto 'static'
    option ipaddr '127.0.0.1'
    option netmask '255.0.0.0'

config interface 'lan'
    option device 'eth0'
    option proto 'static'
    option ipaddr '172.20.99.1'
    option netmask '255.255.255.0'
    option ip6addr '2db8:xxxx:xxxx:xx3:172:20:99:1/112'

config interface 'wan'
    option proto 'static'
    option ipaddr '172.20.62.99'
    option netmask '255.255.255.0'
    option gateway '172.20.62.1'
    list dns '172.30.63.20'
    list dns '172.30.63.10'

config interface 'wan6'
    option proto 'static'
    option ip6addr '2db8:xxxx:xxxx:xx2::99/64'
    option ip6gw '2db8:xxxx:xxxx:xx2::1'
    list dns '2db8:xxxx:xxxx:xx1:172:30:63:20'
    list dns '2db8:xxxx:xxxx:xx1:172:30:63:10'

Configuration FreeBSD

Le bridge:

$ cat /etc/rc.conf.d/bridge
autobridge_interfaces="bridge0"
autobridge_bridge0="tap0"

Le réseau:

$ cat /etc/rc.conf.d/network
cloned_interfaces="bridge0 tap0"

ifconfig_bridge0="inet 172.20.99.10 netmask 255.255.255.0"
ifconfig_bridge0_ipv6="inet6 auto_linklocal"
ifconfig_bridge0_alias0="inet6 2db8:xxxx:xxxx:xx3:172:20:99:10 prefixlen 112"

$ cat /etc/rc.conf.d/routing
defaultrouter="172.20.99.1"
ipv6_defaultrouter="2db8:xxxx:xxxx:xx3:172:20:99:1"

L’accès à la console de la machine virtuelle à l’aide de nmdm:

$ cat /etc/rc.conf.d/kld
kld_list="/boot/modules/amdgpu.ko nmdm"

Le démarrage de la machine virtuelle:

$ cat /etc/rc.local
/usr/sbin/bhyve -H -P -S -m 192M -s 0:0,hostbridge -s 1:0,lpc -s 3:0,virtio-blk,/chemin/complet/vers/openwrt-23.05.2-x86-64-generic-ext4-combined-efi.img -s 7:0,passthru,2/0/0 -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd -l com1,/dev/nmdm0A vm0 &

L’accès à la console de la machine virtuelle se fera à l’aide de cu:

$ doas cu -l /dev/nmdm0B # oui "B", man 4 nmdm

Mon réseau

La configuration réseau présentée est loin d’être la plus commune (pas de dhcp ni de nat, ipv6 sans autoconf). Pour une version plus “traditionnelle” (dhcp, pas d’**ipv6**), la machine virtuelle devra:

Ce qui devrait peu ou prou correspondre à (pas encore testé):

root@OpenWrt:/# cat /etc/config/network
....
config interface 'wan'
    option proto 'dhcp'

root@OpenWrt:/# /etc/init.d/firewall enable && /etc/init.d/firewall start

Au pire la documentation d’OpenWrt https://openwrt.org/docs/guide-user/network/start est plus que fournie.

Pour conclure

Le iperf est sans appel:

$ iperf3 -6 -c proxy
...
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec   226 MBytes   190 Mbits/sec   19             sender
[  5]   0.00-10.01  sec   226 MBytes   189 Mbits/sec                  receiver

$ iperf3 -6 -c proxy
...
[  5]   0.00-10.00  sec   263 MBytes   221 Mbits/sec    0             sender
[  5]   0.00-10.02  sec   263 MBytes   220 Mbits/sec                  receiver

C’est loin d’être parfait au vu de la configuration du réseau:

freebox delta [ vm proxy sous FreeBSD ] <-- rj45 --> ap wifi Mikrotik hAP ac^2 <- -> nuc

mais c’est toujours mieux que la dizaine de Mbits/sec (dont je me contentais très bien) de mon ancienne installation.

Le mini routeur qui trônait fièrement au dessus du nuc devrait finir au fin fond d’un tirroir. Mais comme il possède un lien série, j’en ferais bien un Frankenstein à base de bluetooth …

Correction 2024-03-17

J’ai oublié ‘-s 2:0,virtio-net,tap0’ à chaque commande bhyve.

Commentaires: https://github.com/bsdsx/blog_posts/issues/20

Lien vers ce billet