簡體   English   中英

打印 TCP 數據包數據

[英]Print TCP Packet Data

在 TCP 通信中,當數據包從以太網傳輸到網絡(IP)層時,我想打印該數據包中存在的數據?

我在 linux 上工作。

我得到了一些信息,它可以在 linux 內核代碼的幫助下完成,即在 linux NAT 防火牆代碼中。 但是我從哪里得到內核源代碼呢? 這些編碼在哪里完成?

如何從 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命令。

打印_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 章:網絡過濾器
    • 網絡過濾器掛鈎

如何獲取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,請參閱這些說明


細節:

[1] 如何從sk_buff獲取TCP頭

[2] Linux內核中的網絡流量控制

[3]使用 netfilter hooks 編寫可加載內核模塊

[4] Rami Rosen 的“Linux 內核網絡:實現和理論”

[5] 如何從 tcphdr 訪問數據/有效負載


更新

這個例子的鈎子在哪里捕獲數據包? 換句話說,它是否在 TCP 堆棧上,以便我不需要處理數據包丟失、重新排序等問題?

Netfilter 鈎子在ip_rcv()函數( 此處)中調用,因此您基本上是在 IPv4 層(OSI 中的網絡層)中工作。 所以我相信在那個 netfilter 鈎子中還沒有處理數據包丟失處理、數據包重新排序等。

有關見解,請參閱下一個鏈接:

如果您想要在傳輸層 (TCP) 上掛接數據包 - netfilter 不足以完成此任務,因為它僅在網絡層 (IPv4) 中工作。

您想使用tcpdump工具來檢查線路上的 TCP 數據包。

您沒有說明要查看的數據類型。

將在端口 53 上為 DNS 轉儲流量

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

這個頁面有一個很好的概述。

在內核級別不確定。

您可以使用libpcap實用程序來捕獲數據包並對其進行剖析。 例如這個:

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

我不知道“從以太網傳輸到網絡層”是什么意思。 你的意思是當內核停止處理第 2 層頭並移動到第 3 層頭時?

我將讓您自行決定如何使用這些信息。 如果要過濾和攔截第 3 層數據包(IP 數據包),Linux 上有兩個主要選項。 首先,您可以編寫一個 netfilter 鈎子(為此您需要具備內核編程知識和一些 C 技能)。 這基本上是一個內核模塊,因此您必須自己編寫第 4 層過濾器。 Linux 在它的庫中有struct tcphdrstruct ip ,只需谷歌就可以找到包含定義。

其他選項,我不建議在您希望它表現良好的環境中使用 iptables 或 nftables 將數據包排隊到用戶空間。 這更容易編程,因為您可以直接從 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 是 Python,但 scapy 有一些很酷的數據包​​操作)。 給您的提示:如果您以這種方式修改第 4 層數據包,則校驗和很難使用。 將其設置為零后,我無法讓 netfilter 自動計算校驗和,我建議您在線查找 IP 和 TCP 的有效校驗和計算函數,因為您已經在編寫一些不應該投入生產的東西。

如果你想攔截第 2 層幀(以太網幀),我知道從內核 2.x 時代起,ebtables 就在 Linux 中。 但是,這沒有我所知道的簡單的 NFQUEUE 類型解決方案,因此您只能編寫自己的代碼。 我相信它有用於創建過濾器和修改數據包的用戶空間 API,所以你可能很幸運。 如果用戶空間 API 不起作用,請盡情編寫內核模塊 :)。

感謝@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 中使用的內核代碼錯誤檢查器。

您可以像這樣使用 tcpdump:

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

或更好:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM