/dev/net/tun mancante con tun caricato: riparare il nodo del dispositivo
Un modulo tun caricato non dimostra che il nodo del dispositivo esista. Separate il driver dal nodo, ricostruitelo nell'ordine giusto e verificatelo con un'interfaccia.
OpenVPN si ferma con una lamentela errno 2 su /dev/net/tun mentre lsmod elenca tranquillamente il modulo tun. Entrambi i fatti sono veri nello stesso momento. Il modulo fornisce un driver, e il percorso che il client apre è un nodo del dispositivo che qualcos'altro deve creare. Trattarli come un unico oggetto è il motivo per cui la riparazione ovvia di ricaricare il modulo non cambia assolutamente nulla.
Due oggetti, due proprietari diversi
Un modulo del kernel e un nodo del dispositivo sono oggetti indipendenti con cicli di vita indipendenti. Il caricamento del modulo registra un driver, e su un sistema con devtmpfs il kernel chiede che venga creato il nodo corrispondente. Su un sistema senza devtmpfs nulla crea nulla, e il driver resta lì senza alcun percorso che lo punti. Il modulo può essere caricato, sano e completamente irraggiungibile attraverso il filesystem nello stesso momento.
Ecco perché il testo dell'errore e l'elenco dei moduli non si contraddicono. Il client apre un percorso, il percorso non esiste, e il kernel restituisce ENOENT prima che il driver tun venga mai consultato. Ricaricare il modulo non può riparare un percorso mancante, e creare il percorso non può riparare un driver mancante.
lsmod | grep -w tun
stat -c "%F %t:%T" /dev/net/tun
grep " /dev " /proc/mounts
devtmpfs contro un mknod esplicito
Normalmente due meccanismi creano quel nodo, e sapere quale dovrebbe esserne responsabile vi dice cosa riparare. devtmpfs fa creare al kernel i nodi dei dispositivi in un'istanza montata, e udev applica sopra di essi proprietà e permessi. Un mknod esplicito è il percorso manuale per immagini e spazi dei nomi in cui nessuno dei due viene eseguito all'avvio.
Confrontate i risultati invece delle intenzioni. Se devtmpfs è montato su /dev e il nodo è ancora assente, il driver non è quello che supponete, oppure qualcosa ha rimosso il nodo dopo l'avvio. Se /dev è una normale directory dentro un rootfs scompattato, non esiste alcun meccanismo automatico e chi costruisce l'immagine deve creare il nodo.
stat -c "%F major=%t minor=%T" /dev/net/tun
# expect: character special file major=a minor=c8
Dove non viene eseguito nessuno dei due meccanismi, il nodo va creato a mano, e l'ordine conta. Create prima la directory padre, perché mknod non può inventarla, poi il dispositivo a caratteri, poi un modo che lo lasci scrivibile solo da root. Ricaricate il modulo prima di creare il nodo, e rimuovete un percorso esistente solo dopo che ls ha mostrato che si tratta di una directory e non di un dispositivo.
sudo modprobe tun
sudo mkdir -p /dev/net
sudo mknod /dev/net/tun c 10 200
sudo chmod 600 /dev/net/tun
ls -l /dev/net/tun
Il tipo riportato da stat è il discriminante più rapido, e una directory su quel percorso è la sorpresa più comune nelle immagini minime. Un mkdir ben intenzionato in un Dockerfile lascia esattamente quello, e nessuna quantità di caricamento di moduli sostituisce una directory con un dispositivo a caratteri. Major 10 con minor 200 è la coppia registrata per tun, espressa in esadecimale come a e c8.
Quattro tracce, quattro cause
Quattro cause spiegano quasi ogni caso, e ciascuna lascia una traccia diversa nei log. Iniziate dall'ambiente di avvio. Le immagini senza disco, le schede integrate e le fasi initramfs spesso non montano né devtmpfs né un /devgestito da udev, così nessun processo scrive mai il nodo. Poi leggete le opzioni di montaggio di ciò che ospita /dev, perché nodev permette al percorso di esistere ma rifiuta di aprirlo, e un montaggio bind preso da un host indurito porta il flag senza annunciarsi. Un montaggio in sola lettura fallisce prima e più forte: mknod lì restituisce EROFS, mentre EPERM significa che al processo manca la capacità e non che il filesystem si stia proteggendo. Infine, un runtime di container con il proprio /dev tmpfs ha bisogno che il nodo sia iniettato nel proprio spazio dei nomi, e il suo cgroup dei dispositivi deve consentire la coppia major e minor.
Controllate il livello di policy prima di cambiare di nuovo i permessi. SELinux e AppArmor possono rifiutare un'apertura che ogni attributo del file consente, e quel rifiuto compare nel log di audit e non nell'output del client. Un cambio di permessi che non appare mai in un rifiuto è tempo speso sul livello completamente sbagliato.
sudo journalctl -k -n 20 --no-pager
sudo ausearch -m AVC -ts recent | tail -5
Dimostratelo con un'interfaccia, non con un elenco
Un elenco riuscito dimostra che un percorso esiste e nulla di più. Aprite un nodo tun sano in lettura e la chiamata torna con un errore, perché il dispositivo non ha semantica di lettura e non è un file normale. Quell'errore è qui il segnale di successo, e riconoscerlo vi evita di inseguirlo come un guasto.
head -c 1 /dev/net/tun
head: error reading '/dev/net/tun': File descriptor in bad state
Il test decisivo crea un'interfaccia, perché mette alla prova il driver, il nodo e i vostri privilegi in un solo passo. Se l'aggiunta riesce e il link compare, tutti e tre funzionano. Quando fallisce, errno separa le cause: ENOENT significa che non c'è alcun nodo utilizzabile, EPERM punta alla capacità, ai flag di montaggio o alla policy, e EBUSY significa che un altro processo possiede già l'interfaccia.
sudo ip tuntap add dev tun7 mode tun
ip -brief link show tun7
sudo ip tuntap del dev tun7 mode tun
Eseguite quel test nel contesto che fallisce davvero, perché un'unità di servizio, un container o un'attività pianificata possono portare un insieme di capacità diverso da quello della vostra shell di accesso. Un test che passa nel contesto sbagliato non dimostra nulla su quello che fallisce.
Quando il dispositivo non è il problema
Se il test dell'interfaccia riesce e il client fallisce ancora, il problema rimanente è il profilo, il contesto del servizio o la rotta, e il dispositivo è a posto. È il momento di smettere di modificare i nodi dei dispositivi, perché ulteriori cambiamenti aggiungono solo distanza da un sistema funzionante.
Eseguite gli stessi cinque controlli ogni volta invece di inventarne uno nuovo: il modulo è caricato senza un modulo rivale, il percorso è un dispositivo a caratteri con il major e il minor giusti, le opzioni di montaggio sono pulite, il contesto del processo porta la capacità, e c'è un rifiuto di policy al timestamp del guasto. Un guasto che sopravvive a tutti e cinque non è un problema del dispositivo, qualunque cosa menzioni il messaggio.