python網絡自動化netconf配置模塊ncclient學習筆記

ncclient介紹

ncclient簡介:

ncclient是一個用於NETCONF客戶端的Python庫。它旨在體用一個直觀的API,將NETCONF的XML編碼特性映射到Python構造和習語,並使編寫網絡管理腳本更容易。

其他主要功能有:

  • 支持RFC 4741中定義的所有操作和功能。
  • 管道請求。
  • 異步RPC請求。
  • 保持XML的方式,除非真正需要變更。
  • 擴展。可以輕鬆添加新的傳輸映射和功能/操作。

Netconf相關原理,概念:

netconf學習筆記: https://cshihong.github.io/2019/12/29/Netconf%E5%8D%8F%E8%AE%AE%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/

ncclient主要使用python實現了netconf的相關操作。

ncclient庫學習

manager文件內主要功能:

這個模塊是周圍庫的一個抽象層,它公開所有的核心功能。

manager支持的操作:

manager中的操作,都是映射到ncclient.operations.xxx對應的calss。如下:

manager.OPERATIONS
Out[10]: 
{'get': ncclient.operations.retrieve.Get,
 'get_config': ncclient.operations.retrieve.GetConfig,
 'get_schema': ncclient.operations.retrieve.GetSchema,
 'dispatch': ncclient.operations.retrieve.Dispatch,
 'edit_config': ncclient.operations.edit.EditConfig,
 'copy_config': ncclient.operations.edit.CopyConfig,
 'validate': ncclient.operations.edit.Validate,
 'commit': ncclient.operations.edit.Commit,
 'discard_changes': ncclient.operations.edit.DiscardChanges,
 'cancel_commit': ncclient.operations.edit.CancelCommit,
 'delete_config': ncclient.operations.edit.DeleteConfig,
 'lock': ncclient.operations.lock.Lock,
 'unlock': ncclient.operations.lock.Unlock,
 'create_subscription': ncclient.operations.subscribe.CreateSubscription,
 'close_session': ncclient.operations.session.CloseSession,
 'kill_session': ncclient.operations.session.KillSession,
 'poweroff_machine': ncclient.operations.flowmon.PoweroffMachine,
 'reboot_machine': ncclient.operations.flowmon.RebootMachine}

常見方法:

def make_device_handler(device_params):

創建一個設備處理對象,該對象提供設備的特殊參數和功能。它在其ncclient的代碼中多次調用。

如果device_params 參數沒有定義,或者這個參數對應的值在參數字典中不知道,那麼會返會一個默認的處理程序。

使用實例:

manager.make_device_handler({'name':'huawei'})
  • Juniper: device_params={‘name’:’junos’}
  • Cisco CSR: device_params={‘name’:’csr’}
  • Cisco Nexus: device_params={‘name’:’nexus’}
  • Huawei: device_params={‘name’:’huawei’}
  • H3C: device_params={‘name’:’h3c’}

def connect_ssh(*args, **kws):

初始化一個連接,通過:class:Manager over the SSH transport.

通過class:ncclient.transport.SSHSession實現。

return Manager(session, device_handler, **manager_params)

def connect_ioproc(*args, **kwds):

調用ncclient.thransport.third_party。

return Manager(session, device_handler, **manager_params)

def connect(*args, **kwds):

如果host爲localhosthe和device_param爲junos/local

return connect_ioproc(*args, **kwds)

否則:

return connect_ssh(*args, **kwds)

class Manager(object):

將RPC操作的API作爲方法公開調用。這些方法的返回類型取決於我們是處於異步模式(asynchronous mode)還是同步模式(synchronous mode)。

  • asynchronous mode(同步模式下):

同步模式下等待應答並返回相應的RPCReply對象。根據異常引發模式的不同,應答中的rpc錯誤可能會被引發爲RPCError異常。

  • asynchronous mode(異步模式):

    異步模式下操作立即返回相應的RPC對象。錯誤處理和檢查是否已收到回覆必須手動處理。

注意:在get()和get_config()操作中,reply是GetReply 的一個實例, 它公開的附加屬性data (as Element )和data_xml (作爲string ),這是這些操作的主要關注點。

在傳輸層錯誤的情況下,例如意外的會話關閉,TransportError 將被觸發。

有關預期的操作行爲和其他參數在:rfc4741中。

Manager實例也是上下文管理器,所以你可以這樣使用它:

with manager.connect("host") as m:
            # do your stuff

#or like this::

m = manager.connect("host")
try:
     # do your stuff
finally:
     m.close_session()

def get():

檢索運行配置和設備狀態信息。 查詢的是設備當前運行的狀態數據,即只能從配置數據庫中獲取數據。

不需要使用source參數指定配置數據庫。

操作成功,Server回覆的元素中含有參數,中封裝了獲取的結果數據。否則在消息中返回。

def get_config(source, filter=None)

檢索指定配置的全部或部分。

  • source:指定需要查詢的數據庫名稱。有running (正在運行的數據庫) ,startup (下次設備啓動配置數據庫), candidate (兩階段運行數據庫,需要commit提交生效)

  • fileter: 過濾器。過濾器可以採用如下方式:

    • A tuple of (type, criteria)

    這裏的type必須是xpath 或者subtree.

    對於“xpathcriteria 應該是包含XPath表達式的字符串。

    對於substreecriteria應該是一個XML字符串或一個包含標準的元素對象。

    • element 作爲XML 字符串的一個Element 對象。

def edit_config(target, config, default_operation=None, test_option=None, error_option=None):

將指定配置的全部或部分加載到目標配置數據存儲中。

  • target:指定要配置的數據庫。

  • config:必須放在元素中, 它可以指定爲字符串或Element

  • default_operation: 如果指定,必須是{ “merge”, “replace”, or “none” } 其中之一。

  • test_option: { “test_then_set”, “set” } 之一。

  • error_option: { “stop-on-error”, “continue-on-error”, “rollback-on-error” } 之一。

    The “rollback-on-error” error_option :rollback-on-error capability.

def copy_config(source, target):

源配置數據庫替換目標配置數據庫。如果目標配置數據庫沒有創建,則直接創建配置數據庫,否則用源配置數據庫直接覆蓋目標配置數據庫。

  • source: 是配置數據存儲的名稱,用作包含要複製的配置子樹的複製操作或配置元素的源 。
  • target: 是要用作複製操作目標的配置數據存儲的名稱 。

def delete_config(target):

刪除配置數據庫。

  • target: target指定要刪除的配置數據存儲的名稱或URL

def dispatch(rpc_command, source=None, filter=None):

  • rpc_command:指定以純文本或xml元素格式(取決於命令)發送rpc命令

例如:

dispatch('clear-arp-table')

或者:

xsd_fetch = new_ele('get-xnm-information')
sub_ele(xsd_fetch, 'type').text="xml-schema"
sub_ele(xsd_fetch, 'namespace').text="junos-configuration"
dispatch(xsd_fetch)

def lock(target):

允許客戶端鎖定設備的配置系統。

def unlock(target):

釋放一個配置鎖。

def locked(self, target):

返回一個上鎖的數據庫的上下文件管理器,對應執行操作是:

return operations.LockContext(self._session, self._device_handler, target)

target : 是要鎖定的配置數據存儲的名稱,

with m.locked("running"):
	# do your stuff

# ... instead of::
m.lock("running")
try:
    # do your stuff
finally:
    m.unlock("running")

def take_notification(self, block=True, timeout=None)

嘗試從接收的隊列中檢索一個通知,如果block爲True,則調用將等待直到通知收到爲止。如果timeout是一個大於0的數字,調用將等待它超時前收到通知的時間

如果在block爲False或時沒有可用的通知超時後,將不返回任何值。

return self._session.take_notification(block, timeout)

def close_session():

請求優雅地終止NETCONF會話,並關閉傳輸層會話。

def kill_session(session_id):

強制去終止一個NETCONF會話。

  • session_id:要終止會話的NETCONF會話標識符。

def commit(confirmed=False, timeout=None, persist=None):

將數據庫配置數據提交,轉化爲設備的新當運行的配置即。取決於:candidate capability

  • timeout: 操作確認超時時間,單位是秒,缺省值是600秒。設備執行操作後,在確認超時時間內,如果沒有執行確認操作,則對數據庫中的配置進行回滾,配置數據恢復到執行操作之前的狀態,並放棄數據庫中的編輯數據。

  • persist: 使已確認的提交在會話終止後繼續執行,並在正在進行的已確認的提交上設置token 。

def discard_changes():

將候選配置還原爲當前正在運行的配置。任何未提交的更改都會被丟棄。

def validate(source):

驗證指定配置的內容。

  • source 是要驗證的配置數據存儲或包含要驗證的配置子樹的配置元素的名稱

self._session._server_capabilities

返會服務器支持的能力集。

for i in m._session._server_capabilities:
      print(i)
urn:ietf:params:netconf:base:1.0
urn:ietf:params:netconf:base:1.1
urn:ietf:params:netconf:capability:writable-running:1.0
                        ....

self._session._client_capabilities

返會客戶端支持的能力集。

for i in m._session._client_capabilities:
      print(i)

urn:ietf:params:netconf:base:1.0
urn:ietf:params:netconf:base:1.1
urn:ietf:params:netconf:capability:writable-running:1.0
....

self._session.id

返會一個seesion.id 。

self._session.connected

判斷seesion是否已經建立。

raise_mode:

指定哪些錯誤引發RPCError 異常。有效值是在RaiseMode 中定義的常量。默認值是ALL

timeout:

指定同步RPC請求的超時。

async_mode:

指定操作是異步執行的(True)還是同步執行的(False)(缺省值)。

capbilities 文件主要功能;

表示netconf的能力集。

ncclient.capabilities.schemes(url_uri):

給定一個具有scheme查詢字符串的URI(即:url capability URI),將返回一個受支持的模式列表。

class ncclient.capabilities.Capabilities(capabilities) :

表示NETCONF客戶端或服務器可用的能力集。它是由一個capability URI列表初始化的 。

“cap” in caps

可以通過":cap" in caps 判斷一個能力集是否是客戶端或者服務器所支持的能力集。cap也能簡寫。

除了URI之外,對於urn:ietf:params:netconf:capability:name:name:version表示的能力,它們的簡寫可以用作鍵。例如,對於urn:ietf:params:netconf:capability:candidate:1.0,簡寫應該是:candidate。如果版本是重要的,使用:candidate:1.0作爲鍵。

iter(caps):

返會能力集的迭代對象。

xml_文件主要功能:

用於創建、解析和處理XML和ElementTree對象的方法。

XML異常:

exception ncclient.xml_.XMLError , 基於:ncclient.NCClientError

def to_xml(ele, enconding=“UTF-8”, pretty_print=False):

轉換並返回具有指定編碼的ele(Element)的XML。

def to_ele(x, huge_tree=False):

轉換並返回XML文檔x的元素。

ncclient.xml_.parse_root(raw):

有效地解析原始XML文檔的根元素,返回其限定名和屬性字典的元組。

ncclient.xml_.validated_element(x, tags=None, attrs=None**)** :

檢查XML文檔或元素的根元素是否滿足提供的條件。

  • tags: (如果指定)是單個允許的標記名或一系列允許的替代項
  • attrs: (如果指定)是所需屬性的序列,每個屬性都可以是多個允許的替代項的序列
  • 如果不滿足要求,則引發XMLError。

transport-傳輸/會話層:

主要實現了NETCONF傳輸層連接。

session.py:

class ncclient.transport.Session(capabilities)

傳輸協議實現使用的基類。

def add_listener(listener):

註冊一個監聽器,它將收到傳入消息和錯誤的通知。

def client_capabilities():

客戶端的能力集。

def connected():

返回會話的連接狀態。

def get_listener_instance(cls):

如果註冊了指定類型的偵聽器,則返回實例 。

def id():

表示會話id的字符串。如果沒有初始化,爲None。

def remove_listener(listener):

註銷一些監聽器;如果監聽器沒有被註冊,將被忽略掉。

def server_capabilities():

返回服務器支持的能力集。

class ncclient.transport.SeesionListener:

會話監聽器的基類,當接收到新的NETCONF消息或發生錯誤時,會通知該類。

def callback(root, raw)

收到新的XML文檔時調用。 root參數允許回調確定是否要進一步處理文

  • root:是一個元組(tag, arrtibutes)類型。

    tag: 根元素限定名。

    attributes:是屬性名的字典。

  • row : 以字符串的形式包含XML文檔。

def errback(ex):

發生錯誤時調用。

ssh.py:

是ssh會話的實現。

ssh.default_unknow_host_cb(host, fingerprint):

如果未知的主機回調發現可接受的密鑰,則返回True,否則返回False。

默認返回False,可能導致connet()引發SSHUnknowHost 異常。

如果需要以編程方式驗證主機密鑰,需要供另一個有效的回調。

  • host:需要驗證的主機名

  • fingerprint:表示主機指紋的16進制字符串。用冒號分隔。

    例如: “4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21”

class ncclient.transport.SSHSession(device_handler):

實現通過ssh的netconf會話連接。

connect(host[, port=830, timeout=None, unknown_host_cb=default_unknown_host_cb, username=None, password=None, key_filename=None, allow_agent=True, look_for_keys=True, ssh_config=None])

通過SSH連接並初始化NETCONF會話。首先嚐試公鑰身份驗證方法,然後嘗試密碼身份驗證。

若要完全禁用公鑰嘗試身份驗證,設置 allow_agent和look_for_keys作爲False調用 。

  • host:要連接的主機名或者IP地址。
  • port:連接的端口,默認830。有些設備可能是22。網絡設備也能配置自定義端口。根據配置情況進行指定。
  • timeout:超時時間,是socket連接的可選超時。
  • unknow_host_cd: 當服務器主機密鑰無法識別時調用。有兩參數。host,fingerprint。
  • username: 連接ssh認證的用戶名。
  • password:ssh用戶密碼。
  • key_filename: 祕鑰文件。
  • allow_agent: 是否啓用SSH代理。
  • host_for_keys: 允許在~/.ssh/id_* 尋找ssh密鑰。
  • ssh_config: 對OpenSSH配置文件解析,設置路徑,例如:~/.ssh/config

def load_known_hosts(filename=None):

從指定文件中加載主機密鑰。從而可以多次調用。如果未指定文件路徑,會使用默認位置:~/.ssh/know_hosts and ~/ssh/know_hosts

Errors:

  • exceptionncclient.transport.TransportError

    Bases: ncclient.NCClientError

  • exceptionncclient.transport.SessionCloseError(in_buf, out_buf=None)

    Bases: ncclient.transport.errors.TransportError

  • exceptionncclient.transport.SSHError

    Bases: ncclient.transport.errors.TransportError

  • exceptionncclient.transport.AuthenticationError

    Bases: ncclient.transport.errors.TransportError

    exception*ncclient.transport.SSHUnknownHostError(host, fingerprint)

    Bases: ncclient.transport.errors.SSHError

operations類的主要功能:

該文件夾內主要定義了netconf操作的實現方法。

dir(operations)
Out[3]: 
['CancelCommit',
 'CloseSession',
 'Commit',
 'CopyConfig',
 'CreateSubscription',
 'DeleteConfig',
 'DiscardChanges',
 'Dispatch',
 'EditConfig',
 'Get',
 'GetConfig',
 'GetReply',
 'GetSchema',
 'KillSession',
 'Lock',
 'LockContext',
 'MissingCapabilityError',
 'OperationError',
 'PoweroffMachine',
 'RPC',
 'RPCError',
 'RPCReply',
 'RaiseMode',
 'RebootMachine',
 'TimeoutExpiredError',
 'Unlock',
 'Validate',
 'edit',
 'errors',
 'flowmon',
 'lock',
 'retrieve',
 'rpc',
 'session',
 'subscribe',
 'util']

RPC類:

class RPC是所有操作的基類,直接對應於rpc請求。處理髮出請求和接收回復。

classncclient.operations.RPC(session, device_handler, async=False, timeout=30, raise_mode=0)
  • sesssion : 是一個netconf的Session實例。
  • device_handler: 設備廠商標識
  • async: 請求是否異步模式。
  • timeout:超時。
  • raise_mode = 0; 引發異常類型。

classncclient.operations.RaiseMode Operations類異常類型如下:

定義應如何處理RPC指示的錯誤。

  • ALL = 2, 不看錯誤類型,總是引發。
  • ERRORS = 1, 僅僅當出現一個真正的錯誤類型是才引發。
  • NONE = 0, 不嘗試去引發任何類型的rpc-error作爲RPCError.

def _assert(capability):

子類可以在發出RPC請求之前,使用此方法來驗證NETCONF服務器具有的capability, 如果該capablility不可用,將會引發一個MissingCapabilityError異常。

def _request(op):

request 方法的實現,發送RPC請求和處理迴應。

  • 在同步模式下,阻塞直到收到RPCReply。

  • 在異步模式下,立即返會。

  • op:是RPC請求的實際操作。xml類型的數據。

request():

操作的子類必須實現該方法。通常,將所有請求構建成一個Element元素op,然後傳遞給_requests(op)去處理。

RPCReply(raw)類:

表示一個rpc-reply,僅僅關注這個RPC請求操作是否成功。

def ok():

返會一個布爾值,表示RPC回覆是否有錯誤。

RPCError(raw, errs=None):

表示rpc-error類型。基類是ncclient.operations.errors.OperationError.

  • info:XML字符串,或者None。表示error-info 。
  • message:error-message元素的內容如果存在或者None。
  • path: error-path元素的內容如果存在或者None。
  • severity: error-severity元素的內容。
  • tag: error-tag元素的內容。
  • type: error-type元素的內容。

Retrieval檢索操作:

實現netconf查詢類操作。操作類的基類都是: ncclient.operations.rpc.RPC

class Get():

class ncclient.operations.Get(session, device_handler, async=False, timeout=30, raise_mode=0)

def requests((self, filter=None, with_defaults=None):

檢索運行配置和設備狀態信息

  • filter: 指定要檢索的配置部分(默認檢索整個配置)
  • with_defaults: 定義從配置中檢索默認值的顯式方法(參見RFC 6243)

class GetConfig():

class ncclient.operations.GetConfig(session, device_handler, async=False, timeout=30, raise_mode=0)

實現了request(source, filter=None), 檢索指定配置的全部或部分。

  • source:指定要檢測的配置數據庫。
  • filter:過濾。

class GetReply(raw):

基類是; ncclient.operations.rpc.RPCReply

data元素的屬性添加到’ RPCReply ’

  • data: 和data_ele 一樣。
  • data_ele: 數據元素作爲一個Element
  • data_xml: 數據元素作爲一個XML字符串。

class Dispatch():

classncclient.operations.Dispatch(session, device_handler, async=False, timeout=30, raise_mode=0)

request(rpc_command, source=None, filter=None)

rpc_command: 指定以純文本或xml元素格式(取決於命令).

使用示例:

dispatch('clear-arp-table')

or dispatch element like

xsd_fetch = new_ele('get-xnm-information')
sub_ele(xsd_fetch, 'type').text="xml-schema"
sub_ele(xsd_fetch, 'namespace').text="junos-configuration"
dispatch(xsd_fetch)

EditConfig():

實現通過netconf對設備進行配置。 將指定配置的全部或部分加載到目標配置數據存儲中。

classncclient.operations.EditConfig(session, device_handler, async=False, timeout=30, raise_mode=0)

request(config, format='xml', target='candidate', default_operation=None, test_option=None, error_option=None)
  • target: 正在編輯的配置數據存儲的名稱
  • config:需要變更的配置,它必須植根於配置元素。它可以指定爲字符串或Element。
  • default_operation: { “merge”, “replace”, or “none” } 三者之一。
  • test_option: { “test_then_set”, “set” } 之一。
  • error_option: { “stop-on-error”, “continue-on-error”, “rollback-on-error” } 之一。

DeleteConfig():

實現刪除配置的操作。刪除配置數據庫。

classncclient.operations.DeleteConfig(session, device_handler, async=False, timeout=30, raise_mode=0)

request(target)

CopyConfig():

用另一個完整配置數據存儲的內容創建或替換整個配置數據存儲

classncclient.operations.CopyConfig(session, device_handler, async=False, timeout=30, raise_mode=0)

request(source, target)
  • source: 是配置數據存儲的名稱,用作包含要複製的配置子樹的複製操作或配置元素的源
  • target; 配置數據存儲的名稱是否要用作複製操作的目標。

Validate():

驗證指定配置的內容。

classncclient.operations.Validate(session, device_handler, async=False, timeout=30, raise_mode=0)

request(source='candidate')

Commit():

將候選配置提交爲設備的新當前配置。取決於: candidate capability.

classncclient.operations.Commit(session, device_handler, async=False, timeout=30, raise_mode=0)

request(confirmed=False, timeout=None, persist=None)

如果在超時間隔內沒有後續提交,確認提交(即confiremed is True)將被還原。如果沒有指定超時,則確認超時默認爲600秒(10分鐘)。

  • comfirmed: 是否確認提交 。
  • timeout: 指定確認超時(以秒爲單位)
  • persit:使已確認的提交在會話終止後繼續執行,並在正在進行的已確認的提交上設置token。

DiscardChanges():

將候選配置還原爲當前正在運行的配置。任何未提交的更改都會被丟棄。

classncclient.operations.DiscardChanges(session, device_handler, async=False, timeout=30, raise_mode=0)

request()

Lock():

允許客戶端鎖定設備的配置系統。

classncclient.operations.Lock(session, device_handler, async=False, timeout=30, raise_mode=0)

request(target='candidate')

Unlock():

釋放一個配置鎖,這是以前通過鎖操作獲得的。

classncclient.operations.Unlock(session, device_handler, async=False, timeout=30, raise_mode=0)

request(target='candidate')

CloseSession():

請求優雅地終止NETCONF會話,並關閉傳輸。

classncclient.operations.CloseSession(session, device_handler, async=False, timeout=30, raise_mode=0)

request()

KillSession():

強制終止一個NETCONF會話(不是當前會話!)

classncclient.operations.KillSession(session, device_handler, async=False, timeout=30, raise_mode=0)

request(session_id)

使用ncclient獲取設備配置的python小程序示例

獲取acl 3900配置信息:

import logging
from string import Template
from ncclient import manager
from ncclient import operations

log = logging.getLogger(__name__)


def huawei_connect(host, port, user, password):
    return manager.connect(host=host,
                           port=port,
                           username=user,
                           password=password,
                           hostkey_verify = False,
                           device_params={'name': 'huawei'},
                           allow_agent = False,
                           look_for_keys = False)
    
def _check_response(rpc_obj, snippet_name):
    print("RPCReply for %s is %s" % (snippet_name, rpc_obj.xml))
    xml_str = rpc_obj.xml
    print(xml_str)
    if "<ok/>" in xml_str:
        print("%s successful" % snippet_name)
    else:
        print("Cannot successfully execute: %s" % snippet_name)  
        
    
def test_edit_config_running(host, port, user, password):
    #1.Create a NETCONF session
    with huawei_connect(host, port=port, user=user, password=password) as m:

        acl = '''
       <access-lists xmlns="urn:ietf:params:xml:ns:yang:ietf-acl" xmlns:hw-acl="urn:huawei:params:xml:ns:yang:huawei-acl" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
        <access-list>
          <access-control-list-name>3900</access-control-list-name>
          
        </access-list>
      </access-lists>

'''
        get = m.get_config(source='running', filter = ('subtree', acl))
        print(get)


if __name__ == '__main__':
    host = 'x.x.x.x'
    port = 830
    user = 'user'
    password = 'password'
    test_edit_config_running(host, port, user, password)

程序返會內容:

<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="urn:uuid:f6ec534a-0eb1-4aea-a627-de98705cc780">
  <data>
    <access-lists xmlns="urn:ietf:params:xml:ns:yang:ietf-acl" xmlns:hw-acl="urn:huawei:params:xml:ns:yang:huawei-acl">
      <access-list>
        <access-control-list-name>3900</access-control-list-name>
        <hw-acl:vsys>public</hw-acl:vsys>
        <access-list-entries>
          <access-list-entry>
            <rule-name>5</rule-name>
            <matches>
              <protocol>0</protocol>
              <source-ipv4-network>1.1.1.0/24</source-ipv4-network>
              <destination-ipv4-network>1.1.1.2/32</destination-ipv4-network>
            </matches>
            <actions>
              <permit/>
            </actions>
          </access-list-entry>
        </access-list-entries>
      </access-list>
    </access-lists>
  </data>
</rpc-reply>

附加:

華爲Netconf開發指南: https://support.huawei.com/enterprise/zh/doc/EDOC1100109432

華爲netconf API : https://support.huawei.com/enterprise/zh/doc/EDOC1100075576


參考資料: https://ncclient.readthedocs.io/en/latest/#

ncclient源碼: https://github.com/ncclient/ncclient


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章