Turnout 對你的 Mac 做了什麼
Turnout 會安裝一個以 root 身分執行的服務,所以這裡把它做的事和檢查方法交代清楚:這個服務是什麼、它改了什麼、留下什麼、連到哪裡,每一項都能在你自己的 Mac 上確認。
兩個部分:App 與背景服務
App 本身(Turnout.app)以你的身分執行,沒有任何特殊權限。它負責顯示通道、編輯設定檔,並請背景服務連線或中斷。它從不碰你的網路流量。
背景服務是一個 LaunchDaemon,cc.ctsite.turnout.daemon,以 root 身分執行。它的程式 turnout-daemon 就放在 App 裡的 /Applications/。Turnout 透過 macOS 的登入項目(SMAppService)註冊它,所以 macOS 會請你在「系統設定 › 一般 › 登入項目與延伸功能」中允許,你也可以在那裡把它關掉。
服務會為每條通道建立 utun 介面,並用內建的 GotaTun 在使用者空間執行 WireGuard;GotaTun 是 Mullvad 開發、以 MPL-2.0 授權的實作。只有通道的封包會經過它,其餘流量照常由 macOS 路由。通道存在於服務之中,所以關掉 App 時通道仍會連著。
它改了什麼,用的是哪些工具
位址、MTU、路由與 DNS 都用 wg-quick 在 macOS 上使用的程式修改。每個程式都直接帶參數執行,從不經過 shell。
/sbin/ifconfig- 通道介面的位址與 MTU,以及啟用介面。
/sbin/route- 導入通道的路由、讓伺服器位址留在實體網路上的路由,以及不走通道的路由。
/usr/sbin/networksetup- 各個網路服務上的 DNS 伺服器與搜尋網域。
所以用平常的指令就能看到它到底做了什麼:
netstat -rn
networksetup -listallnetworkservices
networksetup -getdnsservers Wi-Fi # 或清單中的其他服務
通道中斷時,服務會移除它加入的路由,並把被它取代的 DNS 設定還原。
你的設定檔
通道存放在 /Library/Application Support/cc.ctsite.turnout/tunnels,這個資料夾屬於 root,每條通道一個檔案,權限為 0600,私鑰也在其中。只有服務會寫入這裡;App 要儲存時是請服務代勞。
sudo ls -l "/Library/Application Support/cc.ctsite.turnout/tunnels"
Hook
PreUp、PostUp、PreDown 和 PostDown 以 root 身分透過 /bin/bash -c 執行,%i 換成通道的 utun 介面,%I 換成通道名稱。這等同於 sudo 的權限,所以:
- hook 只會來自磁碟上的設定檔,絕不會來自 App 傳給服務的任何內容。
- 儲存一個 hook 是新的或有變動的設定檔時,需要管理者核准。核准由服務自己透過 macOS 的 Authorization Services 檢查,不會只聽 App 的說法。
- 沒有動到 hook 的修改不需要重新核准。
核准之前先讀一遍設定檔裡的 hook,就像你會先讀過要用 sudo 執行的指令稿一樣。
服務回應誰
App 透過 Unix socket(/var/run/)和服務溝通。每個連上來的程式,服務都會檢查它的程式碼簽署,只回應由同一個開發團隊簽署、且帶有 Turnout 套件識別碼 cc.ctsite.turnout 的程式。其他程式一律拒絕。
當機之後
服務停止時,macOS 會移除通道介面,介面上的路由也會跟著消失。但經由實體閘道的路由和 DNS 設定不會,這樣設定的 DNS 甚至重新開機後都還在。所以服務在每次修改之前都會先寫進日誌,下次啟動時再把前一次留下的變更復原。完成之前它不會連線任何通道,Turnout 也會顯示還在等待復原的項目,並提供「立即復原」。
「Turnout」›「修復網路設定⋯」可以隨時手動做同樣的事。如果服務沒有在執行,它會要求輸入管理者密碼,自己依日誌復原。
網路連線
為了更新,App 本身會連到兩個地方:GitHub,用來查看有沒有新版本;以及 Google Analytics,用來計算安裝數。為了授權,它會在你買了授權之後連到 Polar,也會連到你的 iCloud 帳號;什麼時候、送出什麼,請見下方的授權。
- 只有
/Applications裡的那份會自動檢查,最多每小時一次,時機是你打開選單或視窗時。「Turnout」›「檢查更新項目⋯」則是你選它就檢查。 - 檢查時會向 GitHub 上這個專案的最新版本索取一個小檔案。請求不帶任何帳號,也不含任何關於你或你的通道的資訊。GitHub 看得到這個請求和你的 IP 位址。
- 每次檢查時,App 也會送出一個事件給 Google Analytics(
www.google-analytics.com,透過它的 Measurement Protocol):一個 Turnout 第一次使用時隨機產生、存在偏好設定裡的安裝 ID,絕不是從硬體推算出來的;Turnout 的版本與建置編號;macOS 版本;處理器架構;系統語言;以及 Google Analytics 計數需要的兩個欄位:取自 Turnout 啟動時間的工作階段編號,和一個固定的參與時間。不含任何關於通道、伺服器、金鑰或網路的資訊,也沒有帳號或使用者 ID。Google 看得到這個請求和你的 IP 位址,適用 Google 隱私權政策。 - 在「一般」(「Turnout」›「設定⋯」)關掉「自動檢查更新項目」,自動檢查和這份回報都會停止。之後選「檢查更新項目⋯」仍會檢查,但不會送出回報。
- 如果你安裝更新,安裝程式來自同一個版本,Turnout 會先檢查它的檢查碼與 Developer ID 簽署,才交給 macOS 的安裝程式。
你的通道連到設定檔裡的伺服器,hook 則做你寫在裡面的事。除此之外,App 和服務都不會連到任何地方:沒有自家的當機回報、沒有廣告,也沒有帳號。
授權
Turnout 在每部 Mac 上都有 7 天的完整試用,需要的話還能再延長一次 7 天;之後,連線通道需要授權。需要授權的只有連線:已連線的通道會維持連線,中斷連線、編輯、匯入、匯出、「修復網路設定⋯」和解除安裝也永遠都能用。Turnout 絕不會因為授權的關係中斷任何連線。
授權的處理全在 App 裡。背景服務完全不涉及授權,也從不連到 Polar 或 iCloud。
- 試用期間不做任何授權檢查。試用的日期存在這部 Mac 的鑰匙圈裡,項目名稱是
cc.ctsite.turnout.license;解除安裝 Turnout 時它會保留,所以重新安裝不會重新開始試用。 - 買了授權之後,App 會在這些時候連到 Polar 的授權服務
api.polar.sh:啟用這部 Mac 時(授權金鑰、你為 Mac 取的名稱、機型識別碼、macOS 與 Turnout 版本,以及由硬體識別碼算出的單向雜湊值);大約每週一次,在你打開選單或視窗時,以及你手動要求檢查時(授權金鑰、這部 Mac 的啟用 ID 和雜湊值);還有移除一部 Mac 時(授權金鑰和那部 Mac 的啟用 ID)。沒有帳號,也不含任何關於你通道的資訊。 - 連不上 Polar 時什麼都不會改變:已授權的 Mac 不論離線多久都照樣能連線,「授權」面板只會說明上次確認授權是什麼時候。
- 授權金鑰和使用它的 Mac 清單,會以記錄的形式存在你 iCloud 帳號中屬於 Turnout 的區域(CloudKit),所以登入同一個帳號的其他 Mac 找得到授權,按一下就能使用。App 啟動時,以及你打開選單或視窗時,會讀取這些記錄;只有在啟用、檢查或移除之後才會寫入。它們受 Apple 標準的 iCloud 保護,但不是端對端加密。沒有登入 iCloud 的 Mac 不會同步任何東西,要在那部 Mac 上手動輸入授權金鑰。
定位與 Wi-Fi 規則
Wi-Fi 規則(#@ OnWiFi)需要知道 Mac 目前所在網路的名稱。macOS 把這個名稱視為位置資訊,只有取得定位權限的 App 才能讀取。Turnout 只在你設定 Wi-Fi 規則時才會要求權限,啟動時絕不會問,而且只用來讀取網路名稱,從不用來得知你在哪裡。你列出的網路名稱只存在 Mac 上的設定檔裡。沒有權限時,Wi-Fi 規則不會有任何作用。
記錄檔
/Library/Logs/cc.ctsite.turnout.daemon.log - 背景服務做了什麼,以及它的錯誤。
/Library/Logs/cc.ctsite.turnout.update.log - 安裝更新時寫入。
「輔助說明」›「在Finder中顯示記錄檔」會在 Finder 裡顯示這兩個檔案。App 自己的訊息在系統記錄中:
/usr/bin/log show --predicate 'subsystem == "cc.ctsite.turnout"' --last 1h
在 zsh 中要打完整路徑,因為只打 log 會執行另一個內建指令。
自己檢查
launchctl print system/cc.ctsite.turnout.daemon # launchd 眼中的這個服務
ls -l /Applications/Turnout.app/Contents/MacOS/ # Turnout 與 turnout-daemon
cat /Applications/Turnout.app/Contents/Resources/uninstall.sh
defaults read cc.ctsite.turnout analyticsClientID # 隨機產生的安裝 ID
netstat -rn
networksetup -getdnsservers Wi-Fi
解除安裝程式是 App 裡的一個 shell 指令稿,執行前可以先讀過。
解除安裝
有三種方式,執行的都是同一個指令稿:
- 在 Turnout 視窗開著時,選擇「Turnout」›「解除安裝Turnout⋯」。在確認對話框中勾選「同時刪除已儲存的通道及其私鑰」,就會一併刪除。已授權的 Mac 還會先把這部 Mac 從你的授權中移除,並刪除它在你 iCloud 帳號中的記錄(確認時可以選擇不要這麼做)。
- 打開安裝程式,在列出安裝項目的地方選擇 Remove Turnout;若也想刪除已儲存的通道,再選 Delete saved tunnels(安裝程式目前只有英文)。
- 在「終端機」中執行(加上
--purge會一併刪除已儲存的通道):sudo sh /Applications/Turnout.app/Contents/Resources/uninstall.sh
每一種方式都會中斷所有通道(同時恢復路由與 DNS)、把背景服務從登入項目移除、復原當機的服務留下的變更,並刪除 App、它的記錄檔、安裝程式的收據,以及每位使用者的 Turnout 偏好設定、視窗狀態與快取。已儲存的通道及其私鑰位於 /Library/Application Support/cc.ctsite.turnout,除非你選擇刪除,否則會保留。
鑰匙圈裡的授權項目會保留,所以重新安裝不會重新開始試用;授權金鑰也會留在你的 iCloud 帳號中,給你的其他 Mac 使用。安裝程式和終端機指令稿以 root 身分執行,碰不到你的鑰匙圈和 iCloud 帳號,所以無法把這部 Mac 從授權中移除:請事先在「授權」面板移除,或事後從另一部 Mac 或 Polar 的客戶入口移除。