声明:本篇文章来自于某公司Cable Modem产品的文档资料,源码来自于博通公司,只提供参考(为保护产权,本人没有源码)。
前文曾提到会写一篇关于博通的tr069,那么福利来了。福利,福利,福利,重要的事情说三遍!
如果你正在阅读博通的相关产品代码而又苦于没有文档参阅,那么我相信本文将会非常适合你。
一. TR069的Makefile和源码
1. 编译:
在编译选项中添加“tr69”, 对应的makefile为: REV/rbb_cm_src/Bfc/make/BfcTR69.mak
2. 相关源码
主要有3部分代码:tr069 client agent代码,用c实现;TR069Thread和CLI配置代码,c++实现;client agent与系统间的接口代码,c++实现。(具体实现在后文讲解)
1 //tr069 client agent代码 2 REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/ 3 inc/ #放置与数据模型相关的宏定义,数据结构定义头文件 4 main/ #agent实现文件,main,informer,event 5 nanoxml/ # 6 SOAPParser/ #soap解析,其中RPCState.c为RPC命令处理接口 7 standard/ #节点数据模型定义文件 8 webproto/ #
1 //tr069thread与CLI参数配置 2 REV/rbb_cm_src/Bfc/IpHelpers/TR069/ 3 BcmBfcTr69CommandTable.cpp #tr69 CLI实现 4 BcmBfcTr69SnmpApi.cpp #提供了通过snmp进行对系统参数的get/set接口,tr69 agent利用这些接口进行大部分参数的get/set 5 BcmBfcTr69ThreadIpStackACT.cpp #ip_stack ip变化时,通知TR069Thread启动或停止agent 6 BcmBfcTr69SocketApi.cpp #提供系统与agent间有关socket操作的接口,供agent调用 7 BcmBfcTr69Entry.cpp # 8 BfcTr69NonVolSettings.cpp 9 BfcTr69NonVolSettingsCommandTable.cpp #tr69 non-vol参数配置CLI 10 BcmBfcTr69NonVolApi.cpp #提供non-vol存取的接口,供agent调用 11 BcmBfcTr69Thread.cpp #tr069进程实现,控制agent的运行/停止/重启
1 //client agent系统调用接口 2 REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/bcmBfcIf/ 3 其中的文件定义了与standard/目录下数据模型结构中对应的object的add/del,parameters的get/set api
二. 配置参数
1. CLI
1 CM>cd non-vol/tr69 2 CD>ls 3 acs_password acs_url acs_username conn_req_password conn_req_url conn_req_username 4 ip_stack_num periodic_inform_enable periodic_inform_interval 5 stun_enable stun_max_keepalive stun_mix_keepalive stun_password stun_server stun_username
其中,ip_stack_num为tr69 agent关联的wan interface对应的ipstack number,默认指定为3
此外,可使用的命令有如下:
1 CM>cd tr69 2 CM>ls 3 acs_url log send_inform show start_client stop_client test walk_tree
其中: client可理解为tr069 client agent
acs_url 修改acsurl
send_inform agent运行前提下,立即发送inform
start_client 如ip_stack_num对应的wan interface处于可用状态,则会运行agent
walk_tree 查看节点结构
2. CM ConfigFile
参数配置 CM Config有定义TR-069参数
1 eRouter Configuration Encodings (202) 2 eRouter TR-069 Management Server (2) 3 EnableCWMP (1) 4 URL (2) 5 Username (3) 6 Password (4) 7 ConnectionRequestUserName (5) 8 ConnectionRequestPassword (6) 9 ACSOverride (7)
其中,EnableSWMP和ACSOverride会影响tr69 agent的行为。 下文详述
3. DHCP option
利用DHCPv6 Option17或DHCPv4 Option125来配置ACSUrl参数
三. 多种配置参数的优先顺序
1. 启动方式
- 通过CLI(start_client)启动,此时使用的为通过CLI配置的参数,即在NonVol中的参数
- 自动启动,条件为CM Config中的EnableCWMP为true,或者得到有效的DHCPv6 Option17或DHCPv4 Option125。此时的参数选择如下
2. 自动启动时的参数选择
1 // ACSUrl的优先顺序: 2 if(ACSOverride==true || (ACSOverride==false && ACSUrl in NonVol=="http://10.10.10.10:8080/acs")) 3 DHCPv6 Option17 > DHCPv4 Option125 > CM Config > NonVol 4 else if(ACSOverride==false and ACSUrl in NonVol!="http://10.10.10.10:8080/acs") 5 DHCPv6 Option17 > DHCPv4 Option125 > NonVol > CM Config
// 其他参数(acsusername,acspassword,connrequsername,connreqpassword)的选择: if(ACSOverride==true || (ACSOverride==false && ACSUrl in NonVol=="http://10.10.10.10:8080/acs")) CM Config > NonVol else if(ACSOverride==false && ACSUrl in NonVol!="http://10.10.10.10:8080/acs") !NonVol > CM Config
3.ipv4 or ipv6
如果ACSUrl中使用"[]"包含IP地址或者由DHCPv6 Option17得到的ACSUrl,则尝试首先使用IPv6发起连接.
四. 具体实现
1. TR069 Thread状态控制
1 // tr069thread有以下状态和消息类型: 2 enum 3 { 4 kStartThread = 0, //目前此msg仅自CLI 5 kStopThread, //同上 6 kStartClient,//同上 7 kStopClient, //同上 8 kSendInform, //同上 9 kIpAddressChanged //当相关的ip_stack ip发生变化时,会收到此消息类型 10 }QCommands; 11 12 typedef enum 13 { 14 kClientStarting = 0, 15 kClientReady, // Thread initialized and ready for Core 16 kClientRunning, 17 kClientStopping, 18 kClientStopped, // Client (core) is stopped, thread running 19 kExitingThread // Going away completely. 20 } ThreadState;
进程根据进程状态和收到的消息类型来控制tr69 client agent,start/stop agent的过程用下图简单描述:
2. Socket建立
建立2个socket,acsconnection socket用于连接ACS;acslisten socket用于接收acs的RPC.
1 // acs connection socket建立 2 wget.c wget_Connect() 3 \_www.c www_EstablishConnection() 4 \_BcmBfcTr69SocketApi.cpp BfcTr69Api_SocketAcsConnection() 5 \_BcmBfcTr69Thread::SocketAcsConnection() 6 7 // acs listen socket建立 8 tr69c_main() -> initTask() 9 \_informer.c initInformer() -> startACSComm() -> 10 startACSListener() -> startACScallback() 11 \_BfcTr69Api_SocketACSListenSocket() 12 \_BcmBfcTr69Thread::SocketACSListenSocket()
3. 数据模型的建立
数据模型标准:TR-181_Issue-2.pdf,非TR-98 Gataway模型
1)模型对应数据结构
1 //节点数据结构定义: 2 REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/inc/tr69cdef.h 3 typedef union TRxPAttrib { 4 struct Attrib { 5 eTRxType etype:8; 6 unsigned slength:16; 7 unsigned inhibitActiveNotify:1; /* set to always inhibit change notification: use on counters */ 8 } attrib; 9 InstanceDesc *instance; 10 } TRxPAttrib; 11 12 typedef struct TRxObjNode { 13 const char *name;//节点或参数名称 14 TRxPAttrib paramAttrib;//主要指示参数类型,object/string/bool... 15 TRxSETFUNC setTRxParam;//object的del,parameter的set 16 TRxGETFUNC getTRxParam;//object的add,parameter的get 17 void *objDetail; //节点类型为object时,对应object的详细内容(包含parameter和下级object) 18 InstanceDope *instanceDope; 19 } TRxObjNode;
用以上结构表示以下节点:
Device.InterfaceStackNumberOfEntries //unsigned类型parameter,readonly Device.DeviceSummary //string类型parameter,readonly Device.IP. //不可动态添加的object类型,readonly Device.IP.IPv4Capable Device.IP.IPv4Enable ... Device.IP.InterfaceNumberOfEntries Device.IP.Interface.{i}. //可动态添加的object类型,read-write Device.IP.Interface.i.Enable //read-write Device.IP.Interface.i.IPv4Enable //read-write ...
的实例对应为
1 tr181i2DeviceParams.c 2 TRxObjNode tr181i2DeviceDesc[] = { 3 {InterfaceStackNumberOfEntries,{{tUnsigned,0,0}}, NULL, getInterfaceStackNumEntries,NULL,NULL}, 4 {DeviceSummary,{{tString}}, NULL, getDeviceSummary,NULL,NULL}, 5 {IP,{{tObject,0,0}}, NULL,NULL, ipDesc,NULL}, 6 {NULL} 7 }; 8 9 tr181i2DeviceParams.c 10 TRxObjNode ipDesc[] = { 11 {IPv4Capable,{{tBool,0}}, NULL,getIPv4Capable,NULL,NULL}, 12 {IPv4Enable,{{tBool,0}}, NULL,getIPv4Enable,NULL,NULL}, 13 {InterfaceNumberOfEntries,{{tUnsigned,0,16}}, NULL,getIPInterfaceNumberOfEntries,NULL,NULL}, 14 {ActivePortNumberOfEntries,{{tUnsigned,0,16}}, NULL,getIPActivePortNumberOfEntries,NULL,NULL}, 15 {Interface,{{tObject,0,0}}, NULL,NULL, ipInterfaceDesc,NULL}, 16 {NULL} 17 }; 18 19 tr181i2IPInterfaceParams.c 20 TRxObjNode ipInterfaceDesc[] = { 21 {instanceIDMASK,{{0}}, deleteIPInterfaceInstance, addIPInterfaceInstance, ipInterfaceInstanceDesc}, 22 };//其中,instanceIDMASK为新节点标识,deleteIPInterfaceInstance为delete Interface节点的API名称 23 24 TRxObjNode ipInterfaceInstanceDesc[] = { 25 {Enable,{{tBool,0}},setIPInterfaceEnable,getIPInterfaceEnable,NULL,NULL}, 26 {IPv4Enable,{{tBool,0}},setIPInterfaceIPv4Enable,getIPInterfaceIPv4Enable,NULL,NULL}, 27 {NULL}//其中,setIPInterfaceIPv4Enable/getIPInterfaceIPv4Enable为Device.IP.Interface.i.IPv4Enable参数的set/get API名称 28 };
以上,根据
tr181i2DeviceDesc[]
\_ipDesc[]
\_ipInterfaceDesc[]
连接成了
Device.
\_IP.
\_Interface.
节点树形结构
4. RPC处理过程
1) 入口函数
对应处理函数入户为: REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/SOAPParser/RPCState.c runRPC()
目前,支持如下方法:
1 switch (rpcAction->rpcMethod) { 2 case rpcGetRPCMethods: 3 ... 4 case rpcSetParameterValues: 5 ... 6 case rpcGetParameterValues: 7 ... 8 case rpcGetParameterNames: 9 ... 10 case rpcGetParameterAttributes: 11 ... 12 case rpcSetParameterAttributes: 13 ... 14 case rpcAddObject: 15 ... 16 case rpcDeleteObject: 17 ... 18 case rpcReboot: 19 ... 20 case rpcFactoryReset: 21 ... 22 #if DOWNLOAD_SUPPORTED 23 case rpcDownload: 24 ... 25 #endif 26 case rpcInformResponse: 27 ... 28 case rpcTransferCompleteResponse: 29 ... 30 case rpcGetRPCMethodsResponse: 31 ... 32 case rpcFault: 33 ...
2) parameters对应的get/set api
仍以上述Device.IP.Interface.节点为例,此节点对应的api位于:
1 REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/bcmBfcIf/BcmBfcTr181i2IPInterfaceHandlers.cpp
注意到实际上这里大部分API没有实现实际功能。
实际上,TR069的get/set是通过SNMP实现的,比如WiFi.Radio.{i}.Enable这个参数的get/set:
3)notify机制
passive notify:(1)在agent启动或者收到inform response后,遍历rootDevice的所有节点参数,将attribute为非none的参数的值更新为从对应mib中获取的值。 change parameter value to mib in some where (2) 在发送inform之前,再遍历所有attribute为非none的参数,将其值与mib中获取的值比较,如有变化,则将其加入到inform发送出去
active notify: 未分析
5. 如何添加object/parameter
以添加如下节点和参数为例:
<object ref="Device.NAT.PortMapping.{i}." requirement="createDelete"> ... <parameter ref="RemoteHost" requirement="readWrite"/> <parameter ref="ExternalPort" requirement="readWrite"/> ... </object>
1)添加参数对应数据结构定义
添加tr181i2NATPortMappingParams.c/h,定义节点参数结构:
1 --- /dev/null 2 +++ b/REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/standard/tr181i2NATPortMappingParams.c 3 @@ -0,0 +1,35 @@ 4 +// Filename: tr181i2NATPortMappingParams.c 5 + 6 +#include "sharedparams.h" 7 +#include "tr181i2NATPortMappingParams.h" 8 + 9 +/* Device.NAT.PortMapping.{i} */ 10 +TRXGFUNC(getPortMappingRemoteHost); 11 +TRXSFUNC(setPortMappingRemoteHost); 12 +TRXGFUNC(getPortMappingExternalPort); 13 +TRXSFUNC(setPortMappingExternalPort); 14 + 15 +TRxObjNode natPortMappingInstanceDesc[] = { 16 + {RemoteHost,{{tString,0,64}}, setPortMappingRemoteHost, getPortMappingRemoteHost, NULL,NULL}, 17 + {ExternalPort,{{tUnsigned,0,16}}, setPortMappingExternalPort,getPortMappingExternalPort,NULL,NULL}, 18 + {NULL} 19 +}; 20 + 21 +/* Device.NAT.PortMapping. */ 22 +TRXGFUNC(addPortMappingInstance); 23 +TRXSFUNC(deletePortMappingInstance); 24 + 25 +TRxObjNode natPortMappingDesc[] = { 26 + {instanceIDMASK,{{0}}, deletePortMappingInstance, addPortMappingInstance, natPortMappingInstanceDesc}, 27 +}; 28 diff --git a/REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/standard/tr181i2NATPortMappingParams.h b/REV/rbb_cm_src/Bf 29 new file mode 100755 30 index 0000000..f45b208 31 --- /dev/null 32 +++ b/REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/standard/tr181i2NATPortMappingParams.h 33 @@ -0,0 +1,24 @@ 34 +// Filename: tr181i2NATPortMappingParams.h 35 + 36 +#ifndef TR181I2_NAT_PORTMAPPING_PARAMS_H 37 +#define TR181I2_NAT_PORTMAPPING_PARAMS_H 38 + 39 +#include "../inc/tr69cdefs.h" 40 + 41 +/* Device.NAT.PortMapping.{i}*/ 42 +SVAR(RemoteHost); 43 +SVAR(ExternalPort); 44 + 45 +#endif // TR181I2_NAT_PARAMS_H
将增加的PortMapping节点加入Device.NAT.节点下:
1 +++ b/REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/standard/standardparams.c 2 #include "../standard/tr181i2NATParams.h" 3 + #include "../standard/tr181i2NATPortMappingParams.h" 4 #include "../standard/tr181i2UPnPParams.h" 5 6 +++ b/REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/standard/tr181i2NATParams.c 7 #include "tr181i2NATParams.h" 8 9 +extern TRxObjNode natPortMappingDesc[]; 10 11 TRxObjNode natDesc[] = { 12 {InterfaceSettingNumberOfEntries,{{tUnsigned,0,16}}, NULL,getNATInterfaceSettingNumberOfEntries,NULL,NULL}, 13 {PortMappingNumberOfEntries,{{tUnsigned,0,16}}, NULL,getNATPortMappingNumberOfEntries,NULL,NULL}, 14 + {PortMapping,{{tObject,0,0}},NULL,NULL,natPortMappingDesc,NULL}, 15 {NULL} 16 }; 17 18 +++ b/REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/standard/tr181i2NATParams.h 19 @@ -60,5 +60,6 @@ 20 21 SVAR(InterfaceSettingNumberOfEntries); 22 SVAR(PortMappingNumberOfEntries); 23 +SVAR(PortMapping);
2) 添加对应Add/Del object,Get/Set parameter API
NAT.PortMapping.对应的api添加在BcmBfcTr181i2NATPortMappingHandlers.cpp:
1 +++ b/REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/bcmBfcIf/BcmBfcTr181i2NATPortMappingHandlers.cpp 2 3 + * File Name : BcmBfcTr181i2NATPortMappingHandlers.c 4 + 5 +#include <stdio.h> 6 +#include <stdlib.h> 7 +#include <string.h> 8 + 9 +#include "bcmtypes.h" 10 +#include "BcmBfcTr69SnmpApi.h" 11 + 12 +// Give these CPP functions C linkage 13 +extern "C" 14 +{ 15 + 16 +#include "../inc/tr69cdefs.h" 17 +#include "BcmBfcTr69Log.h" 18 + 19 +extern char* strdup (char * str); 20 + 21 +TRX_STATUS getPortMappingRemoteHost(char **value) 22 +{ 23 + InstanceDesc *idp; 24 + 25 + if ((idp = getCurrentInstanceDesc()) == NULL) 26 + return TRX_ERR; 27 + 28 + BfcTr69Api_GetFromSnmp(kOID_clabNATPortMappingRemoteHost, idp->hwUserData, value); 29 + 30 + return TRX_OK; 31 +} 32 + 33 +TRX_STATUS setPortMappingRemoteHost(char *value) 34 +{ 35 + InstanceDesc *idp; 36 + 37 + if ((idp = getCurrentInstanceDesc()) == NULL) 38 + return TRX_ERR; 39 + if(value == NULL) 40 + return TRX_ERR; 41 + 42 + if(BfcTr69Api_SnmpSetString(kOID_clabNATPortMappingRemoteHost, idp->hwUserData, value)) 43 + return TRX_OK; 44 + 45 + return TRX_ERR; 46 +} 47 48 +TRX_STATUS addPortMappingInstance(char **value) 49 +{ 50 + InstanceDesc *idp; 51 + 52 + if ((idp = getNewInstanceDesc(getCurrentNode(), getCurrentInstanceDesc(), 0)))//添加新节点 53 + { 54 + idp->hwUserData = *value; 55 + idp->instanceID = BfcTr69Api_SnmpToTr69Index(idp->hwUserData);//此api仅从snmp获取相应节点id,作用不详 56 + return TRX_OK; 57 + } 58 + return TRX_ERR; 59 +} 60 + 61 +TRX_STATUS deletePortMappingInstance(char *value) 62 +{ 63 + TRxObjNode *n; 64 + InstanceDesc *idp; 65 + int id = atoi(value); 66 + 67 + if ((idp = findInstanceDesc(n=getCurrentNode(), id))) 68 + { 69 + if (!deleteInstanceDesc(n, id))//删除节点,但不会通知snmp删除相应节点 70 + { 71 + return TRX_OK; 72 + } 73 + } 74 + return TRX_ERR; 75 +} 76 + 77 +} // extern "C"
3) 修改BfcTR69.mak
因新增加了tr181i2NATPortMappingParams.c和BcmBfcTr181i2NATPortMappingHandlers.cpp文件,需要修改Makefile:
1 +++ b/REV/rbb_cm_src/Bfc/make/BfcTR69.mak 2 3 BFC_TR69C_OBJECTS += BcmBfcTr181i2NATHandlers.o 4 +BFC_TR69C_OBJECTS += BcmBfcTr181i2NATPortMappingHandlers.o 5 BFC_TR69C_OBJECTS += BcmBfcTr181i2UPnPHandlers.o 6 7 BFC_TR69C_OBJECTS += tr181i2NATParams.o 8 +BFC_TR69C_OBJECTS += tr181i2NATPortMappingParams.o 9 BFC_TR69C_OBJECTS += tr181i2UPnPParams.o
4) 增加SNMP对节点的实现
依照目前的实现,TR069对节点/参数的操作,最终是通过SNMP Agent来达成。如果要新添加的节点/参数还未包含在SNMP Agent中,
需先增加SNMP Agent对此节点/参数的实现,包括mib定义,节点的Add/Del。
五. 改善或问题
1. set parameter时的数据有效性检测
目前的实现中,tr69c/SOAPParser/RPCState.c char *doSetParameterValues(RPCAction *a)
有支持对参数名称/参数是否可写做检查,但对设置值的有效性检查只有简单的“对非字符型参数不可设置为空”做了检查,而未对数据范围等做检查。 改进的方法可扩展参数设置SetFunc()的返回类型,在SetFunc中检查数据有效性,如数据无效,返回9007.
2. 增加/删除节点与SNMP同步
从目前已有实现节点中来看,当TR069 Add/Del一个节点时,并没有通知SNMP Add/Del相应节点。而TR069的Get/Set又依赖于SNMP,理论上,TR069和SNMP的Add/Del节点操作应该需要同步。
3. 节点模板
构建节点数据结构的形式相对固定,设想可用模板程序根据节点信息自动生成。如从xml -> *.c