概述
前言:
手里的一个项目有一个这样的需求: 用户态拿到一条完整的ip数据包让我修改tcp的内容, 那具体的要求就是如果用户层协议是http协议, 那就添加自定义的http头, 比如 username:lilei. 这对于nginx来说很容易实现, 因为它本身是个tcp服务也是个tcp客户端, 只需要在转发数据时添加自定义头就行了.
但是如果用户态拿到ip数据包后硬刚, 还是比较麻烦的, 主要包含三个方面: 1要调整tcp头的长度字段和ip头的长度字段, 2要调整seq和ack(这个跟数据的长度还有直接关联), 3要调整checksum
第1和第3很容易实现, 网上就能找到方法, 但是第2个是比较麻烦的. 因为seq和ack跟数据包的长度有直接关联, 所以你插入自定义数据头后, 数据长度增加, 相应的seq和ack也要调整, 所以你要存储这些变量, 这就很麻烦了.
而且我也通过这三个方面的调整, 能初步实现<<修改tcp的数据内容>>功能, 增加的自定义头可以在服务端打印出来, 但是后续测试中发现此方法并不适合这个项目, 所以我就放弃了. 不过还好, linux内核模块netfilter帮我们实现了. 我们只需要使用netfilter写个内核模块即可.
条件:
centos7, linux内核3.10, 针对ipv4协议的tcp数据包, 某个固定端口, 本文为25865. 正文如下:
目录:
该目录主要包含两个文件, 一个是源码文件, 一个是Makefile文件.
append_ipv4.c:
#include <linux/module.h>
#include <net/ip.h>
#include <net/netfilter/nf_conntrack_helper.h>
#include <net/netfilter/nf_conntrack_seqadj.h>
#include <net/netfilter/nf_nat_helper.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("HELLO");
MODULE_DESCRIPTION("Append something");
MODULE_VERSION("0.0.1");
enum {
NF_IP_PRE_ROUTING,
NF_IP_LOCAL_IN,
NF_IP_FORWARD,
NF_IP_LOCAL_OUT,
NF_IP_POST_ROUTING,
NF_IP_NUMHOOKS
};
static struct nf_hook_ops append_in; // netfilter hook选项 in
static struct nf_hook_ops append_out; // netfilter hook选项 out
// netfilter
// 当前无直接操作, 直接返回
static unsigned int hook_out(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff *))
{
return NF_ACCEPT;
}
// netfilter
// 进数据的hook回调函数
static unsigned int hook_in(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff *))
{
struct iphdr *ip_header = ip_hdr(skb);
if (ip_header != NULL && ip_header->version == 4 && ip_header->protocol == IPPROTO_TCP)
{
struct tcphdr *tcp_header = NULL;
struct nf_conn *n_conn = NULL;
enum ip_conntrack_info i_conntract_info;
int offset = 0, tcp_payload_len = 0;
unsigned int saddr = 0, daddr = 0, sport = 0, dport = 0;
char *tcp_payload = NULL, *pos = NULL, append_info[48] = {0x00};
unsigned char src_ip[16] = {0x00};
if (skb_linearize(skb))
{
return NF_ACCEPT;
}
tcp_header = tcp_hdr(skb);
saddr = (unsigned int)ip_header->saddr;
daddr = (unsigned int)ip_header->daddr;
sport = (unsigned int)ntohs(tcp_header->source);
dport = (unsigned int)ntohs(tcp_header->dest);
tcp_payload = (char *)((long long)tcp_header + ((tcp_header->doff) * 4));
tcp_payload_len = skb->len - (ip_header->ihl * 4) - (tcp_header->doff * 4);
if (tcp_payload_len == 0)
{
return NF_ACCEPT;
}
// 为了判断HTTP协议
if (tcp_payload_len < 10)
{
return NF_ACCEPT;
}
// 简单判断下是否为http协议, 如果不是则返回
// 如果是正常的HTTP协议, 第一行格式: GET /uri HTTP/1.1 rn
// 在TCP数据段找到第一个rn的位置, 如果没有rn肯定不是HTTP协议
if ((pos = strstr(tcp_payload, "rn")) == NULL)
{
return NF_ACCEPT;
}
// HTTP协议
if ((strncmp(pos - strlen("HTTP/1.0"), "HTTP/1.0", strlen("HTTP/1.0")) == 0 || strncmp(pos - strlen("HTTP/1.1"), "HTTP/1.1", strlen("HTTP/1.1")) == 0) && dport == 25865)
{
sprintf(src_ip, "%pI4", &saddr);
sprintf(append_info, "Username: This_is_append_inforn");
printk(KERN_INFO "%s append something %sn", src_ip, append_info);
n_conn = nf_ct_get(skb, &i_conntract_info);
nfct_seqadj_ext_add(n_conn);
offset = (int)(pos - tcp_payload) + 2;
if (n_conn && nf_nat_mangle_tcp_packet(skb, n_conn, i_conntract_info, ip_header->ihl * 4, offset, 0, append_info, strlen(append_info)))
{
return NF_ACCEPT;
}
}
}
return NF_ACCEPT;
}
static int append_modules_init(void)
{
append_in.hook = (nf_hookfn*)hook_in;
append_in.hooknum = NF_IP_LOCAL_IN;
append_in.pf = PF_INET;
append_in.priority = NF_IP_PRI_FIRST;
append_out.hook = (nf_hookfn*)hook_out;
append_out.hooknum = NF_IP_LOCAL_OUT;
append_out.pf = PF_INET;
append_out.priority = NF_IP_PRI_FIRST;
printk(KERN_INFO "Register append_ipv4 modules!n");
return (nf_register_hook(&append_in) || nf_register_hook(&append_out)) ? -1 : 0;
}
static void append_modules_exit(void)
{
nf_unregister_hook(&append_in);
nf_unregister_hook(&append_out);
printk(KERN_INFO "Cleaning append_ipv4 modules!n");
}
module_init(append_modules_init);
module_exit(append_modules_exit);
Makefile:
# Makefile for append_ipv4
MODULE_NAME := append_ipv4
obj-m :=$(MODULE_NAME).o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD)
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
HTTP服务:
我是用golang gin组件编写了一个简单的http服务, 监听端口为25865, 并打印自定义头Username. 部分代码如下:
package testforipv4
import (
"fmt"
"github.com/gin-gonic/gin"
)
func Post(c *gin.Context) {
c.JSON(200, gin.H{"POST": "post method"})
}
func Get(c *gin.Context) {
fmt.Println("append info:", c.GetHeader("Username"))
c.JSON(200, gin.H{"GET": "get method"})
}
func HTTPServer() {
router := gin.Default()
router.POST("/post", Post)
router.GET("/get", Get)
err := router.Run(*addr)
if err != nil {
panic(err.Error())
}
}
测试:
加载该内核模块前后对比如下:
最后
以上就是迅速丝袜为你收集整理的Linux下如何修改TCP数据包内容的全部内容,希望文章能够帮你解决Linux下如何修改TCP数据包内容所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复