OpenVPN para com uma queixa errno 2 sobre /dev/net/tun enquanto lsmod lista o módulo tun sem problemas. Ambos os factos são verdadeiros ao mesmo tempo. O módulo fornece um driver, e o caminho que o cliente abre é um nó de dispositivo que algo mais tem de criar. Tratá-los como um único objeto é a razão pela qual a reparação óbvia de recarregar o módulo não muda absolutamente nada.

Dois objetos, dois proprietários diferentes

Um módulo do kernel e um nó de dispositivo são objetos independentes com ciclos de vida independentes. Carregar o módulo regista um driver, e num sistema com devtmpfs o kernel pede que o nó correspondente seja criado. Num sistema sem devtmpfs nada cria nada, e o driver fica ali sem qualquer caminho que aponte para ele. O módulo pode estar carregado, saudável e completamente inalcançável através do sistema de ficheiros no mesmo momento.

É por isso que o texto do erro e a lista de módulos não se contradizem. O cliente abre um caminho, o caminho não existe, e o kernel devolve ENOENT antes de o driver tun ser sequer consultado. Recarregar o módulo não pode reparar um caminho ausente, e criar o caminho não pode reparar um driver ausente.

lsmod | grep -w tun
stat -c "%F %t:%T" /dev/net/tun
grep " /dev " /proc/mounts

devtmpfs contra um mknod explícito

Normalmente dois mecanismos criam esse nó, e saber qual deve ser o responsável diz-lhe o que reparar. O devtmpfs faz com que o kernel crie nós de dispositivo numa instância montada, e o udev aplica propriedade e permissões por cima. Um mknod explícito é o caminho manual para imagens e espaços de nomes onde nenhum dos dois corre no arranque.

Compare resultados em vez de intenções. Se o devtmpfs está montado em /dev e o nó continua ausente, o driver não é o que supõe, ou algo removeu o nó após o arranque. Se /dev é um diretório normal dentro de um rootfs descompactado, não existe qualquer mecanismo automático e quem constrói a imagem tem de criar o nó.

stat -c "%F major=%t minor=%T" /dev/net/tun
# expect: character special file major=a minor=c8

Onde não corre nenhum dos dois mecanismos, o nó tem de ser feito à mão, e a ordem importa. Crie primeiro o diretório pai, porque mknod não o consegue inventar, depois o dispositivo de caracteres, depois um modo que o deixe escrevível apenas pelo root. Recarregue o módulo antes de criar o nó, e remova um caminho existente só depois de ls ter mostrado que é um diretório e não um 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

O tipo reportado por stat é o discriminante mais rápido, e um diretório nesse caminho é a surpresa mais comum em imagens mínimas. Um mkdir bem-intencionado num Dockerfile deixa exatamente isso, e nenhuma quantidade de carregamento de módulos substitui um diretório por um dispositivo de caracteres. Major 10 com minor 200 é o par registado para o tun, expresso em hexadecimal como a e c8.

Quatro vestígios, quatro causas

Quatro causas explicam quase todos os casos, e cada uma deixa um vestígio diferente nos registos. Comece pelo ambiente de arranque. Imagens sem disco, placas integradas e fases de initramfs muitas vezes não montam nem devtmpfs nem um /devgerido pelo udev, pelo que nenhum processo escreve alguma vez o nó. Depois leia as opções de montagem daquilo que aloja /dev, porque nodev permite que o caminho exista mas recusa abri-lo, e uma montagem bind retirada de um anfitrião endurecido transporta a bandeira sem se anunciar. Uma montagem só de leitura falha mais cedo e mais alto: mknod devolve aí EROFS, enquanto EPERM significa que ao processo falta a capacidade e não que o sistema de ficheiros se proteja. Por fim, um runtime de contentores com o seu próprio /dev tmpfs precisa que o nó seja injetado no seu próprio espaço de nomes, e o seu cgroup de dispositivos tem de permitir o par major e minor.

Verifique a camada de política antes de voltar a mudar permissões. O SELinux e o AppArmor podem recusar uma abertura que todos os atributos do ficheiro permitem, e essa recusa aparece no registo de auditoria e não na saída do cliente. Uma mudança de permissões que nunca aparece numa recusa é tempo gasto na camada completamente errada.

sudo journalctl -k -n 20 --no-pager
sudo ausearch -m AVC -ts recent | tail -5

Prove-o com uma interface, não com uma listagem

Uma listagem bem-sucedida prova que um caminho existe e nada mais. Abra um nó tun saudável para leitura e a chamada volta com um erro, porque o dispositivo não tem semântica de leitura e não é um ficheiro normal. Esse erro é aqui o sinal de sucesso, e reconhecê-lo evita que o persiga como uma avaria.

head -c 1 /dev/net/tun
head: error reading '/dev/net/tun': File descriptor in bad state

O teste decisivo cria uma interface, porque exercita o driver, o nó e os seus privilégios num único passo. Se a adição tiver sucesso e a ligação aparecer, os três funcionam. Quando falha, o errno separa as causas: ENOENT significa que não há nenhum nó utilizável, EPERM aponta para a capacidade, as bandeiras de montagem ou a política, e EBUSY significa que outro processo já possui a interface.

sudo ip tuntap add dev tun7 mode tun
ip -brief link show tun7
sudo ip tuntap del dev tun7 mode tun

Execute esse teste no contexto que realmente falha, porque uma unidade de serviço, um contentor ou uma tarefa agendada podem transportar um conjunto de capacidades diferente do da sua shell de início de sessão. Um teste que passa no contexto errado não prova nada sobre o que falha.

Quando o dispositivo não é o problema

Se o teste de interface tiver sucesso e o cliente continuar a falhar, o problema restante é o perfil, o contexto do serviço ou a rota, e o dispositivo está bem. É o momento de parar de editar nós de dispositivo, porque mais alterações só acrescentam distância a um sistema que funciona.

Execute as mesmas cinco verificações de cada vez em vez de inventar uma nova: o módulo está carregado sem um módulo rival, o caminho é um dispositivo de caracteres com o major e o minor corretos, as opções de montagem estão limpas, o contexto do processo transporta a capacidade, e existe uma recusa de política na marca temporal da falha. Uma avaria que sobrevive às cinco não é um problema de dispositivo, diga o que disser a mensagem.

Artigos relacionados