如何找到我可以发送的最大UDP数据包而不会分段?

时间:2009-05-23 02:16:18

标签: networking tcp udp mtu

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

此大小通常称为MTU(最大传输单位)。据推测,在两台计算机之间,将有许多可能具有不同MTU的路由器和调制解调器。

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

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

6 个答案:

答案 0 :(得分:23)

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


有关此主题的更多信息:

我不知道在Windows上通过API生成ICMP:曾经提出这样的API,并且引起争议,因为人们认为通过生成可以轻松编写实现拒绝服务功能的软件大量的ICMP消息。

不,看起来 已实现:请参阅示例Winsock Programmer's FAQ Examples: Ping: Raw Sockets Method

因此,要发现MTU,请使用'do not fragment'标志生成ping数据包。

也许有一个比这更简单的API,我不知道;但我希望我已经让你了解基础协议[s]。

答案 1 :(得分:12)

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

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

如果您在公共互联网上工作并且您只控制交换机的一侧 - 这就是大多数标准的基于UDP的协议所做的事情,这意味着您希望将数据报大小限制在576以下。

另请注意,PMTU是路径的动态属性。这是TCP为您处理的事情之一。除非您准备重新实现大量的排序,定时和重传逻辑,否则请将TCP用于任何关键网络。基准,测试,配置文件,即证明 TCP是你的瓶颈,只考虑UDP。

答案 2 :(得分:3)

对我来说,这是一个有趣的话题。当通过UDP在现实世界互联网上传送粗块UDP数据时,或许一些实际结果可能是有意义的,并且每秒传输速率为1个数据包,数据继续出现,最小数据包丢失高达约2K。在此之后,您开始遇到问题,但我们经常会毫不费力地提供1600多个字节的数据包 - 这是通过GPRS移动网络以及全球范围内的WAN。在~1K假设信号稳定(不是!),你会得到低丢包率。

有趣的是,这不是奇怪的数据包,而是经常会出现几秒钟的数据包 - 这可能就是为什么VoIP呼叫偶尔会崩溃的原因。

答案 3 :(得分:2)

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

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

答案 4 :(得分:0)

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

https://tools.ietf.org/html/rfc5405#section-3.2

  

针对应用设计者的单播UDP使用指南“不应发送超出PMTU的数据报,应发现PMTU或发送数据报<最小PMTU

Windows似乎可以通过其基本的套接字选项界面进行设置并访问PMTU信息:

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

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

答案 5 :(得分:0)

以下是我编写的一些Windows PowerShell,用于检查Path MTU问题。 (一般的技术在其他编程语言中不太难实现。)许多防火墙和路由器都配置为由不了解的人丢弃所有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()
        }
    }
}