VPNHotspot icon indicating copy to clipboard operation
VPNHotspot copied to clipboard

Set static IP addresses (Android 11+)

Open knuxyl opened this issue 5 years ago • 12 comments

I don't know if it's the Pixel 5 Android 11, but before on my Moto X4 Android 10 the IPs given to clients was always 192.168.43.XXX. With my Pixel 5, it is now completely random every time I turn the hotspot on. I do not know if this is Android 11, Pixel 5, or a bug in VPN Hotspot. I need my IPs to be static. What it did on Moto X4 Desktop - 192.168.43.125 Server - 192.168.43.100 What its doing now on Pixel 5 Desktop 192.168.170.125 Server - 192.168.170.100 The bold is random everytime, but it does keep the last digits the same.

If this isn't a ug, could this be a feature request to allow control over this?

knuxyl avatar Nov 12 '20 18:11 knuxyl

This is an intended feature of Android 11. You should complain to Google instead.

Mygod avatar Nov 12 '20 19:11 Mygod

Reopening since technically setStaticIpv4Addresses API addresses this, but it seems it will restrict the tethering to only one device.

Mygod avatar May 31 '21 05:05 Mygod

This "feature" is really useless. Why did they do this?

seandepagnier avatar Aug 23 '21 18:08 seandepagnier

In principle, it is possible to hijack the DNS so that a fixed domain will point to the gateway. However, this requires root and I do not see a useful use case for now.

Mygod avatar Feb 22 '22 03:02 Mygod

This is an example of redirecting DHCP requests to your own instance of Dnsmasq serving on a non-standard port, in this case for usb tethering. This lets you set any local network, dns servers, static leases.

Clear and set addresses and add the route

ndc interface clearaddrs rndis0
ndc interface setcfg rndis0 192.168.42.1 24 up
ndc network route add 99 rndis0 192.168.42.0/24

Take broadcast udp traffic on port 67 and redirect it to 1067.

iptables -t nat -I PREROUTING -i rndis0 -s 0.0.0.0 -d 255.255.255.255 -p udp -m udp --dport 67 -j DNAT --to-destination 255.255.255.255:1067

Start a new Dnsmasq instance with the dhcp-alternate-port option to receive requests on our alternative port.

killall dnsmasq
dnsmasq --keep-in-foreground --no-resolv --no-poll --dhcp-authoritative --dhcp-range=192.168.42.10,192.168.42.99,1h --dhcp-option=6,8.8.8.8,8.8.4.4 --dhcp-leasefile=/sdcard/dnsmasq.leases --dhcp-alternate-port=1067,68 --dhcp-option-force=43,ANDROID_METERED --pid-file=/sdcard/dnsmasq.pid --listen-mark 0xf0063

Edit: Dnsmasq still seems to be used for dns. Specifying the port gets around having to kill it.

dnsmasq --keep-in-foreground --no-resolv --no-poll --dhcp-authoritative --dhcp-range=192.168.42.10,192.168.42.99,1h --server=8.8.8.8 --server=8.8.4.4 --dhcp-leasefile=/sdcard/dnsmasq.leases --dhcp-alternate-port=1067,68 --port=1053 --dhcp-option-force=43,ANDROID_METERED --pid-file=/sdcard/dnsmasq.pid --listen-mark 0xf0063

And we can then redirect to the new dns server.

iptables -t nat -I PREROUTING -i rndis0 -s 192.168.42.0/24 -d 192.168.42.1 -p udp -m udp --dport 53 -j DNAT --to-destination 192.168.42.1:1053

worstperson avatar Mar 17 '22 05:03 worstperson

@worstperson Thanks for looking into this! However, I prefer to not kill dnsmasq since that may not work properly. (If I remember correctly, there are some interfacing between the Android netd services and dnsmasq.)

Mygod avatar Mar 17 '22 15:03 Mygod

Below "Edit:", I had fixed it by setting the DNS port to avoid conflicting and added a rule to point traffic to it.

worstperson avatar Mar 17 '22 16:03 worstperson

Sorry that wasn't very clear. I quickly realized that you wouldn't accept the original PoC shortly after posting, so I hurriedly fixed the dns server conflict and added it at the bottom of the post with inadequate description. Android seems to still use Dnsmasq as a dns cache, so I used the --port option to change the dns listening port and added an iptables rule to intercept dns requests from clients and redirect them to the port we're listening on. I also changed the dnsmasq command to direct clients to it's dns server, rather than sending them the remote servers to query directly.

ndc interface clearaddrs rndis0
ndc interface setcfg rndis0 192.168.42.1 24 up
ndc network route add 99 rndis0 192.168.42.0/24
# Redirect DHCP requests
iptables -t nat -I PREROUTING -i rndis0 -s 0.0.0.0 -d 255.255.255.255 -p udp -m udp --dport 67 -j DNAT --to-destination 255.255.255.255:1067
# Redirect DNS requests
iptables -t nat -I PREROUTING -i rndis0 -s 192.168.42.0/24 -d 192.168.42.1 -p udp -m udp --dport 53 -j DNAT --to-destination 192.168.42.1:1053
dnsmasq --keep-in-foreground --no-resolv --no-poll --dhcp-authoritative --dhcp-range=192.168.42.10,192.168.42.99,1h --server=8.8.8.8 --server=8.8.4.4 --dhcp-leasefile=/sdcard/dnsmasq.leases --dhcp-alternate-port=1067,68 --port=1053 --dhcp-option-force=43,ANDROID_METERED --pid-file=/sdcard/dnsmasq.pid --listen-mark 0xf0063

An example of how to reverse the changes made. The variable $NETWORK should be set to the ip that was originally assigned to the interface.

# Stop your Dnsmasq instance first
iptables -t nat -D PREROUTING -i rndis0 -s 192.168.42.0/24 -d 192.168.42.1 -p udp -m udp --dport 53 -j DNAT --to-destination 192.168.42.1:1053
iptables -t nat -D PREROUTING -i rndis0 -s 0.0.0.0 -d 255.255.255.255 -p udp -m udp --dport 67 -j DNAT --to-destination 255.255.255.255:1067
ndc network route remove 99 rndis0 192.168.42.0/24
ndc interface clearaddrs rndis0
ndc interface setcfg rndis0 $NETWORK 24 up

worstperson avatar Mar 18 '22 05:03 worstperson

Thanks for the POC. Still not sure if I want to implement all of that though. 🤣

Mygod avatar Mar 25 '22 17:03 Mygod

For days I'm looking for a way to have a fixed DHCP range for my pixel 5. In my very specific use case my car connects to my phones hotspot. Then Torque Pro installed on my car is supposed to connect to the app "Torque OBD2 Repeater". This would only ever work if the IP range is static otherwise every restart of my phone I'd have to enter the IP manually on my car. But to make it useable for me this would also need the tasker implementation to turn on the hotspot when connect to my car via Bluetooth so yeah ... 🤷‍♂️

copystring avatar Apr 10 '22 14:04 copystring

It would be very nice if you implement this feature to reverse these annoying changes that Google made to Android. I have a case too where I need to update the ip addresses everytime. Before Android 10 and 11, the android Wi-Fi tethering IP address was 192.168.43.0/24, and 192.168.42.0/24 for USB tehrering. Even the gateway address is now random instead of 192.168.x.1. Furthermore, this problem wouldn't be as annoying as it is now if the phone would have used its own DNS. But thats not the case, as I already tried. See this thread : https://android.stackexchange.com/questions/230983/the-phone-as-hotspot-doesnt-use-its-own-dns

Unnamed3 avatar Aug 18 '22 19:08 Unnamed3

It does not add any meaningful security to my local network that is WPA encrypted anyway. I am not going to hijack my own dns. This is an annoying change that makes using android hot spot less convenient.

seandepagnier avatar Aug 19 '22 03:08 seandepagnier

Any news on this ? Really want this feature added, thanks

Unnamed3 avatar Mar 16 '23 12:03 Unnamed3

Yes I had a similar issue, I was unable to set a IP address and I had to statically for ethernet for my USBC hub and I've never had the problem before I usually used kali Linux, on my phone as chroot o program routers and stuff before but I hadn't tried it on this device yet. There is no option for setting a static IP address for programming a router or something like I could not manually assign it at all plug up the USBC hub it gets what it gets like I couldn't figure it out for anything I finally gave up and went home and got my PC when I've done this before but not on this device Motorola generally was what I roll though outside of a few htcs the HTC One M8 and U11 other than that I've never had this problem before it's confusing wasn't an option anywhere for ethernet for that, there's a tether by (or hotspot)by ethernet in settings that was the only one I found. I was on Android 12 at the time but since I've taken a 13 update I would have to look, I doubt anything has changed. If there wasn't much trouble implementing a feature where you can assign your own IP ranges and addresses they would be helpful because I needed to give myself, on the host/server device an IP address and I was unable to.

GhettiGuru avatar Oct 19 '23 18:10 GhettiGuru

I think the best way to do this at the moment is to write an Xposed module to hook up relevant methods in IpServer.

Mygod avatar Oct 23 '23 18:10 Mygod

Can we please get this feature , I have read on here and stackexchange that it can be done by some hacky method . Maybe some way to Permanently set the gateway as 192.168.1.xyz in VPN hotspot app

cleanerspam avatar Feb 06 '24 17:02 cleanerspam

Mygod is right, making a simple Xposed module or framework patcher is for sure the best approach, it's not VPN Hotspot's fault Android does this.

I spent some time to make a patch if you want to try. It's missing GUI options, ideally you'd set an IP per interface, but here it is hardcoded so you can only safely apply to one interface at a time. DNSMasq's pid file should also go into getFilesDir().getPath() rather than /sdcard/.

In be.mygod.vpnhotspot.net.Routing Near the top I add variables to define the new IP/prefix and redefine hostAddress and hostSubnet so they can be reassigned later on

    val ipv4Addr = "192.168.1.129"
    val ipv4Prefix = ipv4Addr.substring(0, ipv4Addr.lastIndexOf("."))

    //private val hostAddress = try {
    private var hostAddress = try {
        val iface = NetworkInterface.getByName(downstream) ?: error("iface not found")
        val addresses = iface.interfaceAddresses!!.filter { it.address is Inet4Address && it.networkPrefixLength <= 32 }
        if (addresses.size > 1) error("More than one addresses was found: $addresses")
        addresses.first()
    } catch (e: Exception) {
        throw InterfaceNotFoundException(e)
    }
    //private val hostSubnet = "${hostAddress.address.hostAddress}/${hostAddress.networkPrefixLength}"
    private var hostSubnet = "${hostAddress.address.hostAddress}/${hostAddress.networkPrefixLength}"

And create a function to do the override, I placed it right under the function "masquerade"

    fun addressOveride() {
        // Collect the nfmark for the downstream interface
        val markCmd = transaction.execQuiet("iptables-save | grep '\\-i " + downstream + " \\-j MARK \\-\\-set\\-xmark'").out
        val mark = markCmd.substring(markCmd.indexOf("0x300") + 5, markCmd.indexOf("/"));

        // Add a new address to the interface
        transaction.exec("ip address add local " + ipv4Addr + "/24 broadcast " + ipv4Prefix + ".255 scope global dev " + downstream, "ip address del local " + ipv4Addr + "/24 broadcast " + ipv4Prefix + ".255 scope global dev " + downstream)

        // Add route for our address
        transaction.exec("ip route add " + ipv4Prefix + ".0/24 dev " + downstream + " table local_network proto static scope link", "ip route del " + ipv4Prefix + ".0/24 dev " + downstream + " table local_network proto static scope link")

        // Redirect DHCP to our own DNSMasq instance
        transaction.exec("iptables -t nat -A PREROUTING -i " + downstream + " -s 0.0.0.0 -d 255.255.255.255 -p udp --dport 67 -j DNAT --to-destination 255.255.255.255:6767", "iptables -t nat -D PREROUTING -i " + downstream + " -s 0.0.0.0 -d 255.255.255.255 -p udp --dport 67 -j DNAT --to-destination 255.255.255.255:6767")

        // Launch DNSMasq to serve DHCP only, VPN Hotspot does it's own DNS redirect
        transaction.exec("dnsmasq --keep-in-foreground --no-resolv --no-poll --dhcp-authoritative --dhcp-range=" + ipv4Prefix + ".10," + ipv4Prefix + ".99,1h --dhcp-alternate-port=6767,68 --port=0 --dhcp-option-force=43,ANDROID_METERED --listen-mark 0xf00" + mark + " --pid-file=/sdcard/dnsmasq-" + downstream + ".pid >&- 2>&- &", "kill -s 9 \$(cat /sdcard/dnsmasq-" + downstream + ".pid)")
        
        hostAddress = try {
            val iface = NetworkInterface.getByName(downstream) ?: error("iface not found")
            val addresses = iface.interfaceAddresses!!.filter { it.address is Inet4Address && ipv4Addr == it.address.hostAddress!!.toString() }
            if (addresses.size > 1) error("More than one addresses was found: $addresses")
            addresses.first()
        } catch (e: Exception) {
            throw InterfaceNotFoundException(e)
        }
        hostSubnet = "${hostAddress.address.hostAddress}/${hostAddress.networkPrefixLength}"
    }

In be.mygod.vpnhotspot.TetheringService Add the call to do the override when the Downstream is configured

    private class Downstream(caller: Any, downstream: String, var monitor: Boolean = false) :
            RoutingManager(caller, downstream) {
        override fun Routing.configure() {
            addressOveride()
            forward()
            masquerade(masqueradeMode)
            if (app.pref.getBoolean("service.disableIpv6", true)) disableIpv6()
        }
    }

worstperson avatar Feb 13 '24 22:02 worstperson

Thanks a lot @worstperson! Unfortunately that patch seems to hacky to be merged (and having a way to configure this for multiple tethering interfaces is probably also needed). :)

Mygod avatar Feb 18 '24 04:02 Mygod

@worstperson What do you think of @poqdavid's script in #537? It seems to me that that seems like a better solution than hacking around dnsmasq if it works.

Mygod avatar Feb 20 '24 16:02 Mygod

@worstperson What do you think of @poqdavid's script in #537? It seems to me that that seems like a better solution than hacking around dnsmasq if it works.

I have a suggestion maybe we could have both as an option like how IP Masquerade Mode works in the app for more compatibility

poqdavid avatar Feb 20 '24 18:02 poqdavid

I think it would be ideal to avoid keeping a separate instance of dnsmasq alive.

Mygod avatar Feb 20 '24 18:02 Mygod

@Mygod It only supports static configurations, clients that have DHCP enabled will be served addresses from the original range. It is not reversible(must disable and enable tethering) since it removes the original address from the interface which breaks DNSMasq's port 53 binding. The 'ndc network route' command is unnecessary since the correct policy routing rules are already in place.

I think it might be better to just add the new address and route to the interface. Clients can use static addresses if they want to operate in that specific range and clients connecting with DHCP will still get a working connection and ip from the original range. Basically just my patch above without the lines to collect the nfmark, redirect DHCP, and spawn DNSMasq.

worstperson avatar Feb 20 '24 22:02 worstperson

Right. If all we want is a stable IP address for the clients to connect to, why not just create a new interface with a static IP (can make it /32 even)? This way we should break virtually zero things.

Mygod avatar Feb 21 '24 00:02 Mygod

Added static IP support via a staticip dummy interface in 044206fe63bf026ef5e0a46e3956d69862455172. It seems to work fine from my testing. If anyone has a use case that is not covered by this, they may start a new issue.

Mygod avatar Feb 21 '24 04:02 Mygod

No luck here, though I could be just dumb. The interface gets created and assigned, seems to be a single address assignment though.

staticip Link encap:Ethernet HWaddr ae:0c:ad:fb:72:98 inet addr:10.78.2.173 Bcast:0.0.0.0 Mask:255.255.255.255 inet6 addr: fe80::ac0c:adff:fefb:7298/64 Scope: Link UP BROADCAST RUNNING NOARP MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:6 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 TX bytes:420

No route is created afaict, but also I don't know how a dummy interface can be used like this without bridging to the tether interface (which we can't do ofc). Is it supposed to be a virtual sub-interface or something like that?

worstperson avatar Feb 21 '24 07:02 worstperson

Ok, scratch that. Added a cidr to the default address and it sets up the address and route correctly now. Still not getting any traffic to pass between the client and phone in either direction though.

worstperson avatar Feb 21 '24 07:02 worstperson

What do you mean? It works fine for me. What do you get if you run ip route get 10.78.2.173 from <client ip> iif <tethering iface>?

EDIT: Also check that ip route show table local displays the correct entry for staticip? Tested again on my phones and seemed fine.

Mygod avatar Feb 21 '24 14:02 Mygod

btw with my script DHCP still sends the randomly generated IP is there a way to change or prevent that?

poqdavid avatar Feb 21 '24 15:02 poqdavid

@poqdavid I did not plan to support that. What is your use case?

Mygod avatar Feb 21 '24 15:02 Mygod

@poqdavid I did not plan to support that. What is your use case?

Well, I use my phone's hotspot a lot to connect a lot to the internet and I run things like DNSCrypt, VBAN and it gets really difficult when the IP keeps changing so that's why I been using that script

poqdavid avatar Feb 21 '24 15:02 poqdavid