XuQi's Blog

  • 首页

  • 归档

wireshark插件开发之vsomeip协议

发表于 2019-07-05 更新于 2019-10-20

wireshark插件开发之vsomeip协议

XuQi 2019/07/08

Lua语法介绍

Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

Lua 数据类型

Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。

数据类型 描述
nil 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。
userdata 表示任意存储在变量中的C数据结构
table Lua 中的表(table)其实是一个”关联数组”(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过”构造表达式”来完成,最简单构造表达式是{},用来创建一个空表。
Lua 变量
单行注释
1
--
多行注释
1
2
3
4
--[[
多行注释
多行注释
--]]
变量定义和赋值
1
2
3
4
5
a = 5               -- 全局变量
local b = 5 -- 局部变量

a = "hello" .. "world"
t.n = t.n + 1
Lua循环
1
2
3
4
while( true )
do
print("循环将永远执行下去")
end
Lua 流程控制
1
2
3
4
5
--[ 0 为 true ] 控制结构的条件表达式结果可以是任何值,Lua认为false和nil为假,true和非nil为真。
if(0)
then
print("0 为 true")
end
Lua 函数
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
myprint = function(param)
print("这是打印函数 - ##",param,"##")
end

function add(num1,num2,functionPrint)
result = num1 + num2
-- 调用传递的函数参数
functionPrint(result)
end
myprint(10)
-- myprint 函数作为参数传递
add(2,5,myprint)

-- 可变参数
function add(...)
local s = 0
for i, v in ipairs{...} do --> {...} 表示一个由所有变长参数构成的数组
s = s + v
end
return s
end
print(add(3,4,5,6,7)) --->25

--多返回值
function maximum (a)
local mi = 1 -- 最大值索引
local m = a[mi] -- 最大值
for i,val in ipairs(a) do
if val > m then
mi = i
m = val
end
end
return m, mi
end

print(maximum({8,10,23,12,5}))
Lua 字符串
  • 单引号间的一串字符。

  • 双引号间的一串字符。

  • [[和]]间的一串字符。

1
2
3
4
5
6
7
string1 = "Lua"
print("\"字符串 1 是\"",string1)
string2 = 'runoob.com'
print("字符串 2 是",string2)

string3 = [["Lua 教程"]]
print("字符串 3 是",string3)
字符串操作

string.format(…)
返回一个类似printf的格式化字符串

1
2
> string.format("the value is:%d",4)
the value is:4

string.char(arg) 和 string.byte(arg[,int])
char 将整型数字转成字符并连接, byte 转换字符为整数值(可以指定某个字符,默认第一个字符)。

1
2
3
4
5
6
7
> string.char(97,98,99,100)
abcd
> string.byte("ABCD",4)
68
> string.byte("ABCD")
65
>

..
链接两个字符串

1
2
> print("www.runoob.".."com")
www.runoob.com
Lua 数组
1
2
3
4
5
array = {"Lua", "Tutorial"}

for i= 0, 2 do
print(array[i])
end

输出

1
2
3
nil
Lua
Tutorial

正如你所看到的,我们可以使用整数索引来访问数组元素,如果知道的索引没有值则返回nil。

在 Lua 索引值是以 1 为起始,但你也可以指定 0 开始。

Lua 迭代器
1
2
3
4
5
6
array = {"Google", "Runoob"}

for key,value in ipairs(array)
do
print(key, value)
end

输出

1
2
1  Google
2 Runoob
Lua table(表)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 初始化表
mytable = {}

-- 指定值
mytable[1]= "Lua"

-- 移除引用
mytable = nil
-- lua 垃圾回收会释放内存

-- 简单的 table
mytable[1]= "Lua"
mytable["wow"] = "修改前"
print("mytable 索引为 1 的元素是 ", mytable[1])
print("mytable 索引为 wow 的元素是 ", mytable["wow"])
Lua 模块与包
定义模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}

-- 定义一个常量
module.constant = "这是一个常量"

-- 定义一个函数
function module.func1()
io.write("这是一个公有函数!\n")
end

local function func2()
print("这是一个私有函数!")
end

function module.func3()
func2()
end

return module
加载模块
1
2
3
4
5
6
7
-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")

print(module.constant)

module.func3()
加载C库

如果加载动态库或者查找初始化函数时出错,loadlib 将返回 nil 和错误信息,使其检测错误然后调用初始化函数

1
2
3
4
local path = "/usr/local/lua/lib/libluasocket.so"
-- 或者 path = "C:\\windows\\luasocket.dll",这是 Window 平台下
local f = assert(loadlib(path, "luaopen_socket"))
f() -- 真正打开库

SOME/IP简介

SOME/IP = “Scalable service-Oriented middleware over IP” 在IP上,可扩展的面向服务的中间件

1562299432958

1562299029209

SOME/IP其实是构架在传输层之上的应用层通信协议,它的内容虽然很多很杂,但本质上也就是定义了SOME/IP 包头和数据的内容而已。

SOME/IP On-Wire Format

1562298780881

  • Service ID: 每个服务的唯一标识符

  • Method ID: 0-32767 用于 methods, 32768-65535 用于 events

  • Length: 以字节为单位的有效负载长度(也包括下一个ID,表示另外8个字节)

  • Client ID: 客户端的唯一标识符;必须在整个车辆中独一无二

  • Session ID: 会话处理的标识符;每次调用递增

  • Protocol Version: 0x01

  • Interface Version: 服务接口的主要版本

  • Message Type:

    • REQUEST (0x00) 期待响应请求,即使是空的

    • REQUEST_NO_RETURN (0x01) 不期待响应请求

    • NOTIFICATION (0x02) 事件通知,不需要回复

    • RESPONSE (0x80) 响应消息

      …

  • Return Code:

    • E_OK (0x00) 没有错误

    • E_NOT_OK (0x01) 有错误

      …

NOTIFICATION详解

NOTIFICATION属于事件通知类的服务,首先由client向server订阅服务内容,然后server向client自动发布服务内容。

1562300682790

NOTIFICATION又分为Event和Field 两类,这两类通知都需要首先使用SOME/IP-SD(Service Discovery)来进行服务订阅,然后才能发布通知。

区别在于,Event是某一时刻的快照,只是事件通知,而Field除了事件通知之外,还具有Getter和Setter的功能,即对信息进行读写的操作。

1562300743413

SOME/IP Service discovery (发现服务)

SOME/IP-SD提供了两种动态发现服务的机制:

  • Offer Service ,由server向网络上的小伙伴告知它所提供的服务;

  • Find Service ,由client向别人请求可用的服务。

SOME/IP-SD可以被当作SOME/IP的一种特殊服务,client可以远程调用server提供的服务,或者订阅server发布的内容,那么client是怎么知道server提供哪些服务呢,就是通过SOME/IP-SD来实现服务发现过程的。

它对SOME/IP-SD报文中的Payload进行了定义和实现。而Message ID字段则是固定的0xFF FF 81 00。

SOME/IP-SD提供了两种动态发现服务的机制。一种是Offer Service ,由server向网络上的小伙伴告知它所提供的服务;另一种是Find Service ,由client向别人请求可用的服务。

wireshark 插件开发

wireshark提供了灵活的插件机制,使用户可以方便地扩展wireshark的功能。插件的功能主要包括,但不限于协议解析器。

可以使用Lua或C语言来编写Wireshark插件,下表对比了这两种方式,绿色背景代表占优的一方。

1562293564841

Wireshark对Lua的支持

启动wireshark,依次点击“Help”,”About Wireshark“菜单,在打开的对话框中的”Wireshark”标签页上观察版本信息,如果如下图一样显示With Lua,说明此版本支持Lua插件。

1562293578821

然后打开wireshark主目录下的init.lua文件,确保disable_lua的值为false,即开启了lua:

1562293734094

在Wireshark中,可以使用Lua编写以下几种插件:

  • Dissectors 协议解析器,用于解析报文
  • *Post-dissectors *后置解析器,在其他解析器之后被调用
  • *Listeners *监听器,用来收集解析后的信息

注意

  1. wireshark启动时,会调用下图目录中的init.lua,顺序是先调用global目录的,再调用personal目录的。

    修改init.lua,追加以下

    1
    dofile(DATA_DIR.."ipdata.lua")

1562293950521

  1. 通过命令行参数:-X lua_script:my.lua传入的my.lua将会在Init.lua之后被调用。
  2. 所有的Lua脚本会在解析器注册过程的最后被调用,而这一过程是在wireshark启动时就发生的,早于报文被读取的时刻

Lua插件API简介

ProtoField

表示协议字段,一般用于解析字段后往解析树上添加节点。根据字段类型不同,其接口可以分为两大类。

整型:
• ProtoField.{type} (abbr, [name], [desc],[base], [valuestring], [mask])
type包括:uint8, uint16, uint24, uint32, uint64, framenum

1
local f_msg_id      = ProtoField.uint32("someip.messageid","MessageID",base.HEX)

其他类型
• ProtoField.{type} (abbr, [name], [desc])
type包括:float, double, string, stringz, bytes, bool, ipv4, ipv6, ether,oid, guid

这些接口都会返回一个新的字段对象。方括号内是可选字段,花括号内是可替换的类型字段。

Proto

表示一个新的Protocol,在Wireshark中Protocol对象有很多用处,解析器是其中主要的一个。主要接口有:

接口 说明
proto:__call (name,desc) 创建Proto对象。name和desc分别是对象的名称和描述,前者可用于过滤器等
proto.name get名称
proto.fields get/set字段
proto.prefs get配置项
proto.init() 初始化函数,无参数
proto.dissector(tvb,pinfo,tree) 解析函数,3个参数tvb,pinfo,tree,分别是报文内容,报文信息和解析树结构
1
p_someip = Proto("IPData","SOME/IP") -- 创建Proto对象
1
pinfo.cols.protocol = p_someip.name -- 列名为IPData
1
p_someip.fields = {f_msg_id}-- 定义ProtoField
1
2
3
-- initialization routine
function p_someip.init()
end
1
2
3
-- dissection function
function p_someip.dissector(buf,pinfo,root)
end
Tvb

Tvb(Testy Virtual Buffer)表示报文缓存,也就是实际的报文数据,可以通过下面介绍的TvbRange从报文数据中解出信息。主要接口有:

接口 说明
tvb:__tostring() 将报文数据转化为字符串,可用于调试
tvb:reported_len() get tvb的(not captured)长度
tvb:len() get tvb的(captured)长度
tvb:reported_length_remaining() 获取当前tvb的剩余长度,如果偏移值大于报文长度,则返回-1
tvb:offset() 返回原始偏移
TvbRange

表示Tvb的可用范围,常用来从Tvb中解出信息。主要接口有

接口 说明
tvb:range([offset], [length]) 从tvb创建TvbRange,可选参数分别是偏移和长度,默认值分别是0和总长度
tvbrange:{type}() 将tvbrange所表示范围内的数据转换成type类型的值,type包括但不限于:uint,uint64,int,int64,float,ipv4,ether,nstime,string,ustring,bytes,bitfield等,其中某些类型的方法可以带一些参数
Pinfo

报文信息(packet information)。主要接口有:

接口 说明
pinfo.len pinfo.caplen get报文长度
pinfo.abs_ts get报文捕获时间
pinfo.number get报文编号
pinfo.src pinfo.dst get/set报文的源地址、目的地址
pinfo.columns pinfo.cols get报文列表列(界面)

取得报文列表列后,就可以设置该列的文本,比如

pinfo.cols.info = “hello world”

将Info列的文本设为hello world。

TreeItem

表示报文解析树中的一个树节点。主要接口有:

接口 说明
treeitem:add([protofield], [tvbrange], [value], [label]) 向当前树节点添加一个子节点
treeitem:set_text(text) 设置当前树节点的文本
treeitem:prepend_text(text) 在当前树节点文本的前面加上text
treeitem:append_text(text) 在当前树节点文本的后面加上text
DissectorTable

表示一个具体协议的解析表,比如,协议TCP的解析表”tcp.port”包括http,smtp,ftp等。可以依次点击wireshark菜单“Internals”、“Dissector tables”,来查看当前的所有解析表。tcp.port解析表在“Integer tables”选项卡中,顾名思义,它是通过类型为整型的tcp端口号来识别下游协议的:

1562297940288

DissectorTable的主要接口有:

接口 说明
DissectorTable.get(name) get名为name的解析表的引用
dissectortable:add(pattern, dissector) 将Proto或Dissector对象添加到解析表,即注册。pattern可以是整型值,整型值范围或字符串,这取决于当前解析表的类型
dissectortable:remove(pattern, dissector) 将满足pattern的一个或一组Proto、Dissector对象从解析表中删除
插件骨架
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- create a new dissector
p_someip = Proto("IPData","SOME/IP") -- 创建Proto对象

-- dissect packet
function p_someip.dissector(buf,pinfo,root) -- 解析协议

end

-- register this dissector
function p_someip.init() -- 注册解析器
-- register protocol
local udp_dissector_table = DissectorTable.get("udp.port")
local tcp_dissector_table = DissectorTable.get("tcp.port")

-- Register dissector to multiple ports
for i,port in ipairs{55000} do
udp_dissector_table:add(port,p_someip)
tcp_dissector_table:add(port,p_someip)
end
end

1562302859400

完善骨架中的内容
定义协议中各个字段的名称
1
2
3
4
5
6
7
8
9
10
local f_msg_id      = ProtoField.uint32("someip.messageid","MessageID",base.HEX)
local f_len = ProtoField.uint32("someip.length","Length",base.HEX)
local f_req_id = ProtoField.uint32("someip.requestid","RequestID",base.HEX)
local f_pv = ProtoField.uint8("someip.protoversion","ProtocolVersion",base.HEX)
local f_iv = ProtoField.uint8("someip.ifaceversion","InterfaceVersion",base.HEX)
local f_mt = ProtoField.uint8("someip.msgtype","MessageType",base.HEX)
local f_rc = ProtoField.uint8("someip.returncode","ReturnCode",base.HEX)
local f_payload = ProtoField.bytes("someip.payload","Payload")

p_someip.fields = {f_msg_id,f_len,f_req_id,f_pv,f_iv,f_mt,f_rc, f_offset, f_reserved, f_more_seg,f_payload}

1562303216672

过滤消息的方法

依据 ProtoField.uint32(“someip.length“,”Length”,base.HEX)

1562303573720

解析消息内容

1562315812364

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
42
43
44
45
46
47
48
49
50
51
52
53
local band,bor = bit.band,bit.bor -- 与和或
local lshift, rshift = bit.lshift,bit.rshift -- 左右移位操作
local tohex = bit.tohex -- 转成16进制

--fields functions
function field_msgid(subtree,buf,pinfo)
msg_id = subtree:add(f_msg_id,buf(0,4)) -- 0开始,4个字节为message id,名称为f_msg_id
local msg_id_uint = buf(0,4):uint() -- 0开始,4个字节的内容转化为unsigned int

msg_id:append_text( " ("..tohex(buf(0,2):uint(),4)..
":"..band(rshift(msg_id_uint,15),0x01).. -- 右移15位,与0x01做与操作
":"..tohex(band(msg_id_uint,0x7fff),4)..")") -- 0x7fff做与操作
msg_id:add("service_id : "..tohex(buf(0,2):uint(),4)) -- 0开始,2个字节格式化为4位的十六进制
end

-- dissection function
function p_someip.dissector(buf,pinfo,root)
pinfo.cols.protocol = p_someip.name -- 消息协议的列为

-- Header Start
subtree = root:add(p_someip,buf(0)) -- 添加一个节点,名称为SOME/IP
-- Message ID 32bit
field_msgid(subtree,buf,pinfo) -- 取32bit,用于Message ID

-- Length 32bit
subtree:add(f_len,buf(4,4)) -- 4字节开始,取4个字节
-- request ID 32bit
field_reqid(subtree,buf)
-- Protocol Version 8bit
subtree:add(f_pv,buf(12,1))
-- Interface Version 8bit
subtree:add(f_iv,buf(13,1))

-- Message type 8bit
local type = subtree:add(f_mt,buf(14,1))
if msg_types[buf(14,1):uint()] ~= nil then
type:append_text(" (" .. msg_types[buf(14,1):uint()] ..")")
end

-- Return Code 8bit
local rcode = subtree:add(f_rc,buf(15,1))
if ret_codes[buf(15,1):uint()] ~= nil then
rcode:append_text(" (" .. ret_codes[buf(15,1):uint()] ..")")
end
-- Header End

-- SD payload --

if (buf(0,4):uint() == 0xffff8100) and (buf:len() > SOMEIP_SD_OFFSET) then
Dissector.get("sd"):call(buf(SOMEIP_SD_OFFSET):tvb(),pinfo,root) -- 调用另外一个sd.lua文件,把buf(SOMEIP_SD_OFFSET)后的数据,传给sd.lua的dissector
end

end

调试

在代码中加入

1
debug(elementName)

1562546595082

1562546622875

# wireshark,vsomeip,lua
鱼骨图分析法
C-V2X白皮书
  • 文章目录
  • 站点概览

XuQi

44 日志
30 标签
  1. 1. wireshark插件开发之vsomeip协议
    1. 1.0.1. Lua语法介绍
      1. 1.0.1.1. Lua 数据类型
      2. 1.0.1.2. Lua 变量
        1. 1.0.1.2.1. 单行注释
        2. 1.0.1.2.2. 多行注释
        3. 1.0.1.2.3. 变量定义和赋值
      3. 1.0.1.3. Lua循环
      4. 1.0.1.4. Lua 流程控制
      5. 1.0.1.5. Lua 函数
      6. 1.0.1.6. Lua 字符串
        1. 1.0.1.6.1. 字符串操作
      7. 1.0.1.7. Lua 数组
      8. 1.0.1.8. Lua 迭代器
      9. 1.0.1.9. Lua table(表)
      10. 1.0.1.10. Lua 模块与包
        1. 1.0.1.10.1. 定义模块
        2. 1.0.1.10.2. 加载模块
        3. 1.0.1.10.3. 加载C库
    2. 1.0.2. SOME/IP简介
      1. 1.0.2.1. SOME/IP On-Wire Format
      2. 1.0.2.2. SOME/IP Service discovery (发现服务)
    3. 1.0.3. wireshark 插件开发
      1. 1.0.3.1. Wireshark对Lua的支持
    4. 1.0.4. Lua插件API简介
      1. 1.0.4.1. ProtoField
      2. 1.0.4.2. Proto
      3. 1.0.4.3. Tvb
      4. 1.0.4.4. TvbRange
      5. 1.0.4.5. Pinfo
      6. 1.0.4.6. TreeItem
      7. 1.0.4.7. DissectorTable
      8. 1.0.4.8. 插件骨架
      9. 1.0.4.9. 完善骨架中的内容
        1. 1.0.4.9.1. 定义协议中各个字段的名称
        2. 1.0.4.9.2. 过滤消息的方法
        3. 1.0.4.9.3. 解析消息内容
    5. 1.0.5. 调试
© 2019 XuQi
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Muse v7.3.0