ONVIF学习笔记11:搜索设备不匹配问题排查

Posted 李迟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ONVIF学习笔记11:搜索设备不匹配问题排查相关的知识,希望对你有一定的参考价值。

上次移植的系统,编译加载都没问题,但搜索 onvif 设备失败了,经查,根本原因是 gsoap 版本不匹配导致。本文记录分析的过程。

问题

上半年进行一款飞腾板子的应用层系统移植,由于优先级不搞,加之有大量其它事务,断断续续地搞,至上个月终于有了阶段性结果,但遗留了一个问题。当时解决了封装的 onvif 相关的视频动态库编译和加载问题后,以为没问题了,测试发现 Qt 界面没有显示视频,分析日志,原来是视频动态库搜索不到设备。

首先想到网络问题,经分析排除掉。接着抓包分析,能收到回应包。再跟踪源码,最终定位到 gsoap 版本不匹配问题。

过程

原问题重现

其实之前解决加载问题本身就有问题,当时是其它部门提供 gsoap 库,但是是最新的2.8.122版本,因为找不到2.8.90版本,而直接连接新版本库链接时会报如下错误:

/home/latelee/work/videolib-debug/onvif/CMySoap.c:72: undefined reference to `soap_new_REQUIRE_lib_v20890'

经分析,和版本号有关的代码片段如下:

// stdsoap2.h
#define GSOAP_VERSION 20890

// soapStub.h
#include "stdsoap2.h"
#if GSOAP_VERSION != 20890
# error "GSOAP VERSION 20890 MISMATCH IN GENERATED CODE VERSUS LIBRARY CODE: PLEASE REINSTALL PACKAGE"
#endif

// stdsoap2.h
#define soap_versioning_paste(name, ext) name##_REQUIRE_lib_v##ext
#define soap_versioning_ext(name, ext) soap_versioning_paste(name, ext)
#define soap_versioning(name) soap_versioning_ext(name, GSOAP_VERSION)

#define soap_init(soap) soap_init1(soap, SOAP_IO_DEFAULT)
#define soap_init1(soap, mode) soap_init2(soap, mode, mode)
#define soap_init2(soap, imode, omode) soap_versioning(soap_init)(soap, imode, omode)

#define soap_new() soap_new1(SOAP_IO_DEFAULT)
#define soap_new1(mode) soap_new2(mode, mode)
#define soap_new2(imode, omode) soap_versioning(soap_new)(imode, omode)

可以看到,在调用soap_newsoap_init时,会生成和版本号有关的函数,对于 2.9.90 版本,是soap_new_REQUIRE_lib_v20890,而2.9.122,则是soap_new_REQUIRE_lib_v208122。为解决链接问题,将代码中的版本号宏定义改为最新的版本号,如此一来,加载正常。

相机端确认

因为相机并不是生产环境使用的,因此需要首先确认相机是否能正常提供 onvif 功能。将相机与电脑直连,修改IP,再找到当年测试 onvif 的工具 odtt,安装不了,但 odm 能安装成功,打开搜索,搜索不到,手动指定 IP,可找到,观察视频也正常。获取到的地址为:

http://192.168.18.168:80/onvif/device_service

设备上抓包

相机端确认正常后,接着怀疑是网络问题,因为板子上有2个网关,测试时,2个网段都在工作。为保证网络环境纯粹性,禁用另一网关,并设置默认环境,板子系统上已有tcpdump命令,用如下命令抓包:

tcpdump -i eth0 -w result.cap

将抓包文件用 scp 传输到本地,用 wireshark 工具分析,发现搜索包和回应包都有,xml 内容也是正常的。

发送包如下:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsdd="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:chan="http://schemas.microsoft.com/ws/2005/02/duplex" xmlns:wsa5="http://www.w3.org/2005/08/addressing" xmlns:c14n="http://www.w3.org/2001/10/xml-exc-c14n#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml1="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" xmlns:wsc="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:xmime="http://tempuri.org/xmime.xsd" xmlns:xop="http://www.w3.org/2004/08/xop/include" xmlns:tt="http://www.onvif.org/ver10/schema" xmlns:wsrfbf="http://docs.oasis-open.org/wsrf/bf-2" xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" xmlns:wstop="http://docs.oasis-open.org/wsn/t-1" xmlns:tdn="http://www.onvif.org/ver10/network/wsdl" xmlns:tds="http://www.onvif.org/ver10/device/wsdl" xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl" xmlns:trt="http://www.onvif.org/ver10/media/wsdl"><SOAP-ENV:Header><wsa:MessageID/><wsa:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action></SOAP-ENV:Header><SOAP-ENV:Body><wsdd:Probe><wsdd:Types/><wsdd:Scopes/></wsdd:Probe></SOAP-ENV:Body></SOAP-ENV:Envelope>

回应包:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
    xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
    xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:c14n="http://www.w3.org/2001/10/xml-exc-c14n#"
    xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
    xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
    xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
    xmlns:wsadis="http://schemas.xmlsoap.org/ws/2004/08/addressing"
    xmlns:wsrp="http://schemas.xmlsoap.org/rp/"
    xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery"
    xmlns:wsa5="http://www.w3.org/2005/08/addressing"
    xmlns:xmime="http://www.w3.org/2005/05/xmlmime"
    xmlns:xop="http://www.w3.org/2004/08/xop/include"
    xmlns:ter="http://www.onvif.org/ver10/error"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:soap12="http://tempuri.org/soap12.xsd"
    xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
    xmlns:tnsn="http://www.eventextension.com/2011/event/topics"
    xmlns:tt="http://www.onvif.org/ver10/schema"
    xmlns:wsrfbf="http://docs.oasis-open.org/wsrf/bf-2"
    xmlns:wstop="http://docs.oasis-open.org/wsn/t-1"
    xmlns:tns1="http://www.onvif.org/ver10/topics"
    xmlns:wsrfr="http://docs.oasis-open.org/wsrf/r-2"
    xmlns:dndl="http://www.onvif.org/ver10/network/wsdl/DiscoveryLookupBinding"
    xmlns:dnrd="http://www.onvif.org/ver10/network/wsdl/RemoteDiscoveryBinding"
    xmlns:dn="http://www.onvif.org/ver10/network/wsdl"
    xmlns:tds="http://www.onvif.org/ver10/device/wsdl"
    xmlns:tevcp="http://www.onvif.org/ver10/events/wsdl/CreatePullPointBinding"
    xmlns:teve="http://www.onvif.org/ver10/events/wsdl/EventBinding"
    xmlns:tevp="http://www.onvif.org/ver10/events/wsdl/PullPointBinding"
    xmlns:tev="http://www.onvif.org/ver10/events/wsdl"
    xmlns:tevps="http://www.onvif.org/ver10/events/wsdl/PullPointSubscriptionBinding"
    xmlns:tevpsm="http://www.onvif.org/ver10/events/wsdl/PausableSubscriptionManagerBinding"
    xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2"
    xmlns:tevsm="http://www.onvif.org/ver10/events/wsdl/SubscriptionManagerBinding"
    xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl"
    xmlns:timg10="http://www.onvif.org/ver10/imaging/wsdl"
    xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl"
    xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl"
    xmlns:tptz10="http://www.onvif.org/ver10/ptz/wsdl"
    xmlns:tr2="http://www.onvif.org/ver20/media/wsdl"
    xmlns:trc="http://www.onvif.org/ver10/recording/wsdl"
    xmlns:trp="http://www.onvif.org/ver10/replay/wsdl"
    xmlns:trt="http://www.onvif.org/ver10/media/wsdl"
    xmlns:tse="http://www.onvif.org/ver10/search/wsdl">
    <SOAP-ENV:Header>
        <wsadis:MessageID>urn:uuid:5555f550-5535-35f6-ec55-952cbd3d45191</wsadis:MessageID>
        <wsadis:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsadis:To>
        <wsadis:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsadis:Action>
        <d:AppSequence InstanceId="0" MessageNumber="10"></d:AppSequence>
    </SOAP-ENV:Header>
    <SOAP-ENV:Body>
        <d:ProbeMatches>
            <d:ProbeMatch>
                <wsadis:EndpointReference>
                    <wsadis:Address>urn:uuid:5555f550-5535-35f6-ec55-952cbd3d45191</wsadis:Address>
                </wsadis:EndpointReference>
                <d:Types>dn:NetworkVideoTransmitter tds:Device</d:Types>
                <d:Scopes>onvif://www.onvif.org/type/video_encoder onvif://www.onvif.org/type/ptz onvif://www.onvif.org/type/audio_encoder onvif://www.onvif.org/location/city/hangzhou onvif://www.onvif.org/Profile/Streaming onvif://www.onvif.org/Profile/G onvif://www.onvif.org/Profile/T onvif://www.onvif.org/hardware/D2150-10-SIU onvif://www.onvif.org/name/D2150-10-SIU </d:Scopes>
                <d:XAddrs>http://192.168.18.168:80/onvif/device_service</d:XAddrs>
                <d:MetadataVersion>1</d:MetadataVersion>
            </d:ProbeMatch>
        </d:ProbeMatches>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

调试源代码

外部因素排除外,只能跟踪源码了——这是我最不想做的事。由于代码原本为动态库,首先改造为可执行程序,为了兼容动态库,使用自编的 Makefile,再分析 eclipse 工程,添加涉及的库、头文件路径,编译通过后执行,在搜索设备的soap_recv___wsdd__ProbeMatches函数出错,返回3。跟踪该函数代码:

SOAP_FMAC5 int SOAP_FMAC6 soap_recv___wsdd__ProbeMatches(struct soap *soap, struct __wsdd__ProbeMatches *_param_1)

	soap_default___wsdd__ProbeMatches(soap, _param_1);
	soap_begin(soap);
	int ret = 0;
	ret = soap_begin_recv(soap);
if (ret) printf("!!! %d return. %d soap err: %d\\n", __LINE__, ret, soap->error); return ret;
	ret = soap_envelope_begin_in(soap);
if (ret) printf("!!! %d return. %d soap err: %d\\n", __LINE__, ret, soap->error); return ret;
	ret = soap_recv_header(soap);
if (ret) printf("!!! %d return. %d soap err: %d\\n", __LINE__, ret, soap->error); return ret;
	ret = soap_body_begin_in(soap);
	if (ret) printf("!!! %d return. %d soap err: %d\\n", __LINE__, ret, soap->error); return ret;
	ret = soap_closesock(soap);
	if (ret) printf("!!! %d return. %d soap err: %d\\n", __LINE__, ret, soap->error); return ret;

发现在soap_body_begin_in函数出错,返回值 3,宏定义为SOAP_TAG_MISMATCH,相关定义如下:

// stdsoap2.h
#define SOAP_EOF                        EOF
#define SOAP_OK                         0
#define SOAP_CLI_FAULT                  1
#define SOAP_SVR_FAULT                  2
#define SOAP_TAG_MISMATCH               3

soap_body_begin_in函数定义如下:

soap_body_begin_in(struct soap *soap)

  if (soap->version == 0)
    return SOAP_OK;
  soap->part = SOAP_IN_BODY;
  if (soap_element_begin_in(soap, "SOAP-ENV:Body", 0, NULL))
    return soap->error;
  if (!soap->body)
    soap->part = SOAP_NO_BODY;
  return SOAP_OK;

跟踪soap_element_begin_in函数,发现只要soap结构体的other字段为1,即返回SOAP_TAG_MISMATCH

soap_element_begin_in(struct soap *soap, const char *tag, int nillable, const char *type)

  if (!soap_peek_element(soap))
  
    if (soap->other)
      return soap->error = SOAP_TAG_MISMATCH;
    if (tag && *tag == '-')
      return SOAP_OK;
    ...
  
  return soap->error;

跟踪soap_peek_element函数,和other字段有关的代码如下:

soap_peek_element(struct soap *soap)

  soap->other = 0;
    
        else if (!soap_match_tag(soap, tp->name, "SOAP-ENV:actor"))
        
          if ((!soap->actor || strcmp(soap->actor, tp->value))
           && strcmp(tp->value, "http://schemas.xmlsoap.org/soap/actor/next"))
            soap->other = 1;
        
      
      else if (soap->version == 2)
      
#ifndef WITH_NOIDREF
        if (!soap_match_tag(soap, tp->name, "SOAP-ENC:id"))
        
        
        else
#endif
        if (!soap_match_tag(soap, tp->name, "SOAP-ENC:itemType"))
        
        
        else if (!soap_match_tag(soap, tp->name, "SOAP-ENV:role"))
        
          if ((!soap->actor || strcmp(soap->actor, tp->value))
           && strcmp(tp->value, "http://www.w3.org/2003/05/soap-envelope/role/next"))
            soap->other = 1;
        
      
      else
      
      
    

大意是,如果tag匹配了SOAP-ENV:actorSOAP-ENV:role,但某些值却不匹配,就设置other为1,最终返回不匹配错误。

分析对比了 2.8.90版本源码,该函数的判断部分没有改动,至于为什么旧版本可以,暂未知。

为了一探究竟,将相关函数移动到测试代码中,但牵扯到函数太多,最终发现soap结构体字段不相同——特别是有个在使用的函数指针是新版本才有的,于是放弃此路。

其实,到此时已经明确知道了就是 gsoap库版本问题,于是只能继续找原版本的源码来编译了。

重新编译库

在网上搜索 gsoap,很少能找到官方的版本,默认提供的是最新版本的 gsoap,但皇天不负有心人,还是找到官方的 svn 仓库https://sourceforge.net/p/gsoap2/code/177/log/?path=,相关信息如下:

[r172] by  engelen  2019-08-14 13:32:42
gSOAP 2.8.90 stable

源码只能用 svn 下载:

https://sourceforge.net/p/gsoap2/code/HEAD/tree/

下载后,切换到 172 分支,导出,打包,传输到板子系统上进行编译。

首先安装依赖包:

sudo yum install autoconf automake flex bison openssl-devel zlib-devel -y

配置编译gsoap:

./configure prefix=/home/latelee/tools/soap && make && make install

提示aclocal命令找不到:

CDPATH="$ZSH_VERSION+.:" && cd . && /bin/sh /home/latelee/work/gsoap-2.8.90/missing aclocal-1.16 
/home/latelee/work/gsoap-2.8.90/missing: line 81: aclocal-1.16: command not found
WARNING: 'aclocal-1.16' is missing on your system.

使用 yum 安装最高只有 1.13 版本:

$ /usr/bin/aclocal --version
aclocal (GNU automake) 1.13.4

根据错误提示安装1.16版本:

wget http://ftp.gnu.org/gnu/automake/automake-1.16.1.tar.gz
tar -xf automake-1.16.1.tar.gz
cd automake-1.16.1/
./configure
make
sudo make install

再次编译安装:

./configure prefix=/home/latelee/tools/soap && make && make install

得到如下文件:

libgsoap.a    libgsoapck.a    libgsoapssl.a    pkgconfig
libgsoap++.a  libgsoapck++.a  libgsoapssl++.a

视频动态库使用了 gsoap 库(具体名称为libgsoapssl.so),但默认编译没有动态库,为了减少库的依赖,在 Makefile 中指定静态库文件libgsoapssl.a,但是编译出错:

Generating dynamic lib file... libvideolib.so
/usr/bin/ld: /home/latelee/work/videolib-debug/soap/lib/libgsoapssl.a(libgsoapssl_a-stdsoap2_ssl.o): relocation R_AARCH64_ADR_PREL_PG_HI21 against external symbol `GENERAL_NAME_free@@libcrypto.so.10' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: /home/latelee/work/videolib-debug/soap/lib/libgsoapssl.a(libgsoapssl_a-stdsoap2_ssl.o)(.text+0x1ff68): unresolvable R_AARCH64_ADR_PREL_PG_HI21 relocation against symbol `GENERAL_NAME_free@@libcrypto.so.10'
/usr/bin/ld: final link failed: Bad value
collect2: error: ld returned 1 exit status

不知何故无法链接,只能按原来的做法,重新编译 gsoap,指定动态库:

./configure prefix=/home/latelee/tools/soap \\
–-enable-shared

但失败,不支持动态库的生成:

checking build system type... Invalid configuration `–-enable-shared': machine `–-enable' not recognized
configure: error: /bin/sh ./config.sub –-enable-shared failed

想着改编译脚本生成,但太复杂了,于是根据编译过程的输出内容,从生成静态库的相关命令找到目标文件,再重新链接成动态库,手动生成命令如下:

gcc -shared -fPIC -o libgsoapssl.so libgsoapssl_a-stdsoap2_ssl.o libgsoapssl_a-dom.o

虽然有警告,但能成功生成libgsoapssl.so文件,

/usr/bin/ld: libgsoapssl_a-stdsoap2_ssl.o: relocation R_AARCH64_ADR_PREL_PG_HI21 against external symbol `namespaces' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: libgsoapssl_a-stdsoap2_ssl.o: relocation R_AARCH64_ADR_PREL_PG_HI21 against external symbol `GENERAL_NAME_free' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: libgsoapssl_a-stdsoap2_ssl.o: relocation R_AARCH64_ADR_PREL_PG_HI21 against external symbol `X509V3_conf_free' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: libgsoapssl_a-stdsoap2_ssl.o: relocation R_AARCH64_ADR_PREL_PG_HI21 against external symbol `X509V3_conf_free' can not be used when making a shared object; recompile with -fPIC

根据经验分析,代码中没有使用有与X509有关的函数,后面是否使用,届时再看。

得到动态库,再编译就正常了。测试程序也能搜索到设备了。

其它

根据以往的经验,记录一下不同相机的rtsp地址(仅个人经验):

海康摄像机:
rtsp://172.18.203.44:554/Streaming/Channels/1?transportmode=unicast&profile=Profile_1

华为相机:
rtsp://192.168.18.168:554/LiveMedia/ch1/Media1

在测试时发现,必须设置默认网关才能正常搜索到设备,否则,哪怕指定了静态路由也不行。因为在实验环境中才有双网络,因此该问题没有深入研究。

小结

onvif 的框架代码,经典者如soapClient.csoapC.c等,是用 gsoap 根据 onvif 的 wsdl 文件生成的,一旦使用了某个 gsoap 版本的代码,就定型了,如果更换,则必须重新生成框架代码。

代码可以再优化一下,由于问题卡在设备搜索,可以在配置文件中添加选项,可选择跳过搜索步骤,直接连接设备,再根据 xml 分析出 rtsp 地址。但是因为时间原因,而且面对的是传承3年的代码,修改有一定困难。

后记

视频库用的 gsoap 版本是 2019 年8月14日发布的,距今近3年了。

回查工作手账,其后的一天,重构后的二期充电桩系统平稳上线;其后的一个月,因一直未发工资,内部讨论如何有序安全离职;再其后的一个月,正式提交辞呈。

而 onvif 系列文章,其前4月写了上一篇,其3年前则是前一篇。

如今又搞 onvif,天意何时何地都在。

以上是关于ONVIF学习笔记11:搜索设备不匹配问题排查的主要内容,如果未能解决你的问题,请参考以下文章

ONVIF学习笔记11:搜索设备不匹配问题排查

ONVIF客户端搜索设备获取rtsp地址开发笔记(精华篇)

RTSP/Onvif视频平台EasyNVR无法查看HLS视频流的问题排查

RTSP/Onvif智慧安防视频EasyNVR平台Web页面无法打开的排查与解决步骤

Linux下onvif客户端关于ipc摄像头的搜索

ONVIFclient搜索设备获取rtsp地址开发笔记(精华篇)