第311篇:NETCONF/RESTCONF 协议
关键词
NETCONF、RESTCONF、YANG 驱动、网络配置协议、RPC、Edit-config、Get-config、Python ncclient、Curl
一、NETCONF/RESTCONF 概述
1.1 从 CLI 到 API 的进化
网络管理协议进化:
┌──────────────────────────────────────────┐
│ CLI(手工时代) │
│ ┌─ SSH 登录 + 逐条输入命令 │
│ ├─ 非结构化文本输出 │
│ ├─ 无事务支持 │
│ └─ 无标准化错误处理 │
│ │
│ SNMP(监控时代) │
│ ├─ MIB 定义,get/set 操作 │
│ ├─ 只读为主,配置能力弱 │
│ ├─ 安全性差(v2c 明文) │
│ └─ 复杂配置难以表达 │
│ │
│ NETCONF(自动化时代) │
│ ├─ 基于 XML 的结构化数据 │
│ ├─ YANG 数据模型驱动 │
│ ├─ 事务支持(commit/rollback) │
│ ├─ 候选配置、运行配置分离 │
│ └─ SSH 加密传输 │
│ │
│ RESTCONF(云原生时代) │
│ ├─ 基于 HTTP REST 风格 │
│ ├─ JSON/XML 编码 │
│ ├─ 与 Web 技术无缝集成 │
│ └─ 适合云原生/微服务架构 │
└──────────────────────────────────────────┘
1.2 协议对比
| 特性 | NETCONF | RESTCONF |
|---|---|---|
| 传输协议 端口 编码 操作风格 URL 结构 事务 候选配置 通知 重量级 学习成本 Python 库 | SSH 830 XML RPC RPC 方法 ✅ ✅ ✅ 重 较高 ncclient | HTTP/HTTPS 443(默认) XML / JSON REST(CRUD) 路径=YANG 节点 ❌ ❌ ❌ 轻 较低 requests |
选择建议: ┌─ 需要事务/回滚 → NETCONF ├─ 简单 API 调用 → RESTCONF ├─ Python 编程 → 两者均可 └─ 集成 Web 应用 → RESTCONF
二、NETCONF 协议详解
2.1 NETCONF 协议栈
NETCONF 协议层次:
┌──────────────────────────────────────────┐
│ 内容层(Content) │
│ ┌─ YANG 数据模型定义的配置/状态数据 │
│ └─ XML 编码 │
│ │
│ 操作层(Operations) │
│ ┌─ <get>:获取配置和状态数据 │
│ ├─ <get-config>:获取配置数据 │
│ ├─ <edit-config>:修改配置 │
│ ├─ <copy-config>:复制配置 │
│ ├─ <delete-config>:删除配置 │
│ ├─ <lock>/<unlock>:配置锁定 │
│ ├─ <commit>:提交候选配置 │
│ ├─ <discard-changes>:丢弃未提交变更 │
│ └─ <rpc>:自定义 RPC 操作 │
│ │
│ 消息层(Messages) │
│ ┌─ <hello>:能力交换 │
│ ├─ <rpc>/<rpc-reply>:请求/响应 │
│ └─ <notification>:事件通知 │
│ │
│ 安全传输层(Transport) │
│ └─ SSH(RFC 6242)、TLS(RFC 7589) │
└──────────────────────────────────────────┘
2.2 NETCONF 能力(Capabilities)
<!-- NETCONF 能力交换(Hello 消息) -->
<!-- 客户端发送 -->
<?xml version="1.0" encoding="UTF-8"?>
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<capabilities>
<capability>
urn:ietf:params:netconf:base:1.1
</capability>
</capabilities>
</hello>
<!-- 服务器响应 -->
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<capabilities>
<capability>
urn:ietf:params:netconf:base:1.1
</capability>
<capability>
urn:ietf:params:netconf:capability:validate:1.1
</capability>
<capability>
urn:ietf:params:netconf:capability:candidate:1.1
</capability>
<capability>
urn:ietf:params:netconf:capability:confirmed-commit:1.0
</capability>
<capability>
urn:ietf:params:netconf:capability:rollback-on-error:1.0
</capability>
<!-- 支持的 YANG 模块 -->
<capability>
urn:ietf:params:xml:ns:yang:ietf-interfaces?module=ietf-interfaces
</capability>
</capabilities>
</hello>
2.3 NETCONF 常用操作
<!-- ===== 获取运行配置 ===== -->
<rpc message-id="101"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<get-config>
<source>
<running/>
</source>
<filter type="subtree">
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>
</filter>
</get-config>
</rpc>
<!-- ===== 修改配置 ===== -->
<rpc message-id="102"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<edit-config>
<target>
<candidate/>
</target>
<config>
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>GigabitEthernet0/0/1</name>
<description>UPLINK-TO-CORE</description>
<enabled>true</enabled>
<ipv4 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip">
<address>
<ip>10.0.1.1</ip>
<prefix-length>24</prefix-length>
</address>
</ipv4>
</interface>
</interfaces>
</config>
</edit-config>
</rpc>
<!-- ===== 提交配置 ===== -->
<rpc message-id="103"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<commit/>
</rpc>
<!-- ===== 丢弃变更 ===== -->
<rpc message-id="104"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<discard-changes/>
</rpc>
<!-- ===== 自定义 RPC(设备重启) ===== -->
<rpc message-id="105"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<reboot xmlns="http://example.com/ns/reboot">
<reason>Maintenance</reason>
</reboot>
</rpc>
三、Python ncclient 实战
3.1 连接与基础操作
#!/usr/bin/env python3
# ncclient_basic.py — NETCONF 基础操作
# 安装:python -m pip install ncclient
from ncclient import manager
import xml.dom.minidom
# NETCONF 连接参数
NETCONF_PARAMS = {
"host": "192.168.1.1",
"port": 830, # NETCONF 默认端口
"username": "admin",
"password": "admin123",
"hostkey_verify": False, # 跳过主机密钥检查
"device_params": {"name": "huawei"},
"timeout": 30,
}
def pretty_xml(xml_str):
"""美化 XML 输出"""
dom = xml.dom.minidom.parseString(xml_str)
return dom.toprettyxml(indent=" ")
try:
with manager.connect(**NETCONF_PARAMS) as m:
print("✓ NETCONF 连接成功")
# 1. 获取服务器能力
print(f"\n 服务器能力数: {len(m.server_capabilities)}")
for cap in list(m.server_capabilities)[:5]:
print(f" {cap}")
# 2. 获取运行配置(接口部分)
filter_xml = """
<filter type="subtree">
<interfaces
xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>
</filter>
"""
result = m.get_config("running", filter_xml)
print("\n=== 运行配置(接口)===")
print(pretty_xml(result.xml))
# 3. 获取所有配置和状态
result = m.get()
print("\n=== 全部数据(摘要)===")
# 只打印前 500 字符
print(result.xml[:500])
except Exception as e:
print(f"✗ 错误: {e}")
3.2 配置管理
#!/usr/bin/env python3
# ncclient_config.py — NETCONF 配置管理
from ncclient import manager
from ncclient.operations import RPCError
import xml.dom.minidom
NETCONF_PARAMS = {
"host": "192.168.1.1",
"port": 830,
"username": "admin",
"password": "admin123",
"hostkey_verify": False,
"device_params": {"name": "huawei"},
}
def create_vlan_xml(vlan_id, vlan_name):
"""生成 VLAN 配置 XML"""
return f"""
<config>
<vlan xmlns="http://www.huawei.com/netconf/vrp/vlan">
<vlans>
<vlan>
<id>{vlan_id}</id>
<name>{vlan_name}</name>
<type>common</type>
<status>enable</status>
</vlan>
</vlans>
</vlan>
</config>
"""
def configure_interface_xml(if_name, desc, mode):
"""生成接口配置 XML"""
return f"""
<config>
<interfaces
xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>{if_name}</name>
<description>{desc}</description>
<type
xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">
ianaift:ethernetCsmacd
</type>
<enabled>true</enabled>
</interface>
</interfaces>
</config>
"""
try:
with manager.connect(**NETCONF_PARAMS) as m:
# ===== 候选配置模式 =====
# 1. 锁定配置
m.lock(target="candidate")
print("🔒 配置已锁定")
# 2. 创建 VLAN
vlan_xml = create_vlan_xml(100, "VLAN100_SALES")
print(f" 加载 VLAN 配置...")
m.edit_config(
target="candidate",
config=vlan_xml,
default_operation="merge",
)
# 3. 配置接口
intf_xml = configure_interface_xml(
"GigabitEthernet0/0/1",
"Link to CORE",
"trunk",
)
print(f" 加载接口配置...")
m.edit_config(
target="candidate",
config=intf_xml,
default_operation="merge",
)
# 4. 验证候选配置
result = m.get_config("candidate")
print("\n=== 候选配置(摘要)===")
print(pretty_xml(result.xml[:1000]))
# 5. 提交配置
m.commit()
print("\n✓ 配置已提交")
# 6. 解锁
m.unlock(target="candidate")
print("🔓 配置已解锁")
except RPCError as e:
print(f"✗ NETCONF RPC 错误: {e}")
# 回滚
with manager.connect(**NETCONF_PARAMS) as m:
m.discard_changes()
print("已丢弃候选配置变更")
except Exception as e:
print(f"✗ 错误: {e}")
3.3 批量操作
#!/usr/bin/env python3
# ncclient_batch.py — NETCONF 批量操作
from ncclient import manager
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
DEVICES = [
{"host": "192.168.1.1", "name": "SW01"},
{"host": "192.168.1.2", "name": "SW02"},
{"host": "192.168.1.3", "name": "SW03"},
]
COMMON_CREDENTIALS = {
"port": 830,
"username": "admin",
"password": "admin123",
"hostkey_verify": False,
"device_params": {"name": "huawei"},
}
def collect_device_info(device):
"""单设备采集(使用 NETCONF)"""
try:
with manager.connect(
host=device["host"],
**COMMON_CREDENTIALS,
) as m:
# 获取系统信息
filter_xml = """
<filter type="subtree">
<system
xmlns="http://www.huawei.com/netconf/vrp/system"/>
</filter>
"""
result = m.get_config("running", filter_xml)
# 获取接口数量
if_filter = """
<filter type="subtree">
<interfaces
xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"/>
</filter>
"""
if_result = m.get_config("running", if_filter)
return {
"host": device["host"],
"name": device["name"],
"success": True,
"config_size": len(result.xml),
"interface_count": if_result.xml.count("<interface>"),
}
except Exception as e:
return {
"host": device["host"],
"name": device["name"],
"success": False,
"error": str(e),
}
# 并行采集
print("=== NETCONF 批量采集 ===")
start = time.time()
with ThreadPoolExecutor(max_workers=3) as ex:
futures = {ex.submit(collect_device_info, dev): dev for dev in DEVICES}
for future in as_completed(futures):
result = future.result()
status = "✓" if result["success"] else "✗"
name = result["name"]
if result["success"]:
print(f"[{status}] {name}: {result['interface_count']} 接口, "
f"配置 {result['config_size']} 字节")
else:
print(f"[{status}] {name}: {result['error']}")
elapsed = time.time() - start
print(f"\n总耗时: {elapsed:.1f} 秒")
四、RESTCONF 实战
4.1 RESTCONF 基础
RESTCONF 的 RESTful 操作映射:
HTTP 方法 CRUD RESTCONF 操作
─────────────────────────────────────
GET Read 获取配置或状态数据
POST Create 创建数据资源
PUT Update/Repl 替换整个资源
PATCH Partial Upd 部分更新资源
DELETE Delete 删除资源
URL 结构映射 YANG 路径:
GET /restconf/data/ietf-interfaces:interfaces
GET /restconf/data/ietf-interfaces:interfaces/interface=GE0/0/1
返回格式:
Accept: application/yang-data+json
Accept: application/yang-data+xml
4.2 Python requests 操作 RESTCONF
#!/usr/bin/env python3
# restconf_basic.py — RESTCONF 操作
import requests
import json
# RESTCONF 配置
RESTCONF_BASE = "https://192.168.1.1/restconf"
USERNAME = "admin"
PASSWORD = "admin123"
# 禁用 SSL 警告(生产环境使用证书)
requests.packages.urllib3.disable_warnings()
HEADERS_JSON = {
"Accept": "application/yang-data+json",
"Content-Type": "application/yang-data+json",
}
def restconf_get(path):
"""RESTCONF GET 请求"""
url = f"{RESTCONF_BASE}{path}"
resp = requests.get(
url,
auth=(USERNAME, PASSWORD),
headers=HEADERS_JSON,
verify=False,
)
return resp
def restconf_put(path, data):
"""RESTCONF PUT 请求(全量替换)"""
url = f"{RESTCONF_BASE}{path}"
resp = requests.put(
url,
auth=(USERNAME, PASSWORD),
headers=HEADERS_JSON,
json=data,
verify=False,
)
return resp
def restconf_patch(path, data):
"""RESTCONF PATCH 请求(部分更新)"""
url = f"{RESTCONF_BASE}{path}"
resp = requests.patch(
url,
auth=(USERNAME, PASSWORD),
headers=HEADERS_JSON,
json=data,
verify=False,
)
return resp
def restconf_post(path, data):
"""RESTCONF POST 请求(创建)"""
url = f"{RESTCONF_BASE}{path}"
resp = requests.post(
url,
auth=(USERNAME, PASSWORD),
headers=HEADERS_JSON,
json=data,
verify=False,
)
return resp
def restconf_delete(path):
"""RESTCONF DELETE 请求"""
url = f"{RESTCONF_BASE}{path}"
resp = requests.delete(
url,
auth=(USERNAME, PASSWORD),
headers=HEADERS_JSON,
verify=False,
)
return resp
# ===== 操作示例 =====
try:
# 1. GET: 获取所有接口
print("=== 获取接口列表 ===")
resp = restconf_get("/data/ietf-interfaces:interfaces")
if resp.status_code == 200:
data = resp.json()
for intf in data.get("ietf-interfaces:interfaces", {}).get("interface", []):
print(f" {intf['name']}: enabled={intf.get('enabled')}")
else:
print(f" GET 失败: {resp.status_code}")
# 2. GET: 获取单个接口
print("\n=== 获取单个接口 ===")
resp = restconf_get(
"/data/ietf-interfaces:interfaces/"
"interface=GigabitEthernet0/0/1"
)
if resp.status_code == 200:
print(json.dumps(resp.json(), indent=2))
# 3. PUT: 创建/更新接口
print("\n=== 创建接口 LoopBack100 ===")
new_intf = {
"ietf-interfaces:interface": {
"name": "LoopBack100",
"description": "RESTCONF Created Loopback",
"type": "iana-if-type:softwareLoopback",
"enabled": True,
"ietf-ip:ipv4": {
"address": [
{
"ip": "100.100.100.1",
"prefix-length": 32,
}
]
},
}
}
resp = restconf_put(
"/data/ietf-interfaces:interfaces/"
"interface=LoopBack100",
new_intf,
)
print(f" PUT 结果: {resp.status_code}")
# 4. PATCH: 更新接口描述
print("\n=== 更新接口描述 ===")
update = {
"ietf-interfaces:interface": {
"name": "GigabitEthernet0/0/1",
"description": "Updated via RESTCONF PATCH",
}
}
resp = restconf_patch(
"/data/ietf-interfaces:interfaces/"
"interface=GigabitEthernet0/0/1",
update,
)
print(f" PATCH 结果: {resp.status_code}")
# 5. GET: 验证变更
print("\n=== 验证变更 ===")
resp = restconf_get(
"/data/ietf-interfaces:interfaces/"
"interface=GigabitEthernet0/0/1"
)
if resp.status_code == 200:
data = resp.json()
iface = data.get("ietf-interfaces:interface", {})
print(f" 描述: {iface.get('description')}")
print(f" 启用: {iface.get('enabled')}")
except requests.exceptions.ConnectionError:
print("✗ 连接失败,请检查 RESTCONF 是否启用")
except Exception as e:
print(f"✗ 错误: {e}")
4.3 使用 curl 操作 RESTCONF
# curl 操作 RESTCONF 示例
# GET: 获取接口列表
curl -k -u admin:admin123 \
-H "Accept: application/yang-data+json" \
https://192.168.1.1/restconf/data/ietf-interfaces:interfaces
# GET: 获取特定接口
curl -k -u admin:admin123 \
-H "Accept: application/yang-data+json" \
"https://192.168.1.1/restconf/data/ietf-interfaces:interfaces/interface=GE0/0/1"
# PUT: 创建接口
curl -k -u admin:admin123 \
-H "Accept: application/yang-data+json" \
-H "Content-Type: application/yang-data+json" \
-X PUT \
-d '{
"ietf-interfaces:interface": {
"name": "LoopBack200",
"description": "Created via curl",
"type": "iana-if-type:softwareLoopback",
"enabled": true
}
}' \
"https://192.168.1.1/restconf/data/ietf-interfaces:interfaces/interface=LoopBack200"
# DELETE: 删除接口
curl -k -u admin:admin123 \
-X DELETE \
"https://192.168.1.1/restconf/data/ietf-interfaces:interfaces/interface=LoopBack200"
# GET: 获取 YANG 模块列表
curl -k -u admin:admin123 \
-H "Accept: application/yang-data+json" \
https://192.168.1.1/restconf/data/ietf-yang-library:modules-state
五、华为设备 NETCONF 配置
5.1 设备端配置
# 华为设备开启 NETCONF(SSH)
# 1. 使能 NETCONF 服务
system-view
netconf ssh server enable
netconf ssh server port 830
# 2. 创建 NETCONF 用户
aaa
local-user netconf_admin password cipher Admin@123
local-user netconf_admin service-type ssh
local-user netconf_admin privilege level 15
quit
# 3. SSH 用户认证
ssh user netconf_admin
ssh user netconf_admin authentication-type password
ssh user netconf_admin service-type netconf
quit
# 4. 使能 YANG 模型
netconf
yang-module load ietf-interfaces
yang-module load ietf-ip
yang-module load huawei-vlan
commit
5.2 检查 NETCONF 状态
# 查看 NETCONF 会话
display netconf session
# 查看 NETCONF 统计
display netconf statistics
# 查看加载的 YANG 模块
display netconf yang-module
# 调试 NETCONF(troubleshooting)
debugging netconf all
terminal debugging
六、NETCONF vs RESTCONF 选型
6.1 场景对比
选择 NETCONF 的场景:
┌─ 配置变更需要事务(全部成功或全部回滚)
├─ 使用候选配置(先编辑,后提交)
├─ 需要配置锁定(防止冲突)
├─ 大量配置变更(批量 edit-config)
└─ 需要 NETCONF 通知(事件订阅)
选择 RESTCONF 的场景:
┌─ 简单的 CRUD 操作
├─ 与 Web/云应用集成
├─ 需要 JSON 格式(非 XML)
├─ 脚本语言快速调用(Python/JavaScript/curl)
└─ 微服务架构中的配置 API
可以两者都用:
┌─ 事务性配置变更用 NETCONF
├─ 日常监控/查询用 RESTCONF
└─ 先 RESTCONF 快速开发,关键操作用 NETCONF
七、最佳实践
7.1 NETCONF 使用规范
NETCONF 最佳实践:
1. 连接管理
┌─ 使用 with 语句自动释放连接
├─ 设置合理的 timeout(30-60s)
├─ 批量操作复用连接
└─ 捕获 RPCError 做回滚
2. 配置变更
┌─ 优先用候选配置(candidate)
├─ 变更前 lock 防止冲突
├─ 变更后 validate 校验
├─ commit 确认提交
└─ 失败时 discard-changes
3. XML 处理
┌─ 使用 lxml 或 xml.dom 处理 XML
├─ 模板方式生成 XML(Jinja2)
├─ filter 减少传输数据量
└─ 使用 pretty_xml 调试
4. 错误处理
┌─ 检查 rpc-reply 的 ok/error 元素
├─ 解析 error-tag/error-message
├─ 配置事务回滚
└─ 日志记录完整 XML 交互
7.2 安全考量
NETCONF/RESTCONF 安全:
NETCONF:
┌─ SSH 传输层加密(默认)
├─ 设备本地用户认证
├─ 配置锁定防冲突
└─ 操作审计日志
RESTCONF:
┌─ HTTPS 加密(必须启用)
├─ 基本/摘要/Digest 认证
├─ 使用证书认证(推荐)
├─ RBAC 权限控制
└─ API 限流保护
生产环境:
┌─ 管理网络隔离
├─ 使用跳板机
├─ 日志监控异常操作
└─ 定期轮换密码/证书
八、总结
NETCONF/RESTCONF 的定位:
NETCONF — 面向设备配置的"专业协议"
┌─ 强一致性:事务 + 候选 + 回滚
├─ 标准化:YANG 模型驱动
├─ 安全性:SSH 加密传输
└─ 适用:核心网变更、批量配置
RESTCONF — 面向应用的"通用 API"
┌─ 轻量级:HTTP REST 风格
├─ 灵活性:JSON/XML 可选
├─ 易集成:与 Web 技术无缝对接
└─ 适用:监控平台、云编排
CLI → SSH/Paramiko(逐条命令,文本解析)
│
├─ → NETCONF(结构化 XML,事务支持)
│
├─ → RESTCONF(REST API,JSON 编码)
│
└─ → gNMI(gRPC,Protobuf,流式)
下篇预告:第312篇 — gNMI/gNOI 协议,将介绍 Google 推出的 gRPC 网络管理接口,如何用 Protobuf 和流式传输实现高效的网络设备管理。