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_new
或soap_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:actor
、SOAP-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.c
,soapC.c
等,是用 gsoap 根据 onvif 的 wsdl 文件生成的,一旦使用了某个 gsoap 版本的代码,就定型了,如果更换,则必须重新生成框架代码。
代码可以再优化一下,由于问题卡在设备搜索,可以在配置文件中添加选项,可选择跳过搜索步骤,直接连接设备,再根据 xml 分析出 rtsp 地址。但是因为时间原因,而且面对的是传承3年的代码,修改有一定困难。
后记
视频库用的 gsoap 版本是 2019 年8月14日发布的,距今近3年了。
回查工作手账,其后的一天,重构后的二期充电桩系统平稳上线;其后的一个月,因一直未发工资,内部讨论如何有序安全离职;再其后的一个月,正式提交辞呈。
而 onvif 系列文章,其前4月写了上一篇,其3年前则是前一篇。
如今又搞 onvif,天意何时何地都在。
以上是关于ONVIF学习笔记11:搜索设备不匹配问题排查的主要内容,如果未能解决你的问题,请参考以下文章
RTSP/Onvif视频平台EasyNVR无法查看HLS视频流的问题排查