How to turn the Raspberry Pi into a Gateway to mobile phone internet

Your DSL internet connection is too slow? Want to set up an improvised office? You do not want to pay for a DSL internet plan when you already have a fast 4G mobile plan? If yes to one of the above, it is quite easy to configure a Raspberry Pi to share one mobile internet connection to an Ethernet network.

Strictly speaking, you don’t have to use a Raspberry Pi to do this. A laptop or desktop computer with any Operating System would work too, but the Raspberry is so small and consumes only 2-3 W of electrical power, and is so cool (quite literally!), so will will make use of this awesomeness!

 

Prerequisites

The following step-by-step guide is based on a pure Debian 9 (“Stretch”) distribution with a mainline/vanilla/unpatched Linux kernel built according to my previous blog post:

https://blog.michael.franzl.name/2016/10/31/raspberry-pi-debian-stretch/

  • We will not focus on the Raspbian OS nor on any other distribution, because documentation for these other setups exists in abundance.
  • You should not have a graphical interface installed. GUIs also install the NetworkManager service for systemd (Debian package “network-manager”), and I have not tested how NetworkManager interacts with the methods presented below. In addition, a bare-bone system is the preferred choice because it saves RAM and CPU resources.
  • In any case, you should attach a keyboard and screen to the Raspberry because you may temporarily lose network connectivity during the setup.
  • You also need a smart phone with an internet plan, supporting USB tethering. I have only tested recent Android based smartphones. Keep in mind during the following steps that, with most smart phones, you need to re-enable USB tethering after reboots or USB cable reconnects.

 

Goals

  • Computers in the LAN will be able to set the Raspberry Pi’s static IP address as internet Gateway and DNS server.
  • The Raspberry Pi will prefer a smart phone connection (tethered USB) to forward traffic.
  • If the smart phone is disconnected, the Rasbperry Pi will automatically fall back to an already existing gateway if present (i.e. a DSL modem)

 

Step 1: Install a DNS server

This ensures that cached DNS lookups are very fast when a DNS query has already been fetched.

apt-get install bind9

Tell “bind” to use Google’s public DNS servers (they are good). Edit /etc/bind/named.conf.options and change the “forward” block to:

forwarders {
    8.8.8.8;
    8.8.4.4;
};

Restart “bind”:

systemctl restart bind9

 

Step 2: Configure a static IP address for the Ethernet adapter

If you already have a DHCP server running in your local network (we will use the subnet 192.168.0.0 in this guide), give the Raspberry Pi a free static IP address in this existing subnet, e.g. 192.168.0.250.

If you don’t have an existing DHCP server running in your local network, we will set one up on the Raspberry (see Step 8 below).

In both cases, we will give our Rasberry the static IP address 192.168.0.250. Using systemd, change the config file of your ethernet connection /etc/systemd/network/eth.network:

 

[Match]
Name=eth0

[Network]
Address=192.168.0.250/24

If your LAN already has an internet gateway, e.g. a DSL modem with address 192.168.0.1, add the following (optional) section to the same config file:

[Route]
Gateway=192.168.0.1
Metric=9000

The large positive integer value of “Metric” ensures that other configured gateways with a lower Metric will be preferred. This will come in handy in the next step where the smart phone will be our preferred gateway with a Metric value of 1024.

Now reboot the Raspberry or run systemctl restart systemd-networkd.  You may lose network connectivity at this point if you are logged in via ssh.

Now, check that networkctl status eth0 matches our wanted static IP address:

Address: 192.168.0.250

Next, check the output of route -n (the kernel routing table). It should show:

Destination     Gateway         Genmask         Flags Metric Ref    Use Iface 
192.168.0.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0

If you have added the optional [Route] section, you should also see the following as first line, which is our current default route to the internet:

0.0.0.0         192.168.0.1     0.0.0.0         UG    9000   0        0 eth0

 

 

Step 3: Set the smart phone connection as gateway

Plug in your phone’s USB cable into one of the Raspberry’s USB connectors. Then turn on USB tethering in the Settings UI of your smart phone.

Run networkctl. You should see the following entry amongst the other network connections (notice “off” and “unmanaged”).

usb0             ether              off         unmanaged

 

To have the “systemd-networkd” service manage the “usb0” network device, create a file /etc/systemd/network/mobile.network with the following contents:

[Match] 
Name=usb0 

[Network] 
DHCP=yes

To apply this config file, run systemctl restart systemd-networkd .  After a few seconds, networkctl should output (notice the “routable” and “configured” parts):

3 usb0             ether              routable         configured

You also can check networkctl status usb0  to see the dynamic IP address obtained from the DHCP server on the smart phone. For Android phones this is usually in the subnet 42.

Next, check the output of route -n. Now, the phone connection “usb0” should be on the top of the list thanks to the lower metric of 1024:

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.42.129  0.0.0.0         UG    1024   0        0 usb0
0.0.0.0         192.168.0.1     0.0.0.0         UG    9000   0        0 eth0
192.168.0.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.42.0    0.0.0.0         255.255.255.0   U     0      0        0 usb0
192.168.42.129  0.0.0.0         255.255.255.255 UH    1024   0        0 usb0

 

Step 4: Check internet connectivity

With this routing table, we already can connect to the internet via the smart phone. To make sure that we are routed via the smart phone, we will ask the Linux kernel which gateway it would take first for traffic. ip route get 8.8.8.8  should ouput the IP address of the smart phone (192.168.42.129, subnet 42):

8.8.8.8 via 192.168.42.129 dev usb0 src 192.168.42.19

Let’s ping Google’s server a few times: ping 8.8.8.8  to see if we have an actual working route to the internet:

PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 
64 bytes from 8.8.8.8: icmp_seq=1 ttl=51 time=1742 ms 
64 bytes from 8.8.8.8: icmp_seq=2 ttl=51 time=699 ms

The answer: Yes!

Check phone’s DNS server

 

Now let’s check if the phone’s DNS server is working. Type dig google.com (install Debian package “dnsutils” if not yet installed), and make sure that you’ve got an “ANSWER SECTION”:

;; ANSWER SECTION:
google.com.     2      IN      A  216.58.204.110

;; Query time: 567 msec
;; SERVER: 192.168.42.129#53(192.168.42.129)

Note that the response came from the phone’s IP. So, “systemd” has correctly configured the phone’s IP address as DNS server for the Raspberry (that information came from the phone’s DHCP server).

Run dig google.com again. This time the result should be cached and returned much faster (just 1ms):

...
;; Query time: 1 msec

Check local DNS server

Type dig @localhost google.com:

;; ANSWER SECTION:
google.com.     2      IN      A  216.58.204.110

;; Query time: 567 msec
;; SERVER: ::1#53(::1)

Note that this time, the response came from the “bind” DNS server which we have installed in Step 1. It, in turn, forwards queries via the phone connection. This server will be used for all requests via Ethernet.

Step 5: Turn on IP protocol forwarding for the Linux kernel

By default, this feature is turned off. Check the current status of this feature:

sysctl -a | grep net\.ipv4\.ip_forward  will output:

net.ipv4.ip_forward = 0

To permanently set this variable to 1, create /etc/sysctl.d/30-ipforward.conf and add the following:

net.ipv4.ip_forward=1

Reload all settings by typing sysctl --system. Now, and also after a reboot, the “ip_forward” variable should stay enabled.

 

Step 6: Turn on Network address translation (NAT) aka. “Masquerading” between Ethernet and USB Smart Phone network links

Create a shell script /usr/bin/startgateway.sh with the following contents and make it executable (chmod a+x):

#!/bin/sh
iptables -t nat -A POSTROUTING -o usb0 -j MASQUERADE
iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i eth0 -o usb0 -j ACCEPT

This will masquerade IP packets coming in through the Ethernet adapter as if they were coming from the Raspberry itself, forward them to the USB smart phone connection, and the incoming answers (from remote servers) will be re-written and forwarded back to whereever in the LAN they came from. That is the central purpose of the problem we’re trying to solve in this tutorial.

Run this script. Check the output of iptables -L -n -v:

Chain INPUT (policy ACCEPT 63 packets, 6443 bytes) 
pkts bytes target     prot opt in     out     source               destination          

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) 
pkts bytes target     prot opt in     out     source               destination          
   0     0 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED 
   0     0 ACCEPT     all  --  eth0 usb0      0.0.0.0/0            0.0.0.0/0            

Chain OUTPUT (policy ACCEPT 23 packets, 3540 bytes) 
pkts bytes target     prot opt in     out     source               destination

 

To run this shell script at system boot, right after the network links have been brought up, create the following systemd service file:

/etc/systemd/system/multi-user.target.wants/startgateway.service

Add the following:

[Unit]
Description=Start Gateway
After=network-online.target
Requires=network-online.target

[Service]
Type=[oneshot]
ExecStart=/usr/bin/startgateway.sh

[Install]
WantedBy=multi-user.target

 

 

Step 7: Test the Raspberry Gateway!

On another machine in your LAN (can be Linux, Windows or Mac), configure the Ethernet connection manually. Set the following:

  • Static IP Address: 192.168.0.10 (or any other freely available address on this subnet)
  • Gateway: 192.168.0.250
  • DNS: 192.168.0.250

Then run traceroute 8.8.8.8  on that other machine. Truncated output:

1  gateway (192.168.0.250)
2  192.168.42.129

The route is correctly resolved. First traffic goes to the Raspberry Pi, then to the smart phone, and from there to the internet.

If you can’t run traceroute on that other machine, using a regular browser to browse the internet should work at this point!

 

Step 8: Running a DHCP server on the Raspberry

TODO

 

Conclusion

This tutorial may seem long, but the commands are few, and with a bit of practice you can turn your Raspberry Pi into a mobile phone Gateway in 10 minutes to enjoy faster 4G internet when your other modems are too slow.

Reading Raspberry Pi chip temperature with mainline Linux kernel

This tutorial is based on a previous article where we installed pure Debian 9 with a recent mainline/vanilla Linux kernel on a Raspberry Pi, and so differs from what would be done on a Raspbian Distribution with a Raspbian kernel. In this article, we will read the Raspberry Pi chip temperature. Here is my previous article:

https://blog.michael.franzl.name/2016/10/31/raspberry-pi-debian-stretch/

As of mainline Linux Kernel 4.9.0-rc3, the sysfs entry /sys/class/thermal for the Broadcom BCM283x chip found on Raspberry Pi’s is empty. You can apply the following patch to Linux kernel 4.9.0-rc3, even though it will soon be superfluous because it seems that currently there is ongoing work by Linux Kernel developers to add in the missing functionalities.

The patch which will get you the standard sysfs temperature node which you can read like this:

cat /sys/class/thermal/thermal_zone0/temp
40084

This is the chip temperature in thousandths of degrees cenigrade, i.e. 40.084 degrees Celsius.

The following patch will give you a new entry in the mainline Kernel config, under Drivers -> SoC -> BCM -> Raspberry Pi thermal sysfs driver, which you have to enable, then recompile your kernel.

I have actually submitted this patch to a Linux kernel developer, but the process to get code into the Linux kernel is quite elaborate, and he said that they are already working on it, so I let it drop and decided to write this blog post instead.

Copied drivers/thermal/bcm2835-thermal.c from Raspberry Pi
Foundation Linux kernel to drivers/soc/bcm/raspberry-thermal.c
and added respective entry to the device tree.

Tested and confirmed working on a Raspberry Pi 2 Model B V1.1 2014.

Signed-off-by: Michael Franzl <office@blog.michael.franzl.name>
Tested-by: Michael Franzl <office@blog.michael.franzl.name>

 arch/arm/boot/dts/bcm2835-rpi.dtsi    |   5 ++
 arch/arm/configs/multi_v7_defconfig   |   1 +
 drivers/soc/bcm/Kconfig               |   9 +++
 drivers/soc/bcm/Makefile              |   1 +
 drivers/soc/bcm/raspberrypi-thermal.c | 109 ++++++++++++++++++++++++++++++++++
 5 files changed, 125 insertions(+)
 create mode 100644 drivers/soc/bcm/raspberrypi-thermal.c

diff --git a/arch/arm/boot/dts/bcm2835-rpi.dtsi b/arch/arm/boot/dts/bcm2835-rpi.dtsi
index e9b47b2..389ee83 100644
--- a/arch/arm/boot/dts/bcm2835-rpi.dtsi
+++ b/arch/arm/boot/dts/bcm2835-rpi.dtsi
@@ -22,6 +22,11 @@
 			mboxes = <&mailbox>;
 		};
 
+		thermal: thermal {
+			compatible = "raspberrypi,bcm2835-thermal";
+			firmware = <&firmware>;
+		};
+
 		power: power {
 			compatible = "raspberrypi,bcm2835-power";
 			firmware = <&firmware>;
diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig
index 11f37ed..1368e63 100644
--- a/arch/arm/configs/multi_v7_defconfig
+++ b/arch/arm/configs/multi_v7_defconfig
@@ -838,6 +838,7 @@ CONFIG_XILINX_XADC=y
 CONFIG_CM36651=m
 CONFIG_AK8975=y
 CONFIG_RASPBERRYPI_POWER=y
+CONFIG_RASPBERRYPI_THERMAL=y
 CONFIG_PWM=y
 CONFIG_PWM_ATMEL=m
 CONFIG_PWM_ATMEL_HLCDC_PWM=m
diff --git a/drivers/soc/bcm/Kconfig b/drivers/soc/bcm/Kconfig
index a39b0d5..c951de4 100644
--- a/drivers/soc/bcm/Kconfig
+++ b/drivers/soc/bcm/Kconfig
@@ -20,4 +20,13 @@ config SOC_BRCMSTB
 
 	  If unsure, say N.
 
+config RASPBERRYPI_THERMAL
+	tristate "Raspberry Pi thermal sysfs driver"
+	depends on ARCH_BCM2835 || (COMPILE_TEST && OF)
+	depends on RASPBERRYPI_FIRMWARE=y
+	depends on THERMAL
+	help
+	  This enables a thermal sysfs driver for the thermal sensor on
+	  Broadcom283x chips found on a Raspberry Pi.
+
 endmenu
diff --git a/drivers/soc/bcm/Makefile b/drivers/soc/bcm/Makefile
index dc4fced..41734e1 100644
--- a/drivers/soc/bcm/Makefile
+++ b/drivers/soc/bcm/Makefile
@@ -1,2 +1,3 @@
 obj-$(CONFIG_RASPBERRYPI_POWER)	+= raspberrypi-power.o
 obj-$(CONFIG_SOC_BRCMSTB)	+= brcmstb/
+obj-$(CONFIG_RASPBERRYPI_THERMAL)	+= raspberrypi-thermal.o
diff --git a/drivers/soc/bcm/raspberrypi-thermal.c b/drivers/soc/bcm/raspberrypi-thermal.c
new file mode 100644
index 0000000..34a829f
--- /dev/null
+++ b/drivers/soc/bcm/raspberrypi-thermal.c
@@ -0,0 +1,109 @@
+/*****************************************************************************
+* Copyright 2011 Broadcom Corporation.  All rights reserved.
+*
+* Unless you and Broadcom execute a separate written software license
+* agreement governing use of this software, this software is licensed to you
+* under the terms of the GNU General Public License version 2, available at
+* http://www.broadcom.com/licenses/GPLv2.php (the "GPL").
+*
+* Notwithstanding the above, under no circumstances may you combine this
+* software in any way with any other Broadcom software provided under a
+* license other than the GPL, without Broadcom's express prior written
+* consent.
+*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+static int bcm2835_thermal_get_property(struct thermal_zone_device *tz,
+					int *temp, u32 tag)
+{
+	struct rpi_firmware *fw = tz->devdata;
+	struct {
+		u32 id;
+		u32 val;
+	} packet;
+	int ret;
+
+	*temp = 0;
+	packet.id = 0;
+	ret = rpi_firmware_property(fw, tag, &packet, sizeof(packet));
+	if (ret) {
+		dev_err(&tz->device, "Failed to get temperature\n");
+		return ret;
+	}
+
+	*temp = packet.val;
+	dev_dbg(&tz->device, "%stemp=%d\n",
+		tag == RPI_FIRMWARE_GET_MAX_TEMPERATURE ? "max" : "", *temp);
+
+	return 0;
+}
+
+static int bcm2835_thermal_get_temp(struct thermal_zone_device *tz,
+				    int *temp)
+{
+	return bcm2835_thermal_get_property(tz, temp,
+					    RPI_FIRMWARE_GET_TEMPERATURE);
+}
+
+static struct thermal_zone_device_ops ops  = {
+	.get_temp = bcm2835_thermal_get_temp,
+};
+
+static int bcm2835_thermal_probe(struct platform_device *pdev)
+{
+	struct device_node *fw_np;
+	struct rpi_firmware *fw;
+	struct thermal_zone_device *tz;
+
+	fw_np = of_parse_phandle(pdev->dev.of_node, "firmware", 0);
+	if (!fw_np) {
+		dev_err(&pdev->dev, "Missing firmware node\n");
+		return -ENOENT;
+	}
+	fw = rpi_firmware_get(fw_np);
+	if (!fw)
+		return -EPROBE_DEFER;
+
+	tz = thermal_zone_device_register("bcm2835_thermal", 0, 0, fw, &ops,
+					  NULL, 0, 0);
+	if (IS_ERR(tz)) {
+		dev_err(&pdev->dev, "Failed to register the thermal device\n");
+		return PTR_ERR(tz);
+	}
+
+	platform_set_drvdata(pdev, tz);
+
+	return 0;
+}
+
+static int bcm2835_thermal_remove(struct platform_device *pdev)
+{
+	thermal_zone_device_unregister(platform_get_drvdata(pdev));
+
+	return 0;
+}
+
+static const struct of_device_id bcm2835_thermal_of_match_table[] = {
+	{ .compatible = "raspberrypi,bcm2835-thermal", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, bcm2835_thermal_of_match_table);
+
+static struct platform_driver bcm2835_thermal_driver = {
+	.probe = bcm2835_thermal_probe,
+	.remove = bcm2835_thermal_remove,
+	.driver = {
+		.name = "bcm2835_thermal",
+		.of_match_table = bcm2835_thermal_of_match_table,
+	},
+};
+module_platform_driver(bcm2835_thermal_driver);
+
+MODULE_AUTHOR("Dorian Peake");
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_DESCRIPTION("Thermal driver for bcm2835 chip");
+MODULE_LICENSE("GPL");
-- 
2.1.4


 

Setting I2C bus speed on a Raspberry Pi via Device Tree

This tutorial is based on my previous article where we installed pure Debian 9 with a recent mainline/vanilla Linux kernel, and so differs from what would be done on a Raspbian Distribution with a Raspbian kernel. In this article, we will set the I2C bus speed on a Raspberry Pi. Here is my previous article:

https://blog.michael.franzl.name/2016/10/31/raspberry-pi-debian-stretch/

Device Trees

The I2C bus on the Broadcom BCM283x chips found on Raspberry Pi’s is well and directly supported by the mainline/vanilla Linux kernel. Since with the Raspberry Pi we’re dealing with a System on a Chip (SoC), and not a regular PC, the hardware is configured with so-called device trees, which is a low-level description of the chip hardware compiled from text into binary format.

The rpi23-gen-image script mentioned in my previous tutorial installs the binary device tree into /boot/firmware/bcm2836-rpi-2-b.dtb. The U-Boot bootloader can read this file and pass it to the Linux kernel which interprets it and enables all the mentioned features in it.

The clock frequency for the I2C bus is configured in this .dtb file, and the default is 100kHz. There is a tool which allows you to inspect the .dtb file, outputting regular text. With this tool you also can make changes to the device configuration. Nowadays, this is the proper way to configure low-level devices on SoC’s!

 

Read the device tree

apt-get install device-tree-compiler
fdtdump /boot/firmware/bcm2836-rpi-2-b.dtb

This will output the decoded device tree as text. Regarding I2C, you will find i2c@-entries like this:

        i2c@7e205000 {
            compatible = "brcm,bcm2835-i2c";
            reg = <0x7e205000 0x0000005c>;
            interrupts = <0x00000002 0x000000e9>;
            clocks = <0x00000008 0x00000022>;
            #address-cells = <0x00000001>;
            #size-cells = <0x00000000>;
            status = "okay";
            clock-frequency = <0x000186a0>;
        };
        i2c@7e804000 {
            compatible = "brcm,bcm2835-i2c";
            reg = <0x7e804000 0x0000005c>;
            interrupts = <0x00000002 0x000000e9>;
            clocks = <0x00000008 0x00000022>;
            #address-cells = <0x00000001>;
            #size-cells = <0x00000000>;
            status = "okay";
            clock-frequency = <0x000186a0>;
        };

Change the device tree

The clock-frequency value is what we want to change. The value is a raw binary unsigned 32-bit int stored big-endian, unreadable for humans.

But you can use another tool fdtget to read just this value decoded:

fdtget /boot/firmware/bcm2836-rpi-2-b.dtb /soc/i2c@7e205000 clock-frequency

This will output 100000.

In my case, I wanted to set the I2C bus to the slowest frequency, to compensate for long cable lengths. I found that one of the lowest supported I2C clock frequencies is 4kHz. With fdtput you can set the clock-frequency property for each i2c device (there are 3 on the RPi):

fdtput --type u /boot/firmware/bcm2836-rpi-2-b.dtb /soc/i2c@7e205000 clock-frequency 4000

And that’s it. You have to reboot for these settings to take effect.

I used an oscilloscope to verify that the SCL pin of the GPIO of the RPi indeed toggled with 4kHz, and it did!

Raspberry Pi2 and Pi3 running pure Debian and the Linux Mainline/Vanilla Kernel

Update 2017 Feb 25: I have updated the step-by-step instructions based on the suggested fixes and improvements contained in the reader comments. I also have copied the step-by-step instructions from this blog post to the README.md file hosted on https://github.com/michaelfranzl/rpi23-gen-image. From now on I will update the instructions only on github, so expect that the instructions in this blog post will grow slightly out of date.

Update 2017 Mar 4: 64 bit kernel and Debian OS now works on the RPi3.

Update 2018 May: Debian 10 “Buster” works, including wireless LAN on RPi3

 

 

Gallium graphics drivers for Raspberry Pi’s VC4 chip are now fully supported by Linux Mainline

This has been a long way coming. In February 2014, Broadcom announced that they would release the formerly closed-source drivers for the VideoCore IV (VC4) GPU of their BCM283x family of System-on-a-chip (SoC), powering Raspberry Pi’s.

To make a long story short, Eric Anholt started porting the Open Source drivers, as documented by this presentation early 2015, and contributed code to the Linux (Mainline) Kernel, libdrm, Mesa, and X.org. I’m sure it was a long and painful work. But the results are worth it. A picture says more than 1000 words:

Debian 9 ("Stretch") running on a Raspberry Pi 2, powered by Linux 4.9.0-rc3 Mainline/Vanilla Kernel. Notable in this image: Graphics driver is "Gallium" running on the VC4 GPU of the Broadcom 2836 system-on-a-chip (SOC). Glxgears runs with 60 FPS and consumes very little CPU. I2C interface is recognized.
Debian 9 (“Stretch”) running on my Raspberry Pi 2 (and 3), powered by Linux 4.9.0-rc3 Mainline/Vanilla Kernel. Notable in this image: Graphics driver is recognized as “Gallium” running on the VC4 GPU of the Broadcom 2836 system-on-a-chip (SOC). The glxgears benchmark runs with 60 FPS (the vsync of the monitor) and consumes very little CPU. Even the Raspberry I2C interface is recognized by the Linux Mainline Kernel.

To emphasize the point: It is no longer necessary to run specialized distributions (like Raspbian) or Linux kernels (like the Raspbian flavor) in order to have the Raspberry Pi well supported. And this is good news. Debian is a well established and maintained standard Distribution. And even though the Raspberry Pi is not powerful enough for the professional desktop user, it is powerful enough for the casual desktop user, and it is very small and cheap, which opens up a whole lot of possibities for new real-world applications.

I ran an additional test: Gnome even runs on Wayland (modern replacement for the X Window System) on the Raspberry Pi 2 (and 3):

Gnome on Wayland on Raspberry Pi 2
Gnome on Wayland on Raspberry Pi 2

 

 

 

 

Remarks

It still is not a matter of a one-click installer to reproduce these results, you need some experience when you run into barriers. But it has gotten a whole lot easier. Github user drtyhlpr thankfully published the script rpi23-gen-image that can create a standard Debian distribution for the Raspberry Pi that can simply be copied to a SD card.

I have created a fork of this script to use the official Linux kernel instead of the Raspberry flavor one. Above screenshots are taken from a system that I’ve created with this script. My fork is developed into a slightly different direction:

  • Only official Debian releases 9 (“Stretch”) and newer are supported.
  • Only the official/mainline/vanilla Linux kernel is supported (not the raspberry flavor kernel).
  • The Linux kernel must be pre-cross-compiled on the PC running this script (instructions below).
  • Only U-Boot booting is supported.
  • The U-Boot sources must be pre-downloaded and pre-cross-compiled on the PC running this script (instructions below).
  • An apt caching proxy server must be installed to save bandwidth (instructions below).
  • The installation of the system to an SD card is done by simple copying or rsyncing, rather than creating, shrinking and expanding file system images.
  • The FBTURBO option is removed in favor or the working VC4 OpenGL drivers of the mainline Linux kernel.

All of these simplifications are aimed at higher bootstrapping speed and maintainability of the script. For example, we want to avoid testing of all of the following combinations:

RPi2 with u-boot, with official kernel
RPi2 without u-boot, with official kernel
RPi2 with u-boot, with raspberry kernel
RPi2 without u-boot, with raspberry kernel
RPi3 with u-boot, with official kernel
RPi3 without u-boot, with official kernel
RPi3 with u-boot, with raspberry kernel
RPi3 without u-boot, with raspberry kernel

Thus, the script only supports:

RPi2 with u-boot with official kernel
RPi3 with u-boot with official kernel

RPi2 (setting RPI_MODEL=2) is well supported. It will run the arm architecture of Debian, and a 32-bit kernel. You should get very good results, see my related blog posts:

https://blog.michael.franzl.name/2016/10/31/raspberry-pi-debian-stretch/

https://blog.michael.franzl.name/2016/11/10/setting-i2c-speed-raspberry-pi/

https://blog.michael.franzl.name/2016/11/10/reading-cpu-temperature-raspberry-pi-mainline-linux-kernel/

The newer RPi3 (setting RPI_MODEL=3) is supported too. It will run the arm64 architecture of Debian, and a 64-bit kernel. The support of this board by the Linux kernel will very likely improve over time.

In general, this script is EXPERIMENTAL. I do not provide ISO file system images. It is better to master the process rather than to rely on precompiled images. In this sense, use this project only for educational purposes.

How to do it

Basically, we will deboostrap a minimal Debian 9 (“Stretch”) system for the Raspberry on a regular PC running also Debian 9 (“Stretch”). Then we copy that system onto a SD card, then boot it on the Raspberry.

We will work with the following directories:

~/workspace
  |- rpi23-gen-image
  |- linux
  |- u-boot
  |- raspberry-firmware

Set up your working directory:

mkdir workspace
cd workspace

Do the following steps as root user.

 

Set up caching for apt

This way, you won’t have to re-download hundreds of megabytes of Debian packages from the Debian server every time you run the rpi23-gen-image script.

apt-get install apt-cacher-ng

Check its status page:

http://localhost:3142

 

Install dependencies

The following list of Debian packages must be installed on the build system because they are essentially required for the bootstrapping process.

apt-get install debootstrap debian-archive-keyring qemu-user-static binfmt-support dosfstools rsync bmap-tools whois git bc device-tree-compiler

For a RPi2, you also need:

apt-get install crossbuild-essential-armhf

For a RPi3, you also need:

apt-get install crossbuild-essential-arm64

Kernel compilation

Get the latest Linux mainline kernel. This is a very large download, about 2GB. (For a smaller download of about 90 MB, consider downloading the latest stable kernel as .tar.xz from https://kernel.org.)

git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux

Confirmed working revision (approx. version 4.10, Feb 2017): 60e8d3e11645a1b9c4197d9786df3894332c1685

git checkout 60e8d3e116

Working configuration files for this Linux kernel revision are included in this repository. (working-rpi2-linux-config.txt and working-rpi3-linux-config.txt).

If you want to generate the default .config file that is also working on the Raspberry, execute

make mrproper

For a RPi2:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- multi_v7_defconfig

For a RPi3:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig

Whichever .config file you have at this point, if you want to get more control as to what is enabled in the kernel, you can run the graphical configuration tool at this point:

apt-get install libglib2.0-dev libgtk2.0-dev libglade2-dev

For a RPi2:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- gconfig

For a RPi3:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- gconfig

Before compiling the kernel, back up your .config file so that you don’t lose it after the next make mrproper:

cp .config ../kernelconfig-backup.txt

Compiling the kernel

Clean the sources:

make mrproper

Optionally, copy your previously backed up .config:

cp ../kernelconfig-backup.txt .config

Find out how many CPU cores you have to speed up compilation:

NUM_CPU_CORES=$(grep -c processor /proc/cpuinfo)

Run the compilation on all CPU cores. This takes about 10 minutes on a modern PC:

For a RPi2:

make -j${NUM_CPU_CORES} ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

For a RPi3:

make -j${NUM_CPU_CORES} ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

Verify that you have the required kernel image.

For a RPi2 this is:

./arch/arm/boot/zImage

For a RPi3 this is:

./arch/arm64/boot/Image.gz

 

U-Boot bootloader compilation

cd ..
git clone git://git.denx.de/u-boot.git

Confirmed working revision: b24cf8540a85a9bf97975aadd6a7542f166c78a3

git checkout b24cf8540a

Let’s increase the maximum kernel image size from the default (8 MB) to 64 MB. This way, u-boot will be able to boot even larger kernels. Edit ./u-boot/include/configs/rpi.h  and add above the very last line (directly above “#endif”):

#define CONFIG_SYS_BOOTM_LEN (64 * 1024 * 1024)

Find out how many CPU cores you have to speed up compilation:

NUM_CPU_CORES=$(grep -c processor /proc/cpuinfo)

Compile for a RPi model 2 (32 bits):

make -j${NUM_CPU_CORES} ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- rpi_2_defconfig all

Compile for a RPi model 3 (64 bits):

make -j${NUM_CPU_CORES} ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- rpi_3_defconfig all

Verify that you have the required bootloader file:

./u-boot.bin

Pre-download Raspberry firmware

The Raspberry Pi still needs some binary proprietary blobs for booting. Get them:

cd ..
mkdir -p raspberry-firmware/boot
cd raspberry-firmware/boot
wget https://github.com/raspberrypi/firmware/raw/master/boot/bootcode.bin
wget https://github.com/raspberrypi/firmware/raw/master/boot/fixup_cd.dat
wget https://github.com/raspberrypi/firmware/raw/master/boot/fixup.dat
wget https://github.com/raspberrypi/firmware/raw/master/boot/fixup_x.dat
wget https://github.com/raspberrypi/firmware/raw/master/boot/start_cd.elf
wget https://github.com/raspberrypi/firmware/raw/master/boot/start.elf
wget https://github.com/raspberrypi/firmware/raw/master/boot/start_x.elf

Confirmed working revision: bf5201e9682bf36370bc31d26b37fd4d84e1cfca

Build the system!

This is where you call the rpi23-gen-image.sh script.

cd ../..
git clone https://github.com/michaelfranzl/rpi23-gen-image.git
cd rpi23-gen-image

For example:

DEBIAN_RELEASE="stretch" \
USER_NAME="pi" \
PASSWORD="xxx" \
APT_INCLUDES="i2c-tools,rng-tools,avahi-daemon,rsync,vim" \
UBOOTSRC_DIR="$(pwd)/../u-boot" \
KERNELSRC_DIR="$(pwd)/../linux" \
RPI_MODEL=2 \
HOSTNAME="rpi2" \
RPI_FIRMWARE_DIR="$(pwd)/../raspberry-firmware" \
ENABLE_IPTABLES=true \
ENABLE_REDUCE=true \
REDUCE_SSHD=true \
./rpi23-gen-image.sh

You may want to modify the variables according to the section “Command-line parameters” below.

The file example.sh in my github repostory contains a working example.

Install the system on a SD card

Insert a SD card into the card reader of your host PC. You’ll need two partitions on it. I’ll leave as an exercise for the reader the creation of a partition table according to the following output of fdisk for a 64GB card:

Device         Boot  Start        End    Sectors    Size   Id  Type
/dev/mmcblk0p1        2048     500000     497953  243.1M    c  W95 FAT32 (LBA)
/dev/mmcblk0p2      501760   62552063   62050304   29.6G   83  Linux

The following commands will erase all contents of the SD card and install the system (copy via rsync) on the SD card:

umount /dev/mmcblk0p1
umount /dev/mmcblk0p2

mkfs.vfat /dev/mmcblk0p1
mkfs.ext4 /dev/mmcblk0p2

mkdir -p /mnt/raspcard

mount /dev/mmcblk0p2 /mnt/raspcard
mkdir -p /mnt/raspcard/boot/firmware
mount /dev/mmcblk0p1 /mnt/raspcard/boot/firmware

rsync -a ./images/stretch/build/chroot/ /mnt/raspcard

umount /dev/mmcblk0p1
umount /dev/mmcblk0p2

Note about SD cards: Cheap (or sometimes even professional) SD cards can be weird at times. I’ve repeatedly noticed corrupt/truncated files even after proper rsync and proper umount on different brand new SD cards. TODO: Add a method to verify all file checksums after rsync.

Try booting the Raspberry

Insert the SD card into the Raspberry Pi, and if everything went well, you should see a console-based login prompt on the screen. Login with the login details you’ve passed into the script (USER_NAME and PASSWORD).

Alternatively, if you have included “avahi-daemon” in your APT_INCLUDES, you don’t need a screen and keyboard and can simply log in via SSH from another computer, even without knowing the Rasberry’s dynamic/DHCP IP address (replace “hostname” and “username” with what you have set as USER_NAME and HOSTNAME above):

ssh username@hostname.local

Finishing touches directly on the Raspberry

Remember to change usernames, passwords, and SSH keys!

Check uber-low RAM usage

Running top shows that the freshly booted system uses only 23 MB out of the availabl 1GB RAM! Confirmed for both RPi2 and RPi3.

Network Time Synchronization

The Raspberry doesn’t have a real time clock. But the default systemd conveniently syncs time from the network. Check the output of timedatectl. Confirmed working for both RPi2 and RPi3.

Hardware Random Number Generator

The working device node is available at /dev/hwrng. Confirmed working for both RPi2 and RPi3.

I2C Bus

Also try I2C support:

apt-get install ic2-tools
i2cdetect -y 0

Confirmed working for both RPi2 and RPi3.

Test onboard LEDs

As of the kernel revision referenced above, this only works on the RPi2. The RPi3 has only the red PWR LED on all the time, but otherwise is working fine.

By default, the green onboard LED of the RPi blinks in a heartbeat pattern according to the system load (this is done by kernel feature LEDS_TRIGGER_HEARTBEAT).

To use the green ACT LED as an indicator for disc access, execute:

echo mmc0 > /sys/class/leds/ACT/trigger

To toggle the red PWR LED:

echo 0 > /sys/class/leds/PWR/brightness # Turn off
echo 1 > /sys/class/leds/PWR/brightness # Turn on 

Or use the red PWR LED as heartbeat indicator (kernel option for this must be enabled):

echo heartbeat > /sys/class/leds/PWR/trigger

Notes about systemd

systemd now replaces decades-old low-level system administration tools. Here is a quick cheat sheet:

Reboot machine:

systemctl reboot

Halt machine (this actually turns off the RPi):

systemctl halt

Show all networking interfaces:

networkctl

Show status of the Ethernet adapter:

networkctl status eth0

Show status of the local DNS caching client:

systemctl status systemd-resolved

Install GUI

Successfully tested on the RPi2 and RPI3.

If you want to install a graphical user interface, I would suggest the light-weight LXDE window manager. Gnome is still too massive to run even on a GPU-accelerated Raspberry.

apt-get install lightdm lxde lxde-common task-lxde-desktop

Reboot, and you should be greeted by the LightDM greeter screen!

Test GPU acceleration via VC4 kernel driver

Successfully tested on the RPi2 and RPI3.

apt-get install mesa-utils
glxgears
glxinfo | grep '^OpenGL'

Glxinfo should output:

OpenGL vendor string: Broadcom
OpenGL renderer string: Gallium 0.4 on VC4
OpenGL version string: 2.1 Mesa 12.0.3
OpenGL shading language version string: 1.20
OpenGL ES profile version string: OpenGL ES 2.0 Mesa 12.0.3
OpenGL ES profile shading language version string: OpenGL ES GLSL ES 1.0.16

Kernel compilation directly on the Rasberry

Only successfully tested on the RPi2. Not yet tested on the RPI3.

In case you want to compile and deploy another Mainline Linux kernel directly on the Raspberry, proceed as described above, but you don’t need the ARCH and CROSS_COMPILE flags. Instead, you need the -fno-pic compiler flag for modules. The following is just the compilation step (configuration and installation omitted):

make -j5 CFLAGS_MODULE="-fno-pic"
make modules_install

Follow-up articles

Setting I2C bus speed on a Raspberry Pi via Device Tree

Reading Raspberry Pi chip temperature with mainline Linux kernel

Zero Client: Boot kernel and root filesystem from network with a Raspberry Pi2 or Pi3

.

How to convert images to PDF with paper and image size, without ImageMagick

ImageMagick’s convert tool is handy for converting a series of images into a PDF. Just for future reference, here is one method how you can achieve the same without convert. It is useful if you have 1-bit PBM images (e.g. scanned text) at hand:

cat *.pbm | pnmtops -setpage -width 6 -height 8 -imagewidth 3 - | ps2pdf -dEPSFitPage - > book.pdf

This command concatenates all .pbm files, pipes the data to pnmtops to create an intermediary PostScript file on-the-fly, and pipes this PostScript file to ps2pdf to create the final pdf.

With the -width and -height parameter you specify the paper size of the generated PDF in inches, which should correspond to the paper size of the original book. With the -imagewidth parameter you specify the width that your scan/photo takes up on the page.

Digitize books: Searchable OCR PDF with text overlay from scanned or photographed books on Linux

Here is my method to digitize books. It is a tutorial about how to produce searchable, OCR (Optical Character Recognition) PDFs from a hardcopy book using free software tools on Linux distributions. You probably can find more convenient proprietary software, but that’s not the objective of this post.

Important: I should not need to mention that depending on the copyright attached to a particular work, you may not be allowed to digitize it. Please inform yourself before, so that you don’t break copyright laws!!!

Digitize books

To scan a book, you basically have 2 choices:

  1. Scan each double page with a flatbed scanner
  2. Take a good photo camera, mount it on a tripod, have it point vertically down on the book, and then take photos of each double page. Professional digitizers use this method due to less strain on the originals.

No matter which method, the accuracy of OCR increases with the resolution and contrast of the images. The resolution should be high enough so that each letter is at least 25 pixels tall.

Since taking a photo is almost instant, you can be much faster with the photographing method than using a flatbed scanner. This is especially true for voluminous books which are hard to repeatedly take on and off a scanner. However, getting sharp high-resolution images with a camera is more difficult than using a flatbed scanner. So it’s a tradeoff that depends on your situation, equitpment and your skills.

Using a flatbed scanner doesn’t need explanation, so I’ll only explain the photographic method next.

Photographing each page

If you use a camera, and you don’t have some kind of remote trigger or interval-trigger at hand, you would need 2 people: someone who operates the camera, and another one who flips the pages. You can easily scan 1 double page every 2 seconds once you get more skilled in the process.

Here are the steps:

  • Set the camera on a tripod and have it point vertically down. The distance between camera and book should be at least 1 meter to approximate orthagonal projection (imitates a flatbed scanner). Too much perspective projection would skew the text lines.
  • Place the book directly under the camera – avoid pointing the camera at any non-90-degree angles that would cause perspective skewing of the contents. Later we will unskew the images, but the less skewing you get at this point, the better.
  • Set up uniform lighting, as bright as you are able. Optimize lighting directions to minimize possible shadows (especially in the book fold). Don’t place the lights near the camera or it will cause reflections on paper or ink.
  • Set the camera to manual mode. Use JPG format. Turn the camera flash off. All pictures need to have uniform exposure characteristics to make later digital processing easier.
  • Maximize zoom so that a margin of about 1 cm around the book is still visible. This way, aligning of the book will take less time. The margin will be cropped later.
  • Once zoom and camera position is finalized, mark the position of the book on the table with tape. After moving the book, place it back onto the original position with help of these marks.
  • Take test pictures. Inspect and optimize the results by finding a balance between the following camera parameters:
    • Minimize aperture size (high f/value) to get sharper images.
    • Maximize ISO value to minimize exposure time so that wiggling of the camera has less of an effect. Bright lighting helps lowering ISO which helps reducing noise.
    • Maximize resolution so that the letter size in the photos is at least 25 pixels tall. This will be important to increase the quality of the OCR step below, and you’ll need a good camera for this.
  • Take one picture of each double page.
One double page of a book that will be digitized. This is actually a scan, but you also can use a good photo camera. Note that the right page is slighty rotated.
One double page of a book that will be digitized. This is actually a scan, but you also can use a good photo camera. Make sure that letters are at least 25 pixels tall. Note that the right page is slighty rotated.

Image Preprocessing

Let’s remember our goal: We want a PDF …

  • which is searchable (the text should be selectable)
  • whose file size is minimized
  • has the same paper size as the original
  • is clearly legible

The following steps are the preprocessing steps to accomplish this. We will use ImageMagick command line tools (available for all platforms) and a couple of other software, all available for Linux distributions.

A note on image formats

Your input files can be JPG or TIFF, or whatever format your scanner or camera support. However, this format must also be supported by ImageMagick. We’ll convert these images to black-and-white PBM images to save space and speed up further processing. PBM is a very simple, uncompressed image format that only stores 1 bit per pixel (2 colors). This image format can be embedded into the PDF directly, and it will be losslessly compressed extremely well, resulting in the smallest possible PDF size.

Find processing parameters by using just a single image

Before we will process all the images as a batch, we’ll just pick one image and find the right processing parameters. Copy one photograph into a new empty folder and do the following steps.

Converting to black and white

Suppose we have chosen one image in.JPG. Run:

convert -normalize -threshold 50% -brightness-contrast 0x10 in.JPG 1blackwhite.pbm

Inspect the generated 1blackwhite.pbm file. Optimize the parameters threshold (50% in above example), brightness (0 in above example), and contrast (10 in above example) for best legibiligy of the text.

Black-white conversion of the original image. Contrast and resolution is important.
Black-white conversion of the original image. Contrast and resolution is important.

Cropping away the margins

Next we will crop away the black borders so that the image will correspond to the paper size.

convert -crop 2400x2000+760+250 1blackwhite.pbm 2cropped.pbm

In this example, the cropped image will be a rectangle of 2400×2000 pixels, taken from the offset 760,250 of the input image. Inspect 2cropped.pbm until you get the parameters right, it will be some trial-and-error. The vertical book fold should be very close to the horizontal middle of the cropped image (important for next step).

The cropped image
The cropped image

Split double pages into single pages

convert +repage -crop 50%x100% +repage 2cropped.pbm split%04d.pbm

This will generate 2 images. Inspect split0001.pbm and split0002.pbm. You only can use 50% of horizontal cut, otherwise you’ll get more than 2 images.

Left split page
Left split page
Right split page
Right split page

Deskewing the image

Your text lines are probably not exactly horizontal (page angles, camera angles, perspective distortion, etc.). However, having exactly horizontal text lines is very important for accuracy of OCR software. We can deskew an image with the following command:

convert -deskew 40% split0001.pbm 3deskewed.pbm

Inspect the output file 3deskewed.pbm for best results.

The deskewed left page
The deskewed left page
The deskewed right page. Notice that the text lines are now perfectly horizontal. However, deskewing can have its limits, so the original image should already be good!
The deskewed right page. Notice that the text lines are now perfectly horizontal. However, deskewing can have its limits, so the original image should already be good!

Process all the images

Now that you’ve found the paramters that work for you, it’s simple to convert all of your images as a batch, by passing all the paramters at the same time to convert. Run the following in the folder where you stored all the JPG images (not in the folder where you did the previous single-image tests):

convert -normalize -threshold 50% -brightness-contrast 0x10 -crop 2400x2000+760+250 +repage -crop 50%x100% +repage -deskew 40% *.JPG book%04d.pbm

Now, for each .JPG input file, we’ll have two .pbm output files. Inspect all .pbm files and make manual corrections if needed.

Note: If you have black borders on the pages, consider using unpaper to remove them. I’ll save writing about using unpaper for a later time.

 

Producing OCR PDFs with text overlay

The tesseract OCR engine can generate PDFs with a selectable text layer directly from our PBM images. Since OCR is CPU intensive, we’ll make use of parallel processing on all of our CPU cores with the parallel tool. You can install both by running

apt-get install tesseract-ocr parallel

 

For each PBM file, create one PDF file:

find . -name '*.pbm' | parallel 'echo "Processing {}"; tesseract {} {.} pdf'

To merge all the PDF files into one, run pdfunite from the poppler-utils package:

pdfunite *.pdf book.pdf

Success! And this is our result:

The PDF has been OCR'd and a selectable text layer has been generated
The PDF has been OCR’d and a selectable text layer has been generated

 

100% HTTPS in the internet? Non-Profit makes it possible!

HTTPS on 100% of websites in the internet? This just has gotten a lot easier! Let’s Encrypt is a free, automated, and open certificate authority (CA), run for the public’s benefit. Let’s Encrypt is a service provided by the Internet Security Research Group (ISRG), a Section 501(c)(3) Non-Profit entity dedicated to reduce financial, technological, and education barriers to secure communication over the Internet.

Let’s Encrypt offers free-of-cost certificates that can be used for HTTPS websites, even when these websites are ran for commercial purposes. Unlike traditional CA’s they don’t require cumbersome registration, paperwork, set-up and payment. The certificates are fetched in an automated way through an API (the ACME Protocol — Automatic Certificate Management Environment), which includes steps to prove that you have control over a domain.

Dedicated to transparency, generated certificates are registered and submitted to Certificate Transparency logs. Here is the generous legal Subscriber Agreement.

Automated API? This sounds too complicated! It is actually not. There are a number of API libraries and clients available that do the work for you. One of them is Certbot. It is a regular command-line program written in Python and the source code is available on Github.

After downloading the certbot-auto script (see their documentation), fetching certificates consists of just one command line (in this example certs for 3 domains are fetched in one command with the -d switch):

certbot-auto certonly --webroot -w /var/www/example -d example.com -d www.example.com -d blah.example.com

With the -w  flag you tell the script where to put temporary static files (a sub-folder .well-known  will be created) that, during the API control flow, serve as proof to the CA’s server that you have control over the domain. This is identical to Google’s method of verifying a domain for Google Analytics or Google Webmaster Tools by hosting a static text file.

Eventually, the (already chained, which is nice!) certificate and private key are copied into /etc/letsencrypt/live/example.com/ :

fullchain.pem
privkey.pem

Then it is only a matter of pointing your web server (Nginx, Apache, etc.) to these two files, and that’s trivial.

Let’s Encrypt certificates are valid for 90 days. The automatic renewal of ALL certificates that you have loaded to your machine is as easy as …

./certbot-auto renew

… which they suggest should be put into a Cron job, run twice daily. It will renew the certificates just in time. No longer do you have to set a reminder in your calendar to renew a certificate, and then copy-paste it manually!

A bit of a downside is that Let’s Encrypt unfortunately doesn’t support wildcard domain certificates. For these, you still have to pay money to some other CA’s who support them. But in above shown code example, you would generate only 1 certificate for the domains example.com and its two subdomains www.example.com and blah.example.com. The two subdomains are listed in the Subject Alternative Name field of the certificate, which is as close to wildcard subdomains as it gets. But except for SAAS providers and other specialized businesses, not having wildcard certificates should not be too big of an issue, especially when one can automate the certificate setup.

On the upside, they even made sure that their certificates work down to Windows XP!

Today, I set up 3 sites with Let’s Encrypt (one of them had several subdomains), and it was a matter of a few minutes. It literally took me longer to configure proper redirects in Nginx (no fault of Nginx, I just keep forgetting how it’s done properly) than to fetch all the certificates. And it even gave me time to write this blog post!

Honestly, I never agreed with the fact that for commercial certificate authorities, one has to pay 1000, 100 or even 30 bucks per certificate per year. Where’s the work invested into such a certificate that is worth so much? The generation of a certificate is automated, and is done in a fraction of a second on the CPU. Anyway, that now seems to be a thing of the past.

A big Thumbs-up and Thanks go to the Let’s Encrypt CA, the ISRG, and to Non-Profit enterprises in general! I believe that Non-Profits are the Magic Way of the Future!

Icon made by Freepik from www.flaticon.com 

Resume rsync transfers with the –partial switch

Recently I wanted to rsync a 16GB file to a remote server. The ETA was calculated as 23 hours and, as it usually happens, the file transfer aborted after about 20 hours due to a changing dynamic IP address of the modem. I thought: “No problem, I just re-run the rsync command and the file transfer will resume where it left off.” Wrong! Because by default, on the remote side, rsync creates a temporary file beginning with a dot, and once rsync is aborted during the file transfer, this temporary file is deleted without a trace! Which, in my case, meant that I wasted 20 hours of bandwidth.

It turns out that one can tell rsync to keep temporary files by passing the argument –partial . I tested it, and the temporary file indeed is kept around even after the file transfer aborts prematurely. Then, when simply re-running the same rsync command, the file transfer is resumed.

In my opinion, rsync should adopt this behavior by default. Simple thing to fix, but definitely an argument that should be passed every time!

Added: Simply use -P  ! This implies –partial  and you’ll also see a nice progress output for free!

Unprivileged Unix Users vs. Untrusted Unix Users. How to harden your server security by confining shell users into a minimal jail

As a server administrator, I recently discovered a severe oversight of mine, one that was so big that I didn’t consciously see it for years.

What can Unprivileged Unix Users do on your server?

Any so-called “unprivileged Unix users” who have SSH access to a server (be it simply for the purpose of rsync’ing files) is not really “unprivileged” as the word may suggest. Due to the world-readable permissions of many system directories, set by default in many Linux distributions, such an user can read a large percentage of directories and files existing on the server, many of which can and should be considered as a secret. For example, on my Debian system, the default permissions are:

/etc: world-readable including most configuration files, amongst them passwd which contains plain-text names of other users
/boot: world-readable including all files
/home: world-readable including all subdirectories
/mnt: world-readable
/src: world-readable
/srv: world-readable
/var: world-readable
etc.

Many questions are asked about how to lock a particular user into their home directory. User “zwets” on askubuntu.com explained that this is besides the point and even “silly”:

A user … is trusted: he has had to authenticate and runs without elevated privileges. Therefore file permissions suffice to keep him from changing files he does not own, and from reading things he must not see. World-readability is the default though, for good reason: users actually need most of the the stuff that’s on the file system. To keep users out of a directory, explicitly make it inaccessible. […]

Users need access to commands and applications. These are in directories like /usr/bin, so unless you copy all commands they need from there to their home directories, users will need access to /bin and /usr/bin. But that’s only the start. Applications need libraries from /usr/lib and /lib, which in turn need access to system resources, which are in /dev, and to configuration files in /etc. This was just the read-only part. They’ll also want /tmp and often /var to write into. So, if you want to constrain a user within his home directory, you are going to have to copy a lot into it. In fact, pretty much an entire base file system — which you already have, located at /.

I agree with this assessment. Traditionally, on shared machines, users needed to have at least read access to many things. Thus, once you give someone even just “unprivileged” shell access, this user — not only including his technical knowledge but also the security of the setup of his own machine which might be subject to exploits — is still explicitly and ultimately trusted to see and handle confidentially all world-readable information.

The problem: Sometimes, being “unprivileged” is not enough. The expansion towards “untrusted” users.

As a server administrator you sometimes have to give shell access to some user or machine who is not ultimately trusted. This happens in the very common case where you transfer files via rsync  (backups, anyone?) to/from machines that do not belong to you (e.g. a regular backup service for clients), or even those machines which do belong to you but which are not physically guarded 24/7 against untrusted access. rsync  however requires shell access, period. And if that shell access is granted, rsync  can read out all world-readable information, and for that matter, even when you have put into place rbash  (“restricted bash”) or rssh  (“restricted ssh”) as a login shell. So now, in our scenario, we are facing a situation where someone ultimately not trusted can rsync all world-readable information from the server to anywhere he wants to simply by doing:

rsync user@host:/ .

One may suggest to simply harden the file permissions for those untrusted users, and I agree that this is a good practice in any case. But is it practical? Hardening the file permissions of dozens of configuration files in /etc  alone is not an easy task and is likely to break things. For one obvious example: Do I know, without investing a lot of research (including trial-and-error), which consequences chmod o-rwx /etc/passwd  will have? Which programs for which users will it break? Or worse, will I even be able to reboot the system?

And what if you have a lot of trusted users working on your server, all having created many personal files, all relying on the world-readable nature as a way to share those files, and all assuming that world-readable does not literally mean ‘World readable’? Grasping the full extent of the user collaboration and migrating towards group-readable instead of world-readable file permissions likely will be a lot of work, and again, may break things.

In my opinion, for existing server machines, this kind of work is too expensive to be justified by the benefits.

So, no matter from which angle you look at this problem, having ultimately non-trusted users on the system is a problem that can only be satisfactorily solved by jailing them into some kind of chroot directory, and allowing only those tasks that are absolutely neccessary for them (in our scenario, rsync  only). Notwithstanding that, and to repeat, users who are not jailed must be considered as ultimately trusted.

The solution: Low-level system utilities and a minimal jail

For above reasons regarding untrusted users, ‘hardening’ shell access via rbash  or even rssh  is just a cosmetic measure that still doesn’t prevent world-readable files to be literally readable by the World (you have to assume that untrusted users will share data liberally). rssh  has a built-in feature for chroot’ing, but it was originally written for RedHat and the documentation about it is vague, and it wouldn’t even accept a chroot environment created by debootstrap.

Luckily, there is a low-level solution, directly built into the Linux kernel and core packages. We will utilize the ability of PAM to ‘jailroot’ a SSH session on a per-user basis, and we will manually create a very minimal chroot jail for this purpose. We will jail two untrusted system users called “jailer” and “inmate” and re-use the same jail. Each user which will be able to rsync  files, but either will not be able to escape the jail, nor see the files of the other.

The following diagram shows the directory structure of the jail that we will create:

/
|-- bin
|-- home
|   |-- jailer
|   \-- inmate
|-- lib
|   \-- i386-linux-gnu
|       \-- cmov
|-- usr
    \-- bin

The following commands are based on Debian and have been tested in Debian Wheezy.

First, create user accounts for two ultimately untrusted users, called “jailer” and “inmate” (note that the users are members of the host operating system, not the jail):

adduser jailer
adduser inmate

Their home directories will be /home/jailer  and /home/inmate  respectively. They need home directories so that you can set up SSH keys (via ~/.ssh/authorized_keys ) for passwordless-login later.

Second, install the PAM module that allows chroot’ing an authenticated session:

apt-get install libpam-chroot

The installed configuration file is /etc/security/chroot.conf . Into this configuration file, add

jailer /home/minjail
inmate /home/minjail

These two lines mean that after completing the SSH authentication, the users jailer and inmate will be jailed into the directory /home/minjail of the host system. Note that both users will share the same jail.

Third, we have to enable the “chroot” PAM module for SSH sessions. For this, edit /etc/pam.d/sshd  and add to the end

session required pam_chroot.so

After saving the file, the changes are immediately valid for the next initiated SSH session — thankfully, there is no need to restart any service.

Making a minimal jail for chroot

All that is missing now is a minimal jail, to be made in /home/minjail . We will do this manually, but it would be easy to make a script that does it for you. In our jail, and for our described scenario, we only need to provide rsync  to the untrusted users. And just for experimentation, we will also add the ls  command. However, the policy is: Only add into the jail what is absolutely neccessary. The more binaries and libraries you make available, the higher the likelihood that bugs may be exploited to break out of the jail. We do the following as the root user:

cd /home
mkdir minjail
cd minjail

Next, create the home directories for both users:

mkdir -p ./home/jailer
mkdir -p ./home/inmate

Next, for each binary you want to make available, repeat the following steps (we do it here for rsync , but repeat the steps for ls ):

1. Find out where a particular program lives:

which rsync
$ /usr/bin/rsync

2. Copy this program into the same location in the jail:

mkdir -p ./usr/bin
cp /usr/bin/rsync ./usr/bin

3. Find out which libraries are used by that binary

ldd /usr/bin/rsync

linux-gate.so.1 => (0xb7798000)
libacl.so.1 => /lib/i386-linux-gnu/libacl.so.1 (0xb7774000)
libpopt.so.0 => /lib/i386-linux-gnu/libpopt.so.0 (0xb7767000)
libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xb7603000)
libattr.so.1 => /lib/i386-linux-gnu/libattr.so.1 (0xb75fd000)
/lib/ld-linux.so.2 (0xb7799000)

4. Copy these libraries into the corresponding locations inside of the jail (linux-gate.so.1 is a virtual file in the kernel and doesn’t have to be copied):

mkdir -p ./lib/i386-linux-gnu/i686/cmov
cp /lib/i386-linux-gnu/libacl.so.1 ./lib/i386-linux-gnu
cp /lib/i386-linux-gnu/libpopt.so.0 ./lib/i386-linux-gnu
cp /lib/i386-linux-gnu/i686/cmov/libc.so.6 ./lib/i386-linux-gnu/cmov
cp /lib/i386-linux-gnu/libattr.so.1 ./lib/i386-linux-gnu
cp /lib/ld-linux.so.2 ./lib

After these 4 steps have been repeated for each program, finish the minimal jail with proper permissions and ownership:

chown root:root ./bin ./lib ./usr
chown jailer:jailer ./home/jailer
chown inmate:inmate ./home/inmate
chmod 751 ./home
chmod 750 ./home/jailer
chmod 750 ./home/inmate

The permission 751 of the ./home  directory (drwxr-x–x  root  root) will allow any user to enter the directory, but forbid do see which subdirectories it contains (information about other users is considered private). The permission 750 of the user directories (drwxr-x— ) makes sure that only the corresponding user will be able to enter.

We are all done!

Test the jail from another machine

As stated above, our scenario is to allow untrusted users to rsync  files (e.g. as a backup solution). Let’s try it, in both directions!

Testing file upload via rsync

Both users “jailer” and “inmate” can rsync a file into their respective home directory inside the jail. See:

rsync -avz myfile.tar jailer@host:~

sending incremental file list
sent 75 bytes received 11 bytes 8.19 bytes/sec
total size is 24,070,782 speedup is 279,892.81

To allow password-less transfers, set up a public key in /home/jailer/.ssh/authorized_keys  of the host operating system.

Testing file download via rsync

This is the real test. We will attempt do download as much as possible with rsync (we will try to get the root directory recursively):

rsync -avz jailer@host:/ .

receiving incremental file list
rsync: opendir "/home" failed: Permission denied (13)
./
bin/
bin/bash
bin/ls
home/
lib/
lib/ld-linux.so.2
lib/i386-linux-gnu/
lib/i386-linux-gnu/libacl.so.1
lib/i386-linux-gnu/libattr.so.1
lib/i386-linux-gnu/libc.so.6
lib/i386-linux-gnu/libdl.so.2
lib/i386-linux-gnu/libpopt.so.0
lib/i386-linux-gnu/libpthread.so.0
lib/i386-linux-gnu/librt.so.1
lib/i386-linux-gnu/libselinux.so.1
lib/i386-linux-gnu/libtinfo.so.5
usr/
usr/bin/
usr/bin/rsync

sent 285 bytes received 1,639,733 bytes 142,610.26 bytes/sec
total size is 3,515,881 speedup is 2.14
rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1655) [generator=3.1.1]

Here you see that all world-readable files were transferred (the programs ls and rsync and their libraries), but nothing from the home directory inside of the jail.

However, rsync  succeeds to grab the user’s home directory. This is expected and desired behavior:

rsync -avz jailer@host:~ .

receiving incremental file list
jailer/
jailer/.bash_history
jailer/myfile.tar

sent 53 bytes received 23,773,208 bytes 1,901,860.88 bytes/sec
total size is 24,070,805 speedup is 1.01

 

Testing shell access

We have seen that we cannot do damage or reveal sensitive information with rsync . But as stated above, rsync  cannot be had without shell access. So now, we’ll log in to a bash shell and see which damage we can do:

ssh jailer@host "/bin/bash -i"

Put “/bin/bash -i”  as an argument to use the host system’s bash in interactive mode, otherwise you would have to set up special device nodes for the terminal inside of the jail, which makes it more vulnerable for exploits.

We are now dumped to a primitive shell:

bash-4.2$

At this point, you can explore the jail. Try to do some damage (Careful! Make sure you’re not in your live host system, prefer an experimental virtual machine instead!!!) or try to read other user’s files. However, you will likely not succeed, since everything you have available is Bash’s builtin commands plus rsync  and ls , all chroot’ed by a system call to the host’s kernel.

If any reader of this article should discover exploits of this method, please leave a comment.

Conclusion

I have argued that the term “unprivileged user” on a Unix-like operating system can be misunderstood, and that the term “untrusted user” should be introduced in certain use cases for clarity. I have presented an outline of an inexpensive method to accomodate untrusted users on a shared machine for various purposes with the help of the low-level Linux kernel system call chroot()  through a PAM module called pam_chroot.so  as well as a minimal, manually created jail. This method still is experimental and has not entirely been vetted by security specialists.

 

 

Exim and Spamassassin: Rewriting headers, adding SPAM and Score to Subject

This tutorial is a follow-up to my article Setting up Exim4 Mail Transfer Agent with Anti-Spam, Greylisting and Anti-Malware.

I finally got around solving this problem: If an email has a certain spam score, above a certain threshold, Exim should rewrite the Subject header to contain the string *** SPAM (x.x points) *** {original subject}

Spamassassin has a configuration option to rewrite a subject header in its configuration file /etc/spamassassin/local.cf  …

rewrite_header Subject ***SPAM***

… but this is misleading, because it is used only when Spamassassin is used stand-alone. If used in combination with a MTA (Mail Transfer Agent) like Exim, the MTA is ultimately responsible for modifying emails. So, the solution lies in the proper configuration of Exim. To modify an already accepted message, the Exim documentation suggests a System Filter. You can set it up like this:

Enable the system filter in your main Exim configuration file. Add to it:

system_filter = /etc/exim4/system.filter
system_filter_user = Debian-exim

Then create the file /etc/exim4/system.filter , set proper ownership and permission, then insert:

if $header_X-Spam-Score matches "^[^-0][0-9\.]+" and ${sg{$header_X-Spam-Score:}{\\.}{}} is above 50
then
headers add "Old-Subject: $h_subject"
headers remove "Subject"
headers add "Subject: *** SPAM ($header_X-Spam_score points) *** $h_old-subject"
headers remove "Old-Subject"
endif

This means: If the header $header_X-Spam_score_int  is present (has been added by Exim in the acl_check_data  ACL section, see my previous tutorial), and is more than 50 (this is 5.0), rewrite the Subject header. The regular expression checks if the spam score is valid and not negative.

Note that in the acl_check_data section of the Exim config, you can deny a message above a certain spam score threshold. This means, in combination with this System Filter, you can do the following:

  • If spam score is above 10, reject/bounce email from the ACL.
  • If spam score is above 5, rewrite the Subject.