作者简介:吴琪 2015.09-至今 于北京邮电大学信息光子学与光通信国家重点实验室攻读硕士研究生
1 简介
在写SDN控制器的应用或者修改控制器的源码的时候,经常需要抓包分析,验证流表的结果以及网络功能等,众所周知,wireshark是一个很好的分析包的网络工具,下面就要从源码的角度介绍wireshark对OpenFlow1.0协议的解析,用于更好的理解OpenFlow协议和wireshark对其分析的过程,同时为后续OpenFlow协议扩展的抓包分析做准备。
2 源码分析
目前,高版本的wireshark已经自带OpenFlow协议的解析程序了,这里我们分析wireshark2.0.0,源码链接:http://pan.baidu.com/s/1sktyBvB。
在wireshark源码根目录下有一个epan文件夹,这个文件夹就是负责所有网络协议的识别工作。epan文件夹下有一个dissectors文件夹,这个文件夹就是各种协议的解码器,其中涉及到OpenFlow1.0协议的解码器主要是packet-OpenFlow_v1.c文件。这个文件主要包含了三个部分:协议涉及的静态变量的定义、OpenFlow包各部分的解析、OpenFlow协议的注册。
2.1 静态变量的定义
OpenFlow协议包括很多内容,wireshark根据OpenFlow协议的定义,定义了很多静态变量,比如OpenFlow协议的版本、action的类型、action域的各个字段、match域的各个字段等,由于内容比较多,我们就以Modify-Field行动为例:
1 2 3 4 5 6 7 8 9 10 11 12 |
static int hf_openflow_output = -1; /* Output to switch port. */ static int hf_openflow_set_vlan_vid = -1; /* Set the 802.1q VLAN id. */ static int hf_openflow_set_vlan_pcp = -1; /* Set the 802.1q priority. */ static int hf_openflow_strip_vlan = -1; /* Strip the 802.1q header. */ static int hf_openflow_set_dl_src = -1; /* Ethernet source address. */ static int hf_openflow_set_dl_dst = -1; /* Ethernet destination address. */ static int hf_openflow_set_nw_src = -1; /* IP source address. */ static int hf_openflow_set_nw_dst = -1; /* IP destination address. */ static int hf_openflow_set_nw_tos = -1; /* IP ToS (DSCP field, 6 bits). */ static int hf_openflow_set_tp_src = -1; /* TCP/UDP source port. */ static int hf_openflow_set_tp_dst = -1; /* TCP/UDP destination port. */ static int hf_openflow_enqueue = -1; /* Output to queue. */ |
将静态变量先赋值为-1,在后面的解析过程中再将解析结果赋值给相应的字段。
2.2 包的解析
这里我们首先先解释下这部分的代码结构,其中解析代码的主函数是dissector_openflow_v1(),首先会解析出8byte长度的openflow协议头部,包括version、length等信息。然后会根据不同的消息类型解析调用不同的解析函数,我们以FLOW_MOD消息中match域的解析为例。其中match域的解析函数是dissect_openflow_ofp_match_v1()。match域的长度一共是40byte,结构如下所示:
在解析match域的时候,就是根据各个字段的长度一步步解析,其中代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
static int dissect_openflow_ofp_match_v1(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset) { /* uint32_t wildcards; Wildcard fields. */ proto_tree_add_item(tree, hf_openflow_wildcards, tvb, offset, 4, ENC_BIG_ENDIAN); offset+=4; /* uint16_t in_port; Input switch port. */ proto_tree_add_item(tree, hf_openflow_in_port, tvb, offset, 2, ENC_BIG_ENDIAN); offset+=2; /* uint8_t dl_src[OFP_ETH_ALEN]; Ethernet source address. */ proto_tree_add_item(tree, hf_openflow_eth_src, tvb, offset, 6, ENC_NA); offset+=6; /* uint8_t dl_dst[OFP_ETH_ALEN]; Ethernet destination address. */ proto_tree_add_item(tree, hf_openflow_eth_dst, tvb, offset, 6, ENC_NA); offset+=6; /* uint16_t dl_vlan; Input VLAN id. */ proto_tree_add_item(tree, hf_openflow_dl_vlan, tvb, offset, 2, ENC_BIG_ENDIAN); offset+=2; /* uint8_t dl_vlan_pcp; Input VLAN priority. */ proto_tree_add_item(tree, hf_openflow_dl_vlan_pcp, tvb, offset, 1, ENC_BIG_ENDIAN); offset++; /* uint8_t pad1[1]; Align to 64-bits */ proto_tree_add_item(tree, hf_openflow_padd8, tvb, offset, 1, ENC_BIG_ENDIAN); offset++; /* uint16_t dl_type; Ethernet frame type. */ /* uint8_t nw_tos; IP ToS (actually DSCP field, 6 bits). */ /* uint8_t nw_proto; IP protocol or lower 8 bits of * ARP opcode. */ /* uint8_t pad2[2]; Align to 64-bits */ /* uint32_t nw_src; IP source address. */ /* uint32_t nw_dst; IP destination address. */ /* uint16_t tp_src; TCP/UDP source port. */ /* uint16_t tp_dst; TCP/UDP destination port. */ proto_tree_add_text(tree, tvb, offset, 18, "Data not dissected yet"); offset +=18; return offset; } |
其中,解析过程是树状的,proto_tree_add_item()函数用于添加新的树节点,它包含6个参数,第一个参数是被添加节点的树,第二个参数是解析之后的结果,同时控制此项的格式,第三个参数“tvb”是传入的缓冲数据,第四个参数“offset”用于数据包解析的位置,第五个参数表示该字段需解析的长度,这和协议定义的该字段的长度一致,同时这个值和它下面offset的变化值保持一致,最后一个字段是网络字节序或编码参数。从上面的代码可以发现,只解析了前22byte,即只解析到pad1字段,剩余的12byte没有解析,只用了proto_tree_add_text()函数添加了新的文本“Data not dissected yet”。要是有需要的话,也可以自己添上剩余12byte字段的解析。
2.3 协议注册
proto_register_openflow_v1()函数是注册的主函数,其中proto_register_protocol()函数用来注册协议,可以给它3个名字用于在不同的地方显示:“Preferences”、“Enabled protocols”以及过滤器。
1 2 3 |
/* Register the protocol name and description */ proto_openflow_v1 = proto_register_protocol("OpenFlow 1.0", "openflow_v1", "openflow_v1"); |
这部分代码还定义了两个数组:hf[]和*ett[],这两个数组在proto_register_protocol()调用之后被注册:
1 2 3 |
/* Required function calls to register the header fields and subtrees */ proto_register_field_array(proto_openflow_v1, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); |
这两个数组中的变量就是2.1部分阐述的变量,我们分析其中的一个变量:
1 2 3 4 5 |
{ &hf_openflow_wildcards, { "Wildcards", "openflow.wildcards", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } } |
其中hf_openflow_wildcards是此节点的索引,它的值就是2.2部分解析时添加的;Wildcards是此项的标识;openflow.wildcards是过滤用的字符串;FT_UIN32指出此项是一个32bit的无符号整数;BASE_DEC代表对于整型来说,它令其打印为一个10进制数,这一项还可以是BASE_HEX(16进制)或BASE_OCT(8进制)。
将协议涉及到所有变量添加在这两个数组中,就实现注册了。
3 总结
以上就是wireshark对openflow1.0协议解析的代码了,当我们需要对openflow协议做扩展的时候,我们可以在这个文件的基础上添加相应的扩展内容的解析,就能实现扩展数据包的分析。