Dans la mesure où ARM prend de plus en plus d’ampleur, ça faisait quelques
temps que je voulais voir ce que ça donnait de router avec, notamment par
rapport à la gestion des IRQ et de la fréquence.
En effet, pour router il n’y a pas besoin d …
Mise en place d’un NanoKVM sur mon routeur
J’ai récemment fait l’acquisition d’un NanoKVM Full afin de pouvoir reprendre la main sur mon
routeur (qui est une machine ARM sans IPMI intégré) si jamais je foire
sa configuration réseau alors que je ne suis pas chez moi.
Je n’avais jusqu’alors jamais eu d’OOB sur mon routeur à la maison, bien
que j’ai un backup 4G en cas de défaillance de ma FTTH (principalement dû
à des écrasements/rebranchements). Ce qui fait que je suis peu ou prou
protégé contre la bêtise de l’infra FTTH, mais pas contre ma propre
bêtise.
Mon setup est fait de telle façon que le routeur 4G n’est pas là pour
complètement prendre la main du routeur de la FTTH, mais pour servir de
support à des tunnels wireguard qui ont un cost OSPF plus élevé que le PPP
sur la FTTH. De cette façon, j’ai fallback transparent (modulo le MTU)
entre les connexions. Par contre, si jamais le routeur FTTH tombe, la 4G
tombe avec. Je prévoie d’avoir une redondance complète le jour où j’aurai
une deuxième fibre, mais ça viendra dans un second temps.
Histoire de pouvoir prendre la main sur mon NanoKVM même en cas de coupure
de fibre je veux avoir la main dessus par la 4G, mais aussi directement
via mon LAN pour éviter de bouffer mon forfait 4G pour rien lorsque je
l’utilise depuis chez moi (par exemple quand j’installe une nouvelle
machine et que j’ai la flemme de brancher un écran dessus).
La configuration de base de mon routeur 4G est de nater tout ce que va
vers l’interface 4G. Le plus propre serait d’avoir du routage dynamique,
mais la flash n’est pas assez grosse pour installer ce daemon en plus ;
après avoir installé snmpd et tcpdump dessus, il ne reste que 1.2M libres
sur la flash. De plus, bird n’est pas packagé et frr n’est pas
particulièrement léger.
J’ai donc dessus une route par défaut sur la 4G (qmimux0),
mes IPs en more-spec sur l’interco avec mon routeur FTTH
(br-lan), et un /30 dédié au NanoKVM
(br-ifLan1). J’ai gardé la configuration en DHCP côté NanoKVM
comme ça je peux facilement le brancher ailleurs si besoin, d’où le besoin
de /30 (pour avoir l’IP de broadcast) au lieu d’un classique /31. Et avec
ça, je peux accéder au NanoKVM depuis chez moi et en même temps faire
monter le VPN wireguard par la 4G.
root@Teltonika-RUT950:~# ip r
default dev qmimux0 proto static scope link src 10.222.240.167 metric 1
10.0.0.0/8 via 10.0.4.1 dev br-lan proto static metric 3
10.0.4.0/31 dev br-lan proto static scope link metric 3
10.0.4.64/30 dev br-ifLan1 proto kernel scope link src 10.0.4.65
10.222.240.167 dev qmimux0 proto static scope link metric 1
45.91.126.0/24 via 10.0.4.1 dev br-lan proto static metric 3
172.16.0.0/12 via 10.0.4.1 dev br-lan proto static metric 3
192.168.0.0/16 via 10.0.4.1 dev br-lan proto static metric 3
root@Teltonika-RUT950:~# grep -C2 nanokvm /etc/config/dhcp
config host
option mac '48:da:35:6f:a0:73'
option name 'nanokvm'
option ip '10.0.4.66'
alarig@x280 ~ % ping nanokvm.int.no.swordarmor.fr
PING nanokvm.int.no.swordarmor.fr (10.0.4.66) 56(84) bytes of data.
64 bytes from nanokvm.int.no.swordarmor.fr (10.0.4.66): icmp_seq=1 ttl=62 time=1.71 ms
64 bytes from nanokvm.int.no.swordarmor.fr (10.0.4.66): icmp_seq=2 ttl=62 time=1.59 ms
^C
--- nanokvm.int.no.swordarmor.fr ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 1.591/1.649/1.707/0.058 ms
Arrivé ici je vais naturellement sur l’interface web (le port ssh n’était
pas ouvert par défaut) pour voir si je peux activer le SSH, et on
peut !
Une fois loggué en root, je commence à regarder ce que j’ai sous la main,
par simple curiosité, mais je pense que vous me comprendrez. Nous avons
donc affaire à un Linux (pas surprenant) qui tourne sur un RISC-V 64-bit
(malin pour avoir une faible conso) dont je connais pas le modèle exact
parce que lscpu n’est pas dispo. Il s’agît d’une disto
custom, /etc/os-release parle de Buildroot 2023.11.2, mais on
a tout le userland habituel et même htop ainsi que 6.4G
encore disponibles sur la partition principale.
Pour les curieux, voici le
dmesg.
Puisque je suis root, et bien que l’interface web ne propose pas ces
options, mon premier réflexe est d’apporter quelques finitions au système
afin de l’intégrer au mieux au reste de mon infra. Cela commence donc par
utiliser mon propre résolveur DNS et serveur NTP et aussi exporter les
logs.
Pour les deux premiers c’est très facile, les fichiers de configuration
habituels sont déjà là, il suffit donc de les éditer afin d’avoir les
valeurs que je veux puis ensuite de redémarrer les services.
Par contre pour les logs, je n’en ai pas trouvé. Je
suis donc allé voir comment le syslogd est lancé afin de
savoir si je pouvais utiliser un fichier de configuration absent par
défaut ou bien des variables d’environnement. Mon premier réflexe a été de
lister les units systemd mais la commande n’existe pas.
root@kvm-a073 ~ # systemctl list-units
bash: systemctl: command not found
C’est somme toute assez logique puisque nous sommes tout de même sur une
petite machine, et systemd est tout sauf léger. L’étape suivante est donc
de fouiller /etc/init.d à l’ancienne, et bingo !
root@kvm-a073 ~ # find /etc/init.d/ -iname '*log*'
/etc/init.d/S02klogd
/etc/init.d/S01syslogd
root@kvm-a073 ~ # cat /etc/init.d/S01syslogd
#!/bin/sh
DAEMON="syslogd"
PIDFILE="/var/run/$DAEMON.pid"
SYSLOGD_ARGS=""
# shellcheck source=/dev/null
[ -r "/etc/default/$DAEMON" ] && . "/etc/default/$DAEMON"
# BusyBox' syslogd does not create a pidfile, so pass "-n" in the command line
# and use "-m" to instruct start-stop-daemon to create one.
start() {
printf 'Starting %s: ' "$DAEMON"
# shellcheck disable=SC2086 # we need the word splitting
start-stop-daemon -b -m -S -q -p "$PIDFILE" -x "/sbin/$DAEMON" \
-- -n $SYSLOGD_ARGS
status=$?
if [ "$status" -eq 0 ]; then
echo "OK"
else
echo "FAIL"
fi
return "$status"
}
stop() {
printf 'Stopping %s: ' "$DAEMON"
start-stop-daemon -K -q -p "$PIDFILE"
status=$?
if [ "$status" -eq 0 ]; then
rm -f "$PIDFILE"
echo "OK"
else
echo "FAIL"
fi
return "$status"
}
restart() {
stop
sleep 1
start
}
case "$1" in
start|stop|restart)
"$1";;
reload)
# Restart, since there is no true "reload" feature.
restart;;
*)
echo "Usage: $0 {start|stop|restart|reload}"
exit 1
esac
Le script d’init parle donc de la variable SYSLOGD_ARGS
sourcée depuis le fichier /etc/default/syslogd. Un
file nous apprend qu’il s’agît de l’implémentation busybox
(/sbin/syslogd: symbolic link to ../bin/busybox. On peut ainsi aller lire
le man de busybox pour savoir quelles options on peut
utiliser pour avoir le droit au bonheur.
root@kvm-a073 ~ # ps aux | grep log
224 root /sbin/syslogd -n
237 root /sbin/klogd -n
1671 root grep log
root@kvm-a073 ~ # cat /etc/default/syslogd
SYSLOGD_ARGS="-R msi.no.swordarmor.fr -L"
root@kvm-a073 ~ # /etc/init.d/S01syslogd restart
Stopping syslogd: OK
Starting syslogd: OK
root@kvm-a073 ~ # ps aux | grep log
237 root /sbin/klogd -n
1935 root /sbin/syslogd -n -R msi.no.swordarmor.fr -L
1941 root grep log
Tout cela est très sympathique mais ce que j’aimerais quand même à un
moment c’est avoir un wireguard dessus pour prendre la main via la 4G,
sinon on va encore dire que je me disperse. Malheureusement l’interface
web ne parle que de tailscale mais pas d’un simple wireguard comme je
l’aimerais. Qu’à cela ne tienne je suis root, je vais trouver un
arrangement.
Le classique /etc/rc.local pour configurer le réseau sous
debian lancer des commandes au boot n’est pas vide mais parle de
fichiers qui n’existent pas. Dans le doute je me dis que ça risque d’être
écrasé lors d’une mise à jour. Je ne peux pas non plus lancer un cron
@reboot car le daemon est installé mais n’est pas lancé.
root@kvm-a073 ~ # cat /etc/rc.local
#!/bin/sh
echo "HELLO WORLD"
if [ -e /boot/maixcam ]
then
/maixapp/apps/launcher/launcher &
fi
root@kvm-a073 ~ # file /boot/maixcam
/boot/maixcam: cannot open `/boot/maixcam' (No such file or directory)
root@kvm-a073 ~ # file /maixapp/apps/launcher/launcher
/maixapp/apps/launcher/launcher: cannot open `/maixapp/apps/launcher/launcher' (No such file or directory)
root@kvm-a073 ~ # which crond
/usr/sbin/crond
root@kvm-a073 ~ # ps aux | grep cron
1384 root grep cron
Donc il ne me reste plus qu’à faire les choses proprement : écrire un
script d’init. Je me dis que c’est une bonne idée de se mettre au même
niveau de priorité que sshd, puisqu’il a également besoin que le réseau
soit up avant de démarrer. Je ne m’embête pas avec la gestion du PID vu
que c’est un module kernel que je lance, ni avec les commandes pour faire
tomber le VPN vu que j’ai pas prévu de le faire, mais ça serait assez
trivial à ajouter.
root@kvm-a073 ~ # find /etc/init.d/ -iname '*ssh*'
/etc/init.d/S50sshd
root@kvm-a073 ~ # vim /etc/wireguard/wg0.conf
root@kvm-a073 ~ # sed 's/PrivateKey.*/PrivateKey = blah/' /etc/wireguard/wg0.conf
[Interface]
PrivateKey = blah
[Peer]
#Endpoint = ovpn.fr.swordarmor.fr:10017
Endpoint = 89.234.186.219:10017
PublicKey = ZzPmmpKns18Q6lpJ5V+T6MenBXjMEx2qDDj58yaIzTo=
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
root@kvm-a073 ~ # vim /etc/init.d/S50wireguard
root@kvm-a073 ~ # cat /etc/init.d/S50wireguard
#!/bin/sh
case "$1" in
start)
echo "Start ovpn.fr connection"
ip link add dev wg0 type wireguard
wg setconf wg0 /etc/wireguard/wg0.conf
ip addr add 10.0.1.9/31 dev wg0
ip link set up dev wg0
ping -c1 10.0.1.8
#ip route add 0.0.0.0/0 dev wg0
;;
stop)
;;
restart|reload)
"$0" stop
"$0" start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
exit $?
root@kvm-a073 ~ # chmod +x /etc/init.d/S50wireguard
root@kvm-a073 ~ # /etc/init.d/S50wireguard start
root@kvm-a073 ~ # ip a
1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 48:da:35:6f:a0:73 brd ff:ff:ff:ff:ff:ff permaddr c2:e8:e7:a3:16:80
inet 10.0.4.66/30 brd 10.0.4.67 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::4ada:35ff:fe6f:a073/64 scope link
valid_lft forever preferred_lft forever
3: ip6tnl0@NONE: mtu 1452 qdisc noop state DOWN group default qlen 1000
link/tunnel6 :: brd :: permaddr 8aa8:c467:33d4::
4: usb0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 48:da:35:6e:a0:73 brd ff:ff:ff:ff:ff:ff
inet 10.160.115.1/24 scope global usb0
valid_lft forever preferred_lft forever
inet6 fe80::4ada:35ff:fe6e:a073/64 scope link
valid_lft forever preferred_lft forever
5: wg0: mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
link/none
inet 10.0.1.9/31 scope global wg0
valid_lft forever preferred_lft forever
Donc on a bien un tunnel wireguard en plus du LAN, avec la MTU par défaut qui semble être fonctionnelle désormais ; même si lorsque j’avais monté les tunnels pour redonder ma FTTH avec la 4G j’avais dû abaisser le MTU à 1340.
root@kvm-a073 ~ # tracepath ovpn.fr.swordarmor.fr
1?: [LOCALHOST] pmtu 1500
1: Teltonika-RUT950.com 2.881ms
1: eth0.105.rut950.int.no.swordarmor.fr 2.950ms
2: no reply
3: 192.168.8.62 59.745ms asymm 2
4: 192.168.255.22 47.473ms asymm 7
5: no reply
6: no reply
7: no reply
8: no reply
9: no reply
10: no reply
11: no reply
12: no reply
13: no reply
14: no reply
15: no reply
16: ovpn.fr.as208627.net 87.731ms reached
Resume: pmtu 1500 hops 16 back 13
La configuration de mon routeur wireguard est de nater tout ce qui va vers
un tunnel. L’utilité première est de prendre la main sur des machines
nomades à distance pour lesquelles je dois assurer la maintenance (celles
de la famille quoi) et sur lesquelles je ne maîtrise pas forcément le
routage de leur LAN.Ici ça m’arrange parce que du coup j’ai pas besoin de gérer des tables de routage différentes sur le NanoKVM entre le LAN et le wireguard. L’IP source vue par le KVM sur le wireguard sera forcément celle sur la route directement connectée et le LAN pourra continuer à utiliser la default, donc la réponse ira au bon endroit.
Le mtr montre bien que je fais le tour de mon réseau pour revenir chez moi. Je ne distribue pas d’IP publique dans le VPN parce que je ne vois pas spécialement de cas d’usage où je ne pourrais pas monter mon propre VPN pour y accéder.
alarig@x280 ~ % mtr -bzwe nanokvm-wg.int.no.swordarmor.fr
Start: 2026-03-07T15:44:08+0100
HOST: x280.int.no.swordarmor.fr Loss% Snt Last Avg Best Wrst StDev
1. AS??? enp3s0.101.core02-rennes.fr.as208627.net (192.168.5.254) 0.0% 10 1.1 1.1 0.8 1.2 0.1
2. AS??? ppp0.edge01-th2lf.swordarmor.fr (10.0.4.16) 0.0% 10 6.9 7.0 6.3 8.4 0.7
3. AS??? eno1.104.regis.swordarmor.fr (10.0.4.24) 0.0% 10 13.6 13.9 13.1 15.7 0.8
4. AS204092 ovpn.fr.as208627.net (89.234.186.219) 0.0% 10 14.1 13.9 13.1 15.2 0.6
5. AS??? nanokvm-wg.int.no.swordarmor.fr (10.0.1.9) 0.0% 10 57.4 79.0 53.0 119.6 19.3
Et maintenant qu’on a tout ça, on peut enfin regarder si la fonction de
KVM est bien remplie. Donc déjà premier test : est-ce qu’il reste bien
allumé si je redémarre le routeur ? (si jamais il prenait l’alimentation
électrique depuis l’USB qui sert à envoyer les I/O clavier).
Et c’est bien le cas, l’uptime du KVM est supérieur à celui du routeur.
alarig@x280 ~ % ssh root@nanokvm.int.no.swordarmor.fr
root@nanokvm.int.no.swordarmor.fr's password:
# uptime
11:57:12 up 23 min, load average: 5.30, 5.57, 4.10
#
alarig@x280 ~ % ssh root@core01-rennes.fr.swordarmor.fr
Linux core01-rennes.fr.swordarmor.fr 6.18.12-gentoo-core01-arm #1 SMP PREEMPT_DYNAMIC Fri Feb 20 00:23:04 CET 2026 aarch64 GNU/Linux
Last login: Sat Mar 7 11:28:15 CET 2026 from 192.168.5.1 on pts/1
core01-rennes ~ # uptime
12:57:32 up 8 min, 1 user, load average: 0.17, 0.12, 0.07
core01-rennes ~ #
J’ai bien les logs dmesg qui me disent que l’émulation de
clavier/souris est présente. J’ai même le /data du NanoKVM
qui est exposé.
core01-rennes ~ # dmesg | grep -i NanoKVM
[ 3.190596] usb 16-1: Product: NanoKVM
[ 3.274675] input: sipeed NanoKVM as /devices/platform/PNP0D10:09/usb16/16-1/16-1:1.2/0003:3346:1009.0001/input/input1
[ 3.426555] hid-generic 0003:3346:1009.0001: input,hidraw0: USB HID v1.01 Keyboard [sipeed NanoKVM] on usb-PNP0D10:09-1/input2
[ 3.427383] input: sipeed NanoKVM as /devices/platform/PNP0D10:09/usb16/16-1/16-1:1.3/0003:3346:1009.0002/input/input2
[ 3.427588] hid-generic 0003:3346:1009.0002: input,hidraw1: USB HID v1.01 Mouse [sipeed NanoKVM] on usb-PNP0D10:09-1/input3
[ 3.428332] input: sipeed NanoKVM as /devices/platform/PNP0D10:09/usb16/16-1/16-1:1.4/0003:3346:1009.0003/input/input3
[ 3.428452] hid-generic 0003:3346:1009.0003: input,hidraw2: USB HID v1.01 Mouse [sipeed NanoKVM] on usb-PNP0D10:09-1/input4
[ 4.537290] scsi 0:0:0:0: Direct-Access NanoKVM USB Mass Storage 0520 PQ: 0 ANSI: 2
core01-rennes ~ # fdisk -l /dev/sda
Disk /dev/sda: 21.49 GiB, 23076012032 bytes, 45070336 sectors
Disk model: USB Mass Storage
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00000000
Et quand je vais sur l’interface web, j’ai bien la sortie tty et je peux
taper des trucs dedans sans avoir des caractères bizarres comme sur les
IPMI supermicro quand il y a un mix qwerty/azery.

La puissance du CPU ne semble pas être très élevée, puisque quand j’ai
téléchargé l’ISO d’arch (qui fait 1.4Go) ça a pris un peu plus de vingt
minutes, soit un débit de ~10Mbps. Pendant ce temps le CPU était à 100%
tout du long. Mais c’est largement suffisant pour afficher la sortie
console et je ne compte pas passer mon temps à télécharger plein d’ISO
différents.
Donc à voir sur la durée si mes bricoles résistent aux upgrades, mais ça
me semble être un produit de qualité tout à fait respectable et largement
hackable.