Tag: Linux

PXE on vanilla ASUSwrt firmware

As a previous dd-wrt user, I love the idea of adding services to my home router, but just lacked the time to commit to moving from ASUSwrt to merlin.

Since ASUSwrt uses dnsmasq for DNS and DHCP and offers telnet login by default, adding the necesary configurations for PXE was relatively simple, albeit it took some tinkering.

Most PXE-live implementations rely on read-write access to a filesystem via NFS – but I was after something that was truly read-only.

The setup:

  • DHCP server PXE options via dnsmasq
  • TFTP server via dnsmasq
  • tftp-root folder on a mounted USB-drive
  • Full ISOs (live-filesystems, etc.) via HTTP – on a separate server
  • lpxelinux.0 – for loading kernel/initrd via HTTP (/usr/lib/PXELINUX/lpxelinux.0)
  • syslinux64.efi – for x64 EFI boot support (/usr/lib/SYSLINUX.EFI/efi64/syslinux.efi)

OS’ supporting live-boot over PXE/HTTP

Debian was by far the most PXE-friendly OS to get running. Kali took some doing, but I got there eventually – thanks to this post and kali-linux-2017.1-amd64.iso!

I was also able to get debian working with using debian-live-9.3.0-amd64-lxde.iso

RAW-iso mode worked for getting Arch Linux (archlinux-2018.01.01-x86_64.iso @ 522MB) and TinyCore (Core-current.iso @ 12MB) booted (command-line only) – see the append arguments in ‘default’, below.

For some reason, even though Ubuntu, Lubuntu and Tails are all based on Debian, I was not able to get these to ‘fetch’ the filesystem over HTTP at all (vanilla iso); even trying to load the iso into memory ‘raw’ failed to find the live filesystem once the initramfs loaded…

I was also unsuccessful at getting Puppy Linux to boot from PXE (failed to find/mount the filesystem), despite following (similar) instructions from a few different places on how to rebuild the initrd to include the filesystem.

UEFI + PXE -friendly OS’ (13-Jan edit)

After a good while of tinkering, I found the following post in a discussion on the ArchLinux wiki:

The currently generated archiso file does not contain the required syslinux files for booting a PXE image. I have filed a bug for this at https://bugs.archlinux.org/task/50188

Bummer…

Custom dnsmasq options

Fortunately, customising the dnsmasq configuration is as easy as adding additional options (1 per line) to /etc/dnsmasq.conf  & restarting the service (killall dnsmasq && dnsmasq –log-async -C /etc/dnsmasq.conf). Lower down I share the script I built to apply (new) options and restart the service with one incantation.

The dnsmasq options I added were:

dhcp-boot=lpxelinux.0 default image
enable-tftp
tftp-root=/mnt/sdxX/<tftp-root>
dhcp-option=vendor:PXEClient,6,2b “kill multicast”
tftp-no-blocksize (not sure if this is necessary)
log-dhcp outputs to /tmp/syslog.log
pxe-prompt=PXE-Booting,5 (not sure if this is necessary)
dhcp-match=set:bios-x86_64,option:client-arch,0 detect BIOS machines
dhcp-boot=tag:bios-x86_64,lpxelinux.0 assign bootfile for BIOS
dhcp-match=set:efi-x86_64,option:client-arch,7 detect EFI machines
dhcp-match=set:efi-x86_64,option:client-arch,9 detect EFI machines
dhcp-boot=tag:efi-x86_64,syslinux64.efi assign bootfile for EFI
pxe-service=X86PC,lpxelinux.0,lpxelinux.0 assign bootfile for BIOS
EDIT (13-Jan): Corrected the dnsmasq EFI-detection/assignemnt options based on this git project.
EDIT (20-Jan): Had to fall-back to BIOS-only to make sure everything works. (EFI line removed are struck-out, above)

minimal tftp-root filestructure:

<tftp-root>/
ldlinux.c32
lpxelinux.0
pxelinux.0
syslinux64.efi
ldlinux.e64
pxelinux.cfg/
default
menu.msg
bootimage/
dos622.img
dosnet.img
memdisk
memtest-kali
memtest86p-5.01.bin
vmlinuz-kali
initrd-kali.img

With the upgrade to lpxelinux.0, I was able to eliminate all vmlinuz and initrd images from the TFTP server, instead pulling them from the HTTP server directly (read: much more quickly, too!). The kali vmlinuz/initrd entries are noted as an excercise for the reader.

Theoretically, one could also verify the checksum of the mounted .iso/squashfs to ensure no modifications had been injected, but I digress.

‘menu.msg’ contents

This text-file gets displayed by [l]pxelinux.0, and is meant to present the user with options provided by the PXE-server. Put in as little or as much as you want!

my pxelinux.cfg/menu.msg:

Local Boot options
 - local (boot from local storage)

PXE-BOOT Options
 > Tools
 - dos (dos 6.22)
 - dosnet (dos with networking support)
 - memtest (memtest86+ v5)
 - memtest-kali (memtest86+ v5)
> Live ISOs
 - kali (Kali in forensic-live mode)
 - debian (Debian in live-mode)

‘default’ contents

These instruct [l]pxelinux.0 which kernel/initrd/etc. files should be pulled (and from where), based on user-input (or lack thereof).

All paths are relative to <tftp-root> – ‘http://‘ paths (for kernel and initrd) are only supported with lpxelinux.0 (not pxelinux.0)

The ‘fetch’ option tells the initrd to download the filesystem from the provided path. if ‘httpfs’ is used with the same path

my ‘default’:

serial 0 115200
default local
timeout 150
prompt 1
display pxelinux.cfg/menu.msg

label local
  localboot 0

## Tools
label dos
  kernel bootimage/memdisk
  append initrd=bootimage/dos622.img
label dosnet
  kernel bootimage/memdisk
  append initrd=bootimage/dosnet.img
label memtest
  kernel bootimage/memdisk append initrd=bootimage/memtest86p-5.01.bin
label memtest-kali
  kernel bootimage/memtest-kali

## Live ISOs
label kali
  kernel bootimage/vmlinuz-kali
  append initrd=bootimage/initrd-kali.img edd=off boot=live noconfig=sudo username=root hostname=klive device=eth0 noswap components fetch=http://<HTTP-server IP>/pxe/kali/live/filesystem.squashfs
  IPAPPEND 2
label debian
  #kernel bootimage/vmlinuz-debian
  kernel http://<HTTP-server IP>/pxe/debian/live/vmlinuz-4.9.0-4-amd64
  append initrd=http://<HTTP-server IP>/pxe/debian/live/initrd.img-4.9.0-4-amd64 boot=live components fetch=http://<HTTP-server IP>/pxe/debian/live/filesystem.squashfs
  IPAPPEND 2

## Direct ISO
label tc
  kernel bootimage/memdisk
  append initrd=http://192.168.6.248/pxe/tinycore.iso iso raw
label arch
  kernel bootimage/memdisk
  append initrd=http://192.168.6.248/pxe/archlinux.iso iso raw

The notice how the kali entries instruct lpxelinux.0 to pull vmlinuz/initrd via TFTP, while the debian ones pull directly from the HTTP server.

ISOLINUX ‘append’ options – as a reference (from their respective iso’s .cfg files):

Debian (isolinux.cfg)
#LABEL English (en)
  linux /live/vmlinuz-4.9.0-4-amd64
  APPEND initrd=/live/initrd.img-4.9.0-4-amd64 boot=live components locales=en_US.UTF-8
Kali (live.cfg)
#label live-forensic
  append boot=live noconfig=sudo username=root hostname=kali noswap
#label live-persistence
  append boot=live noconfig=sudo username=root hostname=kali persistence

HTTP-server files:

The HTTP server basically has a mount folder for each ISO: Kali and Debian, serving all files (including vmlinuz, initrd and filesystem.squashfs)

When using lpxelinux.0 (or gpxelinux.0) vmlinuz and initrd can also be loaded via HTTP, which is much quicker than doing so via TFTP.

Pulling it all together

IMHO, the key advantages of merlin over ASUSwrt would (could) have been HTTP-server hosted on the router and reboot-resilience; As things stand, if my router reboots (it’s on a UPS, so a very rare occurrence), I need to reapply the settings to /etc/dnsmasq.conf and resart the service. Fortunately, I am the only one using PXE at home, so connecting via telnet and running the script is a trivial operation.

The script itself is rather straightforward (for anyone with a little BASH experience) – and there are probably some inefficiencies, but I can take constructive criticism 🙂

#!/bin/sh

default_conf="/etc/dnsmasq.conf"

local_pxe_options='dhcp-boot=lpxelinux.0
enable-tftp
tftp-root=/mnt/sda1/tftpboot
#kill_multicast
dhcp-option=vendor:PXEClient,6,2b
tftp-no-blocksize
log-dhcp
#pxe-prompt=PXE-Booting,5
#PXEClient:Arch:00000
pxe-service=X86PC,lpxelinux.0,lpxelinux.0
#PXEClient:Arch:00007
#pxe-service=BC_EFI,syslinux64.efi,syslinux64.efi
#PXEClient:Arch:00009
#pxe-service=X86-64_EFI,syslinux64.efi,syslinux64.efi
#modified_by_pxe-enable_bios.sh'

usb_mountpoint="/tmp/mnt/sda1"
usb_mount_str="/dev/sda1 on /tmp/mnt/sda1 type vfat"

# Check USB is mounted
if $(mount | grep -qi "$usb_mount_str"); then
        cd ${usb_mountpoint}/tftpboot
else
        echo "Error: USB not mounted - exiting(1)"
        exit 1
fi

# check/apply dnsmaq config
dnsmasq_ps=$(ps w | grep dnsmasq | grep -v grep)
dnsmasq_args=$(echo "$dnsmasq_ps" | sed 's/^.*dnsmasq //g')
if ! $(echo "$dnsmasq_args" | grep -qi ".conf"); then
        echo "no .conf specified: \"$dnsmasq_args\" (assuming \"/etc/dnsmasq.conf\")"
        dnsmasq_conf_file="/etc/dnsmasq.conf"
else
        setopt shwordsplit
        for word in $dnsmasq_args; do
                if echo "$word" | grep -qi ".conf"; then
                        unsetopt shwordsplit
                        dnsmasq_conf_file="$word"
                        break
                fi
        done
        unsetopt shwordsplit
        if [ "$dnsmasq_conf_file" == "" ]; then
                echo "error parsing .conf filename from \"$dnsmasq_args\" - exiting(1)"
                exit 1
        fi
fi

modified="0"
for cmd in $local_pxe_options; do
        #echo "testing $cmd"
        if ! grep -qi $cmd $dnsmasq_conf_file; then
                # command not found
                echo "adding: $cmd"
                echo $cmd >> $dnsmasq_conf_file
                modified="1"
        fi
done

if [[ $modified -ne "0" ]]; then
        echo "Restarting dnsmasq"
        killall dnsmasq
elif [ "$(ps w | grep dnsmasq | grep -v grep)" == "" ]; then
        echo "Starting dnsmasq"
else
        echo "All pxe-related options are already present ($dnsmasq_conf_file)"
fi

if [ "$dnsmasq_conf_file" != "$default_conf" ]; then
        /usr/sbin/dnsmasq --log-async -C "$dnsmasq_conf_file"
else
        /usr/sbin/dnsmasq --log-async
fi

Next steps:

Diskless thin client(s) with NFS-root? Maybe Clonezilla to quickly restore a family member’s PC when they bungle it up? Only time will tell 🙂

Advertisements
Serving AirPrint via RaspberryPi

Serving AirPrint via RaspberryPi

So I’ve got a Pi, a few Apple devices and a Brother network-enabled laser printer, so I thought: “Hey, wouldn’t it be wonderful if I could print directly from the iDevice to the printer?”

It really would, but Brother’s app requires that you (re-)open whatever needs printing in their built-in browser, so that’s a few extra steps than I am happy with for printing from Safari, etc. (NOTE: The network scanning function from the app is pretty amazing, however)

The workaround: Use the PI as an AirPrint server (since it’s already handling other ‘always-on’ functions) – easy peasy! I’ve seen Brother’s got a Linux driver on their website!

UPDATE: Something got borked, so I did some debugging; turns out the ‘share printers’ option needs to be set in CUPS (see Step1’s update), and with the MIME types configured (maybe? Added step5), CUPS support Airprint directly (iOS 6+), without avahi??

Step 1: Install/setup CUPS

Easy enough, I followed these steps, which basically amount to:

  • Install CUPS: sudo apt-get install cups
  • Allow your user admin rights (to configure cups): sudo usermod -a -G lpadmin pi
  • I skipped some of the more questionably secure mods in the CPUS config-file, namely:
    • allowing printing via (CUPS server) to any machine on the LAN (since my printer is already network-enabled & I only need to add AirPrint support), and
    • opening up the admin interface to any machine on the LAN (since I have a monitor on my pi and am comfortable working with X11-redirects)
  • Restart CUPS: sudo /etc/init.d/cups restart
  • Add a printer via the admin WebUI (http://localhost:631)

UPDATE: You MUST check the ‘Share printers connected  to this system’ option, even though this seems less secure – see the UPDATE below for more on security.

GREAT! My printer is detected by CUPS! …Oh wait, my model isn’t listed 😦

UPDATE: For the security-minded, I came across this little gem by Marcus Povey: Limit printer access to your local LAN and loopback via /etc/cupsd.conf:

<Location />
  Order Deny,Allow
  Deny From All
  Allow From 127.0.0.1
  Allow From 192.168.0.0/24
</Location>

Don’t forget! You need to specify the NETWORK address, not the address of a specific host, or it will get stuck! (Thanks lifeafter2am!)


Stuff I tried, but ultimately gave up on:

  • Remember that wonderful Linux driver on Brothers’ website? Download and install the .deb package… Oh wait, it’s an x86 driver 😥
  • Finding a .ppd in the cupswrapper and LPR .deb packages (ar vx <filename>.deb to open them, in case you were wondering)

Then, the jackpot. A fine gentleman named Peter De Wachter was kind enough to build a brother driver with which CUPS can print to a few models of DCP- laser printers – INCLUDING MINE! The source code is graciously shared on GitHub under a GPLv2: https://github.com/pdewacht/brlaser

If you run into similar issues (missing linux armhf driver support from the manufacturer), you can try finding a suitable printer driver here: http://www.openprinting.org/printers


Step 2: Installing the brlaser driver

Special thanks to ‘hal58’ who wrote the ‘Jackpot’ post referenced above about getting his brother printer working with his Pi: https://www.raspberrypi.org/forums/viewtopic.php?t=116509&p=898138

His instructions amount to:

  • Download the source code .zip from https://github.com/pdewacht/brlaser
  • Install CUPS and software libraries/compilers:  sudo apt-get install cups build-essential libcups2-dev libcupsimage2-dev automake
    • I had an issue with ‘automake’, and hal58’s ‘automake-1.11’. An ‘apt-get update’ cleared it up.
  • Prepare, configure, build and install the driver: ./autogen.sh; ./configure; make; sudo make install
  • Restart CUPS service: sudo service cups restart

Hooray! Now back to the original guide!


Step 3: Finish adding the printer

Getting back to the interrupted ‘Add Printer’ process in CUPS WebUI, under the ‘Model:’ field just select the line ending with “using brlaser v3 (en)”

That’s it! 1hr later, I can print!! – Who says Linux/RPi aren’t fun? 😉

 Step 4: Install  avahi-daemon

UPDATE: This step (avahi-daemon) may not even be necessary!?
While debugging (after finding out my CUPS was not set to share printers), I had visible AirPrint printers in my iPhone: “AirPrint-Brother” (from /etc/avahi/services/airprint.service) and “Brother DCP-XXXX” (directly from CUPS??)

Most of the guides tell you to simply sudo apt-get install avahi-daemon, but somehow omit a key step: avahi’s .service config-file! (SERIOUSLY!?)

Step 4a: setting up the AirPrint service (avahi-daemon)

The ones that do seem to be aware that avahi is not simply plug-and-play, refer to a python script that will auto-create and configure the .service file required.

Disclaimer: My malfunction is that I don’t like running a random script called “airprint-generate.py” from github and hoping for the best… call me paranoid. Part of the problem is that I don’t know enough python to (time-effectively) audit that the script is actually doing what it’s advertised to do, and nothing more.

Fortunately, good ol’ google led me to archlinux, who gave a sample config-file and listed what should be customised: https://wiki.archlinux.org/index.php/avahi#Airprint_from_Mobile_Devices. As far as I can tell:

  • adminurl is the same link as you would use to administrate your printer via CUPS’ webUI (Administration > Manage Printers > ‘PrinterName’),
  • rpand ty are sub-strings of adminurl, and
  • as for nameand note, just pick a printer name and location-hint that make sense (will be displayed in the iOS device looking for printers)

Copy-paste the sample from ArchLinux into a text file and update the fields above before saving, e.g.: sudo vi /etc/avahi/services/airprint.service

A quick sudo service avahi-daemon restart and you’re set!

Step 5: Adding MIME types to CUPS (UPDATED)

While debugging, I came across this post by Friedrich Hotz, and added the MIME types indicated and it now seems that CUPS supports AirPrint on its own? I’ve deactivated the avahi ‘.services’ files (sudo mv airprint.service airprint.service.off) and my Brother DCP-XXXX is still detected on my iPhone! And it prints too!

echo ”image/urf urf string(0,UNIRAST<00>)” >  /usr/share/cups/mime/airprint.types
echo ”image/urf application/pdf 100 pdftoraster” > /usr/share/cups/mime/airprint.convs

Let me know if this helped you get a similar setup going on your own Pi.

Looks like Duplex printing will be a battle for another day 🙂