Linux内核模块中借助Netfilter获取HTTP请求数据与主机名的方法
Linux内核模块中获取HTTP请求数据与主机名的方法
我编写了一个Linux内核模块,目标是当请求的服务器名为特定字符串时,将80端口的HTTP网络请求重定向至8000端口。本以为只需读取请求、过滤并对比服务器名即可,但实际难度远超预期,尤其是内核头文件的文档极为匮乏。
我通过如下方式注册网络钩子:
static int __init my_hook_init(void) { pr_info("lavawall: loaded\n"); return nf_register_net_hook(&init_net, &my_nfho); }
经过空值检查并获取IP地址后,我存储了端口并打印数据包的目标地址:
static unsigned int lavawall(__attribute__((unused)) void* _, struct sk_buff* skb, const struct nf_hook_state* state) { //... port = ntohs(tcp->dest); printk(KERN_INFO "lavawall: %s:%d\n", request_ip_string, port); // ... return NF_ACCEPT; }
我尝试从skb结构体中读取数据字符串,但结果要么为空,要么是乱码。
我的问题很明确:
- 如何获取HTTP请求数据?
- 如何获取请求指向的主机名(例如
my.site.com)?
核心解决思路与实现步骤
1. 正确获取HTTP请求数据
TCP是流式协议,一个完整的HTTP请求可能被拆分成多个skb数据包,且skb可能是非线性存储的,直接读取会出现空值或乱码,需按以下步骤处理:
struct tcphdr *tcp; struct iphdr *ip; unsigned char *data; int data_len; // 提取IP和TCP头部 ip = ip_hdr(skb); tcp = tcp_hdr(skb); // 计算TCP数据段的长度(总长度减去IP头、TCP头长度) data_len = skb->len - (ip->ihl * 4) - (tcp->doff * 4); if (data_len <= 0) { return NF_ACCEPT; // 无有效数据,直接放行 } // 处理非线性skb:将分散的数据线性化,否则无法连续读取 if (skb_is_nonlinear(skb)) { if (skb_linearize(skb) != 0) { return NF_ACCEPT; // 线性化失败,放行 } // 线性化后重新获取头部指针 ip = ip_hdr(skb); tcp = tcp_hdr(skb); } // 定位TCP数据起始位置 data = (unsigned char *)((unsigned char *)tcp + (tcp->doff * 4));
此外,必须维护连接的状态缓存:用**五元组(源IP、源端口、目标IP、目标端口、协议)**作为键,在内核中创建哈希表,缓存每个连接的已接收数据,直到检测到HTTP请求头结束标记\r\n\r\n,再进行完整的请求解析。
2. 提取HTTP请求的主机名
HTTP请求的主机名存放在Host头字段中,拿到完整的请求头后:
- 遍历缓存的请求头内容,匹配以
Host:开头的行(HTTP头字段不区分大小写,实际场景可兼容大小写判断) - 提取
Host:之后、\r\n之前的内容,即为目标主机名(若包含端口如my.site.com:80,需额外截取端口前的字符串)
关键注意事项
- 内核内存管理:使用
kmalloc/kzalloc分配缓存内存,用kfree释放,避免内存泄漏 - 并发与性能:哈希表操作需加锁防止并发冲突,请求处理完成后及时清理连接缓存,避免内存占用过高
- 钩子点选择:根据需求选择合适的Netfilter钩子点,如本地接收数据包选
NF_INET_LOCAL_IN,转发数据包选NF_INET_FORWARD
内容的提问来源于stack exchange,提问作者MrKleeblatt
相关产品推荐
相关产品推荐

