Reliable and updatable access point

Hi all,

I’m working on a project that needs an access point. I’m using a Raspberry Pi 4, which has built-in Wi-Fi, but I need that Wi-Fi chip to connect to the internet. So I’m using a USB Wi-Fi dongle to create the access point.

I’ve tried the TP-Link WN722N and TP-Link WN725N. Both are not supported by NetworkManager. They use the same driver (r8188eu). So after some investigation, a version of hostapd seems like the winner. So I’ve set some udev rules in the config.json file of my device, like so:

"os": {
    "udevRules": {
      "60-tp-link-wn725n-v3-hostapd": "ACTION==\"add\", SUBSYSTEM==\"net\", SUBSYSTEMS==\"usb\", ATTRS{idVendor}==\"0bda\", ATTRS{idProduct}==\"8179\", NAME=\"ap0\", ENV{NM_UNMANAGED}=\"1\"",
      "60-tp-link-wn722n-v3-hostapd": "ACTION==\"add\", SUBSYSTEM==\"net\", SUBSYSTEMS==\"usb\", ATTRS{idVendor}==\"2357\", ATTRS{idProduct}==\"010c\", NAME=\"ap0\", ENV{NM_UNMANAGED}=\"1\""
    }
  }

So far so good, because it works :slight_smile:
Created a container with hostapd running, using interface ap0 as defined in the udev rules, and we have an access point!

But after some thinking, I don’t think this is a sustainable solution. We’ve had some problems in the past with USB dongles being discontinued and the next version having another chipset. And we’re selling hundreds of these devices a year, so we’ve to come up with something sustainable and updatable.

Some of my questions:

  • Update udev rules
    Is it even possible to update udev rules after devices are deployed? Because after some research, the only way to do it is to change the config.json. But can a container do this automatically?

  • Container udev rules
    Is it possible to create udev rules inside a container? I’ve tried it but without success. If this is possible and I can create a container with the udev rules shown above, this would fix question #1 and some headaches.

  • Best AP device
    Of course nobody knows what is the best practice or best device for setting up an AP. But is it better to use the onboard Wi-Fi chip and lose the 5GHz functionality, or use the USB Wi-Fi dongles with hostapd running in a container? Any suggestions?

  • Hotplug AP
    Maybe it’s fixable when you can set udev rules inside a container, but when a USB AP is being plugged in, the hostapd should start. Even when you plug it in after the container has started. Any idea how to do that?

I’ve tried many things, and I’m happy that I got hostapd working. But these are still some open questions that I’d like to discuss with some people.

Thanks in advance!

Hello,
To shed some light on your udev questions, yes you can update the udev rules after devices are deployed by changing the config.json file. Having a container do this automatically is not yet supported but something being looked at for future functionality (as part of the supervisor API.) However, we have an example in our playground of modifying that file after deployment using the balena CLI from another machine.

1 Like

Hi there,

Following on from Alan’s suggestion. As part of the UDEV rules in your container you may also be able to add a RUN+="/path/to/script" to trigger the starting or reloading of your AP’s userspace when the device is connected. I need to point out that I haven’t tested this though.

Your question about “Best AP device”. I don’t think we have an “officially blessed” USB wifi dongle that we prefer. It may be better to use the onboard wifi device for the access point even though you lose 5GHz support because you have much more control over the chipset and drivers in use. Ultimately I think it’s a choice for you and your use case.

Good luck with your project and let us know how you get on!

Thanks,
James.

2 Likes

Hi Alan and Jim,

Thanks for responding!
I’ll take a look at changing the config.json from inside a container. Adding SSH keys is also one thing I’d like to do from inside the container, so your example is a perfect place to start! Adding these functionalities in the supervisor API would be awesome.

About the UDEV rules, I had the same idea about running the script when adding the USB for the hotplugging part. But I didn’t get the UDEV rules to work from inside a container. Only using the config.json on the HostOS. I’ve added ENV UDEV=1 to the Dockerfile and made it a privileged container, copied the UDEV rule from the config.json (which works), and added a RUN. But it doesn’t get triggered. With udevadm monitor inside the container, I see the events when plugging in the USB, but no UDEV rule gets triggered.

Besides that, is it also possible to add the ENV{NM_UNMANAGED}=1 from inside a container, or is it too late then? I haven’t tried that out yet, because it doesn’t even trigger when using a RUN. But it’s something to keep in mind if it isn’t possible.

And I know there isn’t a ‘Best AP device’. But your answer about using the onboard wifi chip because we’d have more control over the chipset and drivers in use is something that I’ve thought about. I can use NetworkManager for the AP when using the onboard chip, which is a big plus. And every USB Wi-Fi dongle that’s plugged in can be used as a Wi-Fi client without complications. The only thing is, we’d have to figure out what’s more reliable as a Wi-Fi client. Because the AP is going to be used a few times, but the Wi-Fi client connection is more important.

So thanks to both of you for your input, and I hope someone can help me with the UDEV inside a container :slight_smile:

Hey Bart, I think this example will give you an idea about how to run UDEV rules inside a container: https://github.com/tmigone/balena-udev-examples. There is also a more detailed blogpost here: https://medium.com/swlh/hot-plugging-usb-devices-in-balenaos-5dca4b05ca7e that explains how UDEV works in general (in that case, applied to balenaDash). Let us know if that is enough for you to make it work, and we can assist you further if you get stuck somewhere. Cheers!

Hi,

Thanks for your response.
I’ve found those links already, and I didn’t manage to do what I want it to do.

But I think it’s not possible what I want to do. I want to change the network device name of the onboard chip from within a container. But because the host OS has started already, I think it’s not possible to change this later on from within the container, is it?

Hey, I’ll talk to one of our balenaOS engineers and will return back to you with more information.

1 Like

I’ve been busy with using the onboard wi-fi chip, and creating something like the wifi-connect project by @majorz (if I’m right?).

I’ve created a NetworkManager library in NodeJS that communicaties with the dbus API. I’ve managed to create an access point config and activate it on wlan0, if it’s not connected to the internet yet. But when the AP is activated, I can’t get a list of all access points, because it’s in AP mode. Is there a way to scan all Wi-Fi access points while in AP mode with NetworkManager?

I’ve checked the code of the wifi-connect project on how they manage to do it, but I’m not familiar with Rust. I’ve googled in many different ways, but nobody seems to have this problem or nobody has tried it before…

It’d be awesome if there’s a way to scan and be an AP at the same time.

I think wifi-connect does it by scanning for networks before it starts the access point. Is you AP going to be running for a long time? In that case you might have to drop out of AP mode once in a while to re-scan

2 Likes

Thanks for the info!

Other idea, is it possible to set the bluetooth in a Raspberry Pi in tethering mode? So that it’s a kind of network device? That way, I can also run a HTTP server on the bluetooth device and use that as a way to connect the onboard chip to Wi-Fi.

Added question: Is it possible to read & write the /boot partition (with the config.json & config.txt) from within a container? My idea is to create wifi-connect like software, but when the AP doesn’t show and you’d like to force it, you can add a file like access-point.balena to the boot partition on the SD card and when it boots, it forces the AP to start.

Hello there, as you say and as far as I’m aware you can’t scan for networks while in AP mode, you need to do it before entering that mode otherwise the only network you will get back is the AP one.

Regarding setting the pi to tethering mode using bluetooth, perhaps this blog post is of use: https://www.balena.io/blog/control-your-devices-from-a-web-browser-using-bluetooth/ It describes a way of exchanging data via bluetooth with a Pi device.

Lastly, about the /boot partition. It’s not possible to modify files as the rootfs is read-only. Even if you remount the partition as writeable the network files on the /boot partition are only used for the initial setup and are ignored later. Seeing that you are developing in nodejs perhaps this project is of interest or inspiration: https://github.com/balenalabs-incubator/wifi-repeater If you check the code you’ll see several ways of manipulating NetworkManager via DBUS to create access points, connect to wifi networks, etc. Perhaps if you share a bit more on what exactly do you mean with “forcing it” I can provide extra help in that regard.

Also, are able to share a link to the library you created? Always interesting to check others implementations. Cheers!

1 Like

Thanks for your reaction!

I’ve seen the blog and GitHub playground of that blog, but it uses Chrome bluetooth functions. That’s really neat, but our customers are not really technical. So we want a simple solution that works on many devices. Probably 9 out of 10 times the Wi-Fi will be configured via a phone or tablet. But other than that, awesome project!

I’ll definitely take a look at the wifi-repeater project for inspiration. I’ve already made a library for connecting with Wi-Fi networks and creating an AP via the NetworkManager DBUS in Node.js. My plan was to make this library open-source and maybe make an NPM package so that it can be used by others!

And what I mean by forcing it, our current plan is using the onboard Wi-Fi chip as an AP at first, and use that to configure it as a client, just like the wifi-connect project. But it has to monitor if it still has an internet connection all of the time, because if the connection is lost for some reason, it has to change back to an AP. If this fails, it’d be nice if you can somehow force the device to create that AP. One of our ideas was, just like the Raspbian SSH file (#3), to add a file to the boot partition on your PC, and when that file is found, it forces the AP to be set. But if we can’t access the boot partition, we’ve to find another way.

Another thought that came to mind. Is it possible to detect if there’s a client connected to the AP, when the onboard Wi-Fi chip is in AP mode? That way we can monitor if anyone’s connected at the moment.

I think you can definitely achieve what you are saying with dbus. If I understood you correctly you can:

  1. Create an AP on boot/init. Clients connect to the AP and configure you device (presumably providing WiFi credentials?).
  2. After configuration, device removes the AP and connects to the internet
  3. At any point if internet access is lost, device needs to go back to step 1.

For this scenario, the wifi-repeater project will get you almost there. You can check the code at https://github.com/balenalabs-incubator/wifi-repeater/blob/master/src/nm.ts where you already have utilities to create an AP and connect to a wifi. Pair that with polling the NM for connectivity and you only need to solve the webserver/interface for your clients to connect to in case internet goes down. I’m actually the dev for that project and plan on adding this functionality (see https://github.com/balenalabs-incubator/wifi-repeater/issues/1) at some point.

About the client list in AP mode, I can’t find a way of doing it through NM.

Also, please share your library once you open source it! That sounds awesome.

1 Like

I’ll figure out a way to get the client list in AP mode. There has to be a way to get this information, right?

Also, is it possible to change the IP address range? In our previous version, we had the 172.24.1.0/24 range, and we’d like to maintain this range so that our customers can still use that range. I’ve tried it with settings the ipv4.addresses, but connecting to the AP doesn’t succeed.

Hey bart,

According to NM documnetation, you should be able to set the DHCP IP range for your clients. Regarding the client list, this thread could serve as a good starting point.

Thanks for reaching out and please let us know how it goes.

Thanks for linking me to the documentation and the thread about the client list. I’m going to figure out if I can get a nice client list via Node.

Just an update about the NM library. I’ve made some progress in scanning access points and configuring the client. The best part is the scanning. The software requests a scan, waits for the PropertiesChanged signal of LastScan and then gets all access points. If it fails, it handles it quite nicely. For example, if you trigger the RequestScan too fast, you get the error that you can’t scan immediately following the previous scan. If that happens, it just tries it again, with a maximum of 5 times. This happens really fast, so no issues there.

Also, I’ve created a function for my access point for scanning. If I do a request that triggers the AP to scan, it checks if it is in AP mode right now. If it is, it disables the AP and saves the connection path. Then, it scans. And after the scan, it activates the connection again and it becomes an AP again. And this is not even the best part, because if I connect to the AP, do the request on the AP’s IP address, I get a response back and my computer doesn’t even disconnect from the AP because it’s too fast! (About 4 seconds).

I’ll test some more, but if it’s the case that it can scan while being an AP, and I get a response without losing connection, we’ll go with the onboard Wi-Fi chip obviously!

Only downside is that when you instruct the onboard chip to connect with a Wi-Fi network, and for example the password is wrong, you will not get feedback because the AP is gone. But that’s just something we’ve to accept probably.

Hi @bversluijs,

Nice to read about your progress with this.

I was recently looking at AP+STA mode support in NetworkManager - this is having one real client (STA) interface and a virtual one (AP) running on the same chip. I could make it work on my Laptop, but it was not working on a RPi 3 device. Since you are using RPi 4 you may have better luck with that. If you can make it work then you won’t need a second dongle attached to the RPi and you can optimize the logic flow of your application including the issue from your paragraph.

To do that you can run iw dev wlan0 interface add vap0 type __ap addr 12:34:56:78:ab:cd. This will create a vap0 interface and on top of it you can create an AP NetworkManager connection profile. If that works on RPi 4 it could be helpful for your use-case. I am definitely looking at continuing to debug this on the RPi 3 and others and have it working under NetworkManager, though not having a timeline for this.

Another idea: you can possibly benefit from is using the libnm library directly. This way you won’t have to deal with D-Bus deficiencies like changing object IDs and so on, since libnm takes care of this internally. You can use it with NodeJS through the node-gtk package like this:

const gi = require('node-gtk');
NM = gi.require('NM, '1.0');
...

Unfortunately I could not find the node-gtk code I was experimenting with. You should be fine with using D-Bus directly though.

For reading the list of connected clients to the AP, I have looked into that before, but do not remember the exact details. I remember checking how projects like DD-WRT and OpenWRT implement it. Using iw or the corresponding netlink calls to the kernel (the ones that iw uses internally) should be fine.

Thanks,
Zahari