Posted on 2012-06-15 17:33
幻海藍(lán)夢 閱讀(10227)
評論(0) 編輯 收藏 所屬分類:
C語言學(xué)習(xí)
Linux 用戶態(tài)與內(nèi)核態(tài)的交互
在 Linux 2.4 版以后版本的內(nèi)核中,幾乎全部的中斷過程與用戶態(tài)進(jìn)程的通信都是使用 netlink 套接字實(shí)現(xiàn)的,例如iprote2網(wǎng)絡(luò)管理工具,它與內(nèi)核的交互就全部使用了netlink,著名的內(nèi)核包過濾框架Netfilter在與用戶空間的通 讀,也在最新版本中改變?yōu)閚etlink,無疑,它將是Linux用戶態(tài)與內(nèi)核態(tài)交流的主要方法之一。它的通信依據(jù)是一個(gè)對應(yīng)于進(jìn)程的標(biāo)識,一般定為該進(jìn) 程的 ID。當(dāng)通信的一端處于中斷過程時(shí),該標(biāo)識為 0。當(dāng)使用 netlink 套接字進(jìn)行通信,通信的雙方都是用戶態(tài)進(jìn)程,則使用方法類似于消息隊(duì)列。但通信雙方有一端是中斷過程,使用方法則不同。netlink 套接字的最大特點(diǎn)是對中斷過程的支持,它在內(nèi)核空間接收用戶空間數(shù)據(jù)時(shí)不再需要用戶自行啟動(dòng)一個(gè)內(nèi)核線程,而是通過另一個(gè)軟中斷調(diào)用用戶事先指定的接收函 數(shù)。
《UNIX Network Programming Volume 1 - 3rd Edition》第18章
講到BSD UNIX系統(tǒng)中routing socket的應(yīng)用,這種套接字是按下面方式生成的:
rt_socket = socket(AF_ROUTE, SOCK_RAW, 0);
然 后就可以用它跟內(nèi)核交互,進(jìn)行網(wǎng)絡(luò)環(huán)境管理的操作,如讀取/設(shè)置/刪除路由表信息,更改網(wǎng)關(guān)等等,但書中所列代碼只在4.3BSD及以后版本的原始 UNIX系統(tǒng)下可用,Linux雖然實(shí)現(xiàn)了AF_ROUTE族套接字,但用法卻完全不同。由于網(wǎng)上這方面知識的資料想對匱乏,現(xiàn)對Linux下 routing socket的使用做一介紹。
由于我現(xiàn)在在Magic Linux1.0下工作,所以以下的講解全部基于2.4.10內(nèi)核。Linux從v2.2開始引入這一機(jī)制,因此可以肯定從v2.2到v2.4的內(nèi)核都是適用的,更新的v2.6我沒有試過。
Linux下雖然也有AF_ROUTE族套接字,但是這個(gè)定義只是個(gè)別名,請看
/usr/include/linux/socket.h, line 145:
#define AF_ROUTE AF_NETLINK /* Alias to emulate 4.4BSD */
可 見在Linux內(nèi)核當(dāng)中真正實(shí)現(xiàn)routing socket的是AF_NETLINK族套接字。AF_NETLINK族套接字像一個(gè)連接用戶空間和內(nèi)核的雙工管道,通過它,用戶進(jìn)程可以修改內(nèi)核運(yùn)行參 數(shù)、讀取和設(shè)置路由信息、控制特定網(wǎng)卡的up/down狀態(tài)等等,可以說是一個(gè)管理網(wǎng)絡(luò)資源的絕佳途徑。
1 生成所需套接字,并綁定一個(gè)sockaddr結(jié)構(gòu)
先來看如何生成一個(gè)AF_NETLINK族套接字:
sockfd = socket(AF_NETLINK, socket_type, netlink_faimly);
這里socket_type可選SOCK_DGRAM或SOCK_RAW;因?yàn)锳F_NETLINK族是面向數(shù)據(jù)報(bào)的套接字,所以不能使用SOCK_STREAM。
netlink_family指定要和內(nèi)核中的哪個(gè)子系統(tǒng)進(jìn)行交互,目前支持:
NETLINK_ROUTE 與路由信息相關(guān),包括查詢、設(shè)置和刪除路由表中的條目等。待會兒我們將以這類family舉個(gè)實(shí)際的例子;
NETLINK_FIREWALL 接收由IPv4防火墻代碼發(fā)送的包;
NETLINK_ARPD 可以在用戶空間進(jìn)行arp緩存的管理;
NETLINK_ROUTE6 在用戶空間發(fā)送和接收路由表信息更新;
還有幾種雖然沒有實(shí)現(xiàn),但已經(jīng)有了定義,為以后擴(kuò)展做好了準(zhǔn)備。
接下來要給該套接字綁定一個(gè)sockaddr結(jié)構(gòu),實(shí)際上是一個(gè)sockaddr_nl結(jié)構(gòu):
struct sockaddr_nl {
sa_family_t nl_family; /*AF_NETLINK*/
unsigned short nl_pad; /* 0 */
pid_t nl_pid; /* 進(jìn)程pid */
u_32 nl_groups; /* 多播組掩碼*/
}nl;
這個(gè)結(jié)構(gòu)一般按照注釋填好就可以了,nl_groups我也不知道怎么用,一般填零了,表示沒有多播。綁定:
bind(sockfd, (struct sockaddr*) &nl, sizeof(nl));
2 填充所需數(shù)據(jù)結(jié)構(gòu),并通過sendmsg()/send()等函數(shù)寫到套接字里去
到 此為止,與內(nèi)核通信的準(zhǔn)備工作就完成了,下面要做的工作是,選取適當(dāng)?shù)臄?shù)據(jù)結(jié)構(gòu)進(jìn)行填充,并作為sendmsg()的參數(shù)發(fā)送出去,并recv()收到的 消息。這個(gè)數(shù)據(jù)結(jié)構(gòu)就是nlmsghdr,它只是一個(gè)信息頭,后面可以接任意長的數(shù)據(jù),這些數(shù)據(jù)實(shí)際上又是針對某一需求所采用的特定數(shù)據(jù)結(jié)構(gòu)。先來看 nlmsghdr:
struct nlmsghdr {
_u32 nlmsg_len; /* Length of msg including header */
_u32 nlmsg_type; /* 操作命令 */
_u16 nlmsg_flags; /* various flags */
_u32 nlmsg_seq; /* Sequence number */
_u32 nlmsg_pid; /* 進(jìn)程PID */
};
/* 緊跟著是實(shí)際要發(fā)送的數(shù)據(jù),長度可以任意 */
nlmsg_type 決定這次要執(zhí)行的操作,如查詢當(dāng)前路由表信息,所使用的就是RTM_GETROUTE。標(biāo)準(zhǔn)nlmsg_type包括:NLMSG_NOOP, NLMSG_DONE, NLMSG_ERROR等。根據(jù)采用的nlmsg_type不同,還要選取不同的數(shù)據(jù)結(jié)構(gòu)來填充到nlmsghdr后面:
操作 數(shù)據(jù)結(jié)構(gòu)
RTM_NEWLINK ifinfomsg
RTM_DELLINK
RTM_GETLINK
RTM_NEWADDR ifaddrmsg
RTM_DELADDR
RTM_GETADDR
RTM_NEWROUTE rtmsg
RTM_DELROUTE
RTM_GETROUTE
RTM_NEWNEIGH ndmsg/nda_chcheinfo
RTM_DELNEIGH
RTM_GETNEIGH
RTM_NEWRULE rtmsg
RTM_DELRULE
RTM_GETRULE
RTM_NEWQDISC tcmsg
RTM_DELQDISC
RTM_GETQDISC
RTM_NEWTCLASS tcmsg
RTM_DELTCLASS
RTM_GETTCLASS
RTM_NEWTFILTER tcmsg
RTM_DELTFILTER
RTM_GETTFILTER
由于情形眾多,我從現(xiàn)在開始將用一個(gè)特定的例子來說明問題。我們的目的是從內(nèi)核讀取IPV4路由表信息。從上面表看,nlmsg_type一定使用RTM_xxxROUTE操作,對應(yīng)的數(shù)據(jù)結(jié)構(gòu)是rtmsg。既然是讀取,那么應(yīng)該是RTM_GETROUTE了。
struct rtmsg {
unsigned char rtm_family; /* 路由表地址族 */
unsigned char rtm_dst_len; /* 目的長度 */
unsigned char rtm_src_len; /* 源長度 */ (2.4.10頭文件的注釋標(biāo)反了?)
unsigned char rtm_tos; /* TOS */
unsigned char rtm_table; /* 路由表選取 */
unsigned char rtm_protocol; /* 路由協(xié)議 */
unsigned char rtm_scope;
unsigned char rtm_type;
unsigned int rtm_flags;
};
對于RTM_GETROUTE操作來說,我們只需指定兩個(gè)成員:rtm_family:AF_INET, rtm_table: RT_TABLE_MAIN。其他成員都初始化為0即可。將這個(gè)結(jié)構(gòu)體跟nlmsghdr結(jié)合起來,得到我們自己的新結(jié)構(gòu)體:
struct {
struct nlmsghdr nl;
struct rtmsg rt;
}req;
填充好rt結(jié)構(gòu)之后,還要調(diào)整nl結(jié)構(gòu)相應(yīng)成員的值。Linux定義了多個(gè)宏來處理nlmsghdr成員的值,我們這里用到的是NLMSG_LENGTH(size_t len);
req.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
這將計(jì)算nlmsghdr長度與rtmsg長度的和(其中包括了將rtmsg進(jìn)行4字節(jié)邊界對齊的調(diào)整),并存儲到nlmsghdr的nlmsg_len成員中。接下來要做的就是將這個(gè)新結(jié)構(gòu)體req放到sendmsg()函數(shù)的msghdr.iov處,并調(diào)用函數(shù)。
sendmsg(sockfd, &msg, 0);
3 接收數(shù)據(jù),并進(jìn)行分析
接下來的操作是recv()操作,從該套接字讀取內(nèi)核返回的數(shù)據(jù),并進(jìn)行分析處理。
recv(sockfd, p, sizeof(buf) - nll, 0);
其中p是指向一個(gè)緩沖區(qū)buf的指針,nll是已接收到的nlmsghdr數(shù)據(jù)的長度。
由于內(nèi)核返回信息是一個(gè)字節(jié)流,需要調(diào)用者檢查消息結(jié)尾。這是通過檢查返回的nlmsghdr的nlmsg_type是否等于NLMSG_DONE來完成的。返回的數(shù)據(jù)格式如下:
-----------------------------------------------------------
| nlmsghdr+route entry | nlmsghdr+route entry | .........
-----------------------------------------------------------
| 解出route entry
V
-----------------------------------------------------------
| dst_addr | gateway | Output interface| ...............
-----------------------------------------------------------
可 以看出,返回消息由多個(gè)(nlmsghdr + route entry)組成,當(dāng)某個(gè)nlmsghdr的nlmsg_type == NLMSG_DONE時(shí)就表示信息輸出已經(jīng)完畢。而每一個(gè)route entry由多個(gè)rtattr結(jié)構(gòu)體組成,每個(gè)結(jié)構(gòu)體表示該路由項(xiàng)的某個(gè)屬性,如目的地址,網(wǎng)關(guān)等等。根據(jù)這個(gè)示意圖我們就能夠輕松解析需要的數(shù)據(jù)了。
原文:
http://www.douban.com/note/29453304/