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-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


2023-12-10

[ stm32 openocd ]

Faire clignoter une LED avec openocd

J’utilise openocd pour communiquer avec mes cartes stm32. N’utilisant ni tcl ni gdb (désolé pour les afficionados valencians j’ai ajouté ces lignes dans mon fichier de configuration:

gdb_port disabled
tcl_port disabled

Il ne me reste donc plus que le telnet en écoute sur le port 4444 pour dialoguer avec mes bestioles, ce que je fais à l’aide de nc:

$ echo 'reset halt' | nc -N 127.0.0.1 4444
$ echo "flash write_image erase ${PWD}/main.bin 0x8000000" | nc -N 127.0.0.1 4444
$ echo 'reset run' | nc -N 127.0.0.1 4444

Pris d’une inspiration aussi divine que soudaine j’ai dernièrement tenté un:

$ echo help | nc -N 127.0.0.1 4444

et quelle ne fût pas ma surprise devant cette avalanche de commandes ! Je suis loin d’en avoir fait le tour mais les suivantes ont attiré mon attention:

mdb ['phys'] address [count]
      display memory bytes
mdd ['phys'] address [count]
      display memory double-words
mdh ['phys'] address [count]
      display memory half-words
mdw ['phys'] address [count]
      display memory words

mwb ['phys'] address value [count]
      write memory byte
mwd ['phys'] address value [count]
      write memory double-word
mwh ['phys'] address value [count]
      write memory half-word
mww ['phys'] address value [count]
      write memory word

reset [run|halt|init]

Le manuel de référence indique, page 172, que la valeur initiale du Port configuration register high vaut 0x4444 4444. Du fait qu’elle soit facilement reconnaissable, c’est cette valeur que je vais tenter d’afficher. Autre information sur cette page, le décalage à appliquer depuis l’adresse de base du GPIO:

Address offset: 0x04

Page 51, on peut lire que l’adresse de base du GPIO Port C se trouve en 0x4001 1000, le registre de configuration se trouve donc en 0x4001 1004. Je vérifie:

$ echo 'reset halt' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> reset halt
target halted due to debug-request, current mode: Thread 
xPSR: 00000000 pc: 00000000 msp: 00000000

$ echo 'reset init' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> reset init
target halted due to debug-request, current mode: Thread 
xPSR: 00000000 pc: 00000000 msp: 00000000

$ echo 'mdw 0x40011004' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mdw 0x40011004
0x40011004: 44444444

Comme on dit du côté de Valence: Rodriguez ! Autre valeur particulière, page 1087, le DBGMCU_IDCODE à l’adresse 0xE004 2000:

$ echo 'mdw 0xE0042000' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mdw 0xE0042000
0xe0042000: 20036410

Il s’agit d’une révision 3 (2003) d’un “medium-density device” (0x410). La taille de la mémoire flash (page 1076) sur 16 bits (half-words):

$ echo 'mdh 0x1FFFF7E0' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mdh 0x1FFFF7E0
0x1ffff7e0: 0080

Et bien sur l’identifiant de la carte, page (1077-1078), 2 x 16 bits + 2 * 32 bits:

$ echo 'mdh 0x1FFFF7E8 2' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mdh 0x1FFFF7E8 2
0x1ffff7e8: 0048 0024

$ echo 'mdw 0x1FFFF7EC 2' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mdw 0x1FFFF7EC 2
0x1ffff7ec: 31333911 41373931

La LED PC13

Pour contrôler la PIN 13 du GPIO C je dois d’abord activer ce dernier. Page 106, dans le “APB2 peripheral reset register”, on peut voir que le GPIO C correspond au bit 4. Ce registre se situe à 0x18 de l’adresse de base de RCC (Reset and Clock Control) qui se trouve en 0x4002 1000 (page 50). Je commence par vérifier la valeur initiale puis j’active le GPIO:

$ echo 'mdw 0x40021018' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mdw 0x40021018
0x40021018: 00000000

$ echo 'mww 0x40021018 0x00000010' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mww 0x40021018 0x00000010

$ echo 'mdw 0x40021018' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mdw 0x40021018
0x40021018: 00000010

Maintenant que le GPIO est activé, je peux passer la PIN 13 en sortie (Output) depuis le registre de configuration (0x4001 1004, vu plus haut). Pour se faire, les bits 21 et 20 (le “mode”) devront avoir pour valeur respective 1 et 0:

$ echo 'mww 0x40011004 0x44644444' | nc -N 127.0.0.1 4444
Open On-Chip Debugger
> mww 0x40011004 0x44644444

A ce moment la LED devrait être allumée. Pour changer l’état de la PIN j’utilise les bits 13 et 29 du registre “Port bit set/reset register”, page 173, dont le décalage vaut 0x10:

# bit 13 à 1, état haut, LED éteinte
$ echo 'mww 0x40011010 8192' | nc -N 127.0.0.1 4444

# bit 29 à 1, état bas, LED allumée
$ echo 'mww 0x40011010 536870912' | nc -N 127.0.0.1 4444

Pour conclure

Manipuler les bits et les adresses d’un micro-contrôleur reste pour moi une source d’émerveillement dont je doute me lasser un jour. Il existe un lien naturel entre ce monde et celui de l’informatique “générale” qu’illustre parfaitement cette (célèbre ?) sentence:

Fonctions, variables ? Mensonges ! Tout n'est qu'adresse -- iMil

Il ne me reste plus qu’à lire tranquillement la documentation officielle, la partie concernant Tcl me faisant de l’oeil …

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

Lien vers ce billet