CWMP开源代码研究番外篇——博通方案

声明:本篇文章来自于某公司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的过程用下图简单描述:

CWMP开源代码研究番外篇——博通方案

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

上一篇:Redis 集合(Set)


下一篇:C# 执行Cmd窗口中的命令 [复制文件实例]