ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Suricata 开发指南:从 GIT 构建、编码规范到 C/Rust 单元测试与模糊测试全流程

Suricata 开发指南:从 GIT 构建、编码规范到 C/Rust 单元测试与模糊测试全流程 网络安全【免费下载链接】suricataSuricata is a network Intrusion Detection System, Intrusion Prevention System and Network Security Monitoring engine developed by the OISF and the Suricata community.项目地址https://gitcode.com/gh_mirrors/su/suricata点击查看免费下载导读本文是 Suricata 开发者手册 Working with the Codebase代码库协作章节的系统性解读覆盖六个核心主题从 GIT 源码构建最新版、C 代码强制编码规范clang-format 工作流、C 与 Rust 两套单元测试体系、模糊测试fuzz testing以及测试输入的生成方法。读者将掌握一套从拉取源码、编译安装到按规范提交补丁、编写并运行单元测试的完整开发闭环并了解每个环节背后对应的仓库文件与命令行用法可直接用于 Suricata 的二次开发与贡献。章节地图Codebase 开发指南包含什么doc/userguide/devguide/codebase/index.rst是该指南的索引页它通过 Sphinx toctree 组织以下六篇文档installation-from-git.rst在 Ubuntu 上从 GIT 拉取并构建最新代码code-style.rstSuricata C/Rust 编码规范与 clang-format 使用流程fuzz-testing.rst启用并运行模糊测试目标testing.rst测试总览单元测试、Suricata-Verify、静态/动态分析、CI与测试输入生成unittests-c.rstC 单元测试的编写与运行unittests-rust.rstRust 单元测试的编写与运行。这六篇文档共同定义了 Suricata 社区如何开发的基线代码必须符合严格风格、每个改动应配有对应语言的单元测试、复杂的协议行为用 Suricata-Verify 验证、持续集成与 OSS-Fuzz 持续兜底。从 GIT 安装最新代码Ubuntu 22.04installation-from-git.rst以 Ubuntu 22.04 为基准环境文档注明这些指令已在 Ubuntu 22.04 上测试通过说明了完整流程其他操作系统流程基本相同只需把sudo、apt-get替换为对应发行版的命令。预安装依赖构建前先安装编译工具链、库与 Rust 工具链sudo apt-get -y install libpcre2-dev build-essential autoconf \ automake libtool libpcap-dev libnet1-dev libyaml-0-2 libyaml-dev \ pkg-config zlib1g zlib1g-dev libcap-ng-dev libcap-ng0 make \ libmagic-dev libjansson-dev rustc cargo jq git-core然后将 Cargo 的二进制目录加入 PATH 并安装 cbindgenRust FFI 绑定生成器Suricata 构建脚本依赖它生成src/flow-bindgen.h、src/output-eve-bindgen.h等绑定头文件export PATH$PATH:${HOME}/.cargo/bin cargo install --force cbindgen首次安装 cbindgen 可能需要较长时间。若需要以 IPS入侵防御模式运行额外安装 netfilter 队列库sudo apt-get -y install libnetfilter-queue-dev libnetfilter-queue1 \ libnfnetlink-dev libnfnetlink0克隆仓库并生成构建系统mkdir suricata # 目录名可自取如 oisf cd suricata git clone https://github.com/OISF/suricata.git cd suricata注意Suricata-update 并不随主仓库捆绑需要单独获取./scripts/bundle.sh接着运行 autogen.sh用 autoconf/automake/libtool 生成 configure 脚本再依次 configure、编译、安装./autogen.sh ./configure make sudo make install sudo ldconfig一键自动配置install-conf / install-rules / install-full文档提供了三种 auto-setup 组合免去手动建目录、写suricata.yaml、下载规则的繁琐步骤./configure make sudo make install-confmake install-conf会执行常规make install然后自动创建运行所需的全部目录并生成suricata.yaml。./configure make make install-rulesmake install-rules会执行常规安装并自动下载、配置来自 Emerging ThreatsET的最新规则集。./configure make make install-fullmake install-full是前两者的合体安装 配置 规则一次完成交付一个开箱即跑的 Suricata。安装完成后请继续参考 doc/userguide 下的 Basic Setup 文档完成运行配置。更新本地代码库若已克隆过仓库拉取最新代码后必须重新运行 autogencd suricata/suricata git pull ./autogen.sh重新生成 configure 是为了把新增的 m4 宏、Makefile 规则等同步进构建系统。C 编码规范clang-format 驱动的严格风格Suricata 采用相当严格的 C 编码风格code-style.rst并以仓库根目录的 .clang-format 配置要求 clang 9 及以上当前 CI 使用 clang-format-14 校验格式强制落地。仓库同时提供了封装脚本 scripts/clang-format.sh屏蔽不同版本 clang-format 的差异。clang-format 工作流格式化你的改动打开 PR 前应先格式化自己的改动。git-clang-format只格式化你改动的代码而非整个文件。只格式化最近一次提交$ git clang-format HEAD^ # 或用封装脚本 $ scripts/clang-format.sh commit如果改动是琐碎的格式化修正直接并入上一个提交$ git commit --amend -a较大的格式化调整应单独成 commit不要与逻辑改动混在一起。格式化已暂存git add过的代码$ git clang-format # 或用脚本 $ scripts/clang-format.sh cached连未暂存的改动一起处理$ git clang-format --force # 或用脚本 $ scripts/clang-format.sh cached --force批量修复分支上所有 commit 的格式会按原有 commit 元数据重写历史建议先复制分支再操作$ scripts/clang-format.sh rewrite-branch只格式化分支上各 commit 的改动注意用first_commit_on_your_branch^而非main避免把 main 上新提交也卷进来$ git clang-format first_commit_on_your_branch^ # 或用脚本 $ scripts/clang-format.sh branch检查分支改动格式是否合规$ scripts/clang-format.sh check-branch可加--diffstat查看需要格式化的文件列表或加--diff查看格式化差异。注意不要默认对整个文件跑 clang-format。若确有必要例如历史遗留的非规范代码clang-format -i {file}产生的纯格式改动必须单独成 commit严禁与功能改动混在一起。某些场景宏、多维数组、结构体初始化、手工精心排版处可以局部关闭 clang-format/* clang-format off */ #define APP_LAYER_INCOMPLETE(c, n) (AppLayerResult){1, (c), (n)} /* clang-format on */clang-format 与 git-clang-format 的安装Ubuntu 24.04 只需sudo apt-get install clang-format-14Fedora 执行sudo dnf install clang git-clang-format。格式化与排版规则行宽限制 100 字符。换行时从上一行缩进至少 8 个空格并尽量只换行最少的内容对应 clang-formatColumnLimit: 100、ContinuationIndentWidth: 8、ReflowComments: true。缩进统一 4 空格。函数参数、循环、if 语句换行用 8 空格变量定义换行用 4 空格对应IndentWidth: 4、UseTab: Never、AlignAfterOpenBracket: DontAlign。花括号函数左花括号另起新行控制/循环语句左花括号留在同一行else采用 cuddled 风格与右花括号同行struct/union/enum 左花括号在同一行int SomeFunction(void) { DoSomething(); } if (unlikely(len ETHERNET_HEADER_LEN)) { ENGINE_SET_INVALID_EVENT(p, ETHERNET_PKT_TOO_SMALL); return TM_ECODE_FAILED; } if (this) { DoThis(); } else { DoThat(); } struct { uint8_t type; uint8_t code; } icmp_s;控制流禁止条件与语句写在同一行if (a) b a;是反例短函数、空函数、短 struct 不得压缩成一行避免无谓分支例如if (error) { goto error; } else { a b; }应简写为if (error) { goto error; } a b;。指针对齐指针符号右对齐void *ptr;、void f(int *a, const char *b);对应PointerAlignment: Right。注释对齐连续行的行尾注释应对齐对应AlignTrailingComments: true。宏宏名ALL_CAPS_WITH_UNDERSCORES宏体内每次使用参数都要加括号连续行的宏值对齐多行宏的续行符\右对齐到列宽上限#define ACTION_ALERT 0x01 #define ACTION_DROP 0x02 #define ACTION_REJECT 0x04 #define MULTILINE_DEF(a, b) \ if ((a) 2) { \ auto temp (b) / 2; \ (b) 10; \ someFunctionCall((a), (b)); \ }命名、注释与文件组织函数命名SCNamedLikeThis()所有非 static 函数必须以SC前缀开头能声明为 static 的函数尽量 staticinline 仅用于关键路径热路径性能优化。变量命名全小写下划线named_like_this如SCConfNode *parent_node root;循环变量i应为有符号 int。宏与枚举枚举值ALL_CAPS_WITH_UNDERSCORES且使用公共前缀每个值独占一行给最后一项加尾逗号可强制 clang-format 保持每行一个值暴露在头文件中的枚举以SC_为前缀// 正确写法 enum { VALUE_ONE, VALUE_TWO, // - 尾逗号强制每行一个 };结构体与 typedef使用TitleCase命名暴露在头文件中时加SC前缀如typedef struct SCPlugin_ { ... } SCPlugin;。函数注释使用 Doxygen 记号\brief、\param、\retval标注齐全/** * \brief Helper function to get a node, creating it if it does not * exist. * * \param name The name of the configuration node to get. * \param final Flag to set created nodes as final or not. * * \retval The existing configuration node if it exists, or a newly * created node for the provided name. On error, NULL will be returned. */ static SCConfNode *SCConfGetNodeOrCreate(char *name, int final)普通注释优先/* foobar */风格尽量避免//。文件名全小写.c/.h/.rs后缀通常带子系统前缀如detect-dsize.c、util-ip.c多层前缀如util-mpm-ac.c。switch 语句case相对switch缩进贯穿fall through的 case 用/* fall through */注释说明case 标签不与语句同行case 后如需声明变量左花括号与 case 同行switch (ntohs(p-ethh-eth_type)) { case ETHERNET_TYPE_IP: DecodeIPV4(tv, dtv, p, pkt ETHERNET_HEADER_LEN, len - ETHERNET_HEADER_LEN, pq); break; case 13: { int a bla(); break; } }goto仅用于错误处理等场景标签与花括号同级缩进static DetectFileextData *DetectFileextParse (char *str) { DetectFileextData *fileext NULL; fileext SCMalloc(sizeof(DetectFileextData)); if (unlikely(fileext NULL)) goto error; memset(fileext, 0x00, sizeof(DetectFileextData)); if (DetectContentDataParse(fileext, str, fileext-ext, fileext-len, fileext-flags) -1) { goto error; } return fileext; error: if (fileext ! NULL) DetectFileextFree(fileext); return NULL; }includes.c文件应先包含自身同名头文件或紧跟suricata-common.h之后包含。单元测试数据注释测试中若使用包含协议报文的字节数组务必添加可读内容注释例如/* 220 mx.google.com ESMTP d15sm986283wfl.6CRLF */而不是只给一行十六进制。禁用函数Banned Functions为保证可移植性与安全性以下函数被禁止使用并给出替代被禁函数替代原因strtokstrtok_r线程安全sprintfsnprintf不安全strcatstrlcat不安全strcpystrlcpy不安全strncpystrlcat—strncatstrlcpy—strndup—操作系统相关strchrnul——rand / rand_r——index / rindex——bzeromemset—编写新代码时还应对照既有实现例如 src/decode-ethernet.c如果风格差别悬殊多半是写错了。Rust 代码风格纯 Rust 代码遵循常规 Rust 风格rustfmt/cargo fmt格式化若重排既有文件先单独提交格式化再改逻辑此类改动在 PR 中可能被拒。而暴露给 C 的 FFI 代码所有#[no_mangle]函数必须遵循 C 侧命名规范#[no_mangle] pub extern C SCJbNewArray() - *mut JsonBuilder { }单元测试总览与测试输入生成testing.rst将 Suricata 的测试手段划分为五个层次单元测试独立验证某个函数或代码片段C 与 Rust 各有独立的编写/运行方式见下两节Suricata-Verify独立测试项目验证更复杂的行为例如给定输入通常是多个报文组成的 pcap时的日志输出或告警计数适合验证协议日志、特征检测在重构后是否回归静态与动态分析工具如 clang 的 scan-build同时用于格式检查、ASAN内存问题检测模糊测试善于暴露既有且往往不平凡non-trivial的 bug详见 fuzz-testing 一节CI 检查每个提交到公共仓库的 PR 都会运行一系列 CI 工作流覆盖格式与提交检查、模糊测试以及多种构建配置。运行全部单元测试C Rust只需在主目录执行make check单元测试代码示例Rust 侧以 DNS 解析器rust/src/dns/parser.rs 中的dns_parse_name为例构造原始字节输入注意注释标明每个字节段的含义断言解析出的名字与未解析的剩余部分/// Parse a simple name with no pointers. #[test] fn test_dns_parse_name() { let buf: [u8] [ 0x09, 0x63, /* .......c */ 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2d, 0x63, 0x66, /* lient-cf */ 0x07, 0x64, 0x72, 0x6f, 0x70, 0x62, 0x6f, 0x78, /* .dropbox */ 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, /* .com.... */ ]; let expected_remainder: [u8] [0x00, 0x01, 0x00]; let (remainder,name) dns_parse_name(buf, buf).unwrap(); assert_eq!(client-cf.dropbox.com.as_bytes(), name[..]); assert_eq!(remainder, expected_remainder); }C 侧以 src/decode-ethernet.c 中的 DCE 以太网帧过小测试为例用FAIL_IF_*断言引擎正确设置了解码事件/** * Test a DCE ethernet frame that is too small. */ static int DecodeEthernetTestDceTooSmall(void) { uint8_t raw_eth[] { 0x00, 0x10, 0x94, 0x55, 0x00, 0x01, 0x00, 0x10, 0x94, 0x56, 0x00, 0x01, 0x89, 0x03, }; Packet *p PacketGetFromAlloc(); FAIL_IF_NULL(p); ThreadVars tv; DecodeThreadVars dtv; memset(dtv, 0, sizeof(DecodeThreadVars)); memset(tv, 0, sizeof(ThreadVars)); DecodeEthernet(tv, dtv, p, raw_eth, sizeof(raw_eth)); FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, DCE_PKT_TOO_SMALL)); PacketFree(p); PASS; }Suricata-Verify验证端到端行为单元测试难以覆盖完整会话级别的行为Suricata-Verify 正是为此而生无需模拟网络流量和引擎内部机制直接以期望的 pcap 输入、配置和检查项运行 Suricata 即可。它特别适合保证代码重构不影响协议日志或特征检测——这类回归对用户与集成方影响巨大。简单测试只需提供 pcap复杂场景还可附上规则让 Suricata-Verify 匹配告警与特定事件。其测试仓库中的 app-layer-template 等样例是绝佳的起步参照。生成测试输入方法一用真实流量 Wireshark 提取用 Wireshark 打开目标协议的抓包选中作为测试输入的报文使用Follow [TCP/UDP/HTTP/HTTP2/QUIC] Stream或顶部菜单Analyze - Follow - TCP Stream打开流视图选择Show and save data as中的C Arrays并可选择查看整个会话或仅 client / server 方向的报文。Wireshark 会以 C 数组风格的十六进制呈现报文数据该格式同样易于适配 Rust 测试Wireshark 也常用来抓取样例流量并生成 pcap 文件。方法二用 Scapy 构造流量Scapy 适合按需构造特定流量。Suricata-Verify 测试集中有大量由 Scapy 生成的 pcap 样例例如 dcerpc-udp-scapy 测试中的dcerpc_udp_scapy.py脚本。此外其测试 readme 中还收录了 http2-range、http-range、smb2-delete、smtp-rset、http-auth-unrecognized 等一批带生成说明的样例。方法三公开数据集若无法抓取或构造所需协议流量可尝试在公开数据集中寻找Suricata 官方论坛有分享优质抓包来源的讨论帖可供参考。C 单元测试编写、注册与运行单元测试是检查解析器、结构体等内部状态的最佳手段unittests-c.rst。测试应满足使用FAIL/PASS宏、确定性deterministic、PASS时不泄漏内存、不使用条件语句。启用与运行单元测试默认不随 Suricata 编译需在 configure 阶段显式开启./configure --enable-unittests按模块运行例如只跑 flowbits 相关测试suricata -u -U flowbit排查失败测试时可用调试构建辅助./configure --enable-debug SC_LOG_LEVELDebug suricata -uDebug 级别输出非常冗长可用 grep 风格的SC_LOG_OP_FILTER过滤SC_LOG_LEVELDebug SC_LOG_OP_FILTER(something|somethingelse) suricata -u注意日志级别优先级例如选了 Info 级别就不会显示其他级别的消息。编写 C 单元测试C 单元测试是一个无参数、返回 0失败或 1成功的函数实践中不必显式 return而是使用FAIL_*与PASS宏void MyUnitTest(void) { int n 1; void *p NULL; FAIL_IF(n ! 1); FAIL_IF_NOT(n 1); FAIL_IF_NOT_NULL(p); FAIL_IF_NULL(p); PASS; }每个测试必须通过UtRegisterTest()注册第一个参数是测试名第二个是函数指针UtRegisterTest(MyUnitTest, MyUnitTest);已有模块通常自带注册函数新模块可参考结构相近的既有模块来组织注册。文档给出的两个实战范例一是 src/conf-yaml-loader.c 中的ConfYamlOverrideTest验证 YAML 配置中后出现的键覆盖前值some-log-dir最终为/tmp以及父节点被后续定义整体替换后parent.child0不再存在而parent.child1.key存在二是detect-ike-chosen-sa.c中的解析测试展示了#ifdef UNITTESTS包裹测试、DetectIkeChosenSaFree释放资源以及集中注册的模式#ifdef UNITTESTS static int IKEChosenSaParserTest(void) { DetectIkeChosenSaData *de NULL; de DetectIkeChosenSaParse(alg_hash2); FAIL_IF_NULL(de); FAIL_IF(de-sa_value ! 2); FAIL_IF(strcmp(de-sa_type, alg_hash) ! 0); DetectIkeChosenSaFree(NULL, de); PASS; } #endif /* UNITTESTS */ void IKEChosenSaRegisterTests(void) { #ifdef UNITTESTS UtRegisterTest(IKEChosenSaParserTest, IKEChosenSaParserTest); #endif /* UNITTESTS */ }Rust 单元测试cargo test 与 tests 模块Rust 侧测试走 Cargo 内置体系unittests-rust.rst基本命令为cargo test [options][testname][-- test-options]测试某个 Rust 模块例如 http2进入rust目录执行cargo test http2运行 Rust 代码库的全部单元测试cargo test在源码文件中添加测试单元测试应放在被测代码同一文件末尾的mod tests中没有就先建一个测试函数加#[test]属性并use被测模块及其他依赖模块。来自nfs rpc_records.rs的范例mod tests { use crate::nfs::rpc_records::*; use nom::Err::Incomplete; use nom::Needed::Size; #[test] fn test_partial_input_ok() { let buf: [u8] [ 0x80, 0x00, 0x00, 0x9c, // flags 0x8e, 0x28, 0x02, 0x7e, // xid 0x00, 0x00, 0x00, 0x01, // msgtype 0x00, 0x00, 0x00, 0x02, // rpcver 0x00, 0x00, 0x00, 0x03, // program 0x00, 0x00, 0x00, 0x04, // progver 0x00, 0x00, 0x00, 0x05, // procedure ]; let expected RpcRequestPacketPartial { hdr: RpcPacketHeader { frag_is_last: true, frag_len: 156, xid: 2384986750, msgtype: 1 }, rpcver: 2, program: 3, progver: 4, procedure: 5 }; let r parse_rpc_request_partial(buf); match r { Ok((rem, hdr)) { assert_eq!(rem.len(), 0); assert_eq!(hdr, expected); }, _ { panic!(failed {:?},r); } } } }运行指定测试单个测试按模块路径精确定位cargo test module::file_name::tests::test_name其中tests即mod tests。若测试名全局唯一可直接cargo test test_name同样可只测某个模块或子模块cargo test nfs::rpc_records模糊测试启用 fuzz targetsfuzz-testing.rst篇幅精炼但要点明确在 configure 时加入--enable-fuzztargets即可编译 fuzz 目标。./configure --enable-fuzztargets重要警告启用该选项会改变 Suricata 的多个组成部分使suricata二进制不再适合生产环境使用——fuzz 目标本质上是为暴露崩溃与畸形输入而生的测试替身。该配置项在 configure.ac 中定义AC_ARG_ENABLE(fuzztargets, ...)并据此设置BUILD_FUZZTARGETS条件编译configure 输出中也会打印Fuzz targets enabled:状态。编译出的目标可用于 libFuzzer、AFL 及其他模糊测试平台。Suricata 还通过 OSS-Fuzz 项目接受持续的云端模糊测试。仓库中 qa/run-ossfuzz-corpus.sh 提供了运行 OSS-Fuzz 语料库的脚本可作为本地复现与扩展覆盖的起点。文档中的运行模糊器复现问题扩展覆盖新增 fuzz 目标等小节当前标注为 TODO具体细节可查阅src/tests/fuzz目录下的 README。结语把六份文档串成开发闭环从git clone与./autogen.sh的构建起步到.clang-format与scripts/clang-format.sh强制风格一致再到 C 侧UtRegisterTest、Rust 侧mod tests双轨单元测试以及--enable-fuzztargets OSS-Fuzz 的持续兜底——doc/userguide/devguide/codebase章节实际给出了 Suricata 社区贡献代码的完整质量基线。无论你是想修一个解码器 bug、给某个协议解析器补测试还是为引擎增加新配置项都可以按本文顺序先构建出可运行环境再对照编码规范与既有实现如 src/decode-ethernet.c、rust/src/dns/parser.rs动手最后用make check与定向的suricata -u/cargo test验证改动。赞分享网络安全【免费下载链接】suricataSuricata is a network Intrusion Detection System, Intrusion Prevention System and Network Security Monitoring engine developed by the OISF and the Suricata community.项目地址https://gitcode.com/gh_mirrors/su/suricata点击查看免费下载相关推荐CVXPY 贡献指南从源码构建、代码规范、单元测试到基准测试的完整开发流程CVXPY 贡献指南从源码构建、代码规范、单元测试到基准测试的完整开发流程 本文是 CVXPYPython 凸优化建模语言开发者的实战贡献指南完整梳理了科学计算INAV 开发者贡献指南编码规范、单元测试、Git 分支工作流与发布流程全解析INAV 开发者贡献指南编码规范、单元测试、Git 分支工作流与发布流程全解析 本篇技术指南以 INAV 官方开发者文档 docs/development/无人机嵌入式智能硬件Docker Compose 源码构建指南CLI 编译、单元测试、E2E 测试与发布全流程Docker Compose 源码构建指南CLI 编译、单元测试、E2E 测试与发布全流程 本指南以仓库根目录 BUILDING.md https://lin云原生容器编排DevOpsCLI上一篇黑苹果终极实战指南从架构设计到高级优化的完整解决方案下一篇Python驱动CATIA自动化从重复劳动到智能设计的技术革命创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表