How to debug/examine data usage leaks in Android using iptables

First of all, you'll need a rooted device. I guess that's not much of a surprise after all. Wink Then you'll need the iptables executable.

I'm not quite sure how it came on my device (it's path is /system/bin/iptables which makes the impression as if it was part of the factory Android setup). Maybe it was already there or it was installed by some app, I don't know. But one certain way to get it is via installing an app that uses it and therefore installs it as a dependency. There're quite a few of these, eg.:
These have an iptables_armv5 binary in their data directory, eg. /data/data/com.googlecode.droidwall.free/app_bin/iptables_armv5.

And if you're already at installing apps, you should probably get BusyBox as well, since lots of standard linux commands are not available by default on your Android device and BusyBox is an easy way to get them.

Now that you're prepared, let's get down to the dirty work. You should get familiar with iptables so if you haven't yet done so, it's time to read the manual page or some howto/guide.

First: print and save the default rules list for later reference:
iptables -L -n -v

You'll be messing with the rules and having a copy of the original ones is always good practise. Smile You should save the ruleset as iptables commands as well:
iptables -S

The default ruleset (without any firewall app/rules enabled) looks like this for me (running a Jelly Bean 4.1.2 on a Galaxy Nexus phone):
$ adb shell su -c "iptables -L -n -v"
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target     prot opt in     out     source               destination

    0     0 bw_INPUT   all  --  *      *       0.0.0.0/0            0.0.0.0/0


Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target     prot opt in     out     source               destination

    0     0 bw_FORWARD  all  --  *      *       0.0.0.0/0            0.0.0.0/0

    0     0 natctrl_FORWARD  all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target     prot opt in     out     source               destination

    0     0 bw_OUTPUT  all  --  *      *       0.0.0.0/0            0.0.0.0/0


Chain bw_FORWARD (1 references)
pkts bytes target     prot opt in     out     source               destination


Chain bw_INPUT (1 references)
pkts bytes target     prot opt in     out     source               destination

    0     0            all  --  !lo+   *       0.0.0.0/0            0.0.0.0/0
         ! quota globalAlert: 2097152 bytes
    0     0 RETURN     all  --  lo     *       0.0.0.0/0            0.0.0.0/0

    0     0            all  --  *      *       0.0.0.0/0            0.0.0.0/0
         owner socket exists

Chain bw_OUTPUT (1 references)
pkts bytes target     prot opt in     out     source               destination

    0     0            all  --  *      !lo+    0.0.0.0/0            0.0.0.0/0
         ! quota globalAlert: 2097152 bytes
    0     0 RETURN     all  --  *      lo      0.0.0.0/0            0.0.0.0/0

    0     0            all  --  *      *       0.0.0.0/0            0.0.0.0/0
         owner socket exists

Chain costly_shared (0 references)
pkts bytes target     prot opt in     out     source               destination

    0     0 penalty_box  all  --  *      *       0.0.0.0/0            0.0.0.0/0


Chain natctrl_FORWARD (1 references)
pkts bytes target     prot opt in     out     source               destination


Chain penalty_box (1 references)
pkts bytes target     prot opt in     out     source               destination

    0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0
         owner UID match 10140 reject-with icmp-net-prohibited
    0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0
         owner UID match 10107 reject-with icmp-net-prohibited
    0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0
         owner UID match 1019 reject-with icmp-net-prohibited
    0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0
         owner UID match 1013 reject-with icmp-net-prohibited
    0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0
         owner UID match 10065 reject-with icmp-net-prohibited
    0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0
         owner UID match 10008 reject-with icmp-net-prohibited
(...)
    0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0
         owner UID match 10193 reject-with icmp-net-prohibited
    0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0
         owner UID match 10153 reject-with icmp-net-prohibited
    0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0
         owner UID match 10158 reject-with icmp-net-prohibited
    0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0
         owner UID match 10123 reject-with icmp-net-prohibited
    0     0 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0
         owner UID match 10133 reject-with icmp-net-prohibited

Most probably you'll never ever get any packets in the FORWARD chain, so for now we will deal only with the INPUT and OUTPUT chains.

Let's add some logging to both:
iptables -I INPUT 1 -j LOG --log-prefix "[IPT IN START] " --log-level 4 --log-uid
iptables -A INPUT -j LOG --log-prefix "[IPT IN END] " --log-level 4 --log-uid
iptables -I OUTPUT 1 -j LOG --log-prefix "[IPT OUT START] " --log-level 4 --log-uid
iptables -A OUTPUT -j LOG --log-prefix "[IPT OUT END] " --log-level 4 --log-uid

Your rules for the INPUT and OUTPUT chain should look like this now:
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target     prot opt in     out     source               destination

    0     0 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0
         LOG flags 8 level 4 prefix "[IPT IN START] "
    0     0 bw_INPUT   all  --  *      *       0.0.0.0/0            0.0.0.0/0

    0     0 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0
         LOG flags 8 level 4 prefix "[IPT IN END] "

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target     prot opt in     out     source               destination

    0     0 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0
         LOG flags 8 level 4 prefix "[IPT OUT START] "
    0     0 bw_OUTPUT  all  --  *      *       0.0.0.0/0            0.0.0.0/0

    0     0 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0
         LOG flags 8 level 4 prefix "[IPT OUT END] "

Now open up two other ADB shells and start monitoring these logs:
$ adb shell su -c "fgrep '[IPT IN ' /proc/kmsg"
$ adb shell su -c "fgrep '[IPT OUT ' /proc/kmsg"

Open up the stock "Data usage" app on your device (it's in "Settings" in Ice Cream Sandwich and Jelly Bean) and take a look at the currently used bandwidth of your mobile data service.

Now enable your mobile data connection (APN) and check the logs. You'll see tons of apps doing tons of stuff. This is because many apps get notified when an internet connection becomes available and many instantly start reaching out to various services (eg. Google Play will instantly connect to Google's servers to check for app updates, etc.).

Now you can start experimenting with various firewall apps, etc. and see what traffic/rules do generate data usage (according to the stock "Data usage" app).

Let's do a little experiment.
Add a single drop rule into the output chain. Smile
iptables -F OUTPUT
iptables -A OUTPUT -j DROP

Now start again, ie. note the current value of data usage and enable the mobile data connection while you're monitoring the logs.

Obviously you'll see no logs for the output chain since we deleted the logging rule. But the surprise comes in the INPUT chain! There're occasionally packets coming in through the mobile connection! And if you check the data usage, the counter indeed increased! It might take a minute or two, but the packets will turn up in the INPUT chain. For me the following traffic got logged:
<4>[ 2696.294952] [IPT IN START] IN=rmnet0 OUT= MAC= SRC=173.194.70.188 DST=5.204.72.62 LEN=52 TOS=0x00 PREC=0x00 TTL=50 ID=10861 PROTO=TCP SPT=5228 DPT=57862 WINDOW=256 RES=0x00 ACK FIN URGP=0
<4>[ 2696.295715] [IPT IN END] IN=rmnet0 OUT= MAC= SRC=173.194.70.188 DST=5.204.72.62 LEN=52 TOS=0x00 PREC=0x00 TTL=50 ID=10861 PROTO=TCP SPT=5228 DPT=57862 WINDOW=256 RES=0x00 ACK FIN URGP=0
<4>[ 2696.296752] [IPT IN START] IN=rmnet0 OUT= MAC= SRC=173.194.70.188 DST=5.204.72.62 LEN=52 TOS=0x00 PREC=0x00 TTL=50 ID=10862 PROTO=TCP SPT=5228 DPT=57862 WINDOW=256 RES=0x00 ACK FIN URGP=0
<4>[ 2696.297515] [IPT IN END] IN=rmnet0 OUT= MAC= SRC=173.194.70.188 DST=5.204.72.62 LEN=52 TOS=0x00 PREC=0x00 TTL=50 ID=10862 PROTO=TCP SPT=5228 DPT=57862 WINDOW=256 RES=0x00 ACK FIN URGP=0
<4>[ 2696.596588] [IPT IN START] IN=rmnet0 OUT= MAC= SRC=173.194.70.188 DST=5.204.72.62 LEN=52 TOS=0x00 PREC=0x00 TTL=50 ID=10863 PROTO=TCP SPT=5228 DPT=57862 WINDOW=256 RES=0x00 ACK FIN URGP=0
<4>[ 2696.597290] [IPT IN END] IN=rmnet0 OUT= MAC= SRC=173.194.70.188 DST=5.204.72.62 LEN=52 TOS=0x00 PREC=0x00 TTL=50 ID=10863 PROTO=TCP SPT=5228 DPT=57862 WINDOW=256 RES=0x00 ACK FIN URGP=0
<4>[ 2700.734130] [IPT IN START] IN=rmnet0 OUT= MAC= SRC=173.194.70.188 DST=5.204.72.62 LEN=52 TOS=0x00 PREC=0x00 TTL=50 ID=10864 PROTO=TCP SPT=5228 DPT=57862 WINDOW=256 RES=0x00 ACK FIN URGP=0
<4>[ 2700.734832] [IPT IN END] IN=rmnet0 OUT= MAC= SRC=173.194.70.188 DST=5.204.72.62 LEN=52 TOS=0x00 PREC=0x00 TTL=50 ID=10864 PROTO=TCP SPT=5228 DPT=57862 WINDOW=256 RES=0x00 ACK FIN URGP=0

Now that's something quite interesting. All outgoing packets are dropped, thus theoretically your "random" IP address (that most probably changes every time you connect to your ISP) should not be known to any third party ... only your ISP should know about your new IP. But if you resolve the source address of the incoming packets, it turns out that the traffic is coming from Google servers! Shock For me it was 173.194.70.188 which resolves to fa-in-f188.1e100.net, which belongs (according to the Whois database) to Google Inc. I got 4 incoming TCP packets not much after the connection was estabilished and they took up 208 bytes of "unwanted" traffic.

This pretty much proves that there must be some outgoing traffic (probably when the mobile data connection is built up) between your Android device and Google's servers that either does not go through the OUTPUT chain or is hidden from the OUTPUT chain. Btw. you can see above that those incoming TCP packets are most likely the termination phase of a previous TCP connection initiated by your Android device. This strengthens the theory that there was some sort of an outgoing connection that was not piped through the OUTPUT chain and was not closed properly (since iptables got these handshake terminating TCP packets, but not the preceeding packets).

Sad news, but that's life. So unfortunately you're not in full control of your Android device's network traffic and data usage. Google added some behind-the-scenes traffic and you've no chance to stop it (unless you compile your Android from source and dig up and comment out the code that is responsible for this traffic). Or you can manually enable/disable the mobile data connection and use it only whenever you're up for it.

One more thing: DNS requests. Smile They are small, but many small IP packets can lead to considerable data usage over time.

If you install eg. "avast! Mobile Security" (it's firewall is the descendant of DroidWall) and disable mobile data for all apps and turn on the whitelisting mode, there still will be a single iptables rule that'll allow outgoing traffic: it's for DNS requests and looks like this:
Chain avastwall (1 references)
pkts bytes target     prot opt in     out     source               destination

    0     0 RETURN     udp  --  *      *       0.0.0.0/0            0.0.0.0/0
         udp dpt:53

It comes before any 3G or Wi-Fi rules/chains so it'll allow DNS requests at all times. A DNS request doesn't take too many bytes (it's an UDP based protocol and each packet is around 62 bytes and a request takes at most only a few packets), but it seems to me that Android either does not have a DNS cache or it's quite inferior, because the very same DNS lookups were sent out again and again within a couple of minutes.

If you've an app that does generate frequent DNS lookups, these can add up to a fair amount of data usage.

One more thing: there're quite a few types of packets that are reported by the "Data usage" app as part of the network traffic of "Android OS". Among others DNS requests belong to these kind of traffic. But don't get fooled: UID=1000 belongs to an Android component that is usually called "Android OS", but DNS requests come from a process running as root (UID=0) -according to iptables-, so if you see "Android OS" in an app list, it can mean a root process or an UID=1000 process as well.

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Unwanted traffic

I had the same problem, when I checked the source address (me) was composed of two parts, an ipv6 was first and then my ISP assigned internal ipv4. Blocking outgoing traffic on iptables did not work, so I compiled a binary for ip6tables and blocked all traffic and those connections disappearead.
I don't know what it contains, but it does connect to google and it is a TCP connection, it only lasts a few seconds and then disappears. Check them using 'netstat -t'.

JR

100 Kilobytes a day data plan

With my Taiwan CHT 183 plan, I have about 3MB of data I can use free if I don't make many phone calls. I.e., 100 Kilobytes a day. From reading this I see that not many tools I can use will give me the exact amount that I can expect on the next bill..