The problem can be split in two parts:
identify when a restricted access point (tethering's) is in use
identify which outgoing packets belong to restricted applications
Combining the two together will identify packets belonging to restricted applications going out using the restricted access point (in order to drop such packets).
Preparation
Some glue before rather than after explanations, so typing everything in order just works.
Use two iptables chains: one doing the action (blocksomeapps), the other (tetheractivated) to call it and that can be easily flushed:
iptables -N tetheractivated
iptables -N blocksomeapps
iptables -I OUTPUT -j tetheractivated
1. Access point
This answer assumes only one access point is in use at any moment, and that this access point is using an already known network interface name (wlan0
). It also assumes wpa_supplicant
will be the low level daemon handling it. It doesn't matter if standalone or a backend of NetworkManager, as long as it can be queried with wpa_cli
and its -a
(action) option.
action script myaction.sh
:
#!/bin/sh
TETHERSSID='My Tethering'
case "$2" in
'CONNECTED')
ssid="$(wpa_cli -p "$WPA_CTRL_DIR" -i "$1" get_network "$WPA_ID" ssid)"
# Note: ssid's value already comes with an added pair of double quotes around:
# adding one as well
if [ "$ssid" = \""$TETHERSSID"\" ]; then
iptables -A tetheractivated -j blocksomeapps
fi
;;
'DISCONNECTED')
iptables -F tetheractivated
;;
esac
This script (which must be executable) is then used as an event loop by keeping this program running:
wpa_cli -a myaction.sh
This will then make the iptables chain blocksomeapps appear in the packet path when connected to the target SSID, and be removed from packet path when (any SSID is) disconnected.
2. Restricted applications
This is more difficult to handle. There are various methods to identify a packet belonging to a specific process, requiring anyway additional preparation. Some methods require more things than others.
Some examples:
- the packet could be enqueued to an userspace application doing additional checks (the program has to be written, probably in C or python)
- the restricted processes can be run as a separate user: quite easy to deal with.
- the restricted processes could be run from their own dedicated network namespace: once done, quite easy to deal with. Putting them there in the first place (involving configuration and probable root access and back to user access to start them) can give some trouble.
Here's a quite simple method found in this Q/A: Block network access of a process?
Classify packets from the same net_cls cgroup with a value that can be looked up in iptables (in addition to the main target: Traffic Controller tc
).
Create the specific net_cls group and give it a specific classid:
# mkdir -p /sys/fs/cgroup/net_cls/blocksomeapps
# echo 42 > /sys/fs/cgroup/net_cls/blocksomeapps/net_cls.classid
Identify target processes (threads must be included), and add them to the group (by writting to tasks
one pid (or tid) at a time. This could be racy if processes change a lot, so it should better done during startup of application. Of course once done, children automatically stay in the same group.
Example with firefox (which uses multiple threads within multiple processes, so pgrep
needs --lightweight
to output all thread ids):
# for i in $(pgrep --lightweight -f firefox); do echo $i > /sys/fs/cgroup/net_cls/blocksomeapps/tasks; done
( pid/tid to be removed from this group should be written to the parent /sys/fs/cgroup/net_cls/tasks
)
Add the blocking rule in the empty chain blocksomeapps prepared before:
# iptables -A blocksomeapps -m cgroup --cgroup 42 -j DROP
To block only packets going through wlan0
, use instead:
# iptables -A blocksomeapps -o wlan0 -m cgroup --cgroup 42 -j DROP
but then indirect activity generated by those applications, such as DNS queries to a local DNS caching daemon, might still generate some data traffic since for example the DNS caching daemon wouldn't be itself blocked.
Notes:
- The SSID part could be improved (and deal with multiple simultaneous access points) by involving route checks somewhere, but would rapidly become very complex. The simple
myaction.sh
script wouldn't then be enough (because knowing the SSID we connected to gives nothing about the network layer not even configured yet).
- Nowadays, cgroups have already been mounted and configured, probably by systemd or cgmanager. It's out of this Q/A scope to configure this, if it's not available.
- cgroups v1 are slowly being replaced by cgroups v2, so this answer should be adapted to v2 someday.
- Also this method will probably be incompatible (or at least difficult to use) with containers or network namespaces created with
ip netns
just because both apply restrictions to the use of cgroups (for ip netns
: as ip netns exec ...
remounts /sys/
, the /sys/fs/cgroup
tree becomes unavailable, and it can't really be mounted twice).
- All output packets cross the cgroup match.
tetheractivated
could be inserted after the usual ESTABLISHED
stateful rule to decrease load, but then already established flows wouldn't be dropped, which could keep them working for some time depending on the application. This could be helped with conntrack
(-D
or -F
).
- Instead of blocking traffic with iptables (or nftables)
tc
(and its cgroup filter) could be used to apply severe bandwidth restrictions. Just remember that while output traffic is in full control, incoming traffic is not: once a packet is received (and associated to the same flow than previous outgoing), even if dropped, data was already spent.