Linux内核在Netfilter框架的基础上提供了IP Queue机制,从而使得基于用户态的防火墙开发成为可能。从而可以在用户态对报文内容进行分析,同时可以给出对这个报文的处理意见,也可以修改报文。
简单介绍一下NF中各个钩子(hook)函数对数据包处理的返回值,即该函数告诉内核对该数据包的处理意见。
NF_DROP: 丢弃该报文,释放所有与该报文相关的资源;
NF_ACCEPT: 接受该报文,并继续处理;
NF_STOLEN: 该报文已经被HOOK函数接管,协议栈无须继续处理;
NF_QUEUE: 将该报文传递到用户态去做进一步的处理;
NF_REPEAT: 再次调用本HOOK函数。
当HOOK处理函数返回值为NF_QUEUE时,内核协议栈将通过IP Queue机制把当前报文传递到用户态,由用户态的应用程序进行处理。这样,只要能够在相应的HOOK点上返回NF_QUEUE值,就可以将符合要求的报文传送到用户态去做进一步对报文行处理。随后,用户态程序会将处理后的报文以及对报文的处理意见(ACCEPT,DROP等)传递给内核协议栈。内核协议栈就会按照用户态对报文的处理意见将报文做接受、丢弃等处理。整个处理的过程就相当于一个用户态的防火墙,所有数据包的实质性处理都放在用户态进行。这样,即使是不具有深入内核知识的开发人员,也可以开发出适应一定应用场合的用户态防火墙。
基于以上的介绍可以大致讲一下应用层编程实现防火墙的流程:内核和应用层之间的通信以及报文的传输采用netlink套接字来实现。用户态可以通过IPTABLES来决定哪一类报文将要发送到应用层(采用iptables的QUEUE任务,如iptables -t mangle -A FORWARD -j QUEUE,则所有匹配到这条规则的报文都将通过IP_QUEUE发送到用户空间),而应用层通过对报文的分析和处理最终返回一个处理意见给内核,返回的值同上面的hook函数返回值一样,可以是其中的一种。
下面是一个简单的应用层获获取内核报文的实现:
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <errno.h>
- #include <sys/time.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <asm/types.h>
- #include <sys/socket.h>
- #include <linux/netlink.h>
-
- #include<linux/netfilter_ipv4/ip_queue.h>
-
-
- #define VAL_LEN 1024
- #define RS_OK 0
- #define RS_WRONG -1
-
- typedef unsigned int u32;
-
-
-
- enum {
- SET_MODE = 0,
- RETURN_IDE
- };
-
-
-
-
- enum {
- NF_DROP = 0,
- NF_ACCEPT,
- NF_STOLEN,
- NF_QUEUE,
- NF_REPEAT,
- NF_STOP,
- NF_MAX_VERDICT,
- };
-
- typedef struct ipq_hand {
- int ipqfd;
- struct sockaddr_nl local, peer;
- }ipqhander;
-
-
-
-
- struct req_head {
-
- struct nlmsghdr nlh;
-
- ipq_peer_msg_t reqh;
- };
-
- struct ipq_hand *ipqh;
-
-
- int create_ipqueue_hander()
- {
- ipqh = (struct ipq_hand *)malloc(sizeof(struct ipq_hand));
- if (NULL == ipqh) {
- printf ("[%s:%d]malloc ipq hander failed, error is %s\n", __FUNCTION__, __LINE__, strerror(errno));
- return RS_WRONG;
- }
-
- memset(ipqh, 0, sizeof(struct ipq_hand));
- ipqh->ipqfd = socket(PF_NETLINK, SOCK_RAW, NETLINK_FIREWALL);
- if (ipqh->ipqfd == -1) {
- printf ("[%s:%d]create ip_queue socket failed, error is %s\n", __FUNCTION__, __LINE__, strerror(errno));
- return RS_WRONG;
- }
-
- memset(&ipqh->local,0, sizeof(ipqh->local));
- ipqh->local.nl_family = AF_NETLINK;
- ipqh->local.nl_pid = getpid();
- ipqh->local.nl_groups = 0;
-
- memset(&ipqh->peer, 0, sizeof(ipqh->peer));
- ipqh->peer.nl_family = AF_NETLINK;
- ipqh->peer.nl_pid = 0;
- ipqh->peer.nl_groups = 0;
-
- if(bind(ipqh->ipqfd, (struct sockaddr *)&(ipqh->local), sizeof (ipqh->local))) {
- printf ("[%s:%d]bind ip_queue socket failed, error is %s\n", __FUNCTION__, __LINE__, strerror(errno));
- return RS_WRONG;
- }
-
- return RS_OK;
- }
-
-
- int send_msg_to_kernel(u32 msgtype, ipq_packet_msg_t *m, u32 verdict)
- {
- int ret;
- struct req_head req;
-
- req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof (req));
- req.nlh.nlmsg_flags = NLM_F_REQUEST;
- req.nlh.nlmsg_pid = getpid();
-
- switch(msgtype) {
- case SET_MODE:
- printf ("now set the mode\n");
- req.nlh.nlmsg_type = IPQM_MODE;
- req.reqh.msg.mode.value = IPQ_COPY_PACKET;
- req.reqh.msg.mode.range = VAL_LEN;
- break;
- case RETURN_IDE:
- req.nlh.nlmsg_type = IPQM_VERDICT;
- req.reqh.msg.verdict.value = verdict;
- req.reqh.msg.verdict.id = m->packet_id;
- req.reqh.msg.verdict.data_len = 0;
- break;
- default:
- break;
- }
-
- ret = sendto(ipqh->ipqfd, &req, sizeof(req), 0, (struct sockaddr *)&ipqh->peer, sizeof(ipqh->peer));
- if (ret == -1) {
- printf ("[%s:%d]send ipq msg failed, error is %s\n", __FUNCTION__, __LINE__, strerror(errno));
- return RS_WRONG;
- }
-
- return RS_OK;
- }
-
- int recv_msg_from_kernel()
- {
- int ret, status;
- char buf[VAL_LEN];
- struct nlmsghdr *nlh;
- ipq_packet_msg_t *packet;
- fd_set read_fds;
- FD_ZERO(&read_fds);
- FD_SET(ipqh->ipqfd, &read_fds);
-
- while (1) {
- ret = select(ipqh->ipqfd +1, &read_fds, NULL, NULL, NULL);
- if (ret < 0) {
- if (errno == EINTR) {
- continue;
- } else {
- printf ("[%s:%d]recvfrom msg failed, error is %s\n", __FUNCTION__, __LINE__, strerror(errno));
- continue;
- }
- }
-
- if (!FD_ISSET(ipqh->ipqfd, &read_fds))
- continue;
- memset(buf, 0, sizeof(buf));
- status = recvfrom(ipqh->ipqfd, buf, VAL_LEN, 0, NULL, NULL);
- if (status < 0) {
- printf ("[%s:%d]recvfrom msg failed, error is %s\n", __FUNCTION__, __LINE__, strerror(errno));
- continue;
- }
-
- if (status == 0) {
- printf ("[%s:%d]recv NULL msg\n", __FUNCTION__, __LINE__);
- continue;
- }
- nlh = (struct nlmsghdr *)buf;
- if (nlh->nlmsg_flags & MSG_TRUNC || nlh->nlmsg_len > status) {
- continue;
- }
-
- packet = NLMSG_DATA((struct nlmsghdr *)(buf));
- printf("recv bytes =%d, nlmsg_len=%d, indev=%s, datalen=%d, packet_id=%x\n", status, nlh->nlmsg_len,
- packet->indev_name, packet->data_len, (u32)packet->packet_id);
- if (send_msg_to_kernel(RETURN_IDE, packet, NF_ACCEPT)) {
- printf ("[%s:%d]send_msg_to_kernel error\n", __FUNCTION__, __LINE__);
- continue;
- }
- }
- return RS_OK;
- }
-
- int main(int argc, char *argv[])
- {
- if (create_ipqueue_hander()) {
- printf ("[%s:%d]create_ipqueue_hander error\n", __FUNCTION__, __LINE__);
- return RS_WRONG;
- }
-
- if (send_msg_to_kernel(SET_MODE, NULL, 0)) {
- printf ("[%s:%d]send_msg_to_kernel error\n", __FUNCTION__, __LINE__);
- return RS_WRONG;
- }
- recv_msg_from_kernel();
-
- return 0;
- }