Try   HackMD

透過 Netfilter 自動過濾廣告

contributed by < ZhuMon >

tags: linux2020 sysprog2020 final project

動機

儘管我們可在網頁瀏覽器中透過像是 AdBlock 這類的 extension 來過濾廣告,但需要額外的設定和佔用更多系統資源,倘若我們能透過 netfilter,直接在核心層級過濾網路廣告,那所有應用程式都有機會受益。

參考資訊:

預計目標和特徵

  • 找到廣告的 IP list
  • 使用 iptables 能夠成功抵擋廣告

    光使用 IP 無法正確阻擋廣告

  • 使用 netfilter 快速將廣告封包丟棄

    若將上述 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 中的網址 map 到 127.0.0.1
  • /etc/hosts 依照下列格式將 adblock.txt 的網域 map 到 127.0.0.1
    ​​​​127.0.0.1 googleads.g.doubleclick.net
    
  • 結果
    • 原先網站(使用痞克邦作為範例)
      • Image Not Showing Possible Reasons
        • The image file may be corrupted
        • The server hosting the image is unavailable
        • The image path is incorrect
        • The image format is not supported
        Learn More →
    • 將 adblock.txt 內的網域放到 hosts
      • Image Not Showing Possible Reasons
        • The image file may be corrupted
        • The server hosting the image is unavailable
        • The image path is incorrect
        • The image format is not supported
        Learn More →
    • 可以明顯看到原先的廣告消失,只是顯示無法連接到伺服器

      記得要重開瀏覽器,因為瀏覽器會暫存廣告


Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
環境

$ 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

  • 簡介

    $ 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
    • 參考資料 使用 $ iptables-restore < /etc/ads,將 iptables 的 rule 寫入
    • 寫入的 iptables rule 格式如下
      ​​​​​​​​$ 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 的廣告
      Image Not Showing Possible Reasons
      • The image file may be corrupted
      • The server hosting the image is unavailable
      • The image path is incorrect
      • The image format is not supported
      Learn More →
    • 而添加 rules 後,依舊存有廣告,代表這些 IP 有可能不是廣告的 IP

nftables

XDP

  • 簡介

    • 全名為 eXpress Data Path,可以在封包被 kernel 任何程序處理前執行,是 eBPF 的一個 subset
      Image Not Showing Possible Reasons
      • The image file may be corrupted
      • The server hosting the image is unavailable
      • The image path is incorrect
      • The image format is not supported
      Learn More →
  • 以下參考自 https://github.com/xdp-project/xdp-tutorial

  • 安裝環境

    https://github.com/xdp-project/xdp-tutorial/blob/master/setup_dependencies.org

    • 使用 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 應該會過
      ​​​​​​​​$ make
      
    2. 可以使用 llvm-objdump 看一下編譯後的組合語言

      ​​​​​​​​$ 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

      ​​​​​​​​$ 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 試試

      ​​​​​​​​$ 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 查看有沒有成功

      ​​​​​​​​$ 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. 刪除

      ​​​​​​​​$ 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
      

參考資料