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

前言:

        手里的一个项目有一个这样的需求: 用户态拿到一条完整的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())
	}
}

测试:

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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值