Network Management in Android: Routing

I plan on diving deep into Android in future blog posts but for now I thought I might share something that was really confusing to me in hope to prevent the confusion to others. The confusion was not actually Android exclusive, I just stumpled upon it while working with Android and it was a great reminder that routing is a lot more complicated than I usually think of it.

Anyway, if you have an Android machine (in my case a container), you can run ip-route(8) and see that there is probably only one route and it’s not the default gateway. This is because, at least in my Android container, Android doesn’t use the main table for routing configuration. Linux routing actually have multiple routing tables which the kernel looks at for each packet. We can see the rules that decide which table to look at through ip-rule(8). The rule list in my Android container looks like so:

0:	from all lookup local 
10000:	from all fwmark 0xc0000/0xd0000 lookup legacy_system 
11000:	from all iif lo oif eth0 uidrange 0-0 lookup eth0 
16000:	from all fwmark 0x10063/0x1ffff iif lo lookup local_network 
16000:	from all fwmark 0x10064/0x1ffff iif lo lookup eth0 
17000:	from all iif lo oif eth0 lookup eth0 
18000:	from all fwmark 0x0/0x10000 lookup legacy_system 
19000:	from all fwmark 0x0/0x10000 lookup legacy_network 
20000:	from all fwmark 0x0/0x10000 lookup local_network 
23000:	from all fwmark 0x64/0x1ffff iif lo lookup eth0 
31000:	from all fwmark 0x0/0xffff iif lo lookup eth0 
32000:	from all unreachable

This is x4 more rules than what I have on my Fedora Silverblue machine:

0:	from all lookup local
32766:	from all lookup main
32767:	from all lookup default

I actually don’t know why the big difference but the important difference is that Android doesn’t even look at the main table as part of its rules. When you want to look at the routing rules you probably want to look at all of them together. Luckily the ip command accept all as a pseudo table that prints the routing rules from all tables. For example, in my container I get this:

c340878bf6f5:/ # ip route show table all
172.17.0.0/16 dev eth0 table eth0_local proto static scope link 
224.0.0.0/24 dev eth0 table eth0_local proto static scope link 
default via 172.17.0.1 dev eth0 table eth0 proto static 
172.17.0.0/16 dev eth0 table eth0 proto static scope link 
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2 
local 127.0.0.0/8 dev lo table local proto kernel scope host src 127.0.0.1 
local 127.0.0.1 dev lo table local proto kernel scope host src 127.0.0.1 
broadcast 127.255.255.255 dev lo table local proto kernel scope link src 127.0.0.1 
local 172.17.0.2 dev eth0 table local proto kernel scope host src 172.17.0.2 
broadcast 172.17.255.255 dev eth0 table local proto kernel scope link src 172.17.0.2 
fe80::/64 dev eth0 table eth0_local proto static metric 1024 pref medium
fe80::/64 dev eth0 table eth0 proto static metric 1024 pref medium
fe80::/64 dev eth0 proto kernel metric 256 pref medium
local fe80::3fcd:8604:cea7:9ecd dev eth0 table local proto kernel metric 0 pref medium
multicast ff00::/8 dev eth0 table local proto kernel metric 256 pref medium

I can also use this output to extract all the tables which have routing rules, unlike ip-rule(8) output which shows several empty routing tables. The command I came up with which is probably not the shortest is this:

c340878bf6f5:/ # ip route show table all | grep -o "table [a-zA-Z_0-9]*" | cut -d' ' -f2 | sort | uniq
eth0
eth0_local
local

That’s it, I didn’t realise how I can ping something without having a default gateway and this was the answer.