InterfaceCmd用來管理和控制系統中的網絡設備,其支持較多的控制選項。另外,InterfaceCmd除了和控制對象InterfaceController交互外,還會和ThrottleController、SecondaryTableController交互。InterfaceCmd涉及較多的背景知識,本節先集中介紹這部分內容。
**1.背景知識介紹**
InterfaceCmd涉及三個重要知識點,分別是IFB設備、Netdevice編程和Linux策略路由管理。
(1)IFB設備[13]
IFB(Intermediate Functional Block)是IMQ(InterMediate Queuing)的替代者,二者都是Linux為更好地完成流量控制而實現的虛擬設備。相比IMQ而言,IFB的實現代碼更少,對SMP(多核)系統支持的也更好。
為什么需要IFB設備呢?2.3.1節介紹tc命令的時候提到,Linux中豐富的流量控制手段和規則都是針對出口流量的,即大多數排隊規則(qdisc)都是用于輸出方向的。而輸入方向主要是入口流量限制,只有一個排隊規則,即ingress qdisc。有沒有辦法讓輸入流量也能像輸出流量那樣得到更多的控制呢?
于是,系統新增了一種方式,用于重定向incoming packets。通過ingress qdisc把輸入方向的數據包重定向到虛擬設備IFB,而在IFB的輸出方向配置多種qdisc,就可以達到對輸入方向的流量做隊列調度的目的。
圖2-15所示為加上IFB后整個流量控制示意圖。系統中一共有兩個IFB設備,IFB Device 0用于輸入流量控制,IFB Device 1用于輸出流量控制。
:-: 
圖2-15 增加IFB設備后的流量控制
(2)Netdevice編程[15]
Netdevice是Linux平臺中用于直接針對底層網絡設備編程的一套接口,其使用方法很簡單,就是利用socket句柄和ioctl函數來操作指定的網絡設備。常用的數據結構如下所示。
~~~
#include <net/if.h>
struct ifreq {
char ifr_name[IFNAMSIZ]; // 指定要操作的NIC名
union {// 一個聯合體,可以設置各種參數
struct sockaddr ifr_addr; // 網卡地址
struct sockaddr ifr_dstaddr;
struct sockaddr ifr_broadaddr; // 組播地址
struct sockaddr ifr_netmask; // 網絡掩碼
struct sockaddr ifr_hwaddr; // MAC地址
short ifr_flags;
int ifr_ifindex;
int ifr_metric;
int ifr_mtu;
struct ifmap ifr_map;
char ifr_slave[IFNAMSIZ];
char ifr_newname[IFNAMSIZ];
char *ifr_data;
};
};
~~~
Netdevice支持一些特殊的ioctl參數,表2-2展示了幾個常見的參數。
:-: 
表2-2 Netdevice ioctl參數說明
關于Netdevice的詳細信息,讀者可通過man netdevice獲得。Android為Netdevice編程提供了一些更為簡單的API,它們被封裝在libnetutils中,統稱為ifc_utils,下面介紹其中的三個常用API。
使用ifc_utils之前,需調用ifc_init進行初始化,其代碼如下所示。
**ifc_utils.c::ifc_init**
~~~
int ifc_init(void) // ifc是interface control的縮寫
{
int ret;
if (ifc_ctl_sock == -1) { // 創建一個socket句柄
ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM, 0);
......// 錯誤處理
}
ret = ifc_ctl_sock < 0 ? -1 : 0;
return ret;
}
~~~
如果要啟動某個NIC(如"ifb0"設備),需調用ifc_up函數,其代碼如下所示。
**ifc_utils.c::ifc_up**
~~~
int ifc_up(const char *name)
{
// 要啟動"ifb0"設備,name設置為"ifb0"
int ret = ifc_set_flags(name, IFF_UP, 0); // 設置IFF_UP參數
return ret;
}
~~~
ifc_up函數將通過調用ifc_set_flags函數并傳遞IFF_UP來啟動對應的設備,ifc_set_flags的代碼如下所示。
**ifc_utils.c::ifc_set_flags**
~~~
static int ifc_set_flags(const char *name, unsigned set, unsigned clr)
{
struct ifreq ifr;
ifc_init_ifr(name, &ifr); // 初始化ifr參數,把name復制到其ifr_name字符數組中
// 先獲取該NIC以前的flag信息
if(ioctl(ifc_ctl_sock, SIOCGIFFLAGS, &ifr) < 0) return -1;
// 將新的flag加入到原有的flags中
ifr.ifr_flags = (ifr.ifr_flags & (~clr)) | set;
return ioctl(ifc_ctl_sock, SIOCSIFFLAGS, &ifr);// 將組合后的flag傳遞給Kernel
}
~~~
相比直接使用Netdevice來說,Android封裝的ifc_utils更加直觀和易用。建議有需要的讀者使用ifc_utils對NIC進行操作。
(3)Linux策略路由[16][17]
**策略路由**是指系統依據網絡管理員定下的一些策略對IP包進行的路由選擇。例如網管可設置這樣的策略:“所有來自網A的包,選擇X路徑,其他選擇Y路徑”,或者“所有TOS(Type Of Service,IP協議頭的一部分)為A的包選擇路徑F,其他選擇路徑K”。
從Kernel 2.1開始,Linux采用了策略性路由機制。相比傳統路由算法,策略路由主要引入了多路由表及規則的概念。
傳統路由算法僅使用一張路由表。但在某些情況下,系統需要使用多個路由表。例如,一個子網通過一個路由器與外界相連。而該路由器與外界有兩條線路相連,其中一條的速度較快,另一條的速度較慢。對于子網內的大多數用戶來說,由于對速度沒有特殊要求,可以讓他們用速度較慢的路由;但是子網內有一些特殊用戶對速度的要求較苛刻,他們需要使用速度較快的路由。很明顯,僅使用一張路由表是無法實現上述要求的。而如果根據源地址或其他參數,對不同的用戶使用不同的路由表,就可以實現這項要求。
傳統Linux下配置路由的工具是route,而實現策略性路由配置的工具是iproute2工具包,常用的命令就是ip命令。
Linux最多可以支持255張路由表,其中有4張表是內置的。
- 表255:本地路由表(local table)。本地接口地址,廣播地址和NAT地址都放在這個表中。該路由表由系統自動維護,管理員不能直接修改。
- 表254:主路由表(main table)。如果沒有指明路由所屬的表,所有的路由都默認都放在這個表里,一般來說,傳統路由工具命令(如route)所添加的路由都會加到這個表中。一般是普通的路由。
- 表253:默認路由表(default table)。一般來說默認的路由都放在這張表,但是如果特別指明,該表也可以存儲所有的網關路由。
- 表0:默認保留。
2.3.1節簡單介紹了ip命令,ip命令的一些具體用法如下。
~~~
// ①查看路由表的內容命令
ip route list table table_number
// ②對于路由的操作包括change、del、add、append、replace、monitor
// 向主路由表(main table)即表254添加一條路由,路由的內容是設置192.168.0.4成為網關
ip route add 0/0 via 192.168.0.4 table main
// 向路由表1添加一條路由,子網192.168.3.0(子網掩碼是255.255.255.0)的網關是192.168.0.3
ip route add 192.168.3.0/24 via 192.168.0.3 table 1
~~~
在多路由表的路由體系里,所有的路由操作(如添加路由等)都需要指明要操作的路由表。如果沒有指明路由表,系統默認該操作是針對主路由表(表254)開展的。而在單表體系里,路由操作無須指明路由表。
至此,分別介紹了IFB、Netdevice和Linux路由策略等背景知識。在接下來的分析中,讀者將看到它們在InterfaceCmd中的應用。
**2.InterfaceCmd命令選項**
InterfaceCmd支持較多命令選項,筆者將它們粗略地分為6類。
(1)NIC設備信息管理選項
此類選項包括以下內容。
- list:列舉系統當前的網絡設備。這是通過枚舉/sys/class/net目錄下的文件名而來。該目錄下的文件(其實是一個設備文件)代表一個具體的網絡設備,例如eth0、lo等。/sys目錄是Linux設備文件系統(sysfs)的掛載點,用于Linux統一設備管理之用。
- readrxcounter和readtxcounter:這兩個選項分別用于讀取系統所有網絡設備的接收字節數和發送字節數等統計信息。它們都是通過讀取/proc/net/dev下的對應文件來實現的。
圖2-16所示為Galaxy Note 2對應文件的內容。該文件顯示了系統所有網絡設備的收發字節數、包數、錯誤及丟包等各種統計信息。
:-: 
圖2-16 /proc/net/dev文件內容
:-: 
圖2-17 list和readrxcounter結果
圖2-17展示了在Galaxy Note 2中用ndc執行interface list和readrxcounter選項后的結果。其中最后一行,216為readrxcounter選項對應的返回碼,0為錯誤碼,23847為wlan0設備的接收字節數。比較圖2-16最后一行wlan0的接收字節數可知,二者完全一致。
(2)輸入和輸出流量控制選項
InterfaceCmd支持對指定網絡設備設置流量閾值,其中包括如下。
- getthrottle:throttle是節流之意,用于流量控制。該選項支持讀取發送和接收閾值。不過目前代碼中這兩個值都將返回0。
- setthrottle:該選項的參數為setthrottle interface rx_kbps tx_kbps,用于控制網卡輸入和輸出流量。
setthrottle的實現比較復雜,核心代碼在ThrottleController類的setInterfaceThrottle函數中。這部分代碼充分利用了tc命令。以setthrottle wlan0 100 200為例,調用的命令順序如下。
~~~
#htb是Classful類qdisc的一種,屬于tc命令中較為復雜的一種用法
tc qdisc add dev wlan0 root handle 1: htb default 1 r2q 1000
tc class add dev wlan0 parent 1: classid 1:1 htb rate 200kbit
#利用ifc_utils啟動IFB0設備
tc qdisc add dev ifb0 root handle 1: htb default 1 r2q 1000
tc class add dev ifb0 parent 1: classid 1:1 htb rate 100kbit
tc qdisc add dev wlan0 ingress
tc filter add dev wlan0 parent ffff: protocol ip prio 10 u32 match \
u32 0 0 flowid 1:1 action mirred egress redirect dev ifb0
~~~
由上述setthrottle的實現可知,其要完成的功能很簡單(就是想對wlan0設備施加輸入和輸出流量控制),但實現過程卻比較復雜,使用了tc命令的很多高級選項。
* * * * *
**提示** 本書非專業的Linux網絡管理書籍,故此處僅向讀者展示這些命令。對其背后原理感興趣的讀者,不妨閱讀本章參考資料中列出的書籍。
* * * * *
(3)Route控制選項
InterfaceCmd支持路由控制,選項名為"route"。它支持對Route的添加、修改和刪除。另外還支持對多路由表的配置。相關代碼如下所示。
**CommandListener::InterfaceCmd:runCommand**
~~~
static int ifc_set_flags(const char *name, unsigned set, unsigned clr)
{
struct ifreq ifr;
ifc_init_ifr(name, &ifr); // 初始化ifr參數,把name復制到其ifr_name字符數組中
// 先獲取該NIC以前的flag信息
if(ioctl(ifc_ctl_sock, SIOCGIFFLAGS, &ifr) < 0) return -1;
// 將新的flag加入到原有的flags中
ifr.ifr_flags = (ifr.ifr_flags & (~clr)) | set;
return ioctl(ifc_ctl_sock, SIOCSIFFLAGS, &ifr);// 將組合后的flag傳遞給Kernel
}......// 略去部分代碼
if (!strcmp(argv[1], "route")) {
int prefix_length = 0;
......// 參數檢測
if (!strcmp(argv[2], "add")) {
if (!strcmp(argv[4], "default")) {// 調用ifc_add_route為指定NIC設備添加路由
ifc_add_route(argv[3], argv[5], prefix_length, argv[7]);
}......// 錯誤處理
} else if (!strcmp(argv[4], "secondary")) {// 對多路由表進行操作
return sSecondaryTableCtrl->addRoute(cli, argv[3], argv[5],prefix_length, argv[7]);
} ......// 錯誤處理
}
......// 處理刪除、修改等
}
~~~
由上述代碼可知:
- 當添加的是默認路由時,直接調用ifc_add_route函數為指定設備添加一個路由。該功能的實現利用了多種ifc_utils的ifc_add_route函數(也就是基于Netdevice封裝的API),對應的IOCTL參數為SIOCADDRT。請讀者自行研究該函數的實現。
- 如果是針對多路由表的"secondary",則使用SecondaryTableCtrontroller的addRoute函數來添加路由。該函數中,SecondaryTableCtrl首先會計算一個Table索引,然后利用ip命令將路由添加到對應的Table中。
addRoute的核心是調用modifyRoute,其代碼如下所示。
**SecondaryTableController.cpp::modifyRoute**
~~~
int SecondaryTableController::modifyRoute(SocketClient *cli,
const char *action, char *iface,
char *dest, int prefix, char *gateway, int tableIndex) {
char *cmd;
if (strcmp("::", gateway) == 0) {
asprintf(&cmd, "%s route %s %s/%d dev %s table %d",
IP_PATH, action, dest, prefix, iface,
tableIndex+BASE_TABLE_NUMBER);
} else {
// 調用ip route命令的參數
asprintf(&cmd, "%s route %s %s/%d via %s dev %s table %d",
IP_PATH, action, dest, prefix, gateway,
iface, tableIndex+BASE_TABLE_NUMBER);
}
if (runAndFree(cli, cmd)) {
......// 錯誤處理
return -1;
}
// SecondaryTableControl對各個Table表的使用都有計數控制。請讀者自行閱讀相關代碼
......// 略去相關代碼
modifyRuleCount(tableIndex, action);
cli->sendMsg(ResponseCode::CommandOkay, "Route modified", false);
return 0;
}
~~~
上述代碼中,以wlan0為例,對應的ip命令情況如下。
~~~
ip route add 192.168.0.100/24 via 192.168.0.1 dev wlan0 table 78#使用路由表78
~~~
(4)Driver控制選項
該選項的名為"driver",由InterfaceController加載/system/lib/libnetcmd.so對Kernel中的Driver進行控制。而針對Driver操作的API由InterfaceController的interfaceCmd來完成。
**InterfaceController.cpp**
~~~
char if_cmd_lib_file_name[] = "/system/lib/libnetcmdiface.so";
char set_cmd_func_name[] = "net_iface_send_command";
char set_cmd_init_func_name[] = "net_iface_send_command_init";
char set_cmd_fini_func_name[] = "net_iface_send_command_fini";
~~~
InterfaceController構造時會加載libnetcmdinface.so,然后獲取上面三個函數的指針。這三個函數用于直接向驅動發送命令。由于代碼中沒有任何說明,而且也沒有地方用到它,故讀者僅簡單了解即可。
* * * * *
**提示**
筆者認為libnetcmdiface的目的應該是支持某些網絡設備生產廠商(如Broadcom)特有的一些命令選項。由于廠商的這些特殊選項不通用,它們不能向其他命令選項一樣在代碼中被列舉出來。
另外,根據本書審稿專家的反饋,在實際產品中不存在libnetcmdiface.so文件。對博通公司的芯片而言,其私有命令的代碼在hardware/broadcom/wlan/bcmhdh/wpa_supplicant_8_lib中,對應的庫文件為lib_driver_cmd_bcmdhd.so。
* * * * *
(5)IFC設備控制選項
IFC設備控制選項主要是利用ifc_utils API對設備進行管理和控制,例如獲取某個設備的配置信息、啟動某個設備、配置某個設備的MTU等。主要利用上文介紹的IFC接口對設備進行控制,例如啟動、設置MTU等。本節僅給出利用getcfg選項查詢wlan0網絡設備配置情況的示意圖,如圖2-18所示。
:-: 
圖2-18 getcfg wlan0結果
圖2-18顯示了Galaxy Note 2打開Wi-Fi后wlan0設備的配置信息,其中:
- 90:18:7c:69:88:e2為該設備的MAC地址。
- 192.168.1.101和24為IP地址以及子網掩碼(255.255.255.0)。
- up、broadcast、running和multicast表示該設備當前已經啟動并正在運行,并支持廣播和組播。
(6)IPv6控制選項[18]
InterfaceCmd支持兩個IPv6選項。
- ipv6privacyextensions:用于控制網卡使用臨時IPv6地址的情況。該功能需要要3.0以上的kernel控制方法就是向/proc/sys/net/ipv6/網卡名/conf/use_tempaddr寫入對應的值。值為0表示不允許IPv6臨時地址,值為2表示優先使用臨時IP地址,值為1則表示優先使用非臨時IP地址。
- ipv6:用于啟動或禁止網卡的IPv6支持。往/proc/sys/net/ipv6/網卡名/conf/disable_ipv6中寫入0或1即可。
- 前言
- 第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 參考資料說明
- 附錄