bigywater.tech
网络协议 · 103

Syslog:UDP 日志协议的格式与可靠性问题

Syslog 是最古老也最广泛的日志协议:一个 数字、一行文字、一个 UDP 包。它简单到随手一写就能发,也简单到出了问题悄无声息。这篇把格式、优先级的算法、UDP 丢多少、接收端不够快丢多少、以及日志怎么被伪造,逐个在本机量一遍。

PRI = 设施 × 8 + 级别
<36> 是 auth(4)× 8 + warning(4)
28.4%
30% 丢包下 UDP syslog 实际丢失的比例(TCP 为 0)
79.7%
突发 20000 条时默认缓冲区的接收端丢失的比例

PRI 的算法

实测 · 本机执行输出
    原始报文(线上字节)                                           PRI  设施(PRI÷8)       级别(PRI mod 8)
    b'<36>w103app[30057]: sample warning message\x00'             36  auth      ( 4)   warning (4)
    b'<30>w103app[30057]: sample info message\x00'                30  daemon    ( 3)   info    (6)
    b'<131>w103app[30057]: sample error message\x00'             131  local0    (16)   err     (3)
    b'<23>w103app[30057]: sample debug message\x00'               23  mail      ( 2)   debug   (7)
  PRI = 设施 × 8 + 级别:例如 auth(4)+ warning(4)→ 4 × 8 + 4 = 36。注意这个传统格式里**没有时间戳和主机名**(Python 的 SysLogHandler 不写,由接收端补)——接收端只能用“收到的时间”和“发送方 IP”,发送方的时间与身份无法验证

PRI = 设施 × 8 + 级别,<36> 是 auth(4)× 8 + warning(4);传统格式没有时间戳和主机名。

RFC 5424

实测 · 本机执行输出
  原文:<165>1 2026-10-15T12:34:56.789Z host1.example.net w103app 4321 ID47 [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"] An application event log entry
  解析:PRI 165 = 设施 local4(20)× 8 + 级别 notice(5);版本 1;时间戳 2026-10-15T12:34:56.789Z(ISO 8601,带时区和毫秒);主机名 host1.example.net;应用名 w103app;进程号 4321;消息 ID ID47;结构化数据 [exampleSDID@32473 iut="3" eventSource="…;消息 'An application event log entry'
  5424 把时间戳、主机名、应用名放进了格式本身,并增加了结构化数据([ID k="v" …]),比 3164 更容易被机器解析

时间戳、主机名、应用名和结构化数据都放进了格式本身。

UDP 的丢失

实测 · 本机执行输出
  丢包  0%:UDP 发 500 条,接收端收到 500 条(丢了 0 条,0.0%);TCP 发 500 条,接收端收到 500 条(丢了 0 条)
  丢包 10%:UDP 发 500 条,接收端收到 456 条(丢了 44 条,8.8%);TCP 发 500 条,接收端收到 500 条(丢了 0 条)
  丢包 30%:UDP 发 500 条,接收端收到 358 条(丢了 142 条,28.4%);TCP 发 500 条,接收端收到 500 条(丢了 0 条)
  UDP 丢的包发送端完全不知道:syslog 没有确认机制,出问题时恰恰是网络不稳、日志最重要的时候

10% 丢包下 UDP 丢 8.8%,30% 丢 28.4%;TCP 不丢。

接收端溢出

实测 · 本机执行输出
  默认缓冲区:发送端 0.14 秒发完 20000 条;接收端处理完收到 4064 条,丢了 15936 条(79.7%);发送端不知道丢了
  SO_RCVBUF = 8 KiB:发送端 0.17 秒发完 20000 条;接收端处理完收到 262 条,丢了 19738 条(98.7%);发送端不知道丢了

突发 20000 条时默认缓冲区的接收端丢 79.7%,8 KiB 缓冲区丢 98.7%,发送端不知道。

没有认证

实测 · 本机执行输出
  接收端收到:b'<38>Oct 15 12:00:00 victim-host sshd[999]: Accepted password for root from 203.0.113.9 port 51234 ssh2'——这条日志完全是我伪造的(没有任何 sshd,主机名 victim-host 也是我写的)。收到后只能看到来源 IP(UDP 也能伪造源地址),内容的真实性无法验证;这就是为什么审计日志需要 TLS 传输 + 签名,或者放在可信的本机再转发

任何人可以伪造一条日志,接收端无法验证内容和来源。

分步演示

Syslog
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。全部在本机 lo0:发送端是 Python 的 logging.handlers.SysLogHandler(它会在每条报文末尾加 NUL 字节、不带时间戳和主机名);接收端是我写的 UDP / TCP 套接字;第四节接收端每条 sleep 0.5 ms,模拟处理慢;第五节只是向我自己的套接字发一个字符串
    • 没有验证:真实 syslog 守护进程(rsyslog、syslog-ng、macOS 的 unified logging)的行为;RFC 5425 的 TLS syslog;RELP 等带确认的传输;日志长度限制(1024 字节,各实现不同);macOS 本机日志通过 /var/run/syslog 域套接字而不是网络
    京ICP备2026067502号-1京公网安备11010502063487号