简介
Linux内核在Netfilter框架的基础上提供了IP Queue机制,从而使得基于用户态的防火墙开发成为可能。从而可以在用户态对报文内容进行分析,同时可以给出对这个报文的处理意见,也可以修改报文。
libipq是基于netfilter提供的接口进行二次封装的库,目的是让大家能够更多专注于用户态的数据处理,简化用户态和内核态数据交换流程。
环境依赖
需要安装libnetfilter_queue,libnetfilter_queue-devel,iptables,iptables-devel
yum install -y libnetfilter_queue libnetfilter_queue-devel iptables iptables-devel
头文件
#include <linux/netfilter.h>
#include <libipq.h>
示例
ipq_test.c
/*
* * This code is GPL.
* */
#include <linux/netfilter.h>
#include <libipq.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFSIZE 2048
static void die(struct ipq_handle *h)
{
ipq_perror("passer");
ipq_destroy_handle(h);
exit(1);
}
int main(int argc, char **argv)
{
int status;
unsigned char buf[BUFSIZE];
struct ipq_handle *h;
// 创建handle,用于数据传输,NFPROTO_IPV4指ipv4的协议
h = ipq_create_handle(0, NFPROTO_IPV4);
if (!h)
die(h);
// 设置传递元数据和原始报文到用户态
status = ipq_set_mode(h, IPQ_COPY_PACKET, BUFSIZE);
if (status < 0)
die(h);
do{
// 读取数据包
status = ipq_read(h, buf, BUFSIZE, 0);
if (status < 0)
die(h);
// 判断数据包类型,类型IPQM_PACKET代表通过queue传过来的数据包
switch (ipq_message_type(buf)) {
case NLMSG_ERROR:
fprintf(stderr, "Received error message %d\n",
ipq_get_msgerr(buf));
break;
case IPQM_PACKET:
{
// 获取数据包,包含元数据(包摘要)和原始数据包
ipq_packet_msg_t *m = ipq_get_packet(buf);
/* 将处理结果发到内核,内核根据结果进行处理
* NF_ACCEPT表示接收数据
*/
status = ipq_set_verdict(h, m->packet_id,
NF_ACCEPT, 0, NULL);
if (status < 0)
die(h);
break;
}
default:
fprintf(stderr, "Unknown message type!\n");
break;
}
} while (1);
// 释放资源
ipq_destroy_handle(h);
return 0;
}
- ipq_create_handle(0, NFPROTO_IPV6)
创建handle, 第1个参数目前没有用到,直接设置为0。第2个参数表示要接收的数据包类型,主要有下面几种:- NFPROTO_UNSPEC = 0,
- NFPROTO_IPV4 = 2,
- NFPROTO_ARP = 3,
- NFPROTO_BRIDGE = 7,
- NFPROTO_IPV6 = 10,
- NFPROTO_DECNET = 12,
- ipq_set_mode(h, IPQ_COPY_PACKET, BUFSIZE)
设置队列模式,模式有下面几种:- IPQ_COPY_NONE, 报文将被丢弃
- IPQ_COPY_META, 内核将在其后的报文传递中只传递“报文的元数据”
- IPQ_COPY_PACKET 内核将同时传递“报文的元数据”和报文本身,报文本身的传递长度由BUFSIZE指定。 BUFSIZE的最大值不能超过IP报文的最大长度,也就是0xFFFF。如果请求的长度大于报文自身的长度,将会按照报文自身长度进行传递。
- ipq_read(h, buf, BUFSIZE, 0)
通过h读取queue中的数据到buf指向的内存中,最大长度为BUFSIZE,第4个参数表示超时时间,单位是微秒,0表示不超时。 - ipq_message_type(buf)
判断数据包类型,返回IPQM_PACKET表示内核传到queue中的数据。 - ipq_get_packet(buf)
从buf中获取数据包,这里返回的是ipq_packet_msg_t结构
typedef struct ipq_packet_msg {
unsigned long packet_id; /* 数据包id */
unsigned long mark; /* Netfilter mark value */
long timestamp_sec; /* 收包时间 (秒) */
long timestamp_usec; /* 收包时间 (微秒) */
unsigned int hook; /* 钩子在netfilter上挂载的位置 */
char indev_name[IFNAMSIZ]; /* Name of incoming interface */
char outdev_name[IFNAMSIZ]; /* Name of outgoing interface */
__be16 hw_protocol; /* Hardware protocol (network order) */
unsigned short hw_type; /* Hardware type */
unsigned char hw_addrlen; /* Hardware address length */
unsigned char hw_addr[8]; /* Hardware address */
size_t data_len; /* Length of packet data */
unsigned char payload[0]; /* 这里指向原始数据包 */
} ipq_packet_msg_t;
- ipq_set_verdict(h, m->packet_id, NF_ACCEPT, 0, NULL)
发送处理结果给内核,主要有如下几种:
NF_DROP: 丢弃该报文,释放所有与该报文相关的资源;
NF_ACCEPT: 接受该报文,并继续处理;
NF_STOLEN: 该报文已经被HOOK函数接管,协议栈无须继续处理;
NF_QUEUE: 将该报文传递到用户态去做进一步的处理;
NF_REPEAT: 再次调用本HOOK函数。
NF_STOP : 与NF_ACCEPT类似但强于NF_ACCEPT,一旦挂接链表中某个hook节点返回NF_STOP,该skb包就立即结束检查而接受,不再进入链表中后续的hook节点,而NF_ACCEPT则还需要进入后续hook点检查。 - ipq_destroy_handle(h)
释放handle资源。
测试
编译
gcc ipq_test.c -o ipqtest -lipq
导入模块
需要导入iptable_filter和ip_queue模块
modprobe iptable_filter
modprobe ip_queue
配置iptables规则
在用户态对数据包进行处理,需要配置iptables规则,将符合条件的数据包转到netfilter提供的queue中。
iptables -A INPUT -p icmp -j QUEUE
如果是ipv6的数据包,需要使用ip6tables来配置规则
ping本机loopback地址
ping 127.0.0.1
此时没有数据包回复
启动ipqtest程序
./ipqtest
此时ping包能够收到回复数据