同EAP模塊類似,EAPOL模塊的實現參考了另外一個規范,即IEEE 802.1X。
>[info] 注意 參考IEEE 802.1X 2004版規范的主要原因是,WPAS中EAPOL模塊也基于該版本的規范。另外,筆者比較了2004版和2010版的802.1X,發現2004版的內容組織相對清晰易讀。
在介紹802.1X前,先來看其描述的EAP和EAPOL之間的關系,如圖4-25所示。
:-: 
圖4-25 EAP和EAPOL的關系
根據上一節對EAP SUPP SM的介紹,讀者會發現圖4-25中所示的eapResp、eapSuccess等變量就是RFC4137中定義的用于LL層和EAP SUPP SM層交互的變量。很明顯,802.1X模塊(在WPAS中,它就是EAPOL模塊)是EAP SUPP的LL層(參考圖4-22)。
另外一個可能會讓讀者感到驚奇的是,802.1X規范為EAPOL Supplicant定義了5個不同的狀態機,分別如下。
1. **Port Timers SM**:Port超時控制狀態機。Port的概念請參考3.3.7節802.1X介紹。
2. **Supplicant PAE SM**:PAE是Port Access Entitiy的縮寫。該狀態機用于維護Port的狀態。
3. **Supplicant Backend SM**:規范并沒有明示該狀態機的作用。但筆者覺得它主要用于給Authenticator發送EAPOL回復消息。
4. **The Key Receiver SM**:用于處理Key(指EAPOL-Key幀)相關流程的狀態機。
5. **The Supplicant Key Transmit SM**:該狀態機非必選項,所以WPAS未實現它。
說實話,EAPOL Supplicant定義5個狀態機確實有些復雜。主要原因是這5個狀態機相互之間都有關聯,這些關聯體現在它們可能都受同一個變量的影響,從而導致各自的狀態發生變化。例如Port Timers SM修改了一些變量后,就有可能使得其他狀態機的狀態發生變化。規范中把這些變量成為全局變量(Global Varaibles)。
**規范閱讀提示**
1. 除了SUPP包含的這五個狀態機外,規范還為Authenticator定義了四個狀態機。Authenticator也需要實現Port Timers SM和The Key Receiver SM。
2. 規范中將這些狀態機統稱為PACP(Port Access Control Protocol)State Machine。
下面,先來認識這些全局變量。
**1. EAPOL SUPP全局變量**
802.1X定義了一些全局變量,它們被多個狀態機使用。這些全局變量的定義如表4-6所示。
:-: 
表 4-6 SUPP全局變量的定義
注意,表4-6省略了部分和Authenticator相關的全局變量。另外,規范還定義了一些全局超時變量,它們將在Port Timers SM中介紹。
**2. SUPP PACP狀態機**
**①、Port Timers SM**
Port Timers SM(PT SM)對應的狀態切換如圖4-26所示。
PT SM的功能比較簡單,就是每一秒觸發一次以從ONE_SECOND狀態進入TICK狀態。TICK狀態的EA中,它將遞減(圖4-26中的dec函數)某些變量的值。
:-: 
圖4-26 PT SM狀態切換
注意,PT SM在SUPP和AUTH兩端都有。所以,圖4-26中的一些變量只用于AUTH端。這些變量的含義如表4-7所示。
:-: 
表4-7 PT SM變量
雖然規范定義了PT SM,但WPAS中,PT SM的功能并不是通過狀態機宏來實現的,而僅僅是向eloop模塊注冊了一個超時時間為1秒的函數eapol_port_timers_tick,其代碼如下所示。
**eapol_supp_sm.c::eapol_port_timers_tick**
~~~
static void eapol_port_timers_tick(void *eloop_ctx, void *timeout_ctx)
{
struct eapol_sm *sm = timeout_ctx;
if (sm->authWhile > 0) {// 處理authWhile
sm->authWhile--;
if (sm->authWhile == 0)
wpa_printf(MSG_DEBUG, "EAPOL: authWhile --> 0");
}
// 處理heldWhile,startWhen,idleWhile(idleWhile見表4-1)
......
if (sm->authWhile | sm->heldWhile | sm->startWhen | sm->idleWhile) {
// 重新注冊超時處理函數,相當于切換到圖4-26中的ONE_SECOND狀態
eloop_register_timeout(1, 0, eapol_port_timers_tick, eloop_ctx, sm);
} else {
sm->timer_tick_enabled = 0;
}
eapol_sm_step(sm);// 處理其他狀態機的狀態切換,此函數內容下文會介紹
}
~~~
上述代碼中,eapol_port_timers_tick除了遞減相關變量外,最后還需要調用eapol_sm_step函數以判斷其他狀態機是否需要切換狀態。這是PT SM和其他狀態機聯動的關鍵紐帶,而這個紐帶在規范中并不能直接體現出來(規范中,PT SM只是修改某些變量,至于其他狀態機到底怎么被觸發,則沒有說明。而eapol_port_timers_tick函數修改完變量后,直接調用eapol_sm_step
函數完成了對其他狀態機的檢查)。
下面來看第二個狀態機The Key Receiver SM。
**②、The Key Receiver SM**
圖4-27所示為The Key Receiver SM(以后簡稱TKR SM)狀態切換圖。主要有兩點值得關注。
:-: 
圖4-27 TKR SM狀態切換
* TKR SM包含兩個狀態。第一個是NO_KEY_RECEIVE狀態。當rxKey(boolean型變量,當Supplicant收到EAPOL Key幀后,該值為TRUE)變為TRUE時,TKR進入KEY_RECEIVE狀態。
* TKR在KEY_RECEIVE狀態時需要調用processKey函數處理EAPOL Key消息。
WPAS中,TKR的代碼也非常簡單,如下所示。
**eapol_supp_sm.c::TRK SM相關函數**
~~~
SM_STATE(KEY_RX, NO_KEY_RECEIVE)
{
SM_ENTRY(KEY_RX, NO_KEY_RECEIVE);
}
SM_STATE(KEY_RX, KEY_RECEIVE)
{
SM_ENTRY(KEY_RX, KEY_RECEIVE);
eapol_sm_processKey(sm); // 對應圖4-27所示的processKey函數
sm->rxKey = FALSE;
}
SM_STEP(KEY_RX) // TKR狀態機狀態切換函數
{
if (sm->initialize || !sm->portEnabled)
SM_ENTER_GLOBAL(KEY_RX, NO_KEY_RECEIVE); // 直接進入NO_KEY_RECEIVE狀態
switch (sm->KEY_RX_state) {
case KEY_RX_UNKNOWN:
break;
case KEY_RX_NO_KEY_RECEIVE:
if (sm->rxKey)
SM_ENTER(KEY_RX, KEY_RECEIVE);
break;
case KEY_RX_KEY_RECEIVE:
if (sm->rxKey)
SM_ENTER(KEY_RX, KEY_RECEIVE);
break;
}
}
~~~
TKR SM的代碼非常簡單,此處不詳述。下面來看PAE SM。
**③、PAE SM**
PAE SM比較復雜,其狀態切換如圖4-28所示。
:-: 
圖4-28 PAE SM狀態切換
圖4-28中涉及的變量定義見表4-8。
:-: 
表4-8 PAE SM 變量定義
圖4-28還包括兩個函數。
* txStart:用于發送EAPOL-Start消息給Authenticator。
* txLogoff:用于發送EAPOL-Logoff消息給Authenticator。
>[info] 提示 PAE SM中的狀態雖然較多,但筆者覺得它們的劃分似乎并無涇渭分明的根據。另外,規范對它們的描述也僅是說明滿足什么條件將進入什么狀態。至于為什么劃分這么多狀態也沒有太多可參考的依據。所以,讀者也不必拘泥于求根究底了,只要把握圖4-28即可。
WPAS中,PAE SM相關的代碼也比較簡單,此處僅看LOGOFF狀態的EA,如下所示。
**eapol_supp_sm.c::SM_STATE(SUPP_PAE,LOGOFF)**
~~~
SM_STATE(SUPP_PAE, LOGOFF) // 狀態機名為SUPP_PAE,狀態名為LOGOFF
{
SM_ENTRY(SUPP_PAE, LOGOFF);
eapol_sm_txLogoff(sm); // 對應圖4-28中的txLogoff函數
sm->logoffSent = TRUE;
sm->suppPortStatus = Unauthorized;
// 這個函數內部將通過Nl80211 API設置WLAN Driver的狀態
// 屬于EAPOL模塊和WPAS中其他模塊的交互處理
eapol_sm_set_port_unauthorized(sm);
}
~~~
**④、Backend SM**
Backend SM(BE SM)的狀態轉換如圖4-29所示。
需要介紹和BE SM相關的變量authPeriod,它和authWhile(見表4-7)有關,默認值為30秒。
:-: 
圖4-29 BE SM狀態切換
BE SM包含如下幾個重要函數。
* abortSupp:停止認證工作,釋放相關的資源。
* getSuppResp:這個函數本意是用來獲取EAP Response信息的,然后用txSuppResp函數發送出去。但WPAS中,該函數沒有包括任何有實質意義的內容。
* txSuppResp:發送EAPOL-Packet包給Authenticator。
BE SM的部分代碼如下所示。
**eapol_supp_sm.c::SM_STATE(SUPP_BE,REQUEST)**
~~~
SM_STATE(SUPP_BE, REQUEST) // REQUEST狀態對應的EA
{
SM_ENTRY(SUPP_BE, REQUEST);
sm->authWhile = 0;
sm->eapReq = TRUE;
eapol_sm_getSuppRsp(sm); // 此函數內部并無任何有實質意義的內容,讀者不妨自行閱讀它
}
~~~
>[info] 提示 前面幾節介紹了802.1X中SUPP PACP幾個狀態機相關的知識。相比EAP SUPP SM而言,雖然PACP狀態機的個數增加了不少,但每個狀態機包含的狀態卻少了許多,所以PACP狀態機反而容易理解。
有了理論知識后,馬上來看EAPOL SUPP模塊中的幾個重要數據結構和函數。
**3.EAPOL SUPP代碼分析**
圖4-23和圖4-24介紹了EAPOL和EAP模塊的關系,那么EAPOL和WPAS其他模塊是什么關系呢?相關數據結構如圖4-30所示。
:-: 
圖4-30 WPAS中EAPOL/EAP模塊數據結構
由圖4-30可知,WPAS定義了一個數據結構eapol_sm來存儲和PACP狀態機相關的內容。其內部定義了三個狀態機(TKR SM、PAE SM和BE SM)各自的狀態信息(由三個狀態枚舉值表達)、相關變量等。EAPOL模塊和WPAS中重要模塊wpa_supplicant的交互接口是通過結構體eapol_ctx來定義的。EAPOL模塊通過eap變量指向EAP模塊的代表eap_sm結構體。
>[info] 提示 eapol_sm和eapol_ctx實際包含的成員變量非常多,此處僅列舉其中一部分。
雖然WPAS包括EAPOL和EAP兩個模塊,但WPAS其他模塊一般只和EAPOL模塊交互。至于EAP模塊,它的操作(例如EAP的初始化以及EAP SUPP SM的運作)則由EAPOL模塊來觸發。
**①、EAPOL模塊的初始化**
先來看EAPOL和EAP模塊的初始化函數,由wpa_supplicant_init_eapol函數完成,代碼如下所示。
**wpas_glue.c::wpa_supplicant_init_eapol**
~~~
int wpa_supplicant_init_eapol(struct wpa_supplicant *wpa_s)
{
#ifdef IEEE8021X_EAPOL
struct eapol_ctx *ctx;
ctx = os_zalloc(sizeof(*ctx));
......
ctx->ctx = wpa_s;
ctx->msg_ctx = wpa_s;
ctx->eapol_send_ctx = wpa_s;
ctx->preauth = 0;
ctx->eapol_done_cb = wpa_supplicant_notify_eapol_done;
ctx->eapol_send = wpa_supplicant_eapol_send;
......// 其他eapol_ctx成員變量的初始化
ctx->wps = wpa_s->wps;
ctx->eap_param_needed = wpa_supplicant_eap_param_needed;
ctx->port_cb = wpa_supplicant_port_cb;
ctx->cb = wpa_supplicant_eapol_cb;
ctx->cert_cb = wpa_supplicant_cert_cb;
ctx->cb_ctx = wpa_s;
wpa_s->eapol = eapol_sm_init(ctx); // 初始化EAPOL模塊
......
#endif /* IEEE8021X_EAPOL */
return 0;
}
~~~
wpa_supplicant_init_eapol首先設置eapol_ctx對象,然后調用eapol_sm_init來完成EAPOL模塊的初始化。eapol_sm_init的代碼如下所示。
**eapol_supp_sm.c::eapol_sm_init**
~~~
struct eapol_sm *eapol_sm_init(struct eapol_ctx *ctx)
{
struct eapol_sm *sm;
struct eap_config conf;
sm = os_zalloc(sizeof(*sm)); // EAPOL對應的狀態機信息
......
sm->ctx = ctx;// eapol_ctx是WPAS中EAPOL模塊和其他模塊交互的接口
sm->portControl = Auto;
sm->heldPeriod = 60;
sm->startPeriod = 30;
sm->maxStart = 3;
sm->authPeriod = 30;
os_memset(&conf, 0, sizeof(conf));
conf.opensc_engine_path = ctx->opensc_engine_path;
......
conf.wps = ctx->wps;
// 初始化EAP Supplicant SM相關資源
sm->eap = eap_peer_sm_init(sm, &eapol_cb, sm->ctx->msg_ctx, &conf);
......
// 先設置initialize變量為TRUE,然后初始化相關狀態
sm->initialize = TRUE;
eapol_sm_step(sm);
sm->initialize = FALSE; // 設置為FALSE,再初始化相關狀態
eapol_sm_step(sm);
sm->timer_tick_enabled = 1;
eloop_register_timeout(1, 0, eapol_port_timers_tick, NULL, sm);
return sm;
}
~~~
在eapol_sm_init代碼中:
1. 先通過調用eap_peer_sm_init初始化EAP SUPP SM相關資源。
2. 然后完成EAPOL PACP三個狀態機的初始化工作。初始化的方法很簡單,即先設置initialize為TRUE,然后執行eapol_sm_step函數(該函數代碼見下文,其主要目的是根據條件以跳轉到下一個狀態。initialize為TRUE時,將觸發一些狀態的EA被調用,從而某些變量的初值將被設定)。然后設置initialize為FALSE后,再度執行eapol_sm_step函數(這樣,對應狀態的EA也將被執行,從而剩余變量的初值將被設定)。
3. 最后通過注冊一個eloop超時任務實現了PT SM。
**②、狀態機的聯動**
根據前面的介紹,EAPOL和EAP一共有四個狀態機,它們到底是怎么聯動的呢?答案就在eapol_sm_step中。eapol_sm_step的代碼如下所示。
**eapol_supp_sm.c::eapol_sm_step**
~~~
void eapol_sm_step(struct eapol_sm *sm)
{
int i;
/*
筆者一直很好奇EAPOL和EAP中的四個狀態機是怎么聯動的。通過下面的代碼可知,
根據EAPOL和EAP的關系,首先要運行EAPOL中的三個狀態機(Port Timers SM由eloop定時任務
來實現),分別是SUPP_PAE、KEY_RX和SUPP_BE。然后執行EAP SUPP SM。如果changed變量
為TRUE,表示狀態發生了切換。由于每個狀態對應的EA又有可能改變其中一些變量從而引起其他狀
態機狀態發生變化,所以,這里有一個for語句來循環處理狀態切換,直到四個狀態機都沒有狀態切
換為止。一般情況下,for循環應該是一個無限循環,但此次通過100來控制循環次數,是為了防止
某些情況下狀態機陷入死循環而不能退出(這也說明規范中定義的SM在聯動時可能有邏輯錯誤)。
*/
for (i = 0; i < 100; i++) {
sm->changed = FALSE;
SM_STEP_RUN(SUPP_PAE);
SM_STEP_RUN(KEY_RX);
SM_STEP_RUN(SUPP_BE);
if (eap_peer_sm_step(sm->eap)) // eap_peer_sm_step返回非零,表示狀態有變化
sm->changed = TRUE;
if (!sm->changed) break; // 如果沒有狀態變化,則跳出循環
}
/*
運行超過100次,需要重新啟動EAPOL模塊狀態機運行,eapol_sm_step_timeout將重新調用
eapol_sm_step函數。
*/
if (sm->changed) {
eloop_cancel_timeout(eapol_sm_step_timeout, NULL, sm);
eloop_register_timeout(0, 0, eapol_sm_step_timeout, NULL, sm);
}
/*
cb_status是一個枚舉類型的變量,可取值有EAPOL_CB_IN_PROGRESS, EAPOL_CB_SUCCESS和
EAPOL_CB_FAILURE。
*/
if (sm->ctx->cb && sm->cb_status != EAPOL_CB_IN_PROGRESS) {
int success = sm->cb_status == EAPOL_CB_SUCCESS ? 1 : 0;
/*
該值在PAE AUTHENTICATED狀態中被置為EAPOL_CB_SUCCESS,表示認證成功。在PAE
HELD狀態被置為EAPOL_CB_FAILURE,表示認證還未成功。
*/
sm->cb_status = EAPOL_CB_IN_PROGRESS;
// 回調通知WPAS,真實的函數是wpa_supplicant_eapol_cb,這個函數以后介紹
sm->ctx->cb(sm, success, sm->ctx->cb_ctx);
}
}
~~~
WPAS中狀態機聯動代碼的實現非常巧妙,它通過循環來處理各個狀態機的狀態變換,直到四個狀態機都穩定為止。
>[info] 注意 初始化結束后,各個狀態機的狀態為:SUPP_PAE為DISCONNECTED狀態、KEY_RX為NO_KEY_RECEIVE狀態、SUPP_BE為IDLE狀態、EAP_SM為DISABLED狀態。
至此,對FRC4137和IEEE 802.1X-2004協議中EAP Supplicant和EAPOL Supplicant涉及的狀態機進行了詳細介紹。
在具體實現中,WPAS實現的EAPOL和EAP狀態機較為嚴格得遵循了這兩個文檔。所以,讀者只要理解了協議中的狀態切換和相關變量,則能輕松理解WPAS的實現。反之,如果僅單純從代碼入手,EAPOL/EAP狀態機的代碼將會非常難以理解。
關于EAPOL/EAP狀態機相關的知識就介紹到這,以后碰到具體代碼時,讀者根據狀態切換圖直接進入某個狀態中去看其處理函數(即EA)。
>[info] 提示 本章第二條分析路線使用的目標AP采用WPA2-PSK作為認證算法,故后續章節不會涉及太多和EAPOL及EAP相關的代碼分析。感興趣的讀者可在本節基礎上,自行搭建RAIDUS服務器來研究WPAS中EAPOL/EAP的工作過程。
- 前言
- 第1章 準備工作
- 1.1 Android系統架構
- 1.2 工具使用
- 1.2.1 Source Insight的使用
- 1.2.2 Eclipse的使用
- 1.2.3 BusyBox的使用
- 1.3 本書資源下載說明
- 第2章 深入理解Netd
- 2.1 概述
- 2.2 Netd工作流程
- 2.2.1 main函數分析
- 2.2.2 NetlinkManager分析
- 2.2.3 CommandListener分析
- 2.2.4 DnsProxyListener分析
- 2.2.5 MDnsSdListener分析
- 2.3 CommandListener中的命令
- 2.3.1 iptables、tc和ip命令
- 2.3.2 CommandListener構造函數和測試工具ndc
- 2.3.3 InterfaceCmd命令
- 2.3.4 IpFwd和FirewallCmd命令
- 2.3.5 ListTtysCmd和PppdCmd命令
- 2.3.6 BandwidthControlCmd和IdletimerControlCmd命令
- 2.3.7 NatCmd命令
- 2.3.8 TetherCmd和SoftapCmd命令
- 2.3.9 ResolverCmd命令
- 2.4 NetworkManagementService介紹
- 2.4.1 create函數詳解
- 2.4.2 systemReady函數詳解
- 2.5 本章總結和參考資料說明
- 2.5.1 本章總結
- 2.5.2 參考資料說明
- 第3章 Wi-Fi基礎知識
- 3.1 概述
- 3.2 無線電頻譜和802.11協議的發展歷程
- 3.2.1 無線電頻譜知識
- 3.2.2 IEEE 802.11發展歷程
- 3.3 802.11無線網絡技術
- 3.3.1 OSI基本參考模型及相關基本概念
- 3.3.2 802.11知識點導讀
- 3.3.3 802.11組件
- 3.3.4 802.11 Service介紹
- 3.3.5 802.11 MAC服務和幀
- 3.3.6 802.11 MAC管理實體
- 3.3.7 無線網絡安全技術知識點
- 3.4 Linux Wi-Fi編程API介紹
- 3.4.1 Linux Wireless Extensions介紹
- 3.4.2 nl80211介紹
- 3.5 本章總結和參考資料說明
- 3.5.1 本章總結
- 3.5.2 參考資料說明
- 第4章 深入理解wpa_supplicant
- 4.1 概述
- 4.2 初識wpa_supplicant
- 4.2.1 wpa_supplicant架構
- 4.2.2 wpa_supplicant編譯配置
- 4.2.3 wpa_supplicant命令和控制API
- 4.2.4 git的使用
- 4.3 wpa_supplicant初始化流程
- 4.3.1 main函數分析
- 4.3.2 wpa_supplicant_init函數分析
- 4.3.3 wpa_supplicant_add_iface函數分析
- 4.3.4 wpa_supplicant_init_iface函數分析
- 4.4 EAP和EAPOL模塊
- 4.4.1 EAP模塊分析
- 4.4.2 EAPOL模塊分析
- 4.5 wpa_supplicant連接無線網絡分析
- 4.5.1 ADD_NETWORK命令處理
- 4.5.2 SET_NETWORK命令處理
- 4.5.3 ENABLE_NETWORK命令處理
- 4.6 本章總結和參考資料說明
- 4.6.1 本章總結
- 4.6.2 參考資料說明
- 第5章 深入理解WifiService
- 5.1 概述
- 5.2 WifiService的創建及初始化
- 5.2.1 HSM和AsyncChannel介紹
- 5.2.2 WifiService構造函數分析
- 5.2.3 WifiStateMachine介紹
- 5.3 加入無線網絡分析
- 5.3.1 Settings操作Wi-Fi分析
- 5.3.2 WifiService操作Wi-Fi分析
- 5.4 WifiWatchdogStateMachine介紹
- 5.5 Captive Portal Check介紹
- 5.6 本章總結和參考資料說明
- 5.6.1 本章總結
- 5.6.2 參考資料說明
- 第6章 深入理解Wi-Fi Simple Configuration
- 6.1 概述
- 6.2 WSC基礎知識
- 6.2.1 WSC應用場景
- 6.2.2 WSC核心組件及接口
- 6.3 Registration Protocol詳解
- 6.3.1 WSC IE和Attribute介紹
- 6.3.2 802.11管理幀WSC IE設置
- 6.3.3 EAP-WSC介紹
- 6.4 WSC代碼分析
- 6.4.1 Settings中的WSC處理
- 6.4.2 WifiStateMachine的處理
- 6.4.3 wpa_supplicant中的WSC處理
- 6.4.4 EAP-WSC處理流程分析
- 6.5 本章總結和參考資料說明
- 6.5.1 本章總結
- 6.5.2 參考資料說明
- 第7章 深入理解Wi-Fi P2P
- 7.1 概述
- 7.2 P2P基礎知識
- 7.2.1 P2P架構
- 7.2.2 P2P Discovery技術
- 7.2.3 P2P工作流程
- 7.3 WifiP2pSettings和WifiP2pService介紹
- 7.3.1 WifiP2pSettings工作流程
- 7.3.2 WifiP2pService工作流程
- 7.4 wpa_supplicant中的P2P
- 7.4.1 P2P模塊初始化
- 7.4.2 P2P Device Discovery流程分析
- 7.4.3 Provision Discovery流程分析
- 7.4.4 GO Negotiation流程分析
- 7.5 本章總結和參考資料說明
- 7.5.1 本章總結
- 7.5.2 參考資料說明
- 第8章 深入理解NFC
- 8.1 概述
- 8.2 NFC基礎知識
- 8.2.1 NFC概述
- 8.2.2 NFC R/W運行模式
- 8.2.3 NFC P2P運行模式
- 8.2.4 NFC CE運行模式
- 8.2.5 NCI原理
- 8.2.6 NFC相關規范
- 8.3 Android中的NFC
- 8.3.1 NFC應用示例
- 8.3.2 NFC系統模塊
- 8.4 NFC HAL層討論
- 8.5 本章總結和參考資料說明
- 8.5.1 本章總結
- 8.5.2 參考資料說明
- 第9章 深入理解GPS
- 9.1 概述
- 9.2 GPS基礎知識
- 9.2.1 衛星導航基本原理
- 9.2.2 GPS系統組成及原理
- 9.2.3 OMA-SUPL協議
- 9.3 Android中的位置管理
- 9.3.1 LocationManager架構
- 9.3.2 LocationManager應用示例
- 9.3.3 LocationManager系統模塊
- 9.4 本章總結和參考資料說明
- 9.4.1 本章總結
- 9.4.2 參考資料說明
- 附錄