執行人: aa860630, steven523
專題解說影片(aa860630)
專題解說影片(steven523)
ollieni
為何只擷取udp 通訊協定與tcp通訊協定的封包就可以屏蔽廣告?
steven523
對於屏蔽廣告,主要涉及到的協定的確是 UDP 和 TCP 通訊協定的封包,不過這並不是絕對的,因為還有其他層次和協議可能會參與到廣告的傳輸和顯示中,但通常在常規廣告過濾中不太常見:
QUIC (Quick UDP Internet Connections) 協定,一種基於 UDP 的傳輸層協定,旨在提高 HTTP/3 的效能,它透過 UDP 來取代 TCP 以在 TLS 上串流各種協定。
攔截和分析 UDP port 53 上的 DNS Query,過濾包含廣告域名的查詢。WebSocket 協定,一種在單一 TCP 連接上進行全雙工通訊的協定,有時也用於傳輸廣告內容。
攔截和分析 TCP 上的 WebSocket 流量,過濾包含廣告內容的請求。
gawei1206
對於有些網址即使我把他加入到了規則中,但卻無法阻止與他的連線,像是文中提到 Youtube 那段,原因就只有沒有把所有 IP 地址加入規則這一種可能嗎,還是有其他可能的原因?
aa860630
內容傳遞網路 (CDN) 是一組地理上分散的伺服器,用於在靠近終端使用者的位置快取內容,以達到更好的閱讀質量即使將某些網址的 IP 地址加入 iptables 的規則中,仍無法阻止與該網址的連線,原因可能是因為你使用代理伺服器或 VPN,上述方法可以繞過本地的 iptables 規則,通過其他 IP 地址訪問目標網站,導致封鎖無效
jujuegg
NF_DROP
:將封包丟棄NF_STOLEN
:由 hook function 處理該封包,不再繼續傳送
Netfilter 可以藉由在 hook function 中回傳不同的操作來告訴防火牆我們要執行的動作,因為 NF_DROP
和 NF_STOLEN
都不會將封包繼續往下傳,請問這兩個操作還有特別不一樣的地方嗎?
steven523
有的,NF_DROP
是指不再處理也不會繼續傳送封包,並將sk_buff
所佔的資源丟棄,大多情況都是透過此操作來過濾不安全或不需要的封包。而
NF_STOLEN
的部分雖然也不會將封包繼續傳送給後續的 Netfilter hook,但是會將此封包給 hook function 進一步接管,這同時也意味著需要自己管理封包佔用的資源,因為核心不會釋放掉sk_buff
資源,使用完sk_buff
後需要再呼叫kfree_skb()
將其釋放。
使用到NF_STOLEN
的情況比較少,像是對封包進行分析或修改,然後再重新傳入 network stack,或是將封包交給其他核心子系統或使用者空間處理。
在 Linux v6.8 重現透過 Netfilter 自動過濾廣告實驗,並修正對應的程式碼。
可重用去年報告的素材,但要更新到 Linux v6.8+
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 點對應於封包處理的不同階段,如下圖所示:
以下是五個主要的 hook 點,以及它們對應 chain 的標識:
NF_IP_PRE_ROUTING
( PREROUTING
): 在做任何路由決策之前觸發,當作檢查和修改封包的初始點,適用於路由前的目標 NAT 和封包處理NF_IP_LOCAL_IN
( INPUT
): 針對發往本地系統的封包, 允許檢查和修改那些要交給本機的入站封包NF_IP_FORWARD
( FORWARD
): 用於在系統中從一個網路介面轉發到另一個網路介面的封包,對應用轉發規則和過濾不屬於本地系統的封包非常重要NF_IP_LOCAL_OUT
( OUTPUT
): 在所有路由決策做出之後但在封包實際傳送之前觸發,通常用於源 NAT 和在封包傳送前的附加封包處理NF_IP_POST_ROUTING
( POSTROUTING
): 針對本地系統生成的封包,在它們實際發出之前,提供修改出站封包、設置源 NAT 或應用其他出站過濾規則的機會假設伺服器知道如何路由封包,防火牆允許封包傳輸,以下就是不同場景下封包的傳輸流程:
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 funciton網路封包過濾是一種用於控制網路流量的方法,它根據指定的規則來允許或拒絕封包進入或離開網路。這些規則通常基於封包的 source IP/Port、destination IP/Port 和 協定類型。
常見的廣告一般分成 HTTPS 和 UDP 兩種封包,以下是透過掛載核心模組來過濾廣告的策略:
除了掛載核心模組之外,我們還能透過實作以下手動更改 iptables 的方式來過濾廣告:
實驗前清除所有規則
使用命令查找目標網頁的資訊
將 youtube.com
加入到 iptables 的 reject 名單
使用以下指令查看 iptables 內容
REJECT 中指定了目標是 cg-in-f0.1e100.net/15
,這可能不是 YouTube 所有的 IP 地址,因此瀏覽器還可以順利打開
拒絕所有可能是 YouTube 的 IP 地址
測試連接 : 連接至 youtube.com
失敗
不過使用核心模組比起上述的手動更改 iptables,不僅更靈活,同時也可以提高瀏覽器性能,主要差別如下:
在 Linux v6.8 重現透過 Netfilter 自動過濾廣告實驗,並修正對應的程式碼。
這個部份我們各自參照了兩個不同的專案來實作
使用 Linux 核心模組,即可在核心裡頭註冊不同的 hook 來處理封包,為了在使用者瀏覽網站時屏蔽廣告,因此 hook 必須作用在進入本機之前,也就是 Local In 或 PreRouting。
而 hook 函式需接受三個參數
priv
: 為一個私有指針,通常用於傳遞 hook 函式自身需要的私有資料skb
: 是一個指向 Linux 中網路封包(socket buffer)的指針。在 netfilter 中,封包通過系統中的不同階段(如 INPUT、OUTPUT 等)時,可以被 hook 函式捕獲和處理。skb 對象包含了網路封包的所有信息,包括協定頭、有效資料等。state
: 是一個指向 nf_hook_state
構體的指針,這個結構體提供了有關 hook 點和系統狀態的詳細信息my_hook
函式主要由 should_run_get_sfilter()
與 should_run_dns_sfilter()
組成,兩個 funtion 的功用在於檢查並提取傳入的封包是否為 DNS 封包或 HTTP封包,並將其資料存儲到 buf
結構體中
run_get_sfilters(&buf)
與 run_dns_sfilters(&buf)
則將 buf
與預先設置好的 DNS 過濾規則或 HTTP 過濾規則做比對,若比對成功說明其封包包含與廣告相關的內容,因此在函式中將 ret
設置為 NF_DROP
,即丟棄該封包
從 UDP 通訊協定中獲取 DNS 資訊
檢查傳入的網路封包 (skb
) 是否為 DNS 封包需經過一連串條件審核,步驟如下:
skb
是否不為空skb
是否包含 ip_header
ip_header
裡的協定是否為 UDP 通訊協定udp_header
是否不為空埠號 53 是用於 DNS(域名系統)服務的預設埠,且許多廣告和跟蹤服務使用 DNS 請求來進行域名解析,過濾埠號 53 上的數據包可以有效地阻止這些廣告和跟蹤請求
在確認某網路封包就是目標封包後,提取其 DNS 資訊,及下圖中的 Data。
udp_header
指標位置 + udphdr
長度,即為 Data 位置
dns_data
,即 DNS Message Packet,用 16 進位法表示如下:
de b2 01 20 00 01 00 00 00 00 00 01 09 67 6f 6f 67 6c 65 61 64 73 01 67 0b 64 6f 75 62 6c 65 63 6c 69 63 6b 03 6e 65 74 0b 64 6c 69 6e 6b 72 6f 75 74 65 72 00 00 1c 00 01 00 00 29 04 b0 00 00
前 12 個 bytes 為 DNS Header,分別代表:
Question Name 欄位存放所欲查詢的 FQDN 名稱,每一名稱長度不定,因此,此欄位的長度也不定。網域名稱的存放是以 ASCII 字元表示,最長限制在 64 個字元之內,名稱中的『.』(dot),並不表示出來,而以字元計數取代。譬如:
09 67 6f 6f 67 6c 65 61 64 73 01 67 0b 64 6f 75 62 6c 65 63 6c 69 63 6b 03 6e 65 74 0b 64 6c 69 6e 6b 72 6f 75 74 65 72
Fully Qualified Domain Name: googleads.g.doubleclick.net.dlinkrouter
參考資料: DNS 訊息格式
封包在執行完 should_run_dns_sfilter
之後,若包含 DNS 資訊的話會將其存入 buf->data
裡,用來與 dns_sfilters[]
裡的字串做比對,若比對成功則丟棄封包,dns_sfilters[]
包含許多過濾規則,內容如下:
使用 sudo dmesg
查看核心訊息
從 TCP 通訊協定中獲取 HTTP 資訊
檢查傳入的網路封包(skb
)是否為 DNS 封包需經過一連串條件審核,步驟如下:
skb
是否不為空skb
是否包含 ip_header
ip_header
裡的協定是否為 TCP 通訊協定tcp_header
是否不為空我在解析 TCP 通訊協定的資料時,獲取 payload 的方式與 UDP 一樣,但印出來的都是以 16 03 03
或 17 03 03
為開頭且無法被閱讀的資料。我嘗試理解格式訊息發現資料使用 TLS 進行加密,因此無法被解析
16 03 03
16
TLS Handshake protocol03 03
SSL 3.3 (TLS 1.2)00 7a
Length of handshake message (122 bytes)17 03 03
17
TLS Application Data03 03
version 1.200 45
length of encrypted data (69 bytes)得到 tcp_header
後,嘗試讀取其 Source port 及 Destination port
埠號為 443 說明這個 TCP 連接是用於 HTTPS 通訊,是 HTTP 協定的安全版本,通過 TLS/SSL 進行加密,確保資料在傳輸過程中受到保護,這也解釋上述 payload 無法被解析的原因
本 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)註冊 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
這段函式是從 sk_buff
結構中提取 TCP 資料,會先透過宣告的 *ip
和tcp
分別指向 ip_hdr
和 tcp_hdr
提取的 IP header 和 TCP header,接著檢查他們是否有被正確的提取。
計算封包資料的偏移量和長度過後利用 skb_linearize
將 paged skb 轉換成線性的,確保資料在記憶體區塊中是連續分佈,接著把資料指標指向實際資料位置後,回傳資料的長度以便 hook function 操作。
skb_linearize
主要作用是將非線性資料轉換為線性資料,以便於核心的處理和傳遞。執行成功時返回 0,失敗實則返回非 0 值。
這段函式從 sk_buff
結構中提取 UDP 資料,基本上作法跟 extract_tcp_data
類似,但是在這段函式中將 tcp->doff * 4
修改為 sizeof(struct udphdr)
,這是因為 UDP header 是固定長度的,而 TCP header 可能包含額外的欄位。
在 filter_hook
函式中包含三個參數:
*priv
: 指向註冊 hook function 時傳遞私有資料的指標*skb
: 指向作為 sk_buff
結構的網路封包指標*state
: 指向 nf_hook_state
結構的指標,該結構包含有關 hook point 的資訊,例如網路協定、網路介面和路由資訊透過 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 之通訊工作階段的過程。過程中通訊雙方交換訊息以相互確認,彼此驗證,確立它們將使用的加密演算法,並生成一致的工作階段金鑰。
透過 ntohs 函式將 network byte 順序轉換為 host byte 順序。檢查提取到的 UDP 封包其 destination port 是否等於 53。port 53 是 DNS 的標準 port,因此這個檢查是為了確定該封包是否是 DNS 請求。
創建 struct Protocol
結構描述封包的網絡協定內容
name
: 協定的名稱default_port
: 協定的默認 port,例如 HTTP 通常使用 port 80 、DNS 使用 port 53 、TLS 使用 port 443parse_packet
: 用於解析封包的 function point,從中提取有用的資訊,例如從 HTTP 請求中提取域名abort_message
: 當協定中止時使用的消息abort_message_len
: 中止消息的長度透過已設置的 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 包,強制關閉連接使用 GNU gperf 來為要阻擋的域名建立 hash table,gperf 是一個用於生成完美 hash function 和 hash table 的工具,適合用來快速搜尋和配對一組鍵值(如域名)。
在終端機輸入 make kernel
先執行上述 generate_hash.sh
後會透過 gperf 生成一份 host_table.h
檔案,並可以在裡面看到 in_word_set
函式存放多個從 hosts 生成的域名列,如果提取出的域名在列表中,則傳回指向該域名的指標,只要從封包提取出來的域名與 hosts 內容匹配,便可以將封包直接拋棄。
以下圖片是 opgg 網站,這個網站內有時包含 https 廣告有時則是 udp 廣告,經測試後可看見比對結果,原本分佈在五處的廣告在掛載模組後看不見了。左下的影片是該網站原本就插入的,不算廣告。
在課程期末專題找出同樣從事 netfilter 相關專案開發的學員,在其開發紀錄提出你的疑惑和建議。
在此彙整你的認知和對比你的產出。