-
Notifications
You must be signed in to change notification settings - Fork 19
feat(adblock): implement device-based enforcement and migration scripts #1875
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -736,12 +736,37 @@ def dns_list_zones(e_uci): | |
| zones.append(zone['name']) | ||
| return { 'data': zones } | ||
|
|
||
| def dns_get_enforced_zones(e_uci): | ||
| # the user selection is stored in ns_tsdns_zones: adb_nftdevforce holds the | ||
| # devices derived from it and can't be mapped back to zones without ambiguity | ||
| zones = list(e_uci.get('adblock', 'global', 'ns_tsdns_zones', list=True, default=[])) | ||
| if zones: | ||
| return zones | ||
| # before the switch to device-based enforcement the selection was stored inside | ||
| # adb_nftdevforce: keep reading it until the migration has run | ||
| return list(e_uci.get('adblock', 'global', 'adb_nftdevforce', list=True, default=['lan'])) | ||
|
|
||
| def dns_zones_to_devices(e_uci, zones): | ||
| # adb_nftdevforce is rendered as an nft iifname match, so it needs the devices | ||
| # of the selected zones and not the zone names | ||
| devices = [] | ||
| for zone in zones: | ||
| for device in utils.get_all_devices_by_zone(e_uci, zone, exclude_aliases=True): | ||
| if device not in devices: | ||
| devices.append(device) | ||
| return sorted(devices) | ||
|
Comment on lines
+752
to
+757
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Next time you can use a |
||
|
|
||
| def dns_set_enforced_devices(e_uci, zones): | ||
| devices = dns_zones_to_devices(e_uci, zones) | ||
| if devices: | ||
| e_uci.set('adblock', 'global', 'adb_nftdevforce', devices) | ||
| elif e_uci.get('adblock', 'global', 'adb_nftdevforce', list=True, default=[]): | ||
| # no device in the selected zones: adblock skips the enforcement altogether | ||
| e_uci.delete('adblock', 'global', 'adb_nftdevforce') | ||
|
|
||
| def dns_list_settings(e_uci): | ||
| ts_enabled = e_uci.get('adblock', 'global', 'ts_enabled', default='0') | ||
| try: | ||
| zones = list(e_uci.get_all('adblock', 'global', 'adb_nftdevforce')) | ||
| except: | ||
| zones = ['lan'] | ||
| zones = dns_get_enforced_zones(e_uci) | ||
| try: | ||
| ports = list(e_uci.get_all('adblock', 'global', 'adb_nftportforce')) | ||
| except: | ||
|
|
@@ -750,13 +775,15 @@ def dns_list_settings(e_uci): | |
|
|
||
| def dns_edit_settings(e_uci, payload): | ||
| if payload['enabled']: | ||
| if 'zones' in payload and 'wan' in payload['zones']: | ||
| raise ValidationError('zones', 'wan_zone_not_allowed', payload['zones']) | ||
| zones = payload.get('zones', ['lan']) | ||
| if 'wan' in zones: | ||
| raise ValidationError('zones', 'wan_zone_not_allowed', zones) | ||
| e_uci.set('adblock', 'global', 'ts_enabled', '1') | ||
| e_uci.set('adblock', 'global', 'adb_enabled', '1') | ||
| e_uci.set('adblock', 'global', 'adb_triggerdelay', '30') | ||
| e_uci.set('adblock', 'global', 'adb_nftforce', '1') | ||
| e_uci.set('adblock', 'global', 'adb_nftdevforce', payload.get('zones', ['lan'])) | ||
| e_uci.set('adblock', 'global', 'ns_tsdns_zones', zones) | ||
| dns_set_enforced_devices(e_uci, zones) | ||
| e_uci.set('adblock', 'global', 'adb_nftportforce', payload.get('ports', ['53', '853'])) | ||
| else: | ||
| e_uci.set('adblock', 'global', 'ts_enabled', '0') | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,9 +56,13 @@ define Package/ns-threat_shield/install | |
| $(INSTALL_DATA) ./files/banip.nethesis.feeds $(1)/etc/banip | ||
| $(INSTALL_BIN) ./files/adjust-banip.py $(1)/usr/libexec/ns-api/post-commit/ | ||
| $(INSTALL_BIN) ./files/configure-banip-wans.py $(1)/usr/libexec/ns-api/pre-commit/ | ||
| $(INSTALL_BIN) ./files/configure-adblock-devices.py $(1)/usr/libexec/ns-api/pre-commit/ | ||
| $(INSTALL_DIR) $(1)/usr/libexec | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move it at the top where all install dir resides, this missing could've cause issues. |
||
| $(INSTALL_BIN) ./files/adblock-devices-migrate.py $(1)/usr/libexec/ts-dns-migrate-devices | ||
| $(INSTALL_DIR) $(1)/etc/uci-defaults | ||
| $(INSTALL_BIN) ./files/banip-defaults $(1)/etc/uci-defaults/99-nethsec-banip | ||
| $(INSTALL_BIN) ./files/35_ns-threat_shield $(1)/etc/uci-defaults/35_ns-threat_shield | ||
| $(INSTALL_BIN) ./files/36_ns-threat_shield $(1)/etc/uci-defaults/36_ns-threat_shield | ||
| gzip -9n $(1)/usr/share/threat_shield/nethesis-dns.sources | ||
| gzip -9n $(1)/usr/share/threat_shield/community-dns.sources | ||
| endef | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| #!/bin/sh | ||
|
|
||
| # | ||
| # Copyright (C) 2026 Nethesis S.r.l. | ||
| # SPDX-License-Identifier: GPL-2.0-only | ||
| # | ||
|
|
||
| # Rebuild the adblock local DNS enforcement on systems upgraded from adblock 4.1.5, | ||
| # where adb_nftdevforce ended up holding firewall zone names instead of devices. | ||
|
|
||
| set -e | ||
|
|
||
| /usr/libexec/ts-dns-migrate-devices |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| #!/usr/bin/env python | ||
|
|
||
| # | ||
| # Copyright (C) 2026 Nethesis S.r.l. | ||
| # SPDX-License-Identifier: GPL-2.0-only | ||
| # | ||
|
|
||
| # this script is supposed to be run by the 36_ns-threat_shield uci defaults | ||
| # | ||
| # Up to adblock 4.1.5 the local DNS enforcement was configured through adb_zonelist, | ||
| # holding firewall zone names, and adblock turned it into uci redirect sections | ||
| # letting fw4 resolve the zones. Since 4.5.5 the enforcement is rendered as nft rules | ||
| # matching on iifname, so adb_nftdevforce needs device names: the zone names stored | ||
| # there produce rules that never match and DNS traffic is no longer redirected. | ||
| # | ||
| # Move the selection to ns_tsdns_zones and rebuild adb_nftdevforce out of it. | ||
|
|
||
| import subprocess | ||
|
|
||
| from euci import EUci | ||
| from nethsec import utils | ||
|
|
||
|
|
||
| def migrate_zones(): | ||
| e_uci = EUci() | ||
|
|
||
| if list(e_uci.get('adblock', 'global', 'ns_tsdns_zones', list=True, default=[])): | ||
| # already migrated | ||
| return False | ||
|
|
||
| zones = list(e_uci.get('adblock', 'global', 'adb_nftdevforce', list=True, default=[])) | ||
| if not zones: | ||
| return False | ||
|
|
||
| devices = [] | ||
| for zone in zones: | ||
| for device in utils.get_all_devices_by_zone(e_uci, zone, exclude_aliases=True): | ||
| if device not in devices: | ||
| devices.append(device) | ||
|
|
||
| if not devices: | ||
| # the stored values are not zone names, or the zones have no interface: | ||
| # leave the configuration untouched rather than clearing it | ||
| return False | ||
|
|
||
| e_uci.set('adblock', 'global', 'ns_tsdns_zones', zones) | ||
| e_uci.set('adblock', 'global', 'adb_nftdevforce', sorted(devices)) | ||
| e_uci.commit('adblock') | ||
| return True | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| if migrate_zones(): | ||
| subprocess.run(["/etc/init.d/adblock", "restart"], capture_output=True) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| #!/usr/bin/python | ||
|
|
||
| # | ||
| # Copyright (C) 2026 Nethesis S.r.l. | ||
| # SPDX-License-Identifier: GPL-2.0-only | ||
| # | ||
|
|
||
| # This script keeps the adblock local DNS enforcement aligned with the network setup: | ||
| # adb_nftdevforce is rendered as an nft iifname match, so it must list the devices of | ||
| # the zones selected in ns_tsdns_zones. Without this, adding an interface to an | ||
| # enforced zone would silently leave its DNS traffic unfiltered. | ||
|
|
||
| # The changes variable is already within the scope from the caller | ||
| if 'adblock' in changes or 'firewall' in changes or 'network' in changes: | ||
| import syslog | ||
| from euci import EUci | ||
| from nethsec import utils | ||
|
|
||
| uci = EUci() | ||
| zones = list(uci.get('adblock', 'global', 'ns_tsdns_zones', list=True, default=[])) | ||
|
|
||
| if zones: | ||
| devices = [] | ||
| for zone in zones: | ||
| for device in utils.get_all_devices_by_zone(uci, zone, exclude_aliases=True): | ||
| if device not in devices: | ||
| devices.append(device) | ||
| # keep a stable order, the value is compared before being rewritten | ||
| devices = sorted(devices) | ||
|
|
||
| current = list(uci.get('adblock', 'global', 'adb_nftdevforce', list=True, default=[])) | ||
| if devices != current: | ||
| if devices: | ||
| uci.set('adblock', 'global', 'adb_nftdevforce', devices) | ||
| else: | ||
| uci.delete('adblock', 'global', 'adb_nftdevforce') | ||
| uci.save('adblock') | ||
| # adblock is reloaded by the uci reload_config at the end of the commit | ||
| if 'adblock' not in changes: | ||
| changes['adblock'] = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After the uci default, this is not needed, right?
We can safely remove it and put this logic underneath