SwordArMor

Mise en place d’un NanoKVM sur mon routeur

Je n’ai 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 si jamais je me coupe la main sur mon routeur sans être chez moi.
Mon setup est fait de telle façon que le routeur 4G n’est pas là pour prendre complètement 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.
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 qui vient du routeur FTTH vers n’importe quoi. L’idée est que ça m’évite de faire du routage dynamique sur le routeur 4G pour que la réponse des tunnels wireguard revienne à mon routeur. Après avoir installé snmpd et tcpdump dessus, il ne reste que 1.2M libres sur la flash ; bird n’est pas packagé et frr n’est pas particulièrement léger.
La configuration de mon routeur wireguard est très similaire puisque 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).
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 sera forcément celle sur la route directement connectée, donc la réponse ira au bon endroit.

Le setup sur le Teltonika qui gère la 4G est très simple : j’ai 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. Et avec ça, je peux accéder au NanoKVM depuis chez moi.

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 dispo) 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, parce que sinon c’est pas drôle. 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.
Tout cela est très sympathique mais ce que j’aimerais quand même, c’est avoir un wireguard dessus pour prendre la main via la 4G (c’est à dire sans être routé par la FTTH, puisque c’est sur ce routeur que je risque de reprendre la main). 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 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, le daemon est installé mais n’est pas lancé.
J’ai également envie d’envoyer les logs vers la machine chez moi qui les reçoit tous, histoire d’être carré. Bien que l’interface web ne propose pas de pouvoir le faire, le service d’init parle de la variable SYSLOGD_ARGS dans le fichier /etc/default/syslogd (il faut lire le man de busybox pour savoir quelles options on peut utiliser).
Pour les curieux, voici le dmesg.

# cat /etc/rc.local
#!/bin/sh

echo "HELLO WORLD"

if [ -e /boot/maixcam ]
then
	/maixapp/apps/launcher/launcher &
fi
# file /boot/maixcam
/boot/maixcam: cannot open `/boot/maixcam' (No such file or directory)
# file /maixapp/apps/launcher/launcher
/maixapp/apps/launcher/launcher: cannot open `/maixapp/apps/launcher/launcher' (No such file or directory)
# which crond
/usr/sbin/crond
# ps aux | grep cron
 1384 root     grep cron
# ps aux | grep log
  224 root     /sbin/syslogd -n
  237 root     /sbin/klogd -n
 1671 root     grep log
# cat /etc/default/syslogd 
SYSLOGD_ARGS="-R msi.no.swordarmor.fr -L"
# /etc/init.d/S01syslogd restart
Stopping syslogd: OK
Starting syslogd: OK
# ps aux | grep log
  237 root     /sbin/klogd -n
 1935 root     /sbin/syslogd -n -R msi.no.swordarmor.fr -L
 1941 root     grep log

Donc il ne me reste plus qu’à faire les choses proprement : écrire un script d’init.
La commande systemctl n’existe pas et /etc/init.d/ contient tous les services qui sont lancés. 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.
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.
À noter aussi qu’à l’époque où j’avais monté les tunnels wireguard pour sécuriser ma FTTH, je n’avais de MTU à 1500 sur la 4G, mais ça semble désormais être le cas.

# ls -lh /etc/init.d/
total 120K
-rwxr-xr-x    1 root     root         646 Mar  7 10:50 S00kmod
-rwxr-xr-x    1 1000     1000         735 May  9  2025 S00pmu
-rwxr-xr-x    1 root     root        1.2K Mar  7 10:50 S01fs
-rwxr-xr-x    1 1000     1000        1.2K May  9  2025 S01seedrng
-rwxr-xr-x    1 1000     1000        1012 May  9  2025 S01syslogd
-rwxr-xr-x    1 1000     1000        1.7K May  9  2025 S02config
-rwxr-xr-x    1 1000     1000        1004 May  9  2025 S02klogd
-rwxr-xr-x    1 1000     1000        2.7K May  9  2025 S02sysctl
-rwxr-xr-x    1 root     root        5.1K Mar  7 10:50 S03usbdev
-rwxr-xr-x    1 1000     1000         324 May  9  2025 S04fb
-rwxr-xr-x    1 1000     1000        1.6K May  9  2025 S10udev
-rwxr-xr-x    1 1000     1000        1.3K May  9  2025 S10uuid
-rwxr-xr-x    1 root     root        6.7K Mar  7 10:50 S15kvmhwd
-rwxr-xr-x    1 1000     1000         404 May  9  2025 S21haveged
-rwxr-xr-x    1 1000     1000         229 May  9  2025 S25wifimod
-rwxr-xr-x    1 1000     1000        1.6K May  9  2025 S30dbus
-rwxr-xr-x    1 root     root        1.7K Mar  7 10:50 S30eth
-rwxr-xr-x    1 1000     1000        1.3K May  9  2025 S30rndis
-rwxr-xr-x    1 1000     1000         820 May  9  2025 S35iptables
-rwxr-xr-x    1 1000     1000         438 May  9  2025 S40network
-rwxr-xr-x    1 1000     1000         918 May  9  2025 S49ntp
-rwxr-xr-x    1 1000     1000         690 May  9  2025 S50ser2net
-rwxr-xr-x    1 root     root        1.2K Mar  7 10:50 S50sshd
-rwxr-xr-x    1 1000     1000         538 May  9  2025 S80dnsmasq
-rwxr-xr-x    1 root     root        2.2K Mar  7 10:50 S95nanokvm
-rwxr-xr-x    1 1000     1000         423 May  9  2025 rcK
-rwxr-xr-x    1 1000     1000         408 May  9  2025 rcS
# vim /etc/wireguard/wg0.conf
# vim /etc/init.d/S50wireguard
# 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 $?
# chmod +x /etc/init.d/S50wireguard
# /etc/init.d/S50wireguard start
# 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
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 


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.
Capture d’écran de la sortie KVM dans firefox
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.

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.