在处理来自各种监控应用(如 Zeek 和 Suricata)的流数据时,通常希望 能够快速地从一份数据集切换到另一份。虽然所需的流元组信息通常都存 在于数据集中,但这种“关联(join)”的细节可能十分繁琐,尤其是在 边界情况下。本规范描述了“Community ID”流哈希,它将表示给定网络 流的字符串标识符的生成过程标准化,从而把这种切换简化为简单的字符 串比较。
function community_id_v1(ipaddr saddr, ipaddr daddr, port sport, port dport, int proto, int seed=0)
{
# Get seed and all tuple parts into network byte order
seed = pack_to_nbo(seed); # 2 bytes
saddr = pack_to_nbo(saddr); # 4 or 16 bytes
daddr = pack_to_nbo(daddr); # 4 or 16 bytes
sport = pack_to_nbo(sport); # 2 bytes
dport = pack_to_nbo(dport); # 2 bytes
# Abstract away directionality: flip the endpoints as needed
# so the smaller IP:port tuple comes first.
saddr, daddr, sport, dport = order_endpoints(saddr, daddr, sport, dport);
# Produce 20-byte SHA1 digest. "." means concatenation. The
# proto value is one byte in length and followed by a 0 byte
# for padding.
sha1_digest = sha1(seed . saddr . daddr . proto . 0 . sport . dport)
# Prepend version string to base64 rendering of the digest.
# v1 is currently the only one available.
return "1:" + base64(sha1_digest)
}
function community_id_icmp(ipaddr saddr, ipaddr daddr, int type, int code, int seed=0)
{
port sport, dport;
# ICMP / ICMPv6 endpoint mapping directly inspired by Zeek
sport, dport = map_icmp_to_ports(type, code);
# ICMP is IP protocol 1, ICMPv6 would be 58
return community_id_v1(saddr, daddr, sport, dport, 1, seed);
}
Community ID 是一个附加的流标识符,不需要取代监控器已支持的现有 流识别机制。不过,如果愿意,也可以将监控器配置为仅记录 Community ID。
Community ID 可以在监控器生成流时计算,也可以在后续阶段添加到 已有的流记录中,前提是这些记录包含所有必需的流端点信息。
Community ID 中的碰撞虽然不可取,但并不被视为致命问题,因为用户 仍应拥有流时间信息,以及可能的监控器原生 ID 机制(希望比 Community ID 更强)来进行消歧。
哈希机制使用种子(seeding),以便对 Community ID 使用的“域”进行 额外控制。种子默认为 0,因此该机制不会造成干扰,也不影响不关心 它的运维人员的操作。
在 ID 的版本 1 中,哈希算法为 SHA1。未来的哈希版本可能会更换算法 或允许额外配置。
与通常基于 ASCII 的 SHA1 表示相比,20 字节的二进制 SHA1 结果将 进行 base64 编码,以减少输出体积。这假设主要关注的是空间而非计 算时间,并可能在以后的版本中变为可配置项。
生成的流 ID 包含一个版本号,以明确底层的 Community ID 实现。这 使用户能够确保他们是在进行同类比较,同时为算法未来的变更提供支 持。例如,当一台监控器的 ID 版本包含 VLAN ID 而另一台不包含时, 哈希值比较应当可靠地失败。该功能更复杂的形式可以在实现版本之外, 还允许捕获配置设置。
版本方案目前只是简单地在哈希值前加上“:”前缀,在当前版 本 1 中会得到类似下面的结果:
1:hO+sN4H+MG5MY/8hIrXPqc4ZQz0=
哈希输入按 32 位边界对齐。流元组组件使用网络字节序(大端序), 以标准化排序,与主机硬件无关。
哈希输入经过排序以消除流元组中的方向性:如有必要,交换端点,使 数值较小的 IP:port 元组排在最前。如果 IP 地址相同,则由端口决定。 例如,以下 NetFlow 5 元组会生成相同的 Community ID 哈希,因为它 们都会被排序为序列 10.0.0.1、127.0.0.1、1234、80。
本版本包含以下协议和字段:
TCP / UDP / SCTP:
IP src / IP dst / IP proto / 源端口 / 目标端口
ICMPv4 / ICMPv6:
IP src / IP dst / IP proto / ICMP 类型 + “counter-type” 或 code
ICMP 类型与 code 的具体处理方法取自 Zeek; 参见以下实现:
其他基于 IP 的协议:
IP src / IP dst / IP proto
以上内容目前未涵盖如何处理嵌套(IP in IP、v6 over v4 等)以及 VLAN 和 MPLS 等封装。
如果网络监控器不支持上述任何协议组合,它可以安全地为流 ID 报告 空字符串(或其他不冲突的值)。
请将 v1 视为原型。我们 感谢来自社区的反馈,尤其是该 ID 的 实现者和运维用户。请直接在 GitHub 项目 中提交 issue,或联 系 Christian Kreibich()。
完整的实现可在 pycommunityid 包中获得。它包含一系列测试,用于验证各种协议的正确计算。我们推荐 使用它来指导新的实现。
本仓库中的 community-id.py 脚本提供了一个更小巧的实现,包括哈希值 的字节布局(参见 packet_get_comm_id())。请参阅 --help 和 make.sh 以开始使用:
$ ./community-id.py --help
usage: community-id.py [-h] [--seed NUM] PCAP [PCAP ...]
Community flow ID reference
positional arguments:
PCAP PCAP packet capture files
optional arguments:
-h, --help show this help message and exit
--seed NUM Seed value for hash operations
--no-base64 Don't base64-encode the SHA1 binary value
--verbose Show verbose output on stderr
为便于故障排查,该实现支持省略 base64 操作,并可提供有关进入 SHA1 哈希计算的确切字节序列的更多详细信息。
本仓库中的 baseline 目录包含数据集,可帮助您验证您的
Community ID 实现是否正常工作。
欢迎通过 GitHub 在此讨论 Community ID 的各个方面: https://github.com/corelight/community-id-spec/issues
非常感谢 Victor Julien、Johanna Amann 和 Robin Sommer 提供的有益 讨论和反馈,也感谢所有实现者和支持者。