Подключение к работающему nos
Доступ к шеллу ядра через последовательную консоль, UDP-шелл или SSH и трансляция лога ядра на вашу машину через netconsole.
До работающего 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 потерял датаграммы по дороге. Машина в этом месте не останавливалась.
Что дальше
- Отладка nos с GDB: паники, уровни трассировки, машина без последовательного порта
- Установка nos на выделенный сервер — там сеть единственная консоль
- Загружаемый модуль ядра на Rust
- Linux-гость под гипервизором nos:
hv attachподключает к консоли гостя черезssh -t - Полные справочники: UDP remote shell, sshd, Netconsole