天天看點

「Python 網絡自動化」NETCONF —— Python 使用 NETCONF 管理配置 H3C 網絡裝置

「Python 網絡自動化」系列文章總目錄

Nornir 中文手冊——基于 Nornir3.0 官方文檔的不完全翻譯

文章目錄

    • NETCONF 簡單介紹
      • NETCONF 協定結構
      • NETCONF 封包結構
        • 請求封包格式
        • 封包回複格式
      • NETCONF 配置資料庫
      • NETCONF 支援的操作
    • 實驗操作
      • 基礎環境配置
        • 網絡環境
        • 裝置配置
        • 代碼環境
      • 使用 NETCONF 擷取裝置接口資訊
        • 導入子產品
        • 建構 XML
        • 連接配接裝置,執行 XML
      • 使用 NETCONF 下發接口配置
        • 建構 XML
        • 連接配接裝置,執行 XML
      • 使用 NETCONF 下發 BGP 配置
        • 建構 XML
        • 連接配接裝置,執行 XML
    • 總結

上一篇文章 中簡單介紹了 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 提供面向連接配接的、可靠的、順序的資料鍊路。

可參考下圖:

「Python 網絡自動化」NETCONF —— Python 使用 NETCONF 管理配置 H3C 網絡裝置

NETCONF 封包結構

NETCONF 指令必須符合 XML 語言的基本格式。NETCONF 封包格式遵循 RFC 4741/RFC 6241。

請求封包格式

對于 H3C 網絡裝置,請求封包分為兩部分:協定定義部分、H3C 自有部分,格式如下:

<?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 位址的消息為例,請求封包結構可以用下圖來說明:

「Python 網絡自動化」NETCONF —— Python 使用 NETCONF 管理配置 H3C 網絡裝置

封包回複格式

封包回複格式統一使用協定定義的

<rpc-reply>

<?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 模拟器,打開一台裝置,連接配接到本地網絡

「Python 網絡自動化」NETCONF —— Python 使用 NETCONF 管理配置 H3C 網絡裝置

裝置配置

#
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 擷取裝置接口資訊

導入子產品

# 導入 lxml 相關子產品,用于建構 xml
from lxml import etree
from lxml.builder import ElementMaker
# 導入 ncclient 相關子產品,用于使用 NETCONF 協定連接配接裝置
from ncclient import manager

# 根據網絡環境,建構包含裝置資訊的字典
host = {
    'host': '192.168.56.20',
    'username': 'netdevops',
    'password': 'netdevops',
    'port': 830,
    'device_params': {'name': 'h3c'},
}
           

建構 XML

XML 資訊可以使用純文字格式手寫,也可以使用 lxml 工具來建構,建構方式可以參考上一篇文章 。

上文請求封包格式中說明了,對于 get 操作,需要加入 H3C 自有部分的命名空間。

擷取裝置資訊需要使用 data 命名空間。

# 建構 xml 請求檔案,以下 xml 用于擷取裝置上所有的接口名稱
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>
"""
# 可以使用 lxml 相關子產品建構擷取接口資訊需要的 xml
# 以下 xml 用于擷取裝置上所有的接口名稱
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

# 對于 ssh 協定,連接配接裝置時會先儲存對端的 key,并從本機查找并驗證,使用以下兩個 False 的參數來跳過檢查
conn = manager.connect(**host, hostkey_verify=False, look_for_keys=False)
# 擷取裝置所有接口的名稱、IP位址、狀态
ret = conn.get(('subtree', top))
print(ret)
           

上面代碼中使用了 ncclient 封裝的 get 操作,我們隻需要傳入 Content 層的 XML 資訊即可,實際上傳遞給網絡裝置完整的一個請求封包包含了協定定義的部分,這部分屬于 Operation 層,具體的原始 XML 是:

<rpc message-id="ncclient 自動生成的 id" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
    <get>                         <!--- Operation 層,使用 get 操作 --->
        <filter type="subtree">
			<top>"建構的 xml 内容"</top>
        </filter>
    </get>
</rpc>
           

上述幾段代碼結合起來,執行結果如下:

<?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:

# 下發配置需要有 config 元素,且命名空間固定,之後再加入 top 元素及具體的配置資訊元素
from lxml import ElementMaker, etree

BASE_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 内容列印如下:

<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

# 将接口配置下發到 running 配置庫中
conn = manager.connect(**host, hostkey_verify=False, look_for_keys=False)
ret = conn.edit_config(target="running", config=xml_ifcfg)
print(ret)
           

傳回值為 ok,說明配置下發成功,列印執行結果如下:

<?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>
           

到裝置上檢查配置下發成功:

「Python 網絡自動化」NETCONF —— Python 使用 NETCONF 管理配置 H3C 網絡裝置

使用 NETCONF 下發 BGP 配置

建構 XML

為裝置配置 ASNumber 為 62333,并宣告 G1/0 的接口位址。

根據一般的 BGP 配置邏輯,應該:

  1. 配置 ASN,即啟動 BGP 程序
  2. 配置位址族,表明配置生效的範圍,如單點傳播 IPv4,帶有 VPN Instance 的單點傳播 IPv4 等,并配置相關屬性,如本地優先級、等價路由數目等
  3. 配置宣告路由等

對應的在 NETCONF 中下發配置時,操作邏輯也是一樣的。

根據需要進行的配置建構以下 XML:

# 配置 asn
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>"""
# 配置單點傳播 ipv4 位址族
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>
"""
# 在單點傳播 ipv4 位址族中宣告網段
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

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)
           

依次執行三項配置并傳回成功後,可以在裝置上看到相關的配置:

「Python 網絡自動化」NETCONF —— Python 使用 NETCONF 管理配置 H3C 網絡裝置

總結

這篇文章簡單介紹了 NETCONF 協定,并結合上篇文章中關于 XML 的知識,進行了三個實際的操作案例。

乍一看,你可能會想:用 NETCONF 下發配置和我用指令行差不多啊,而且看起來好複雜啊,用指令行三下五除二就配置完成了。

NETCONF 的好處在于,如果将日常運維的操作封裝為接口進行調用,并且以 WEB 的方式顯示出來或者進行配置操作,會友善許多,而且可以做成标準化、流程化的操作進行變更,且傳回的資料都是 XML 格式,可以很輕松的轉換成 JSON,與其他平台進行關聯,這些都是指令行操作不可控的(指令行的操作邏輯及傳回資料處理不如 NETCONF 友善)。

問:你怎麼知道擷取接口資訊、配置 BGP 的 XML 怎麼寫?

答:參考官方的 NETCONF API 開發手冊。華為的可以在官網直接找到,華三的可以點選連結進行下載下傳(官方資料)~