各位好久不見~下半年又是忙論文又是忙項目的,實在是沒什麼時間更新筆記了。趁着今天有點空閒,咱來寫寫最近抽空解決的一個小網絡問題叭(゜ー゜)。
0. 問題背景
咱實驗室有一台連接着打印機的計算機,我們在這台機器上掛了一個專門註冊的 QQ 賬號,需要打印文件時把文件發送到這個 QQ 賬號上,在打印機計算機上下載下來就行了。
但是吧,像是比較機密文件的話,如果這樣過一道別人的服務器感覺不太好,正好咱當時也找到了局域網文件傳輸工具 LocalSend,遂試了試。
結果我發現,打印機計算機上的 LocalSend 客户端無法發現我筆記本上的 LocalSend 端,反之亦然。我還得到打印機計算機上手動輸入筆記本電腦被分配的 IP 地址才能傳輸文件,略顯麻煩。
- 更不提校園網這有線網和無線網給設備分配的全是動態 IP,可能過一段時間就會變。
分析了一下發現:
-
沒有辦法互相發現是因為,我的筆記本接入的是校園網無線網絡,而打印機計算機接入的是有線網絡,兩個主機在不同的子網段內,這阻隔了 LocalSend 發出的組播包 (可以參考 LocalSend 協議)。
-
可以手動輸入 IP 地址來指定客户端傳輸文件,是因為 LocalSend 實際的傳輸等請求是單播的,而校園網在三層設備上有配置路由轉發,所以單播包是可以互通的。
於是咱要解決的問題就是,如何在這種多播 (組播) 隔離但是單播互通的不同局域子網間實現 LocalSend 的發現功能。
1. 問題描述
Figure 1: 問題示意圖。 可以看到 VLAN 0 中的 LocalSend 客户端無法成功發現 VLAN 2 中的 LocalSend 客户端,反之亦然。
LocalSend 客户端採用 UDP 組播來把自己的存在通告給局域網中其他客户端。然而,像校園網這種大型局域網,通常為了管理和減小廣播域規模等目的,會將網絡劃分為多個 VLAN(虛擬局域網),對應多個子網,即使是現實中距離很近的兩個設備,也有可能在不同的 VLAN 中。
- 比如我連接到校園網 WiFi 的筆記本電腦和連接有線校園網的實驗室打印機電腦,雖然在同一間屋子,但就是處於不同網段的網絡中。
不同子網之間的數據轉發依賴於第三層路由設備來實現,不幸的是,LocalSend 向 224.0.0.x 組播地址及應用端口發送的 UDP 報文段是不會被三層設備轉發的,而且其 TTL 值為 1,Wireshark 抓包如下:
Figure 2: Wireshark 抓包顯示 LocalSend 發送的組播 UDP 報文段的 TTL 值為 1。
因此就有了明明兩台設備近在咫尺,但是卻沒法互相發現對方 LocalSend 客户端的尷尬局面 ㄟ( ▔, ▔ )ㄏ。
2. 解決問題
2.0. 思路
儘管多播被隔離了,但是辦公區校園網在三層配置上是會轉發單播包的,我可以通過單播和不同的 VLAN 中的主機進行通信。
一個 LocalSend 客户端在嘗試發現局域網內其他客户端時,會發送組播 UDP 包來聲明自己的存在,其他客户端收到組播包後會通過單播的 HTTP 請求來在這個客户端上進行註冊。因為單播可以跨 VLAN,所以這個註冊操作是可以實現的,我可以替 LocalSend 客户端向局域網內的其他 LocalSend 客户端發送註冊請求,從而實現跨 VLAN 的發現和註冊。
從官方的協議文檔可以看到 LocalSend 的通告包和註冊請求的負載中都只有端口信息,沒有源 IP 信息,客户端在處理到來的請求時實際上是從網絡層分組頭部獲取到源 IP 地址的,因此這個請求必須從 LocalSend 客户端所處的主機上發出。為了實現這點,我可以在每台有 LocalSend 的主機上都額外運行一個工具進程來代發註冊請求。
關鍵的問題來了,這些工具進程怎麼知道局域網內其他 LocalSend 客户端的存在呢?其實我可以藉助單播傳輸來實現這些工具進程之間的通信,從而讓它們互相交換各自了解的 LocalSend 客户端信息。
為了解決動態 IP 的問題,我可以把其中一個或多個工具進程作為交換節點部署在擁有靜態 IP 的服務器上(內網和外網的均可),然後讓其他工具進程連接到這些交換節點,當交換過程收斂時,這些工具進程就能互相瞭解對方所處主機上的 LocalSend 客户端信息了(也讓 LocalSend 客户端互相知曉了對方的存在)。
正好最近學了 Go 語言,照着上面這個思路實現下來,LocalSend Switch 這個工具就誕生辣!٩(>௰<)و
- 簡單來説 LocalSend Switch 充當的角色就有點類似於 BT 下載中的 Tracker 服務器了,但同時也會幫忙發送單播的註冊請求,用於輔助組播隔離、單播允許的局域網子網之間的 LocalSend 客户端互相發現。
2.1. 工作原理
Figure 3: LocalSend Switch 的工作原理示意圖。實線表示的是單播分組的傳播路徑,虛線表示的是 TCP 邏輯連接;虛線上的箭頭對應數據在邏輯上的傳播方向。LocalSend 客户端和 Switch 進程的旁邊標記了連接端口,只有 VLAN 1 中的 Switch 進程監聽了服務端口
7761,其餘兩個 Switch 進程的均為 OS 分配的臨時端口;LocalSend 客户端默認服務端口是53317。
Fig.3 為 LocalSend Switch 的工作原理示意圖,展示了單次的客户端信息傳播以及註冊請求代發的過程。圖中,首先 10.84.0.0/15 網段中 10.84.123.223 這台主機上的 LocalSend 客户端發送了組播包,通告自己的存在,被同一台機器上的 LocalSend Switch 捕獲到,Switch 進程隨後將該通告信息通過單播發送 (圖中標記為 CLIENT ANNOUNCE,傳播路徑為藍色) 給它所連接的所有 Switch 節點 (圖中只有 192.168.232.47:7761 這一個)。
- 發送的數據中封裝了 LocalSend 客户端的 IP 和端口,無論被轉發多少次,這部分數據都不會變,指向最初發出這條通告信息的 LocalSend 客户端。
47 主機上 Switch 節點接收到通告的客户端信息後,會將該信息轉發至它所連接的其他 Switch 節點(圖中只有 10.94.23.114:52341),圖中標記為 FORWARD ANNOUNCE,傳播路徑為紫色。因為這台主機上沒有 LocalSend 客户端,所以不會有註冊請求的代發操作。
114 主機上的 Switch 節點接收到通告信息後,會將該信息發送給它所連接的其他所有 Switch 節點(圖中沒有其他節點了);因為這台主機上有 LocalSend 客户端,所以 Switch 節點隨後會向通告信息中攜帶的 LocalSend 客户端地址 (圖中為 10.84.123.223:53317 ) 發送 HTTP(S) 註冊請求(圖中標記為 REGISTER CLIENT,傳播路徑為棕色),告知對方本地客户端的 IP 和地址 (圖中為 10.94.23.114:53317),完成註冊請求的代發操作。注意這個註冊請求是直接由 Switch 發送給 LocalSend 客户端的。
實際上每個 Switch 節點都有這樣的轉發功能,甚至可以在邏輯上串聯或者組成樹形、星型、網狀、混合等拓撲結構。
3. 更進一步
解決了 LocalSend 互相發現的問題後,我又考慮並解決了以下幾個問題:
- 傳輸安全性問題。
- 交換信息的環路問題。
- 自啓動問題。
注:這節的 "Switch" 均指 LocalSend Switch 工具。
3.0. 傳輸安全性問題
Switch 節點間的數據傳輸在 TCP 連接上進行,默認情況下是明文的,其中主要是 LocalSend 客户端的主機的地址、設備型號等信息。
儘管在校園網這種較為可信的局域網中不用擔心遭到中間人攻擊,而且傳輸的數據本身也沒有那麼敏感,但如果中間有的 Switch 節點在外網上,就還是有一定風險的,如中間人可以偽造 LocalSend 客户端信息,誘導其他 Switch 節點向惡意構造的內網客户端地址發送註冊請求,從而造成拒絕服務攻擊 (DoS)。
為此咱考慮過 TLS 加密傳輸,但是考慮到配置和證書管理的複雜性,最終選擇了預共享密鑰 (PSK) 方式來對傳輸的數據進行簡單的對稱加密,即持有相同密鑰的 Switch 節點才能互相通信,傳輸的是密文。
-
另外為了防止接收到惡意構造的 LocalSend 客户端信息,限制每個 Switch 節點僅可向私有 IP 地址發送 HTTP(S) 註冊請求,避免主機被利用向公網地址發送請求。
-
這個工具的使用場景實際上挺簡單的,因此這個程度的安全性應該已經足夠了 (* ̄0 ̄)ノ。
3.1. 交換信息的環路問題
本科學計算機網絡時,在網絡層這一塊就聽老師講過環路問題。而本工具如果不加以限制也會導致同一條 LocalSend 客户端信息被瘋狂重複轉發,浪費帶寬和計算資源。
為了解決這點咱採用了兩個措施:
-
經典的 TTL 機制。給每條消息都設置一個 TTL 字段,每經過一個 Switch 節點 TTL 則減
1,當其減到0時該消息就不再被轉發。 -
唯一 ID 緩存機制。每條信息都有一個唯一 ID,由 Switch 節點的臨時隨機標識以及消息的遞增編號組成。每個 Switch 節點都會避免重複把相同 ID 的客户端信息重複加入轉發緩衝區,也就不會重複轉發已經轉發過的 LocalSend 客户端信息。
- 這個唯一 ID 緩存是有過期時間的,咱默認設置為了
5分鐘,以防止內存無限增長。 - 臨時隨機標識是在 Switch 啓動時生成的,重啓後會改變,因此重啓後的 Switch 節點會被認為是一個新的節點。
- 唯一 ID 機制還能一定程度上防止重放攻擊。
- 這個唯一 ID 緩存是有過期時間的,咱默認設置為了
3.2. 自啓動問題
如果每次使用 LocalSend Switch 都需要手動啓動的話,多少還是有些麻煩,因此咱特地還寫了開機 (登錄後) 自啓的相關配置模塊,目前支持了 Windows 和 Linux (帶桌面)。
- Windows 下是參考了 LocalSend 的自啓動實現,往註冊表裏寫入了開機自啓項。
- Linux 下則是依照 FreeDesktop 的 Desktop Entry 規範,寫入了
~/.config/autostart/目錄下。 - MacOS...咳咳,咱還沒蘋果電腦呢,暫時沒法測試和實現 Or2...
4. 項目地址 & 總結
✨ 項目地址: https://github.com/SomeBottle/localsend-switch
一頓折騰下來,咱又花了好多時間寫了這樣一個使用場景其實挺小眾的工具...不過總的來説還是挺有趣的,一方面熟悉了 Go 語言程序的基本思想和編寫規範,另一方面也複習複習了一下計算機網絡和基本的網絡編程知識(像是手動去搭個 TCP 服務,實現簡單的連接維護等等)。
- 複習歸複習,個人學藝不精,難免有疏漏之處,請各位多指教!
動手去解決些實際問題,總歸是個不錯的學習途徑呢 (๑•̀ㅂ•́)و✧
轉眼也 2026 了,祝各位新年快樂,事業順利!咱們下一篇文章再會~ (づ ̄ ³ ̄)づ