OpenVPN запира со errno 2 поплака за /dev/net/tun додека lsmod мирно го наведува tun модулот. Двата факти се точни во исто време. Модулот обезбедува драјвер, а патеката што клиентот ја отвора е јазол на уред што нешто друго мора да го создаде. Третирањето на двете како еден објект е причината зошто очигледната поправка со повторно вчитување на модулот не менува апсолутно ништо.

Два објекта, два различни сопственици

Модулот на јадрото и јазолот на уредот се независни објекти со независни животни циклуси. Вчитувањето на модулот регистрира драјвер, и во систем со devtmpfs јадрото бара да се создаде соодветниот јазол. Во систем без devtmpfs ништо ништо не создава, и драјверот седи таму без ниту една патека што покажува кон него. Модулот може истовремено да биде вчитан, здрав и целосно недостапен преку системот на датотеки.

Затоа текстот на грешката и списокот на модули не си противречат. Клиентот отвора патека, патеката не постои, и јадрото враќа ENOENT пред tun драјверот воопшто да биде прашан. Повторното вчитување на модулот не може да поправи патека што недостасува, а создавањето на патеката не може да поправи драјвер што недостасува.

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

devtmpfs наспроти експлицитен mknod

Обично два механизми го создаваат тој јазол, и знаењето кој треба да биде одговорен ви кажува што да поправите. devtmpfs му дозволува на јадрото да создава јазли на уреди во монтирана инстанца, а udev применува сопственост и дозволи врз нив. Експлицитен mknod е рачната патека за слики и именски простори каде ниту еден од двата не работи при подигање.

Споредете резултати, а не намери. Ако devtmpfs е монтиран на /dev и јазолот сè уште недостасува, драјверот не е тој што го претпоставувате, или нешто го отстранило јазолот по подигањето. Ако /dev е обична директорија во отпакуван rootfs, нема автоматски механизам и тој што ја гради сликата мора да го создаде јазолот.

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

Каде што не работи ниту еден механизам, јазолот мора да се направи рачно, и редоследот е важен. Прво создадете ја родителската директорија, бидејќи mknod не може да ја измисли, потоа знаковниот уред, потоа режим што го остава запишлив само за root. Вчитајте го модулот повторно пред да го создадете јазолот, и отстранете постоечка патека само откако ls покажал дека тоа е директорија, а не уред.

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

Најбрзиот разликувач е типот што го пријавува stat и директорија на таа патека е најчестото изненадување во минималните слики. Добронамерен mkdir во Dockerfile остава точно тоа, и никакво вчитување на модули не заменува директорија со знаковен уред. Major 10 со minor 200 е регистрираниот пар за tun, изразен хексадецимално како a и c8.

Четири траги, четири причини

Четири причини објаснуваат речиси секој случај, и секоја остава различна трага во дневниците. Започнете со околината за подигање. Сликите без диск, вградените плочи и фазите на initramfs често не монтираат ниту devtmpfs, ниту /devуправуван од udev, па ниту еден процес никогаш не го запишува јазолот. Потоа прочитајте ги опциите за монтирање на она што го хостира /dev, бидејќи nodev дозволува патеката да постои, но одбива да ја отвори, а bind монтирањето земено од зајакнат домаќин ја носи оваа знаменце без да се најави. Монтирањето само за читање пропаѓа порано и погласно: mknod таму враќа EROFS, додека EPERM значи дека на процесот му недостига способност, а не дека системот на датотеки се штити. Конечно, контејнерски runtime со свој /dev tmpfs бара јазолот да биде вбризган во неговиот сопствен именски простор, а неговата cgroup за уреди мора да го дозволи парот major и minor.

Проверете го слојот на политика пред повторно да менувате дозволи. SELinux и AppArmor можат да одбијат отворање што секој атрибут на датотеката го дозволува, и тоа одбивање се појавува во ревизорскиот дневник, а не во излезот на клиентот. Промена на дозвола што никогаш не се појавува во одбивање е време потрошено на сосема погрешен слој.

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

Докажете го со интерфејс, не со список

Успешниот список докажува дека патеката постои и ништо повеќе. Отворете здрав tun јазол за читање и повикот се враќа со грешка, бидејќи уредот нема семантика за читање и не е обична датотека. Таа грешка е овде сигналот за успех, и нејзиното препознавање ве спречува да ја бркате како дефект.

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

Одлучувачкиот тест создава интерфејс, бидејќи во еден чекор ги вежба драјверот, јазолот и вашите привилегии. Ако додавањето успее и врската се појави, сите три работат. Кога не успее, errno ги одделува причините: ENOENT значи дека нема употреблив јазол, EPERM покажува на способност, знаменца за монтирање или политика, а EBUSY значи дека друг процес веќе го поседува интерфејсот.

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

Извршете го тој тест во контекстот што навистина пропаѓа, бидејќи услужна единица, контејнер или закажана задача можат да носат различен сет способности од вашата пријавна обвивка. Тест што поминува во погрешен контекст не докажува ништо за оној што пропаѓа.

Кога уредот не е проблемот

Ако тестот на интерфејсот успее, а клиентот сè уште пропаѓа, преостанатиот проблем е профилот, контекстот на услугата или маршрутата, и уредот е во ред. Тоа е моментот да престанете да уредувате јазли на уреди, бидејќи понатамошните промени само додаваат растојание од систем што работи.

Секој пат извршете ги истите пет проверки наместо да измислувате нова: дали модулот е вчитан без конкурентен модул, дали патеката е знаковен уред со точниот major и minor, дали опциите за монтирање се чисти, дали контекстот на процесот ја носи способноста, и дали има одбивање на политика во временската ознака на дефектот. Дефект што ги преживува сите пет не е проблем на уредот, што и да спомене пораката.

Поврзани статии