「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 内容層 | 具體的配置資料、狀态資料等資訊 | 被管理對象的資訊,包括配置、狀态等,如: 這個 XML 就是一個簡單的内容層,它表示了一個接口的名稱資訊:G0/0。 |
Operations 操作層 | , , | RPC 中的基本的原語操作集,NETCONF 對其進行擴充,全面定義了對被管理裝置的各種基礎操作,如 , , , 等。 |
RPC 遠端調用層 | , | 為 RPC 子產品的編碼提供了簡單的、傳輸協定無關的機制,在 XML 中使用 , 對上層的請求和響應資料進行封裝。 |
Transport Protocol 通信協定層 | 裝置登入方式,支援 Console、SSH、HTTP、TLS、Telnet 等 | 為 NETCONF 提供面向連接配接的、可靠的、順序的資料鍊路。 |
可參考下圖:
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 位址的消息為例,請求封包結構可以用下圖來說明:
封包回複格式
封包回複格式統一使用協定定義的
<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 支援的操作
操作 | 說明 |
---|---|
| 用來從 、 和 資料庫中擷取全部或部配置設定置資料。 |
| 用來從 資料庫中擷取全部或部分運作配置資料或裝置的狀态資料。 |
| 用來對 或 資料庫新增、修改、删除配置資料。 |
| 用源資料庫替換目标資料庫。如果目标資料庫沒有建立,則直接建立資料庫,然後進行拷貝。 |
| 用來删除一個資料庫,但不能删除 資料庫。 |
| 用來鎖定一個資料庫,獨占資料庫的修改權限,防止多使用者并行操作裝置産生沖突。 |
| 用來取消使用者自己之前執行的 操作,但不能取消其他使用者的 操作。 |
| 用來正常關閉NETCONF會話。 |
| 用來強制關閉NETCONF會話,隻有管理者使用者才有權限執行 操作。 |
實驗操作
基礎環境配置
網絡環境
使用 HCL 模拟器,打開一台裝置,連接配接到本地網絡
裝置配置
#
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>
到裝置上檢查配置下發成功:
使用 NETCONF 下發 BGP 配置
建構 XML
為裝置配置 ASNumber 為 62333,并宣告 G1/0 的接口位址。
根據一般的 BGP 配置邏輯,應該:
- 配置 ASN,即啟動 BGP 程序
- 配置位址族,表明配置生效的範圍,如單點傳播 IPv4,帶有 VPN Instance 的單點傳播 IPv4 等,并配置相關屬性,如本地優先級、等價路由數目等
- 配置宣告路由等
對應的在 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)
依次執行三項配置并傳回成功後,可以在裝置上看到相關的配置:
總結
這篇文章簡單介紹了 NETCONF 協定,并結合上篇文章中關于 XML 的知識,進行了三個實際的操作案例。
乍一看,你可能會想:用 NETCONF 下發配置和我用指令行差不多啊,而且看起來好複雜啊,用指令行三下五除二就配置完成了。
NETCONF 的好處在于,如果将日常運維的操作封裝為接口進行調用,并且以 WEB 的方式顯示出來或者進行配置操作,會友善許多,而且可以做成标準化、流程化的操作進行變更,且傳回的資料都是 XML 格式,可以很輕松的轉換成 JSON,與其他平台進行關聯,這些都是指令行操作不可控的(指令行的操作邏輯及傳回資料處理不如 NETCONF 友善)。
附
問:你怎麼知道擷取接口資訊、配置 BGP 的 XML 怎麼寫?
答:參考官方的 NETCONF API 開發手冊。華為的可以在官網直接找到,華三的可以點選連結進行下載下傳(官方資料)~