Raspberry Pi 3 and additional network dongle


#1

Hi all! I have the need to establish a 2nd wireless connection, using a separate USB wifi dongle on my resin managed raspberry pi. It’s unclear to me on where to look at how this can be done. the config.json doesn’t seem to indicate specific interfaces in the “files” section.

How can I bring up a specific wifi connection on wlan0 and a different wifi connection on wlan1?


#2

Good question, we are checking it out how it would work with the network setup on the device. In the meantime, what sort of use case you have in mind?


#3

I have a fleet of drones, with onboard Raspberry PIs communicating with the onboard flight controller (via serial). We need the primary wifi adapter communicating with the network at the home location (configured to communicate with a real internet capable wifi link). In additional to this, there is an onboard Gopro camera we’re connecting with on Gopro’s dedicated wifi network (Gopro cameras act as their own access point). We would like to do this by having the Pi join a second wifi network (the Gopro’s network) using a separate wifi dongle.


#4

So I tried configuring the second WIFI adapter from within the docker, by installing wpa_supplicant and associating it from there. This doesn’t seem to work, wpa_supplicant is unable to establish a connection. This approach works fine in a native (Jesse) environment on the Pi. I’m guessing this is either because: A) I really can’t do this from within a container (even with privileged mode) or B) wpa_supplicant isn’t compatible with whats running on the host.

Which raises a question, is it possible for me to connect / login to the host itself while it’s running (instead of the container)? I’d like to see what’s going on there.


#5

wow, that’s a very cool use case! :helicopter: @joe, our device networking expert is also working with drones these days, I’m sure he’d have some thoughts on this :smile:

For reference, host OS 1.x series are using Connman, while the upcoming 2.x series switched to NetworkManager, which is more flexible. Here’s a project, called resin-wifi-connect, and while its aim is different, the code shows some examples of how to manipulate network connections from the container (both from Connman and NetworkManager). Need to take some care setting up networking code, as, as this way if your device loses network connection, you might not be able recover it. If you have physical access to the device, you can always just reflash, though.

Looking at the docs of NetworkManager.conf, there should be settings to set up with specific wireless interface (by name or by mac), and then you don’t need any of the coding mentioned above, just properly prepared config file dropped on the SD card on the /resin-boot partition’s /system-connections directory. If you want to try out these, the beta releases of 2.x series are available at the staging website (which is our testing ground, first to get new stuff, but not guaranteed to work, just for testing) at https://dashboard.resinstaging.io .

Will try it out next week as well, should be an interesting example! And the GoPro control is super interesting, just found python-goprohero by searching around based on your description. :thumbsup:

That’s not really the way forward, unfortunately, this FAQ should give some explanation: Why can’t I SSH into or run code in the HostOS?


Changing to DHCP
#6

Thank you very much! I will check these out. I’m looking at the resin-wifi-connect code right now :slight_smile:

The Gopro has an incredible API with dozens of REST endpoints, it’s pretty awesome in this regard, but ultimately frustrating in that in insists on being an AP (which adds a lot of avoidable complexity to this, and other potential Gopro projects).

On the host access, I do understand where you’re coming from in the FAQ. I won’t belabor the point as I understand it’s part of your project philosophy, but I would encourage you to revisit that decision at a future time. Being able to access the running host (perhaps by setting a temporary boot or diagnostic “flag”) can go a long way for developers to help track down potential issues, develop container-side applications (such as the wifi project you referenced above) with proper knowledge of what’s really going on, and lastly generate new ideas and more direct feedback for future enhancements.

(Ok, I guess I did belabor the point :slight_smile:, but not trying to argue - I can accept it, just sharing some feedback you’ve probably already heard before).


#7

Hey @mtwomey, checked out the NetworkManager settings, it is indeed doable to do dual wifi interface with Network Manager (so resinOS 2.x series from resinstaging at the moment).

:wrench: The solution is to create an extra NetworkManager config file, specify interface-name in there, and add that to resin-boot partition’s system-connections directory. These are the rough steps for setting up dual connectivity:

  1. So download the host OS configured to your network-connected access point
  2. Check the resin-boot/system-connections/resin-wifi file, and in the [connection] section add interface-name=wlan0 to the end. This will be your default network connection
  3. Make a copy of that file there, call it for example GoPro, update the id= (in [connection]), ssid- (in [wifi]), and psk= (in [wifi-security]), and add interface-name=wlan1 (for example when using Raspberry Pi wifi dongle, that is the name, otherwise you need to check what name your dongle gets, and add that. So you’ll end up with something like this:
[connection]
id=GoPro Control
type=wifi
interface-name=wlan1

[wifi]
hidden=true
mode=infrastructure
ssid=GoPro

[ipv4]
method=auto

[ipv6]
addr-gen-mode=stable-privacy
method=auto

[wifi-security]
auth-alg=open
key-mgmt=wpa-psk
psk=supersecretpassword

Once you boot up, the Pi should connect to both networks. :slight_smile: I’ve checked that with iwconfig:

Host OS 2.x is currently only on resinstaging (which has no guarantees of working), but should come to the production in the foreseeable future!


As for host OS access:

You can already do that: take the SD card, and in the resin-root partition add your ssh key to /var/lib/dropbear/authorized_keys (replacing our device access key or just a new line adding it). That way you can SSH into the host (on port 22222). BUT this comes with a huge caveat, that once you modify the SD card like that, we won’t be able to help you with issues on that device, so should not ever do this for a device that you put out in the field into production. HostOS updates cannot be applied anymore (you would need to reprovision the device), and we won’t take on any support for that device as we don’t know what modifications were done in the host OS.

Our general philosophy is, that if we are doing things right, you wouldn’t ever need to touch the host, just work from the the container (both the answer to your question in this thread and the above resin-wifi-connect was developed without modifications to the hostOS or having device access). Instead check out interacting with the system through the messaging interface between the container and the host, see the docs here: Container Runtime.

Another way is to use the open source resinOS release which is all under your control, and when it works on that, you can adapt your application to be deployed on resin.io.

Really appreciate your questions, though, we are always open for improvement proposals, anything on your mind, just start a new topic here in the forums :writing_hand:


Port Forwarding with Resin 2.0 on RPi 3
#8

So awesome! Thank you very much. I also tested this with success on the staging version. That will be a great improvement!

I also appreciate the detail in your answer, which made it easy to test.

Based on the hint regarding resin-wifi-connect I’ve also devised a workaround we can apply in the current version:

It turns out that we can almost just configure the second wifi from within the container. What’s holding us back is the fact that wpa_supplicant is running (on the host) on all managed interfaces. I discovered this, thanks to your ssh authorized_hosts hint (perfect for troubleshooting). So when you try to run wpa_supplicant in the container, it can’t auth (probably because the host is “stealing” / interrupting the process).

So the workaround is to remove the “other” interfaces from management, by wpa_supplicant, via dbus.

The below reads all interfaces and “unmanages” any inactive interfaces. Inactive in this sense, is basically any Wifi links not already connected. You are then free to configure them as you wish from inside the container (it could use a rewrite / cleanup, but posting it here in case it helps others):

let dbus = require('dbus-native');

process.env['DBUS_SESSION_BUS_ADDRESS'] = 'unix:path=/host/run/dbus/system_bus_socket';

function unmanageUnusedInterfaces() {
    return new Promise((resolve, reject) => {
        let unmanagedInterfaces = [];
        getWpaInterfacePaths()
        .then((interfacePaths) => {
            Promise.all(
                interfacePaths.map((interfacePath) => {
                    return new Promise((resolve, reject) => {
                        getWpaInterfaceState(interfacePath)
                        .then((result) => {
                            if (result == 'inactive') {
                                getWpaInterfaceNameFromPath(interfacePath)
                                .then((interfaceName) => {
                                    unmanageWpaInterface(interfacePath)
                                    .then((result) => {
                                        unmanagedInterfaces.push(interfaceName);
                                        resolve();
                                    });
                                });
                            } else {
                                resolve();
                            }
                        })
                    })
                })
            )
            .then(() => {
                resolve(unmanagedInterfaces);
            });
        });
    });
}


function getWpaInterfacePaths() {
    let sessionBus = dbus.sessionBus();
    return new Promise((resolve, reject) => {
        sessionBus.invoke({
            path: "/fi/w1/wpa_supplicant1",
            interface: "org.freedesktop.DBus.Properties",
            member: "Get",
            destination: "fi.w1.wpa_supplicant1",
            signature: "ss",
            body: [
                "fi.w1.wpa_supplicant1",
                "Interfaces"
            ]
        }, function (err, result) {
            sessionBus.connection.end();
            if (err) {
                reject(err);
            } else {
                resolve(result[1][0]);
            }
        });
    });
}

function getWpaInterfaceNameFromPath(path) {
    let sessionBus = dbus.sessionBus();
    return new Promise((resolve, reject) => {
        sessionBus.invoke({
            path: path,
            interface: "org.freedesktop.DBus.Properties",
            member: "Get",
            destination: "fi.w1.wpa_supplicant1",
            signature: "ss",
            body: [
                "fi.w1.wpa_supplicant1.Interface",
                "Ifname"
            ]
        }, function (err, result) {
            sessionBus.connection.end();
            if (err) {
                reject(err);
            } else {
                resolve(result[1][0]);
            }
        });
    });
}

function getWpaInterfaceState(path) {
    let sessionBus = dbus.sessionBus();
    return new Promise((resolve, reject) => {
        sessionBus.invoke({
            path: path,
            interface: "org.freedesktop.DBus.Properties",
            member: "Get",
            destination: "fi.w1.wpa_supplicant1",
            signature: "ss",
            body: [
                "fi.w1.wpa_supplicant1.Interface",
                "State"
            ]
        }, function (err, result) {
            sessionBus.connection.end();
            if (err) {
                reject(err);
            } else {
                resolve(result[1][0]);
            }
        });
    });
}

function unmanageWpaInterface(path) {
    let sessionBus = dbus.sessionBus();
    return new Promise((resolve, reject) => {
        sessionBus.invoke({
            path: "/fi/w1/wpa_supplicant1",
            interface: "fi.w1.wpa_supplicant1",
            member: "RemoveInterface",
            destination: "fi.w1.wpa_supplicant1",
            signature: "o",
            body: [
                path
            ]
        }, function (err, result) {
            sessionBus.connection.end();
            if (err) {
                reject(err);
            } else {
                resolve(result);
            }
        });
    });
}

module.exports = {
    unmanageUnusedInterfaces: unmanageUnusedInterfaces
};

Detect if wifi is connected
#9

That’s pretty awesome, thanks a lot for sharing! :smiley: dbus has a lot of of untapped potential, I think… including general lack of good docs too. That makes it possible to find interesting solutions like this by poking around the applications connected to it.

Your solution is well thought out, maybe the one thing would be to make sure that at least some interface is connected to outside. If I understand this correctly, if this code runs before the main wifi interface gets a chance to connect, it will be “unmanaged” too? (e.g. if there’s a slow access points). Would be good to avoid that sort of race condition. Maybe it would work even better with the persistent interface names even in this connman case, as you’d then always know what interface to unmanage…

Now I got to dig in the code a bit more :mag_right:


#10

Yes sir - really good catch there about the potential for the container to boot before wifi. The code can easily be modified to account for this. Perhaps, simply not “unmanaging” the interface if there are no connected interfaces (as you’ve suggested).

I have now come full circle on this approach in general though. At first I thought it may be theoretically “correct” to manage any additional interfaces (not directly related to basic Resin connectivity) in the container. This seems to fit with the basic philosophy of Resin. However, I’m now of the opinion that network should be treated as a low level function, and the low level establishment of network connections should probably be handled on the host side (what uses those connections should run on the host side). I’ve been doing a lot of work on the staging instance using the 2.0 beta release which allows this.

I think perhaps having the container “mess” with network interfaces may not be the “correct” long term approach. Would be interested to discuss anyone’s thoughts here.


#11

FYI: here is a shell option using dbus-send to un-configure wireless interface(s):

    printf "enumerating wpa_supplicant interfaces interfaces (DBUS)...\n"
    for path in $(DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host/run/dbus/system_bus_socket \
      dbus-send --system --print-reply \
      --dest=fi.w1.wpa_supplicant1 /fi/w1/wpa_supplicant1 \
      org.freedesktop.DBus.Properties.Get string:fi.w1.wpa_supplicant1 string:Interfaces | grep "object path" | awk -F'"' '{print $2}'); do
        printf "removing interface ${path}...\n"
        DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host/run/dbus/system_bus_socket \
          dbus-send --system --print-reply --dest=fi.w1.wpa_supplicant1 \
          /fi/w1/wpa_supplicant1 fi.w1.wpa_supplicant1.RemoveInterface objpath:${path}
    done