如何打印 TCP 数据包中的数据
下面是一个完全满足您需要的示例:挂钩收到的 TCP 数据包并打印其有效负载。如果您想从接收到的数据包中打印一些其他信息(例如二进制数据),您只需修改此注释下的部分即可:
/* ----- Print all needed information from received TCP packet ------ */
如果您需要追踪传送的数据包而不是received的,你可以替换这一行:
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");
Makefile:
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]。您特别对接下来的章节感兴趣:
- chapter 11: Layer 4 Protocols
- TCP (Transmission Control Protocol)
- 使用 TCP 从网络层 (L3) 接收数据包
- 使用 TCP 发送数据包
- chapter 9: Netfilter
如何获取Linux内核源代码
您可以使用您喜欢的方式之一获取内核源代码:
-
香草仁来自内核.org https://kernel.org/(更具体地说来自内核/git/torvalds/linux.git https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git), 使用Git http://git-scm.com/。例如。如果您需要 k3.13,可以通过以下方式完成:
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
$ cd linux/
$ git checkout v3.13
来自您的发行版的内核源代码。例如。在 Debian 中你只需安装linux-source
包(源将安装到/usr/src
)。对于 Ubuntu,请参阅这些说明 https://help.ubuntu.com/community/Kernel/Compile#Get_the_kernel_source.
Details:
[1] 如何从sk_buff获取TCP头 https://stackoverflow.com/questions/16528868/c-linux-kernel-module-tcp-header
[2] Linux内核中的网络流量控制 http://www.linuxfoundation.org/collaborate/workgroups/networking/kernel_flow
[3] 使用 netfilter 钩子编写可加载内核模块 http://fcns.eu/2010/02/15/netfilter-hooks/
[4] “Linux 内核网络:实现和理论”作者:Rami Rosen https://rads.stackoverflow.com/amzn/click/com/143026196X
[5] 如何从 tcphdr 访问数据/有效负载 https://stackoverflow.com/questions/12073963/how-to-access-data-payload-from-tcphdr-sk-buff-struct-on-debian-64-bits
UPDATE
本例中钩子在哪里捕获数据包?换句话说,它是在 TCP 堆栈上,这样我就不需要处理数据包丢失、重新排序等问题吗?
Netfilter 钩子被调用ip_rcv()
功能 (here http://lxr.free-electrons.com/source/net/ipv4/ip_input.c?v=4.6#L478),所以你基本上是在 IPv4 层(即 OSI 中的网络层)工作。所以我相信数据包丢失处理、数据包重新排序等是not尚未在该 netfilter 挂钩中处理。
请参阅以下链接了解见解:
- Netfilter数据包流 https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg
- Linux 网络中的控制流 http://www.linuxfoundation.org/collaborate/workgroups/networking/kernel_flow
- 通过内核的网络数据流(图) https://wiki.linuxfoundation.org/images/1/1c/Network_data_flow_through_kernel.png
如果您想要在传输层 (TCP) 上挂钩数据包 - netfilter 不足以完成此任务,因为它专门在网络层 (IPv4) 中工作。