wireshark源码学习-添加基本​​解剖器

static int 
dissect_foo(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,void * data _U_)
{ 

    col_set_str(pinfo-> cinfo,COL_PROTOCOL,“FOO”); 
    / *清除信息列中的内容* / 
    col_clear(pinfo-> cinfo,COL_INFO); 

    proto_item * ti = proto_tree_add_item(tree,proto_foo,tvb,0,-1,ENC_NA); 

    return tvb_captured_length(tvb); 
}

9.2。添加基本​​解剖器

让我们逐步添加一个基本的解剖器。我们将从制作的“foo”协议开始。它包含以下基本项目。

  • 数据包类型 - 8位,可能的值:1 - 初始化,2 - 终止,3 - 数据。
  • 一组标志存储在8位,0x01 - 开始包,0x02 - 结束包,0x04 - 优先包。
  • 序列号 - 16位。
  • IPv4地址。

9.2.1。设置解剖器

你需要做出的第一个决定是,这个解剖器是一个内置的解剖器,包含在主程序中,还是一个插件。

插件最初是最容易编写的,所以让我们从头开始。有点小心,插件可以很容易地作为内置运行,所以我们没有丢失任何东西。

 

例9.1。解剖器初始化。

#include“config.h” 

#include <epan / packet.h> 

#define FOO_PORT 1234 

static int proto_foo = -1; 


void 
proto_register_foo(void)
{ 
    proto_foo = proto_register_protocol(
        “FOO Protocol”,/ * name * / 
        “FOO”,/ * short name * / 
        “foo”/ * abbrev * / 
        ); 
}
 

让我们一点一点地讨论这个问题。首先,我们有一些样板包含文件。这些将是非常不变的开始。

接下来我们有一个初始化为-1记录我们协议的int 。当我们向主程序注册此解剖器时,这将更新。最好将所有未导出的变量和函数设置为静态,以减少名称空间污染。通常这不是问题,除非你的解剖器变得如此之大,它必须跨越多个文件。

然后是一个#define携带_foo_的UDP端口。

现在我们已经掌握了与主程序交互的基础知识,我们将从两个协议解析器设置函数开始。

首先,我们将调用proto_register_protocol()哪个注册协议。我们可以给它三个名字,用于在不同的地方展示。完整和短名称用于例如“首选项”和“启用协议”对话框以及文档中生成的字段名称列表。缩写用作显示过滤器名称。

接下来我们需要一个切换程序。

 

例9.2。解剖器切换。

void 
proto_reg_handoff_foo(void)
{ 
    static dissector_handle_t foo_handle; 

    foo_handle = create_dissector_handle(dissect_foo,proto_foo); 
    dissector_add_uint(“udp.port”,FOO_PORT,foo_handle); 
}

这里发生了什么事?我们正在初步解剖。首先,我们创建一个解剖器手柄; 它与foo协议相关联,并与调用例程进行实际解剖。然后我们将句柄与UDP端口号相关联,这样主程序就知道在该端口上获得UDP流量时会给我们打电话。

标准Wireshark的解剖惯例是把proto_register_foo()和 proto_reg_handoff_foo()在剥离源作为最后两个功能。

最后我们写了一些解剖代码。目前我们将其作为基本占位符。

 

例9.3。解剖。

static int 
dissect_foo(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree _U_,void * data _U_)
{ 
    col_set_str(pinfo-> cinfo,COL_PROTOCOL,“FOO”); 
    / *清除信息列中的内容* / 
    col_clear(pinfo-> cinfo,COL_INFO); 

    return tvb_captured_length(tvb); 
}

调用此函数来剖析呈现给它的数据包。分组数据保存在这里称为tvb的特殊缓冲区中。随着我们深入了解协议的细节,我们将对此非常熟悉。数据包信息结构包含有关协议的一般数据,我们可以在此处更新信息。树参数是进行细节剖析的地方。

现在我们将做到最低限度,我们可以逃脱。在第一行中,我们将此文本设置为我们的协议,因此每个人都可以看到它被识别。我们唯一要做的就是清除INFO列中的任何数据(如果正在显示)。

在这一点上,我们应该有一个基本的解剖器准备编译和安装。除了识别协议并标记协议之外,目前它没有做太多工作。

为了编译这个解剖器并创建一个插件,除了packet-foo.c中的解剖器源之外,还需要几个支持文件:

  • CMakeLists.txt - 包含此插件的CMake文件和版本信息。
  • packet-foo.c - 您的解剖器源。
  • plugin.rc.in - 包含Windows的DLL资源模板。(可选的)

您可以在gryphon插件目录中找到这些文件的一个很好的示例。 必须使用正确的插件名称和版本信息修改CMakeLists.txt,以及要编译的相关文件。在主要顶级源目录中,将CMakeListsCustom.txt.example复制到CMakeListsCustom.txt,并将插件的路径添加到CUSTOM_PLUGIN_SRC_DIR中的列表中。

将解剖器编译为DLL或共享库,并从构建目录运行Wireshark,如第3.6节“运行生成的Wireshark”中所述,或将插件二进制文件复制到Wireshark安装的插件目录中并运行它。

9.2.2。剖析协议的细节

现在我们已经启动并运行了基本的解剖器,让我们用它做点什么。最简单的事情就是标记有效负载。这将允许我们设置一些我们需要的部分。

我们要做的第一件事是构建一个子树来解码我们的结果。这有助于在详细显示中保持良好的外观。

 

例9.4。插件包解剖。

 

 

我们在这里做的是为解剖添加一个子树。该子树将保存此协议的所有细节,因此在不需要时不会使显示混乱。

我们还标记了此协议正在使用的数据区域。在我们的例子中,所有这些都已传递给我们,因为我们假设此协议不会封装另一个协议。因此,我们添加新的树节点proto_tree_add_item(),将其添加到传入的树中,使用协议标记它,使用传入的tvb缓冲区作为数据,并从0消耗到此数据的结尾(-1)。ENC_NA(“不适用”)被指定为“encoding”参数。

在此更改之后,协议的详细显示中应该有一个标签,选择此选项将突出显示数据包的剩余内容。

现在让我们进入下一步并添加一些协议解析。对于这一步,我们需要构建一些有助于解剖的表格。这需要对proto_register_foo()前面显示的功能进行一些补充。

在开头添加两个静态分配的数组 proto_register_foo()。然后在调用之后注册数组 proto_register_protocol()

 

例9.5。注册数据结构。

void 
proto_register_foo(void)
{ 
    static hf_register_info hf [] = { 
        {&hf_foo_pdu_type,
            {“FOO PDU Type”,“foo.type”,
            FT_UINT8,BASE_DEC,
            NULL,0x0,
            NULL,HFILL} 
        } 
    }; 

    / *设置协议子树数组* / 
    static gint * ett [] = { 
        &ett_foo 
    }; 

    proto_foo = proto_register_protocol(
        “FOO Protocol”,/ * name * / 
        “FOO”,/ * short name * / 
        “foo”/ * abbrev * / 
        ); 

    proto_register_field_array(proto_foo,hf,array_length(hf));
    proto_register_subtree_array(ett,array_length(ett)); 
}

 

变量hf_foo_pdu_typeett_foo还需要申报的地方接近文件的顶部。

 

例9.6。解析器数据结构全局。

static int hf_foo_pdu_type = -1; 

static gint ett_foo = -1;

 

现在我们可以通过一些细节来增强协议显示。

 

例9.7。解剖器开始剖析数据包。

    proto_item * ti = proto_tree_add_item(tree,proto_foo,tvb,0,-1,ENC_NA); 
    proto_tree * foo_tree = proto_item_add_subtree(ti,ett_foo); 
    proto_tree_add_item(foo_tree,hf_foo_pdu_type,tvb,0,1,ENC_BIG_ENDIAN);

 

现在解剖开始变得更有趣了。我们挑选了第一部分协议。数据包开头的一个字节数据,用于定义foo协议的数据包类型。

proto_item_add_subtree()调用已将一个子节点添加到协议树中,这是我们进行详细剖析的地方。该节点的扩展由ett_foo 变量控制。这会记住在数据包之间移动时是否应该扩展节点。所有后续解剖都将添加到此树中,您可以从下一个调用中看到。proto_tree_add_item()在foo_tree中调用,这次使用hf_foo_pdu_type控制项的格式。pdu类型是一个字节的数据,从0开始。我们假设它是网络顺序(也称为大端),这就是我们使用的原因ENC_BIG_ENDIAN。对于1字节数量,没有订单问题,但优良做法是使其与可能存在的任何多字节字段相同,并且正如我们将在下一节中看到的,此特定协议使用网络顺序。

如果我们详细查看hf_foo_pdu_type静态数组中的声明,我们可以看到定义的细节。

  • hf_foo_pdu_type - 此节点的索引。
  • FOO PDU类型 - 此项目的标签。
  • foo.type - 这是过滤字符串。它使我们能够foo.type=1在过滤器框中输入构造。
  • FT_UINT8 - 指定此项为8位无符号整数。这符合我们上面的调用,我们告诉它只看一个字节。
  • BASE_DEC - 对于整数类型,它告诉它打印为十进制数。如果更有意义,它可以是十六进制(BASE_HEX)或八进制(BASE_OCT)。

我们现在暂时忽略其余的结构。

如果你安装这个插件并试用它,你会看到一些看起来很有用的东西。

现在让我们完成解剖简单协议。我们需要在hfarray中添加一些变量,以及更多的过程调用。

 

例9.8。收拾数据包解剖。

... 
static int hf_foo_flags = -1; 
static int hf_foo_sequenceno = -1; 
static int hf_foo_initialip = -1; 
... 

static int 
dissect_foo(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,void * data _U_)
{ 
    gint offset = 0; 

    ... 
    proto_item * ti = proto_tree_add_item(tree,proto_foo,tvb,0,-1,ENC_NA); 
    proto_tree * foo_tree = proto_item_add_subtree(ti,ett_foo); 
    proto_tree_add_item(foo_tree,hf_foo_pdu_type,tvb,offset,1,ENC_BIG_ENDIAN); 
    偏移+ = 1; 
    proto_tree_add_item(foo_tree,hf_foo_flags,tvb,offset,1,ENC_BIG_ENDIAN); 
    偏移+ = 1;
    proto_tree_add_item(foo_tree,hf_foo_sequenceno,tvb,offset,2,ENC_BIG_ENDIAN); 
    偏移+ = 2; 
    proto_tree_add_item(foo_tree,hf_foo_initialip,tvb,offset,4,ENC_BIG_ENDIAN); 
    偏移+ = 4; 
    ... 

    返回tvb_captured_length(tvb); 
} 

void 
proto_register_foo(void){ 
    ... 
        ... 
        {&hf_foo_flags,
            {“FOO PDU Flags”,“foo.flags”,
            FT_UINT8,BASE_HEX,
            NULL,0x0,
            NULL,HFILL} 
        },
        {&hf_foo_sequenceno,
            {“FOO PDU序号“,”foo.seqn“,
            FT_UINT16,BASE_DEC,
            NULL,为0x0,
            NULL,HFILL} 
        },
        {&hf_foo_initialip,
            { “FOO PDU初始IP”, “foo.initialip”,
            FT_IPv4,BASE_NONE,
            NULL,为0x0,
            NULL,HFILL} 
        } 
        ... 
    ... 
} 
.. 。

 

这解剖了这个简单的假设协议的所有部分。我们在混合中引入了一个新的变量offset,以帮助跟踪我们在数据包解剖中的位置。有了这些额外的位,现在解剖整个协议。

9.2.3。改善解剖信息

我们当然可以通过一些额外的数据来改进协议的显示。第一步是添加一些文本标签。让我们从标记数据包类型开始。通过添加一些额外的东西,可以为这种事情提供一些有用的支持。首先,我们为name添加一个简单的类型表。

 

例9.9。命名数据包类型。

static const value_string packettypenames [] = { 
    {1,“Initialise”},
    {2,“Terminate”},
    {3,“Data”},
    {0,NULL} 
};

 

这是一个方便的数据结构,可用于查找值的名称。有一些例程可以直接访问这个查找表,但是我们不需要这样做,因为支持代码已经添加了。我们只需要使用VALS宏将这些细节提供给数据的适当部分。

 

例9.10。将名称添加到协议。

   {&hf_foo_pdu_type,
        {“FOO PDU Type”,“foo.type”,
        FT_UINT8,BASE_DEC,
        VALS(packettypenames),0x0,
        NULL,HFILL} 
    }

 

这有助于解密数据包,我们可以为标志结构做类似的事情。为此,我们需要向表中添加更多数据。

 

例9.11。将标志添加到协议中。

#define FOO_START_FLAG 0x01 
#define FOO_END_FLAG 0x02 
#define FOO_PRIORITY_FLAG 0x04 

static int hf_foo_startflag = -1; 
static int hf_foo_endflag = -1; 
static int hf_foo_priorityflag = -1; 

静态INT 
dissect_foo(tvbuff_t * TVB,packet_info * PINFO,proto_tree *树,无效*数据_U_)
{ 
    ... 
        ... 
        静态const int的*位[] = { 
            &hf_foo_startflag,
            &hf_foo_endflag,
            &hf_foo_priorityflag 
        }; 

        proto_tree_add_bitmask(foo_tree,tvb,offset,hf_foo_flags,ett_foo,bits,ENC_BIG_ENDIAN); 
        偏移+ = 1; 
        ... 
    ...
    return tvb_captured_length(tvb); 
} 

void 
proto_register_foo(void){ 
    ... 
        ... 
        {&hf_foo_startflag,
            {“FOO PDU Start Flags”,“foo.flags.start”,
            FT_BOOLEAN,8,
            NULL,FOO_START_FLAG,
            NULL,HFILL} 
        },
        {&hf_foo_endflag,
            { “FOO PDU End Flags”,“foo.flags.end”,
            FT_BOOLEAN,8,
            NULL,FOO_END_FLAG,
            NULL,HFILL} 
        },
        {&hf_foo_priorityflag,
            {“FOO PDU Priority Flags”,“foo.flags.priority”,
            NULL,FOO_PRIORITY_FLAG,
            NULL,HFILL} 
        } 
        ... 
    ... 
} 
...

这里要注意一些事情。对于标志,因为每个位都是不同的标志,所以我们使用该类型FT_BOOLEAN,因为标志是打开或关闭的。其次,我们在数据的第7个字段中包含标志掩码,这允许系统屏蔽相关位。我们还将第5个字段更改为8,表示在提取标志时我们正在查看8位数。最后,我们将额外的构造添加到解剖例程中。

现在看起来已经开始看起来相当全面,但我们还可以采取其他一些措施让事情看起来更漂亮。目前,我们的解剖显示数据包为“Foo协议”,虽然正确但有点无法提供信息。我们可以通过添加更多细节来增强这一点。首先,让我们掌握协议类型的实际价值。我们可以使用方便的功能 tvb_get_guint8()来完成这项工作。有了这个价值,我们可以做一些事情。首先,我们可以设置非详细视图的INFO列,以显示它是什么类型的PDU - 这在查看协议跟踪时非常有用。其次,我们还可以在解剖窗口中显示此信息。

 

例9.12。增强显示效果。

static int 
dissect_foo(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,void * data _U_)
{ 
    gint offset = 0; 
    guint8 packet_type = tvb_get_guint8(tvb,0); 

    col_set_str(pinfo-> cinfo,COL_PROTOCOL,“FOO”); 
    / *清除信息列中的内容* / 
    col_clear(pinfo-> cinfo,COL_INFO); 
    col_add_fstr(pinfo-> cinfo,COL_INFO,“Type%s”,
             val_to_str(packet_type,packettypenames,“Unknown(0x%02x)”)); 

    proto_item * ti = proto_tree_add_item(tree,proto_foo,tvb,0,-1,ENC_NA); 
    proto_item_append_text(ti,“,Type%s”,
        val_to_str(packet_type,packettypenames,“Unknown(0x%02x)”));
    proto_tree * foo_tree = proto_item_add_subtree(ti,ett_foo); 
    proto_tree_add_item(foo_tree,hf_foo_pdu_type,tvb,offset,1,ENC_BIG_ENDIAN); 
    偏移+ = 1; 

    return tvb_captured_length(tvb); 
}

 

因此,在获取前8位的值之后,我们将其与内置的实用程序例程一起使用val_to_str(),以查找该值。如果找不到该值,我们提供一个后备,只打印十六进制值。我们使用它两次,一次在列的INFO字段中 - 如果它显示,同样我们将这些数据附加到我们的解剖树的基础。

 

转载于:https://my.oschina.net/u/3904422/blog/2251277

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值