Linux的Netfilter框架注册Hook函数返回NF_QUEUE时用户态怎么接收
本帖最后由 千夜瞬 于 2021-4-28 10:10 编辑楼主想要在Linux的Netfilter框架中注册一个Hook函数
Hook函数想要实现的功能是:将符合条件的数据包返回给用户态进行处理,也就是
return NF_QUEUE;
目前的Hook函数代码为
#include <linux/time.h>
#include <linux/init.h>
#include <linux/netfilter.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/netfilter_ipv4.h>
#include <linux/net.h>
#include <net/ip.h>
#include <linux/if_ether.h>
#include <net/protocol.h>
#include <net/icmp.h>
#include <net/tcp.h>
#include <linux/if_vlan.h>
#include <linux/netfilter_bridge.h>
unsigned int my_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state){
struct iphdr *iph=ip_hdr(skb);
/* struct tcphdr *tcph;
struct udphdr *udph; */
struct icmphdr *icmph;
int header=0;
int length = 0;
unsigned char *data=NULL;
if(likely(iph->protocol==IPPROTO_ICMP))
{
icmph=icmp_hdr(skb);
data=skb->data+iph->ihl*4+sizeof(struct icmphdr);
header=iph->ihl*4+sizeof(struct icmphdr);
length=ntohs(iph->tot_len)-iph->ihl*4-sizeof(struct icmphdr);
printk("loss packet header = %d,length = %d",header,length);
return NF_QUEUET;
}
return NF_ACCEPT;
}
static struct nf_hook_ops nfho = {
.hook = my_func, //hook处理函数
.pf = PF_INET, //协议类型
.hooknum = NF_INET_POST_ROUTING, //hook注册点
.priority = NF_IP_PRI_FIRST, //优先级
};
static int __init http_init(void){
if (nf_register_net_hook(&init_net,&nfho)) {
printk(KERN_ERR"nf_register_hook() failed\n");
return -1;
}
return 0;
}
static void __exit http_exit(void)
{
nf_unregister_net_hook(&init_net,&nfho);
}
module_init(http_init);
module_exit(http_exit);
MODULE_AUTHOR("AFCC_");
MODULE_LICENSE("GPL");
现在内核态的Hook函数注册基本实现,但是用户态如何接收数据包现在还是一窍不通
原本用iptables命令行插入规则时,会有一个queue_num,通过queue_num从队列中取数据包
插规则的命令行是:
iptables -I INPUT -j NFQUEUE --queue-num 8008
用户空间处理数据包的代码是:
#include "nfqueue.h"
//定义winsize 结构体变量, 获取当前终端的大小
struct winsize size;
#define ENCLENBASE 16
extern unsigned char keyDataUnion;//extern:已经定义的外部变量
extern unsigned char ivData;
//如果当前的宏__LITTLE_ENDIAN已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译。
//#define IPQUAD(addr) :带参数的宏
#ifdef __LITTLE_ENDIAN
#define IPQUAD(addr) \
((unsigned char *)&addr), \
((unsigned char *)&addr), \
((unsigned char *)&addr), \
((unsigned char *)&addr)
#else
#define IPQUAD(addr) \
((unsigned char *)&addr), \
((unsigned char *)&addr), \
((unsigned char *)&addr), \
((unsigned char *)&addr)
#endif
/**
* u_int8_t:__uint8_t>unsigned char,无符号字符型
* u_int32_t: __uint32_t》 unsigned int 无符号int型
* char* 或者 char[]去定义一个字符串
* char *str1 = "abcd1234";
char str2[] = "abcd1234";
*/
void show_data(u_int8_t *data, u_int32_t data_size)
{
u_int32_t i = 0;//unsigned int
//typedef __uint32_t > u_int32
//typedef unsigned int >__uint32_t
for(i=0; i<data_size; ++i)
printf("%02X", data);
//以16进制的格式输出整数类型的数值,输出域宽为2,右对齐,不足的用字符0替代。
printf("\n");
}
// 回调函数定义, 基本结构是先处理包,然后返回裁定
/**
* 回调函数:解析数据包并根据业务逻辑做出裁决
*/
static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data)
{
u_int32_t id = 0, i = 0;
u_int8_t* payload = NULL;
unsigned char *pdata = NULL;
int pdataLen = 0, length = 0;
struct nfqnl_msg_packet_hw *hwph = NULL; // nfqueue netlink消息数据包头硬件部分,MAC地址
struct nfqnl_msg_packet_hdr *ph = NULL;// nfqueue netlink消息数据包头
struct tcphdr *tcphdrp = NULL;
struct udphdr *udphdrp = NULL;
struct iphdr *iphdrp = NULL;
//表示一个完整数据包的各种头,我们常用到的iphdr,tcphdr,udphdr,icmpdr
uint16_t sport = 0, dport = 0;
char src_mac;
unsigned char tmp = {0};
// 提取数据包头信息,包括id,协议和hook点信息
ph = nfq_get_msg_packet_hdr(nfa);
if (ph)
id = ntohl(ph->packet_id);//将一个无符号长整形数从网络字节顺序转换为主机字节顺序。
// 获取数据包载荷,data指针指向载荷,从实际的IP头开始
pdataLen = nfq_get_payload(nfa, ((unsigned char**)&pdata)); //ip头+tcp头+数据的报文的长度
if(pdataLen == -1)
pdataLen = 0;
iphdrp = (struct iphdr *)pdata;
// 获取到终端的大小数据
ioctl(STDIN_FILENO, TIOCGWINSZ, &size);
// 打印出和终端大小一样的框框
for(i = 0; i < size.ws_col; i++)
printf("*");
printf("packet: %u, packetN:%u , protocol = %u\n", id, ph->packet_id, iphdrp->protocol);
//%u 无符号10进制整数
if(iphdrp->protocol == IPPROTO_TCP)
{
/**
* iphdrp->ihl;IP头部的长度,表示有32位长度多少个,在计算ip头部的长度时,通常用ipiph->ihl*4,或(iphdrp->ihl<<2)
* iphdrp->tot_len;整个ip报文的长度,减去ip头部的长度,就是ip包的数据部分了,在使用过程中需要注意网络字节序的问题。
* https://blog.csdn.net/jcw321/article/details/102530957
* https://blog.csdn.net/qq_35493457/article/details/80588381
*/
tcphdrp = (struct tcphdr*)((u_int8_t*)iphdrp + (iphdrp->ihl<<2));
//ip报文的指针移动ip报文首部的长度,指向的就是tcp报文的首部
length = pdataLen - (iphdrp->ihl<<2) - (tcphdrp->doff<<2);
//ip报文的长度减去ip首部长度,减去tcp首部长度,就是payload数据长度
payload = (u_int8_t*)( (u_int8_t*)tcphdrp + (tcphdrp->doff<<2) );
//指向tcp数据部分
sport = tcphdrp->source;
dport = tcphdrp->dest;
printf("len = %d\n", length);
printf("二进制源码是:");
for(int i=0;i<length;i++){
printf("%02x,",payload);
}
/* 修改數據 */
for(i = 0; i < (length > 1024 ? 1024 : length); i++)
tmp = payload;//将数据部分同意置为发送过来的数据的最后一位
memcpy(payload, tmp, length > 1024 ? 1024 : length);
/**
* void * memcpy ( void * dest, const void * src, size_t num );
* 复制 src 所指的内存内容的前 num 个字节到 dest 所指的内存地址上。
*/
}
else if(iphdrp->protocol == IPPROTO_UDP)
{
udphdrp = (struct udphdr*)((u_int8_t*)iphdrp + (iphdrp->ihl<<2));
length = pdataLen - (iphdrp->ihl<<2) - 8;
payload = (u_int8_t*)((u_int8_t*)iphdrp + (iphdrp->ihl<<2) + 8);
sport = udphdrp->source;
dport = udphdrp->dest;
}
printf("line = %d, sport = %d, dport = %d\n", __LINE__, sport, dport);
//__LINE__用以指示本行语句在源文件中的位置信息,也就是行数
/* 如果對數據報進行了修改,那麼必須進行校驗 */
set_ip_checksum(iphdrp);
if(iphdrp->protocol == IPPROTO_TCP)
set_tcp_checksum1(iphdrp);
if(iphdrp->protocol == IPPROTO_UDP)
set_udp_checksum1(iphdrp);
/* 打印出和终端大小一样的框框 */
for(i = 0; i < size.ws_col; i++)
printf("*");
// 设置裁定
return nfq_set_verdict(qh, id, NF_ACCEPT, (u_int32_t)pdataLen, pdata);
//用户态进程做出裁决后,调用nfq_set_verdict()通知内核,内核根据裁决继续处理数据包。
}
int nfq_create_queue_run(int queue_num)
{
struct nfq_handle *h;
struct nfq_q_handle *qh;
struct nfnl_handle *nh;
int fd;
int rv;
char buf;
/**
* assert:断言
* assert(expr):如果断言(expr)非真,程序停止,标准错误输出一条具体的错误信息
* 也可以通过define定义宏
*/
// 打开nfq_handle
assert((h = nfq_open()) != NULL);
// 先解开和AF_INET的绑定
assert(nfq_unbind_pf(h, AF_INET) == 0);
// 绑定到AF_INET
assert(nfq_bind_pf(h, AF_INET) == 0);
/**
* int nfq_bind_pf(struct nfq_handle *h, u_int16_t pf)
* h:nfq_open()返回struct nfq_handle结构的指针;
pf: 协议族 AF_BRIDGE:7多协议桥,
AF_INET:2 路由模式
*/
// 建立nfq_q_handle, 号码是 80 , 回调函数是cb
// 可建立多个queue,用不同的号码区分即可
// 此处的号码 nfq_create_queue 的第 2 个参数 80 与后来的设置 iptables 的那个队列是需要一一对应的
assert((qh = nfq_create_queue(h, queue_num, &cb, NULL)) != NULL);
/**
* nfq_create_queue:将此套接字绑定到特定的队列号.
* h : nfq_open()返回struct nfq_handle结构的指针;
num: 队列的序列号,也就是iptables里设置的规则的nfqueue的序列号
cb : 数据包的处理函数(回调函数)
data: 自定义数据
*/
// 设置数据拷贝模式, 全包拷贝
assert(nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) == 0);
/**
* nfq_set_mode:从netlink系统里设置copy数据的方式
* qh :nfq_create_queue()函数的返回值
mode : copy 方式
NFQNL_COPY_NONE,- 丢弃
NFQNL_COPY_META,- 复制元数据
NFQNL_COPY_PACKET,- 复制整个数据包
range:期望得到的数据包的大小
*/
fd = nfq_fd(h);
// 从netlink套接字接收数据
while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0) {
// 处理数据,最终会调用到相应的回调函数
nfq_handle_packet(h, buf, rv);
/**
* 处理接收的数据,处理的逻辑由nfq_create_queue()函数中的回调函数cb决定
*h : nfq_open()返回struct nfnl_handle结构的指针;
buf: 待处理的数据
len:数据长度
*/
}
// 释放队列
nfq_destroy_queue(qh);
nfq_close(h);
return 0;
}
现在是用注册Hook函数的方式来实现这个功能,注册Hook函数时,除了return NF_QUEUE之外,似乎没有其他地方与queue_num有关
如果说不要标记队列序号,难道是所有的数据包都放入序号0的队列之中吗?
可是nfqueue应该是最多支持65536个子队列的
现在就是卡在了这里,请各位大佬指点如何在用户态接收内核通过nf_queeu传过来的数据包
页:
[1]