Boot do Raspberry pela rede (BOOTPC)

Fazer o boot do Raspberry pela rede me trouxe saudades.

Em 2005 trabalhava como sysadmin em uma empresa espanhola, mas outras tarefas me eram delegadas. Ajudar a equipe de suporte era uma delas, quando não tinham uma solução para um problema. Na época, instalações de sistemas em mais de 3.000 computadores eram feitas periodicamente utilizando o Norton Ghost com boot pelo disquete. Então, dois operadores eram necessários para liberar a gravação do sistema, pois o servidor ficava em um andar e os computadores a receber a imagem, em diversos andares. Como achei o processo muito burro, idealizei o Phantom; um sistema que iniciou de uma remasterização do knoppix, passou aos moldes de embarcado (com a ajuda do mestre Marcelo Barros) e finalizou seus dias (lá pra 2011) com 14MB e uma interface gráfica feita em Qt. Era um grande sistema, que contava com boot por CD, pendrive, HD externo e boot pela rede. Tinha muitas outras características, mas o artigo não é sobre ele.

Características de boot por PXE no Raspberry

Primeiro, o boot pela rede só é suportado via cabo, utilizando PXE. A ROM procede inicializando o dispositivo ethernet, enviando uma requisição DHCP e, após receber a resposta, segue com o processo advindo do DHCP, que define os caminhos a seguir. Vou deixar claro no decorrer do artigo.

Configuração do OTP

Verifique previamente se já não está suportado por padrão em seu Raspberry, assim não será necessário sequer fazer a modificação a seguir.

Pra começar, precisamos fazer a primeira configuração da OTP. Para isso, adicionamos ao final do arquivo /boot/config.txt o parâmetro program_usb_boot_mode=1 . Depois de um boot do sistema com essa definição, podemos ler  o OTP com o vcgencmd (comando do qual discorri amplamente nesse outro artigo).

vcgencmd otp_dump|egrep '^17:'

O resultado deve ser esse:

Agora já pode desligar o sistema e remover o SD.

Antes de religar o Raspberry, conecte o cabo ethernet. Quando ligado, deve levar um tempo aproximado de 10 segundos para haver manifestação na rede.

Pode ser necessário em algum momento depurar a comunicação para ver como está acontecendo o handshake, ainda que seja apenas para fins didáticos. Nesse caso, recomendo a leitura desse tutorial (muito bem elaborado, por sinal). De qualquer modo, será necessário analisar se está ocorrendo a comunicação, portanto, instale o programa tcpdump:

sudo su
apt-get update
apt-get install -y tcpdump

Um comando inicial poderia ser:

tcpdump -i eth0 -vvv -XX -s9182 port bootpc

Se estiver usando Ubuntu com o novo esquema (podre) de nome de interface, deve ser algo como enp2s0 no lugar de eth0. Para confirmar, preceda o comando anterior identificando sua interface:

ifconfig

Se não tiver o comando ifconfig em sua distribuição Linux (outra mudança podre e inútil), tem duas opções: instale o pacote net-tools para ter o comando ifconfig, ou use o comando ip. No primeiro caso:

sudo apt-get update
sudo apt-get install -y net-tools

E no caso do comando ip:

ip addr show

A saída será numerada e, tirando a interface de loopback (lo) e a WiFi (wlp3s0, wlan0 ou algo do tipo), deve sobrar a interface ethernet.

Usando o comando tcpdump citado mais acima, o resultado deve ser algo como:

Como pegar o serial do Raspberry Pi

Cada Raspberry tem um serial único, que pode ser pego com o comando vcgencmd ou direto no arquivo /proc/cpuinfo, gerado pelo kernel. Para pegar o (número) serial pelo arquivo cpuinfo, faça login no Raspberry e use:

cat /proc/cpuinfo | grep Serial | cut -d ' ' -f 2

E para pegar o (número) serial pelo comando vcgencmd, utilize:

vcgencmd otp_dump|egrep '^28'|cut -f2 -d:

Se quiser saber mais sobre o comando vcgencmd, recomendo o tutorial mais elaborado, exclusivamente sobre os recursos do comando vcgencmd. Se quiser saber em detalhes o significado dos bits do OTP, recomendo a documentação oficial do Raspberry.

Enfim, separe o valor do serial, pois utilizaremos mais adiante. Também vamos precisar do MAC address. Tendo identificado o nome da interface, use o comando a seguir. No Raspberry a interface ainda está com o nome eth0, então:

ifconfig eth0|egrep ether|awk '{print $2}'

O resultado deve ser algo como:

Agora podemos iniciar o processo de configuração.

Setup do servidor TFTP / PXE

Antigamente a única opção era configurar um servidor DHCP e um PXE para fazer a transação com o TFTP. Na época atual temos uma opção que poupa um monte de trabalho, da qual discorro mais adiante.

Instalação de pacotes

Nesse primeiro momento, vamos focar na instalação dos pacotes:

sudo apt-get install -y nfs-kernel-server dnsmasq kpartx

Cópia do sistema de arquivos e boot para o servidor

O Raspberry não tem uma BIOS ou algo do tipo. Quando ligada, simplesmente busca o ponteiro para inicialização em um ou mais lugares, dependendo da versão da placa. Primeiramente, busca pelo inicializador em /boot, no micro SD. No caso da Raspberry Pi 3, busca pelo sistema na rede, caso o micro SD não esteja presente. O que precisamos fazer é disponibilizar o sistema na rede, e isso requer dois caminhos distintos.

Disponibilizar o boot pelo servidor TFTP

Teremos um serviço TFTP rodando no computador servidor. A partir dele será carregado o bootloader no Raspberry que, previamente à carga do sistema raiz, deverá carregar o kernel. Para tal, criamos um diretório que conterá tudo que está em /boot de um sistema do Raspberry. Por convenção, o diretório é criado em /tftpboot. Pode ser em qualquer lugar que desejar, desde que adeque os arquivos de configuração dos serviços.

Para copiar o conteúdo da partição de boot do Raspberry existem várias formas. Particularmente, tomei o caminho mais rápido.

Tire o SD do Raspberry e, em outro Linux, faça a cópia da partição:

sudo mkdir /tftpboot
sudo chown 777 /tftpboot
cp -a /media/<usuario>/bootfs/* /tftpboot

Se quiser ter imagens diferentes para diferentes computadores (no caso, Raspberry), crie o diretório /tftpboot/<serial> (ou /tftpboot/mac ? Não lembro; EXPERIMENTE) e coloque o conteúdo da partição /boot lá dentro.

Disponibilizar o sistema raiz por NFS

Se não conhece, o NFS (Network File System) é um sistema de arquivos em rede. Com ele, podemos fazer compartilhamento de conteúdo como se fosse um compartilhamento SAMBA (ou compartilhamento Windows, se preferir referenciar-se a ele).

Criando um compartilhamento NFS, poderemos colocar o sistema raiz lá dentro e assim, permitir que o Raspberry carregue a raiz como se estivesse lendo de /dev/mmcblk0p2.

Para tal, crie o diretório /nfs/client1 e copie a partição raiz para lá.

sudo su
mkdir -p /nfs/client1
chmod -R 777 /nfs
apt-get update
apt-get install rsync
rsync -xa --progress /media/<usuario>/rootfs/* /nfs/client1/

Configurações iniciais de interface

Garanta que não será servidor IP por DHCP na interface interna. Crie os seguintes arquivos:

/etc/systemd/network/10-eth0.netdev

Coloque esse conteúdo:

[Match]
Name=enp2s0

[Network]
DHCP=no

/etc/systemd/network/11-enp2s0.netdev

Repare que aqui o arquivo tem o nome estético da interface ethernet do computador servidor. Seu conteúdo:

[Match]
Name=enp2s0

[Network]
Address=10.42.0.211/24
DNS=10.42.0.211

[Route]
Gateway=192.168.1.1

Esse gateway nem pertence à mesma rede, mas está aí por estar. O gateway é a porta de saída de uma rede para outra, logo, o IP do gateway haveria de ser o IP da interface que está servindo o boot, exceto se o Raspberry estiver conectado a um switch, que pode lhe prover outro caminho na LAN para que ele tenha uma rota de saída da rede 10.42.0.0/24 para outra rede da LAN ou para a Internet. O caso é que não me importei de fornecer acesso à Internet, pois sendo meu notebook o servidor tftp, também haveria de configurar forwarding e mascaramento. Não é complicado, só não era o intuito, mas se desejar fazer esse roteamento, dando acesso à Internet para o Raspberry que carregou o sistema pelo TFTP, basta ler o artigo de firewall doméstico que escrevi há um bom tempo. Dadas as explicações, sigamos.

/etc/systemd/resolved.conf

Supondo que esteja configurando a rede para navegar, também será necessário especificar um servidor DNS. Tem duas formas de oferecer o recurso, mas vou deixar a cereja do bolo para o final. Por enquanto, considere preencher esse arquivo com o IP do gateway (supondo então 10.42.0.211):

[Resolve]
DNS=10.42.0.211

Reiniciando serviços

Agora é hora de por os serviços para rodar, mas vamos instalar uns pacotes mais.

sudo su
systemctl enable systemd-networkd
reboot
#depois....
sudo su
apt-get install -y dnsmasq
systemctl enable dnsmasq
tcpdump -i enp2s0 port bootpc -vvv -XX -s9182

Aqui eu fiz o seguinte; configurei a estrela desse artigo – o dnsmasq – depois observei a interface de rede, fazendo um boot normal com o Raspberry e configurando a interface ethernet dele por DHCP para ver se o dnsmasq estava cumprindo seu papel. Vou dedicar um artigo exclusivo ao dnsmasq porque ele é demais! Mas por enquanto, vamos nos ater à configuração sem entrar nos detalhes técnicos do serviço.

Edite o arquivo /etc/dsnmasq.d/dnsmasq.conf e deixe seu conteúdo assim:

port=0
dhcp-range=10.42.0.10,10.42.0.12,12h
dhcp-option=3,10.42.0.211
dhcp-option=6,10.42.0.211
log-dhcp
enable-tftp
tftp-root=/tftpboot
pxe-service=0,"RPI Do bit Ao Byte"

Mas não vamos deixar de saber o que significa essa configuração, certo?

Protocolo DHCP e BOOTP

Existe um conjunto consideravelmente grande de regras nesses protocolos, cujas definições podem ser vistas aqui. No parâmetro dhcp-option (definido duas vezes), na primeira dizemos que o gateway é o 10.42.0.211. O código que diz se tratar do gateway é o que precede a vírgula: 3.

Posteriormente dizemos que a resolução de nome vem do mesmo endereço IP, cujo código que diz se tratar disso é o 6.

O dnsmasq, entre outras coisas, é servidor DHCP. Configuramos ele para distribuir IPs entre 10.42.0.1010.42.0.12 com “lease time” de 12h. Isto é, ele não renovará o IP por 12 horas.

Hora de reiniciar o serviço para que ele assuma as configurações:

sudo su
systemctl enable dnsmasq.service
systemctl restart dnsmasq.service

Configurar o servidor NFS

Não podemos nos esquecer de configurar o servidor NFS, senão não tem boot do Raspberry pela rede. Esse é bastante simples:

sudo su
apt-get install -y nfs-kernel-server
echo "/nfs/client1 *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports
echo "/tftpboot *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports
systemctl enable rpcbind
systemctl restart rpcbind
systemctl enable nfs-kernel-server
systemctl restart nfs-kernel-server

Como o Raspberry saberá onde buscar o sistema de arquivos?

Não adianta nada estar servindo um sistema para o boot do Raspberry pela rede se ele não souber de onde ler o sistema raiz. Essa informação não é papel do PXE, BOOTP ou DHCP. Isso já está por conta do sistema operacional, portanto, devemos configurar o sistema raiz que está no servidor para que, após o carregamento do kernel, ele possa saber onde está o restante do sistema operacional. E de onde o sistema operacional lê essa informação? – Fácil, hum? Do arquivo /boot/cmdline.txt. Nesse caso, devemos editar o arquivo /tftpboot/boot/cmdline.txt e deixá-lo assim:

console=serial0,115200 console=tty1 root=/dev/nfs nfsroot=10.42.0.211:/nfs/client1,vers=4.1,proto=tcp rw ip=dhcp rootwait elevator=deadline

Lembre-se de acertar o IP. Além disso, o boot do Raspberry pela rede será lido das definições de seus arquivos de configuração de sistema, então se estiver lendo de /tftboot/ ou /tftpboot/<serial>/boot/ ou outro nível, o sistema não iniciará. O diagnóstico é relativamente rápido; e óbvio até.

Um último ponto a considerar é a tabela de partições. Quando o sistema operacional inicia, o sistema de arquivos é montado conforme as definições que se encontram no arquivo /etc/fstab. Se estiver apontando para o UUID ou para o dispositivo de bloco diretamente, o sistema não iniciará. Desse modo, antes de fazermos qualquer teste, devemos modificar a tabela de partições no arquivo /etc/fstab. Lembre-se que, no servidor o sistema raiz ficou hospedado em /nfs/client1. Edite o arquivo /nfs/client1/etc/fstab e deixe-o assim:

proc            /proc           proc    defaults          0       0
# a swapfile is not a swap partition, no line here
#   use  dphys-swapfile swap[on|off]  for that
10.42.0.211:/tftpboot /boot nfs defaults,vers=4.1,proto=tcp 0 0

Mais uma vez, atente-se ao IP. Um erro bobo poderá lhe causar uma bela dor de cabeça.

Só para garantir que todos os serviços serão reiniciados, talvez seja desejável reiniciar o sistema do servidor. Ao voltar, faremos uso do comando anunciado precocemente. Não se esqueça de configurar o IP 10.42.0.211 (ou a rede que disponibilizou para essa tarefa, seja uma subnet da classe A 10.0.0.0/24, 172.16.0.0/14 da classe B ou alguma rede da classe C, desde que não conflite com a rede que está utilizando para ler esse artigo). No terminal:

tcpdump -i enp2s0 -vvv -XX -s9182 port bootpc or port 69

Quando iniciar o boot do Raspberry pela rede, deverá ver informações assim:

Boot do Raspberry pela rede
Boot do Raspberry pela rede

Vou explicar algumas coisas dessa mensagem, mas não deixe de ler o artigo sobre tcpdump para ter um pouco mais de intimidade.

O início do handshake do boot do Raspberry pela rede é feito na camada de enlace de dados, onde o MAC se anuncia, esperando que um servidor DHCP lhe ofereça um IP, para então subir para a layer 3 (camada de rede). Esse anúncio pode ser visto bem no começo da segunda linha em 0.0.0.0.bootpc > 255.255.255.255.bootps, onde o boot client faz um broadcast em busca de um boot server, anunciando seu MAC. Essa não é a primeira mensagem, é a dissolução. A primeira mensagem tem esse formato:

Boot do Raspberry pela rede
Boot do Raspberry pela rede

Mas isso é só um anúncio padrão, que vai acontecer de qualquer modo. Ele pode ser útil para saber se seu Raspberry está com o suporte a bootpc, mas se seguiu o procedimento inicial para verificar o OTP, já deverá saber a resposta antes de chegar nesse ponto do artigo. Continuando da mensagem anterior, temos o anúncio (ainda da camada de enlace) que segue com [udp sum ok], sendo um bom sinal de que houve um handshake adequado. O MAC é passado mais uma vez e então outras características são informadas, mas o que importará nesse momento é ver que Your-IPServer-IP estão preenchidos com os IPs configurados. Daí segue uma série de outros parâmetros, dos quais podemos contemplar as opções citadas anteriormente; 3 e 6, para gateway e DNS, respectivamente.

O texto é longo, mas o processo é curto. Entendendo-o, na segunda vez será rápido!

Boot do Raspberry pela rede na versão 4

Para o Raspberry Pi 4 será necessário algumas modificações, então não vou escrever a respeito agora porque o importante aqui é focar no servidor.

Onde comprar Raspberry?

E se pretende comprar Raspberry, ficam como sugestões os parceiros do blog:

Saravati – com diversas opções de Raspberry.

CurtoCircuito – para Raspberry Pi 3.

MASUGUX – Também com diversas opções de Raspberry.

Vídeo

Fiz um vídeo simples apenas para mostrar o sistema “vivo”, enquanto narro com uma bela enxaqueca, então, releve. Veja em nosso canal DobitaobyteBrasil, no Youtube. Inscreva-se e clique no sininho para receber notificações e, deixe seu like!

Espero que tenha gostado do artigo e até a próxima!

 

Revisão: Ricardo Amaral de Andrade

Djames Suhanko

Djames Suhanko é Perito Forense Digital. Já atuou com deployer em sistemas de missão critica em diversos países pelo mundão. Programador Shell, Python, C, C++ e Qt, tendo contato com embarcados ( ora profissionalmente, ora por lazer ) desde 2009.