UDP-Lite 协议 (RFC 3828)

UDP-Lite 是一种标准跟踪 IETF 传输协议,其特点是可变长度的校验和。这对于通过无线网络传输多媒体(视频、VoIP)具有优势,因为部分损坏的数据包仍然可以被送入编解码器,而不是因为校验和测试失败而被丢弃。

本文简要描述了现有的内核支持和套接字 API。有关深入信息,您可以查阅

1. 应用程序

一些应用程序已成功移植到 UDP-Lite。Ethereal(现在称为 wireshark)默认支持 UDP-Litev4/v6。

将应用程序移植到 UDP-Lite 非常简单:只需更改套接字级别和 IPPROTO;发送方还需要设置校验和覆盖长度(默认为头部长度 = 8)。详细信息在下一节中。

2. 编程 API

UDP-Lite 提供无连接的、不可靠的数据报服务,因此使用与 UDP 相同的套接字类型。实际上,从 UDP 移植到 UDP-Lite 非常容易:只需在 socket(2) 调用的最后一个参数中添加 IPPROTO_UDPLITE,使语句看起来像这样

s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDPLITE);

或者,分别地,

s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDPLITE);

只需进行上述更改,您就可以运行 UDP-Lite 服务或连接到 UDP-Lite 服务器。内核将假定您对使用部分校验和覆盖不感兴趣,因此模拟 UDP 模式(完全覆盖)。

要使用部分校验和覆盖功能,需要设置一个套接字选项,该选项采用一个整数来指定覆盖长度

  • 发送方校验和覆盖:UDPLITE_SEND_CSCOV

    例如

    int val = 20;
    setsockopt(s, SOL_UDPLITE, UDPLITE_SEND_CSCOV, &val, sizeof(int));
    

    将校验和覆盖长度设置为 20 个字节(12 字节数据 + 8 字节头部)。每个数据包仅校验前 20 个字节(加上伪头部)。这对于具有 12 字节基本头部的 RTP 应用程序很有用。

  • 接收方校验和覆盖:UDPLITE_RECV_CSCOV

    此选项是接收端的类似物。它实际上是可选的,即不需要启用部分校验和覆盖的流量。它的功能是作为流量过滤器:启用后,它指示内核丢弃所有覆盖范围_小于_此值的数据包。例如,如果要保护 RTP 和 UDP 头部,接收方可以强制只允许最低覆盖范围为 20 的数据包

    int min = 20;
    setsockopt(s, SOL_UDPLITE, UDPLITE_RECV_CSCOV, &min, sizeof(int));
    

对 getsockopt(2) 的调用是类似的。作为扩展而不是独立的协议,所有从 UDP 已知的套接字选项都可以像以前一样以相同的方式使用,例如 UDP_CORK 或 UDP_ENCAP。

有关 UDP-Lite 校验和覆盖选项的详细讨论在第四节中。

3. 头部文件

套接字 API 需要通过 /usr/include 中的头文件来支持

  • /usr/include/netinet/in.h 定义 IPPROTO_UDPLITE

  • /usr/include/netinet/udplite.h 定义 UDP-Lite 头部字段和协议常量

出于测试目的,以下内容可以用作 mini 头部文件

#define IPPROTO_UDPLITE       136
#define SOL_UDPLITE           136
#define UDPLITE_SEND_CSCOV     10
#define UDPLITE_RECV_CSCOV     11

各种发行版的现成头文件在 UDP-Lite tarball 中。

4. 内核在各种套接字选项方面的行为

要启用调试消息,日志级别需要设置为 8,因为大多数消息使用 KERN_DEBUG 级别 (7)。

  1. 发送方套接字选项

如果发送方指定 0 作为覆盖长度值,则模块假定完全覆盖,发送覆盖长度为 0 的数据包并进行相应的校验和。如果发送方指定覆盖范围 < 8 且不等于 0,则内核假定 8 为默认值。最后,如果指定的覆盖长度超过数据包长度,则使用数据包长度代替覆盖长度。

  1. 接收方套接字选项

接收方指定其愿意接受的覆盖长度的最小值。此处的 0 值表示接收方始终希望覆盖整个数据包。在这种情况下,所有部分覆盖的数据包都将被丢弃,并记录一个错误。

不可能指定非法值(<0 和 <8);在这些情况下,将假定默认值为 8。

所有覆盖值小于指定阈值的数据包都将被丢弃,这些事件也会被记录。

  1. 禁用校验和计算

在发送方和接收方,始终会执行校验和,并且无法使用 SO_NO_CHECK 禁用。因此

setsockopt(sockfd, SOL_SOCKET, SO_NO_CHECK,  ... );

将始终被忽略,而

getsockopt(sockfd, SOL_SOCKET, SO_NO_CHECK, &value, ...);

的值是无意义的(如 TCP 中一样)。校验和字段为零的数据包是非法的(参见 RFC 3828,第 3.1 节),将被静默丢弃。

  1. 分片

校验和计算同时考虑缓冲区大小和 MTU。UDP-Lite 数据包的大小由发送缓冲区的大小决定。发送缓冲区的最小大小为 2048(在 include/net/sock.h 中定义为 SOCK_MIN_SNDBUF),默认值可配置为 net.core.wmem_default 或通过设置 SO_SNDBUF 套接字 (7) 选项。发送缓冲区的最大上限由 net.core.wmem_max 确定。

如果有效负载大小大于发送缓冲区大小,则 UDP-Lite 会将有效负载拆分为几个单独的数据包,并在每种情况下填充发送缓冲区大小。

确切的值还取决于接口 MTU。接口 MTU 反过来可能会触发 IP 分片。在这种情况下,生成的 UDP-Lite 数据包被拆分为几个 IP 数据包,其中只有第一个包含 L4 头部。

发送缓冲区大小对校验和覆盖长度有影响。考虑以下示例

Payload: 1536 bytes          Send Buffer:     1024 bytes
MTU:     1500 bytes          Coverage Length:  856 bytes

UDP-Lite 将以两个单独的数据包发送 1536 个字节

Packet 1: 1024 payload + 8 byte header + 20 byte IP header = 1052 bytes
Packet 2:  512 payload + 8 byte header + 20 byte IP header =  540 bytes

覆盖数据包覆盖第一个数据包中的 UDP-Lite 头部和 848 个字节的有效负载,第二个数据包被完全覆盖。请注意,对于第二个数据包,覆盖长度超过了数据包长度。在这种情况下,内核始终会将覆盖长度重新调整为数据包长度。

作为当一个 UDP-Lite 数据包被拆分为几个很小的片段时发生情况的示例,请考虑以下示例

Payload: 1024 bytes            Send buffer size: 1024 bytes
MTU:      300 bytes            Coverage length:   575 bytes

+-+-----------+--------------+--------------+--------------+
|8|    272    |      280     |     280      |     280      |
+-+-----------+--------------+--------------+--------------+
            280            560            840           1032
                                    ^
*****checksum coverage*************

UDP-Lite 模块生成一个 1032 字节的数据包(1024 + 8 字节头部)。根据接口 MTU,这些数据包被拆分为 4 个 IP 数据包(280 字节 IP 有效负载 + 20 字节 IP 头部)。内核模块在将片段释放到 IP 模块之前,将前两个完整数据包的内容加上最后一个数据包的 15 个字节的总和。

要查看 IPv6 分片的类似情况,请考虑 1280 字节的链路 MTU 和 3356 字节的写入缓冲区。如果校验和覆盖范围小于 1232 字节(MTU 减去 IPv6/片段头长度),则只需要考虑第一个片段。当使用较大的校验和覆盖长度时,需要校验每个符合条件的片段。假设我们的校验和覆盖范围为 3062。3356 字节的缓冲区将拆分为以下片段

Fragment 1: 1280 bytes carrying  1232 bytes of UDP-Lite data
Fragment 2: 1280 bytes carrying  1232 bytes of UDP-Lite data
Fragment 3:  948 bytes carrying   900 bytes of UDP-Lite data

前两个片段必须完全校验,最后一个片段只校验 598 个字节 (= 3062 - 2*1232)。

虽然正确处理这种情况非常重要,但它们(令人恼火地)很少见:UDP-Lite 旨在优化无线(或通常是嘈杂的)链路上的多媒体性能,因此很可能会预期较小的覆盖长度。

5. UDP-Lite 运行时统计数据及其含义

异常和错误情况会以 KERN_DEBUG 级别记录到 syslog。有关 UDP-Lite 的实时统计信息可在 /proc/net/snmp 中获得,并且(使用较新版本的 netstat)可以使用以下命令查看

netstat -svu

这将显示 UDP-Lite 统计变量,其含义如下。

InDatagrams

传递给用户的总数据报数量。

NoPorts

接收到未知端口的数据包数量。这些情况单独计算(不作为 InErrors)。

InErrors

错误的 UDP-Lite 数据包数量。错误包括

  • 内部套接字队列接收错误

  • 数据包太短(小于 8 个字节或声明的覆盖长度超过接收长度)

  • xfrm4_policy_check() 返回错误

  • 应用程序指定的最小覆盖长度大于传入数据包的覆盖长度

  • 违反校验和覆盖

  • 错误的校验和

OutDatagrams

发送的数据报总数。

这些统计数据来自 UDP MIB (RFC 2013)。

6. IPtables

有针对 UDP-Lite 的数据包匹配支持以及对 LOG 目标的支持。如果您将以下行复制并粘贴到 /etc/protocols 中

udplite 136     UDP-Lite        # UDP-Lite [RFC 3828]

那么

iptables -A INPUT -p udplite -j LOG

将在 syslog 中生成日志输出。丢弃和拒绝数据包也有效。

7. 维护者地址

UDP-Lite 补丁是在以下地点开发的

阿伯丁大学电子研究小组工程系 Fraser Noble Building Aberdeen AB24 3UE; 英国

当前的维护者是 Gerrit Renker,<gerrit@erg.abdn.ac.uk>。初始代码由 William Stanislaus 开发,<william@erg.abdn.ac.uk>。