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, ни управляемый udev /dev, поэтому ни один процесс никогда не записывает узел. Затем прочитайте параметры монтирования того, что содержит /dev, потому что nodev разрешает пути существовать, но отказывает в его открытии, а bind-монтирование, взятое с усиленного хоста, несёт этот флаг, не объявляя себя. Монтирование только для чтения падает раньше и громче: mknod возвращает там EROFS, тогда как EPERM означает, что процессу не хватает возможности, а не то, что файловая система защищает себя. Наконец, контейнерному рантайму с собственным /dev tmpfs нужно, чтобы узел был внедрён в его собственное пространство имён, а его device 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, чисты ли параметры монтирования, несёт ли контекст процесса нужную возможность, и есть ли отказ политики на отметке времени сбоя. Неисправность, пережившая все пять, не является проблемой устройства, что бы ни упоминало сообщение.

Похожие статьи