WiFi Connect and Port 80

Good Morning,

My application listens on port 80, for the usual local services. I also want to use WiFi Connect. When the network is unavailable, port 80 needs to go to the WiFi Connect captive authentication portal.

Any suggestions on how to handle these situations?

I am also wondering if access to an applications admin UI over HTTP might be handy while WiFi Connect has the AP mode enabled, in some circumstances. So maybe I should change the port used by WiFi Connect…

I tried changing PORTAL_LISTENING_PORT but it seems there is no redirect to the new port, so captive auth fails:

$ curl -v www.apple.com
* Trying
* Connected to www.apple.com ( port 80 (#0)
> GET / HTTP/1.1
> Host: www.apple.com
> User-Agent: curl/7.64.1
> Accept: */*
< HTTP/1.1 200 OK
< Server: nginx/1.10.3
< Date: Sun, 13 Dec 2020 15:52:17 GMT
< Content-Type: text/html
< Content-Length: 94
< Last-Modified: Sun, 13 Dec 2020 01:49:19 GMT
< Connection: keep-alive
< ETag: "5fd5731f-5e"
< Accept-Ranges: bytes
<!doctype html>
<title>Hello World</title>
Hello World
* Connection #0 to host www.apple.com left intact
</html>* Closing connection 0

The Hello World is coming from my admin UI placeholder.

I think this is a bug in WiFi Connect, so I opened an issue. I’ve love to hear from more experienced users, though.

I guess it isn’t a bug, dnsmasq is just answering all queries with the local IP, so whatever port the client system uses is what you’re going to get. So I’m not sure what real use PORTAL_LISTENING_PORT actually is.

What’s the solution, here? Can I have two containers trying to listen on the same port with one of them taking precedence? Ideas?

Wifi-connect has a script that launches it. If the wifi is already connected, it just goes into sleep. Instead of sleep, you could put your app there. Then if the device is not connected to wifi it will launch wifi connect on port 80 with a captive portal, and if it is connected it will launch your app on port 80 and never the two at the same time. Would likely add in a check in the script to loop through and see if the connection state has changed.

This however depends on not wanting to have your app running at the same time as wifi connect, I.e. forcing users to connect to a wifi hotspot rather than use your app offline.

I have on my to do list to find a solution for this although do need both online and offline access to my app which is why I’m interested in this thread. May have to explore whether I can make my own captive portal in the app.

1 Like

Best I can tell, if you have WiFi connect running (not on port 80), a user connected, and something else running on port 80 that redirects certain urls to a page of your choice then the captive portal will open and load that page. https://github.com/balena-io/wifi-connect/issues/207#issuecomment-380039522

Surprisingly it’s tricky to find a comprehensive list of URLs to forward but this was helpful:

Hopefully someone can verify this.

1 Like

Hi there, the change to PORTAL_LISTENING_PORT would need to be coupled with an iptables rule to forward packets from port 80 (default) to appropriate non-default port where the portal is listening. This can be done in a privileged container with host networking.

1 Like

That makes a lot of sense! And could easily be made optional via an environment variable. Should we do this in the wifi-connect block or in the wifi-connect app?

You should start with https://github.com/balenablocks/wifi-connect. There are a few ways of doing this, but if you take the wifi-connect Dockerfile.template and in start.sh insert a line to add the iptables rule before the wifi-connect starts, that should be enough. You should check if the rule is there first, before adding it and also probably a good idea to remove it on container exit using traditional trap signal handling function in scripts.

1 Like

I’m working on this. First, I will simple do an add and a remove of the iptables rule. Something like this to add the rule:

iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports ${PORTAL_LISTENING_PORT}

And simply the following to remove it:

iptables -t nat -D PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports ${PORTAL_LISTENING_PORT}

Then I will learn about trying to trap the container exit and cleanup that way.

Meanwhile, I can’t get a console on the wifi-connect container:

$ balena ssh c9c0792 wifi-connect
OCI runtime exec failed: exec failed: container_linux.go:349: starting container process caused "exec: \"/bin/sh\": stat /bin/sh: no such file or directory": unknown
OCI runtime exec failed: exec failed: container_linux.go:349: starting container process caused "exec: \"bash\": executable file not found in $PATH": unknown
Shared connection to ssh.balena-devices.com closed.

Does that indicate that this container has no shell present? I’d like to be able to debug this…

Any thoughts?


Look at the wifi-connect Dockerfile: https://github.com/balenablocks/wifi-connect/blob/master/Dockerfile.template#L16-26

It uses https://github.com/larsks/dockerize to only pack the required files in the image and reduce its size.

The image has no sh or bash available.

You’ll need to update this dockerfile to add bash in the image (or not use dockerize at all).

1 Like

Right, I hadn’t heard of that. So that means it won’t have iptables, either.

Yes, you can try adding

`which iptables` `which bash`

at the end of this line https://github.com/balenablocks/wifi-connect/blob/master/Dockerfile.template#L16

After getting a shell and iptables setup, which required adding all three:

`which iptables` `which bash` `which sh`

I checked to see what is visible from inside the container and found that the rules in the host are not:

$ balena ssh c9c0792 wifi-connect
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
# Warning: iptables-legacy tables present, use iptables-legacy to see them

While the same command in the host produces all the usual Docker iptables rules. So I assume this plan is not going to work that easily.

Also, I see that my decades-old knowledge of iptables appears to need updating to nftables. Anything I should worry about?


One of my colleagues did a couple tests running a container on a balenaOS device (his was a NUC, but this procedure should work regardless of hardware):

  • balena-engine run -it --privileged ubuntu /bin/bash
  • balena-engine run -it --privileged --network host ubuntu /bin/bash

He observed that iptables lists an empty table with the former, and a populated table with the latter. Therefore, we presume the container is running without specifying network_mode: host in the docker-compose.yml file. It would be strange, though, because the sample docker-compose.yml file of the balenablock has network_mode: host, and we presume you used https://github.com/balenablocks/wifi-connect

Can you confirm?


What does the above do, exactly?

Here is my docker-compose.yml:

version: "2.1"

    # image: balenablocks/wifi-connect:armv7hf
    build: ./wifi-connect
    restart: always
    network_mode: host
    privileged: true
      io.balena.features.dbus: "1"
      io.balena.features.firmware: "1"
    build: ./frontend
      - "80/tcp"

Seems correct.



The commands launch a simple Ubuntu container in privileged mode, which means they have access to hostOS stuff. The first example uses the “Docker” network addressing (usually 172.x.x.x.). The second uses your device’s host networking (something like 192.x.x.x). One thing you might try is adding network_mode: host to your frontend: stanza.


But I can’t/shouldn’t be using those commands myself, on my macOS workstation.

The frontend has nothing to do with the wifi-connect container, it’s a placeholder for the UI that needs to run on port 80 once the device has connectivity. I’m testing the iptables commands from within the wifi-connect container.

Right, those commands are run on balenaOS. They’re very much like “docker exec -it…” Re: frontend: I understand.


I’m running my commands ssh’d into the host OS and the container.

Just found this while searching for something else: