Skip to content

Instantly share code, notes, and snippets.

@peci1
Last active June 29, 2022 23:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save peci1/d8e7fc5ca8c9ea764541ad9afd8c2829 to your computer and use it in GitHub Desktop.
Save peci1/d8e7fc5ca8c9ea764541ad9afd8c2829 to your computer and use it in GitHub Desktop.
Hide the network interface created by Emlid Reach M+ GPS when you connect it via USB (tested on Ubuntu 18.04/systemd)
# Only add this if you need to run Apache on the host
LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so
<!-- Only add this if you need to run Apache on the host -->
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
<Directory /var/www/html>
AllowOverride All
</Directory>
</VirtualHost>
<?xml version="1.0" standalone='no'?>
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<service-group>
<name replace-wildcards="yes">reach-%h</name>
<service>
<type>_reach._tcp</type>
<port>80</port> <!-- Specifying a different port doesn't help. The Android app ignores it. -->
<txt-record>device=ReachM+</txt-record>
</service>
</service-group>
[Unit]
Description = Move Emlid Reach USB network card to network namespace "reach"
[Service]
Type = oneshot
RemainAfterExit = true
ExecStart = /usr/bin/reach_netns.sh %I
# Use ID_NET_NAME_MAC if the device gets interface name starting with enx after renaming
ACTION=="add", SUBSYSTEM=="net", ENV{ID_VENDOR_ID}=="3032", TAG+="systemd", ENV{SYSTEMD_WANTS}="emlid-reach@%E{ID_NET_NAME_PATH}.service"
#!/bin/bash -x
# This script encapsulates a dumb non-configurable device which creates a USB NIC with a static address.
# The encapsulation allows the host system to communicate with this device using a different IP address and
# makes sure the non-configurable static address of the device is not normally accessible or propagated via ARP.
: ${GPS_DEV:=enp6s0f3u1u3} # The non-configurable device
[ -n "$1" ] && GPS_DEV="$1"
: ${NS:=reach} # Network namespace, can be anything
: ${NET:=24} # Network mask
: ${HOST_IP:=192.168.4.1} # Static IP of the host that will be used for communication with the dumb device
: ${HOST_NS_IP:=192.168.4.2} # Static IP of the host inside the network namespace $NS (not important)
: ${GPS_NS_IP:=192.168.4.3} # Static IP under which the dumb device will be accessible from the host
: ${HOST_FAKE_IP:=192.168.2.1} # Fake IP address of the host computer in the subnet of the dumb device (not important, but dumb device has to have a route and ARP to it)
: ${GPS_IP:=192.168.2.15} # Static IP address of the dumb device
# With the given configuration, you will be able to `ping 192.168.4.3` from the host, while actually pinging the `192.168.2.15` static IP of the dumb device.
# If you can SSH to the dumb device, then the host computer is reachable via `192.168.2.1`.
# Create the network namespace
ip netns add $NS
# Create a tunnel between the host and the network namespace
ip link add host-$NS type veth peer name ns-$NS
# Move the dumb device's interface and one end of the tunnel to the namespace
ip link set $GPS_DEV netns $NS
ip link set ns-$NS netns $NS
# Bring up all 3 devices
ip link set host-$NS up
ip -n $NS link set ns-$NS up
ip -n $NS link set $GPS_DEV up
# Assign static IP addresses to the devices
ip addr add $HOST_IP/$NET dev host-$NS
ip -n $NS addr add $HOST_NS_IP/$NET dev ns-$NS
ip -n $NS addr add $HOST_FAKE_IP/$NET dev $GPS_DEV
# Tell the network inside namespace that all unknown traffic should be directed through the tunnel.
ip netns exec $NS ip ro add default dev ns-$NS
# Enable IPv4 forwarding from the host to the GPS device
echo 1 > /proc/sys/net/ipv4/ip_forward
# Enable IPv4 forwarding inside the namespace
echo 1 | ip netns exec $NS tee /proc/sys/net/ipv4/ip_forward
# Enable proxy ARP behavior inside the namespace. This is needed so that we can fake the addresses and it works both on the IP and ARP layer.
echo 1 | ip netns exec $NS tee /proc/sys/net/ipv4/conf/$GPS_DEV/proxy_arp
echo 1 | ip netns exec $NS tee /proc/sys/net/ipv4/conf/$GPS_DEV/proxy_arp_pvlan
echo 1 | ip netns exec $NS tee /proc/sys/net/ipv4/conf/ns-$NS/proxy_arp
echo 1 | ip netns exec $NS tee /proc/sys/net/ipv4/conf/ns-$NS/proxy_arp_pvlan
# Set up standard 1:1 NAT inside the namespace between the dumb device and one end of the tunnel
ip netns exec $NS iptables -t nat -A PREROUTING -i $GPS_DEV -d $HOST_FAKE_IP -j DNAT --to $HOST_IP
ip netns exec $NS iptables -t nat -A PREROUTING -i ns-$NS -d $GPS_NS_IP -j DNAT --to $GPS_IP
ip netns exec $NS iptables -t nat -A POSTROUTING -o $GPS_DEV -s $HOST_IP -j SNAT --to $HOST_FAKE_IP
ip netns exec $NS iptables -t nat -A POSTROUTING -o ns-$NS -s $GPS_IP -j SNAT --to $GPS_NS_IP
# These gateways are needed so that proxy ARP can find which interfaces to assign to the fake IP addresses.
ip netns exec $NS route add $HOST_FAKE_IP gw 127.0.0.1
ip netns exec $NS route add $GPS_NS_IP gw 127.0.0.1
# Expose a TCP port from the dumb device on a different port of the host computer
: ${GPS_PORT:=80} # The port on the dumb device on which a service is running
: ${HOST_PORT:=8088} # The port on host on which the service will be available (on any interface); feel free to change it to port 80 if you do not need Apache running on the host
iptables -A FORWARD -p tcp -d $GPS_NS_IP --dport $GPS_PORT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -p tcp -s $GPS_NS_IP --sport $GPS_PORT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to-destination $GPS_NS_IP:$GPS_PORT
iptables -t nat -A POSTROUTING -o host-$NS -j MASQUERADE
# Only add this if you need to run Apache on the host
RewriteEngine on
RewriteCond %{HTTP_USER_AGENT} Ktor|okhttp
RewriteRule ^(.*)$ http://%{HTTP_HOST}:8088/$1 [R=301,L]
@peci1
Copy link
Author

peci1 commented May 4, 2022

Names of the files have underscores instead of slashes because Gists do not allow absolute paths...

@peci1
Copy link
Author

peci1 commented May 4, 2022

The script cannot be launched directly from the UDEV rule because UDEV is running in some kind of sandbox and does not have access to networking...

Also, I wanted this in the UDEV rule: ENV{SYSTEMD_WANTS}="emlid-reach@%E{INTERFACE}.service", but it seemed INTERFACE was passed with the value before network interface renaming (from usb0). I happened to find out that ID_NET_NAME_PATH contains the renamed interface, but I'm not sure why (and how stable that is).

@peci1
Copy link
Author

peci1 commented May 4, 2022

I would still prefer if Emlid allowed to configure the USB network :)

@peci1
Copy link
Author

peci1 commented May 4, 2022

I've added avahi and apache2 configuration (assuming the default apache2 config installed with ubuntu desktop). This allows discovering and configuring the Reach GPS via the phone app.

If you do not need apache running on the host, you can get rid of the .htaccess file and apache configs and directly expose port 80 of the device via the port forwarding rule in the main script. The config provided here leaves default Apache site alone, and redirects port 80 to the Reach device only if User Agent matches Ktor client or okhttp, which are the two strings used by the Android app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment