
上周在客户那边做流量分析模块部署一台 CentOS 7.9 内网服务器系统盘装完网络策略只放开了业务端口外网完全不通。采集程序对 libpcap 的版本有硬性要求必须用支持pcap_set_immediate_mode()的新版 API而系统自带的 libpcap 还是 1.5.3压根没有这个接口。于是只能在内网环境离线编译安装 libpcap顺手把 flex、bison、m4 这一套构建工具链全部过了一遍。整个过程踩了不少坑花了一晚上才把问题理清楚这里把完整过程记录下来给同样在内网环境做离线安装的朋友一个参考。这篇内容适合以下几类人在内网服务器上部署抓包工具、流量分析 Agent、IDS/IPS 探针的运维或开发需要在离线环境源码编译 C 库的程序员以及被configure: error: neither flex nor lex found、libpcap.so.1: cannot open shared object file这类报错折磨过的同学。我尽量把每一步的为什么也讲清楚而不只是甩给你一串命令。1. 内网部署场景为什么CentOS 7上非要源码编译libpcap1.1 系统自带libpcap版本太老满足不了新程序的接口需求CentOS 7 的 base 源里 libpcap 的版本是 1.5.3这个版本本身不旧但它暴露的 API 比较有限。比如pcap_set_immediate_mode()、pcap_set_tstamp_precision()、pcap_open()这些接口都是在 1.6 或 1.7 之后才逐步引入的。如果你在内网部署的是比较新的流量采集框架它编译的时候会直接检查 pcap.h 里有没有某些宏定义版本不够就编不过去。有人可能会说那我在线装一个 libpcap-devel 不就行了问题在于 CentOS 7 的 yum 源里 libpcap-devel 对应的还是 1.5.3即使你把 yum 源切到阿里云或者网易主流镜像里的 EPEL 包通常也是跟随系统版本的不会提供特别新的 libpcap。所以只要程序对版本有硬性要求走 RPM 这条路基本死路一条只能源码编译。还有一点离线环境下你没法用常规的yum install libpcap libpcap-devel自动拉依赖就算提前下载好了 RPM 包RPM 的依赖树也可能缺这缺那。比如 libpcap-devel 会依赖 glibc-headers、gcc 等一堆包离线机器上不一定都有。与其在网上一个个找 RPM 依赖不如直接源码编译源码包本身是自包含的依赖的工具链也就那几个可控性高得多。1.2 离线环境依赖链条不是只编libpcap一个包libpcap 的源码包在 configure 阶段需要三个东西m4、flex、bison。如果系统里原本就装了那自然省事但内网机器经常是精简安装连 gcc 都不一定有更别说这些构建辅助工具了。这里的依赖逻辑是这样的libpcap 的语法解析器由 bison 生成scanner.c由 flex 生成bison 运行的时候又要调用 m4 这个宏处理器flex 本身在编译的时候也可能用到 m4虽然有预生成文件兜底但保险起见还是先装 m4。所以离线编译 libpcap 的完整构建顺序是m4 - bison - flex - libpcap这个顺序不能乱尤其是如果你打算把这三个工具链都装进/usr/local目录后面每一步的 configure 都依赖前面的安装结果。我遇到的坑就是这样第一台机器上我以为只需要 flex 和 bison结果 configure 的时候 bison 报错说找不到 m4当时人直接傻掉。后来才意识到 m4 是 bison 的运行时依赖不是可选项。2. 离线物料准备在有网机器上备齐一套源码包2.1 版本选择与下载离线安装的第一步是在一台有网的机器上把所有需要的源码包下载好传到内网机器上。这里版本搭配很关键不是越新越好要考虑 CentOS 7 默认 GCC 4.8.5 的编译能力。我最终选择的版本组合如下组件推荐版本说明m41.4.191.4.16 也能用1.4.19 是 GNU 官方长期维护版本在 CentOS 7 上编译无压力bison3.0.4CentOS 7 自带的就是这个版本兼容性最好不要尝试 3.8 以上对 GCC 4.8 不友好flex2.6.42.6.4 之后的版本对 autotools 版本有更高要求2.6.4 足够稳libpcap1.9.1对 CentOS 7 的老 GCC 最友好1.10.x 我也试过能编译但部分代码对内核头文件版本有要求下载地址方面libpcap 的官方发布页面在https://www.tcpdump.org/release/上面能找到libpcap-1.9.1.tar.gz以及 1.10.x 的包。m4、bison、flex 的源码包在 GNU 镜像站都能下到比如https://ftp.gnu.org/gnu/m4/、https://ftp.gnu.org/gnu/bison/flex 在 GitHub Releases 页面也可以下载。这里提醒一句下载的时候尽量选择官方源不要随便到一个第三方网站拿包原因不光是安全问题第三方打包的源码经常改动过目录结构解压之后 configure 脚本各种报错排查起来非常浪费时间。2.2 校验工具与包完整性离线传输前我建议先做一次完整性校验。源码包在传输或下载过程中损坏会导致 configure 或者 make 阶段报出莫名其妙的问题而且报错位置往往离真正的坏文件很远排查成本很高。校验方法很简单在下载机器上执行sha256sum libpcap-1.9.1.tar.gz m4-1.4.19.tar.gz bison-3.0.4.tar.gz flex-2.6.4.tar.gz然后把输出的哈希值和官网页面公布的值核对一遍。libpcap 官网上每个版本都给出了对应的 SHA256 值GNU 镜像站的文件旁边也有.sig签名文件可以验证。确认一致后再打包上传到内网机器。2.3 备选用 yumdownloader 拉 RPM 依赖树如果你的场景对 libpcap 版本没有硬性要求只是为了在内网装一个可用的抓包环境那其实不必走源码编译直接在离线机器上安装 RPM 包更快。具体做法是在有网的 CentOS 7 机器上执行yum install -y yum-utils mkdir -p /tmp/rpms yumdownloader --destdir/tmp/rpms --resolve libpcap libpcap-develyumdownloader --resolve会把 libpcap 和 libpcap-devel 的所有依赖 RPM 包都拉下来然后你把/tmp/rpms整个目录传到内网机器执行rpm -ivh /tmp/rpms/*.rpm如果提示依赖缺失一般是某个仓库没有启用或者 RPM 包版本冲突可以再用rpm -qpR查看具体缺什么补齐后再装。不过要记住CentOS 7 base 源里的 libpcap-devel 最高也就是 1.5.3。如果你需要新 APIRPM 方案就白搭了老老实实源码编译。3. 源码编译全流程从 m4 到 libpcap 一步步落地3.1 先确认系统基础工具gcc、make、kernel-devel在编译任何东西之前先确认内网机器的基本编译环境是否可用。执行rpm -q gcc make kernel-headers kernel-devel如果 gcc 都没有那离线安装 libpcap 之前还得先离线装 gcc这就又是另一套 RPM 依赖树了。实操中我见过很多内网机器在交付时已经带了 GCC 和 Make但 kernel-devel 不一定有而 libpcap 编译时如果检测到内核头文件版本不匹配可能在某些 feature 上直接给你 disable 掉或者报一些奇怪的错误。我的建议是至少保证rpm -q gcc make有输出make -v和gcc --version能正常执行。如果连 gcc 都没有可以考虑用 CentOS 7 安装光盘作为本地 yum 源从光盘里装 gcc这一点不展开但值得先确认。3.2 编译 m4 与 bison解决 configure 阶段的“工具链缺失”把源码包传到内网机器的/opt/src目录后开始按顺序编译。先编 m4cd /opt/src tar xzf m4-1.4.19.tar.gz cd m4-1.4.19 ./configure --prefix/usr/local make -j$(nproc) make install这里--prefix/usr/local的目的是让整个工具链统一装到/usr/local下避免和系统自带的/usr/bin下的老版本混在一起。CentOS 7 默认的/usr/bin/m4其实是有的但版本通常比较老如果直接系统 m4 能用你也可以跳过这一步。我之所以自己编是因为后面 bison 需要 m4而我不想冒险用老版本 m4 去处理新版本 bison 的语法文件。编译完 m4 之后把/usr/local/bin加到 PATH 里确保后续 configure 能找到新工具export PATH/usr/local/bin:$PATH接下来编译 bisoncd /opt/src tar xzf bison-3.0.4.tar.gz cd bison-3.0.4 ./configure --prefix/usr/local make -j$(nproc) make install如果你的系统里已经有 bison 且版本在 3.0 以上可以跳过这一步。但考虑到内网环境千奇百怪直接自己编一份最稳。3.3 编译 flex避免“neither flex nor lex found”flex 是 libpcap 编译过程中最容易出问题的点。libpcap 的 configure 脚本会做这样的检查checking for flex... no checking for flex... no checking for lex... no configure: error: Your operating systems lex is insufficient to compile libpcap这个报错的意思就是系统里没有 flex也没有传统的 lex。有些老教程让你去装 lex 兼容包但在 CentOS 7 上最直接的解决办法就是编译 flexcd /opt/src tar xzf flex-2.6.4.tar.gz cd flex-2.6.4 ./configure --prefix/usr/local make -j$(nproc) make installflex 2.6.4 在 CentOS 7 的 GCC 4.8.5 下编译没有太大问题偶尔会有几个 warning不影响生成可执行文件。编译完成后验证一下/usr/local/bin/flex --version如果输出的是flex 2.6.4说明这一步过了。3.4 编译 libpcapconfigure 参数与可选依赖处理工具链备齐后终于可以进入正题编译 libpcapcd /opt/src tar xzf libpcap-1.9.1.tar.gz cd libpcap-1.9.1 ./configure --prefix/usr/local --disable-dbus --without-libnl make -j$(nproc) make install这里的两个参数说一下--disable-dbuslibpcap 的蓝牙监控功能依赖 D-Bus内网服务器上通常没有dbus-devel不显式禁用的话configure 可能会自动检测并尝试链接导致编译环境不一致。--without-libnllibnl 主要用于 Wi-Fi 接口和某些 netlink 功能普通以太网抓包用不到。如果系统里没有 libnl 的开发库不指定这个参数configure 也会自动禁用但显式写上能减少config.log里的干扰信息。configure 结束后建议看一眼输出摘要里面会列出各个可选组件的启用情况libpcap version 1.9.1 ... libnl: no dbus: no只要 grep 到libnl: no、dbus: no就说明这些可选依赖已经被禁用不会影响编译。如果某些选项显示yes说明系统里有对应的库这也是正常的。编译完成后默认安装到/usr/local/lib和/usr/local/include。接下来要让动态链接器找到这个库echo /usr/local/lib /etc/ld.so.conf.d/libpcap-local.conf ldconfig这一步非常关键。如果没有这段配置后面运行依赖 libpcap 的程序时大概率会报libpcap.so.1: cannot open shared object file。3.5 安装后的验证tcpdump 版本与 C 程序测试装完别急着走先做一轮验证。如果系统里已经带了 tcpdump可以用它来确认链接的是新版 libpcapldd /usr/sbin/tcpdump | grep pcap如果显示/usr/local/lib/libpcap.so.1说明系统 tcpdump 已经用上了新版库。但更稳妥的做法是写一个简单的 C 程序直接调用 libpcap 的 API确认接口可用。下面这个程序会枚举本机所有网卡设备并打印名字#include stdio.h #include pcap.h int main(void) { char errbuf[PCAP_ERRBUF_SIZE]; pcap_if_t *alldevs NULL; if (pcap_findalldevs(alldevs, errbuf) -1) { fprintf(stderr, pcap_findalldevs error: %s\n, errbuf); return 1; } for (pcap_if_t *d alldevs; d ! NULL; d d-next) { printf(%s\n, d-name); } pcap_freealldevs(alldevs); return 0; }编译命令gcc -o pcap_test pcap_test.c -I/usr/local/include -L/usr/local/lib -lpcap运行之前假设不在/etc/ld.so.conf.d里配置过你需要指定库路径export LD_LIBRARY_PATH/usr/local/lib:$LD_LIBRARY_PATH ./pcap_test如果能看到eth0或ens192之类的网卡名说明 libpcap 已经可以正常使用。4. 编译链接与运行期的典型报错排查4.1 编译期找不到 pcap.h 和 -lpcap常见报错有两种pcap.h: No such file or directory或者/usr/bin/ld: cannot find -lpcap第一种是头文件路径没指定第二种是库文件路径没指定。原因很简单——libpcap 源码编译默认装到/usr/local/include和/usr/local/lib而 GCC 默认搜索的路径是/usr/include和/usr/lib64不会去/usr/local里找。解决方式就是编译命令里显式加上gcc -o my_capture my_capture.c -I/usr/local/include -L/usr/local/lib -lpcap如果你想用得省心一点可以设置两个环境变量export CFLAGS-I/usr/local/include export LDFLAGS-L/usr/local/lib然后编译时直接gcc -o my_capture my_capture.c -lpcapGCC 会从环境变量里读取额外的搜索路径。4.2 运行期 libpcap.so.1 无法加载编译链接都过了但运行时报./my_capture: error while loading shared libraries: libpcap.so.1: cannot open shared object file: No such file or directory这说明链接阶段没问题但程序运行时动态链接器找不到libpcap.so.1。Linux 的动态链接器默认搜索的目录基本是/usr/lib64、/usr/lib再加上/etc/ld.so.conf.d/里配置的目录。源码编译安装的/usr/local/lib并不在默认搜索范围内。解决办法有两种第一种临时设置环境变量export LD_LIBRARY_PATH/usr/local/lib:$LD_LIBRARY_PATH ./my_capture这种方式只对当前 shell 生效适合快速验证。第二种写入动态链接器配置echo /usr/local/lib /etc/ld.so.conf.d/libpcap-local.conf ldconfig这种是全局生效的写完后任何用户执行程序都能找到这个库。注意ldconfig是必须要执行的光写配置文件不重新生成缓存系统不会立刻生效。如果程序是通过 systemd 托管的服务还要在 service 文件里加EnvironmentLD_LIBRARY_PATH/usr/local/lib或者使用RuntimeDirectory相关的配置但更简单的还是用 ld.so.conf.d 方案。4.3 pkg-config 找到旧版 .pc 文件有些程序的构建系统会用 pkg-config 来查找 libpcap如果执行pkg-config --modversion libpcap输出的不是 1.9.1那就说明 pkg-config 找到的是系统自带的旧版.pc文件。CentOS 7 的 libpcap-devel 会安装/usr/lib64/pkgconfig/libpcap.pc里面写的是 1.5.3 的版本信息和路径。解决方法是指定 PKG_CONFIG_PATHexport PKG_CONFIG_PATH/usr/local/lib/pkgconfig pkg-config --modversion libpcap看到1.9.1就对了。如果希望以后的路程都指向新版本可以把这行写进/etc/profile.d/libpcap.sh。4.4 链接到旧库的隐性坑最隐蔽的问题不是找不到库而是同时存在两个libpcap.so.1程序链接到了新版但运行加载的却是系统旧版。你编译时明明链接的是/usr/local/lib/libpcap.so可是程序运行后ldd ./my_capture显示/usr/lib64/libpcap.so.1用的还是旧库。原因在于动态链接器加载共享库时是根据 SONAME 来找的两个库的 SONAME 都是libpcap.so.1所以运行时到底加载哪一个取决于动态链接器的搜索路径顺序。这时候LD_LIBRARY_PATH或ld.so.conf.d里/usr/local/lib的优先级就变得非常关键。我建议在/usr/local/lib存在的情况下把LD_LIBRARY_PATH设置好或者直接在编译时写死 rpathgcc -o my_capture my_capture.c -I/usr/local/include -L/usr/local/lib -Wl,-rpath,/usr/local/lib -lpcap-Wl,-rpath,/usr/local/lib会把/usr/local/lib写进可执行文件的 RUNPATH 中运行时动态链接器会优先去这个目录找不受环境变量影响。这在部署到很多节点时特别有用不会因为个别节点的环境变量配置遗漏出问题。5. 和系统 RPM 版 libpcap 的冲突与权限处理5.1 两个 libpcap.so.1 并存时动态链接器怎么选如果你之前用 RPM 装过 libpcap源码编译又装了一份两者都是libpcap.so.1就出现了同名库共存的问题。RPM 版的动态库在/usr/lib64/libpcap.so.1源码版在/usr/local/lib/libpcap.so.1。动态链接器的搜索顺序大致是LD_LIBRARY_PATH环境变量指定的目录可执行文件里 RUNPATH 指定的目录如果有-rpath/etc/ld.so.conf.d/中配置的目录默认目录/lib64、/usr/lib64。所以如果你设置了LD_LIBRARY_PATH/usr/local/lib那么运行时会优先用新版如果没有设置即使编译时链接的新版运行也可能会加载旧版导致某些新接口调用失败。我的建议是两条路二选一要么把系统 RPM 版卸载干净只保留源码版要么不要卸载但所有需要新版接口的程序都通过-Wl,-rpath,/usr/local/lib编译并且用ldd检查最终运行加载路径。生产环境不建议随意卸载系统自带的 libpcap因为tcpdump、yum等工具可能依赖它卸载后可能引发连锁问题。5.2 静态链接 libpcap.a一劳永逸绕过运行时路径问题如果你要分发的程序必须在多台内网机器上运行每台机器去配LD_LIBRARY_PATH或者改 ld.so.conf 实在太麻烦那可以考虑静态链接 libpcap把库直接编进可执行文件里。libpcap 源码编译后会在/usr/local/lib下生成libpcap.a静态库你可以这样编译gcc -o my_capture my_capture.c -I/usr/local/include /usr/local/lib/libpcap.a -lpthread注意要显式传递静态库路径或者用gcc -o my_capture my_capture.c -I/usr/local/include -L/usr/local/lib -Wl,-Bstatic -lpcap -Wl,-Bdynamic这种方式编译出来的程序不再依赖libpcap.so.1运行时不关心目标机器上有没有装 libpcap非常适合在大量内网服务器上批量分发。缺点是二进制体积会变大并且如果 libpcap 有安全更新你需要重新编译程序才能应用新版本。对于流量采集这种对版本敏感的场景我认为静态链接利大于弊。5.3 非 root 用户的抓包权限setcap 怎么设置这是另一个高频问题。程序编译好了用 root 跑没问题但切到普通用户就开始报权限错误比如tcpdump: raw socket: Operation not permitted原因是 libpcap 抓包时通过AF_PACKET协议族创建套接字这个操作需要CAP_NET_RAW权限普通用户默认没有。解决办法不是给用户 sudo 权限而是用 setcap 给可执行文件单独授权sudo setcap cap_net_raw,cap_net_admineip /usr/local/bin/my_capture这里cap_net_raw是抓包必需的能力cap_net_admin是设置网卡混杂模式需要的如果程序会调pcap_set_promisc。eip分别表示 effective、inheritable、permittedeip三个标志位可以简写成ep但为了保险我一般写eip。验证一下getcap /usr/local/bin/my_capture输出如果是/usr/local/bin/my_capture cap_net_admin,cap_net_rawep说明授权成功普通用户可以直接运行这个程序抓包。注意一个坑setcap 之后的文件不能再有写权限否则内核会丢弃 capabilities。所以不要在编译目录里对my_capture做 setcap然后又在同一个目录下重新 make那样 capabilities 会被清掉。正确做法是先把二进制拷贝到安装目录再执行 setcap。如果是在容器里部署还需要在 docker run 时加上--cap-addNET_RAW --cap-addNET_ADMIN或者在 docker-compose 里配置cap_add否则容器内的进程同样没有权限创建AF_PACKET套接字。最后分享一个小经验我在内网机器的/opt/src目录下把 m4、bison、flex、libpcap 四个源码包连同安装日志、configure 的 config.log 都保存在那里下次再需要离线编译别的 C 库时工具链已经是现成的直接复用即可。建议你也把这次用到的所有源码包备份到一个单独的目录别随手删了内网环境重新下载一次源码包的成本远比你想象的高。