nos_
ENRU

Подключение к работающему nos

Доступ к шеллу ядра через последовательную консоль, UDP-шелл или SSH и трансляция лога ядра на вашу машину через netconsole.

8 мин чтенияОбновлено

До работающего nos можно достучаться четырьмя способами, и на машине без последовательного порта остаются только три сетевых. В итоге вы попробуете каждый из них на nos, запущенном в QEMU: поработаете в консоли, выполните команды через UDP-шелл, войдёте по SSH и получите лог ядра через netconsole.

Канал Что передаёт Как включить Кому доступен
Последовательная консоль шелл и лог загрузки включена всегда; куда идёт порт, решает QEMU тому, у кого есть консоль
UDP-шелл команды шелла, по одному ответу на каждую udpshell=PORT в командной строке ядра любому, кто может послать машине датаграмму
SSH шелл с терминалом или без, либо одну команду модуль sshd владельцам разрешённых вами ключей Ed25519
Netconsole лог ядра, в одну сторону netconsole=ip:port в командной строке ядра любому, кто получает датаграммы; ничего не шифруется

Понадобится:

  • собранный nos, который загружается в QEMU;
  • Python 3 для двух клиентов, scripts/udpsh.py и scripts/netconsole.py;
  • для SSH — клиент OpenSSH и ключ Ed25519 (его создаёт ssh-keygen -t ed25519). Другие типы ключей sshd в nos не принимает.

Последовательная консоль

Где находится консоль, зависит от скрипта:

Скрипт Где печатать Куда идёт последовательный порт
qemu.sh в окне QEMU в nos.log
qemu-disk.sh в вашем терминале (-nographic) в ваш терминал
run.sh в VNC-клиенте на дисплее :0 в nos.log
qemu-arm64.sh негде; используйте UDP-шелл или SSH в nos-arm64.log

Последовательный порт передаёт лог загрузки с первой строки. Когда запускается шелл, трассировка перестаёт выводиться в консоль, и там остаётся только вывод самого шелла; остальное по-прежнему доступно через dmesg. console=serial или console=vga оставляет шелл только на одной из двух консолей; образ диска загружается с console=serial.

UDP-шелл

UDP-шелл выполняет любую команду для любого, кто может послать машине датаграмму, без шифрования и без аутентификации. Поэтому он выключен по умолчанию во всех конфигурациях. Используйте его в частной сети виртуальной машины и никогда — на порту, доступном из интернета.

Его запускает параметр ядра udpshell=PORT. У скрипта для arm64 есть для этого переменная, и он пробрасывает в гостя UDP-порт 9000 хоста:

NOS_UDPSHELL=9000 ./scripts/qemu-arm64.sh

Когда в логе появится boot: complete, подключитесь из другого терминала:

python3 scripts/udpsh.py 127.0.0.1
nos udp shell -> 127.0.0.1:9000
$ uptime

На x86-64 UDP-порт 9000 пробрасывает скрипт загрузки с диска. Добавьте параметр в пункт GRUB на диске, в build/grub-disk.cfg:

-	multiboot2 /boot/kernel64.elf console=serial dhcp=auto dns=on root=auto
+	multiboot2 /boot/kernel64.elf console=serial dhcp=auto dns=on root=auto udpshell=9000

Затем пересоберите диск, загрузите его и подключитесь:

./scripts/build-disk.sh
./scripts/qemu-disk.sh
python3 scripts/udpsh.py 127.0.0.1

Правки на этой странице меняют файлы под контролем версий (build/grub-disk.cfg, build/grub.cfg, scripts/qemu-arm64.sh), так что по окончании откатите их.

Клиент вызывается как udpsh.py <host> [port] [timeout]; по умолчанию порт 9000, таймаут 30 секунд. Выход — Ctrl-D.

  • Ответ вмещает не больше 32 КБ. Всё сверх этого обрезается с пометкой [output truncated]. Лог ядра хранит 2048 сообщений, поэтому запрашивайте его конец: dmesg 40.
  • Команде, которая работает дольше таймаута, например прогону blkload или долгому ping, нужен таймаут побольше: python3 scripts/udpsh.py 127.0.0.1 9000 90.
  • По таймауту клиент выводит [timeout, reconnecting] и начинает заново. Если часть датаграмм ответа потерялась, он выводит то, что дошло, и называет пропавшие фрагменты.
  • loglevel N повышает уровень трассировки работающего ядра: так видно подсистему, которая пишет в лог выше уровня по умолчанию, без пересборки.

На сервере, развёрнутом из системного образа, UDP-шелл включает nos-install --udpshell[=PORT] (см. «Установка nos на выделенный сервер»).

SSH

sshd — загружаемый модуль, который отдаёт тот же шелл по SSH. Он использует обмен ключами curve25519-sha256, ключи хоста и пользователей ssh-ed25519 и шифр chacha20-poly1305@openssh.com. Каждое соединение получает один сеанс: шелл с терминалом или без либо одну команду. Всё остальное отклоняется: sftp, а значит, и современный scp, проброс портов, проброс агента и X11. Войти с разрешённым ключом можно под любым именем пользователя. qemu.sh, qemu-disk.sh и qemu-arm64.sh пробрасывают порт 2222 хоста на порт 22 гостя.

Запуск sshd при загрузке

При загрузке insmod никто не выполняет, если этого не просит /etc/rc. Каждая строка этого файла — команда шелла, и задача шелла выполняет их один раз, после того как DHCP настроил сеть. Поэтому корневая ФС, на которой лежат модуль, ваш ключ и этот файл, поднимается сразу с работающим SSH. Для x86-64:

mkdir -p rootdir/etc/ssh rootdir/lib/modules
cp out/x86_64/modules/sshd.ko rootdir/lib/modules/
cp ~/.ssh/id_ed25519.pub rootdir/etc/ssh/authorized_keys
printf 'insmod /lib/modules/sshd.ko\nsshd start\n' > rootdir/etc/rc
./scripts/mkrootfs.sh root.img 64 rootdir

Загрузите ISO с этим образом в качестве корневого диска. Это та же машина, которую загружает тест sshd:

qemu-system-x86_64 -display none -m 1G -smp 4 -cdrom nos.iso -serial file:nos.log \
    -drive file=root.img,format=raw,id=drive0,if=none \
    -device virtio-blk-pci,drive=drive0,disable-legacy=on,disable-modern=off \
    -device virtio-net-pci,netdev=net0,disable-legacy=on,disable-modern=off \
    -netdev user,id=net0,hostfwd=tcp::2222-:22 -device virtio-rng-pci

Когда в nos.log появится sshd: listening on port 22, войдите:

ssh -p 2222 root@127.0.0.1
nos -- `help` lists the commands, `exit` leaves

Чтобы выполнить одну команду: ssh -p 2222 root@127.0.0.1 uptime.

Тот же каталог подходит и для образа диска: выполните ROOTFS=rootdir ./scripts/build-disk.sh, затем ./scripts/qemu-disk.sh. Для arm64 соберите каталог с модулем arm64 и превратите его в диск, который загружает скрипт для arm64:

mkdir -p rootdir-arm64/etc/ssh rootdir-arm64/lib/modules
cp out/aarch64/modules/sshd.ko rootdir-arm64/lib/modules/
cp ~/.ssh/id_ed25519.pub rootdir-arm64/etc/ssh/authorized_keys
printf 'insmod /lib/modules/sshd.ko\nsshd start\n' > rootdir-arm64/etc/rc
./scripts/mkrootfs.sh root-arm64.img 64 rootdir-arm64
qemu-img convert -f raw -O qcow2 root-arm64.img nos-arm64.qcow2
./scripts/qemu-arm64.sh

Сервер, развёрнутый из системного образа, стартует так же: его /etc/rc — это те же две строки, а ключ добавляет nos-install --ssh-key.

Загрузка sshd вручную

В работающем nos insmod загружает .ko с любой смонтированной файловой системы, а wget может скачать его туда с веб-сервера на вашей машине. Из пользовательской сети QEMU хост доступен по адресу 10.0.2.2. Включите UDP-шелл (в этом примере — на arm64) и раздайте модули с хоста:

python3 -m http.server 8000 --bind 127.0.0.1 --directory out/aarch64/modules

Затем в udpsh.py:

$ wget http://10.0.2.2:8000/sshd.ko /sshd.ko
$ insmod /sshd.ko
$ sshd allow ssh-ed25519 AAAA... you@laptop
$ sshd start

insmod отвечает module: sshd loaded at 0xFFFF8000.... sshd allow принимает целиком строку из файла .pub и дописывает ключ в /etc/ssh/authorized_keys. Первый sshd start создаёт ключ хоста в /etc/ssh/ssh_host_ed25519_key, а затем сообщает, где слушает:

sshd: made a host key, /etc/ssh/ssh_host_ed25519_key
sshd: listening on port 22 -- eth0 10.0.2.15 -- host key SHA256:... (ED25519), 1 authorized keys

Проверьте, что этот отпечаток совпадает с тем, который ssh покажет при первом подключении. Чтобы sshd снова запускался после перезагрузки, выполните rc add insmod /sshd.ko и rc add sshd start. Это помогает только на корневой ФС, которая переживает перезагрузку; см. Starting it at boot.

Команда sshd

Команда Что делает
sshd показывает, что он обслуживает, его счётчики и кто вошёл
sshd start [port] [nic=eth0] начинает слушать — на порту 22, если не указан другой
sshd stop закрывает порт и завершает все сеансы
sshd allow <ssh-ed25519 AAAA... [comment]> разрешает ключ
sshd deny <SHA256:...> удаляет ключ по отпечатку
sshd keys [reload] показывает ключи; reload перечитывает файл

Ключ хоста и разрешённые ключи живут столько же, сколько корневая ФС. На корне в ramfs они пропадают при следующей загрузке вместе с /etc/rc, ключ хоста меняется, и ssh отказывается подключаться, потому что ключ изменился. Для одноразовой виртуальной машины проверку можно пропустить, как делает тест sshd:

ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@127.0.0.1

Модуль должен быть из того же дерева или того же релиза, что и ядро. Загрузчик отвергает модуль, собранный под другой интерфейс ядра (см. What a module may call).

Netconsole

Параметр ядра netconsole=ip:port отправляет лог ядра UDP-сборщику по мере появления строк. Поток начинается с первой строки загрузки, потому что строки буферизуются, пока у сети нет адреса. Раз адрес нужен, используйте параметр вместе с dhcp=auto. Запустите сборщик:

python3 scripts/netconsole.py

Он сообщает netconsole: listening on 0.0.0.0:6666. В пользовательской сети QEMU хост — это 10.0.2.2, а исходящему UDP проброс портов не нужен, так что параметр — netconsole=10.0.2.2:6666. Для arm64 добавьте его в scripts/qemu-arm64.sh:

-APPEND="dhcp=auto dns=on root=auto"
+APPEND="dhcp=auto dns=on root=auto netconsole=10.0.2.2:6666"

Для ISO на x86-64 добавьте его в build/grub.cfg и пересоберите nos.iso:

-	multiboot2 /boot/kernel64.elf dhcp=auto dns=on root=auto fstest=on
+	multiboot2 /boot/kernel64.elf dhcp=auto dns=on root=auto fstest=on netconsole=10.0.2.2:6666

Сборщик ставит перед каждой строкой время её прихода (в секундах с момента запуска сборщика). Пропуск в порядковых номерах датаграмм он отмечает строкой --- netconsole: N datagram(s) lost, seq A..B ---, а также сообщает о перезапуске отправителя. Так можно отличить лог, который оборвала сеть, от машины, которая остановилась. Его параметры:

Параметр Что делает
-p PORT слушает порт PORT (по умолчанию 6666)
-b ADDR привязывается к адресу ADDR (по умолчанию 0.0.0.0)
-o FILE дополнительно дописывает каждую строку в FILE
--from IP показывает строки только от этого отправителя
--no-stamp убирает отметку времени
--show-source всегда добавляет адрес отправителя
  • nctail=N отправляет только последние N КиБ строк, накопленных до появления связи. Если машина умирает вскоре после того, как поднялась сеть, оставшееся у неё время уходит на строки, ближайшие к сбою.
  • Поток — это лог ядра, те же строки, что хранит dmesg, а не эхо шелла. Команда шелла netconsole показывает адресата, сколько байт в буфере, а также счётчики отброшенного и отправленного.
  • Отчёт о панике тоже отправляется сборщику, но по возможности: только если MAC-адрес сборщика уже есть в кэше ARP.
  • На сервере, развёрнутом из системного образа, используйте nos-install --netconsole IP:PORT.

Лог уходит без шифрования любому, у кого этот адрес. Используйте netconsole только в сети, которой доверяете.

Решение проблем

  • udpsh.py раз за разом выводит [timeout, reconnecting]. Ядро загружено без udpshell=, порт не проброшен (qemu.sh не пробрасывает ни одного UDP-порта) или команда выполняется дольше таймаута клиента. Для медленной команды передайте больший таймаут третьим аргументом.
  • ssh подключается, но сеанс так и не начинается. Проверьте через консоль или UDP-шелл: lsmod покажет, загружен ли sshd, sshd — слушает ли он и сколько у него ключей, а dmesg 60 rc: — что сделал /etc/rc. Без разрешённого ключа sshd start сообщает sshd: nobody can log in yet, а ключ не типа ssh-ed25519 не принимается никогда.
  • insmod отвергает модуль как собранный под другой интерфейс ядра. .ko и ядро собраны из разных деревьев. Пересоберите оба или возьмите оба из одного релиза.
  • Сборщик ничего не получает. У ядра нет адреса, потому что оно загружено без dhcp=auto, или сборщик слушает другой порт. До окончания DHCP ничего не отправляется.
  • Сборщик выводит строки --- netconsole: ... lost ---. UDP потерял датаграммы по дороге. Машина в этом месте не останавливалась.

Что дальше