之前简单介绍了 Python 针对 XML 文件的操作方式,XML 的诸多特性使得它非常适合程序之间的数据传输,NETCONF 就是采用 XML 来进行工作。
NETCONF 简单介绍 NETCONF(Network Configuration Protocol,网络配置协议)是一种基于 XML 的网络管理协议,它提供了一种可编程的、对网络设备进行配置和管理的方法。
NETCONF 报文使用 XML 格式,具有强大的过滤能力,而且每一个数据项都有一个固定的元素名称和位置,所以具有很强的兼容性,不同厂家不同设备可以通过 XML 得到相同的结果,便于混合不同厂商不同设备的为冷热软件开发。
NETCONF 协议结构 NETCONF 采用分层结构,分别为:
Content 内容层 
Operations 操作层 
RPC(Remote Procedure Call)远程调用层 
Transport Protocol 通信协议层 
 
XML 分层与 NETCONF 协议分层模型对应关系
NETCONF 分层 
XML 分层 
说明 
 
 
Content 
具体的配置数据、状态数据等信息 
被管理对象的信息,包括配置、状态等,如:<Ifmgr><Interfaces><Interface><Name>G0/0</Name></Interface></Interfaces></Ifmgr> 
 
Operations 
<get>,<get-config>,<edit-config>RPC 中的基本的原语操作集,NETCONF 对其进行扩展,全面定义了对被管理设备的各种基础操作,如get,get-config,get-bulk,edit-config等。 
 
RPC  
<rpc>,rpc-reply为 RPC 模块的编码提供了简单的、传输协议无关的机制,在 XML 中使用<rpc>,rpc-reply对上层的请求和响应数据进行封装。 
 
Transport Protocol 
设备登录方式,支持 Console、SSH、HTTP、TLS、Telnet 等 
为 NETCONF 提供面向连接的、可靠的、顺序的数据链路。 
 
可参考下图:
NETCONF 报文结构 NETCONF 命令必须符合 XML 语言的基本格式。NETCONF 报文格式遵循 RFC 4741 /RFC 6241 。
请求报文格式 对于 H3C 网络设备,请求报文分为两部分:协议定义部分、H3C 自有部分,格式如下:
1 2 3 4 <?xml version="1.0" encoding="utf-8"?>  <rpc  message-id  ="101"  xmlns ="urn:ietf:params:xml:ns:netconf:base:1.0" >   <operation >   </rpc >  
协议定义部分:
encoding 表示使用的 XML 编码格式,默认使用 UTF-8。 
message-id 表示消息 ID。客户端使用单调递增的整数来表示消息 ID。服务器端在应答中会使用相同的消息 ID 以表示应答对应的请求。 
协议定义部分的命名空间必须为 urn:ietf:params:xml:ns:netconf:base:1.0。 
 
H3C 自有部分:
Base 命名空间:http://www.h3c.com/netconf/base:1.0 
Config 命名空间:http://www.h3c.com/netconf/config:1.0 
Data 命名空间:http://www.h3c.com/netconf/data:1.0 
Action 命名空间:http://www.h3c.com/netconf/action:1.0 
 
具体使用哪个命名空间与操作类型和内容有关。
以为接口配置一个 IP 地址的消息为例,请求报文结构可以用下图来说明:
报文回复格式 报文回复格式统一使用协议定义的  <rpc-reply>:
1 2 3 4 <?xml version="1.0" encoding="utf-8"?>  <rpc-reply  xmlns ="urn:ietf:params:xml:ns:netconf:base:1.0"  message-id ="101" >   <ok />   </rpc-reply >  
NETCONF 配置数据库 NETCONF 有三个配置数据库,用来对设备的配置进行管理。
<running/>:存储正在运行的配置,等价于 show run / display cur,所有设备都具有该数据库。<startup/>:存储下次启动时生效的配置,等价于show startup / display saved。<candidate/>:存储没有生效的候选配置,等价于一些设备需要commit来使配置生效,并不是所有设备都支持。 
NETCONF 支持的操作 
操作 
说明 
 
 
<get-config>用来从<running/>、<candidate/>和<startup/>数据库中获取全部或部分配置数据。 
 
<get>用来从<running/>数据库中获取全部或部分运行配置数据或设备的状态数据。 
 
<edit-config>用来对<running/>或<candidate/>数据库新增、修改、删除配置数据。 
 
<copy-config>用源数据库替换目标数据库。如果目标数据库没有创建,则直接创建数据库,然后进行拷贝。 
 
<delete-config>用来删除一个数据库,但不能删除<running/>数据库。 
 
<lock>用来锁定一个数据库,独占数据库的修改权限,防止多用户并行操作设备产生冲突。 
 
<unlock>用来取消用户自己之前执行的<lock>操作,但不能取消其他用户的<lock>操作。 
 
<close-session>用来正常关闭NETCONF会话。 
 
<kill-session>用来强制关闭NETCONF会话,只有管理员用户才有权限执行<kill-session>操作。 
 
实验操作 基础环境配置 网络环境 使用 HCL 模拟器,打开一台设备,连接到本地网络
设备配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # interface GigabitEthernet0/0  port link-mode route  ip address 192.168.56.20 255.255.255.0 # local-user netdevops  password simple netdevops  authorization-attribute user-role network-admin  service-type ssh  #  ssh server enable  netconf ssh server enable  #  user-interface vty 0 63  authentication-mode scheme  # 
代码环境 
Python 3.8 
ncclient 0.6.7 
 
本次实验使用 ncclient 模块来操作网络设备,可以使用 pip install ncclient 来进行安装,可以先把 pip 下载源修改为国内的,否则下载速度会很慢,参考 pip 设置国内源 。
使用 NETCONF 获取设备接口信息 导入模块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 from  lxml import  etreefrom  lxml.builder import  ElementMakerfrom  ncclient import  managerhost = {     'host' : '192.168.56.20' ,     'username' : 'netdevops' ,     'password' : 'netdevops' ,     'port' : 830 ,     'device_params' : {'name' : 'h3c' }, } 
构建 XML XML 信息可以使用纯文本格式手写,也可以使用 lxml 工具来构建,构建方式可以参考上一篇文章  。
上文请求报文格式中说明了,对于 get 操作,需要加入 H3C 自有部分的命名空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 get_all_iface = """  <top xmlns="http://www.h3c.com/netconf/data:1.0"> <Ifmgr> <Interfaces> <Interface> <Name></Name> <InetAddressIPV4></InetAddressIPV4> <AdminStatus></AdminStatus> </Interface> </Interfaces> </Ifmgr> </top> """ H3C_DATA_1_0 = "http://www.h3c.com/netconf/data:1.0"  H3C_DATA_1_0_C = '{'  + H3C_DATA_1_0 + '}'  E = ElementMaker(namespace=H3C_DATA_1_0, nsmap={None : H3C_DATA_1_0}) top = E.top(     E.Ifmgr(         E.Interfaces(             E.Interface(                 E.Name(),                 E.InetAddressIPV4(),                 E.AdminStatus()         )     ) )) 
不论哪种方式构建,最终的内容都是一样的
连接设备,执行 XML 1 2 3 4 5 conn = manager.connect(**host, hostkey_verify=False , look_for_keys=False ) ret = conn.get(('subtree' , top)) print (ret)
上面代码中使用了 ncclient 封装的 get 操作,我们只需要传入 Content 层的 XML 信息即可,实际上传递给网络设备完整的一个请求报文包含了协议定义的部分,这部分属于 Operation 层,具体的原始 XML 是:
1 2 3 4 5 6 7 <rpc  message-id ="ncclient 自动生成的 id"  xmlns ="urn:ietf:params:xml:ns:netconf:base:1.0" >     <get >                                   <filter  type ="subtree" >  			<top > "构建的 xml 内容"</top >          </filter >      </get >  </rpc > 
上述几段代码结合起来,执行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?xml version="1.0" encoding="UTF-8"?> <rpc-reply  xmlns ="urn:ietf:params:xml:ns:netconf:base:1.0"  message-id ="urn:uuid:c2124ac3-2c72-4046-a575-de8ea8d151a7" > <data > <top  xmlns ="http://www.h3c.com/netconf/data:1.0" > <Ifmgr > <Interfaces > <Interface > <IfIndex > 1</IfIndex > <Name > GigabitEthernet0/0</Name > <AdminStatus > 1</AdminStatus > <InetAddressIPV4 > 192.168.56.20</InetAddressIPV4 > </Interface > <Interface > <IfIndex > 2</IfIndex > <Name > GigabitEthernet0/1</Name > <AdminStatus > 1</AdminStatus > </Interface > <Interface > <IfIndex > 3</IfIndex > <Name > GigabitEthernet0/2</Name > <AdminStatus > 1</AdminStatus > </Interface > <Interface > <IfIndex > 4</IfIndex > <Name > Serial1/0</Name > <AdminStatus > 1</AdminStatus > </Interface > <Interface > <IfIndex > 5</IfIndex > <Name > Serial2/0</Name > <AdminStatus > 1</AdminStatus > </Interface > <Interface > <IfIndex > 6</IfIndex > <Name > Serial3/0</Name > <AdminStatus > 1</AdminStatus > </Interface > <Interface > <IfIndex > 7</IfIndex > <Name > Serial4/0</Name > <AdminStatus > 1</AdminStatus > </Interface > <Interface > <IfIndex > 8</IfIndex > <Name > GigabitEthernet5/0</Name > <AdminStatus > 1</AdminStatus > </Interface > <Interface > <IfIndex > 9</IfIndex > <Name > GigabitEthernet5/1</Name > <AdminStatus > 1</AdminStatus > </Interface > <Interface > <IfIndex > 10</IfIndex > <Name > GigabitEthernet6/0</Name > <AdminStatus > 1</AdminStatus > </Interface > <Interface > <IfIndex > 11</IfIndex > <Name > GigabitEthernet6/1</Name > <AdminStatus > 1</AdminStatus > </Interface > <Interface > <IfIndex > 129</IfIndex > <Name > NULL0</Name > <AdminStatus > 1</AdminStatus > </Interface > <Interface > <IfIndex > 130</IfIndex > <Name > InLoopBack0</Name > <AdminStatus > 1</AdminStatus > <InetAddressIPV4 > 127.0.0.1</InetAddressIPV4 > </Interface > <Interface > <IfIndex > 131</IfIndex > <Name > Register-Tunnel0</Name > <AdminStatus > 1</AdminStatus > </Interface > </Interfaces > </Ifmgr > </top > </data > </rpc-reply > 
可以看到,已经成功从设备中获取到了想要的接口信息,对于设备不存在的信息,返回值没有该标签;
使用 NETCONF 下发接口配置 构建 XML 以给 G0/1 接口配置 IP 地址为例,由于 NETCONF 只支持通过 IfIndex 来进行配置,如果实际使用中想要根据接口名称来进行配置,则需要对功能进行封装;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from  lxml import  ElementMaker, etreeBASE_NS_1_0 = "urn:ietf:params:xml:ns:netconf:base:1.0"  H3C_CONFIG_1_0 = "http://www.h3c.com/netconf/config:1.0"  C = ElementMaker(namespace=BASE_NS_1_0, nsmap={None : BASE_NS_1_0}) E = ElementMaker(namespace=H3C_CONFIG_1_0, nsmap={None : H3C_CONFIG_1_0}) xml_ifcfg = C.config(     E.top(         E.Ifmgr(             E.Interfaces(                 E.Interface(                     E.IfIndex("2" ),                     E.Description("Configured by netconf" ),                     E.InetAddressIPV4("1.1.1.1" ),                     E.InetAddressIPV4Mask("24" )                 )             )         )     ) ) print (etree.tostring(xml_ifcfg))
实际生成的 XML 内容打印如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 <config  xmlns ="urn:ietf:params:xml:ns:netconf:base:1.0" > 	<top  xmlns ="http://www.h3c.com/netconf/config:1.0" >  		<IPV4ADDRESS >  			<Ipv4Addresses >  				<Ipv4Address >  					<IfIndex > 2</IfIndex >  					<Ipv4Address > 1.1.1.1</Ipv4Address >  					<Ipv4Mask > 255.255.255.0</Ipv4Mask >  					</Ipv4Address >  				</Ipv4Addresses >  		</IPV4ADDRESS >  	</top >  </config > '
连接设备,执行 XML 1 2 3 4 conn = manager.connect(**host, hostkey_verify=False , look_for_keys=False ) ret = conn.edit_config(target="running" , config=xml_ifcfg) print (ret)
返回值为 ok,说明配置下发成功,打印执行结果如下:
1 2 3 4 <?xml version="1.0" encoding="UTF-8"?> <rpc-reply  xmlns ="urn:ietf:params:xml:ns:netconf:base:1.0"  message-id ="urn:uuid:67ad766a-83de-45ba-a575-95059a6cfce6" > <ok /> </rpc-reply > 
到设备上检查配置下发成功:
使用 NETCONF 下发 BGP 配置 构建 XML 为设备配置 ASNumber 为 62333,并宣告 G1/0 的接口地址。
配置 ASN,即启动 BGP 进程 
配置地址族,表明配置生效的范围,如单播 IPv4,带有 VPN Instance 的单播 IPv4 等,并配置相关属性,如本地优先级、等价路由数目等 
配置宣告路由等 
 
对应的在 NETCONF 中下发配置时,操作逻辑也是一样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 xml_bgp_asn_cfg = """  <config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <top xmlns="http://www.h3c.com/netconf/config:1.0"> <BGP>  <Instances>  <Instance>  <Name></Name>  <ASNumber>62333</ASNumber>  </Instance>  </Instances> </BGP> </top> </config>""" xml_bgp_familys_cfg="""  <config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <top xmlns="http://www.h3c.com/netconf/config:1.0"> <BGP> <Familys> <Family> <Name></Name> <VRF></VRF> <Type>1</Type> </Family> </Familys> </BGP> </top> </config> """ xml_bgp_net_cfg = """  <config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <top xmlns="http://www.h3c.com/netconf/config:1.0"> <BGP>  <Networks>  <Network>  <Name></Name>  <VRF></VRF>  <Family>1</Family>  <IpAddress>1.1.1.1</IpAddress>  <Mask>24</Mask>  </Network>  </Networks> </BGP> </top> </config> """ 
连接设备,执行 XML 1 2 3 4 conn = manager.connect(**host, hostkey_verify=False , look_for_keys=False ) conn.edit_config(target="running" , config=xml_bgp_net_cfg) conn.edit_config(target="running" , config=xml_bgp_net_cfg) conn.edit_config(target="running" , config=xml_bgp_net_cfg) 
依次执行三项配置并返回成功后,可以在设备上看到相关的配置:
总结 这篇文章简单介绍了 NETCONF 协议,并结合上篇文章中关于 XML 的知识,进行了三个实际的操作案例。
乍一看,你可能会想:用 NETCONF 下发配置和我用命令行差不多啊,而且看起来好复杂啊,用命令行三下五除二就配置完成了。
NETCONF 的好处在于,如果将日常运维的操作封装为接口进行调用,并且以 WEB 的方式显示出来或者进行配置操作,会方便许多,而且可以做成标准化、流程化的操作进行变更,且返回的数据都是 XML 格式,可以很轻松的转换成 JSON,与其他平台进行联动,这些都是命令行操作不可控的(命令行的操作逻辑及返回数据处理不如 NETCONF 方便)。
附 问:你怎么知道获取接口信息、配置 BGP 的 XML 怎么写?点击链接进行下载 (官方资料)~