2010年10月6日星期三

onvif设备搜索小结

Onvif是新提出的一种监控协议框架标准,它利用了现有的一些成熟的web service技术,组成了一个完整的监控协议。其中,设备搜索功能就使用到了ws-discoverysoap技术。下面就主要对在研究onvif设备搜索时遇到的问题,做一个总结。

Onvif的设备搜索机制和我们目前前端设备的搜索基本类似,都是基于的组播搜索:发送一个搜索请求(Probe)到一个指定的组播地址,然后等待前端的单播搜索应答(PM: Probe Match)。关于前端的搜索应答,我们目前采用的是组播应答,可以搜索到不同网段内的前端设备;而Onvif则仅使用单播来做应答;这样,Onvif只能搜索到同一网段内的设备(Onvif还另外提供了一种DP: Discovery Proxy机制来实现搜索不同网段内的设备)。
此外,Onvif还提供了Hello(前端上线通告),Bye(前端下线通告)和Resolve消息(为了兼容ws-discovery,局域网内搜索基本用不到)。


了解了Onvif的搜索机制后,我们就可以具体实现了。首先获得标准的ws-discoverywsdl和一个gsoap开发环境,利用gsoap,我们可以很轻松的生成包含所有搜索接口的代码文件。(具体生产方法可参考之前同事的一篇案例《gSOAP开发WebService程序》)
生成的相关函数声明如下:


我们最关心的是这两个搜索函数:
负责发送搜索请求(Probe)的soap_send___tns__ProbeOp和负责接收搜索应答的(Probe Match)的soap_recv___tns__ProbeMatchOp,如下:



好,万事俱备,我们只需调用这两个接口就能实现搜索了。可惜,事实证明前途是光明的,道路是曲折的,在我们通过搜索获取到设备基本信息前,还得越过几道坎:
1.      Uuid
我们的程序第一次运行后,并没有出现我们期望的结果。不慌,首先抓包,看看我们的搜索请求有没有发出去,ok,抓包数据如下:

仔细分析了下我们的数据和Onvif官方提供的测试工具发出的搜索请求包,发现并没有少哪一个字段啊?并且所有涉及的命名空间也都一致,可就是我们发出的搜索请求后,一个前端应答都没有。那是不是消息中的哪个字段内容不对呢?回头再读文档,终于找到了这一段:

其中提到,为了防止被攻击,设备应该忽略带有重复messageID的请求,而对照我们的代码,其中的messageID字段是我们自己构造的,写死的,每次的请求都是一样的,自然就被前端给忽略掉了(也许有人说了,那第一次呢?对,我们第一次发请求时,前端其实是响应的,可惜,我们第一次搜索时没抓包啊!并且,可以想见,这个措施是有时效性的,只要我们一直发送请求,原来前端“记忆”的ID失效了,那就会认为是一个正常的请求而响应了)。

2.      AppSequence
问题定位了,那就好办了,网上找了下uuid的生成代码,其实就是一个时间和空间上全局唯一的号,每次发搜索请求时,生成一个uuid,然后填充到消息了。重新编译,再次运行程序,这次抓包终于看到响应了,前端回复了应答;可是再看我们的程序,还是没显示搜到的前端。应答包是收到了,可程序并没有返回结果,应该是程序内部出了问题,可这部分的工作其实是gsoap生成的代码完成的,难道只能单步跟踪吗?幸好,gsoap还提供了日志功能,先看看日志里能不能提供些有用的信息:
Unexpected element 'wsa:AppSequence' in input (level=2, 1)
比较明显的一处非正常提示,通过提示,找到了具体代码,看来是解析消息出错了,不认识“AppSequence”这个元素;再来看我们抓到的应答包,里面的确有这项数据:

代码是gsoap生成的,是生成代码有误,还是我们提供的源文件有问题?google了下,同时又看了下文档,才知道AppSequencesoap:Envelope::Head中的一个可选元素,但在标准的ws-discovery中的soap:Envelope::Head中是没有显式提供的,所以导致gsoap生成代码时没有包含对该元素的解析。我们只需要在soap:Envelope的定义中包含AppSequence即可:

#ifndef SOAP_TYPE_SOAP_ENV__Header
#define SOAP_TYPE_SOAP_ENV__Header (29)
/* SOAP Header: */
struct SOAP_ENV__Header
{
        char *wsa__MessageID; /* optional element of type wsa:MessageID */
        struct wsa__Relationship *wsa__RelatesTo;   /* optional element of type wsa:RelatesTo */
        struct wsa__EndpointReferenceType *wsa__From;   /* optional element of type wsa:From */
        struct wsa__EndpointReferenceType *wsa__ReplyTo;     /* mustUnderstand */
        struct wsa__EndpointReferenceType *wsa__FaultTo;     /* mustUnderstand */
        char *wsa__To;    /* mustUnderstand */
        char *wsa__Action;    /* mustUnderstand */
        struct tns__AppSequenceType tns__AppSequence;  /* mustUnderstand */
};
#endif
终于,经过修改的代码运行后,屏幕上出现了期望的打印:

说明下,由于Onvif搜索采用的ws-discovery技术,所以搜索到的不都是Onvif设备;只要是支持ws-discovery的设备都可以搜到,譬如上图中的172.16.200.75就是一台WIN7系统的PC

3.      又是AppSequence
平静没几天,又出问题了:上次测的是一款AXIS设备,这次换成了另一款SONY的设备,发现搜不到了,抓包看了下,没有搜索应答包;又是我们的搜索请求内容不符合规范吗?可究竟是哪儿呢?恰巧,Onvif协议的设备通信部分也基本做好了,可以获取前端的一些其他信息了,于是试了下这款SONY设备,看基于TCPHttp交互信息能否成功;结果是:获取同样失败了,不过,这次tcp交互却把错误原因显示了出来:
<SOAP-ENV:Fault SOAP-ENV:encodingStyle="http://www.w3.org/2003/05/soap-encoding"><SOAP-ENV:Code><SOAP-ENV:Value>SOAP-ENV:MustUnderstand</SOAP-ENV:Value></SOAP-ENV:Code><SOAP-ENV:Reason><SOAP-ENV:Text xml:lang="en">The data in element 'wsa:AppSequence' must be understood but cannot be handled</SOAP-ENV:Text></SOAP-ENV:Reason></SOAP-ENV:Fault>
找到问题所在了,又是AppSequence惹得祸;分析了下错误信息:原来,AppSequence是一个可选项(optional),而我们在上次改动时,却把AppSequence设置成了必须项(mustUnderstand),导致发给前端后,前端发现解析不了这个mustUnderstand项,所以导致后续的失败(也就是说SONY的设备并不关心这个AppSequence)。修改也很简单,把mustUnderstand去掉即可,即变成:
struct tns__AppSequenceType tns__AppSequence; /* optional element of type tns:AppSequence */

1.      Onvif提供的搜索机制和我们原先的搜索机制有很大的不同,其中消息内容中的很多字段都有一定用途,需要很好的理解。
2.      gsoap还是很强大的,不仅体现在快速开发上,一些错误处理机制,也使开发人员能比较容易定位问题。
3.      Onvif除了搜索机制外,还涉及了许多其他web service服务,特别是安全机制,对比我们目前的协议,可以说基本没有考虑安全,所以,值得好好研究。