# 透過 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
```
* 結果
* 原先網站(使用痞克邦作為範例)
* 
* 將 adblock.txt 內的網域放到 hosts
* 
* 可以明顯看到原先的廣告消失,只是顯示無法連接到伺服器
> 記得要重開瀏覽器,因為瀏覽器會暫存廣告
---
## :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 的廣告

* 而添加 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