透過 netfilter,直接在核心層級過濾網路廣告。
下載指定的核心版本並解壓縮
$ wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.8.tar.xz
$ tar -xvf linux-6.8.tar.xz
查看目前版本
$ uname -r
6.5.0-35-generic
進入 linux-6.8 目錄,並複製核心檔案到當下目錄的 config 檔
$ cd linux-6.8
$ cp /boot/config-6.5.0-35-generic .config
配置 linux kernel,進入選單後將 config 檔載入
$ make menuconfig
點擊 load .config -> OK
點擊 save .config -> OK
離開選單
打開 .config
並將下列兩行內容刪除
CONFIG_SYSTEM_TRUSTED_KEYS=" "
CONFIG_SYSTEM_REVOCATION_KEYS=" "
編譯核心模組
$ make -j 8
編譯的過程中可能會遇到以下錯誤:
scripts/sign-file.c:25:10: fatal error: openssl/opensslv.h: No such file or directory
25 | #include <openssl/opensslv.h>
| ^~~~~~~~~~~~~~~~~~~~
compilation terminated.
make[2]: *** [scripts/Makefile.host:116: scripts/sign-file] Error 1
這個錯誤表示編譯過程中缺少 OpenSSL 的標頭檔 opensslv.h
,導致無法編譯 sign-file
工具
sudo apt-get install libssl-dev
安裝核心模組,安裝新核心,然後重新啟動系統
$ sudo make modules_install
$ sudo make install
$ sudo reboot
確認是否安裝成功
$ uname -mrs
Linux 6.8.0 x86_64
$ gcc --version
gcc (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
$ uname -mrs
Linux 6.8.0 x86_64
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 39 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 12
On-line CPU(s) list: 0-11
Vendor ID: GenuineIntel
Model name: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz
CPU family: 6
Model: 165
Thread(s) per core: 2
Core(s) per socket: 6
Socket(s): 1
Stepping: 2
CPU max MHz: 5000.0000
CPU min MHz: 800.0000
BogoMIPS: 5199.98
Virtualization features:
Virtualization: VT-x
Caches (sum of all):
L1d: 192 KiB (6 instances)
L1i: 192 KiB (6 instances)
L2: 1.5 MiB (6 instances)
L3: 12 MiB (1 instance)
NUMA:
NUMA node(s): 1
NUMA node0 CPU(s): 0-11
參照 ItisCaleb 於去年的實驗,可知一開始他是透過以 Linux 核心模組來阻擋已知的廣告網址,這些已知的網址以資料庫的方式儲存,如果封包經解析過後其域名符合儲存列表(hosts) 的內容,則拋棄。後來結合 eBPF 在 userspace 的應用程式透過額外的過濾程式來告訴作業系統它希望收到哪些網路封包。
不過在本實驗只執行使用 netfilter 操作核心模組來阻擋已知的廣告網址。
Netfilter 是 Linux 2.4 引入的一個子系統,它提供了一個用於實現高級網路功能的框架,例如封包過濾、網路位址轉換(NAT) 和連接跟蹤。它利用核心網路程式碼中的 Hook 來實現,核心程式碼可以為特定網路事件註冊呼叫函式的位置,例如當收到封包時,就會觸發事件的處理程式並執行模組指定的操作。
Iptables 允許系統管理員配置 Linux 核心防火牆的 IP 封包過濾規則。可透過 Iptables 工具使我們方便在 userspace 對 Netfilter 進行操作。
此外 Netfilter 框架提供了一種強大的機制,用於阻擋和操作 Linux 核心中的網路封包。該框架有 2 種元件 - Netfilter hooks 和 Conntrack。
Conntrack 所做的事情就是發現並追蹤這些連線的狀態,具體包括:
基本上 conntrack 可以用來清理掉封包的殘留連接狀態。例如在 TCP 協定中,如果有一個封包通過 iptables 的 DROP
目標被丟棄,但這個封包是 TCP 在 three way handshake 過程中的一部分(如 SYN 封包),則 Conntrack 可能會記錄這個封包的狀態為 SYN_RECV
,即已接收到 SYN 封包但尚未進行確認。在這種情況下,即使這個 SYN 封包被丟棄了,Conntrack 也可能會記錄相關的連接狀態。
不過在我們的專案中,這樣的情況在阻擋網路廣告時並不常見,所以基本上不太會用到這個指令。
Netfilter hooks 是在核心中註冊的函式,要在 network stack 中的特定點呼叫。這些 hooks 可以看作是在 stack 不同層中的檢查點。
每個 hook 點對應於封包處理的不同階段,如下圖所示:
PREROUTING
( NF_IP_PRE_ROUTING
): 封包進入路由表之前INPUT
( NF_IP_LOCAL_IN
): 通過路由表後,目的地為本機FORWARD
( NF_IP_FORWARD
): 通過路由表後,目的地不為本機OUTPUT
( NF_IP_LOCAL_OUT
): 由本機產生,向外轉發POSTROUTING
( NF_IP_POST_ROUTING
): 發送到網卡接口之前假設伺服器知道如何路由封包,防火牆允許封包傳輸,以下就是不同場景下封包的傳輸流程:
PREROUTING
-> INPUT
PREROUTING
-> FORWARD
-> POSTROUTING
OUTPUT
-> POSTROUTING
當封包到達或離開網路介面時,它會按順序通過每個 hook 點。而在每個 hook 點,核心都會為該 hook 點呼叫所有已註冊的 netfilter hook 函式。每個 netfilter hook 函式都可以檢查封包並決定如何處理它,可能的操作包括:
NF_ACCEPT
:繼續正常的封包處理NF_DROP
:將封包丟棄NF_STOLEN
:由 hook function 處理該封包,不再繼續傳送NF_QUEUE
: 將封包送入 NFQUEUE,通常交由 userspace 處理(如 iptables 或 nftables)NF_REPEAT
:再次呼叫該 hook funcitonBPF, eBPF, XDP
最初於 20 世紀 90 年代初開發,作為網路封包擷取和分析的封包過濾機制。它的設計目標是高效、輕量級和安全,只允許根據過濾規則捕獲特定的資料包。 BPF 實現了更快的資料包處理,並減少了將不必要的資料包從核心複製到 user space 的開銷。
核心概念是讓 user space 的應用程式可以透過額外的過濾程式來告訴作業系統它希望收到哪些網路封包,這麼做的好處顯而易見:系統可以在封包一進入到 Kernel Space 時就過濾掉沒有作用的封包,避免這些封包一路經過作業系統的 Networking Stack(網路堆疊)一路傳到 User Space 上面的應用程式。
jserv 教材
比起 BPF,它還可以用於非網路相關的功能,使開發者可以輕易地做到作業系統層級的動態追蹤,或是針對系統的某一個部分進行最佳化。
上圖顯示,當我們將 eBPF program 附加到 XDP Hook 上,NIC(網卡)接收到來自其他主機的封包,它會判斷應該對該封包做出何種行為:
一種基於 eBPF 的高效能資料路徑,目的是儘早處理網路封包,並繞過 Linux 的大部分網路堆疊,以高速率發送和接收網路封包。這允許在封包進入網卡驅動程式的最早階段就進行處理,從而顯著降低延遲和提高效能,透過它我們可以在網路封包進入到 Networking Stack 之前完成封包的處理。
XDP 和 netfilter 可以互補使用,XDP 提供低層次的高效封包過濾,而 netfilter 提供高層次的靈活封包處理
本 Linux 核心模組,用於阻擋和過濾特定網絡流量。主要功能包括:
hosts
列表內容包含我瀏覽各網頁時蒐集的廣告域名,以及此網站提供用於 iptables 的命令列表,域名位於每列命令的尾端。
struct sk_buff
sk_buff
(socket buffer) 結構在 Linux 核心中用於管理網路封包,它本身是一個 metadata structure,不包含任何封包資料,所以真正的封包資料儲存在它所指向的緩衝區內。
下圖的各指標是 sturct sk_buff
在封包緩衝區內的不同位置和緩衝區的佈局:
head
:指向已分配記憶體的開始位置data
:指向實際封包資料的開始位置tail
:指向封包資料的結束位置end
:指向已分配記憶體的結束位置headroom
:資料開始之前的空間data
:實際的封包資料tailroom
:資料結束之後的空間skb_shared_info
:儲存關於緩衝區的共享信息,比如分頁碎片(page frags) 和分段列表(frag_list)static struct nf_hook_ops filter_ops = {
.hook = filter_hook,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_PRE_ROUTING
};
static int mod_init(void) {
return nf_register_net_hook(&init_net, &filter_ops);
}
static void mod_exit(void) {
nf_unregister_net_hook(&init_net, &filter_ops);
}
module_init(mod_init);
module_exit(mod_exit);
註冊 hook 時,通過 struct nf_hook_ops
的 .hook
function point 來指定實際的處理函式 filter_hook
,該函式在被呼叫時返回 NF_DROP
或 NF_ACCEPT
來決定是否阻擋該封包。
struct nf_hook_ops
的參數:
hook
: 指向 netfilter hook function 的指標pf
: 要阻擋的封包協定類別hooknum
: 指定要呼叫函式的 hook point
NFPROTO_IPV4
和NF_INET_PRE_ROUTING
皆定義在 linux/netfilter.h
static int extract_tcp_data(struct sk_buff *skb, char **data)
{
struct iphdr *ip = NULL;
struct tcphdr *tcp = NULL;
size_t data_off, data_len;
if (!skb || !(ip = ip_hdr(skb)) || IPPROTO_TCP != ip->protocol)
return -1; // not ip - tcp
if (!(tcp = tcp_hdr(skb)))
return -1; // bad tcp
/* data length = total length - ip header length - tcp header length */
data_off = ip->ihl * 4 + tcp->doff * 4;
data_len = skb->len - data_off;
if (data_len == 0){
return -1;
}
if (skb_linearize(skb))
return -1;
*data = skb->data + data_off;
return data_len;
}
這段函式是從 sk_buff
結構中提取 TCP 資料,會先透過宣告的 *ip
和tcp
分別指向 ip_hdr
和 tcp_hdr
提取的 IP header 和 TCP header,接著檢查他們是否有被正確的提取。
計算封包資料的偏移量和長度過後利用 skb_linearize
將 paged skb 轉換成線性的,確保資料在記憶體區塊中是連續分佈,接著把資料指標指向實際資料位置後,回傳資料的長度以便 hook function 操作。
skb_linearize
主要作用是將非線性資料轉換為線性資料,以便於核心的處理和傳遞。執行成功時返回 0,失敗實則返回非 0 值。
static int extract_udp_data(struct sk_buff *skb, char **data)
{
struct iphdr *ip = NULL;
struct udphdr *udp = NULL;
size_t data_off, data_len;
if (!skb || !(ip = ip_hdr(skb)) || IPPROTO_UDP != ip->protocol)
return -1; // not ip - udp
if (!(udp = udp_hdr(skb)))
return -1; // bad udp
/* data length = total length - ip header length - udp header length */
data_off = ip->ihl * 4 + sizeof(struct udphdr);
data_len = skb->len - data_off;
if (skb_linearize(skb))
return -1;
*data = skb->data + data_off;
return data_len;
}
這段函式從 sk_buff
結構中提取 UDP 資料,基本上作法跟 extract_tcp_data
類似,但是在這段函式中將 tcp->doff * 4
修改為 sizeof(struct udphdr)
,這是因為 UDP header 是固定長度的,而 TCP header 可能包含額外的欄位。
static unsigned int filter_hook(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
在 filter_hook
函式中包含三個參數:
*priv
: 指向註冊 hook function 時傳遞私有資料的指標*skb
: 指向作為 sk_buff
結構的網路封包指標*state
: 指向 nf_hook_state
結構的指標,該結構包含有關 hook point 的資訊,例如網路協定、網路介面和路由資訊int len = extract_tcp_data(skb, &data);
if (len != -1) {
tcpflag = 1;
printk("TCP info: %s", data);
if (data[0] == 0x16) {
printk("TLS handshake len: %d", len);
proto = tls_protocol;
} else if (strncmp(data, "GET ", sizeof("GET ") - 1) == 0) {
proto = http_protocol;
printk("HTTP GET request detected.");
}
}
透過 extract_tcp_data
函式從 sk_buff
中提取到 TCP 資料後,檢查資料的類型,並將對應的協定設置到 proto
結構中,在後續的處理過程根據 proto
結構的內容來處理不同類型的協定資料(TLS 或 HTTP)。
在 TLS 協定資料中,第一個 byte 通常等於 0x16
,表示這是一個 TLS handshake 封包 ; 而 HTTP GET 請求的資料通常以 "GET " 開頭。
HTTP over TLS : HTTPS 經由 HTTP 進行通訊,但利用 SSL/TLS 來加密封包。HTTPS 開發的主要目的,是提供對網站伺服器的身分認證,保護交換資料的隱私與完整性。
TLS handshake 是啟動使用 TLS 之通訊工作階段的過程。過程中通訊雙方交換訊息以相互確認,彼此驗證,確立它們將使用的加密演算法,並生成一致的工作階段金鑰。
len = extract_udp_data(skb, &data);
if (len != -1) {
if (ntohs(udp_hdr(skb)->dest) == 53) {
proto = dns_protocol;
printk("Get UDP %d", len);
}
}
透過 ntohs 函式將 network byte 順序轉換為 host byte 順序。檢查提取到的 UDP 封包其 destination port 是否等於 53。port 53 是 DNS 的標準 port,因此這個檢查是為了確定該封包是否是 DNS 請求。
struct Protocol {
const char *const name;
const uint16_t default_port;
int (*const parse_packet)(const char *, size_t, char **);
const char *const abort_message;
const size_t abort_message_len;
};
創建 struct Protocol
結構描述封包的網絡協定內容
name
: 協定的名稱default_port
: 協定的默認 port,例如 HTTP 通常使用 port 80 、DNS 使用 port 53 、TLS 使用 port 443parse_packet
: 用於解析封包的 function point,從中提取有用的資訊,例如從 HTTP 請求中提取域名abort_message
: 當協定中止時使用的消息abort_message_len
: 中止消息的長度const struct Protocol *proto = NULL;
...
if (proto) proto->parse_packet(data, len, &host);
if (host) {
printk("Host: %s", host);
flag = in_word_set(host, strlen(host));
if (flag) {
printk("Dropping Packet");
ret = NF_DROP;
if (tcpflag) {
send_server_ack(skb, state);
send_close(skb, proto, state);
send_tcp_reset(skb, state);
}
}
kfree(host);
}
return ret;
透過已設置的 proto
呼叫其結構內的 parse_packet
提取域名。透過 in_word_set
函式檢查是否在 hosts 檔案中,若有的話則將 ret
設定成 NF_DROP
並阻擋該封包。
如果丟棄的是 TCP 封包,則逐一呼叫 send_server_ack
、send_close
和 send_tcp_reset
函式來發送 TCP 控制包,以通知對方終止連接。
send_server_ack(skb, state)
: 發送一個 ACK 包,確認接收到資料send_close(skb, proto, state)
: 發送一個 FIN/PSH 包,表示將關閉連接,並附加一個自定義的中止消息send_tcp_reset(skb, state)
: 發送一個 RST 包,強制關閉連接# generate_hash.sh
#/bin/sh
if ! [ -x "$(command -v gperf)" ];
then
echo "gperf could not be found" >&2
echo "Please download it first" >&2
exit 1
fi
if [ "$#" -ne 1 ] || ! [ -d "$1" ];
then
echo "Must provide output directory" >&2
echo "Usage: $0 <output directory>" >&2
exit 1
fi
echo '%{' > hosts.gperf
echo '%}' >> hosts.gperf
echo '%%' >> hosts.gperf
cat hosts >> hosts.gperf
echo '%%' >> hosts.gperf
gperf -D -L ANSI-C hosts.gperf > "$1/host_table.h"
rm hosts.gperf
使用 GNU gperf 來為要阻擋的域名建立 hash table,gperf 是一個用於生成完美 hash function 和 hash table 的工具,適合用來快速搜尋和配對一組鍵值(如域名)。
# host_table.h
const char *in_word_set (register const char *str, register size_t len)
{
static const char * wordlist[] =
{
"onestat.com",
"Adsatt.ABCNews.starwave.com",
"adsend.de",
"admeta.com",
"nedstatbasic.net",
"adtoma.com",
"demandbase.com",
"mmismm.com",
"content.ad",
"aistat.net",
...
}
}
在終端機輸入 make kernel
先執行上述 generate_hash.sh
後會透過 gperf 生成一份 host_table.h
檔案,並可以在裡面看到 in_word_set
函式存放多個從 hosts 生成的域名列,如果提取出的域名在列表中,則傳回指向該域名的指標,只要從封包提取出來的域名與 hosts 內容匹配,便可以將封包直接拋棄。
# Makefile
kernel:
./generate_hash.sh .
$(MAKE) -C $(KDIR) M=$(PWD) modules
$(MAKE) load
在 Makefile
中,我一開始透過以上方法在終端機執行 $ make kernel
,直接做編譯 generate_hash.sh
、編譯核心模組和掛載模組的動作,不過執行時會發生以下兩種錯誤:
error: implicit declaration of function ‘in_word_set’ [-Werror=implicit-function-declaration]
127 | flag = in_word_set(host, strlen(host));
| ^~~~~~~~~~~
/home/steven/linux2024/final/Netfilter-Adblock/kadblock.c:127:14: warning: assignment to
‘const char *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
127 | flag = in_word_set(host, strlen(host));
| ^
cc1: some warnings being treated as errors
這段報錯意思是,編譯器找不到 in_word_set
函式的聲明或定義,通常是因為編譯器在編譯過程中沒有找到相應的標頭檔,導致無法辨識 in_word_set
函式。
CC [M] /home/steven/linux2024/final/Netfilter-Adblock/kadblock.o
In file included from /home/steven/linux2024/final/Netfilter-Adblock/kadblock.c:11:
hosts.gperf:88:1: warning: no previous prototype for ‘in_word_set’ [-Wmissing-prototypes]
而這個警告表示,編譯器在編譯 hosts.gperf
生成的程式碼時,沒有找到 in_word_set
函式的先前聲明,通常是因為在 hosts.gperf
生成的程式碼中,沒有提供 in_word_set
函式的函式原型。
所以我將 Makefile
的編譯內容改為:
kernel:
- ./generate_hash.sh .
$(MAKE) -C $(KDIR) M=$(PWD) modules
$(MAKE) load
日後在編譯模組前,先單獨編譯 generate_hash.sh
,並在生成的 host_table.h
中新增以下函式原型:
# host_table.h
+ const char *in_word_set(const char *str, size_t len);
const char *in_word_set (register const char *str, register size_t len)
{
static const char * wordlist[] =
{
"onestat.com",
"Adsatt.ABCNews.starwave.com",
"adsend.de",
"admeta.com",
...
}
}
如此便可成功編譯模組。
查看網路設備的狀態,並確認本機 IP 和網卡名稱
$ ifconfig
enp7s0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
ether 08:97:98:e0:51:7a txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 130405 bytes 12779024 (12.7 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 130405 bytes 12779024 (12.7 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
wlp0s20f3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.100.170 netmask 255.255.255.0 broadcast 192.168.100.255
inet6 fe80::39eb:78ba:58ef:c490 prefixlen 64 scopeid 0x20<link>
ether 84:1b:77:00:f8:e8 txqueuelen 1000 (Ethernet)
RX packets 2704249 bytes 2343983910 (2.3 GB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 989360 bytes 305554448 (305.5 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
透過 tcpdump
觀察封包的來源 IP 和埠、目標 IP 和埠,以及封包的長度。這段指令用於捕捉從指定網卡 lo
進入的,來源或目的地為 127.0.0.1 的封包,並在捕捉到 10 個封包後停止。
$ sudo tcpdump -ni lo -c 10 -t host 127.0.0.1
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
00:46:35.144993 IP 127.0.0.1.37354 > 127.0.0.53.53: 17123+ A? google.com. (28)
00:46:35.145080 IP 127.0.0.1.32121 > 127.0.0.53.53: 65502+ Type65? google.com. (28)
00:46:35.145430 IP 127.0.0.53.53 > 127.0.0.1.32121: 65502 1/0/0 Type65 (53)
00:46:35.153299 IP 127.0.0.53.53 > 127.0.0.1.37354: 17123 1/0/0 A 172.217.163.46 (44)
00:46:40.240913 IP 127.0.0.1.7845 > 127.0.0.53.53: 55449+ A? mail.google.com. (33)
00:46:40.240945 IP 127.0.0.1.55228 > 127.0.0.53.53: 2123+ Type65? mail.google.com. (33)
00:46:40.251998 IP 127.0.0.53.53 > 127.0.0.1.7845: 55449 1/0/0 A 172.217.160.69 (49)
00:46:40.252763 IP 127.0.0.53.53 > 127.0.0.1.55228: 2123 0/1/0 (83)
00:46:49.263768 IP 127.0.0.1.32516 > 127.0.0.53.53: 26712+ A? ogs.google.com. (32)
00:46:49.263892 IP 127.0.0.1.23883 > 127.0.0.53.53: 29786+ Type65? ogs.google.com. (32)
10 packets captured
26 packets received by filter
0 packets dropped by kernel
tcpdump
是一個擷取網路封包的工具,用於捕捉和分析網路流量。
從這段指令可以看到 UDP 協定的封包
$ sudo tcpdump -ni lo -c 10 -t udp and host 127.0.0.1
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
IP 127.0.0.1.37578 > 127.0.0.53.53: 12977+ [1au] A? tpc.googlesyndication.com. (54)
IP 127.0.0.1.34392 > 127.0.0.53.53: 23572+ [1au] A? www.googleadservices.com. (53)
IP 127.0.0.1.52780 > 127.0.0.53.53: 25730+ [1au] A? www.googleadservices.com. (53)
IP 127.0.0.1.53841 > 127.0.0.53.53: 39705+ [1au] A? pagead2.googlesyndication.com. (58)
IP 127.0.0.1.48540 > 127.0.0.53.53: 51286+ [1au] A? www.googleadservices.com. (53)
IP 127.0.0.1.40488 > 127.0.0.53.53: 64589+ [1au] A? www.googleadservices.com. (53)
IP 127.0.0.1.49773 > 127.0.0.53.53: 5303+ [1au] A? www.googleadservices.com. (53)
IP 127.0.0.1.41083 > 127.0.0.53.53: 63712+ [1au] A? www.googleadservices.com. (53)
IP 127.0.0.1.42716 > 127.0.0.53.53: 22789+ [1au] A? www.googleadservices.com. (53)
IP 127.0.0.1.1363 > 127.0.0.53.53: 22673+ A? www.googleadservices.com. (42)
10 packets captured
22 packets received by filter
0 packets dropped by kernel
先在原本的阻擋列表 hosts 新增上述的幾個域名
google.com // https
mail.google.com // https
www.googleadservices.com // udp
pagead2.googlesyndication.com // udp
透過 dmesg
查看核心環境中有關 HTTP GET、TLS、UDP 封包以及域名的相關訊息,可看到編譯並掛載核心模組後,UDP 封包顯示已被丟棄,不過屬於 https 的封包 google.com
卻沒顯示被丟棄。
$ sudo dmesg
[32735.615743] TLS handshake len: 4284
[32735.615805] TLS handshake len: 4284
[32735.618173] TLS handshake len: 4284
[10211.770548] Host: connectivity-check.ubuntu.com
[10212.352794] HTTP GET request detected.
[10447.714883] Host: detectportal.firefox.com
[10447.778043] HTTP GET request detected.
[10448.461515] Host: ipv4only.arpa
[10448.461738] HTTP GET request detected.
[13412.789060] Host: x.trvdp.com
[13412.789125] Get UDP
[13412.789126] Host: x.trvdp.com
[13418.142087] Get UDP
[13418.142091] Host: google.com
[13418.142162] Get UDP
[13418.142165] Host: google.com
[13419.865934] Get UDP
[13419.865938] Host: www.googleadservices.com
[13419.865939] Dropping Packet
[13419.865968] Get UDP
[13419.865969] Host: www.googleadservices.com
[13419.865970] Dropping Packet
[13419.927362] Get UDP
[13419.927366] Host: www.googleadservices.com
[13419.927367] Dropping Packet
[13419.927401] Get UDP
[13419.927402] Host: www.googleadservices.com
[13419.927403] Dropping Packet
[13419.963672] Get UDP
[13419.963675] Host: www.googleadservices.com
[13419.963677] Dropping Packet
[13419.963721] Get UDP
[13419.963722] Host: www.googleadservices.com
[13419.963722] Dropping Packet
以下圖片是 opgg 網站,這個網站內有時包含 https 廣告有時則是 udp 廣告,經測試後可看見比對結果,原本分佈在五處的廣告在掛載模組後看不見了。左下的影片是該網站原本就插入的,不算廣告。
目前無法處理 https 封包,遇到時即便該域名儲存在 hosts 列表,但無法阻擋它。
因為通過網路傳輸 https 封包時會在 TCP 上添加一個加密層,通常是 TLS 或 SSL,所以當我們在 kernel space 收到時即使能提取到 TCP 資料部份,也無法讀取或修改其中的內容,若要查看解密後的內容,則需要訪問解密金鑰並進行解密,這通常是在應用層(例如瀏覽器或伺服器)資料傳輸後完成的。
若要阻擋 https 封包,可嘗試兩種方法: