Linux下如何修改TCP数据包内容

本文介绍如何在CentOS 7环境下,利用Linux内核模块netfilter,针对IPv4的TCP数据包(端口25865),在用户态修改HTTP头部,包括调整TCP头和IP头,处理seq/ack及checksum。重点讲解了如何通过nf_conntrack和nf_nat_helper处理数据长度变化带来的seq/ack调整问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:

        手里的一个项目有一个这样的需求: 用户态拿到一条完整的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 \r\n
                // 在TCP数据段找到第一个\r\n的位置, 如果没有\r\n肯定不是HTTP协议
                if ((pos = strstr(tcp_payload, "\r\n")) == 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_info\r\n");
                        printk(KERN_INFO "%s append something %s\n", 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())
	}
}

测试:

        加载该内核模块前后对比如下:

如何修改TCP/IP并发连接数,网络上提供不少修改办法,但其中有不少是行通的,本文将做详细分析. 一、注册表修改法的误区 为了突破SP2对TCP并发连接数的限制,网上曾经流传过一种修改注册表的方法,操作步骤如下: 单击“开始”/运行,输入Regedit打开注册表,定位到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters下,修改的“TcpNumConnections”的键值,将之由10改为150,即设置TCP最大并发连接数为150。 经过实际测试,我们发现该方法看起来有效,但实质上并不能突破并发连接数限制,提高SP2的多线程访问速度。因为SP2对线程数目的控制,是通过“Tcpip.sys”这个系统文件来实现的,并不是通过注册表实现的,因此,该方法不能增加SP2的TCP并发连接数。 二、使用工具来替换Tcpip.sys 为了突破SP2的TCP并发连接数限制,正确地方法是修改Windows XP SP2的系统文件Tcpip.sys。Tcpip.sys是Windows XP SP2重要的系统文件,位于“C:\Windows\system32\drivers目录下。该文件由于平时受到系统保护,所以正常情况下你是无法替换它的,必须在安全模式或纯DOS模式下才能替换,建议你使用以下专门工具、来替换“Tcpip.sys”文件,操作步骤如下: 从网上http://www.lvllord.de/download.php?url=en/EvID4226Patch223d-en.zip下载替换工具(仅为30KB),用它来修改系统文件Tcpip.sys最大安全并发连接限制;然后备份一下C:\Windows\system32\drivers\Tcpip.sys文件。 接下来,双击打开下载文件ZIP压缩包,运行其中的替换工具EvID4226Patch.exe,随之将弹出一个命令行提示符窗口,首先显示Windows当前的Tcpip.sys文件版本,以及并发连接的限制数值(默认为10);接着询问你是否将连接数限制在50,你可以选择“Yes/No/Change”,如果你输入“Y”,则会将并发连接数改为“50”,如果想改为其他数(例如150),可以在提示符后输入“c”,然后输入最大的并发连接数(例如150)回车,最后在提示符下输入“Y”并回车,这样就替换了Tcpip.sys文件Tcpip.sys文件被替换后,随之会弹出系统文件保护对话框,你可以点击“取消”按钮,然后点击“是”按钮,重新启动后,Tcpip.sys文件的替换就大功告成了! 现在你的最大并发连接数已超过10个,达到了150个,因此Windows XP SP2的多线程访问速度得到了提升,当你用FlashGet、BT等多线程下载时,就不会感到网络带宽的限制了。 三、DOS下修改Tcpip.sys文件 以上替换程序EvID4226Patch.exe也可以在DOS下使用,方法是:首先把EvID4226Patch.exe拷贝到C盘根目录下;然后再进入DOS模式,进入C盘根目录,输入命令EvID4226Patch/L=$n$/w=C:\WINDOWS\system32\drivers/L=tcpip.sys即可修改Tcpip.sys文件。 注意:以上$n$为你要设置的最大安全连接数,假如要把最大并发连接数设置为150个线程,那么输入命令EvID4226Patch/L=150/w=C:\WINDOWS\system32\drivers/L=tcpip.sys即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值