I have spent a fair amount of time recently thinking about bootstrapping network configuration in IoT devices (in particular headless devices that do not allow system access).
Traditionally, most devices fall into one of the two:
- DHCP (or a static hard coded IP), and then a web interface for configuring advanced network (think switches, routers and similar)
- Ad-hoc WiFi which uses a captive portal for configuring the network (think Chromecast etc)
The former works well for wired devices where you have a tech savvy user and where you only bring up one device at the time. The latter on the other hand works great for consumer devices where you usually only configure one or two.
The premise of Core is to be the foundation to build on for IoT products. Networking is of course a fundamental part of modern IoT. Moreover, it is highly desirable in many situations to not let the customer access the actual device, but rather have it fully locked down and just receive updates. That wouldn’t necessarily rule out either of the two above, but neither of them are very scale-able if you’re deploying say 100+ devices.
A few years ago we added support in Screenly for reading a file called network.ini
on the boot partition of the Raspberry Pi. We also created a tool (wizard.screenly.io) for generating these files. The idea was simply that you can generate this once and then just copy it in every time you need a new file.
Raspbian recently adopted a similar model, but using wpa_supplicant.conf
(and NOOBS does something similar IIRC). If the file exist on the boot partition, it will copy it into the system during the boot process. The problem with this approach is that it only takes into account wireless configuration, while our solution also added support for things like DNS and NTP (as well as Ethernet configuration).
We are right now in the process of adopting this into our Core migration, but that got me thinking. This is a very common situation, and it would make sense to have this in the public domain instead of having everyone reinventing the wheel.
What I’m thinking is that we would create a rather universal configuration file that can define things like:
- Ethernet configuration (static, DHCP)
- We’d also need support for 802.1x etc at some point, so that’d need to be considered
- Wireless configuration [1]
- In addition to regular WPA/WPA2 etc, we’d also need to support WPA2 Enterprise
- Hostname and domain config
- NTP servers
- DNS servers
- Arbitrary application configs (such as API keys)
This would then be combined with some lookup logic during the boot:
- Priority 0: USB mass storage drive
- Priority 1: boot partition
If neither of the files exist, then silently skip it and use DHCP on Ethernet. The benefit of both being able to read it from USB and boot partition is that it covers both the use case of a box product (i.e. manufactured in large quantities) and home users who flash out themselves. Moreover, there is an interesting discussion going on over in Etcher’s Github repo about being able to make this part of the SD flashing workflow.
Perhaps out of the scope of this, but in a perfect world, we’d also add a final step that would kick in if the system boots up and there is no active connection, we’d spin up a hotspot with a captive portal for network configuration with simple options (i.e. no WPA2 enterprise etc) [2].
To date there is no such standard (to my knowledge). It would make a lot of sense to create such standard and we’re happy to put our work in the public domain. If the config file can be standardized, it would be rather straight forward to do clients that can consume the file both in Raspbian and Core.
File format
The two obvious candidates for formats would be YAML and JSON. Both have pros and cons. The primary benefit with YAML would be that it is more human readable and that it’s fairly intuitive to embed something like a snippet in it (say if we want to embed wpa_supplicant.conf
for instance).
To wpa_supplicant or to not wpa_supplicant
There’s already an API exposed for Network Manager through Core. For simple network configurations, that’s fine, but if you were to do something more complicated, such as this example, that would be rather challenging.
As such, there is an argument to be made that it would be possible to pass on full wpa_supplicant files as part of the standard. This could be embedded in the configuration file. On Raspbian that would be easy to simply write to disk, but on Core unfortunately that is not possible to my knowledge as Core 16 doesn’t use wpa_supplicant.
Footnotes
[1] This is really where wpa_supplicant.conf
excels. It’s widely used and many users simply copy their files across platforms. Perhaps we would need support for consuming wpa_supplicant.conf
files too to cover all user cases, but that will be challenging on Core since it doesn’t support it (afaik).
[2] This gets a bit tricky since we need to have some kind of logic determining if the device is just temporarily offline (e.g network outage) or if it actually does require network reconfiguration.