Posts

Building a DIY SOHO router using the Yocto Project build system OpenEmbedded, Part 2

In part one of this series I explained some of my motivations for this project. Now it’s time to start on implementing the project itself. At this point I’m going to assume the reader has basic familiarity with using OpenEmbedded.  Otherwise, the Yocto Project (YP) quickstart guide and OpenEmbedded getting started pages are useful to bring yourself up to speed.  I assume that you’ve followed these instructions on how to prepare your build host and gone so far as to have completed a build for some target previous to this.  In this guide, I’m going to do my best to follow common best practices. Whenever I deviate from best practices, I’ll explain why we want something a bit different. After all, best practices are supposed to be taken as guidelines, not absolutes. Finally, I’m going to be working against the Yocto Project thud code-name release as that’s what is current as of this writing.

The first thing I’m going to do in my project is create a build directory now so that I can easily get access to various tools.  The next thing I’m going to do is use those tools to make a layer to store our configuration in.

$ . oe-core/oe-init-build-env
You had no conf/local.conf file. This configuration file has therefore been
created for you with some default values. You may wish to edit it to, for
example, select a different MACHINE (target hardware). See conf/local.conf
for more information as common configuration options are commented.
...
You can also run generated qemu images with a command like 'runqemu qemux86'
$ bitbake-layers create-layer ../meta-local-soho NOTE: Starting bitbake server... Add your new layer with 'bitbake-layers add-layer ../meta-local-soho' $ bitbake-layers add-layer ../meta-local-soho NOTE: Starting bitbake server... $

Be sure to change oe-core to wherever the core layer was checked out.  Now that the layer has been created, let’s go ahead and start by putting it into git. Why? I am a firm believer in “commit early and commit often” as well as “cleanup and rebase once you’re done”. To me, one of the big selling points of git is that you can track your work incrementally, and when you notice unexpected breakage later on you can easily go back in time and locate the bugs.

$ cd ../meta-local-soho
$ git init .
$ git add *
$ git commit -s -m "Initial layer creation"
$ git branch -m thud

With the first commit in place, it’s time to start customizing the layer. We don’t need the example recipe, so let’s remove it.

$ git rm -r recipes-example/example
$ git commit -s -m "Remove example recipe"

Next, it’s time for a more substantive set of customizations. I’ll start by editing the README. Why? To start with, I’m going to list all of the layers I know of that are dependencies at this point. The README will also be a handy place to note which physical port is for WAN, which port(s) are for LAN, and the network devices that each port is associated with. For now, I add the following to the README:

  URI: git://git.openembedded.org/meta-openembedded
  branch: thud
  layers: meta-oe, meta-python, meta-networking, meta-filesystems

  URI: git://git.yoctoproject.org/meta-virtualization
  branch: thud

  URI: https://github.com/mendersoftware/meta-mender
  branch: thud

Now that I’ve documented these requirements, I’ll also enforce them in code. I edit conf/layer.conf and document these requirements too. The end of the file should look like:

LAYERDEPENDS_meta-local-soho = "core"
LAYERDEPENDS_meta-local-soho += "openembedded-layer meta-python"
LAYERDEPENDS_meta-local-soho += "networking-layer filesystems-layer"
LAYERDEPENDS_meta-local-soho += "virtualization-layer mender"
LAYERSERIES_COMPAT_meta-local-soho = "thud"

Since there are so many layers in use, I will make use of the TEMPLATECONF functionality so that the build directory will be populated correctly to start with. Next, I copy over meta/conf/bblayers.conf.sample, meta/conf/local.conf.sample and meta/conf/conf-notes.txt from the core layer over to the conf directory and commit them without change. Why? This will isolate my local edits later on and make my life easier next year when I decide it’s time to update to a current release. After I’ve committed those files, I the edit conf/bblayers.conf.sample and change it to look like:

BBLAYERS ?= " \
  ##OEROOT##/meta \
  ##OEROOT##/../meta-openembedded/meta-oe \
  ##OEROOT##/../meta-openembedded/meta-python \
  ##OEROOT##/../meta-openembedded/meta-networking \
  ##OEROOT##/../meta-openembedded/meta-filesystems \
  ##OEROOT##/../meta-virtualization \
  ##OEROOT##/../meta-mender/meta-mender-core \
  ##OEROOT##/../meta-local-soho \

Note that this assumes a directory structure where I’ve put all of the layers I will use in the same base directory. If I didn’t do that then I would need to adjust all of the paths to match how to get from the core layer to where the layers are stored. Now I want to use git add and commit all of these changes, so I can move on to the next step.

I will enable systemd next, and while there’s a number of places I could do this, including going so far as to create my own “distro” policy file, for this article I’ll just use conf/local.conf.sample to store these changes. While the core layer’s conf/local.conf.sample.extended has an example on switching to systemd, I’ll do it slightly differently. At the end of the conf/local.conf.sample insert the following, git add and then commit:

# Switch to systemd
DISTRO_FEATURES_append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
VIRTUAL-RUNTIME_initscripts = ""
VIRTUAL-RUNTIME_syslog = ""
VIRTUAL-RUNTIME_login_manager = "shadow-base"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"

This differs from the core example in a few ways. First, I blank out pulling in an initscripts compat package. While this can be useful, for these images it’s going to end up being redundant. Next, I blank out a syslog provider as I’ll be letting systemd handle all of the logging. Finally, while busybox can be available and provide the login manager, I’ll be using shadow-base instead. This particular change is something now done upstream and will be in later releases, so keep that in mind for the future.

The next functional chunk for conf/local.conf.sample is enabling support for virtualization through meta-virtualization. That layer has its own well documented README file, and taking the time to go over it is a great idea. In my configuration, I’m just going to enable a few things that give me the minimal functionality. So add, git add and git commit:

# Add virtualization support
DISTRO_FEATURES_append = " virtualization aufs kvm"

When talking about system security there are many aspects. One aspect is to harden the system as much as possible by having the compiler apply various build-time safety measures that turn certain classes of attack from “exploit the system” to “Denial of Service by crashing the application”, or even “this is wildly unsafe code, fail to build”. To enable those checks in the build, add, git add and git commit:

# Security flags
require conf/distro/include/security_flags.inc

There’s one last bit of functionality I want in the conf/local.conf.sample file, and that’s support for Mender. How do I go about that? That’s going to depend a bit on what hardware you’re going to do this project on, as it’s a little bit different for something like the APU2 than on an ARM platform. Fortunately, there’s great documentation on how to integrate Mender here. While reading over all of the information there is important, it’s best to focus on the Configuring the build section to understand all of the required variables. In fact, now is a good time to talk more about how I’m going to use Mender in this particular setup. Looking at the overall Mender documentation, it’s very flexible. For this very small deployment scenario, I’ll use standalone mode rather than managed mode. This lets me skip all of the things about setting up a host of other services to make upgrades happen automatically. So, we’ll follow the instructions for configuring Mender in standalone mode. The next thing to touch on is how to handle persistent data. One way to do this would be to make sure that anything which needs to be manually customized or that will be persistently changed at runtime is written somewhere under /data on the device rather than at its normal location. This is because the normal location is going to change when I apply a new update but /data will always be the same. For a lot of deployments, this idea works best, as there are likely many users, and beyond our initial configuration the end user will make changes I know nothing about. For my use case, I am the user, and making the system as stateless as possible will in turn make backing the system up as easy as possible. So instead I will be modifying recipes to include our local configuration when needed. Having an easy to access and modify backup of the various configuration files and so forth will make my life easier in the long run if, for example, something happens to the hardware and it needs to be replaced.

Now that I have everything configured, it’s time to create the build directory. This will let me take advantage of all of the configuration work I just did. At this point, I can go back to the shell, leave meta-local-soho and do this:

$ TEMPLATECONF=../meta-local-soho/conf . oe-core/oe-init-build-env build-router

Again, be sure to change oe-core to wherever the core layer was checked out. Running cat conf/bblayers.conf will show how the changes that were made were now expanded.

What to do next? Well, that depends a little on what I already know about the system. The next step in this project is to configure systemd to take care of creating the networks. If I knew what all of the devices will be named, I could move on to that step. But if I didn’t know, or wasn’t sure, the next step is to build core-image-minimal. That’s as easy as doing:

$ bitbake core-image-minimal

and waiting for the final result, assuming that local.conf.sample file was configured to default to the appropriate target MACHINE. Otherwise I’ll need to pass that in on the command line above. Once that completes, take the appropriate image and boot it. The reward should be a root login prompt.  In this configuration, there’s no root password right now. Login and do:

# ip link

and this will show me what the interface names are. Assuming there is a DHCP server somewhere already, I can then use udhcpc -i NAME after plugging in an Ethernet cable to see which interface is which and note it down for the next step.

Now that I have my network interface names, I can configure systemd to handle them. In my specific case I have 4 Ethernet ports, and coincidentally the one closest to the physical console port (enp1s0) is the one I wanted to call the WAN port. This lets me put the other 3 ports into a bridge for my LAN. Now, to configure these interfaces, I’m going to dump some files under /etc/systemd/network/, and I will use the base-files recipe to own these files, rather than systemd itself. Why? Whenever a change forces a rebuild of systemd that forces a rebuild of a lot of other packages, so this allows me to isolate that kind of build churn. Now I go to meta-local-soho and enter the following:

$ mkdir -p recipes-core/base-files/base-files
$ cd recipes-core/base-files/base-files

I’m going to create four different files. Note that all of the filenames are arbitrary and are meant to help poor humans figure out what file does what. If another naming scheme is helpful, it can be used just as easily. First I’ll take care of the WAN port by creating wan-ethernet.network with the following content:

# Take the eth port closest to the console port for WAN
[Match]
Name=enp1s0

[Network]
DHCP=yes

Next, I’ll create our bridge for the other ports and this is done with two files. First I need a lan-bridge.network with:

# Bridge the other 3 remaining ports into one.
[Match]
Name=enp2s0 enp3s0 enp4s0

[Network]
Bridge=br0

Second I create br0.netdev with:

[NetDev]
Name=br0
Kind=bridge

And now I have a bridge device that I can use to manage all of our LAN ethernet ports. Later I’ll even put the AP on this bridge for simplicity. Now that I have a bridge, I need to configure it, so create bridge-ethernet.network and add:

[Match]
Name=br0

[Network]
Address=192.168.0.1
IPForward=yes
IPMasquerade=yes
IPv6AcceptRA=false
IPv6PrefixDelegation=dhcpv6

[IPv6PrefixDelegation]
RouterLifetimeSec=1800

There’s a lot in there, but it can be broken down pretty easily. Working my way up from the bottom, the first portion is needed to have the IPv6 address on the WAN port pass along what’s needed to the LAN to have devices configure themselves. Since we still live in a world with IPv4, I need to enable masquerade and forwarding. Finally, I configure a static IP for the interface that matches to br0. I haven’t talked about configuring the LAN for IPv4 yet. This is going to be a bit more complex and while systemd supports a trivial DHCP server, I want to do more complex things like assign specific addresses, so that will come later.

Finally, it’s time to make use of these new files. I’ll head up a level and back to recipes-core/base-files and create base-file_%.bbappend with the following:

FILESEXTRAPATHS_prepend := ":${THISDIR}/${PN}"

SRC_URI += "file://wan-ethernet.network \
            file://lan-bridge.network \
            file://bridge-ethernet.network \
            file://br0.netdev \
" do_install_append() { # Add custom systemd conf network files install -d ${D}${sysconfdir}/systemd/network # Add custom systemd conf network files install -m 0644 ${WORKDIR}/*.network ${D}${sysconfdir}/systemd/network/ install -m 0644 ${WORKDIR}/*.netdev ${D}${sysconfdir}/systemd/network/ }

What this does is to look in that directory where I created those 4 files, add them to the recipe and then finally install them on the target where systemd will want them. At this point, I can git add the whole of recipes-core/base-files, and git commit.

There’s one last thing I want to configure right now. I’m going to have systemd handle things like turning on NAT, and doing some other basic iptables work. As a result, I need to enable that part of systemd. Another thing I’ll want to do here is work around a current systemd issue with respect to bridges. To do so, I need to make the directory recipes-core/systemd/ and create the file systemd_%.bbappend in that directory with the following:

do_install_append() {
    # There are problems with bridges and this service, see
    # https://github.com/systemd/systemd/issues/2154
    rm -f ${D}${sysconfdir}/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service
}

PACKAGECONFIG_append = " iptc"

The first part of these changes is to work around the github issue mentioned in the comment. While it’s quite frustrating that the issue in question has been open since the end of 2015, I can easily work around it by just deleting the service for my use case. The second part is to add iptc to the PACKAGECONFIG options, and in turn that part of systemd will be enabled and it can support iptables so the IPMasquerade flag above will work.

At this point, I can now build core-image-minimal again and have a system that will automatically bring up the network. However, it is not a router yet. In the next part of the series I will create a new image that is intended to be a router and customize that.

Building a DIY SOHO router using the Yocto Project build system OpenEmbedded, Part 1

I spend my days working on embedding Linux in devices where the end user doesn’t typically think about what’s running inside. As a result, I became motivated to embed Linux in a device that’s a little more visible, even if only to myself. To that end, in this series of articles I will discuss how to build your own SOHO router using the Yocto Project build system, OpenEmbedded.

I realize that many people may be asking the question, “Why build our own router when there are any number of SOHO routers available on the market that are specifically built using good Open Source technologies?”. Commonly, when this question is answered in other guides the major reasons for Roll Your Own (RYO) tend to be better performance and enhanced security. While these are both true, that’s not the primary motivation behind this series. While projects like pfSense and OpenWrt are wonderful for a “turn-key solution”, they don’t meet one of my primary requirements. My requirement is to use my favorite Linux distribution creation tools, and gain a stronger understanding of the inner working of these tools when building a system from the ground up. This is why I’ve chosen to build my router with OpenEmbedded.

What are the Yocto Project and OpenEmbedded? To quote from the former’s website:

The Yocto Project (YP) is an open source collaboration project that helps developers create custom Linux-based systems regardless of the hardware architecture.

The OpenEmbedded Project (OE) is the maintainer of the core metadata used to create highly flexible and customized Linux distributions and a member of the Yocto Project. As a long time developer and user of YP and OE, these projects have become my favorite tools for creating customized distributions. It’s also something that we frequently use and support commercially at Konsulko Group.

Let’s consider what software is required to meet the functional requirements of the SOHO router. My high-level requirements are:

  • Wired/Wireless networking – basic network connectivity
  • Firewall – necessary when a device is on the Internet
  • Wireless access point – required to support the end user’s many WiFi devices
  • Over-The-Air (OTA) software update – My initial software load will be improved and bug fixed in the same manner as any commercial product and requires a simple update path

OpenEmbedded provides all of these features as well as other advanced features I’d like to leverage such as container virtualization.

With the question of software features settled, the next issue is hardware selection. One of the major advantages of OpenEmbedded is that it runs on a wide range of processors and boards, so there are many options. Depending on one’s specific use case, one option is to use any x86-64 based system with two or more ethernet ports. For example, the SolidPC Q4 or the apu2 platform. Another direction would be to use an ARM CPU and look at the HummingBoard Pulse, NXP i.MX6UL EVK, or even the venerable Raspberry Pi 3 B, and making use of a USB ethernet adapter for secondary ethernet. If you have a specific piece of hardware in mind, so long as there’s Linux support for it, you can use it. I did pick something from the above list for my specific installation. However, this guide is intended to be hardware agnostic and so I will highlight any hardware-dependent portions along the way.

Now it’s necessary to further develop detailed requirements and expectations that we have for the router. First, the device is expected to be able to perform all of the standard duties of a modern SOHO router. It’s not just a WiFi access point and IPv4 NAT. It also must handle complicated firewall rules, and support public IPv6 configuration of the LAN (when the ISP supports IPv6). This is an advanced router, so it’s not enough to simply pass along the ISP-defined DNS servers. I may like to push my outbound traffic, with a few exceptions for streaming services, perhaps, through a VPN. OpenEmbedded, via various metadata collections stored in layers, supports all of these features with its core layer and a few of the main additional layers.

I’m selecting Mender for self-managed A/B style OTA upgrades. Why? I don’t have redundant routers, and I’d like to be able to both experiment with enabling new features and updates (such as a new Linux kernel version or other core component), firewall changes, and minimize downtime if anything goes wrong. Neither myself nor my users (my family) are tolerant of much in the way of Internet outages, so the ability to fall back to a known good state with just a reboot is a major feature. I want the router to be kept as current as possible, and with A/B style updates there’s much less state to maintain from release to release. As a result, the router installation will be made as stateless as possible. If a component parameter is configured by the router administration, it comes in the installation image. This will not only help with rollback, but also make it much easier to back up the router. Having the exact config file for something like dnsmasq is a lot more portable than an nvram export that’s tied to a specific hardware model, just in case of lightning strikes.

As a framework component for enabling some advanced router features, LXC will be included via the meta-virtualization layer to support containers. I realize that many people will be asking, “Why would anybody want containers on a router?”. The answer is, quite simply, that there are many good examples of router-appropriate software that’s well managed by containers rather than directly on the installation itself. In this guide, I use the example of pi-hole as an advanced router feature that I want to enable. A container allows us keep it current in the manner which the project itself recommends. The container also supports the goal of keeping a minimum amount of state on the device that must be backed up elsewhere. Could Docker have been used here instead? Potentially, yes. Due to some peculiarities of the IPv6 deployment by my ISP, LXC is much easier to deploy than Docker.

Finally, lets talk about security. For this first guide, what that means is that YP does its best to keep software up to date with respect to known security issues as well as making it easy to enable compiler-generated safeguards such as -fstack-protector-strong and string format protections. On top of that there are layers available which support various forms of owner-controlled measured boot and various Linux Security Models such as Integrity Measurement Architecture (IMA). As the former is hardware dependent we’re going to leave that to a follow-on series as the specific hardware I’m using lacks certain hardware components to make that useful. As for IMA, it can be difficult to combine that with containers so it too will be covered in another series.

The Year in Review

2018 has been a very important year for Konsulko Group.

We are privileged to work with outstanding customers, helping them build the software for exciting and essential devices and vehicles. From Level 5 autonomous taxis and open source automotive platforms to innovative consumer devices, from lifesaving medical devices and advanced robotic surgery tools to the high-end networking equipment that powers the Internet, these are all products that impact and even shape our lives, now and into the future.

In addition to our commercial activity, we continued our community work in key open source projects, including the Linux kernel, Yocto Project, OpenEmbedded and Automotive Grade Linux, and participated at open source conferences around the world, like FOSDEM, ELC/ELCE, FOSSASIA, TuxCon and OpenFest.

We accelerated our work with Automotive Grade Linux and the Linux Foundation, supporting AGL development, demos and member meetings in North America, Europe and Japan, as well as presenting multiple technical talks at all four Embedded Linux Conferences, developing the AGL Deep Dive workshop and providing expert training, both as a Linux Foundation Authorized Training Partner and via the Embedded Apprentice Linux Engineer program found at leading conferences (e-ale.org).

Finally, we continued to grow our company, welcoming new engineers, increasing the size of our European development center in Bulgaria and opening a new branch in Sweden.

Hopefully, 2018 has been merely a prelude to great things coming in 2019. We look forward to working with all of you in the coming year.

Don’t be afraid to get help with the basics

Fifteen years ago, we used to say that getting an embedded Linux kernel and drivers successfully running on your target hardware got you about 10% of the way to your finished product. Now, thanks to OpenEmbedded and the Yocto project, you can do a lot better than that, but there are usually a few technical challenges along the way.

At Konsulko Group, we’ve seen many of these issues (often repeatedly) in the world of Yocto/OE, and we are often called upon by our clients for this “basic” work. If you are moving to new hardware or building the next generation of your device, we can help with your hardware bring-ups, “upreving” your software stacks, porting your unique OS work, determining if rearchitecting is necessary, and providing on-going maintenance.

Don’t hesitate to contact us, no matter how large or small your technical challenge. Chances are that our past two decades of experience can solve your problems very efficiently, so you can spend your time focussing on the things that make your product unique.

Konsulko joins Developer Panel Keynote at AGL AMM in Tokyo

At the Automotive Grade Linux All Member Meeting at the Hilton Tokyo, Scott Murray, Konsulko Group Senior Staff Software Engineer, will bring his 20+ years experience developing with Linux, plus expertise building custom distributions with OpenEmbedded and the Yocto Project, to the Developer Panel Keynote on Tuesday, February 20, 2018, 11:10am – 11:50am. It promises to be a lively and informative discussion among some of the top engineers working with AGL. If you are planning to attend the AMM, don’t miss this session. We hope to see you in Tokyo!