Let's say rmnet_data1
is your Mobile Data interface (on Qualcomm devices) and wlan0
is WiFi interface. When you switch on Mobile Data, your ISP assigns rmnet_data1
an IP address, possibly dynamic and not necessarily a public IP (see details in this answer), but let's assume it is a static public IP - say 1.2.3.10
. When you switch on tethering, wlan0
is turned into AP mode and a static IP is assigned (used to be 192.168.43.1
but is randomized since Pie). A DHCP/DNS server (dnsmasq
) is listening on this interface to assign dynamic IP addresses to any hosts which join the local network.
Now phone is essentially a router connecting two networks (different subnets) at gateways (rmnet_data1
for external/public network i.e. WAN and wlan0
for local/private network i.e. WLAN). Since only one IP is assigned by the ISP to your phone, phone is doing NAT (at OSI layer 3) to make internet accessible for all local clients. As in your case the only connected host (router) is assigned address, let's say, 192.168.43.10
. You'll have to do specific Port Forwarding or DMZ to access the router from internet.
BRIDGING ON LINUX / ANDROID:
What you want to do is assign address 1.2.3.10
to the router using Ethernet bridge as you stated in comment:
I only want to connect one device behind the phone, and I want the phones public IP to be given to it.
If you want to retain current network configuration on device, Ethernet bridging - which is an alternative to NAT for internet sharing - doesn't seem to be an option at all, unless you get a whole subnet (e.g. /30
) from ISP instead of a single IP address. See Appendix A for reasons.
BRIDGING ON MODEM / ROUTER:
That said, what you are looking for is called Half Bridge Mode; a feature provided in many modem/router combo devices to avoid the problem of double NAT. It's also called IP Pass-through, IP Extension, DHCP Spoofing or True DMZ by different manufacturers. Other operation modes on ISP provided combo devices are NAT and Full Bridge.
We can use some workaround to expose the public IP to connected host. It involves changing IP configurations and kernel routing policy/tables, so it breaks internet connectivity on Android device. That's not a problem on modems/routers, but on Android it is. So we have to use NAT for Android's internal traffic (see Appendix B).
HOW TO TURN ANDROID INTO HALF-BRIDGED MODEM / ROUTER?
ISPs doing CGNAT usually create small subnets for connected clients, in case of public IP it could be even /8
. In our case, let's say, the network ID is 1.2.3.8/30
, so 1.2.3.9
will be the ISP's gateway address. There are different approaches we can go with, see Appendix C.
Rest of the details are given in the following script which can be executed followed by dhcp
, static
, arp
or stop
argument:
#!/system/bin/sh
set -e
[ "$(id -u)" = 0 ] || { echo 'Not running as root!' >&2; exit 1; }
# internal filed separator
IFS=$' \t\n'
##########################
# define / get variables #
##########################
SSID=MyAP # set this to your desired string (avoid spaces and non-ascii characters)
PASSCODE=foobarfoobar # set this to your desired string (8 to 63 characters)
PUB_IFACE=rmnet_data # set this according to your device (Qualcomm SoCs use 'rmne_data[N]')
WIFI_IFACE=wlan0 # set this according to your device (check with 'lshw' or 'ip link show')
AP_IFACE=${WIFI_IFACE}-AP # we'll create new WiFi interface for hotspot
DIR=/data/local/tmp/$AP_IFACE # where to put config files
LOCAL_SUBNET=192.168.1 # for public interface
PST_RT_CHN=my_chain # to define source NAT for local packets
DNS1=1.1.1.1
DNS2=1.0.0.1
# read public interface name, IP and mask
read -r PUB_IFACE PUB_IP MASK <<< \
$(ip -4 -o a | tr -s ' ' | awk -F'[ /]' '/'"$PUB_IFACE"'/ {print $2,$4,$5}')
# read ISP's default gateway
GW=$(ip r s t all | awk '/^default/ && /'"$PUB_IFACE"'/ {print $3}')
# ip for WiFi hotspot interface in public subnet
AP_IP=$GW/$MASK
############################
# start / stop bridge mode #
############################
STOP()
(
echo 'Cleaning up...'
# ignore errors
set +e
# don't print error messages
exec >/dev/null 2>&1
# hope there are no other instances of same daemons
pkill -15 hostapd
pkill -15 dnsmasq
# remove local IP from public interface
ip address del ${LOCAL_SUBNET}.1 dev $PUB_IFACE
# remove rule and clear RPDB cache
ip rule del lookup main
ip route flush cache
# flush main table
ip route flush table main
# disable forwarding
printf 0 >/proc/sys/net/ipv4/ip_forward
iptables -D FORWARD -i $AP_IFACE -o $PUB_IFACE -j ACCEPT
iptables -D FORWARD -o $AP_IFACE -i $PUB_IFACE -j ACCEPT
# stop listening on DHCP port
iptables -D INPUT -i $AP_IFACE -p udp -m udp --dport 67 -j ACCEPT
# stop local NAT
iptables -t nat -D POSTROUTING -j $PST_RT_CHN
iptables -t nat -F $PST_RT_CHN
iptables -t nat -X $PST_RT_CHN
# delete AP interface
iw $AP_IFACE del
# delete config directory
rm -rf $DIR
)
if [ "$1" = stop ]
then
STOP
exit
elif [ "$1" != dhcp -a "$1" != static -a "$1" != arp ]
then
echo 'Usage:' >&2
printf '\t%s\n' "$(basename "$0") dhcp|static|arp|stop" >&2
exit 1
fi
################
# basic checks #
################
if [ -z $PUB_IP ]
then
echo 'Turn on Mobile Data first.' >&2
exit 1
fi
if ! iw phy | grep -A10 'Supported interface modes:' | grep -q '\*[ ]*AP'
then
echo 'AP mode not supported.' >&2
exit 1
fi
if ! iw dev $WIFI_IFACE link | grep -q '^Not connected'
then
echo 'First disconnect form Wi-Fi.' >&2
exit 1
fi
##########################
# clear previous configs #
##########################
STOP
# clean up if error occurs
trap '[ $? = 0 ] || STOP' EXIT
#####################################
# create virtual wireless interface #
#####################################
if ! iw dev $WIFI_IFACE interface add $AP_IFACE type __ap
then
echo "Couldn't create AP interface." >&2
exit 1
fi
####################################
# configure interfaces and routing #
####################################
echo 'Configuring network...'
# add local IP to public interface and delete public IP
ip address add ${LOCAL_SUBNET}.1/30 dev $PUB_IFACE
ip address del $PUB_IP/$MASK dev $PUB_IFACE
# activate the WLAN interface
ip link set dev $AP_IFACE up
if [ "$1" = dhcp ]
then
# assign ip in public subnet to WiFi hotspot interface
ip address add $AP_IP dev $AP_IFACE
elif [ "$1" = static ]
then
# assign ip in local subnet to WiFi hostspot interface
ip address add ${LOCAL_SUBNET}.2/30 dev $AP_IFACE
else
# start proxying ARP queries both ways
printf 1 >/proc/sys/net/ipv4/conf/$AP_IFACE/proxy_arp
fi
# clear auto added routes and add new
ip route flush table main
ip route add $PUB_IP dev $AP_IFACE
ip route add default via $GW dev $PUB_IFACE
ip route flush cache
# Android doesn't look up into main table by default
ip rule add lookup main
# let packets be forwarded
printf 1 >/proc/sys/net/ipv4/ip_forward
iptables -I FORWARD -i $AP_IFACE -o $PUB_IFACE -j ACCEPT
iptables -I FORWARD -o $AP_IFACE -i $PUB_IFACE -j ACCEPT
# apply source NAT on internally generated packets
iptables -t nat -N $PST_RT_CHN
iptables -t nat -I POSTROUTING -j $PST_RT_CHN
iptables -t nat -I $PST_RT_CHN -o $PUB_IFACE ! -s $PUB_IP -j SNAT --to-source $PUB_IP
#######################
# access point daemon #
#######################
# create configuration file
mkdir -p $DIR
cat <<-HOSTAPD >$DIR/hostapd.conf
# network name
ssid=$SSID
# passphrase to use for protected access
wpa_passphrase=$PASSCODE
# maximum STA clients allowed to connect
max_num_sta=1
# network interface to listen on
interface=$AP_IFACE
# wi-fi driver
driver=nl80211
# set operation mode, 'g' for 2.4GHz band
hw_mode=g
# WLAN frequency channel to use
channel=1
# key management protocol; use pre-share key
wpa_key_mgmt=WPA-PSK
# enforce WPA2
wpa=2
HOSTAPD
echo 'Starting hostapd...'
hostapd -B $DIR/hostapd.conf
##########################################
# IP assignment; manually or dynamically #
##########################################
if [ "$1" = dhcp ]
then
# create configuration file
cat <<-DNSMASQ >$DIR/dnsmasq.conf
# we dont want DNS server, only DHCP
port=0
# network interface to listen on
interface=$AP_IFACE
bind-interfaces
# nameservers to be sent to clients
dhcp-option=6,$DNS1,$DNS2
# range of IPs to make available to wlan devices and when to renew IP
dhcp-range=$PUB_IP,$PUB_IP,24h
# where to save leases
dhcp-leasefile=$DIR/dnsmasq.leases
# respond to requests from a different IP broadcast subnet
dhcp-authoritative
# log extra information about DHCP handshakes
log-dhcp
DNSMASQ
# open listening port for dnsmasq
iptables -I INPUT -i $AP_IFACE -p udp -m udp --dport 67 -j ACCEPT
echo 'Starting DHCP server...'
dnsmasq -C $DIR/dnsmasq.conf </dev/null
else
echo
echo 'On connected host disable DHCP, set DNS and do:'
echo 'ip a f dev <wifi>'
echo "ip a a $PUB_IP dev <wifi>"
echo 'ip r f t main'
if [ "$1" = static ]
then
echo "ip r a ${LOCAL_SUBNET}.2 dev <wifi>"
echo "ip r a default via ${LOCAL_SUBNET}.2 dev <wifi>"
else
echo 'ip r a default dev <wifi>'
echo 'ip n f dev <wifi>'
fi
echo
fi
echo Done.
NOTE:
- All of the required binaries (
iw
,ip
, iptables
, hostapd
, dnsmasq
) are available on Android but hostapd
doesn't work on command-line. I used a static built binary, tested on Pie.
- Based on the factors discussed here, your ISP may figure out (and mind) that your traffic is not originating from Android device.
- After stopping half bridge mode, you need to turn Mobile Data OFF and ON to resume normal working because routes are cleared. Also on device boot you may need to switch on WiFi once so that
wlan0
interface is created.
ISP's nameservers - if needed - (and default gateway) can also be obtained by:
~$ dumpsys connectivity | grep CONNECTED | grep -o 'Routes: .* DnsAddresses: [^ ]*'
Routes: [0.0.0.0/0 -> 1.2.3.9 rmnet_data1,1.2.3.8/30 -> 0.0.0.0 rmnet_data1,] DnsAddresses: [1.1.1.1,8.8.8.8,]
VERIFICATION:
Your configuration should look like this:
1. Routing
==========
~$ ip rule show
0: from all lookup local
9999: from all lookup main
~$ ip route show table main
default via 1.2.3.9 dev rmnet_data1
1.2.3.10 dev wlan0-AP
2. Forwarding
=============
~$ cat /proc/sys/net/ipv4/ip_forward
1
~# iptables -S
-A FORWARD -i rmnet_data1 -o wlan0-AP -j ACCEPT
-A FORWARD -i wlan0-AP -o rmnet_data1 -j ACCEPT
3. Source NAT
=============
~$ ip -4 -o address
rmnet_data1 inet 192.168.1.1/30
~# iptables -t nat -S
-A POSTROUTING -j my_chain
-A my_chain ! -s 1.2.3.10/32 -o rmnet_data1 -j SNAT --to-source 1.2.3.10
4. Access Point Daemon
======================
~# iw dev
Interface wlan0-AP
ssid MyAP
type AP
5 (a). DHCP Server
==================
~$ ip -4 -o address
wlan0-AP inet 1.2.3.9/30
~# netstat -lup
Proto Local Address Foreign Address PID/Program name
udp 1.2.3.9:67 0.0.0.0:* 23693/dnsmasq
~# iptables -S
-A INPUT -i wlan0-AP -p udp -m udp --dport 67 -j ACCEPT
5 (b). Static IP
================
~$ ip -4 -o address
wlan0-AP inet 192.168.1.2/30
5 (c). Proxy ARP
================
~$ cat /proc/sys/net/ipv4/conf/wlan0-AP/proxy_arp
1
APPENDIX A:
Why Ethernet bridging isn't a straightforward option on Android:
- There are at least two hosts on your local network; phone and router, but only one IP address is received from ISP. Two hosts on same network can't have same IP address.
- If you transparently bridge
rmnet_data1
and wlan0
, both can have same IP address i.e. 1.2.3.10
but router still needs to be assigned another IP address, either statically or by DHCP server from ISP side. If you don't assign full bridge an IP, internet connectivity will break on Android which can't work (see Appendix B).
- Ethernet bridging (which operates at OSI L2) doesn't work for WiFi interfaces in STA mode without WDS / 4addr support (on both sides of WiFi) which isn't supported by all physical interfaces. In AP mode, however, WiFi interface supports bridging, but 3G/4G interfaces don't. A recent patch to
rmnet
adds bridging support only for interfaces which can send MAP data.
- An alternative to Ethernet bridging is proxy ARP (at OSI L3) or something like wlan_kabel which can be applied to both interfaces. But again you need three or at least two IP addresses; one for phone and one for router.
APPENDIX B:
Why NAT is required on device?
Modems have their own processors - called baseband or communication processor (BP or CP) on Android devices - and they run their own minimal RTOS. On small devices like phones they are integrated in SoCs. Communication with modem is done using a set of standard control protocol. AT commands is the traditional one used with PPP (1, 2), but manufacturers have developed new methods (e.g. QMI (3, 4, 5) / RmNet (6, 7) on Qualcomm devices).
The closed source vendor specific control/data modem protocols used on Android devices are not (or at least well) documented. Vendors provide native RIL Daemon as a binary blob which communicates with modem through RILJ
and telephony framework in Java stack. When we turn on Mobile Data, these components work together to authenticate with MNO (by reading SIM data, APN settings etc.) and then configure the network stack (IP, DNS, route etc.) to make internet connectivity possible. So the modem is available to Android OS as in full bridge mode (no NAT involved on device).
Using methods explained above we assign public IP to connected host, so Android device itself loses internet connection. As soon as we make a change to the IP configuration, Android's telephony stack detects disconnections and sends a reset command to modem (through RILD) and whole network stack is refreshed with new (or same) IP settings received from MNO.
So to set Android device in Half Bridge mode, a more reliable approach is to take full control of communication with modem by altering the telephony framework (which isn't a simple task). As a workaround we do source NATing for internally generated packets on Android device so that it's not disconnected from internet. While the public IP is routed without any NAT to the only host connected to wlan0
interface, so that it's accessible from internet.
APPENDIX C:
Routing Methods for Half Bridge Mode on Android:
- DHCP:
Assign 1.2.3.10
to your router using DHCP server, 1.2.3.9
to wlan0
and 192.168.1.1/30
(or any other private IP) to rmnet_data1
. It's also possible to create a hypothetical 1.2.3.0/X
network and assign random IP from that hypothetical network to wlan0
. But public IP address must not conflict with subnet ID or broadcast address in the faked subnet. It becomes difficult to achieve if IP is assigned dynamically, particularly if IP class and subnet mask keep on changing from ISP.
In this way AP interface and router appear on same local network, and wlan0
IP is set as default gateway (next hop) for router. Here we are faking an IP address (at OSI L3).
- Static Route:
Setting IP/DNS on router manually allows you to assign local IP address to AP interface instead of using pseudo IP address. Add AP's IP address as default gateway on router. However router might have problem using default gateway on different subnet.
ProxyARP:
Another option when not using DHCP is ProxyARP (8, 9, 10). If wlan0
has no IP address, we need to add a static route without IP address as default gateway. So router can't query gateway's MAC address and thus keeps asking wlan0
(in global broadcasts) where an internet address (next hop) is. It's because target of ARP queries is always the IP address, either of a local host or of default gateway (if destination IP is on another network).
Due to ProxyARP, wlan0
provides its own MAC address for every ARP query received from connected host. ARP queries from rmnet_data1
(for 1.2.3.10
's MAC address) are forwarded/received by wlan0
to/from connected host. rmnet_data1
itself doesn't have a MAC address (like PPP). A side effect of this approach is that the connected router will pollute its ARP cache by associating every visited internet address to AP's MAC address. Here we are faking a MAC address (at OSI L2).
CREDITS:
with a public IP given by sprint
. You get exactly one IP address not multiple. A mobile network is AFAIK not like a LAN where you can simply get as many IP addresses via DHCP as you need. Only with IPv6, bridging may be possible if you get a subnet instead of one address.