打印TCP数据包数据

时间:2015-04-10 05:25:40

标签: c tcp linux-kernel hook netfilter

在TCP通信中,当数据包从以太网传输到网络(IP)层时,我想打印该数据包中的数据?

我正在使用linux。

我得到了一些信息,可以借助linux内核代码在linux NAT防火墙代码中完成。但是我会在哪里获得内核源代码?这些编码在哪里?

6 个答案:

答案 0 :(得分:20)

如何从TCP数据包打印数据

下面是一个完全满足您需求的示例:挂钩接收TCP数据包并打印其有效负载。如果你想从收到的数据包中打印一些其他信息(比如二进制数据),你只需要修改一下这个评论下的部分:

/* ----- Print all needed information from received TCP packet ------ */

如果您需要跟踪传输的数据包而非已接收数据包,则可以替换此行:

nfho.hooknum = NF_INET_PRE_ROUTING;

这一个:

nfho.hooknum = NF_INET_POST_ROUTING;

保存下一个文件并发出make命令来构建内核模块。然后执行sudo insmod print_tcp.ko加载它。之后,您将能够使用dmesg命令查看嗅探信息。如果要卸载模块,请运行sudo rmmod print_tcp命令。

<强> print_tcp.c

#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/tcp.h>

#define PTCP_WATCH_PORT     80  /* HTTP port */

static struct nf_hook_ops nfho;

static unsigned int ptcp_hook_func(const struct nf_hook_ops *ops,
                                   struct sk_buff *skb,
                                   const struct net_device *in,
                                   const struct net_device *out,
                                   int (*okfn)(struct sk_buff *))
{
    struct iphdr *iph;          /* IPv4 header */
    struct tcphdr *tcph;        /* TCP header */
    u16 sport, dport;           /* Source and destination ports */
    u32 saddr, daddr;           /* Source and destination addresses */
    unsigned char *user_data;   /* TCP data begin pointer */
    unsigned char *tail;        /* TCP data end pointer */
    unsigned char *it;          /* TCP data iterator */

    /* Network packet is empty, seems like some problem occurred. Skip it */
    if (!skb)
        return NF_ACCEPT;

    iph = ip_hdr(skb);          /* get IP header */

    /* Skip if it's not TCP packet */
    if (iph->protocol != IPPROTO_TCP)
        return NF_ACCEPT;

    tcph = tcp_hdr(skb);        /* get TCP header */

    /* Convert network endianness to host endiannes */
    saddr = ntohl(iph->saddr);
    daddr = ntohl(iph->daddr);
    sport = ntohs(tcph->source);
    dport = ntohs(tcph->dest);

    /* Watch only port of interest */
    if (sport != PTCP_WATCH_PORT)
        return NF_ACCEPT;

    /* Calculate pointers for begin and end of TCP packet data */
    user_data = (unsigned char *)((unsigned char *)tcph + (tcph->doff * 4));
    tail = skb_tail_pointer(skb);

    /* ----- Print all needed information from received TCP packet ------ */

    /* Show only HTTP packets */
    if (user_data[0] != 'H' || user_data[1] != 'T' || user_data[2] != 'T' ||
            user_data[3] != 'P') {
        return NF_ACCEPT;
    }

    /* Print packet route */
    pr_debug("print_tcp: %pI4h:%d -> %pI4h:%d\n", &saddr, sport,
                              &daddr, dport);

    /* Print TCP packet data (payload) */
    pr_debug("print_tcp: data:\n");
    for (it = user_data; it != tail; ++it) {
        char c = *(char *)it;

        if (c == '\0')
            break;

        printk("%c", c);
    }
    printk("\n\n");

    return NF_ACCEPT;
}

static int __init ptcp_init(void)
{
    int res;

    nfho.hook = (nf_hookfn *)ptcp_hook_func;    /* hook function */
    nfho.hooknum = NF_INET_PRE_ROUTING;         /* received packets */
    nfho.pf = PF_INET;                          /* IPv4 */
    nfho.priority = NF_IP_PRI_FIRST;            /* max hook priority */

    res = nf_register_hook(&nfho);
    if (res < 0) {
        pr_err("print_tcp: error in nf_register_hook()\n");
        return res;
    }

    pr_debug("print_tcp: loaded\n");
    return 0;
}

static void __exit ptcp_exit(void)
{
    nf_unregister_hook(&nfho);
    pr_debug("print_tcp: unloaded\n");
}

module_init(ptcp_init);
module_exit(ptcp_exit);

MODULE_AUTHOR("Sam Protsenko");
MODULE_DESCRIPTION("Module for printing TCP packet data");
MODULE_LICENSE("GPL");

<强>生成文件

ifeq ($(KERNELRELEASE),)

KERNELDIR ?= /lib/modules/$(shell uname -r)/build

module:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) C=1 modules

clean:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) C=1 clean

.PHONY: module clean

else

MODULE = print_tcp.o
CFLAGS_$(MODULE) := -DDEBUG
obj-m := $(MODULE)

endif

说明

我建议你阅读这本书:[4]。特别是您对下一章感兴趣:

  • 第11章:第4层协议
    • TCP(传输控制协议)
      • 使用TCP
      • 从网络层(L3)接收数据包
      • 使用TCP发送数据包
  • 第9章:Netfilter
    • Netfilter Hooks

如何获取Linux内核源代码

您可以使用您喜欢的方式获取内核源代码:

  1. 来自kernel.org的香草内核(更具体地来自 kernel/git/torvalds/linux.git),使用Git。例如。如果您需要k3.13,可以通过下一步方式完成:

    $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
    $ cd linux/
    $ git checkout v3.13
    
  2. 来自发行版的内核源代码。例如。在Debian中,您只需安装linux-source包(来源将安装到/usr/src)。对于Ubuntu,请参阅these instructions


  3. 详细说明:

    [1] How to get TCP header from sk_buff

    [2] Network flow control in Linux kernel

    [3] Writing Loadable Kernel Modules using netfilter hooks

    [4] "Linux Kernel Networking: Implementation and Theory" by Rami Rosen

    [5] How to access data/payload from tcphdr


    更新

      

    钩子捕获此示例的数据包?换句话说,它是否在TCP堆栈上,以便我不需要处理数据包丢失,重新排序等?

    ip_rcv()函数(here)中调用Netfilter挂钩,因此您基本上在IPv4层(OSI中的网络层)中工作。所以我认为丢包处理,数据包重新排序等在netfilter挂钩中处理。

    请参阅下一个链接了解见解:

    如果您想在传输层(TCP)上使用挂钩数据包 - netfilter不足以执行此任务,因为它仅适用于网络层(IPv4)。

答案 1 :(得分:0)

您希望使用tcpdump工具检查线路上的TCP数据包。

您没有说出您要查看的数据类型。

This会在DNS端口53上转储流量

tcpdump -vvv -s 0 -l -n port 53

This page有一个很好的概述。

答案 2 :(得分:0)

在内核级别不确定。

您可以使用libpcap实用程序捕获数据包并进行剖析。例如:

http://yuba.stanford.edu/~casado/pcap/section2.html

答案 3 :(得分:0)

我不知道“从以太网传输到网络层”是什么意思。您的意思是当内核停止处理第2层标头并移至第3层标头时吗?

我将由您自行使用此信息。如果要过滤和拦截第3层数据包(IP数据包),在Linux上有两个主要选项。首先,您可以编写一个netfilter钩子(为此您需要内核编程知识和一些C技能)。这基本上是一个内核模块,因此您必须自己编写第4层过滤器。 Linux的库中有struct tcphdrstruct ip,只有google,您可以找到包含定义。

我不建议在您希望它表现良好的环境中使用的其他选项是使用iptables或nftable将数据包排队到用户空间。这很容易编程,因为您可以直接从cli使用IPtables和nftables挂钩,而不必担心学习内核模块编程。为此,示例iptables挂钩为iptables -A PREROUTING -p tcp --dport 8000 -j NFQUEUE --queue-num 0。这会将在PREROUTING中捕获的,发往端口8000的所有tcp数据包都路由到netfilter队列号0(基本上只是一个用户空间套接字)。您将需要为发行版安装libnetfilter_queue或等效的数据包,然后可以使用多种语言捕获和修改此过滤器捕获的数据包。我个人知道并用C,Python,Rust和Golang编写了这些脚本(尽管golang在速度方面有些不足,Python很好,但是,scapy有一些很棒的数据包处理方面的东西)。给您的提示:如果您以这种方式修改第4层数据包,则使用校验和很麻烦。将其设置为零后,我无法让netfilter自动计算校验和,我建议在线为IP和TCP找到一个有效的校验和计算功能,因为您已经在写一些永远不会在生产中使用的东西。

如果您想拦截第2层帧(以太网帧),我知道ebtables从内核2.x开始就存在于Linux中。但是,据我所知,这没有一个简单的NFQUEUE类型的解决方案,因此您不得不编写自己的代码。我相信它具有用于创建过滤器和修改数据包的用户空间API,因此您可能会很幸运。如果用户空间API不起作用,请编写内核模块:)。

答案 4 :(得分:0)

感谢@Sam Protsenko 的回答。但是对于内核版本 >= 4.13,函数 nf_register_hook(&nfho)nf_unregister_hook(&nfho) 已被替换为 nf_register_net_hook(&init__net, &nfho)nf_unregister_net_hook(&init__net, &nfho)

如果您想尝试代码,请检查您的内核版本并根据您的情况修改代码。

此外,对于初学者来说,您可能想要apt install sparse,它是 Makefile 中使用的内核代码错误检查器。

答案 5 :(得分:-2)

您可以像这样使用tcpdump:

tcpdump -vvv -s 0 -l -n port 80 -i NameOfYourDevice

或更好:

tcpdump -i NameOfYourDevice -a -x -n port 80