2016年1月10日 星期日

2015年11月21日 星期六

multiple thread 存取共享資料的小技巧

最近看到不錯的寫法, 備忘一下。

通常我們不希望 main thread 被 block [*1], 所以會另外開 worker thread 處理耗 CPU 的工作。主要的需求是避免 block main thread, 其次是縮短完成工作的時間。

由於 main thread 和 worker thread 需要互相傳遞一些資料, 有可能造成 race condition, 導致程式行為有時不正確或是掛掉。這裡討論幾種避免 race condition 的作法。

用一個 "global" lock 保護整個 thread 的資料

worker thread 全部函式都用同一個 lock 保護。

  • 優點: 容易實作。
  • 缺點: main thread 在 worker thread 忙的時候存取共享資料, 還是會被 worker thread block。

不同群共享的資料用不同的 lock 保護

和上個作法類似, 不過資料分群用不同 lock 保護。

  • 優點: 減少 main thread 被 block 的機會。
  • 缺點: 實作有些複雜, 可能會漏保護到新加的變數。並且 main thread 仍有可能被 block。

避免共享資料

如果資料只有一個 thread 會用到, 就直接轉交給另一個 thread (改變 ownership)。若兩個 thread 都會用到, 先複製一份再轉交給另一個 thread。沒有共享的資料, 就不需用 lock 保護了 (但仍需透過 message loop 或 queue 傳遞資料到另一個 thread)。

  • 優點: 複製的時間不長, 且可以掌控占據 main thread 的時間, 完全不受 worker thread 影響。
  • 缺點: 需要仔細設計複製和傳遞資料的機製, 確保沒有共享資料。

需要同步資料的時候, 記得只能允許一個方向的 block。比方說 main thread 可以透過 conditional variable 來 block worker thread, 但不允許反過來的操作, 這樣就不會有 dead lock。

不用 lock 的注意事項

每個函式都要放 assert 確保函式在正確的 thread 執行。若一個 class 有兩份資料分別在兩個 thread 下執行的話, 可以考慮訂兩個 struct, 比方說 MainData 和 WorkerData, 然後透過 private method 存取資料, 藉此確保 thread safe:

// C++
class MyClass
{
public:
   ...

private:
  MainData& GetMainData();
  WorkerData& GetWorkerData();

  struct MainData {
    ...
  };
  struct WorkerData {
    ...
  };
  MainData m_mainData;
  WorkerData m_workerData;
};

MainData& MyClass::GetMainData()
{
  assert(InMainThread());
  return m_mainData;
}

WorkerData& MyClass::GetWorkerData()
{
  assert(InWorkerThread());
  return m_workerData;
}

這樣程式寫錯時會造成 assert failed, 可以很快地修正。

備註

*1 比方說 daemon 的 main thread 會不斷收新的連線, GUI 的 main thread 會收使用者的輸入。main thread 被 block 而沒有反應的話, 使用者會覺得軟體有問題。

2015年10月3日 星期六

node.js 開發備忘

前陣子用 node.js 寫了一個上線的服務, 沒有意外的話接下來有一大段時間不會碰 node.js。備忘一下有用到和以後可能會用到的東西。

何時以及為什麼要用 node.js?

node.js 開發

主要弱點

其它

  • coding style: callback 函式的第一個參數總是放 error, 沒有 error 時傳入 null。
  • 用 nodemon 在更新程式後自動更新。
  • logger: 有很多家, 我是用 winston, 還不錯用, 寫入 JSON log, 再用 jq 讀。
  • 用 posix 使用缺少的 POSIX API (如 setrlimit), 還有可以參考它如何包 C/C++ 程式給 node.js 用。
  • web server: 大家都用 express, 要用 HTTPS 或 POST 需要作些設定。看起來彈性很大的 web server。
  • 用舊版 node.js (0.10以前) 記得設 http max socket 提高對每個 domain 送出的連線數量。

2015年10月2日 星期五

2015年9月29日 星期二

觀察 HTTP/HTTPS 傳輸的內容以及防範 man-in-the-middle attack

看到許多強者推薦 mitmproxy (MITM 是 man-in-the-middle 的意思), 試用了一下的確超好用的。它用 man-in-the-middle attack 的方式觀察 (甚至更改) HTTP/HTTPS 傳輸的內容。

How To: Use mitmproxy to read and modify HTTPS traffic 介紹原理和用法, 設定很簡單。

官方文件也很清楚, 可交叉參考:

這裡摘要一下 mitmproxy 作為 Transparent Proxy 的方式和原理:

  1. 備好一台機器 R 作為觀察目標裝置 D (如手機) 的 gateway, 這樣就完成 man-in-the-middle 的設置。
  2. 設定 R 的 firewall, 重導往外連往 port 80 和 443 的連線到在 R 上執行的 mitmproxy。
  3. 在 D 上安裝 mitmproxy 的 certificate, 這樣 D 會相信 mitmproxy 幫自己簽的 certificate。

測試 Android 手機在 MITM 情況下 App 的反應

我是用 ASUS Zenfone 2, 不過 App 的測試結果應該和手機無關。

結果如下:

  • Android OS 有出現訊息說網路可能被監控了, 但清掉訊息後就沒再出現了, 使用者應該不會注意到。
  • 沒裝 mitmproxy certificate 的情況, Google Play 會出現連線錯誤; 裝 certificate 後可以用 Google Play 安裝 app。
  • 沒裝 mitmproxy certificate 的情況, Chrome 會警告有問題; 有裝 certificate 會放行。
  • 沒裝 mitmproxy certificate 的情況, Gmail 會如常運作, 看不到 HTTPS 傳的內容。裝 certificate 的後可以看到 HTTPS 傳的內容。雖然 Gmail 用自己訂的資料格式, 在搜尋信件時, 可看到搜尋字、回傳信件的標題、內文前面幾句的文字。
  • 另外在有裝 mitmproxy certificate 的情況測了 Hangouts, Facebook, Messenger, Skype, Line。它們的主要傳輸內容都不是用 HTTPS, 改天再用 SSLsplit 看看它們傳什麼。
    • Line 會說 Wi-Fi 有問題無法用需要連線的功能 (訊息、電話、桌機 QR Code 登入), 剩下的 App 都可以用。
    • Facebook 有部份內容用 HTTPS 傳, 並且用 JSON 作為內文編碼。
    • Facebook 和 Messenger 有時候會顯示連線問題, 但放一陣子又通了, 也許它們有偵測網路情況用不同方式連線吧。
    • Hangouts 意外地都沒有安全警告, 有沒有裝 mitmproxy certificate 都一樣, 可以正常使用。

其它 Web Proxy

這些也是很有名的 Web Proxy, 有看到朋友推薦。不確定作為 transparent proxy 的效果如何, 至少監控本機的訊息應該滿方便的, 留著備忘。

防範 man-in-the-middle attack

聽 Scott 提到可用 Certificate Pinning 防 MITM attack。原理是只相信自己指定的 certificate (或幫你簽 certificate 的 certificate), 這樣就會忽視攻擊者裝到作業系統的 root CA。當 SSL handshake 的 certificate 被替換的時候, 會變成 handshake error。Certificate Pinning 似乎有些不同的作法, 先記住關鍵字, 之後再來研究。

另一個好像可行但可能有問題的解法是不用 OS 提供的 certificate, 直接用自己包在 app 裡的 certificate (放 server 用的 certificate 或簽它的 CA 的 certificate, 意思一樣)。雖然這樣不用擔心裝置被裝了有問題的 root certificate, 但和 self-signed certificate 一樣, 需要處理另一個問題 (原本的問題): 如何確定 app 內裝了正確的 certificate? 這個難題原本是由 OS 處理, 假設 OS 沒安裝有問題的 root certificate, 所以可以用它的 root certificate。

2015-10-01 更新

經 Victor 告知, 發現 Gmail 應該有檢查 certificate, 我又測了一下, 並更正內文。

Gmail 和 Google Play 有檢查 certificate, 只是使用者從系統刪除信任的 certificate 時, app 沒有同步更新, 仍會相信這個 certificate。另一方面, Chrome 有同步更新。

HTTP library 支援 gzip response body 的情況

為了節省 HTTP response 的傳輸量, HTTP 有提供 request header Accept-Encoding: gzip 要求 server 「可以的話, 用 gzip 壓縮 body 再傳回來」, 還有 response header Content-Encoding: gzip 告知 client 「response body 有用 gzip 壓縮」。

在這樣的規定下, 推測 HTTP library 可能會有兩種實作方式:

  • 不處理。使用者自己加 request header "Accept-Encoding: gzip", 然後自己解 response body
  • 有個 flag 開啟後會自動處理。自動加上 "Accept-Encoding: gzip", 然後自動解 response body 並且移掉 response header "Content-Encoding: gzip"。另外要註明 API 取得的 content length 是否會受 gzip 影響, 結果如何變化。

不過實際情況是:

  • iOS NSURLConnection 文件沒有提到這件事, 但是會自動加 request header 並自動解 response body, 不過沒有移掉 response header "Content-Encoding: gzip" (Ref., 實測結果確實如此, 不需手動加 Accept-Encoding: gzip 就有作用)
  • Android HttpURLConnection 指明預設就會作, 並會清掉 Content-Encoding 和 Content-Length。可以用 Accept-Encoding: identity 覆寫預設值 (Ref.)。
  • curl 開啟 flag CURLOPT_ACCEPT_ENCODING 後會支援, 也是自動加 request header 並自動解 response body, 不過沒有移掉 response header "Content-Encoding: gzip" (Ref.)

結果只有 Android 的實作符合預期。使用前還是要先測過才準啊。

附帶一提, http://httpbin.org/ 會回傳 request/response header, 用來測試 HTTP library 很方便。或是用 mitmproxy 作 transparent proxy 也可以, 相對之下費工一點, 不過看得最清楚。

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...