声明:本篇文章来自于某公司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++实现。(具体实现在后文讲解)
//tr069 client agent代码
REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/
inc/ #放置与数据模型相关的宏定义,数据结构定义头文件
main/ #agent实现文件,main,informer,event
nanoxml/ #
SOAPParser/ #soap解析,其中RPCState.c为RPC命令处理接口
standard/ #节点数据模型定义文件
webproto/ #
//tr069thread与CLI参数配置
REV/rbb_cm_src/Bfc/IpHelpers/TR069/
BcmBfcTr69CommandTable.cpp #tr69 CLI实现
BcmBfcTr69SnmpApi.cpp #提供了通过snmp进行对系统参数的get/set接口,tr69 agent利用这些接口进行大部分参数的get/set
BcmBfcTr69ThreadIpStackACT.cpp #ip_stack ip变化时,通知TR069Thread启动或停止agent
BcmBfcTr69SocketApi.cpp #提供系统与agent间有关socket操作的接口,供agent调用
BcmBfcTr69Entry.cpp #
BfcTr69NonVolSettings.cpp
BfcTr69NonVolSettingsCommandTable.cpp #tr69 non-vol参数配置CLI
BcmBfcTr69NonVolApi.cpp #提供non-vol存取的接口,供agent调用
BcmBfcTr69Thread.cpp #tr069进程实现,控制agent的运行/停止/重启
//client agent系统调用接口
REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/bcmBfcIf/
其中的文件定义了与standard/目录下数据模型结构中对应的object的add/del,parameters的get/set api
二. 配置参数
1. CLI
CM>cd non-vol/tr69
CD>ls
acs_password acs_url acs_username conn_req_password conn_req_url conn_req_username
ip_stack_num periodic_inform_enable periodic_inform_interval
stun_enable stun_max_keepalive stun_mix_keepalive stun_password stun_server stun_username
其中,ip_stack_num为tr69 agent关联的wan interface对应的ipstack number,默认指定为3
此外,可使用的命令有如下:
CM>cd tr69
CM>ls
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参数
eRouter Configuration Encodings ()
eRouter TR- Management Server ()
EnableCWMP ()
URL ()
Username ()
Password ()
ConnectionRequestUserName ()
ConnectionRequestPassword ()
ACSOverride ()
其中,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. 自动启动时的参数选择
// ACSUrl的优先顺序:
if(ACSOverride==true || (ACSOverride==false && ACSUrl in NonVol=="http://10.10.10.10:8080/acs"))
DHCPv6 Option17 > DHCPv4 Option125 > CM Config > NonVol
else if(ACSOverride==false and ACSUrl in NonVol!="http://10.10.10.10:8080/acs")
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状态控制
// tr069thread有以下状态和消息类型:
enum
{
kStartThread = , //目前此msg仅自CLI
kStopThread, //同上
kStartClient,//同上
kStopClient, //同上
kSendInform, //同上
kIpAddressChanged //当相关的ip_stack ip发生变化时,会收到此消息类型
}QCommands; typedef enum
{
kClientStarting = ,
kClientReady, // Thread initialized and ready for Core
kClientRunning,
kClientStopping,
kClientStopped, // Client (core) is stopped, thread running
kExitingThread // Going away completely.
} ThreadState;
进程根据进程状态和收到的消息类型来控制tr69 client agent,start/stop agent的过程用下图简单描述:
2. Socket建立
建立2个socket,acsconnection socket用于连接ACS;acslisten socket用于接收acs的RPC.
// acs connection socket建立
wget.c wget_Connect()
\_www.c www_EstablishConnection()
\_BcmBfcTr69SocketApi.cpp BfcTr69Api_SocketAcsConnection()
\_BcmBfcTr69Thread::SocketAcsConnection() // acs listen socket建立
tr69c_main() -> initTask()
\_informer.c initInformer() -> startACSComm() ->
startACSListener() -> startACScallback()
\_BfcTr69Api_SocketACSListenSocket()
\_BcmBfcTr69Thread::SocketACSListenSocket()
3. 数据模型的建立
数据模型标准:TR-181_Issue-2.pdf,非TR-98 Gataway模型
1)模型对应数据结构
//节点数据结构定义:
REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/inc/tr69cdef.h
typedef union TRxPAttrib {
struct Attrib {
eTRxType etype:;
unsigned slength:;
unsigned inhibitActiveNotify:; /* set to always inhibit change notification: use on counters */
} attrib;
InstanceDesc *instance;
} TRxPAttrib; typedef struct TRxObjNode {
const char *name;//节点或参数名称
TRxPAttrib paramAttrib;//主要指示参数类型,object/string/bool...
TRxSETFUNC setTRxParam;//object的del,parameter的set
TRxGETFUNC getTRxParam;//object的add,parameter的get
void *objDetail; //节点类型为object时,对应object的详细内容(包含parameter和下级object)
InstanceDope *instanceDope;
} 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
...
的实例对应为
tr181i2DeviceParams.c
TRxObjNode tr181i2DeviceDesc[] = {
{InterfaceStackNumberOfEntries,{{tUnsigned,,}}, NULL, getInterfaceStackNumEntries,NULL,NULL},
{DeviceSummary,{{tString}}, NULL, getDeviceSummary,NULL,NULL},
{IP,{{tObject,,}}, NULL,NULL, ipDesc,NULL},
{NULL}
}; tr181i2DeviceParams.c
TRxObjNode ipDesc[] = {
{IPv4Capable,{{tBool,}}, NULL,getIPv4Capable,NULL,NULL},
{IPv4Enable,{{tBool,}}, NULL,getIPv4Enable,NULL,NULL},
{InterfaceNumberOfEntries,{{tUnsigned,,}}, NULL,getIPInterfaceNumberOfEntries,NULL,NULL},
{ActivePortNumberOfEntries,{{tUnsigned,,}}, NULL,getIPActivePortNumberOfEntries,NULL,NULL},
{Interface,{{tObject,,}}, NULL,NULL, ipInterfaceDesc,NULL},
{NULL}
}; tr181i2IPInterfaceParams.c
TRxObjNode ipInterfaceDesc[] = {
{instanceIDMASK,{{}}, deleteIPInterfaceInstance, addIPInterfaceInstance, ipInterfaceInstanceDesc},
};//其中,instanceIDMASK为新节点标识,deleteIPInterfaceInstance为delete Interface节点的API名称 TRxObjNode ipInterfaceInstanceDesc[] = {
{Enable,{{tBool,}},setIPInterfaceEnable,getIPInterfaceEnable,NULL,NULL},
{IPv4Enable,{{tBool,}},setIPInterfaceIPv4Enable,getIPInterfaceIPv4Enable,NULL,NULL},
{NULL}//其中,setIPInterfaceIPv4Enable/getIPInterfaceIPv4Enable为Device.IP.Interface.i.IPv4Enable参数的set/get API名称
};
以上,根据
tr181i2DeviceDesc[]
\_ipDesc[]
\_ipInterfaceDesc[] 连接成了 Device.
\_IP.
\_Interface. 节点树形结构
4. RPC处理过程
1) 入口函数
对应处理函数入户为: REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/SOAPParser/RPCState.c runRPC()
目前,支持如下方法:
switch (rpcAction->rpcMethod) {
case rpcGetRPCMethods:
...
case rpcSetParameterValues:
...
case rpcGetParameterValues:
...
case rpcGetParameterNames:
...
case rpcGetParameterAttributes:
...
case rpcSetParameterAttributes:
...
case rpcAddObject:
...
case rpcDeleteObject:
...
case rpcReboot:
...
case rpcFactoryReset:
...
#if DOWNLOAD_SUPPORTED
case rpcDownload:
...
#endif
case rpcInformResponse:
...
case rpcTransferCompleteResponse:
...
case rpcGetRPCMethodsResponse:
...
case rpcFault:
...
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,定义节点参数结构:
--- /dev/null
+++ b/REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/standard/tr181i2NATPortMappingParams.c
@@ -, +, @@
+// Filename: tr181i2NATPortMappingParams.c
+
+#include "sharedparams.h"
+#include "tr181i2NATPortMappingParams.h"
+
+/* Device.NAT.PortMapping.{i} */
+TRXGFUNC(getPortMappingRemoteHost);
+TRXSFUNC(setPortMappingRemoteHost);
+TRXGFUNC(getPortMappingExternalPort);
+TRXSFUNC(setPortMappingExternalPort);
+
+TRxObjNode natPortMappingInstanceDesc[] = {
+ {RemoteHost,{{tString,,}}, setPortMappingRemoteHost, getPortMappingRemoteHost, NULL,NULL},
+ {ExternalPort,{{tUnsigned,,}}, setPortMappingExternalPort,getPortMappingExternalPort,NULL,NULL},
+ {NULL}
+};
+
+/* Device.NAT.PortMapping. */
+TRXGFUNC(addPortMappingInstance);
+TRXSFUNC(deletePortMappingInstance);
+
+TRxObjNode natPortMappingDesc[] = {
+ {instanceIDMASK,{{}}, deletePortMappingInstance, addPortMappingInstance, natPortMappingInstanceDesc},
+};
diff --git a/REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/standard/tr181i2NATPortMappingParams.h b/REV/rbb_cm_src/Bf
new file mode
index ..f45b208
--- /dev/null
+++ b/REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/standard/tr181i2NATPortMappingParams.h
@@ -, +, @@
+// Filename: tr181i2NATPortMappingParams.h
+
+#ifndef TR181I2_NAT_PORTMAPPING_PARAMS_H
+#define TR181I2_NAT_PORTMAPPING_PARAMS_H
+
+#include "../inc/tr69cdefs.h"
+
+/* Device.NAT.PortMapping.{i}*/
+SVAR(RemoteHost);
+SVAR(ExternalPort);
+
+#endif // TR181I2_NAT_PARAMS_H
将增加的PortMapping节点加入Device.NAT.节点下:
+++ b/REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/standard/standardparams.c
#include "../standard/tr181i2NATParams.h"
+ #include "../standard/tr181i2NATPortMappingParams.h"
#include "../standard/tr181i2UPnPParams.h" +++ b/REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/standard/tr181i2NATParams.c
#include "tr181i2NATParams.h" +extern TRxObjNode natPortMappingDesc[]; TRxObjNode natDesc[] = {
{InterfaceSettingNumberOfEntries,{{tUnsigned,,}}, NULL,getNATInterfaceSettingNumberOfEntries,NULL,NULL},
{PortMappingNumberOfEntries,{{tUnsigned,,}}, NULL,getNATPortMappingNumberOfEntries,NULL,NULL},
+ {PortMapping,{{tObject,,}},NULL,NULL,natPortMappingDesc,NULL},
{NULL}
}; +++ b/REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/standard/tr181i2NATParams.h
@@ -, +, @@ SVAR(InterfaceSettingNumberOfEntries);
SVAR(PortMappingNumberOfEntries);
+SVAR(PortMapping);
2) 添加对应Add/Del object,Get/Set parameter API
NAT.PortMapping.对应的api添加在BcmBfcTr181i2NATPortMappingHandlers.cpp:
+++ b/REV/rbb_cm_src/Bfc/IpHelpers/TR069/tr69c/bcmBfcIf/BcmBfcTr181i2NATPortMappingHandlers.cpp + * File Name : BcmBfcTr181i2NATPortMappingHandlers.c
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "bcmtypes.h"
+#include "BcmBfcTr69SnmpApi.h"
+
+// Give these CPP functions C linkage
+extern "C"
+{
+
+#include "../inc/tr69cdefs.h"
+#include "BcmBfcTr69Log.h"
+
+extern char* strdup (char * str);
+
+TRX_STATUS getPortMappingRemoteHost(char **value)
+{
+ InstanceDesc *idp;
+
+ if ((idp = getCurrentInstanceDesc()) == NULL)
+ return TRX_ERR;
+
+ BfcTr69Api_GetFromSnmp(kOID_clabNATPortMappingRemoteHost, idp->hwUserData, value);
+
+ return TRX_OK;
+}
+
+TRX_STATUS setPortMappingRemoteHost(char *value)
+{
+ InstanceDesc *idp;
+
+ if ((idp = getCurrentInstanceDesc()) == NULL)
+ return TRX_ERR;
+ if(value == NULL)
+ return TRX_ERR;
+
+ if(BfcTr69Api_SnmpSetString(kOID_clabNATPortMappingRemoteHost, idp->hwUserData, value))
+ return TRX_OK;
+
+ return TRX_ERR;
+} +TRX_STATUS addPortMappingInstance(char **value)
+{
+ InstanceDesc *idp;
+
+ if ((idp = getNewInstanceDesc(getCurrentNode(), getCurrentInstanceDesc(), )))//添加新节点
+ {
+ idp->hwUserData = *value;
+ idp->instanceID = BfcTr69Api_SnmpToTr69Index(idp->hwUserData);//此api仅从snmp获取相应节点id,作用不详
+ return TRX_OK;
+ }
+ return TRX_ERR;
+}
+
+TRX_STATUS deletePortMappingInstance(char *value)
+{
+ TRxObjNode *n;
+ InstanceDesc *idp;
+ int id = atoi(value);
+
+ if ((idp = findInstanceDesc(n=getCurrentNode(), id)))
+ {
+ if (!deleteInstanceDesc(n, id))//删除节点,但不会通知snmp删除相应节点
+ {
+ return TRX_OK;
+ }
+ }
+ return TRX_ERR;
+}
+
+} // extern "C"
3) 修改BfcTR69.mak
因新增加了tr181i2NATPortMappingParams.c和BcmBfcTr181i2NATPortMappingHandlers.cpp文件,需要修改Makefile:
+++ b/REV/rbb_cm_src/Bfc/make/BfcTR69.mak BFC_TR69C_OBJECTS += BcmBfcTr181i2NATHandlers.o
+BFC_TR69C_OBJECTS += BcmBfcTr181i2NATPortMappingHandlers.o
BFC_TR69C_OBJECTS += BcmBfcTr181i2UPnPHandlers.o BFC_TR69C_OBJECTS += tr181i2NATParams.o
+BFC_TR69C_OBJECTS += tr181i2NATPortMappingParams.o
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