简介:在Windows x64平台下编译生成的Net-SNMP 5.9.4稳定版,是一套完整的SNMP网络设备监控与管理工具包;它由Visual Studio 2022构建,并以静态方式集成OpenSSL 3.5.0 x64加密库,目标机器无需额外安装OpenSSL即可使用SNMPv3加密通信,同时提供debug与release两种构建,方便开发阶段断点调试和上线后的高效部署,适合C++开发者、网络运维人员以及需要基于SNMP协议进行二次开发的技术团队,可应用于设备状态采集、性能告警上报、Trap消息接收与转发、MIB文件解析等常见监控场景。压缩包共883个文件,大小约19.97MB,主要包含430个头文件、156个txt说明文档、48个conf配置示例、38个exe可执行程序,以及lib静态库、pdb调试符号等;头文件用于二次编译链接,txt文档说明用法与配置项,conf提供典型配置模板,exe则可直接运行常用管理命令,整体目录结构清晰,便于快速定位所需组件。目前已有521人浏览学习;该版本免去在Windows上手动编译依赖库的繁琐过程,并附有丰富的开发头文件和示例,适合希望快速获得可用SNMP管理组件、扩展网络监控功能的工程师参考使用。
1. 为什么你需要一份 net-snmp 5.9.4 Windows x64 自编译版
如果你在 Windows 上做过 SNMP 开发,大概率已经翻过一遍 net-snmp 的官方站:只有源码,没有现成的 x64 二进制。更麻烦的是,SNMPv3 的 USM 模型离开 OpenSSL 就退化成弱口令和明文传输,抓包等于裸奔。所以我在整理监控采集程序时,直接把 net-snmp 5.9.4 在 Windows x64 下连 OpenSSL 一起自编译了一份。这篇文章把编译参数、C++ 接入方式和四个高频坑完整写出来。适合用 C++ 写网络监控、被管设备发现、带内巡检工具的人;如果你只是想在 Linux 上敲两条 snmpget,这文帮不上你。
2. 在 Windows 上编译 net-snmp:环境选型和 MSYS2 准备
2.1 为什么自编译,以及官方包和第三方包的边界
net-snmp 官方 release 页面至今只提供源码包,Windows 用户拿到手的是一个 tar.gz 和一套不能直接跑的 VS 工程文件。源码包里虽然有 win32 目录,但那套工程需要你自己去配 OpenSSL 头文件和库路径,配完还要处理 libtool 生成的导入库,对只想要结果的人来说太痛苦。
网上能搜到的第三方编译版还有一个比较隐蔽的问题:很多是 5.7.x 时代的老货,协议栈没问题,但 MIB 模块不全,尤其是 IF-MIB 的 64 位计数器和较新的 RFC 标准里定义的告警项都没有。更麻烦的是,部分第三方包把 OpenSSL 作为动态依赖,换一台没装 OpenSSL 的机器直接跑不起来。而做网络监控的人,最怕的就是采集端能编能跑,部署到现场却崩。
下面是三个方向的对比:
| 维度 | 官方源码自己编 | 第三方旧版二进制 | 带 OpenSSL 自编译版 |
|---|---|---|---|
| SNMPv3 AES/DES 加密 | 要自己配 OpenSSL | 多数不支持或版本旧 | 编译期链好 OpenSSL |
| IPv6 支持 | configure 开启 | 不确定 | 编译期开启 |
| 自定义 MIB 扩展 | 要重新编译 | 不能改 | 按需定制模块 |
| 部署目标机依赖 | 需要额外装 OpenSSL | 取决于原编译者 | 独立运行 |
| 链进 C++ 程序 | 按需编静态库 | 不提供静态库 | 携带完整 include/lib |
我倾向把这条链路彻底打通:编译期就把 OpenSSL 的加解密能力编进 net-snmp,部署时目标机干干净净,只拷贝 bin 目录就能跑。这样做监控采集端时,commit 里能锁住的依赖就只剩我自己的代码。
2.2 工具链准备:MSYS2 环境与 OpenSSL 版本选择
Windows 上编译 net-snmp,我踩过两条路:Visual Studio + CMake,以及 MSYS2 + MinGW-w64。VS 那条路要自己维护 net-snmp 的 configure 逻辑,因为它的构建系统是 autoconf 那一套,原生 Windows 下跑起来非常受罪。MSYS2 能直接跑 configure 脚本,包管理也省心,所以我最终固定在这个方案上。
具体环境我用的 MINGW64,不是 MSYS2 自带的 native 分支。native 分支编出来的 exe 依赖 MSYS2 runtime DLL,到客户机器上容易缺一堆运行时库;MINGW64 产物更接近普通 Windows 程序,依赖面干净得多。
先在 MSYS2 里把基础工具链装齐:
pacman -Syu pacman -S --needed base-devel \ mingw-w64-x86_64-toolchain \ mingw-w64-x86_64-perl \ mingw-w64-x86_64-libtool \ mingw-w64-x86_64-autoconf \ mingw-w64-x86_64-automake \ mingw-w64-x86_64-pkg-configperl 是 net-snmp 编译脚本强制依赖的,缺了它 configure 到一半会报perl not found。pkg-config 用来探测 OpenSSL 的位置,后面 configure 环节会用到。装完后确认 gcc 版本和 perl 版本都正常,再进入 OpenSSL 决策。
这里有个血泪教训:MSYS2 源里的 OpenSSL 默认是 3.x,而 net-snmp 5.9.4 的源码对 OpenSSL 3.0 的适配并不完整,直接编会因为rsa_sslv23_padding这类内部符号被移除而中断,后文第 5 章我会展开讲。我这次编译用的是 OpenSSL 1.1.1 系列,如果仓库里已经只有 3.x,就从 OpenSSL 官网下 1.1.1w 源码自己编一份,放到/opt/openssl-1.1备用。这一步决定了整个编译是顺利收尾还是折腾半天的分水岭。
3. 完整编译 net-snmp 5.9.4:configure 参数、make 与安装验证
3.1 configure 参数逐项拆解
net-snmp 的编译参数直接决定你拿到的东西是"能跑"还是"在自己的场景里敢用"。我一般先在 MSYS2 里切到 MINGW64 环境,把 PKG_CONFIG_PATH 指好,然后执行下面这一串:
export PKG_CONFIG_PATH=/mingw64/lib/pkgconfig ./configure \ --prefix=/usr/local/net-snmp \ --with-openssl=/opt/openssl-1.1 \ --with-defaults \ --enable-ipv6 \ --disable-embedded-perl \ --without-perl-modules \ --disable-debugging \ --with-mib-modules="host mibii if-mib udp-mib tcp-mib" \ --with-out-mib-modules="ucd-snmp/lmSensors" \ --with-persistent-directory=/var/net-snmp每个参数都不白给,我一个个说清楚:
| 参数 | 实际含义 | 我为什么这么设 |
|---|---|---|
--prefix=/usr/local/net-snmp | 安装根目录 | 固定一个目录,后面 C++ 工程引用 include/lib 都从这走 |
--with-openssl=/opt/openssl-1.1 | OpenSSL 安装路径 | 指定 1.1.1w,绕开 3.x 的符号问题 |
--with-defaults | 对 configure 的交互式提问全部选默认 | 不加的话会停下来问你一堆 yes/no,自动化脚本里会卡死 |
--enable-ipv6 | 开启 IPv6 支持 | 现在设备组网 IPv6 太普遍,不开后面还得重编 |
--disable-embedded-perl | 禁止嵌入 Perl 解释器 | Windows 目标机上不一定有 Perl 运行时,嵌进去反而埋雷 |
--with-mib-modules | 额外编译的 MIB 模块 | 监控常用的一定带上:host、if-mib、udp-mib、tcp-mib |
--with-out-mib-modules | 排除的 MIB 模块 | lmSensors 是 Linux 专用,Windows 下编它会报传感器相关错误 |
--with-persistent-directory | 持久化数据存放目录 | 编译期定死,避免运行时到处找目录 |
注意--with-mib-modules里的模块名来自源码mib/目录下的子目录名。如果只是做基本接口状态监控,默认模块就够;如果你要读设备上的温度、电源、风扇这类标准实体 MIB,需要把entity-mib、entity-state-mib也加进来。模块加多了编译时间会变长,但运行时不用的不会被加载,所以宁多勿缺。
3.2 make 编译与 make install 落地
configure 顺利结束后,直接并行编译:
make -j4-j4看机器核心数定,我这边 4 核机器约 5 分钟编完。如果中间报错,先不要急着重跑,看是哪一步挂的——源码编译阶段的错误大多能通过调整上个章节的参数修复,而不是硬改代码。
编译没有报错后安装:
make install安装完成后/usr/local/net-snmp/下会生成四类东西:bin/目录是命令行工具和关键 DLL,include/net-snmp/是 C/C++ 头文件,lib/目录是链接用的导入库和静态库,share/snmp/mibs/是全部标准 MIB 文件。DLL 默认落在bin/下而不是lib/,这是 Windows 版 net-snmp 的惯例,后面 C++ 运行时你大概率会因为这个找不到库,先有个印象。
3.3 编译产物验证:先跑通命令行工具
安装完先不要急着写代码,用命令行工具确认这版编译是健康的:
export PATH=/usr/local/net-snmp/bin:$PATH snmpget --version snmptranslate -On .1.3.6.1.2.1.1.1.0snmpget --version会打出编译的配置摘要,我习惯看里面有没有 openssl 字样,有就说明加密链路已经从配置层面列入了。snmptranslate -On的作用是把符号 OID 转成数字 OID,如果它能输出.1.3.6.1.2.1.1.1.0,说明 MIB 加载路径没坏。两个都通过,再进下一步。
4. 把 libsnmp 接进你的 C++ 项目:一个 SNMPv3 读设备示例
4.1 头文件、库路径与链接方式
net-snmp 编译完成后,C++ 工程要引用的是include/net-snmp/和lib/libnetsnmp.dll.a。前者是一整套头文件,不是你随便 include 一个net-snmp/net-snmp-includes.h就完事,它内部会依赖net-snmp-config.h,这个头文件是编译期由 configure 生成的,必须从你的安装目录走,不能自己凭空写。
安装后的目录结构大致长这样:
/usr/local/net-snmp/ ├── bin/ │ ├── snmpget.exe │ ├── snmptranslate.exe │ └── libnetsnmp-40.dll ├── include/net-snmp/ │ ├── net-snmp-config.h │ ├── net-snmp-includes.h │ └── ... └── lib/ ├── libnetsnmp.dll.a └── libnetsnmp.a链接时-lnetsnmp会自动匹配导入库,但如果 MinGW 的链接器找不到 DLL 对应的导入库,代之以需要显式加-L/usr/local/net-snmp/lib。除了 libnetsnmp 本身,还要手动钉住两个附加库:-lws2_32(Windows socket 接口)和-lcrypto(OpenSSL 的加密原语)。不加ws2_32的话,链接阶段会冒出一堆__imp_htonl之类的未定义符号。
4.2 完整代码:一个带 AES 加密的 SNMPv3 GET 请求
下面这段代码是我在实际采集程序里提炼出来的最小可运行版本,核心是建立 SNMPv3 会话,以 authPriv 安全级别发送 GET 请求读取设备的 sysDescr:
#include <net-snmp/net-snmp-config.h> #include <net-snmp/net-snmp-includes.h> #include <cstring> #include <iostream> int main() { init_snmp("snmp_demo"); snmp_session session; snmp_sess_init(&session); session.version = SNMP_VERSION_3; session.peername = strdup("127.0.0.1:161"); session.securityName = strdup("monitor"); session.securityNameLen = strlen(session.securityName); session.securityLevel = SNMP_SEC_LEVEL_AUTHPRIV; session.authProtocol = SNMP_AUTH_HMAC_SHA; session.authpass = strdup("auth_pass_123"); session.privProtocol = SNMP_PRIV_AES; session.privpass = strdup("priv_pass_123"); void* sessp = snmp_open(&session); if (sessp == nullptr) { snmp_perror("snmp_open"); return 1; } snmp_pdu* pdu = snmp_pdu_create(SNMP_MSG_GET); const char* oid_str = "1.3.6.1.2.1.1.5.0"; // sysName oid oid_buf[MAX_OID_LEN]; size_t oid_len = MAX_OID_LEN; if (snmp_parse_oid(oid_str, oid_buf, &oid_len) == nullptr) { std::cerr << "invalid OID: " << oid_str << std::endl; snmp_close(sessp); return 1; } snmp_add_null_var(pdu, oid_buf, oid_len); snmp_pdu* response = nullptr; int status = snmp_synch_response(sessp, pdu, &response); if (status == STAT_SUCCESS && response->errstat == SNMP_ERR_NOERROR) { for (netsnmp_variable_list* vars = response->variables; vars != nullptr; vars = vars->next_variable) { char name[1024]; snprint_objid(name, sizeof(name), vars->name, vars->name_length); std::cout << name << " = "; if (vars->type == ASN_OCTET_STR) { std::cout << (const char*)vars->val.string << std::endl; } } } else { snmp_perror("snmp_synch_response"); } if (response) snmp_free_pdu(response); snmp_close(sessp); return 0; }代码逻辑分四步:初始化 SNMP 运行环境;填充snmp_session结构体描述会话身份和加密参数;构造 GET 请求 PDU 并同步发送;遍历响应变量打印结果。
几个参数值得单独说明。session.securityLevel = SNMP_SEC_LEVEL_AUTHPRIV表示认证和加密都要,这是 SNMPv3 最安全的组合,等于 Linux 上-v3 -l authPriv。authProtocol是SNMP_AUTH_HMAC_SHA,对应设备侧 snmpd.conf 里的auth SHA;privProtocol是SNMP_PRIV_AES,对应设备侧priv AES。两边任何一个参数对不上,snmp_synch_response会直接返回STAT_ERROR,抓包看时能发现是 USM 层认证失败——这不是网络问题,是口令或协议不匹配。
注意strdup()分配的内存 net-snmp 在snmp_close时内部会处理大部分,但如果你在循环里改了session.peername,记得自己 release,否则每次探测一台设备就泄漏一块。
4.3 编译运行与常见调试手段
编译这段代码用 MinGW 的 g++,命令如下:
g++ -std=c++17 -I/usr/local/net-snmp/include \ -L/usr/local/net-snmp/lib \ snmp_demo.cpp -lnetsnmp -lws2_32 -lcrypto -o snmp_demo运行前把bin目录加进 PATH,或直接把libnetsnmp-40.dll拷到 exe 同目录,否则 Windows 会在启动阶段报错,后面第 5 章详述。第一次跑通前,建议先在目标设备上确认snmpd服务确实开了 authPriv,配置片段参考:
createUser monitor SHA auth_pass_123 AES priv_pass_123 authUser monitor authPriv这段配置放在/etc/snmp/snmpd.conf。注意 net-snmp 5.9.x 的用户名、口令、加密协议必须和服务端一致,否则最常见的现象不是超时,而是返回STAT_ERROR但不带任何错误文本,很容易产生"代码写错了"的错觉。
5. 自编译 net-snmp 最容易踩的四个坑:从 configure 到运行时
5.1 OpenSSL 3.x 引发的 rsa_sslv23_padding 编译中断
现象:configure 顺利通过,执行make -j4时在openssl/openssl.c报错,错误信息指向openssl.c:1520:58: error: 'rsa_sslv23_padding' undeclared,编译直接中断。
原因:OpenSSL 3.0 开始移除了大量内部符号,rsa_sslv23_padding就是其中之一。net-snmp 5.9.4 源码里openssl/openssl.c在 Windows 编译路径下仍然引用了这个旧符号,属于版本适配遗漏,不是你的配置问题。这个翻车点藏得挺深——configure 阶段不会报错,因为那是脚本探测,真正踩雷的是编译期。
解决:换成 OpenSSL 1.1.1 系列重新编译,我这边固定用 1.1.1w。如果你因为安全合规必须用 3.x,可以试着给rsa_sslv23_padding定义个别名补丁,但改动涉及 RSA 填充逻辑,我不建议新手上。从那以后我的惯例就是 Windows 编译一律拿 1.1.1w 做基准,新项目都不碰这个雷。
5.2 configure 说找不到 OpenSSL:路径、pkg-config 与环境变量
现象:configure 输出里checking for openssl... no,或者checking openssl/ssl.h usability... no,然后 configure 中止。你确认/opt/openssl-1.1路径存在,OpenSSL 也确实是装在那里的,但它就是找不到。
原因:net-snmp 的 configure 找 OpenSSL 走的是两套逻辑,先问 pkg-config,再直接在系统路径搜头文件。Windows 下 MSYS2 的 pkg-config 默认只查/mingw64/lib/pkgconfig,你那个/opt/openssl-1.1/lib/pkgconfig不在搜索路径里,自然探测失败。
解决:重点不是把 OpenSSL 装到系统默认路径,而是把搜索路径指过去。在执行 configure 前加上:
export PKG_CONFIG_PATH=/opt/openssl-1.1/lib/pkgconfig export CPPFLAGS="-I/opt/openssl-1.1/include" export LDFLAGS="-L/opt/openssl-1.1/lib"CPPFLAGS和LDFLAGS是 autoconf 系的固定环境变量,分别在预处理和链接阶段生效。加上后再跑./configure,看到checking for openssl... yes就可以继续了。
5.3 exe 能编出来却跑不起来:DLL 缺失是 Windows 上的第一杀手
现象:所有编译都成功,但一运行snmpget --version,Windows 弹出"找不到 libnetsnmp-40.dll"或者"找不到 libcrypto-1_1-x64.dll",程序秒退。这个现象在 MSYS2 环境里不出现,切到干净 Windows 命令行一跑就现原形。
原因:net-snmp 安装后的 DLL 全部放在bin/目录,运行时按 PATH 顺序搜索 DLL,Windows 默认 PATH 里根本没有这个目录。另外如果你用的是动态编译,目标机还没有 OpenSSL 运行时 DLL,那还得把libcrypto-1_1-x64.dll一并带上。
解决:两个方案,二选一。一是把bin/目录写进系统环境变量 PATH,适合开发阶段;二是把libnetsnmp-40.dll、libcrypto-1_1-x64.dll以及 MinGW 运行时所需的libgcc_s_seh-1.dll、libstdc++-6.dll、libwinpthread-1.dll全部拷贝到 exe 同目录,做成便携部署。我做现场交付时走的是第二种,因为客户机器不让乱动环境变量。检查依赖可以用ldd snmpget.exe输出全部 DLL 依赖。
5.4 命令行工具能跑,但 MIB 加载失败:Unknown Object Identifier 的真相
现象:snmpget已经能正常发请求,但你指定一个合法的私有 OID 或厂商 MIB 对象时,返回Unknown Object Identifier或Cannot find module (SNMPv2-TC)。用数字 OID 就能通,符号名不行。
原因:符号 OID 到数字 OID 的解析依赖本地 MIB 文件,net-snmp 的 MIB 搜索路径由环境变量MIBS和MIBDIRS控制。Windows 安装版没有默认设置这两个变量,而命令行的-m参数只在当前命令生效,换一台机器又回到原点。这是最容易被误判为"程序写错了"的一类问题。
解决:在开发机验证阶段用命令行参数快速确认:
export MIBS=+ALL export MIBDIRS=/usr/local/net-snmp/share/snmp/mibs+ALL表示加载全部找到的 MIB 文件。如果你只需要某个厂商 MIB,把+ALL换成具体模块名,比如MIBS=+CISCO-SMI。这个环境变量建议固化进你的工程脚本或 shell 启动文件,而不是每次手工敲。把这一步做掉之后,snmptranslate -On就能正常把符号名转成数字 OID,程序里的 OID 写符号名也不会再报错。
6. 装完之后一定要做的验证:OpenSSL 是否真的生效、MIB 路径固化
6.1 先确认加密链路:ldd 加一条真实的 SNMPv3 请求
拿到编译产物后,我做的第一件事永远是确认 OpenSSL 是真的链上了,而不是 configure 时被跳过。用 ldd 查一下:
ldd /usr/local/net-snmp/bin/snmpget.exe | grep -i -E "crypto|ssl"输出里能看到libcrypto-1_1-x64.dll就说明加密库进去了。只看这个还不够,跑一条真实的 SNMPv3 authPriv 请求做端到端验证:
snmpget -v3 -l authPriv -u monitor -a SHA -A auth_pass_123 \ -x AES -X priv_pass_123 127.0.0.1 .1.3.6.1.2.1.1.1.0能返回 sysDescr 值,说明认证、加密、MIB 解析三重链路全通。如果这步返回Unknown user name,优先去查 snmpd.conf 里createUser和authUser是否配对,而不是怀疑抓包出问题。
6.2 把 MIB 路径固化成 snmp.conf,别每次手动指定
命令行-M参数只对单次命令有效,我强烈建议把 MIB 路径写进snmp.conf。Windows 下 net-snmp 会按顺序找snmp.conf:先当前目录,后安装目录下的etc/snmp/。固定写法如下:
mibdirs +/usr/local/net-snmp/share/snmp/mibs mibs +ALL配好后,snmptranslate -On sysName.0不需要再加任何参数就能直接解析。我在所有项目里都保留这个文件,并且把它提交进工程仓库,新同事拉代码后不用问"为什么我这边 OID 解析不了"。自编译 net-snmp 的价值就在这里:代码、库、配置三件套一次成型,不靠现场拆零件。
从那以后我每次在 Windows 上交付监控采集程序,都要强制走一遍 ldd 查依赖、SNMPv3 加密请求、MIB 解析三条路径的验证,缺一不可。希望帮到你,少走一步,后面现场就多折腾一天。
本文还有配套的精品资源,点击获取