如何找到我可以在不分段的情况下发送的最大 UDP 数据包?

Posted

技术标签:

【中文标题】如何找到我可以在不分段的情况下发送的最大 UDP 数据包?【英文标题】:How to find the largest UDP packet I can send without fragmenting? 【发布时间】:2010-10-28 09:26:03 【问题描述】:

我需要知道我可以发送到另一台计算机的最大 UDP 数据包是多少,没有碎片。

这个大小通常称为 MTU(最大传输单元)。假设在两台计算机之间,会有许多路由器和调制解调器,它们可能具有不同的 MTU。

我读到 Windows 中的 TCP 实现会自动找到路径中的最大 MTU。

我也在试验,我发现从我的计算机到服务器的最大 MTU 是 57712 字节+header。上面的任何东西都被丢弃了。我的电脑在局域网上,MTU 不是应该在 1500 字节左右吗?

【问题讨论】:

C/C++ 代码示例请参见此处:***.com/questions/13073797/… 【参考方案1】:

以下内容并未直接回答您的问题,但您可能会觉得它很有趣;它说 IP 数据包可以被分解/重新组合,因此大于底层媒体的限制(例如 1500 字节以太网):Resolve IP Fragmentation, MTU, MSS, and PMTUD Issues with GRE and IPSEC


关于这个主题的更多信息:

Re: UDP fragmentation 说你应该使用 ICMP 而不是 UDP 来发现 MTU Path MTU Discovery 表示 TCP 连接可能包括通过 ICMP 进行的隐式 MTU 协商

我不知道如何在 Windows 上通过 API 生成 ICMP:曾经有人提出过这样的 API,但引起了争议,因为人们认为这样可以很容易地编写通过生成实现拒绝服务功能的软件大量 ICMP 消息。

不,它看起来已实现:例如参见Winsock Programmer's FAQ Examples: Ping: Raw Sockets Method。

因此,要发现 MTU,请生成带有“不分段”标志的 ping 数据包。

也许有比这更简单的 API,我不知道;但我希望我已经让您了解底层协议。

【讨论】:

谢谢,这有点帮助。我只是想知道其他使用 UDP 的人是否只使用最低默认值 576,这似乎是一个可怕的想法。 当我使用 UDP 实现一个协议时,它使用序列号和重传来检测丢失的数据包。使用小数据包建立“连接”后,我尝试使用更大的数据包“协商”,通过端到端发送更大的数据包并查看我是否收到回复(或者它是否在途中丢失)。 YMMV。小数据包(例如 100 字节)通常在现实世界中用于 VoIP 等应用程序;我不知道您需要什么性能特征,为什么要使用 UDP:如果您要处理大数据包,那么您可能正在尝试优化带宽而不是... ... 抖动;但我不知道使用尽可能大的数据包会对带宽产生巨大影响。 你是怎么想到这里的 160 和 64 的? AFAIK IP 标头 = 20,UDP 标头 = 8,TCP 标头 = 20。 @Nikolai:他一定是在考虑比特,而不是字节。所以对于一个 1500 字节的数据包来说,这就是 【参考方案2】:

除了前面所有的答案,引用classic:

IPv4 和 IPv6 定义了最小重组缓冲区大小,我们保证任何实现都必须支持的最小数据报大小。对于 IPv4,这是 576 字节。 IPv6 将此提升到 1,280 字节。

这几乎意味着如果您在公共互联网上工作并且您只控制交换的一侧,那么您希望将数据报大小限制在 576 以下 - 这是大多数基于标准 UDP 的协议所做的。 另请注意,PMTU 是路径的动态属性。这是 TCP 为您处理的事情之一。除非您准备好重新实现大量的排序、定时和重传逻辑,否则将 TCP 用于任何关键网络。基准测试、测试、配置文件,即证明 TCP 是您的瓶颈,然后才考虑 UDP。

【讨论】:

“除非你准备好重新实现大量的排序、定时和重传逻辑”... 是的,我已经意识到这一点,我准备实现自己的。此外,PMTU 不是 TCP 的一部分。我看不出你有什么理由不能将它用于你自己的协议。 我只是说 TCP 已经实现了 RFC 1191/1981 等等,而且也在内核中。如果你有理由在 UDP 中工作 - 很好 - 继续,只要确保理由是有效的。 如果我一开始只使用一个虚拟的 tcp 连接呢?是否可以从中提取 PMTU? 您可以使用 TCP_MAXSEG 选项从 TCP 套接字中提取 current MSS。仅在建立连接后才有意义 - 而是返回默认 MSS。理论上 PMTU 可能会改变,因为 IP 路由是动态的,所以 YMMV。仅出于完整性考虑 - IPv6 有一个可以查询的明确 IPV6_PATHMTU 套接字选项。 它确实很粗糙!建议是学习这两种协议,并推理适用性。你断章取义了。【参考方案3】:

这对我来说是一个有趣的话题。当通过 UDP 在现实世界的互联网上传输大块 UDP 数据时,可能会感兴趣一些实际结果,并且以每秒 1 个数据包的传输速率,数据会继续以最小的丢包率出现,最高可达 2K。在这个问题上,你开始遇到问题,但我们经常发送 1600+ 字节的数据包而没有痛苦 - 这是通过 GPRS 移动网络以及全球范围内的 WAN。假设信号稳定(不是!),大约 1K 时,丢包率很低。

有趣的是,它不是奇怪的数据包,而是通常会持续几秒钟的数据包狂风 - 这可能是 VoIP 呼叫偶尔崩溃的原因。

【讨论】:

【参考方案4】:

registry 中提供了您自己的 MTU,但实际上 MTU 将指向您的机器和目标之间路径中的最小 MTU。它既可变又只能凭经验确定。有许多RFCs 显示如何确定它。

LAN 可以在内部具有非常大的 MTU 值,因为网络硬件通常是同质的或至少是集中管理的。

【讨论】:

为什么人们常说以太网的MTU是1500? 因为这是 RFC 中为以太网 V2 定义的内容。我不够精明,不知道以太网数据包的碎片是否通常在以太网到 ip-only 路由器上反转,所以它可能与手头的问题相关,也可能不相关。【参考方案5】:

对于 UDP 应用程序,如果您想避免 IP 碎片或丢包,您必须自己处理端到端 MTU。任何应用程序的推荐方法是尽最大努力使用 PMTU 来选择最大数据报,或发送数据报

https://www.rfc-editor.org/rfc/rfc5405#section-3.2

应用程序设计者的单播 UDP 使用指南“不应该发送超过 PMTU 的数据报,应该发现 PMTU 或发送数据报

Windows 似乎通过它的基本套接字选项界面设置和访问 PMTU 信息:

您可以确保通过 IP_MTU_DISCOVER 开启 PMTU 发现,并且可以通过 IP_MTU 读取 MTU。

https://docs.microsoft.com/en-us/windows/desktop/winsock/ipproto-ip-socket-options

【讨论】:

【参考方案6】:

这是我为检查路径 MTU 问题而编写的一些 Windows PowerShell。 (通用技术在其他编程语言中实现起来并不难。)许多防火墙和路由器被配置为丢弃所有不了解的人的所有 ICMP。路径 MTU 发现取决于是否能够接收设置了需要分片的 ICMP 目标不可达消息,以响应发送设置了不分片的数据包。 Resolve IPv4 Fragmentation, MTU, MSS, and PMTUD Issues with GRE and IPsec 实际上很好地解释了发现的工作原理。

function Test-IPAddressOrName($ipAddressOrName)

    $ipaddress = $null
    $isValidIPAddressOrName = [ipaddress]::TryParse($ipAddressOrName, [ref] $ipaddress)

    if ($isValidIPAddressOrName -eq $false)
    
        $hasResolveDnsCommand = $null -ne (Get-Command Resolve-DnsName -ErrorAction SilentlyContinue)
        if ($hasResolveDnsCommand -eq $true)
        
            $dnsResult = Resolve-DnsName -DnsOnly -Name $ipAddressOrName -ErrorAction SilentlyContinue
            $isValidIPAddressOrName = $null -ne $dnsResult
        
    

    return $isValidIPAddressOrName


function Get-NameAndIPAddress($ipAddressOrName)

    $hasResolveDnsCommand = $null -ne (Get-Command Resolve-DnsName -ErrorAction SilentlyContinue)

    $ipAddress = $null
    $validIPAddress = [ipaddress]::TryParse($ipAddressOrName, [ref] $ipAddress)
    $nameAndIp = [PSCustomObject] @ 'Name' = $null; 'IPAddress' = $null 

    if ($validIPAddress -eq $false)
    
        if ($hasResolveDnsCommand -eq $true)
        
            $dnsResult = Resolve-DnsName -DnsOnly $ipAddressOrName -Type A -ErrorAction SilentlyContinue

            if ($null -ne $dnsResult -and $dnsResult.QueryType -eq 'A')
            
                $nameAndIp.Name = $dnsResult.Name
                $nameAndIp.IPAddress = $dnsResult.IPAddress
            
            else
            
                Write-Error "The name $($ipAddressOrName) could not be resolved."
                $nameAndIp = $null
            
        
        else
        
            Write-Warning "Resolve-DnsName not present. DNS resolution check skipped."
        
    
    else
    
        $nameAndIp.IPAddress = $ipAddress

        if ($hasResolveDnsCommand -eq $true)
        
            $dnsResult = Resolve-DnsName -DnsOnly $ipAddress -Type PTR -ErrorAction SilentlyContinue

            if ($null -ne $dnsResult -and $dnsResult.QueryType -eq 'PTR')
            
                $nameAndIp.Name = $dnsResult.NameHost
            
        
    

    return $nameAndIp


<#
    .Synopsis
    Performs a series of pings (ICMP echo requests) with Don't Fragment specified to discover the path MTU (Maximum Transmission Unit).

    .Description
    Performs a series of pings with Don't Fragment specified to discover the path MTU (Maximum Transmission Unit). An ICMP echo request 
    is sent with a random payload with a payload length specified by the PayloadBytesMinimun. ICMP echo requests of increasing size are 
    sent until a ping response status other than Success is received. If the response status is PackeTooBig, the last successful packet 
    length is returned as a reliable MTU; otherwise, if the respone status is TimedOut, the same size packet is retried up to the number 
    of retries specified. If all of the retries have been exhausted with a response status of TimedOut, the last successful packet 
    length is returned as the assumed MTU.

    .Parameter UseDefaultGateway
    If UseDefaultGateway is specified the default gateway reported by the network interface is used as the destination host.

    .Parameter DestinationHost
    The IP Address or valid fully qualified DNS name of the destination host.

    .Parameter InitialTimeout
    The number of milliseconds to wait for an ICMP echo reply. Internally, this is doubled each time a retry occurs.

    .Parameter Retries
    The number of times to try the ping in the event that no reply is recieved before the timeout.

    .Parameter PayloadBytesMinimum
    The minimum number of bytes in the payload to use. The minimum MTU for IPv4 is 68 bytes; however, in practice, it's extremely rare 
    to see an MTU size less than 576 bytes so the default value is 548 bytes (576 bytes total packet size minus an ICMP header of 28 
    bytes).

    .Parameter PayloadBytesMaximum
    The maximum number of bytes in the payload to use. An IPv4 MTU for jumbo frames is 9000 bytes. The default value is 8973 bytes (9001 
    bytes total packet size, which is 1 byte larger than the maximum IPv4 MTU for a jumbo frame, minus an ICMP header of 28 bytes).

    .Example
    Discover-PathMTU -UseDefaultGateway

    .Example
    Discover-PathMTU -DestinationHost '192.168.1.1'

    .Example
    Discover-PathMTU -DestinationHost 'www.google.com'
#>
function Discover-PathMtu

    [CmdletBinding(SupportsShouldProcess = $false)]
    param
    (
        [Parameter(Mandatory = $true, ParameterSetName = 'DefaultGateway')]
        [switch] $UseDefaultGateway,

        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'IPAddressOrName')]
        [ValidateScript( Test-IPAddressOrName $_ )]
        [string] $DestinationHost,

        [Parameter(ParameterSetName = 'IPAddressOrName')]
        [Parameter(ParameterSetName = 'DefaultGateway')]
        [int] $InitialTimeout = 3000,

        [Parameter(ParameterSetName = 'IPAddressOrName')]
        [Parameter(ParameterSetName = 'DefaultGateway')]
        [int] $Retries = 3,

        [Parameter(ParameterSetName = 'IPAddressOrName')]
        [Parameter(ParameterSetName = 'DefaultGateway')]
        $PayloadBytesMinimum = 548,

        [Parameter(ParameterSetName = 'IPAddressOrName')]
        [Parameter(ParameterSetName = 'DefaultGateway')]
        $PayloadBytesMaximum = 8973
    )

    begin
    
        $ipConfiguration = Get-NetIPConfiguration -Detailed | ? $_.NetProfile.Ipv4Connectivity -eq 'Internet' -and $_.NetAdapter.Status -eq 'Up'  | Sort  $_.IPv4DefaultGateway.InterfaceMetric  | Select -First 1
        $gatewayIPAddress = $ipConfiguration.IPv4DefaultGateway.NextHop

        $pingOptions = New-Object System.Net.NetworkInformation.PingOptions
        $pingOptions.DontFragment = $true
        $pinger = New-Object System.Net.NetworkInformation.Ping

        $rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
    

    process
    
        $pingIpAddress = $null

        if ($UseDefaultGateway -eq $true)
        
            $DestinationHost = $gatewayIPAddress
        

        $nameAndIP = Get-NameAndIPAddress $DestinationHost

        if ($null -ne $nameAndIP)
        
            Write-Host "Performing Path MTU discovery for $($nameAndIP.Name) $($nameAndIP.IPAddress)..."

            $pingReply = $null
            $payloadLength = $PayloadBytesMinimum
            $workingPingTimeout = $InitialTimeout

            do
            
                $payloadLength++

                # Use a random payload to prevent compression in the path from potentially causing a false MTU report.
                [byte[]] $payloadBuffer = (,0x00 * $payloadLength)
                $rng.GetBytes($payloadBuffer)

                $pingCount = 1

                do
                
                    $pingReply = $pinger.Send($nameAndIP.IPAddress, $workingPingTimeout, $payloadBuffer, $pingOptions)

                    if ($pingReply.Status -notin 'Success', 'PacketTooBig', 'TimedOut')
                    
                        Write-Warning "An unexpected ping reply status, $($pingReply.Status), was received in $($pingReply.RoundtripTime) milliseconds on attempt $($pingCount)."
                    
                    elseif ($pingReply.Status -eq 'TimedOut')
                    
                        Write-Warning "The ping request timed out while testing a packet of size $($payloadLength + 28) using a timeout value of $($workingPingTimeout) milliseconds on attempt $($pingCount)."
                        $workingPingTimeout = $workingPingTimeout * 2
                    
                    else
                    
                        Write-Verbose "Testing packet of size $($payloadLength + 28). The reply was $($pingReply.Status) and was received in $($pingReply.RoundtripTime) milliseconds on attempt $($pingCount)."
                        $workingPingTimeout = $InitialTimeout
                    

                    Sleep -Milliseconds 10

                    $pingCount++
                 while ($pingReply.Status -eq 'TimedOut' -and $pingCount -le $Retries)
             while ($payloadLength -lt $PayloadBytesMaximum -and $pingReply -ne $null -and $pingReply.Status -eq 'Success')

            if ($pingReply.Status -eq 'PacketTooBig')
            
                Write-Host "Reported IPv4 MTU is $($ipConfiguration.NetIPv4Interface.NlMtu). The discovered IPv4 MTU is $($payloadLength + 27)."
            
            elseif ($pingReply.Status -eq 'TimedOut')
            
                Write-Host "Reported IPv4 MTU is $($ipConfiguration.NetIPv4Interface.NlMtu). The discovered IPv4 MTU is $($payloadLength + 27), but may not be reliable because the packet appears to have been discarded."    
            
            else
            
                Write-Host "Reported IPv4 MTU is $($ipConfiguration.NetIPv4Interface.NlMtu). The discovered IPv4 MTU is $($payloadLength + 27), but may not be reliable, due to an unexpected ping reply status."    
            

            return $payloadLength + 27
        
        else
        
            Write-Error "The name $($DestinationHost) could not be resolved. No Path MTU discovery will be performed."
        
    

    end
    
        if ($null -ne $pinger)
        
            $pinger.Dispose()
        

        if ($null -ne $rng)
        
            $rng.Dispose()
        
    

【讨论】:

以上是关于如何找到我可以在不分段的情况下发送的最大 UDP 数据包?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不下载整个片段的情况下使用媒体源缓冲区和分段 mp4 在特定时间启动视频?

如何在不显示所有结果的最大值的情况下为数学运算以及其他元素选择最大值

TCP UDP 分段 IP分片

如何在不推动我的情况下从GitHub中提取更改?

在不更改界面的情况下更新ios中的分段控件

如何在不发送任何内容的情况下检查频道是不是已满[重复]