简介:面向Java开发者与网络运维工程师的Netconf自动化配置示例工程,解决通过Netconf协议操作华为、H3C交换机实现静态路由条目增删等场景,适用于网络设备配置自动化及运维效率提升。压缩包共85个文件,以XML与Java为主:XML涵盖Netconf请求报文、设备能力集及YANG模型结构,Java代码封装会话建立、RPC消息构建与响应解析等核心逻辑,另含Properties设备参数配置、Maven构建脚本及依赖JAR,整体仅115KB,轻量且便于直接导入运行。目前已有2257人学习下载,可作为快速上手的参考实现。工程内置测试程序入口,将设备IP、用户名、密码等配置替换为真实环境即可运行,并附有配置说明与工程结构预览,便于理解华为与H3C设备Netconf能力差异及静态路由配置XML样例。 上个月接了个不大不小的需求:把公司三十几台核心交换机的静态路由批量改一遍,新增两个网段,删掉三个旧条目。如果还是老办法,SSH登上去逐台敲命令行,等全部搞完,我估计得加三天班,还得祈祷别敲错。后来我用Java通过NETCONF协议把这件事变成了一个两百多行的工具,跑完只用了十几分钟。今天就把这套思路完整分享出来,包括华为和H3C交换机从设备侧开启NETCONF、Java侧建立会话、静态路由条目的增删改查,以及我在生产环境里踩过的几个坑。
这篇文章适合两类人看:一是被批量网络配置折腾得不轻的Java开发,二是想引入自动化但又不知道从哪里下手的网络工程师。NETCONF这个东西不算新,但在国内企业网里真正用起来的其实不多,很多时候大家宁愿继续用CLI脚本硬扛。我先把最核心的几个问题讲透,再给可直接参考的代码和XML模板,你照着一台测试设备跑通流程,就能迁移到批量环境。
1. 为什么我劝你别再用SSH脚本批量改静态路由
1.1 命令行批量操作的三个痛点
很多团队做网络自动化,第一反应是写个脚本SSH上去执行命令。静态路由这种需求看起来也简单:登录设备,进入系统视图,ip route-static一敲,完事。但真到了几十台、上百台的规模,问题立刻浮出水面。
第一是回显解析噩梦。华为设备执行完命令后回显是Info: Succeeded in setting the route.,H3C设备往往是空回显或者带个%开头的提示,不同版本、不同型号之间还经常有差异。你要判断命令到底执行成功没有,就得写一堆正则去匹配这些五花八门的输出,匹配错了还会误判。
第二是批量执行没有事务概念。SSH脚本跑到第三台设备的时候,如果发现命令敲错了或者设备拒绝执行,前面的两台已经改完了,后面的还没改。这个时候只能手动回滚,而且回滚本身还要再写一套脚本。网络变更最怕的就是这种“执行一半”的状态。
第三是配置没有结构化校验。命令行模式下,你很难在提交之前统一验证“这条路由是否和现有路由冲突”“下一跳是否可达”“掩码长度是否合法”。设备本身在解析CLI的时候会做一部分检查,但很多逻辑错误是设备层面校验不出来的。
1.2 NETCONF解决的不只是“格式化输出”
NETCONF协议(RFC 6241)和SSH脚本最本质的区别在于:它传输的不再是给人看的命令行文本,而是结构化的XML数据。你告诉设备的是“我要在VRFpublic下新增一条到192.168.100.0/24、下一跳192.168.1.254的静态路由”,而不是“帮我执行这条CLI”。
这样带来的直接好处有三个:
- 回显和判断标准统一。设备要么返回
<ok/>,要么返回<rpc-error>,错误里还带着错误类型、错误标签、错误信息,程序不用猜。 - 数据可查询、可过滤。你可以直接问设备“你现在的running配置里有哪些静态路由”,设备返回XML格式的完整数据,像查数据库一样方便。
- 可以组合
test-option和error-option实现先校验后提交,甚至回滚。
1.3 华为与H3C对NETCONF支持的现状
从协议实现成熟度来看,华为的CE系列交换机、NE系列路由器和较新的S系列交换机对NETCONF支持得比较完整,VRP版本一般在V200R0xx以上;H3C这边主要是Comware V7平台的设备支持得比较好,老的V5平台虽然能用但功能缩水明显。做项目之前,先确认设备型号和软件版本支不支持,别等代码写完了才发现设备连NETCONF服务都开不了。
我自己用的测试环境是华为S5720和H3C S5560,后面所有配置和XML模板都基于这两款设备验证过。不同系列、不同软件版本字段会有细微差异,但整体框架是通用的。
2. 设备侧准备:先让交换机愿意“说”NETCONF
2.1 华为设备开启NETCONF(以VRP为例)
华为设备开启NETCONF服务的核心命令是netconf ssh server enable,配置在系统视图下执行。下面是一个最小可用配置:
system-view netconf ssh server enable # 创建本地用户,并允许通过SSH接入 local-user netcon password irreversible-cipher YourPassword@2024 local-user netcon service-type ssh local-user netcon privilege level 15 # 配置SSH用户,服务类型必须是netconf ssh user netcon ssh user netcon authentication-type password ssh user netcon service-type netconf这里有一个关键点:SSH用户的服务类型必须明确指定为netconf,否则Java程序即使SSH连接成功,也无法打开netconf channel。另外privilege level 15给了用户最高权限,如果你只想让这个账号做路由配置,可以结合命令行授权做细粒度控制,但生产环境里为了简化,我通常会单独开一个专用账号,密码走密钥管理平台。
2.2 H3C设备开启NETCONF(以Comware V7为例)
H3C这边的配置逻辑类似,但命令风格稍有不同:
system-view netconf ssh server enable local-user netcon class manage password simple YourPassword@2024 service-type ssh authorization-attribute user-role network-admin ssh user netcon service-type netconf authentication-type password注意H3C的local-user后面多了class manage,角色用了network-admin,这是Comware V7的常见写法。如果你登录设备后发现netconf ssh server enable命令不存在,先查一下软件版本是不是Comware V7,V5老版本这套配置是走不通的。
2.3 权限、端口与用户配置的常见坑
端口问题:NETCONF over SSH的标准端口是830,但很多设备默认只在22端口上提供netconf子系统。我建议在设备上手动指定NETCONF监听端口,比如华为可以执行netconf ssh server port 830,H3C在netconf ssh server enable之后默认复用SSH端口。Java程序里连接端口要和设备实际监听保持一致,这个最容易被忽略。
用户权限不足:无论华为还是H3C,如果NETCONF用户的权限级别不够,你会发现get-config能通,但edit-config一提交就返回access denied错误。我在测试环境里踩过一次,华为本地用户只给了level 3,查询正常,一写配置就报错,排查半天才反应过来。生产环境按最小权限原则可以,但调通阶段直接给最高权限最省事。
SSH算法兼容性:老设备(尤其是Comware V5时代的设备)默认只支持ssh-rsa等旧算法,Java高版本的JSch库可能默认不启用了,这时候需要在JSch的Config里显式加上server_host_key和kex算法配置。这个问题在后面代码部分会展开。
3. Java侧连接:从SSH到Netconf Session
3.1 依赖选型:为什么我推荐JSch而不是重量级框架
Java生态里做NETCONF的库不算多。OpenDayLight的netconf-sdk功能全,但依赖很重,学习曲线陡,适合做平台型产品;设备厂商自己的SDK(比如华为的iMaster NCE内置的API适配层)绑定云管理平台,不适合直接操作单台设备。
我最终选的是JSch加手写XML方案。JSch只负责建立SSH连接和打开netconf类型的channel,协议层的内容全部自己控制。这样做的好处是:第一,只看RFC 6241就能完全掌控行为,不需要去理解框架的抽象;第二,华为和H3C设备在细节上有差异,手写XML可以灵活适配;第三,依赖只有一个JSch,离线环境也好部署。
Maven依赖只需要加一个:
<dependency> <groupId>com.github.mwiede</groupId> <artifactId>jsch</artifactId> <version>0.2.17</version> </dependency>这里用的是社区维护分支,对新版JDK和OpenSSH算法的兼容性比原版JSch好很多,强烈建议用这个坐标。
3.2 建立会话的代码骨架
建立NETCONF会话分三步:SSH连接、打开netconf channel、交换hello消息。下面这段代码是完整可运行的最小骨架:
import com.jcraft.jsch.Channel; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; public class NetconfClient { public static void main(String[] args) throws Exception { JSch jsch = new JSch(); Session sshSession = jsch.getSession("netcon", "192.168.1.1", 830); sshSession.setPassword("YourPassword@2024"); sshSession.setConfig("StrictHostKeyChecking", "no"); // 老设备SSH算法兼容 sshSession.setConfig("server_host_key", "ssh-rsa,ssh-ed25519"); sshSession.setConfig("kex", "diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha256"); sshSession.connect(15000); Channel channel = sshSession.openChannel("netconf"); channel.connect(15000); InputStream in = channel.getInputStream(); OutputStream out = channel.getOutputStream(); // 1. 发送hello消息 String 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.0</capability>" + "</capabilities>" + "</hello>]]>]]>"; out.write(hello.getBytes(StandardCharsets.UTF_8)); out.flush(); // 2. 读取server的hello响应(这里只读一次做演示) byte[] buf = new byte[4096]; int len = in.read(buf); System.out.println("Server hello: " + new String(buf, 0, len)); // 3. 后续就可以发送rpc消息了 // ... channel.disconnect(); sshSession.disconnect(); } }这段代码里有个很重要的细节:NETCONF消息的结束符是]]>]]>,不是普通的换行。设备就是靠这个分帧符来判断一条XML消息是否完整,漏掉它会导致设备一直等你的下一个消息,Java程序也读不到响应。这是很多第一次写NETCONF程序的人最容易卡住的地方。
3.3 能力探测:这一步千万不能省
交换完hello消息后,设备会在响应里返回它支持的能力列表(capabilities)。华为和H3C设备都会在能力列表里公布自己私有的YANG模块命名空间,比如华为通常会包含类似于http://www.huawei.com/netconf/vrp/huawei-static-route的命名空间,H3C则是http://www.h3c.com/netconf/data:1.0这类。
我的建议是:程序启动后先打印并保存设备返回的完整capabilities,再决定后续XML用哪套模板。不同款设备、不同软件版本,能力列表会有差异,提前探测可以避免在运行期才发现“该设备根本不支持get-schema”这种尴尬问题。
另外,如果设备的hello消息迟迟不来,先检查网络连通性和SSH认证,再确认设备上的NETCONF服务是不是真的开了。排查顺序别搞反。
4. 静态路由增删改查:XML构造与华为H3C差异
4.1 查询现网静态路由
查询配置用get-config,操作对象是running配置库。下面的XML请求可以拉取华为设备当前所有的IPv4静态路由:
<?xml version="1.0" encoding="UTF-8"?> <rpc message-id="1" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <get-config> <source> <running/> </source> <filter type="subtree"> <static-route xmlns="http://www.huawei.com/netconf/vrp/huawei-static-route"> <ipv4> <route/> </ipv4> </static-route> </filter> </get-config> </rpc>注意message-id属性每个RPC请求必须不同,相当于消息的唯一标识,设备返回的rpc-reply会带着相同的id,方便你匹配请求和响应。程序里建议用AtomicLong自增生成。
设备返回的数据结构大致长这样:
<rpc-reply message-id="1" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <data> <static-route xmlns="http://www.huawei.com/netconf/vrp/huawei-static-route"> <ipv4> <route> <vrfName>_public_</vrfName> <prefix>192.168.100.0</prefix> <mask>255.255.255.0</mask> <nextHop>192.168.1.254</nextHop> <interfaceName>NULL0</interfaceName> </route> </ipv4> </static-route> </data> </rpc-reply>拿到这个XML之后,用JDK自带的DocumentBuilder解析就行,不需要引入额外的JSON库。字段含义很直观:prefix是目的网段,mask是子网掩码,nextHop是下一跳地址,vrfName是VRF实例名,公网实例在华为设备上固定叫_public_。
4.2 新增静态路由(华为模板与字段说明)
新增配置统一用edit-config,操作的默认语义是merge,也就是“如果这条路由已存在就更新,不存在就创建”。这个行为对我们做幂等操作非常友好,同一个脚本跑两遍不会出问题。
华为设备新增静态路由的XML长这样:
<?xml version="1.0" encoding="UTF-8"?> <rpc message-id="2" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <edit-config> <target> <running/> </target> <config> <static-route xmlns="http://www.huawei.com/netconf/vrp/huawei-static-route"> <ipv4> <route> <vrfName>_public_</vrfName> <prefix>192.168.100.0</prefix> <mask>255.255.255.0</mask> <nextHop>192.168.1.254</nextHop> </route> </ipv4> </static-route> </config> </edit-config> </rpc>华为静态路由模型里面,vrfName、prefix、mask、nextHop这几个字段组合起来构成路由的唯一标识,如果还想指定出接口,可以再加interfaceName字段。但大多数场景下只要目的网段和下一跳就能下发成功,多余字段反而可能因为设备型号差异导致报错。
有一个细节值得注意:华为部分版本的静态路由模型里,interfaceName必须在某些特定情况下才允许出现,比如写NULL0接口或者隧道接口。如果你只是配置普通的下一跳路由,别画蛇添足,我一开始为了“完整”强行加了interfaceName,结果在S5720上报了数据校验错误。
4.3 切换到H3C:命名空间与字段差异
H3C设备的静态路由模型和华为不一样,最大的区别在命名空间和字段命名上。同样新增一条到192.168.200.0/24、下一跳192.168.1.254的静态路由,H3C的XML是:
<?xml version="1.0" encoding="UTF-8"?> <rpc message-id="2" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <edit-config> <target> <running/> </target> <config> <top xmlns="http://www.h3c.com/netconf/data:1.0"> <StaticRoute xmlns="http://www.h3c.com/netconf/data:1.0"> <IPv4Route> <Route> <Vrf>default</Vrf> <IpAddress>192.168.200.0</IpAddress> <Mask>255.255.255.0</Mask> <NextHop>192.168.1.254</NextHop> </Route> </IPv4Route> </StaticRoute> </top> </config> </edit-config> </rpc>H3C这边有几个明显差异:
- 命名空间是
http://www.h3c.com/netconf/data:1.0,且top节点后面还要再套一层StaticRoute,层级比华为深。 - 字段名首字母大写,
IpAddress、Mask、NextHop,和华为的全小写风格完全不一致。 - VRF实例名默认叫
default,而不是华为的_public_。
所以在做跨厂商适配的时候,不要想着写一套XML通吃两家。我一般是在代码里做一层适配:根据设备厂商选择不同的XML模板,用模板引擎或字符串拼接的方式填充参数。数据模型不一样,强行统一反而容易出错。
4.4 删除静态路由的key字段与操作符选择
删除比新增要小心,核心原则是:只给关键字段,别带无关字段。
华为设备删除静态路由的XML:
<?xml version="1.0" encoding="UTF-8"?> <rpc message-id="3" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <edit-config> <target> <running/> </target> <config> <static-route xmlns="http://www.huawei.com/netconf/vrp/huawei-static-route"> <ipv4> <route operation="delete"> <vrfName>_public_</vrfName> <prefix>192.168.100.0</prefix> <mask>255.255.255.0</mask> <nextHop>192.168.1.254</nextHop> </route> </ipv4> </static-route> </config> </edit-config> </rpc>operation="delete"是加在route节点上的,不是加在edit-config上。这个用法对应RFC 6241里的delete操作,语义是“删除和这些key字段匹配的配置数据”。如果你只想删某一条特定路由,vrfName、prefix、mask、nextHop四个字段必须和现网完全一致,缺一个都可能删除失败或者删多了。
我实际测试下来,华为的delete操作对key字段要求很严格,例如prefix必须是点分十进制格式,不能写成简写;mask必须是完整子网掩码,不能用CIDR长度替代。如果程序里拿到的数据和设备返回的不一致,先看看是不是格式转换问题。
H3C删除路由基本同理,把新增的XML里的Route节点加上operation="delete"属性即可。还有一个可选的operation="remove",语义是“删掉这个节点,不存在也不报错”,比delete更宽容,适合清理类脚本。但生产环境我建议用delete,起码能发现数据不一致的问题。
5. 生产环境中的四个坑
5.1 配置后不保存,设备一重启全没了
这个问题我在最初测试时差点翻车。通过NETCONF往running配置库里写入的路由,设备默认不会自动保存到配置文件。华为设备上如果你不手动执行save,重启之后所有NETCONF下发的配置全部丢失;H3C设备稍好一点,但也没有保证。
所以每次批量操作完成后,一定要补一步“保存配置”的动作。在NETCONF里可以这样发:
<?xml version="1.0" encoding="UTF-8"?> <rpc message-id="4" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <save/> </rpc>华为设备对这个<save/>操作支持得比较标准。H3C部分版本可能不认这个操作,那就得退回CLI方式执行save force,或者尝试使用<save><file>startup.cfg</file></save>这样的带参数形式。我建议上线前在测试设备上验证一下你的保存操作到底能不能用,别等生产配置丢了再后悔。
5.2 会话空闲超时与并发锁
NETCONF会话本质上是一条SSH连接,如果长时间没有消息交互,设备端的空闲超时会自动断开连接。华为设备默认NETCONF会话空闲超时可能只有10分钟,H3C类似。批量任务如果中途要等人审批或者做数据比对,建议在代码里加一个心跳机制,定期发送一个<get>请求或者空RPC保活。
另外要注意的是设备对并发NETCONF会话的数量限制。我遇到过同时开超过5个会话去操作一台H3C设备,新会话直接被拒绝。所以批量任务里要控制并发度,一个设备一个线程,连接池大小不要开太大,我一般限制在3个以内。
如果担心多个管理员同时改配置产生冲突,可以显式调用<lock><target><running/></target></lock>给设备加锁,操作完成后再unlock。不过这把锁的开销不小,而且如果程序异常退出没解锁,锁会一直占着直到会话超时。单脚本执行的话我通常不加锁,靠操作幂等性保证安全。
5.3 校验与回滚:别把误配置直接怼到running
很多初学者不知道edit-config里可以带校验选项。华为设备支持通过<error-option>rollback-on-error</error-option>让设备在出错时自动回滚本次操作的所有变更,保证“要么全部生效,要么全部不动”。
我在批量脚本里是这样用的:
<edit-config> <target> <running/> </target> <error-option>rollback-on-error</error-option> <config> <!-- 要下发的配置 --> </config> </edit-config>这个选项对网络变更特别重要。想象一下你一次性下发10条路由,第7条因为冲突被设备拒绝,如果没有回滚选项,前6条已经生效了;加上回滚选项后,设备会把前6条也撤销,整个会话保持原状。我在生产环境所有的写操作都强制带这个选项,成本几乎为零,收益却很大。
5.4 编码与分帧符的那些小问题
最后说几个看起来很基础但实际很折磨人的细节。第一是编码,设备返回的XML可能是UTF-8,也可能是设备默认的其他字符集,建议在读取字节流后显式按UTF-8解码,不要依赖系统默认编码,否则中文注释或名称会乱码。第二是分帧符,前面提到过]]>]]>不能省,而且发送完]]>]]>之后建议加一个换行,部分设备对换行敏感。第三是XML特殊字符,如果路由描述信息里含有&、<、>这些字符,一定要做XML转义,我用StringEscapeUtils.escapeXml11()这类工具统一处理。
还有一个SSH层的坑:JSch在低版本JDK下默认TLS算法可能不被老设备支持,连接时会报Algorithm negotiation fail。解决方案就是在Session上手动指定兼容算法,我代码里已经写了示例,生产环境遇到类似报错优先检查这里。
我自己的习惯是,每次跑批量任务之前先在测试设备上跑一遍完整的增删查脚本,再在生产上分批执行;代码里额外加一个成功率统计输出,每台设备的结果都落日志。这套流程跑了半年,目前还没有出过一起因为NETCONF操作导致的网络事故。网络自动化这条路,代码本身不难,难的是对设备特性的尊重和对变更流程的敬畏,希望这篇文章能帮你少走几步弯路。
本文还有配套的精品资源,点击获取