简介:面向网络运维与自动化开发人员,提供一套基于netconf协议配合Python操作H3C交换机静态路由的轻量资源,适合正在学习网络自动化、希望用脚本替代手工配置的初中级运维工程师。包内共14个文件,核心为8个Python脚本,覆盖建立SSH连接、打开netconf会话、构造静态路由XML配置、发送edit_config/get_config请求并验证结果等关键流程;另有1份DOCX格式的Comware V7 StaticRoute NETCONF XML API配置参考文档、README说明与LICENSE文件,压缩包仅64KB,目录按脚本、库、文档、示例分层清晰。已有676人学习下载。对照API文档可快速理解脚本并改造成自己的自动化工具,用于批量增删路由、减少误操作;也可直接替换脚本中的登录参数与目标IP,在测试或生产环境复用,快速实现路由条目的批量变更,是日常网络配置自动化中一份轻量实用的参考。
1. 手工敲 ip route-static 的时代过去了:用 Python 走 NETCONF 增删 H3C 路由
一台 H3C 交换机要加 30 条静态路由,如果还靠人 SSH 上去逐条敲ip route-static,半小时起步不说,敲错一条掩码、漏配一条 next-hop 都是后半夜才会发现的隐患。netconf 协议配合 Python 脚本做这件事,本质是把「人跟命令行对话」换成「程序跟 YANG 模型对话」:一次会话建立后,增、删、查路由条目都变成结构化报文,能批量、能审计、能回滚。这份资源的核心是那套 Python 脚本,解决的是「H3C 交换机上批量增加或删除路由条目」这个小而具体的自动化场景。适合正在做网络自动化改造、又不想被各家私有不兼容模型拖住的一线运维和网络工程师。
2. 先把原理说透:NETCONF 的 YANG 模型与 H3C 的私有适配
2.1 为什么选 NETCONF 而不是 SSH 大批量执行
很多人第一反应是用 paramiko 批量登录执行命令,这在一次性割接里确实能跑通,但维护成本很快暴露:命令回显没有一个稳定的结构,不同版本设备命令写法会有细微差别,想做「变更前检查」和「失败回滚」几乎全靠人肉拼字符串。NETCONF 走的是另一条路。它基于 SSH 通道承载 XML 报文,客户端和服务器之间通过 capability 协商确定双方支持哪些 YANG 模型;对路由这类配置,客户端直接下发一个结构化节点,交换机内部去解析落库。你不再关心命令行的空格、行尾和交互式提醒,只关心 XML 里节点和字段对不对。
我一般在选型时会先确认设备的 NETCONF 实现是否完整。H3C 的 Comware 平台支持 NETCONF 已经不是新功能,标准操作如 get、get-config、edit-config、lock、unlock 都有,静态路由也暴露在私有 YANG 模型里。相比厂商私有 API,NETCONF 最大的好处是传输层统一走标准协议,换设备时只需要适配 YANG 模型和命名空间,SSH 会话、报文封装、错误返回这套骨架不用动。
这里有个容易误会的点:NETCONF 不等于「用 Python 发命令」。它是一套完整的配置管理协议,报文是 XML,操作是标准化的 RPC。你下发的是「配置意图」,设备返回的是「结构化回执」。所以脚本里你不太需要处理「% Invalid input」这种终端回显,而是处理<ok/>或者<rpc-error>,可编程性完全不在一个档次。
2.2 H3C 的 YANG 模型长什么样:命名空间、capability 和编辑操作
H3C 的 NETCONF 模型以私有命名空间为主,常见的形式是http://www.h3c.com/netconf/data:1.0。你通过m.server_capabilities可以看到设备通告的 capability 列表,里面有路由、接口、VLAN 等模块。静态路由这部分结构大致是:
<top xmlns="http://www.h3c.com/netconf/data:1.0"> <route> <static-routes> <static-route> <tosprefix>10.20.30.0/24</tosprefix> <nextHop>192.168.10.1</nextHop> <outInterface>GigabitEthernet1/0/1</outInterface> </static-route> </static-routes> </route> </top>注意几个关键点:tosprefix是整个表项的 key,格式是「目的网段/前缀长度」;nextHop是下一跳地址;outInterface是出接口。对应命令行ip route-static 10.20.30.0 24 192.168.10.1里那段配置。不同版本字段名可能有小差异,有的版本还支持preference和description,但 key 始终是tosprefix。
操作层面,edit-config 支持的标准操作有create、merge、delete、replace。增加路由用merge或create,删除路由用delete。它们的区别直接影响报错行为,我放在第 4 章展开。这里先记住一个结论:H3C 的模型不是标准 IETF 路由模型,别拿 ietf-routing 那套节点名直接套,否则设备会告诉你找不到节点。我第一次测试时就干过这种事,拿着 RFC 的路径往上怼,设备翻脸不认人。
2.3 python.zip 里能复用到的三块内容
这个资源包聚焦的就是「连接—构造报文—下发—校验」这条链路,按我拆包的常见套路,里面能直接复用的东西分为三块。第一块是连接封装:把 ncclient 的 connect 参数、device_params、hostkey_verify 这些容易踩坑的细节收敛成一个函数,脚本使用者不用每次重写握手逻辑。第二块是路由报文的构造与解析:增加路由、删除路由、查询路由的 XML 模板都在里面,改动 host、prefix、next_hop 就能跑。第三块是一层极薄的批量逻辑:从 CSV 或简单列表读入路由,循环调用 edit_config,并把每次请求的报文和返回值写入日志。
如果你之前自己写过 SSH 批量执行脚本,会发现这三块正好补上 SSH 方案最薄弱的部分:结构化的查询结果、可回退的变更基线、可审计的报文记录。拿到包以后我最建议先跑查询函数,它能立刻告诉你这台设备的 YANG 模型和包内置模板是否匹配,顺便验证连接参数。别一上来就 edit_config,那是拿生产环境试错。
3. 环境准备与连接调试:先让交换机开口说话
3.1 设备侧开启 NETCONF 服务
代码写得再漂亮,设备不开 NETCONF 也是白搭。H3C 的开启命令在不同版本上有点差异,我习惯先登录设备,敲netconf ?看当前软件版本支持哪些关键字。比较常见的组合是:
system-view ssh server enable netconf ssh server enable如果版本较老,也可能是netconf agent或netconf server enable,拿?探一下最保险。注意 NETCONF 默认监听 830 端口,部分版本会复用 SSH 的 22 端口,取决于netconf ssh server enable的实现方式。建议开启后先用display netconf status看一眼服务状态,再用display tcp确认端口监听是否正常。
这一步常见的翻车点是只开了 SSH 没开 NETCONF 服务,客户端连接时报「连接被拒绝」或「对方关闭连接」。所以设备侧的命令不要只看一段教程,最好在自己型号的模拟环境或测试机上先验证一遍。另外,NETCONF 账号建议单独建一个低权限账号,别把管理员明文口令直接写进脚本,这个问题第 6 章会再提。
3.2 Python 侧安装 ncclient 并完成首次连接
Python 侧核心依赖是ncclient。个人建议在虚拟环境里装,避免污染系统环境:
python -m venv venv source venv/bin/activate pip install ncclient安装好以后,第一段连接代码可以写得很短:
from ncclient import manager def connect(host): m = manager.connect( host=host, port=830, username="admin", password="admin@123", hostkey_verify=False, device_params={"name": "h3c"}, timeout=30 ) print("连接成功,capability 数量:", len(m.server_capabilities)) return m if __name__ == "__main__": m = connect("10.0.0.1") m.close_session()这里每个参数都有讲究。port=830是 NETCONF 默认端口,如果你的设备 NETCONF 走 22,改成 22;hostkey_verify=False表示跳过 SSH 主机密钥校验,测试环境方便,生产环境建议改为 True 并放入已知主机指纹,否则容易被中间人盯上;device_params={"name": "h3c"}是 ncclient 针对 H3C 设备做的参数适配,不同设备类型会影响到 capability 协商和部分报文的处理方式,这里不能省;timeout=30是 SSH 握手超时,网络质量差的时候调大一点能救命。
m.server_capabilities是设备通告给客户端的能力列表,里面会明确写出它支持哪些 YANG 模块、哪些操作。第一次连接成功后,务必把它打印出来看一眼,后面构造 XML 时所有命名空间都以它为准。我见过有人拿着网上旧模板的命名空间硬套新设备,结果 capability 对不上,排查一晚上,其实打印一下列表就能定位。
3.3 第一段自带仪式感的脚本:拉取当前静态路由
连接通了,下一步是读路由。这一步既是探路,也是后面增删操作的基础。用 get-config 加 subtree filter 只取静态路由这一段:
from ncclient import manager from ncclient.operations import RPCError FILTER_STATIC_ROUTE = """ <top xmlns="http://www.h3c.com/netconf/data:1.0"> <route> <static-routes/> </route> </top> """ with manager.connect( host="10.0.0.1", port=830, username="admin", password="admin@123", hostkey_verify=False, device_params={"name": "h3c"} ) as m: try: resp = m.get_config(source="running", filter=("subtree", FILTER_STATIC_ROUTE)) print(resp.xml) except RPCError as e: print("RPC 错误:", e)get_config(source="running")读的是 running 配置库,也就是设备当前生效的配置。filter=("subtree", ...)表示用 subtree 过滤器,只返回 top/route/static-routes 这一段,避免把整台设备的配置全拉回来,报文小、解析快。返回值 XML 里如果只有一个空的<static-routes/>,说明设备上还没配过静态路由;如果有内容,正好用真实字段核对包内置模板。
这一段的输出会成为后面增删路由的「现状基线」。我再强调一遍:先拉一次,别急着改。我见过有人连上设备直接 edit_config,结果命名空间不对,设备返回的错误又不够明确,排查了半天才发现是模板字段名拼错了。
3.4 连接阶段最容易翻车的四个点
第一个是端口搞错。默认 830,但设备如果配置了复用 SSH 的 22 端口,你连 830 就会一直卡在超时。处理办法是设备侧display netconf status看实际监听端口,或者两个端口都试一遍。第二个是 hostkey 校验导致连接直接中断。ncclient 默认会校验主机密钥,第一次连接会出现 unknown host key 的报错,测试环境直接hostkey_verify=False,生产环境建议先抓一次指纹。第三个是用户名密码包含特殊字符,比如@被命令行或配置文件吃掉,建议先在一个简单脚本里写死测试通过,再改成从环境变量读取。第四个是 Python 版本太低,ncclient 在 3.8 以下会有兼容性报错,建议至少 Python 3.9。
这四个坑我自己都踩过,尤其是端口那个,纯属玄学:明明配置文档写了 830,设备就是没监听。后来养成习惯,连接之前先在脚本里用 socket 探测端口,通了再跑 ncclient,省下不少排查时间。
4. 增删路由的完整实现:从构造 YANG 报文到循环批量下发
4.1 静态路由在 YANG 树里的映射关系
在写增删代码前,先把 YANG 结构再确认一遍。H3C 的静态路由表以static-route为列表项,关键字段如下:
| YANG 字段 | 对应含义 | 示例 | 是否 key |
|---|---|---|---|
| tosprefix | 目的网段/前缀 | 10.20.30.0/24 | 是 |
| nextHop | 下一跳地址 | 192.168.10.1 | 否 |
| outInterface | 出接口 | GigabitEthernet1/0/1 | 否 |
| preference | 路由优先级 | 60 | 否 |
| description | 描述 | to-branch-b | 否 |
key 字段的意义在于唯一标识一条表项。H3C 静态路由的删除操作依赖 key 定位,所以tosprefix必须给对。写入时nextHop和outInterface至少要给一个,实际配置里通常是两个都写,特别是点到点接口场景。字段值的格式要严格按照设备的习惯:接口名大小写、网段写法(斜杠掩码)都不能随意,第 5 章有个坑就是接口名大小写导致的。
有个很容易混淆的点:命令行里写ip route-static 10.20.30.0 24 192.168.10.1,掩码是24,但在 YANG 模型里对应的是/24。也就是说,tosprefix的格式直接是10.20.30.0/24,而不是分开的两个字段。如果你沿用命令行习惯,把前缀和掩码拆开传参,报文就会构造出错。
4.2 增加路由:merge 与 create 的取舍
增加路由有两种常见操作:merge和create。直观理解,merge是「有就更新,没有就新建」,create是「只允许新建,已存在就报错」。对 H3C 静态路由来说,我一般建议新增路由用merge,因为重复执行同一变更不会因为「已存在」而中断脚本;如果你希望严格捕获重复配置,再用create。
添加一条静态路由的核心代码如下:
def build_add_route_xml(prefix, next_hop, out_interface): return f""" <config> <top xmlns="http://www.h3c.com/netconf/data:1.0"> <route> <static-routes> <static-route> <tosprefix>{prefix}</tosprefix> <nextHop>{next_hop}</nextHop> <outInterface>{out_interface}</outInterface> </static-route> </static-routes> </route> </top> </config> """ def add_route(m, prefix, next_hop, out_interface="GigabitEthernet1/0/1"): xml = build_add_route_xml(prefix, next_hop, out_interface) return m.edit_config( target="running", config=xml, operation="merge" )edit_config的config参数接收的是完整 XML 字符串,operation="merge"是作用在最外层<config>上的操作。函数返回值里包含设备的 RPC reply,可以从中确认这次操作有没有报错。operation="create"的用法完全一样,只是把 merge 换成 create。注意如果某个字段设备不支持或者拼写不对,ncclient 本身不报错,错误藏在 RPC reply 里,所以要养成打印返回值或解析<rpc-reply>的习惯。
out_interface加了默认参数GigabitEthernet1/0/1,但真实环境里接口名五花八门,建议调用时显式传入。另外,模板字符串里用了 f-string,如果前缀或接口名里出现花括号这类特殊字符,理论上会破坏模板,路由场景下不太可能,但如果你要扩展成通用工具,建议改成 XML 库构造,而不是字符串拼接。
4.3 删除路由:只带 key 的 delete 操作
删除路由和添加路由的代码结构几乎一样,但有个重要区别:删除时 XML 里只需要带上 key 字段,也就是tosprefix,其他字段不必、也不建议填。因为 delete 操作按 key 定位表项,带多了反而可能触发校验错误。
def build_del_route_xml(prefix): return f""" <config> <top xmlns="http://www.h3c.com/netconf/data:1.0"> <route> <static-routes> <static-route> <tosprefix>{prefix}</tosprefix> </static-route> </static-routes> </route> </top> </config> """ def delete_route(m, prefix): xml = build_del_route_xml(prefix) return m.edit_config( target="running", config=xml, operation="delete" )这里有个坑:如果tosprefix对应的路由根本不存在,设备会返回错误。所以稳妥的做法是先查询确认存在再删,或者捕获错误后当成「目标已达成」处理。我在批处理时一般会先做一轮查询,把已有路由的 key 集合取出来,和待删除列表求交集,只对交集执行删除,避免脚本中途被一条不存在的路由打断。
删除成功后的验证也不能省。H3C 的 delete 在某些版本下返回<ok/>但配置没真正消失,属于比较少见的静默失败,靠 get-config 复查是最直接的兜底手段。
4.4 批量下发:用 CSV 驱动循环,每次操作都留日志
真实场景里不会只加一条路由。把路由表放进 CSV,脚本逐行读取、逐条下发,是最容易维护的方式:
prefix,next_hop,out_interface,action 10.20.30.0/24,192.168.10.1,GigabitEthernet1/0/1,add 10.20.40.0/24,192.168.10.2,GigabitEthernet1/0/2,del对应的 Python 批量逻辑:
import csv from ncclient import manager def apply_csv(m, csv_path): with open(csv_path, newline="", encoding="utf-8") as f: for row in csv.DictReader(f): action = row["action"].strip().lower() prefix = row["prefix"].strip() if action == "add": resp = add_route(m, prefix, row["next_hop"].strip(), row.get("out_interface") or "GigabitEthernet1/0/1") elif action == "del": resp = delete_route(m, prefix) else: print("跳过未知动作:", action) continue print(f"{action} {prefix} -> rc={resp.ok}")csv.DictReader会把每一行读成字典,字段名和 CSV 表头对应,代码里通过row["prefix"]取不怕列顺序变化。每次操作后把resp.ok和结果打印出来,算是给批量下发留一条最简单的审计痕迹。这里要注意 CSV 编码,Windows 环境下导出的文件经常是 GBK 编码,读取时指定encoding="gbk",否则中文字段会乱码导致解析失败。
批量这个环节,我推荐一个习惯:先跑一轮「只读模式」,也就是只查询不下发,确认 CSV 里的每条路由都不缺字段、格式对得上,再真正执行。这比什么都重要,能省掉大半翻车现场。包里的脚本如果带着--dry-run类似的参数,优先用起来;没有的话自己加一个也不难,无非是循环里只打印不下发。
5. 避坑记录:在 H3C 上用 NETCONF 改路由踩过的五个坑
5.1 坑一:命名空间抄错,设备直接回 capability not found
现象:代码逻辑看着都对,connect 也成功,但一执行 edit_config,设备返回的错误类似capability not found,或者干脆找不到route节点。
原因:XML 根节点里写的命名空间和设备实际通告的不一致。H3C 的命名空间写法不止一种,有的软件版本通告http://www.h3c.com/netconf/data:1.0,有的会多出其他私有模块;照抄网上老文章很容易对不上。
解决:连接后先打印m.server_capabilities,从设备实际通告的 URI 里找到 route 模块对应的命名空间,再回填到模板。我后来在连接函数里加了一段自动提取命名空间的逻辑,从 capability 里正则匹配route相关模块,动态生成根节点,再也不用手抄。这一步是避免后续所有报文错误的前提。
5.2 坑二:merge 重复下发报「路由已存在」,create 又报「重复创建」
现象:同一台设备,同一批脚本,之前跑merge能顺利加上路由,某天重新执行却在其中一条上报错,提示路由已存在;换create又遇到重复创建的错误。
原因:H3C 不同版本对merge的语义处理不完全一样。标准 NETCONF 里 merge 是「有则更新,无则新建」,但一些 Comware 版本对静态路由表项的处理偏向「严格校验」,重复下发同 key 且同 next-hop 的路由会直接报错,而不是无声覆盖。
解决:下发之前先 get-config 拉一次已有路由,把 key 集合存下来;对已存在的目标,先判断是否需要删除重建,还是直接跳过。我一般会把「查询—比对—变更」封成一个函数,CSV 里每条路由的状态都能打印出来:新增、跳过、还是更新。这样一来,重复执行脚本只会输出「跳过」,不会炸在中间。
5.3 坑三:删除时把 nextHop 一起带上,路由删不掉
现象:delete 操作返回成功,但用 get-config 一查,路由还在;或者设备直接返回data missing。
原因:delete 操作按 key 定位表项,我最初图省事,直接复用新增时的报文模板,把 nextHop 和 outInterface 也一起带上了。部分版本对 delete 报文的校验是「严格匹配整个表项」,多带字段会让匹配找不到目标,或返回错误;有些版本则忽略多余字段,但删除静默失败。
解决:删除报文只保留 key 字段tosprefix,其余一概不填。我后来给删除函数单独写了一个模板,和新增模板分开维护,避免手滑复用。另外删除后立刻 get-config 验证一遍,确认真的没了再继续下一步。
5.4 坑四:830 端口测不通,其实 NETCONF 开在 22 上
现象:脚本连 830 一直超时,telnet 测试端口也不通,但设备上明明执行过 netconf ssh server enable。
原因:部分 H3C 版本把 NETCONF 服务绑定在 SSH 端口 22 上,830 只是默认值而不是必然值。设备侧display netconf status能看到实际监听端口,但很多人只看了教程里的「默认 830」。
解决:在连接函数里加一个端口探测步骤,先用 socket 依次探测 830、22,哪个端口能建立 TCP 连接就用哪个。这段探测代码很简单,但能省掉大量玄学排查时间。我当时还遇到过一种情况:830 被防火墙策略拦了,22 反而通,换成 22 之后问题瞬间消失。
5.5 坑五:接口名大小写不对,路由静默失败
现象:add 返回成功,但 get-config 里找不到这条路由,设备上display ip routing-table也没有。
原因:outInterface填了gigabitethernet1/0/1或GE1/0/1,而设备要求的是GigabitEthernet1/0/1这种规范拼写。报错在部分版本里不明显,甚至 RPC 返回 ok,但配置并未真正落表。
解决:所有接口名统一从查询结果里复制,不要手动敲。第 3 章查询脚本返回的 XML 里会带设备自己的接口命名,拿那个做标准。我在调用方加了一个接口名校验正则,只允许字母数字连字符的规范格式,写错就报错退出,不让问题路由进入设备。
6. 把脚本收口成工具:先快照、再下发、后校验的三段式习惯
6.1 参数化 CLI:一条命令增或删
把上面封装成命令行入口后,日常操作变成这样:
python route_tool.py add --prefix 10.20.30.0/24 --next-hop 192.168.10.1 python route_tool.py del --prefix 10.20.30.0/24参数解析用 argparse,连接参数从环境变量读,避免口令进 shell 历史。
6.2 变更前抓快照,失败原地回滚
我的习惯是每次批量变更前,先 get-config 把静态路由整段存成 XML 快照文件。执行过程中任何一条失败,立刻用快照里提取的旧路由集合做一次反向下发,把变更回滚掉。虽然 NETCONF 没有事务回滚,但有了快照,后悔药也算有了。
6.3 下发后读一次路由表,把差异打出来
下发完成后不要急着收工,再 get-config 一次,拿变更前后两个 XML 做 diff,输出「新增了哪几条、删除了哪几条」。这个三段式流程固定下来之后,我从没有在路由变更上出过把核心路由删没的事故。从那以后我每次动路由前都强制走一遍先快照、再下发、后校验,希望帮到你。
本文还有配套的精品资源,点击获取