之前简单介绍了 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>
这个 XML 就是一个简单的内容层,它表示了一个接口的名称信息:G0/0。
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 自有部分: 对于 get 系列操作,filter 元素下的内容为 H3C 自有部分;对于 edit-config 系 列操作,config 元素下的内容为 H3C 自有部分。 H3C 自有部分需要使用H3C命名空间,H3C 命名空间又分为 base、config、data、action 命名空间。
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 自有部分的命名空间。 获取设备信息需要使用 data 命名空间。
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 来进行配置,如果实际使用中想要根据接口名称来进行配置,则需要对功能进行封装; 从上面的结果中可以看到 G0/1 的接口索引值为 2,所以构建以下 XML:
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 的接口地址。 根据一般的 BGP 配置逻辑,应该:
配置 ASN,即启动 BGP 进程
配置地址族,表明配置生效的范围,如单播 IPv4,带有 VPN Instance 的单播 IPv4 等,并配置相关属性,如本地优先级、等价路由数目等
配置宣告路由等
对应的在 NETCONF 中下发配置时,操作逻辑也是一样的。 根据需要进行的配置构建以下 XML:
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 怎么写? 答:参考官方的 NETCONF API 开发手册。华为的可以在官网直接找到,华三的可以点击链接进行下载 (官方资料)~