# 透過 Netfilter 自動過濾廣告 contributed by < `ZhuMon` > ###### tags: `linux2020` `sysprog2020` `final project` ## 動機 儘管我們可在網頁瀏覽器中透過像是 [AdBlock](https://getadblock.com/) 這類的 extension 來過濾廣告,但需要額外的設定和佔用更多系統資源,倘若我們能透過 netfilter,直接在核心層級過濾網路廣告,那所有應用程式都有機會受益。 參考資訊: * [How to drop 10 million packets per second](https://blog.cloudflare.com/how-to-drop-10-million-packets/) * [Use the iptables firewall to block ads on your Linux machine](https://securitronlinux.com/debian-testing/use-the-iptables-firewall-to-block-ads-on-your-linux-machine/) * [nBlock](https://github.com/notracking/nBlock) ## 預計目標和特徵 * 找到廣告的 IP list * https://github.com/notracking/hosts-blocklists * 使用 iptables 能夠成功抵擋廣告 > 光使用 IP 無法正確阻擋廣告 * 使用 netfilter 快速將廣告封包丟棄 * 參考 [How to drop 10 million packets per second](https://blog.cloudflare.com/how-to-drop-10-million-packets/) 實現環境後,以自己的方法丟棄廣告封包,並比較各種方式 > 若將上述 block list 的網域皆 mapping 到 localhost,是否便能阻檔廣告,並且不用使用到 netfilter? --- ## 阻檔廣吿方式 1. 得到廣告的 IP,使用 iptables 禁止與該 IP 來往 * 優點:快速 * 缺點:需要經常動態變化,有些 IP 對應到許多服務,若是全部禁止,可能影響到正常服務 2. 得到廣告的網址,使用 DNS 將其 IP 對應到自己(0.0.0.0) * 優點:正確 * 缺點:瀏覽網站需要更多時間比對網址 3. 使用 regular expression 比對網站原始碼,當比對到相關字串,便可以將其 element 設為 none --- ## 將網域 mapping 到 localhost * 將 [adblock.txt](https://github.com/notracking/hosts-blocklists/blob/master/adblock/adblock.txt) 中的網址 map 到 127.0.0.1 * 在 `/etc/hosts` 依照下列格式將 adblock.txt 的網域 map 到 127.0.0.1 ```shell 127.0.0.1 googleads.g.doubleclick.net ``` * 結果 * 原先網站(使用痞克邦作為範例) * ![](https://i.imgur.com/T9aBg9I.png) * 將 adblock.txt 內的網域放到 hosts * ![](https://i.imgur.com/oeWcZB5.png) * 可以明顯看到原先的廣告消失,只是顯示無法連接到伺服器 > 記得要重開瀏覽器,因為瀏覽器會暫存廣告 --- ## :fountain: 環境 :::spoiler ```shell $ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=20.04 DISTRIB_CODENAME=focal DISTRIB_DESCRIPTION="Ubuntu 20.04.1 LTS" $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 36 bits physical, 48 bits virtual CPU(s): 4 On-line CPU(s) list: 0-3 Thread(s) per core: 2 Core(s) per socket: 2 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 37 Model name: Intel(R) Core(TM) i5 CPU 650 @ 3.20GHz Stepping: 2 Frequency boost: enabled CPU MHz: 1501.559 CPU max MHz: 3193.0000 CPU min MHz: 1197.0000 BogoMIPS: 6399.55 Virtualization: VT-x L1d cache: 64 KiB L1i cache: 64 KiB L2 cache: 512 KiB L3 cache: 4 MiB NUMA node0 CPU(s): 0-3 $ uname -sr Linux 5.4.0-54-generic $ gcc --version gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 ``` ::: --- ## Netfilter > 參考 https://netfilter.org/documentation/HOWTO/netfilter-hacking-HOWTO.html * Netfilter 為 Linux 內建的網路封包處理框架 1. 每個 protocol 都定義了 `hooks`,開發者可以向 `hooks` 定義行為,封包在通過該點時,就會進行該行為 * `hooks` 包含「進行 routing 前」、「進行 routing 後」、「送出封包前」等等 2. kernel module 可以監聽封包是否到達某 `hook`,以便對封包進行操作(通過、捨棄、存入 Queue...) ## iptables 參考 [Use the iptables firewall to block ads on your Linux machine](https://securitronlinux.com/debian-testing/use-the-iptables-firewall-to-block-ads-on-your-linux-machine/) * 簡介 > `$ man iptables` > iptables/ip6tables — administration tool for IPv4/IPv6 packet filtering and NAT iptables 藉由 kernel 內的 netfilter 模組,在 User Space 管理網路封包的處理和轉發 * Lab * 以 iptables 寫入 routing rule * 將來自廣告網域的封包捨棄,以達到封鎖廣告的目標 * 使用以下 rule 作為測試 https://pgl.yoyo.org/as/iplist.php?ipformat=iptables * 將上述 script 下載,並放置於 `/etc/ads` > 使用 `wget` 下載需要注意檔案一開始的 html,需先將其刪除 * 以 `ifconfig` 觀察自己的網路介面卡名字為何,因為我的名字是 `eno1`,所以要將 `ads` 內的 `eth0` 換為 `eno1` * [參考資料](https://securitronlinux.com/debian-testing/use-the-iptables-firewall-to-block-ads-on-your-linux-machine/) 使用 `$ iptables-restore < /etc/ads`,將 iptables 的 rule 寫入 * 寫入的 iptables rule 格式如下 ```shell $ sudo iptables -A OUTPUT -o eno1 -d 103.245.223.129 -j REJECT ``` |Commands / Options| Description | |-|-| |-A, --append chain| 將 rule 加到指定 chain 的最後| |-o, --out-interface output name[+]| 選擇封包出去的網路介面卡 | |-d, --destination address[/mask][...] | 封包的目的地 | |-j, --jump target | 此 rule 要執行的動作 | > 使用的 rule 竟然是禁止往外的封包? * 無法使用 `$ iptables-restore < /etc/ads` 將 rules 放入,因此直接使用 `$ sh /etc/ads` 執行 * 使用無痕模式開啟 youtube,可以看到上半部有 apple 的廣告 ![](https://i.imgur.com/97xzAkJ.jpg) * 而添加 rules 後,依舊存有廣告,代表這些 IP 有可能不是廣告的 IP ## nftables * [繼 iptables 之後的新一代包過濾框架是 nftables](https://www.itread01.com/content/1545421335.html) * 簡介 * 用來取代 iptables ## XDP > * [XDP Hands-On Tutorial](https://github.com/xdp-project/xdp-tutorial) > * [The eXpress Data Path: Fast Programmable Packet Processing in the Operating System Kernel](https://github.com/xdp-project/xdp-paper/blob/master/xdp-the-express-data-path.pdf) * 簡介 * 全名為 eXpress Data Path,可以在封包被 kernel 任何程序處理前執行,是 eBPF 的一個 subset <img width=400 src="https://i.imgur.com/5DV20t5.png"></img> * 以下參考自 https://github.com/xdp-project/xdp-tutorial * 安裝環境 > https://github.com/xdp-project/xdp-tutorial/blob/master/setup_dependencies.org * 使用 [libbpf](https://github.com/libbpf/libbpf/) 作為 bpf-program * clone repository ``` $ git clone --recurse-submodules https://github.com/xdp-project/xdp-tutorial ``` * dependency ``` $ sudo apt install clang llvm libelf-dev libpcap-dev gcc-multilib build-essential $ sudo apt install linux-tools-$(uname -r) $ sudo apt install linux-headers-$(uname -r) $ sudo apt install linux-tools-common linux-tools-generic ``` * basic01-xdp-pass > https://github.com/xdp-project/xdp-tutorial/tree/master/basic01-xdp-pass * loading your first BPF program 1. Compile * 若是 dependencies 有安裝成功,`make` 應該會過 ```shell $ make ``` 2. 可以使用 llvm-objdump 看一下編譯後的組合語言 ```shell $ llvm-objdump -S xdp_pass_kern.o xdp_pass_kern.o: file format ELF64-BPF Disassembly of section xdp: 0000000000000000 xdp_prog_simple: ; return XDP_PASS; 0: b7 00 00 00 02 00 00 00 r0 = 2 1: 95 00 00 00 00 00 00 00 exit ``` 3. 使用 `ip` command load 我們的 BPF-program ```shell $ ip link set dev lo xdpgeneric obj xdp_pass_kern.o sec xdp mkdir /sys/fs/bpf/tc/ failed: Permission denied Continuing without mounted eBPF fs. Too old kernel? Prog section 'xdp' rejected: Operation not permitted (1)! - Type: 6 - Instructions: 2 (0 over limit) - License: GPL Verifier analysis: Error fetching program/map! ``` Permission denied,加上 `sudo` 試試 ```shell $ sudo ip link set dev lo xdpgeneric obj xdp_pass_kern.o sec xdp BTF debug data section '.BTF' rejected: Invalid argument (22)! - Length: 532 Verifier analysis: magic: 0xeb9f version: 1 flags: 0x0 hdr_len: 24 type_off: 0 type_len: 256 str_off: 256 str_len: 252 btf_total_size: 532 [1] PTR (anon) type_id=2 [2] STRUCT xdp_md size=20 vlen=5 data type_id=3 bits_offset=0 data_end type_id=3 bits_offset=32 data_meta type_id=3 bits_offset=64 ingress_ifindex type_id=3 bits_offset=96 rx_queue_index type_id=3 bits_offset=128 [3] TYPEDEF __u32 type_id=4 [4] INT unsigned int size=4 bits_offset=0 nr_bits=32 encoding=(none) [5] FUNC_PROTO (anon) return=6 args=(1 ctx) [6] INT int size=4 bits_offset=0 nr_bits=32 encoding=SIGNED [7] FUNC xdp_prog_simple type_id=5 vlen != 0 ``` 4. 使用 `ip link show` 查看有沒有成功 ```shell $ ip link show dev lo 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 prog/xdp id 212 ``` 5. 刪除 ```shell $ sudo ip link set dev lo xdpgeneric off $ ip link show dev lo 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 ``` --- ## 參考資料 * https://www.ichenfu.com/2019/03/10/how-to-drop-10-million-packets-per-second/ * http://arthurchiao.art/blog/conntrack-design-and-implementation-zh/ * http://engineer-leo.blogspot.com/2015/06/netfilter.html