NETCONF est défini dans le RFC 6241. Il s’agit d’un protocole transporté via TLS et SSH, qui comprend des concepts d’opérations et de configuration de datastore permettant la gestion d’un périphérique réseau. Le protocole est présenté comme une API que les applications peuvent utiliser pour envoyer et recevoir des ensembles de données de configuration et recevoir des ensembles de données d’état et de fonctionnement. NETCONF définit l’existence d’un ou plusieurs datastores de configuration et autorise les opérations de configuration dessus. Le RFC 6241 définit trois datastores :
- <running>, présent dans le modèle de base
- <startup>, optionnel et annoncé dans les capabilities
- <candidate>, optionnel et annoncé dans les capabilities
SSH est le principal protocole de transport utilisé par NETCONF. Les étapes suivantes se produisent lors d’une session NETCONF :
- Le client se connecte au sous-système NETCONF SSH.
- Le serveur répond avec un « hello » qui inclut les capabilities prises en charge.
- Le client répond avec des capabilities prises en charge pour établir une connexion.
- Le client émet une requête NETCONF.
- Le serveur émet une réponse ou effectue une opération.
Toutes les opérations NETCONF sont codées sous forme de messages XML et les « Data Models » sont décrits avec un langage appelé YANG. La principale motivation de YANG est de fournir un moyen standard de modéliser les données de configuration et les données opérationnelles afin que la configuration et la surveillance des périphériques réseau puissent être basées sur des modèles communs. Les modèles communs doivent être interopérables entre différents fournisseurs, au lieu d’être basés sur des CLI spécifiques à chacun des fournisseur. NETCONF propose les opérations suivantes :
- get – récupère les informations sur la configuration en cours d’exécution et l’état des périphériques, c’est-à-dire les données opérationnelles.
- get-config – récupère tout ou partie d’un datastore de configuration spécifié.
- edit-config – charge tout ou partie d’une configuration dans un datastore de configuration spécifié.
- copy-config – crée ou remplace complètement un datastore par le contenu d’un autre.
- delete-config – supprime un datastore. Le datastore de configuration <running> ne peut pas être supprimé.
- lock – permet au client de verrouiller tout le système de datastore d’un périphérique. Ces verrous sont destinés à être de courte durée. Ils permettent à un client d’effectuer une modification sans craindre d’interagir avec d’autres clients NETCONF, des clients non NETCONF (par exemple, des scripts SNMP et CLI) et des utilisateurs.
- unlock – libère un verrou de configuration précédemment obtenu à l’aide d’une opération lock.
- close-session – demande la fin d’une session NETCONF.
- kill-session – force la fin d’une session NETCONF.
Pour réaliser ces tests, j’utilise une image 16.12.04a d’un CSR1000v afin de disposer d’un IOS-XE dans EVE-NG. Une fois injectée et initialisée dans le simulateur, un minimum de configuration est nécessaire afin de permettre un accès en NETCONF sur l’équipement virtuel. Entre autre, les deux commandes netconf ssh
et netconf-yang
permettent d’activer la partie NETCONF.
La librairie Python que j’utilise pour implémenter un client NETCONF est ncclient et pour réaliser des scripts sans aucun framework, je mets en place un fichier de configuration au format YAML incluant un concept de template Python pour mettre certaines valeurs sous forme de variables.
--- - host: 192.168.1.98 port: 830 username: cisco password: cisco name: iosxe payloads: expression: > <filter> <interfaces xmlns="http://openconfig.net/yang/interfaces"> <interface> <name>{interface_name}</name> </interface> </interfaces> </filter> configuration: > <config> <interfaces xmlns="http://openconfig.net/yang/interfaces"> <interface> <name>{interface_name}</name> <config> <description>{interface_description}</description> </config> </interface> </interfaces> </config>
#!/usr/bin/env python from ncclient import manager from ncclient.operations import RPCError import yaml if __name__ == '__main__': # get parameters from yaml file with open('hosts.yaml') as f: hosts = yaml.load(f, Loader=yaml.FullLoader) # iterate onto the list for host in hosts: # connect to netconf agent with manager.connect(host=host['host'], port=host['port'], username=host['username'], password=host['password'], timeout=90, hostkey_verify=False, device_params={'name': host['name']}) as m: # execute netconf operation try: for capability in m.server_capabilities: print('=' * 30) print(capability) except RPCError as e: print(e._raw)
Dans les capabilities renvoyées par IOS-XE, on voit des modules de type « standard » et de type « vendor specific ». Dans les modules standard, j’en choisis un de l’OpenConfig qui traite de l’interface. Une analyse sur le catalogue YANG permet de voir la structure du module YANG afin de l’utiliser en tant que « Subtree Filtering » de NETCONF.
#!/usr/bin/env python from ncclient import manager from ncclient.operations import RPCError import xml.dom.minidom import yaml if __name__ == '__main__': # get parameters from yaml file with open('hosts.yaml') as f: hosts = yaml.load(f, Loader=yaml.FullLoader) # iterate onto the list for host in hosts: # connect to netconf agent with manager.connect(host=host['host'], port=host['port'], username=host['username'], password=host['password'], timeout=90, hostkey_verify=False, device_params={'name': host['name']}) as m: # execute netconf operation try: # get XML information from rpc expression filter response = m.get(host['payloads']['expression'].format( interface_name='GigabitEthernet1')) xml_dom = xml.dom.minidom.parseString(str(response)) print(xml_dom.toprettyxml(indent=' ')) except RPCError as e: print(e._raw)
#!/usr/bin/env python from ncclient import manager from ncclient.operations import RPCError import xmltodict import yaml from pprint import pprint if __name__ == '__main__': # get parameters from yaml file with open('hosts.yaml') as f: hosts = yaml.load(f, Loader=yaml.FullLoader) # iterate onto the list for host in hosts: # connect to netconf agent with manager.connect(host=host['host'], port=host['port'], username=host['username'], password=host['password'], timeout=90, hostkey_verify=False, device_params={'name': host['name']}) as m: # execute netconf operation try: # get Python dict information from rpc expression filter response = m.get(host['payloads']['expression'].format( interface_name='GigabitEthernet1')) python_dict = xmltodict.parse(response.xml)[ 'rpc-reply']['data'] pprint(python_dict) print( f"Interface: {python_dict['interfaces']['interface']['name']['#text']}") print( f"In-octets: {python_dict['interfaces']['interface']['state']['counters']['in-octets']}") print( f"Out-octets: {python_dict['interfaces']['interface']['state']['counters']['out-octets']}") except RPCError as e: print(e._raw)
NETCONF / YANG n’est pas utile que pour faire de la consultation, mais aussi pour faire de la configuration. La description de l’interface GigabitEthernet1 est modifiée programmatiquement.
#!/usr/bin/env python from ncclient import manager from ncclient.operations import RPCError import yaml if __name__ == '__main__': # get parameters from yaml file with open('hosts.yaml') as f: hosts = yaml.load(f, Loader=yaml.FullLoader) # iterate onto the list for host in hosts: # connect to netconf agent with manager.connect(host=host['host'], port=host['port'], username=host['username'], password=host['password'], timeout=90, hostkey_verify=False, device_params={'name': host['name']}) as m: # execute netconf operation try: # configure device with rpc configuration expression response = m.edit_config(host['payloads']['configuration'].format( interface_name='GigabitEthernet1', interface_description='EVE-NG Management interface'), target='running') print(response) except RPCError as e: print(e._raw)