jasmine214--love

          只有當你的內心總是充滿快樂、美好的愿望和寧靜時,你才能擁有強壯的體魄和明朗、快樂或者寧靜的面容。
          posts - 731, comments - 60, trackbacks - 0, articles - 0
          Linux 用戶態與內核態的交互
            在 Linux 2.4 版以后版本的內核中,幾乎全部的中斷過程與用戶態進程的通信都是使用 netlink 套接字實現的,例如iprote2網絡管理工具,它與內核的交互就全部使用了netlink,著名的內核包過濾框架Netfilter在與用戶空間的通 讀,也在最新版本中改變為netlink,無疑,它將是Linux用戶態與內核態交流的主要方法之一。它的通信依據是一個對應于進程的標識,一般定為該進 程的 ID。當通信的一端處于中斷過程時,該標識為 0。當使用 netlink 套接字進行通信,通信的雙方都是用戶態進程,則使用方法類似于消息隊列。但通信雙方有一端是中斷過程,使用方法則不同。netlink 套接字的最大特點是對中斷過程的支持,它在內核空間接收用戶空間數據時不再需要用戶自行啟動一個內核線程,而是通過另一個軟中斷調用用戶事先指定的接收函 數。
          《UNIX Network Programming Volume 1 - 3rd Edition》第18章
          講到BSD UNIX系統中routing socket的應用,這種套接字是按下面方式生成的:
          rt_socket = socket(AF_ROUTE, SOCK_RAW, 0);
          然 后就可以用它跟內核交互,進行網絡環境管理的操作,如讀取/設置/刪除路由表信息,更改網關等等,但書中所列代碼只在4.3BSD及以后版本的原始 UNIX系統下可用,Linux雖然實現了AF_ROUTE族套接字,但用法卻完全不同。由于網上這方面知識的資料想對匱乏,現對Linux下 routing socket的使用做一介紹。
          由于我現在在Magic Linux1.0下工作,所以以下的講解全部基于2.4.10內核。Linux從v2.2開始引入這一機制,因此可以肯定從v2.2到v2.4的內核都是適用的,更新的v2.6我沒有試過。

          Linux下雖然也有AF_ROUTE族套接字,但是這個定義只是個別名,請看
          /usr/include/linux/socket.h, line 145:
          #define AF_ROUTE AF_NETLINK /* Alias to emulate 4.4BSD */
          可 見在Linux內核當中真正實現routing socket的是AF_NETLINK族套接字。AF_NETLINK族套接字像一個連接用戶空間和內核的雙工管道,通過它,用戶進程可以修改內核運行參 數、讀取和設置路由信息、控制特定網卡的up/down狀態等等,可以說是一個管理網絡資源的絕佳途徑。

          1 生成所需套接字,并綁定一個sockaddr結構

          先來看如何生成一個AF_NETLINK族套接字:
          sockfd = socket(AF_NETLINK, socket_type, netlink_faimly);
          這里socket_type可選SOCK_DGRAM或SOCK_RAW;因為AF_NETLINK族是面向數據報的套接字,所以不能使用SOCK_STREAM。
          netlink_family指定要和內核中的哪個子系統進行交互,目前支持:
          NETLINK_ROUTE 與路由信息相關,包括查詢、設置和刪除路由表中的條目等。待會兒我們將以這類family舉個實際的例子;
          NETLINK_FIREWALL 接收由IPv4防火墻代碼發送的包;
          NETLINK_ARPD 可以在用戶空間進行arp緩存的管理;
          NETLINK_ROUTE6 在用戶空間發送和接收路由表信息更新;
          還有幾種雖然沒有實現,但已經有了定義,為以后擴展做好了準備。

          接下來要給該套接字綁定一個sockaddr結構,實際上是一個sockaddr_nl結構:
          struct sockaddr_nl {
          sa_family_t nl_family; /*AF_NETLINK*/
          unsigned short nl_pad; /* 0 */
          pid_t nl_pid; /* 進程pid */
          u_32 nl_groups; /* 多播組掩碼*/
          }nl;
          這個結構一般按照注釋填好就可以了,nl_groups我也不知道怎么用,一般填零了,表示沒有多播。綁定:
          bind(sockfd, (struct sockaddr*) &nl, sizeof(nl));

          2 填充所需數據結構,并通過sendmsg()/send()等函數寫到套接字里去

          到 此為止,與內核通信的準備工作就完成了,下面要做的工作是,選取適當的數據結構進行填充,并作為sendmsg()的參數發送出去,并recv()收到的 消息。這個數據結構就是nlmsghdr,它只是一個信息頭,后面可以接任意長的數據,這些數據實際上又是針對某一需求所采用的特定數據結構。先來看 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; /* 進程PID */
          };
          /* 緊跟著是實際要發送的數據,長度可以任意 */

          nlmsg_type 決定這次要執行的操作,如查詢當前路由表信息,所使用的就是RTM_GETROUTE。標準nlmsg_type包括:NLMSG_NOOP, NLMSG_DONE, NLMSG_ERROR等。根據采用的nlmsg_type不同,還要選取不同的數據結構來填充到nlmsghdr后面:
          操作 數據結構
          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
          由于情形眾多,我從現在開始將用一個特定的例子來說明問題。我們的目的是從內核讀取IPV4路由表信息。從上面表看,nlmsg_type一定使用RTM_xxxROUTE操作,對應的數據結構是rtmsg。既然是讀取,那么應該是RTM_GETROUTE了。
          struct rtmsg {
          unsigned char rtm_family; /* 路由表地址族 */
          unsigned char rtm_dst_len; /* 目的長度 */
          unsigned char rtm_src_len; /* 源長度 */ (2.4.10頭文件的注釋標反了?)
          unsigned char rtm_tos; /* TOS */

          unsigned char rtm_table; /* 路由表選取 */
          unsigned char rtm_protocol; /* 路由協議 */
          unsigned char rtm_scope;
          unsigned char rtm_type;

          unsigned int rtm_flags;
          };
          對于RTM_GETROUTE操作來說,我們只需指定兩個成員:rtm_family:AF_INET, rtm_table: RT_TABLE_MAIN。其他成員都初始化為0即可。將這個結構體跟nlmsghdr結合起來,得到我們自己的新結構體:
          struct {
          struct nlmsghdr nl;
          struct rtmsg rt;
          }req;
          填充好rt結構之后,還要調整nl結構相應成員的值。Linux定義了多個宏來處理nlmsghdr成員的值,我們這里用到的是NLMSG_LENGTH(size_t len);
          req.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
          這將計算nlmsghdr長度與rtmsg長度的和(其中包括了將rtmsg進行4字節邊界對齊的調整),并存儲到nlmsghdr的nlmsg_len成員中。接下來要做的就是將這個新結構體req放到sendmsg()函數的msghdr.iov處,并調用函數。
          sendmsg(sockfd, &msg, 0);

          3 接收數據,并進行分析

          接下來的操作是recv()操作,從該套接字讀取內核返回的數據,并進行分析處理。
          recv(sockfd, p, sizeof(buf) - nll, 0);
          其中p是指向一個緩沖區buf的指針,nll是已接收到的nlmsghdr數據的長度。
          由于內核返回信息是一個字節流,需要調用者檢查消息結尾。這是通過檢查返回的nlmsghdr的nlmsg_type是否等于NLMSG_DONE來完成的。返回的數據格式如下:
          -----------------------------------------------------------
          | nlmsghdr+route entry | nlmsghdr+route entry | .........
          -----------------------------------------------------------
          | 解出route entry
          V
          -----------------------------------------------------------
          | dst_addr | gateway | Output interface| ...............
          -----------------------------------------------------------
          可 以看出,返回消息由多個(nlmsghdr + route entry)組成,當某個nlmsghdr的nlmsg_type == NLMSG_DONE時就表示信息輸出已經完畢。而每一個route entry由多個rtattr結構體組成,每個結構體表示該路由項的某個屬性,如目的地址,網關等等。根據這個示意圖我們就能夠輕松解析需要的數據了。

          原文:
          http://www.douban.com/note/29453304/
          主站蜘蛛池模板: 泰来县| 揭西县| 双桥区| 沙坪坝区| 米易县| 东至县| 新密市| 宣汉县| 连山| 玉山县| 从化市| 永平县| 周至县| 抚松县| 敖汉旗| 滁州市| 巴彦县| 吕梁市| 白河县| 贵州省| 亳州市| 军事| 增城市| 虹口区| 平安县| 即墨市| 汽车| 浦县| 航空| 金湖县| 通江县| 麻栗坡县| 安徽省| 镇赉县| 大田县| 霍山县| 贵溪市| 南靖县| 五河县| 浦江县| 邹城市|