Category Archives: Reinventing the wheel

Raspberry Pi netboot

[post in progress]

Promise: this post will not be another rewrite of the official documentation. Here I will deal with issues you might run into but no one of these people actually did because they’re not actually using it.

The company I work for has a very simple need: display on large monitors some data on a web page with huge size fonts in full screen.

Currently we have 20 very old machines, bought as thin clients for VDI and then, when their flash disk (think of an SSD actually slower than a spinning HDD) got fried, converted to netboot via PXE. Not difficult as the CPU is an x86 VIA Eden. The PSU though are a bit custom and only last ~3 years. We bought 20 spare PSU in 2013 and another 20 in 2016;  it’s now 2019 and the PSUs for the now obsolete thin clients are out of production. Time for a change. 

The perfect solution is indeed a Raspberry Pi 3 B+ with PoE HAT – no PSU to fry, netbootable out of the box (with those pesky netboot bugs solved, such as the inability to boot from a TFTP server on a different network), no microSD to take care of, central management (being NFS rooted), small and nice.

First caveat: don’t be a cheapskate, buy the right case. The cost is >3x, but the absolute value is not that much – you can afford 17€ vs 5€.

Second issue: don’t buy a Pi 4 yet: the dramatic changes in the internal structure of the SoC make it so different that the bootstrap firmware is now on an internal 512Kb EEPROM rather than on the boot flash/TFTP server and had to be completely rewritten. However it is still (as of October 2019) lacking the netboot code. Of course you can boot just the kernel and cmdline.txt off a microSD ad NFS mount the root fs, but you’ll end up needing a microSD anyway so part of the advantages in netbooting it would be gone.

Let’s start.

Our x86 thin clients boot off initrd – actually an initramfs – which is nice since the client becomes completely independent from the network, but eats up precious runtime RAM and has size limits. So we decided to go for readonly NFS, with sensible writeable private parts overmounted on tmpfs. The backbone for the old x86 clients is based on DSL; on rPi we’re using dietPi.

Now, if you, like me, live in a structured network you might find that most “tutorials” for netbooting a rPi are nothing more than useless paraphrases of the sparse docs. The point is that you don’t and can’t have a do-it-all dnsmasq; you’ll probably have a centralized DHCP server with failover and relays, a TFTP server, an NFS server, and so on. So here’s some details about setting it all up.

Notes on setting up a NFS filesystem

 

Issue you’ll run into and how to solve them

DHCP on Windows

You can have a Windows DHCP server in lieu of the dnsmasq server or isc-dhcpd all tutorials suggest. This is rather frequent in the real, corporate world. Nothing really fancy to do here, you only need to set up option 66 as a string holding the IP address of the TFTP server. IP address, not hostname: the rPi bootload doesn’t have name resolving capabilities.

TFTP boot

Your TFTP server folder must hold the same content as the boot folder on your SD – that is the FAT32 partition that usually gets mounted under /boot – you can also mount it later but there’s no real need for that as the whole system will be readonly if you’re sane.

The actual files needed to boot a Pi are: bootcode.bin (except for the 4) (which can’t netboot yet anyway but whatever), start.elf, config.txt, (cmdline.txt

It seems there’s no real way to specify a boot path, ie to request files from a custom subfolder rather than from / – so to avoid me

NFS latency

If your NFS server is on a different subnet your NFS packets *will* be fragmented. Period. The default packet size is 8192 and there is no way a rPi can do jumbo frames. Handle fragmentation and your CPU usage will skyrocket. Plus your UDP packets will never go back and forth in the correct order, so retransmissions and timeouts are guaranteed. Just freaking lower the wsize and rsize values, on the kernel command line parameter nfsroot (after the path). You’ll have to modify cmdline.txt for that but I guess you already knew that. Maybe lowering the timeout could help as well, but I measured that just setting the NFS packet size to 1400 does wonders to your NFS performance.

PoE HAT fan

The fun part here is the whole devtree concept.

 

Publish Windows printers to DNS-SD

Problem: given a Windows server with a number of installed printers, publish them to DNS so that they can be used from macos/iOS (et al).

No free software exists AFAIK to automate this trivial but annoying task.

An AWK script can of course process a list of printers into a batch of dnscmd commands.

Usual disclaimers apply: done in 30 minutes, trivial, works with en_us versions only, and so on. I’m not liable for anything, from the script no working to destroying your AD. UAYOR, YMMV, etc.

In short: for a mac/iOS to list printer as Bonjour printers you need broadcast, mDNS or DNS-SD. Which means:

  • five PTR records that “enable” the zone for DNS-SD
  • for every printer, three records in the _printer._tcp.zone.tld subzone:
    • a PTR for the zone that says “there’s a printer with this name”
    • a SRV for the printer’s name that says “and this is its hostname”
    • a TXT for the printer’s name that explains the printer’s capabilities, queue name, and so on.

The printer can be shared as IPP, IPPS, IPP-TLS and, most important, LPD. Windows has had LPD service available since Windows 2000, on server and workstation, and thus that’s simpler than using IPP. My script, having been conceived with Windows in mind, uses LPD. Of course you need to install LPD service and the printers must have been shared. I won’t show you how to do that – it’s trivial and if you need to know how to do it you can google it – or change job altogether.

The script won’t “just work” I guess: the tricky part is the “ty” line in the TXT record, which tells the mac which driver to use. I’m just using the Windows driver’s name, YMMV but I’m positive you’ll have to edit the TXT records if you want the mac to install the printer automatically (and not asking you for the driver to use).

Full specifications on how to fill the TXT record with significant data can be found on Apple’s website. Google for “bonjour priniting specification” site:developer.apple.com

NOTE NOTE NOTE NOTE

might have once again reinvented the wheel. Square. And lost my ability to properly google for stuff. So if you know of a better way to do this, free (no pesky unmantained $29 software to install on a little innocent MS DC) please come forward and tell your story.

Let’s get it on:

  1. Get the list of printers from the server:
    cscript prnmngr.vbs -l -s FQDN.domain.tld > %temp%\prtlist.txt

    (must be run from the folder where the VBS is; in 2008R2 %windir%\system32\printing_admin_scripts\xx-XX – like en-US)

  2. Process the list with AWK. IMPORTANT: if you’re not using AWK on Windows but are moving the list and the script back and forth from/to a *nix machine, you MUST take care of CR/LF conversion.
    ./prnmngr2dnscmd.awk prtlist.txt > dnssd.cmd
  3. Run the script and hope for the best.

The script itself. You need to CHANGE domain.tld with your zone FQDN. Again, I assume you’re not a dummy but a decently experienced sysadmin.

#!/usr/bin/awk -f

BEGIN {
 dom="domain.tld";

 split("b db dr lb t", sr);
 for (rr in sr) printf "dnscmd . /RecordAdd %s %s._dns-sd._udp PTR %s\n", dom, sr[rr], dom;
 }

/^Server name/{
 srv=$3;
 getline name; gsub("Printer name ", "", name);
 n=name; gsub(" ", "_", n);
 getline rp; gsub("Share name ", "", rp);
 getline ty; gsub("Driver name ", "", ty);
 getline;
 getline note; gsub("Location ", "", note);
 product="(" ty ")";
 printf "dnscmd . /RecordAdd %s _printer._tcp PTR %s._printer._tcp.%s\n", dom, n, dom;
 printf "dnscmd . /RecordAdd %s %s._printer._tcp SRV 0 0 515 %s.%s\n", dom, n, srv, dom;
 printf "dnscmd . /RecordAdd %s %s._printer._tcp TXT \"txtvers=1\" \"qtotal=1\" \"rp=%s\" \"ty=%s\" \"note=%s\" \"priority=0\" \"product=%s\" \"printer-state=3\" \"printer-type=0x809056\" \"pdl=application/octet-stream,application/pdf,application/postscript,image/jpeg,image/png\"\n", dom, n, rp, ty, note, product;
}