Cisco IOS-XE et NETCONF / YANG

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 :

  1. Le client se connecte au sous-système NETCONF SSH.
  2. Le serveur répond avec un « hello » qui inclut les capabilities prises en charge.
  3. Le client répond avec des capabilities prises en charge pour établir une connexion.
  4. Le client émet une requête NETCONF.
  5. 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)
IOS-XE NETCONF Get Capabilities
ncclient get capabilities

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.

OpenConfig Interfaces
http://openconfig.net/yang/interfaces
#!/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)
IOS-XE NETCONF Get XML
ncclient get format XML avec minidom
#!/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)
IOS-XE NETCONF Get Python Dict
ncclient get format Python dict avec xmltodict

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)
IOS-XE NETCONF edit_config Reply
Réponse validant le bon aboutissement de la commande edit_config