- 論壇徽章:
- 36
|
三、一個實(shí)現(xiàn)接收內(nèi)核態(tài)發(fā)送的IP Queue數(shù)據(jù)包的用戶態(tài)例程
由于IP Queue是使用Netlink機(jī)制進(jìn)行內(nèi)核態(tài)和用戶態(tài)通信的。因此,用戶態(tài)要接收內(nèi)核態(tài)發(fā)送的IP Queue數(shù)據(jù)包,就需要設(shè)計(jì)相應(yīng)的Netlink程序,也就是設(shè)計(jì)相應(yīng)的基于Netlink的socket程序即可。這里,我不會詳細(xì)介紹如何使用Netlink機(jī)制實(shí)現(xiàn)用戶態(tài)和內(nèi)核態(tài)進(jìn)行通信。我假設(shè)閱讀本文的朋友,已經(jīng)熟悉了Netlink的使用。如果對Netlink的使用還不是很熟悉,那么可以參考獨(dú)孤九賤大俠的文章——《Linux 用戶態(tài)與內(nèi)核態(tài)的交互——netlink 篇》,其鏈接為:
http://linux.chinaunix.net/bbs/thread-822500-1-1.html.
這篇文章提供了一個使用netlink的完整的例程,包括內(nèi)核態(tài)和用戶態(tài)。講的非常清楚,我看完這篇文章,又跑了一下上面提供的例程,基本上熟悉了Netlink的使用方法。
當(dāng)然,如果讀者不想花時間再去了解netlink的話,也可以通過這篇文章熟悉Netlink的使用。因?yàn)槲疫@里提供的是完整的用戶態(tài)例程,我會將源碼完全提供出來,對于急于通過執(zhí)行程序觀察結(jié)果來學(xué)習(xí)Netlink和IP Queue的朋友,也可以通過隨后提供的方法編譯并執(zhí)行程序。
以下講述用戶態(tài)例程接收IP Queue數(shù)據(jù)包的程序設(shè)計(jì)。
其實(shí),由于Netlink程序也是使用socket的方式進(jìn)行通信。那么接收IP Queue報(bào)文的方式應(yīng)該遵循socket的標(biāo)準(zhǔn)流程,具體流程如下:
(1)調(diào)用socket()創(chuàng)建一個地址類型為PF_NETLINK(AF_NETLINK)的套接字。該套接字使用SOCK_RAW方式傳輸數(shù)據(jù),協(xié)議類型為NETLINK_FIREWALL,即使用IP Queue;
(2)調(diào)用bind()將本地地址(Netlink通信雙方使用該協(xié)議特有的地址格式,見下面struct sockaddr_nl)綁定到已建立的套接字上;
struct sockaddr_nl {
sa_family_t nl_family; /* AF_NETLINK */
unsigned short nl_pad; /* Zero. */
pid_t nl_pid; /* Process ID. */
__u32 nl_groups; /* Multicast groups mask. */
};
(3)調(diào)用sendto()發(fā)送相關(guān)的配置信息,告訴內(nèi)核應(yīng)用程序準(zhǔn)備接受的是數(shù)據(jù)包的元數(shù)據(jù),還是同時包括數(shù)據(jù)包本身;
(4)調(diào)用recvfrom()接受內(nèi)核態(tài)發(fā)送來的IP Queue報(bào)文;
(5)調(diào)用close()關(guān)閉套接字,結(jié)束通信。
看了以上流程,我相信很多熟悉socket編程的朋友已經(jīng)可以寫出接收IP Queue報(bào)文的用戶態(tài)程序了。
本文中的示例代碼的實(shí)現(xiàn)整體也是依照上面的步驟。但在細(xì)節(jié)的實(shí)現(xiàn)上,參考了iptables源碼給給出的libipq庫的實(shí)現(xiàn)代碼。libipq庫是iptables中封裝的實(shí)現(xiàn)用戶態(tài)接收和發(fā)送IP Queue報(bào)文操作的,也就相當(dāng)于對上面總結(jié)的IP Queue報(bào)文接受流程進(jìn)行封裝。整個libipq庫分別由libipq.c和libipq.h兩個源文件。我這里將兩個源文件移植(基于iptables-1.3.5版本)到示例代碼中并裁剪,并編寫了測試程序ip_user.c。因此,整個實(shí)現(xiàn)代碼包含三個源文件:ip_user.c、libipq.c和libipq.h。
以下將對三個源文件進(jìn)行分析。
1. libipq.h
該頭文件定義了一個關(guān)鍵的數(shù)據(jù)結(jié)構(gòu),并提供了所有進(jìn)行Netlink通信的API.
數(shù)據(jù)結(jié)構(gòu)的定義如下:
struct ipq_handle
{
int fd;
struct sockaddr_nl local;
struct sockaddr_nl peer;
};
其中,fd是socket通信的描述符,local和peer分別是Netlink通信雙方的地址。
除了定義數(shù)據(jù)結(jié)構(gòu),剩下的主要就是提供給用戶調(diào)用的API,函數(shù)列表如下:
struct ipq_handle *ipq_create_handle(u_int32_t flags, u_int32_t protocol);
int ipq_destroy_handle(struct ipq_handle *h);
ssize_t ipq_read(const struct ipq_handle *h, unsigned char *buf, size_t len);
int ipq_set_mode(const struct ipq_handle *h, u_int8_t mode, size_t len);
ipq_packet_msg_t *ipq_get_packet(const unsigned char *buf);
int ipq_message_type(const unsigned char *buf);
int ipq_get_msgerr(const unsigned char *buf);
int ipq_set_verdict(const struct ipq_handle *h,
ipq_id_t id,
unsigned int verdict,
size_t data_len,
unsigned char *buf);
int ipq_ctl(const struct ipq_handle *h, int request, ...);
char *ipq_errstr(void);
我將在下面libipq.c的講解中對若干我們將要用到的一些函數(shù)進(jìn)行分析。
2. libipq.c
該源文件實(shí)現(xiàn)了libipq.h中定義的所有函數(shù),并定義了一些出錯信息。
(1)ipq_create_handle()函數(shù)申請了一個struct ipq_handle *h結(jié)構(gòu)體,用來存儲隨后創(chuàng)建的IPv4 socket通信的fd,以及通信雙方的地址。本函數(shù)完成了通信雙方地址的初始化,并將本地地址綁定到已生成的fd上。
ipq_create_handle()函數(shù)的源碼如下:
struct ipq_handle *ipq_create_handle()
{
int status;
struct ipq_handle *h;
h = (struct ipq_handle *)malloc(sizeof(struct ipq_handle));
if (h == NULL) {
ipq_errno = IPQ_ERR_HANDLE;
return NULL;
}
memset(h, 0, sizeof(struct ipq_handle));
if (protocol == PF_INET)
h->fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_FIREWALL);
else {
ipq_errno = IPQ_ERR_PROTOCOL;
free(h);
return NULL;
}
if (h->fd == -1) {
ipq_errno = IPQ_ERR_SOCKET;
close(h->fd);
free(h);
return NULL;
}
memset(&h->local, 0, sizeof(struct sockaddr_nl));
h->local.nl_family = AF_NETLINK;
/*傳遞本地的pid*/
h->local.nl_pid = getpid();
h->local.nl_groups = 0;
status = bind(h->fd, (struct sockaddr *)&h->local, sizeof(h->local));
if (status == -1) {
ipq_errno = IPQ_ERR_BIND;
close(h->fd);
free(h);
return NULL;
}
memset(&h->peer, 0, sizeof(struct sockaddr_nl));
h->peer.nl_family = AF_NETLINK;
/*代表通信的另一方為內(nèi)核*/
h->peer.nl_pid = 0;
h->peer.nl_groups = 0;
return h;
}
ipq_destroy_handle()函數(shù)關(guān)閉由ipq_create_handle()建立起來的fd,并釋放申請的內(nèi)存。源碼如下:
int ipq_destroy_handle(struct ipq_handle *h)
{
if (h) {
close(h->fd);
free(h);
}
return 0;
}
(2)向內(nèi)核發(fā)送模式請求的函數(shù)
int ipq_set_mode(const struct ipq_handle *h,
u_int8_t mode, size_t range)
{
/*構(gòu)造一個向內(nèi)核發(fā)送報(bào)文的結(jié)構(gòu)體*/
struct {
struct nlmsghdr nlh;
ipq_peer_msg_t pm;
} req;
memset(&req, 0, sizeof(req));
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req));
req.nlh.nlmsg_flags = NLM_F_REQUEST;
req.nlh.nlmsg_type = IPQM_MODE;
req.nlh.nlmsg_pid = h->local.nl_pid;
/*告訴協(xié)議棧所請求的報(bào)文傳遞模式*/
req.pm.msg.mode.value = mode;
/*請求內(nèi)核返回報(bào)文的長度*/
req.pm.msg.mode.range = range;
return ipq_netlink_sendto(h, (void *)&req, req.nlh.nlmsg_len);
}
在構(gòu)造完向內(nèi)核發(fā)送的結(jié)構(gòu)體req并設(shè)置相關(guān)內(nèi)容之后,調(diào)用ipq_netlink_sendto函數(shù)發(fā)送用戶態(tài)的請求數(shù)據(jù),該函數(shù)代碼如下:
static ssize_t ipq_netlink_sendto(const struct ipq_handle *h,
const void *msg, size_t len)
{
int status = sendto(h->fd, msg, len, 0,
(struct sockaddr *)&h->peer, sizeof(h->peer));
if (status < 0)
ipq_errno = IPQ_ERR_SEND;
return status;
}
ipq_netlink_sendto函數(shù)直接調(diào)用了sendto系統(tǒng)調(diào)用發(fā)送用戶態(tài)的數(shù)據(jù),返回的是發(fā)送出去的數(shù)據(jù)長度。當(dāng)sendto調(diào)用失敗時,對全局變量ipq_errno 賦值IPQ_ERR_SEND。這樣方便以后用專門返回出錯信息的函數(shù)引用。
(3)用戶態(tài)發(fā)送了請求數(shù)據(jù)包之后,就處于等待接收內(nèi)核返回?cái)?shù)據(jù)包的狀態(tài)。一旦內(nèi)核NF得到包處理函數(shù)返回NF_QUEUE時,該包就會被ip_queue模塊發(fā)送到用戶態(tài)。用戶態(tài)接收IP Queue數(shù)據(jù)包的函數(shù)為:
ssize_t ipq_read(const struct ipq_handle *h, unsigned char *buf, size_t len)
該函數(shù)的代碼如下。其中buf存儲來自內(nèi)核態(tài)的數(shù)據(jù)包,len為buf的長度。
ssize_t ipq_read(const struct ipq_handle *h,
unsigned char *buf, size_t len)
{
return ipq_netlink_recvfrom(h, buf, len);
}
該函數(shù)直接調(diào)用ipq_netlink_recvfrom()函數(shù),其源碼為:
static ssize_t ipq_netlink_recvfrom(const struct ipq_handle *h,
unsigned char *buf, size_t len)
{
unsigned int addrlen;
int status;
struct nlmsghdr *nlh;
/*buf長度的校驗(yàn),不能小于Netlink Message的頭部長度*/
if (len < sizeof(struct nlmsgerr)) {
ipq_errno = IPQ_ERR_RECVBUF;
return -1;
}
addrlen = sizeof(h->peer);
status = recvfrom(h->fd, buf, len, 0,
(struct sockaddr *)&h->peer, &addrlen);
if (status < 0) {
ipq_errno = IPQ_ERR_RECV;
return status;
}
/*判斷接收到的發(fā)送方的地址長度是否正確*/
if (addrlen != sizeof(h->peer)) {
ipq_errno = IPQ_ERR_RECV;
return -1;
}
/*內(nèi)核態(tài)向用戶態(tài)發(fā)送數(shù)據(jù)報(bào)文時,其pid=0*/
if (h->peer.nl_pid != 0) {
ipq_errno = IPQ_ERR_RECV;
return -1;
}
if (status == 0) {
ipq_errno = IPQ_ERR_NLEOF;
return -1;
}
nlh = (struct nlmsghdr *)buf;
/*判斷是否發(fā)生數(shù)據(jù)報(bào)文被截?cái)嗟那闆r*/
if (nlh->nlmsg_flags & MSG_TRUNC || nlh->nlmsg_len > status) {
ipq_errno = IPQ_ERR_RTRUNC;
return -1;
}
return status;
}
該函數(shù)返回讀取到報(bào)文的實(shí)際長度。
至此,我們已經(jīng)可以通過上面幾個函數(shù)實(shí)現(xiàn)從內(nèi)核態(tài)接收到既定模式的IP Queue報(bào)文。
(4)輸出出錯信息
char *ipq_errstr(void)
{
return ipq_strerror(ipq_errno);
}
static char *ipq_strerror(int errcode)
{
if (errcode < 0 || errcode > IPQ_MAXERR)
errcode = IPQ_ERR_IMPL;
return ipq_errmap[errcode].message;
}
根據(jù)函數(shù)執(zhí)行過程中記錄的出錯信息,打印對相關(guān)出錯的具體提示。
[ 本帖最后由 Godbach 于 2008-12-3 19:54 編輯 ] |
|