The reason you can’t just connect directly to a Pi (or other device) directly when that device is connected by 3G/4G is because network operators run something called Carrier Grade NAT, or CG-NAT. This has the effect of preventing all inbound connections to your Pi, and only allowing outbound connections. But fortunately, there’s a relatively simple workaround.
Middle-Man
There’s actually a few ways to do this, but one way that will work for any situation is to create a middle-man server that’s accessible from the Pi and your own computer. The cheapest way to do this is to just setup a VPS (Virtual Private Server) from the provider of your choice. Personally, I’d recommend DigitalOcean as the user experience is great. At the time of writing, prices for a VPS (they call them Droplets) start from $4 a month, and aside from acting as a way of communicating to your remote Pi, you also get the utility of having a remote server to play around with.
Once you have a VPS, you essentially have a single point that both the Raspberry Pi and your computer can connect to.
graph BT A["Computer (A)"] --> M["Server (M)"] Pi["Raspberry Pi (Pi)"] --> N["NAT"] N --> M linkStyle 0 stroke:#2ecd71,stroke-width:2px; linkStyle 1 stroke:#2ecd71,stroke-width:2px; linkStyle 2 stroke:#2ecd71,stroke-width:2px;
Now we can use the remote server to act as a “middle-man”. The way this works is by using SSH tunnelling. Despite its name, SSH tunnelling allows any type of connection to be tunnelled to the remote device via the middle-man server. The reason this works is because the Pi connects (using an outbound connection) to the middle-man server. It maintains this connection forever. Then, when a connection is received on the middle-man server, the data is relayed to the Pi over the persistent connection. The Pi can then handle the data as if it had come from the client directly. This is transparent for both the client and the Pi.
Example
sequenceDiagram participant Client participant Server (Tunnel) participant Pi (Tunnel) participant Pi (App) Client-->>Server (Tunnel): TCP connect to 12345, send "hello!" Server (Tunnel)->>Pi (Tunnel): Send "hello!" to port 8080 Note right of Server (Tunnel): This communication happens
over the existing tunnel connection Pi (Tunnel)-->> Pi (App): TCP connect to 8080, send "hello!" Pi (App) -->> Pi (Tunnel): "hello there!" Pi (Tunnel)->>Server (Tunnel): Reply "hello there!" Server (Tunnel)-->>Client: Reply "hello there!"
So for example, if you had a web server hosted on the Pi on port 8080, you could setup tunnelling to forward inbound traffic from the middle-man server received on port 12345 to the Pi’s port on 8080. Then, instead of connecting to your Pi directly on port 8080, you would instead connect to your middle-man server on port 12345. The middle-man server would then forward traffic to the Pi on port 8080, and the Pi would return the data from the web server, and finally the middle-man server would return that data to the client. Notice that in this setup, no connection is made from the server to the Pi. The persistent connection is re-used to relay the data.
Setup the tunnel
We need to configure the middle man server to listen for inbound connections, and to forward the data received in those inbound connections to the Pi. This is all done from the Pi, so it’s a good idea to setup something to run this script when the Pi first starts up.
On the Pi, run the following command:
#!/bin/bash while true; do ssh -vvnNT -o ServerAliveInterval=60 -R 12345:localhost:22 user@ip.address echo "Tunnel failed!" sleep 2 done
Where the ip.address is the IP address of your middle-man server. 12345 is the port the middle-man server will listen on, and 22 is the port the traffic will be forwarded to on the Pi. Given port 22 is the port SSH is hosted on, with this configuration you can connect to the SSH of the Raspberry Pi by connecting to <ip.address>:12345. The script also restarts the tunnel if the tunnel fails. The automatic connection (i.e. with no password prompt) only works if you already have the SSH key of the middle-man server installed in the Pi. You can find out how to do that here. The option ServerAliveInterval is the number of seconds before the client will send a “null packet” to the server, which helps ensure the connection will be kept alive.
Leave a Reply