在上個小節中,已經基本分析了Tor系統的初始化過程。該過程中,最重要的部分,就是對預設設定檔、輸入設定檔以及命令列參數進行綜合整理,定出最後的配置方案。而後通過配置方案,啟動系統的基礎部分。這裡值得說明的是,Tor系統的所有配置選項,均可以在Tor Manual中找到。並且,根據Tor系統配置成不同的身份,使用的配置選項也會有細微差別。預設的配置固化在代碼內部,有興趣的朋友可以就初始化部分往下深究。另外,筆者會將經自己簡要翻譯和分析過的Tor Manual在後續章節中給出。
1. 主命令的執行
在初始化過程中,系統將所有參數配置均最終寫入到全域變數global_options之中。之後利用get_options()函數就可以隨時擷取系統配置和系統主命令。系統主命令在tor_main()函數中被使用,用於指示Tor程式啟動的目的。
if (tor_init(argc, argv)<0) return -1; switch (get_options()->command) { case CMD_RUN_TOR:#ifdef NT_SERVICE nt_service_set_state(SERVICE_RUNNING);#endif result = do_main_loop(); break; case CMD_LIST_FINGERPRINT: result = do_list_fingerprint(); break; case CMD_HASH_PASSWORD: do_hash_password(); result = 0; break; case CMD_VERIFY_CONFIG: printf("Configuration was valid\n"); result = 0; break; case CMD_RUN_UNITTESTS: /* only set by test.c */ default: log_warn(LD_BUG,"Illegal command number %d: internal error.", get_options()->command); result = -1; }
這裡我們只分析CMD_RUN_TOR命令下的系統主迴圈函數,也就是Tor系統的核心函數,不再繼續討論另外的主命令。
2. 主迴圈的初始化
主迴圈的初始化代碼如下:
/* initialize dns resolve map, spawn workers if needed */ if (dns_init() < 0) { if (get_options()->ServerDNSAllowBrokenConfig) log_warn(LD_GENERAL, "Couldn't set up any working nameservers. " "Network not up yet? Will try again soon."); else { log_err(LD_GENERAL,"Error initializing dns subsystem; exiting. To " "retry instead, set the ServerDNSAllowBrokenResolvConf option."); } } handle_signals(1); /* load the private keys, if we're supposed to have them, and set up the * TLS context. */ if (! client_identity_key_is_set()) { if (init_keys() < 0) { log_err(LD_BUG,"Error initializing keys; exiting"); return -1; } } /* Set up the packed_cell_t memory pool. */ init_cell_pool(); /* Set up our buckets */ connection_bucket_init();#ifndef USE_BUFFEREVENTS stats_prev_global_read_bucket = global_read_bucket; stats_prev_global_write_bucket = global_write_bucket;#endif /* initialize the bootstrap status events to know we're starting up */ control_event_bootstrap(BOOTSTRAP_STATUS_STARTING, 0); if (trusted_dirs_reload_certs()) { log_warn(LD_DIR, "Couldn't load all cached v3 certificates. Starting anyway."); } if (router_reload_v2_networkstatus()) { return -1; } if (router_reload_consensus_networkstatus()) { return -1; } /* load the routers file, or assign the defaults. */ if (router_reload_router_list()) { return -1; } /* load the networkstatuses. (This launches a download for new routers as * appropriate.) */ now = time(NULL); directory_info_has_arrived(now, 1); if (server_mode(get_options())) { /* launch cpuworkers. Need to do this *after* we've read the onion key. */ cpu_init(); }
總結來說,主迴圈的初始化包括這幾個部分:DNS,訊號處理,密鑰,cell池,令牌桶,網路設定,多線程配置。對於配置為用戶端身份的Tor系統而言,有些部分實際上沒有進行相應處理,此處我們略去那些一般用於伺服器的初始化部分,直接討論用戶端關注的初始化部分。
2.1 訊號處理初始化
Tor系統中的信處理機制,sock監聽機制以及按時回調機制,都是由libevent開原始碼庫提供的功能完成的。下述handle_signals()函數的部分,將在libevent系統內註冊每個訊號以及相應的訊號處理函數與參數:
if (is_parent) { for (i = 0; signals[i] >= 0; ++i) { signal_events[i] = tor_evsignal_new( tor_libevent_get_base(), signals[i], signal_callback, (void*)(uintptr_t)signals[i]); if (event_add(signal_events[i], NULL)) log_warn(LD_BUG, "Error from libevent when adding event for signal %d", signals[i]); } } else { struct sigaction action; action.sa_flags = 0; sigemptyset(&action.sa_mask); action.sa_handler = SIG_IGN; sigaction(SIGINT, &action, NULL); sigaction(SIGTERM, &action, NULL); sigaction(SIGPIPE, &action, NULL); sigaction(SIGUSR1, &action, NULL); sigaction(SIGUSR2, &action, NULL); sigaction(SIGHUP, &action, NULL);#ifdef SIGXFSZ sigaction(SIGXFSZ, &action, NULL);#endif }
可見,Tor主進程中的訊號處理函數,是signal_callback()函數,其參數是訊號值。Tor子進程的訊號處理,是用sigaction()函數來實現的,而真正的訊號操作則是忽略任何訊號。
2.2 密鑰初始化和TLS初始化
在討論密鑰初始化和TLS初始化之前,我們必須瞭解Tor系統中被使用到的所有密鑰,以及每個身份和密鑰之間的關係。
Tor系統使用到的密鑰分三種:(詳見Tor FAQ:Tell me about all the kyes Tor uses)
1)Encryption(對稱金鑰):TLS鏈路OR與OR之間的對稱金鑰;Tor Protocol OP與OR之間的對稱金鑰;
2)Authenticaiton(非對稱金鑰):屬於OR的Onion Key,也就是用於OP與OR之間進行DH金鑰交換協議時用到的公開金鑰;
3)Coordination(非對稱金鑰):屬於全體的Identity Key;目錄伺服器額外擁有的目錄簽名密鑰Signing Key;
用戶端擁有單獨的Client Identity Key,
non-bridge OR伺服器有相同的Client Identity Key和Server Identity Key,
bridge OR伺服器有不相同的Client Identity Key和Server Identity Key。
註:non-bridge本質上是不公開發布的bridge,由於不公開的原因,其兩身份密鑰必須不同。
因為這樣的密鑰設定,密鑰初始化的過程相對就複雜了些。
對於用戶端來說,Client Identity Key可以臨時產生,使用完畢也可以丟棄。所以,初始化的過程就是簡單的產生Client Identity Key,再初始化TLS上下文。
對於伺服器來說,Server Identity Key是長期儲存的密鑰,需要從儲存密鑰的檔案中讀取,若是初次開機則產生密鑰並儲存(一般存於DataDirectory/keys)。Onion Key的情況也是如此。若伺服器是目錄伺服器,則需要更多的操作來進行目錄伺服器的相關密鑰或認證處理。總的說來,密鑰初始化和TLS初始化的過程,對整個Tor系統所有身份的密鑰都進行了初始化,為Tor系統的正常運行做了密碼學上的密鑰分配。
我們此處就暫且先討論用戶端密鑰初始化和TLS初始化,其代碼如下:
/* OP's don't need persistent keys; just make up an identity and * initialize the TLS context. */ if (!server_mode(options)) { if (!(prkey = crypto_pk_new())) return -1; if (crypto_pk_generate_key(prkey)) { crypto_pk_free(prkey); return -1; } set_client_identity_key(prkey); /* Create a TLS context. */ if (router_initialize_tls_context() < 0) { log_err(LD_GENERAL,"Error creating TLS context for Tor client."); return -1; } return 0; }
Tor系統中用的公開金鑰系統一般為RSA公開金鑰系統,所以身份密鑰初始化是RSA密鑰的初始化。TLS上下文初始化就是標準的初始化方式。
最終,在執行完上述代碼之後,系統中設定了兩個全域變數:client_identitykey,client_tls_context。實際上,若系統配置成伺服器身份,還會對另外兩個相應的全域變數進行修改:server_identitykey,server_tls_context。
2.3 cell池初始化
cell是Tor Protocol進行協議溝通的基本資料單元,該單元的具體組織形式在Tor系統設計檔案tor_design.pdf中已經有詳細描述。在近些版本的Tor系統中,cell的具體格式和相關命令也發生了些許的變化,但是基本的格式和思想是沒有發生大的改變。Tor系統為了統一管理cell,設計了cell池。其初始化就是對全域cell池的初始化,最終設定全域變數cell_pool。
2.4 令牌桶初始化
令牌桶是用於對Tor系統進行流量控制的系統。系統中這個部分的工作似乎用libevent實現的多,由於沒有細細研究,此處就不再細究。
2.5 網路設定
網路設定部分代碼如下,共五個主要函數:
if (trusted_dirs_reload_certs()) { log_warn(LD_DIR, "Couldn't load all cached v3 certificates. Starting anyway."); } if (router_reload_v2_networkstatus()) { return -1; } if (router_reload_consensus_networkstatus()) { return -1; } /* load the routers file, or assign the defaults. */ if (router_reload_router_list()) { return -1; } /* load the networkstatuses. (This launches a download for new routers as * appropriate.) */ now = time(NULL); directory_info_has_arrived(now, 1);
trusted_dirs_reload_certs():該函數從本地cache-certs檔案載入事先已經下載好的各權威目錄伺服器的認證;在系統第一次啟動時,cache-certs檔案是不存在的,也就是說該函數實際上在此處不會被真正執行。
router_reload_v2_networkstatus():該函數從本地cache-status檔案載入事先已經下載好的網路狀態檔案;在新版本的系統中,cache-status檔案對於用戶端而言並不是必須要下載的檔案,也就是說此處函數實際上也不會對系統產生影響。
router_reload_consensus_networkstatus():該函數從本機存放區的網路共識檔案中讀取必要資訊來初始化系統網路認識;在系統第一次啟動時,cache-%s-consensus檔案是不存在的,也就是說該函數實際上不會對系統產生影響。
router_reload_router_list():該函數載入系統新的路由列表;而執行函數核心部分需要用到系統全域變數router_list,而該變數由於上述幾個函數沒有成功執行,也不會被設定,所以本函數的載入系統路由列表也無法成功執行。
directory_info_has_arrived():該函數會在系統成功擷取新的網路狀態或者伺服器描述符時成功執行;而上述的幾個嘗試擷取本地cache的網路狀態和路由資訊產生了失敗,所以該函數也不會對系統執行產生影響。
綜上所述,此處的網路設定初始化,在系統第一次啟動時是沒有成功完成有效操作的。因為筆者按照用戶端系統第一次啟動流程來編寫Tor系統分析,所以這些函數的具體分析和作用會在後續分析中做更多敘述(遇到他們發揮作用的地方)。
於此,介紹完畢主迴圈初始化的基本內容,接下來就到了主迴圈的核心迴圈部分。
3. 主迴圈核心
主迴圈的核心主要做了這麼幾件事情:秒回調設定;令牌桶重填回調設定;事件集主迴圈。
3.1 秒回調設定
/* set up once-a-second callback. */ if (! second_timer) { struct timeval one_second; one_second.tv_sec = 1; one_second.tv_usec = 0; second_timer = periodic_timer_new(tor_libevent_get_base(), &one_second, second_elapsed_callback, NULL); tor_assert(second_timer); }
秒回調的設定,為維護整個Tor系統做了準備。顧名思義,秒回調,即是Tor系統會每秒鐘執行一次此處設定的回呼函數second_elapsed_callback()函數。其核心是通過libevent庫的定時事件來實現的。此處,我們更關心定時回呼函數都做了什麼,但是,因為該回呼函數非常巨大,無法一次性講清。筆者將會將該回呼函數特意寫成一節,細細分析。
3.2 令牌桶重填回調設定
#ifndef USE_BUFFEREVENTS if (!refill_timer) { struct timeval refill_interval; int msecs = get_options()->TokenBucketRefillInterval; refill_interval.tv_sec = msecs/1000; refill_interval.tv_usec = (msecs%1000)*1000; refill_timer = periodic_timer_new(tor_libevent_get_base(), &refill_interval, refill_callback, NULL); tor_assert(refill_timer); }#endif
令牌桶回調設定會在Bufferevent功能未被開啟之時起到作用,即預設情況下是被啟動的。此處的函數,將讀取配置參數來設定固定時間執行的回呼函數refill_callback()。其核心也是libevent的定時事件機制。refill_callback函數的內容是對令牌桶的令牌數量的增加操作,此處略去。另外,Bufferevent是libevent庫的一種IO緩衝機制,由於筆者對Bufferevent也不甚瞭解,關於Bufferevent的問題,在分析後期再詳細給出說明。
3.3 主迴圈核心迴圈
for (;;) { /* All active linked conns should get their read events activated. */ SMARTLIST_FOREACH(active_linked_connection_lst, connection_t *, conn, event_active(conn->read_event, EV_READ, 1)); called_loop_once = smartlist_len(active_linked_connection_lst) ? 1 : 0; update_approx_time(time(NULL)); /* poll until we have an event, or the second ends, or until we have * some active linked connections to trigger events for. */ loop_result = event_base_loop(tor_libevent_get_base(), called_loop_once ? EVLOOP_ONCE : 0); }
此處代碼刪去許多不重要的部分,留下的是整個Tor系統迴圈的核心:
系統每次核心迴圈,從活動linked串連列表中取出所有事件,直接加入到libevent事件池中的活動事件池進行調度運行。並且,若是活動linked串連列表確實有串連,則libevent事件池的調度只執行一次便返回。值得說明的是,直接加入到活動事件池內的事件,被處理完之後就完全從事件池中退出,不會存留。而普通情況下的預備事件在成為活動事件之後,當回呼函數執行完畢,預備事件任處在調度池中。當然,若活動linked串連列表沒有事件,則libevent的調度會阻塞,直到有事件需要被調度執行。而此時跳出loop的時機,則是活動linked串連列表新增加了串連,也就是調用了前面提到過的connection_start_reading_from_linked_conn()函數。該函數調用tor_event_base_loopexit()函數,令loop返回。