跳轉到

OONI 測量資料結構導覽

ASN 觀測資料擷取與分析 說明了如何擷取 OONI 公開資料,取得資料之後會遇到下一個問題:一筆測量有二十多個頂層欄位,test_keys 裡還有二十幾個,該看哪一個。

以下用兩筆台灣的真實測量對照,說明一筆網路連線測試(web_connectivity)的組成,以及每個欄位對應到上游 ooni/spec 的哪份規格。看懂之後,你可以自己判斷一筆測量說了什麼,也能決定分析程式該取哪些欄位。

範例資料來源

兩筆都是 2026-08-04 由台灣的 OONI Probe 產生的公開資料,可在 OONI Explorer 查到原始內容。

正常通過 判定異常
測量對象 http://presidentlee.tw/ https://ntc.party/
measurement_uid 20260804085935.603513_TW_webconnectivity_4a5fd27dec0b32f6 20260804084548.416537_TW_webconnectivity_f4e7b0ab3d0251bf

一筆測量的三層結構

不用一開始就記住所有欄位。一筆測量可以拆成三層來讀:

  1. 外殼:誰在哪裡、用什麼軟體、什麼時候測的。所有測項共用同一套欄位,規格是 df-000-base
  2. 判定:測量得出的結論。欄位隨測項而異,全部收在 test_keys 底下,web_connectivity 的定義在 ts-017
  3. 證據:支撐結論的原始紀錄,包含每一次 DNS 查詢、TCP 連線、TLS 握手與 HTTP 請求,以及對照組的同類紀錄。同樣放在 test_keys 底下,各自對應一份 df- 開頭的規格。

判定層告訴你結論,證據層讓你驗證結論。兩者分開讀,各欄位的角色就清楚了。

自己取一筆來看

邊讀邊對照手上的樣本會快很多。不必先架好環境,用公開 API 就能取得單筆資料。先列出符合條件的測量:

列出台灣最近的網路連線測試
curl -s "https://api.ooni.io/api/v1/measurements?probe_cc=TW&test_name=web_connectivity&limit=5" \
  | python3 -m json.tool | head -40

回應中的 measurement_uid 可以換取完整內容:

取得單筆完整測量資料
curl -s "https://api.ooni.io/api/v1/raw_measurement?measurement_uid=<measurement_uid>" \
  | python3 -m json.tool | head -60

原始回應是未斷行的長字串,直接輸出到終端機不易閱讀,上面兩段都接了 python3 -m json.tool 排版。加上 anomaly=true 可以只列出被判定異常的測量,適合用來找對照範例。把 probe_cc 換成所在地區、probe_asn 換成自己的 ASN,就能看到本地網路上的觀測紀錄。

需要批次處理大量資料時,改走 AWS S3 公開資料集會更有效率,作法見 ASN 觀測資料擷取與分析

外殼:誰在哪裡測的

外殼層的欄位在所有測項中都一樣,實務上最常用到的如下:

欄位 正常通過那筆 判定異常那筆 說明
probe_cc TW TW 測量發生的國家代碼
probe_asn AS3462 AS3462 執行測量的網路所屬 ASN
probe_network_name Chunghwa Telecom Co., Ltd. Chunghwa Telecom Co., Ltd. 該 ASN 的組織名稱
resolver_asn AS3462 AS13335 測量時實際使用的 DNS 解析器所屬 ASN
resolver_network_name Chunghwa Telecom Co., Ltd. Cloudflare Inc 解析器的組織名稱
input http://presidentlee.tw/ https://ntc.party/ 測量對象的網址
test_name web_connectivity web_connectivity 測項名稱
software_name ooniprobe-cli ooniprobe-desktop-unattended 產生資料的 Probe 種類
software_version 3.29.1 3.26.0 Probe 版本
measurement_start_time 2026-08-04 08:59:30 2026-08-04 08:45:45 測量開始時間(UTC)
report_id 20260804T062033Z_webconnectivity_TW_3462_n4_uEH5rGoD07cN2oYQ 20260804T084446Z_webconnectivity_TW_3462_n4_dFfWCDrwouM0TsT2 同一次執行產生的多筆測量共用此值

probe_asnresolver_asn 可能不同,上表就是實例。兩筆都在中華電信的網路上執行,但其中一筆的使用者把 DNS 指向 Cloudflare。做 ASN 分析時兩者要分開看,混用會讓「哪家電信商的網路上看到什麼」失準。

probe_ip 永遠是 127.0.0.1

OONI 刻意不收集測量者的真實 IP,probe_ip 固定寫入本機位址。想追測量來源只能仰賴 probe_asn 加時間,同一條隱私邊界在 OONI Run v2 操作說明 也提醒協助者留意。

判定:測量得出的結論

判定欄位全部收在 test_keys 底下。讀之前要先知道一件事:web_connectivity 的判定建立在雙邊對照上。Probe 測完之後,OONI 架設在外部網路的測量伺服器(test helper)會對同一個網址再測一次,兩邊結果的差異才是判定的依據。test helper 那一側的紀錄收在 test_keys.control,下表的「對照組」指的就是它。

判定結果與內容比對共八個欄位。把兩筆並排,差異一眼可見:

欄位 正常通過 判定異常 說明
blocking false "dns" 判定的干預類型,可能值為 dnstcp_iphttp-failurehttp-diff,未觀測到干預時為布林值 false
accessible true false 是否取得了合理的回應
dns_consistency "consistent" "inconsistent" Probe 的 DNS 結果與對照組是否一致
title_match true null 網頁標題是否與對照組相符
headers_match true null 回應標頭是否相符
status_code_match true null HTTP 狀態碼是否相符
body_length_match true null 回應內容長度是否相近
body_proportion 1 0 內容長度與對照組的比值

各階段的失敗原因另有三個欄位,判讀時要一起看:

欄位 記錄什麼
dns_experiment_failure DNS 階段的失敗原因,判定異常那筆為 "dns_nxdomain_error"
http_experiment_failure HTTP 階段的失敗原因,例如 "generic_timeout_error"
control_failure 對照組本身是否失敗。有值時雙邊比對的前提不成立,判定結果不可信

blocking 的四種值分別對應不同階段的異常:dns 是解析結果與對照組不一致、tcp_ip 是封包送不到目標位址、http-failure 是連線建立後 HTTP 階段失敗、http-diff 是取得的內容與對照組不同(常見於封鎖告示頁)。四種手法在 什麼是 OONI 有概念層的介紹。

兩個容易誤判的型別問題

blocking 未觀測到干預時是布林值 false,有干預時是字串。拿它做統計前要先統一處理,否則 false"dns" 會被算成兩類不同的東西。

四種值的命名本身不一致,tcp_ip 用底線,http-failurehttp-diff 用連字號。上游如此定義,照抄即可,不要自行統一。

四個 *_match 欄位在異常那筆全是 null,原因是 DNS 階段就失敗,連線沒有建立,後續沒有東西可以比對。看到一整排 null 時,回頭找測量流程中第一個有值的 failure 欄位,那裡才是問題發生的地方。

異常不等於封鎖

blocking 有值只代表該筆測量的結果與對照組不一致,判斷是否真的存在網路干預需要更多佐證。以判定異常那筆為例,Probe 對 ntc.party 的 A 與 AAAA 查詢都回報 dns_nxdomain_error(網域不存在),但撰稿時從多個公開 DNS 解析器查詢,該網域可解析到 IPv6 位址。單筆測量無法區分網路干預、解析器當下的暫時狀態,以及網域本身的設定變動。

要下封鎖結論,需要跨時間、跨 ASN、跨解析器的多筆測量交叉比對。判定機制的完整拆解與常見誤判來源見 OONI 怎麼判定一個網站被封鎖,資料集層級的品質控制則可參考 OONI 如何分辨壞掉的量測資料

證據:支撐結論的原始紀錄

test_keys 底下另有幾個欄位,記錄測量過程中每一次網路操作。每個欄位各有一份規格:

欄位 內容 規格
queries 每一次 DNS 查詢的問題、回應與失敗原因 df-002-dnst
tcp_connect 每一次 TCP 連線嘗試的目標與結果 df-005-tcpconnect
tls_handshakes 每一次 TLS 握手的參數、憑證與結果 df-006-tlshandshake
requests 每一次 HTTP 請求與回應的完整內容 df-001-httpt
network_events 連線過程的時序事件,用於分析延遲與中斷點 df-008-netevents
control 對照組的同類紀錄,結構與 Probe 側對應,判定欄位全部由它與 Probe 端的差異算出 ts-017
各欄位的 failure 字串 所有失敗原因的統一命名,例如 dns_nxdomain_errorgeneric_timeout_errorssl_unknown_authority df-007-errors

把兩筆的 queries 攤開對照,就能看到判定的依據:

正常通過:查到位址
{
  "hostname": "presidentlee.tw",
  "query_type": "A",
  "failure": null,
  "answers": [{"answer_type": "A", "ipv4": "43.254.17.201"}]
}
判定異常:查詢落空
{
  "hostname": "ntc.party",
  "query_type": "A",
  "failure": "dns_nxdomain_error",
  "answers": []
}

判定層的 dns_consistency 是結論,queriescontrol 是它的依據。想確認一筆測量的判定是否合理,往證據層查驗即可。

版本與相容性

讀 spec 之前先確認版本,能避免不少困惑:

  • data_format_version 目前是 0.2.0。外殼層的欄位定義穩定,df-000-base 可以直接對照。
  • web_connectivity 實際流通的 test_version0.4.3ts-017 的規格內容已更新為描述 v0.5 演算法,但生產環境仍以 v0.4 為主。spec 明訂新版演算法必須使用不同的 test_keys 欄位,v0.4 的欄位定義保持相容,因此上表的欄位讀法對兩個版本都適用。
  • x_ 開頭的欄位不在 spec 內。實際資料中會看到 x_dns_runtimex_statusx_th_runtime 等欄位,屬於實作端的實驗性擴充,隨版本增減,分析程式不應依賴它們。

上游 spec 的 master 分支自 2025-06 起沒有新的合併,但 issue 與 PR 討論持續進行(進行中的提案包含 ICMP 資料格式與 DPI 分片測項)。spec 現階段適合當作穩定參考,引用時建議連回上游原文,避免自行複製規格內容而在上游更新後失準。

延伸閱讀

看懂欄位之後,接著看判定欄位怎麼算出來,以及為什麼有值不等於封鎖。

上游規格的完整目錄在 ooni/spec,其中 data-formats 收錄資料格式、nettests 收錄各測項的演算法定義。