59

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

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

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

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

4

6 回答 6

28

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


有关此主题的更多信息:

我不知道如何在 Windows 上通过 API 生成 ICMP:曾经有人提出过这样的 API,并引起了争议,因为人们认为通过生成大量ICMP 消息。

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

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

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

于 2009-05-23T02:37:38.290 回答
14

除了所有先前的答案,引用经典

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


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

另请注意,PMTU 是路径的动态属性。这是 TCP 为您处理的事情之一。除非您准备好重新实现大量的排序、定时和重传逻辑,否则将 TCP 用于任何关键网络。Benchmark,test,profile,即证明TCP是你的瓶颈,然后才考虑UDP。

于 2009-05-24T00:55:50.447 回答
3

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

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

于 2014-01-14T17:32:24.460 回答
2

您自己的 MTU 在注册表中可用,但实际上 MTU 将指向您的机器和目标之间的路径中的最小 MTU。它既可变又只能凭经验确定。有许多RFC 展示了如何确定它。

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

于 2009-05-23T02:19:48.543 回答
0

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

https://www.rfc-editor.org/rfc/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

于 2018-10-31T16:30:38.713 回答
0

这是我为检查路径 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()
        }
    }
}
于 2019-09-06T23:51:44.457 回答