简介:minisip-0.7.0源码包是一份基于C++实现的开源SIP用户代理,面向VoIP应用开发者、网络协议学习者及嵌入式通信工程师,用于快速理解SIP会话建立、信令交互与安全通信机制。压缩包共458个文件,核心为160个h头文件与153个cxx源文件,另含构建体系(am、in、ac等)、多份pem测试证书及少量说明文档,整体约822KB,轻量易读。源码以sip_core模块为中心,完整呈现SIP消息的接收、解析、生成与发送,并通过event_loop展示select/epoll驱动的异步事件模型;ua模块中可追踪REGISTER、INVITE、ACK、BYE等呼叫流程的状态迁移,同时给出Digest认证与TLS加密的具体实现。从配置文件解析到插件扩展机制均有清晰编码路径,既便于二次开发,也可作为SIP协议栈课程设计参考,源码注释与模块划分清晰,适合对照协议文档逐段精读。已有249人学习下载,适合具备C++与网络编程基础、希望深入研究VoIP协议的工程师。
1. minisip-0.7.0.tar.gz 是什么:一个能编译、能抓包、能改的 SIP 协议栈源码包
minisip-0.7.0.tar.gz 是老牌开源 SIP 项目在 0.7.0 阶段打出来的完整源码包:里面同时躺着 SIP 协议栈库、SDP 媒体协商、RTP/SRTP 传输,以及一个带图形界面的 SIP 软电话客户端。它和只提供一个 lib 的轻量协议库不同,信令、媒体、界面、测试工具全部摊在源码里,编译产物既能当软电话用,也能把协议栈单独拆出来嵌进别的程序。
适合三类人:想在 Linux 桌面或嵌入式设备里快速接入 SIP 能力的开发者,需要一套最小可运行 SIP 实现配合抓包讲协议栈的测试工程师,以及接手老 VoIP 项目、必须把 0.7.0 这套代码吃透的维护者。这篇文章从解包、编译、注册一路落到排错,最后给出读源码和复用的路径,所有命令都能照敲。
2. 解开 minisip-0.7.0.tar.gz:先把目录骨架和模块分工看清楚
老 tarball 不能上来就./configure,尤其这种 0.x 时代的源码包,目录结构和依赖经常和你的预期差很多。先花五分钟把包里有什么、哪块是哪块搞清楚,后面省出来的时间不止五倍。
2.1 从 tar 包到源码树:一条命令看清单,三条命令落地
先不急着解压,用清单模式看一眼这个包到底装了些什么:
tar -tzf minisip-0.7.0.tar.gz | head -40-t是只列出文件名不落地,-z自动解 gzip,-f指定归档文件。head -40只看前 40 行,重点确认顶层有没有configure.ac、README、INSTALL这些标志性文件。如果包是被第三方重新打包过的,这里就能看出端倪,比如缺了configure.ac只有生成好的configure,那后续改配置就得换条路走。
清单确认没问题再解包:
tar -xzf minisip-0.7.0.tar.gz cd minisip-0.7.0 ls -la du -sh libminisip minisip test play send 2>/dev/null解完先看目录大小,原则很简单:libminisip占大头说明协议栈是主体,minisip目录小说明 GUI 只是薄薄一层壳。这个判断在 0.7.0 里基本成立,GUI 客户端确实只是调用协议栈的展示层。接下来打开README和INSTALL前几十行,确认官方写的依赖和构建方式,别直接信网上二手教程。
依赖清单最权威的来源其实是configure.ac里的PKG_CHECK_MODULES,它会把所有通过 pkg-config 检测的库列出来,比读文档快得多:
grep -n "AC_INIT\|PKG_CHECK_MODULES" configure.ac | head -20AC_INIT告诉你版本号声明在哪,PKG_CHECK_MODULES列出 openssl、glib 这些硬依赖。这一步做扎实,第三章编译时就不会被缺库逼到反复apt install。
2.2 libminisip、minisip、play 与 send:0.7.0 的四块职能
从顶层目录看,职责划分非常清楚:
| 目录 | 编译产物 | 职责 |
|---|---|---|
| libminisip | libminisip.so | SIP 信令(消息、事务、会话)、SDP 解析、RTP/SRTP、音频处理 |
| minisip | minisip 可执行文件 | Qt 图形界面软电话,注册、呼叫、配置入口 |
| play | play 工具 | 把本地音频文件作为 RTP 流发出 |
| send | send 工具 | 发送 RTP 包,用于媒体链路测试 |
| test | 多个测试可执行文件 | 协议栈回归测试 |
为什么这样分层?SIP 项目把信令和媒体分离、UI 和协议栈分离,是那个时代的标准结构。好处直接体现在编译上:libminisip不依赖 Qt,可以单独编成库,GUI 只是它的一个客户端。我一般会先编libminisip和 test,跑通协议栈再考虑要不要碰界面,这样能过滤掉一大半 Qt 相关的编译噪音。
协议栈内部的核心路径是一条直线:SIP 消息对象(请求/响应)→ 事务管理(Transaction)→ 会话(Dialog)→ 注册/呼叫流程,RTP 从媒体协商结果里取端口。0.7.0 的这些类基本都在libminisip/src下,后面的 6.1 节会给出具体定位方式。媒体这块,0.7.0 的音频处理也是插件式,G.711、G.726、iLBC 这类编解码各自独立,读音频相关代码时沿着m=audio行往下追最快。
2.3 0.7.0 的边界:为什么说它是「能跑的参考实现」而不是产品级软电话
先给结论:0.7.0 适合当 SIP 协议的教科书实现,不适合当现代生产软电话。代码量相对小,REGISTER 和 INVITE 的时序完整,digest 鉴权、SDP offer/answer 都能在源码里串起来,这对学习或二次开发非常友好。
边界也很明显。GUI 是 Qt3 时代产物,现代桌面系统直接编译要补兼容层,光 Qt 版本迁移就够折腾一天;NAT 穿透只做了最基础的对称 RTP 处理,复杂局域网环境要自己加 STUN/TURN;单线程事件循环在并发呼叫场景下容易成瓶颈。所以它的定位就是「能跑的参考实现」加「可裁剪的协议栈源码」。
实用价值恰恰在这里:如果只是想把注册状态机抽出来改造成自己的监控终端、对讲模块或测试工具,0.7.0 的改动成本远低于现代全功能协议栈。现代库把所有能力都给你,你要先学会关功能;老代码则只给你够用的东西,加需求反而好下手。
提示:第三方镜像站下载的 tarball,建议先
md5sum minisip-0.7.0.tar.gz再用tar -tvzf核对文件时间戳,避免内容与官方目录不一致导致后续排查方向跑偏。
3. 在本地编译 minisip-0.7.0:依赖、configure 参数与 make 血泪经验
编译老源码包,最核心的心法是:先确认工具链,再跑 autogen,最后 configure 选好开关。顺序反了会反复在同一个坑里横跳。0.7.0 走 autotools 构建,autogen.sh会调用autoreconf生成configure,所以系统里必须有完整的 autotools 工具链。
3.1 前置依赖与工具链:先确认 autotools 和四个库
老项目对工具链版本很敏感。libtool版本太新可能报ltmain.sh不匹配,g++ 太新可能把旧代码里的 deprecated 写法升级成硬错误。与其等报错,不如先扫一遍环境:
which autoreconf automake autoconf libtoolize pkg-config g++ || echo "有缺失项" g++ --version | head -1 pkg-config --exists openssl && echo openssl ok pkg-config --exists glib-2.0 && echo glib okpkg-config --exists只探存在性,不打印版本路径。探不到就说明对应 dev 包没装或不在默认搜索路径,优先装包,不要急着去改 CFLAGS,因为configure.ac里的PKG_CHECK_MODULES会在 configure 阶段直接报错。只有头文件确实装到了/usr/local这类非标准位置时,才需要设置PKG_CONFIG_PATH指向对应的pkgconfig目录。
依赖里最容易出问题的是 openssl 和 glib,第一轮编译报错九成和这俩有关。后续如果需要 Qt GUI,还要装 Qt3 的 dev 包,这个在多数现代发行版里已经很难找了,所以第 3 章的编译命令默认走--without-gui路线。
3.2 编译三步走:autogen、configure、make 的具体命令
第一步生成 configure 和 Makefile.in:
./autogen.sh 2>&1 | tee autogen.logtee同时往终端和文件里写,后面排错不用重新跑一遍。失败就看autogen.log里第一个 error,常见原因是缺libtoolize,或者 autoconf/automake 版本差太大导致某个宏识别不了。老项目常见做法是装一个较老版本的 autotools,或者用发行版自带的版本先试一次,多半能过。
第二步看可配置项,再按需选用:
./configure --help | head -80 ./configure --prefix=$HOME/minisip-0.7.0/build-root \ --disable-video --without-gui 2>&1 | tee configure.log--prefix装到用户目录,不污染系统,卸载直接删目录;--disable-video关掉视频相关代码,少一组依赖;--without-gui跳过 Qt 客户端,这是在没有 Qt3 的环境里最省事的出路。注意 0.7.0 的开关名在不同镜像站之间可能有出入,实际以./configure --help输出为准,有的版本视频开关叫--disable-video,有的叫--with-video=no。
configure 结束后不能只看有没有error:,还要看最后几行有没有checking for... no,老 configure 的退出码不一定可靠。我一般会补两刀:
echo $? tail -20 configure.log两条对上才认为 configure 真成功了。
第三步编译安装:
make -j2 2>&1 | tee make.log make install老 Makefile 对并行编译的依赖追踪不完整,-j4以上容易随机失败,而且失败点每次还不一样,特别像玄学。老老实实-j2,出错了在make.log里搜error:再往上前翻几行看上下文,别只盯着最后一行。
3.3 configure 的三个必调参数与编译兼容处理
| 参数 | 作用 | 什么时候用 |
|---|---|---|
--without-gui | 不编译 Qt 客户端 | 只要协议栈,或 Qt3 装不上 |
--disable-video | 关掉视频支持 | 编译失败在视频相关代码时 |
--prefix | 安装前缀 | 不想污染系统目录时 |
编译老 C++ 源码还有个绕不开的坎:register关键字。0.7.0 代码里大量使用它,C++17 把这个关键字移除了,新 g++ 会把这类代码直接判成编译错误。处理方式很简单,先确认有没有:
grep -rn '\bregister\b' libminisip/src | head有报错就做全局替换,删掉关键字本体,不影响任何变量名:
find libminisip minisip -name '*.cpp' -o -name '*.h' | xargs sed -i 's/\bregister\b//g'替换完从autogen.sh再走一遍,别只重新 make。另一个兼容问题是编译器标准,老代码用隐式转换和旧式写法的地方不少,新 g++ 会报 warning 甚至 error,常见做法是在 configure 前导出兼容参数:
export CXXFLAGS="-std=gnu++98 -O2 -Wno-deprecated-declarations"用gnu++98标准编译老代码,同时忽略 deprecated 告警。CXXFLAGS 要放在 configure 之前设,因为 Makefile 会继承这个值。这一套组合拳打下来,0.7.0 在现代 Linux 上编过三遍以上踩的坑基本就是这个范围。
4. 跑通一次 SIP 注册:用 minisip-0.7.0 对 SIP 服务器完成 REGISTER
编译通过的下一步是跑起来,先做最基础也最有代表性的动作:向 SIP 服务器注册一个分机。注册成功意味着 UDP 通路、SIP 消息解析、digest 鉴权、事务重传这些协议栈核心路径都活过来了。
4.1 最小注册拓扑:软电话、SIP 服务器、抓包工具三件套
最小拓扑只需要三样:一台能响应 REGISTER 的 SIP 服务器、一个编译好的 minisip 软电话、一个抓包工具。服务器常见做法是在局域网里起 Asterisk 或 Kamailio,配一个在 UDP 5060 上监听的分机,具体配置每家不一样,这里不展开。
没有现成服务器也能验证:tcpdump 只抓 minisip 发出去的 REGISTER 也能确认协议栈在干活。minisip 0.7.0 走标准 RFC 3261 流程,服务器选择上不用担心协议兼容问题,随便一个现代 SIP Server 都能对接。
抓包工具用 tcpdump 加 ngrep 组合最顺手,一个管落地保存、一个管实时看内容:
sudo tcpdump -i eth0 -n -s0 port 5060 -w /tmp/sip-register.pcap sudo ngrep -W byline -d eth0 'REGISTER|SIP/2.0' port 5060-n不做 DNS 反查,-s0抓整个包不截断,port 5060只收 SIP 信令流量,媒体 RTP 在另外的端口段,信令包里看不到不影响验证。ngrep -W byline把包内容按行显示,SIP 头字段一眼能读完。
4.2 三种配置入口:GUI 偏好、命令行参数、源码里的注册参数
minisip 0.7.0 的 GUI 在 Preferences 面板里填 SIP 用户名、认证用户名、密码、注册服务器和端口,适合先手动操作跑通基础流程。但如果你按第 3 章用--without-gui编的,就得走命令行或源码改参数。
命令行比较简单,先看帮助再拼参数:
./src/minisip --help 2>&1 | grep -iE 'registrar|credential|user|port' ./src/minisip -u 1001 -p 'secret' -s sip.lab.local:5060-u是分机号,-p是密码,-s是服务器地址带端口。0.7.0 各小版本的实际开关名不完全一致,有的版本长开关是--sip-user这种形式,以--help输出为准,别死记某一条命令。
更稳的入口是源码。注册逻辑在libminisip/src下,定位构造 REGISTER 请求的位置:
grep -rn "REGISTER" libminisip/src | head -20找到后重点看 From、To、Contact、Expires 这四个头字段是怎么被填进去的。改完注册参数重新编译,效果和改配置一样,但能让你顺手理解协议栈的构造流程,后面 6.1 节还要沿着这条路深入。
4.3 抓包验证:从 REGISTER、401 到 200 OK 的状态迁移
SIP digest 注册的关键帧一共四个,抓包里按顺序出现,我截成报文要点展示:
REGISTER sip:lab.local SIP/2.0 Call-ID: abc@192.168.1.10 CSeq: 1 REGISTER Contact: <sip:1001@192.168.1.10:5060> Expires: 3600 SIP/2.0 401 Unauthorized WWW-Authenticate: Digest realm="lab.local", nonce="OjE3..." REGISTER sip:lab.local SIP/2.0 Call-ID: abc@192.168.1.10 CSeq: 2 REGISTER Authorization: Digest username="1001", realm="lab.local", nonce="OjE3...", response="6a8f..." Expires: 3600 SIP/2.0 200 OK整个流程的逻辑是这样:客户端第一次 REGISTER 故意不带 Authorization,等服务器用 401 回一个 challenge,里面带着 realm 和 nonce;拿到后 minisip 计算摘要响应,response = MD5(MD5(user:realm:pass) + nonce + MD5(REGISTER:sip:lab.local)),再发第二条带 Authorization 的 REGISTER;服务器算出来一致才回 200 OK。抓包里还能看到User-Agent: minisip 0.7.0之类的头,可以用来确认对方的协议栈版本。
响应码先说结论:看到 401/407 是正常的,说明鉴权流程走了一半;403 是密码或权限不对;404 是账号不存在;423 是Expires太短,小于服务器设的Min-Expires;408 是事务超时,消息没等来回包。注册成功后 Contact 里的地址和 Expires 会被服务器记录,客户端在到期前要重发 REGISTER 刷新,这个周期就是抓包里 Expires 的值。
验证注册成功还有一条捷径,看服务器侧状态:Asterisk 的sip show peers能看到分机变 Registered,Kamailio 日志里能看到 REGISTER 透传记录。两侧对上,软电话接入就算真正跑通了。
5. minisip-0.7.0 编译与运行避坑:5 个高频翻车点排查
老 SIP 源码的坑不在协议本身,而在工具链、依赖路径和网络环境这三块。下面五条按「现象 → 原因 → 解决」写,照着排查能省掉大半天。
5.1 configure 报错 C++ preprocessor fails sanity check
现象:./configure跑到一半输出configure: error: C++ preprocessor "/lib/cpp" fails sanity check,然后直接退出。
原因:configure 找不到能用的 C++ 预处理器,常见是系统只装了 gcc 没装 g++,或者 g++ 不在 PATH 里。老 configure 对 C++ 工具链的探测逻辑很脆弱,任何一个环节对不上就 abort。
解决:
sudo apt install g++ # 或对应发行版的 g++/gcc-c++ export CXX=g++ ./configure ...设置了CXX环境变量后重新跑 configure,同时把第 3.3 节的CXXFLAGS一起带上,别等第二次再踩编译兼容的坑。
5.2 openssl / glib 头文件找不到:pkg-config 路径与 32/64 位
现象:编译到某个.cpp文件时报fatal error: openssl/ssl.h: No such file or directory,或者 glib 头找不到。
原因:dev 包没装,或者包的 pkg-config 元数据在非默认路径。很多发行版默认只装运行时库,头文件和.pc文件要单独装-dev/-devel包。
解决:
sudo apt install libssl-dev libglib2.0-dev pkg-config --cflags --libs openssl glib-2.0第二条命令打印实际的编译和链接参数,确认 pkg-config 能搜到。如果头文件装到了/usr/local/ssl/include这类位置,把对应的pkgconfig目录加进PKG_CONFIG_PATH再重跑 configure。64 位系统特别注意/usr/lib/x86_64-linux-gnu/pkgconfig这个路径,多个版本共存时.pc文件指错目录也是常见翻车原因。
5.3 注册消息发不出或没回应:端口绑定与 NAT 的锅
现象:minisip 进程起来了,tcpdump 里什么都没抓到,或者只有发出的 REGISTER,服务器的回应一帧都收不到。
原因:一是 UDP 5060 被别的进程占用,minisip 绑定失败但进程没退出,表现就是静默;二是 NAT 环境下服务器把响应发到了公网地址,回不到你本机。
解决:
netstat -ulnp | grep 5060先看端口是否被占,有就把对方杀掉或给 minisip 换端口。NAT 场景最快的收敛办法是把软电话和 SIP 服务器放到同一网段,先排除 NAT 干扰,再谈公网部署。如果必须跨 NAT,重点看服务器侧收到的 Contact 头里的地址是内网还是公网 IP,看到内网地址基本可以确认是 NAT 没正确处理,去 minisip 配置里把对外公布的地址改成公网 IP。
5.4 RTP 通了但没声音:payload type 与编解码协商不一致
现象:信令完全正常,呼叫也建立成功,RTP 包在飞,但双方都听不到声音。
原因:最常见是 SDP 协商里 payload type 对不上,或者一方开着静音抑制,另一方没有按 RFC 3389 处理舒适噪声。这种情况信令看着一切正常,问题全藏在媒体描述里。
解决:
sudo ngrep -W byline -d eth0 'm=audio|a=rtpmap|PCMU|PCMA|telephone-event' port 5060对比两边交换的 SDP:offer 里m=audio行会列出rtpmap:0 PCMU/8000、rtpmap:8 PCMA/8000、rtpmap:101 telephone-event/8000,answer 要回完全相同的 payload 编号才是对齐。minisip 0.7.0 默认走 G.711,如果对端只支持 G.729,就在配置里把本端 codec 列表改成和对方一致,别指望服务器自动转码,老软电话没有这个能力。另外一个低级坑是双方都开了静音检测,以为没人说话,把静音抑制关掉再试。
5.5 make install 后跑不起来:运行时库路径没对上
现象:make install成功,但执行src/minisip报error while loading shared libraries: libminisip.so... cannot open shared object file,而且ls明明看得到这个库文件。
原因:自定义--prefix下的 lib 目录不在系统动态库搜索路径里。运行时加载器不会去/root/minisip/build-root/lib这种地方找库,这是自定义前缀安装最常见的坑。
解决:
export LD_LIBRARY_PATH=$HOME/minisip-0.7.0/build-root/lib:$LD_LIBRARY_PATH ldd src/minisip | grep 'not found'ldd列出哪些库没找到,再export LD_LIBRARY_PATH指过去。每次开终端都手动导太烦,写进~/.bashrc是常见做法。更一劳永逸的方案是编译时加 rpath,configure 之后往 Makefile 的 LDFLAGS 里补-Wl,-rpath,$(libdir),但这只对当前构建树生效,换目录还得重新编。
6. 把 minisip-0.7.0 变成自己的 SIP 工具:读注册状态机与最小复用路径
编译、注册、排错都走通之后,这个包的真正价值才显现:把协议栈拆出来,改成你要的形态。
6.1 从 main 到 REGISTER:跟读 libminisip 的入口
别从 GUI 入手摸入口,直接从协议栈源码找注册构造点。0.7.0 在不同镜像站里的类名可能有差异,先 grep 定位真实的类名再读:
grep -rn "REGISTER" libminisip/src | head -20 grep -rn "class Sip" libminisip/src | head -30第二条命令列出所有Sip开头的类,比对着记忆里的名字找更可靠。找到包含 REGISTER 字符串的类之后,按「事务层 → 会话层 → 传输层」的顺序读:事务层负责 CSeq 匹配和超时重传,会话层维护 From/To/Contact 的 URI,传输层只管把字节扔给 UDP/TCP。要改注册刷新周期,直接搜Expires对应的常量或宏,前后各读五行就能定位。
6.2 验证你改对了:导出符号、最小链接与回归抓包
改动注册逻辑后,第一步确认协议栈库还正常导出符号:
nm -D $HOME/minisip-0.7.0/build-root/lib/libminisip.so | grep -iE 'SipUri|SipMessage|register' | head第二步写一个最小程序只做链接验证,确认库能被外部调用:
#include <string> #include <iostream> // 这里替换为 6.1 节 grep 到的真实类名 // 目标:能创建一条带 Expires 的注册消息并打印 int main() { std::cout << "libminisip link ok" << std::endl; return 0; }真实使用时把输出语句换成调用注册类构造 REGISTER、启动事务,先跑通编译链接,再逐步加抓包断言。最后回归一遍:重跑 4.3 的 tcpdump/ngrep,确认新的 Expires 值或 User-Agent 字段真实出现在线上报文里,抓包结果和预期一致才算改完。
我拿到老源码包的习惯是先确认能编译、再抓一次干净报文当基线,后续任何改动都以这份报文为对比基准。改注册周期也好、改鉴权逻辑也好,只要基线在手,改坏了一对比报文立刻能看出来,这个习惯在维护 0.7.0 这类老代码时救过我很多次。希望帮到你。
本文还有配套的精品资源,点击获取