PowerShellでarpingもどき


目的

PowerShell で Linux ライクな arping コマンドを実現する。

背景

Windows でも ARP エントリ(ARP キャッシュ)を調べるコマンド(ARP -aGet-NetNeighbor)は存在するが、実際に ARP を送出してレスポンスを調べるためのコマンドがない。

コード

IP Helper API(iphlpapi.dll)の SendARP 関数を実行しているだけです。
中身はほぼ PowerShellスクリプトですが、Windows バッチファイルとして実行できます(先頭行をコメントアウトまたは削除して拡張子を .ps1 に変更すると PowerShell スクリプトになります)。PowerShellのコンソール上で使用する場合は、arping 関数を直接使用してください。

arping.bat
@Powershell -NoP -C "&([ScriptBlock]::Create((gc '%~f0'|?{$_.ReadCount -gt 1}|Out-String)))" %* & exit/b
# by earthdiver1
[CmdletBinding(PositionalBinding=$False)]
Param(
    [parameter(Position=0)][String]$Target,  # IP address of the target host
    [String]$Source,                         # if you have multiple interfaces (optional, default value: 0 (all interfaces))
    [Alias("n")][Int]$Count=4,               # the number of ARP requests to send (optional, default value: 4)
    [Int]$Interval=1                         # the interval between ARP requests, in seconds (optional, default value: 1)
)

Function arping {
    [CmdletBinding(PositionalBinding=$False)]
    Param(
        [parameter(Position=0)][String]$Target,  # IP address of the target host
        [String]$Source,                         # if you have multiple interfaces (optional, default value: 0 (all interfaces))
        [Alias("n")][Int]$Count=4,               # the number of ARP requests to send (optional, default value: 4)
        [Int]$Interval=1                         # the interval between ARP requests, in seconds (optional, default value: 1)
    )
    if (-not $Target) {
        Write-Output "Usage: arping <target IP address> [-Source <source IP address>]"
        Write-Output "                                  [-Count/-n <number of ARP requests>] [-Interval <interval between ARP requests>]"
        exit
    }
    if ($Count -lt 1) { exit }
    $code = '[DllImport("iphlpapi.dll")]public static extern int SendARP(UInt32 DestIP,UInt32 SrcIP,byte[] pMacAddr,ref UInt32 PhyAddrLen);'
    $type = Add-Type -MemberDefinition $code -Name Win32SendARP -PassThru
    try {
        $DstIP = [UInt32][System.Net.IPAddress]::Parse($Target).Address
    } catch {
        Write-Output "Target IP address $Target is invalid. Please check it and try again."
        exit
    }
    if ($Source) {
        try {
            $SrcIP = [UInt32][System.Net.IPAddress]::Parse($Source).Address
        } catch {
            Write-Output "Source IP address $Source is invalid. Please check it and try again."
            exit
        }
    } else {
        $SrcIP = [UInt32]0
    }
    $MacAddr = New-Object Byte[] 6
    $n_sent = 0
    $n_received = 0
    $maxT = 0.
    $minT = 9999.
    $sumT = 0.
    Write-Output ""
    Write-Output "ARPING ${Target}:"
    for($i=1; $i -le $Count; $i++) {
        $n_sent++
        $t = (Measure-Command { $result = $type::SendARP($DstIP, $SrcIP, $MacAddr, [ref][UInt32]$MacAddr.Length) }).TotalMilliseconds
        if ($result -eq 0) { # NO_ERROR
            $n_received++
            if ($t -gt $maxT) { $maxT = $t }
            if ($t -lt $minT) { $minT = $t }
            $sumT += $t        
            Write-Output "Reply from $(($MacAddr | %{ '{0:x2}' -F $_ }) -Join '-') (as $Target): index=$i, time=$(($t).ToString('0'))ms"
        } elseif ($result -eq 67) { # ERROR_BAD_NET_NAME
            Write-Output "Request timed out."
        } else {
            Write-Output "SendARP failed with error: $result"
        }
        if ($i -lt $Count) { Start-Sleep -Milliseconds ([Math]::Max(($Interval*1000 - [Int]$t), 0)) }
    }
    Write-Output ""
    Write-Output "Ping statistics for $Target/arp:"
    Write-Output "    Packets: Sent=$n_sent, Received=$n_received, Lost=$($n_sent-$n_received) ($((($n_sent-$n_received)/$n_sent*100).ToString('0'))% loss)"
    if ($n_received -gt 0) {
        Write-Output "Approximate round trip times in milli-seconds:"
        Write-Output "    Minimum = $(($minT).ToString('0'))ms, Maximum = $(($maxT).ToString('0'))ms, Average = $(($sumT/$n_received).ToString('0'))ms"
    }
}

# update $PSBoundParameters with default parameter values
foreach ($p in $MyInvocation.MyCommand.ScriptBlock.Ast.ParamBlock.Parameters) {
    $key = $p.Name.VariablePath.UserPath
    if ($PSBoundParameters.ContainsKey($key)) { Continue }
    $value = (Get-Variable $key).Value
    if ($value) { $PSBoundParameters.Add($key, $value) }
}

arping @PSBoundParameters

実行例

C:\Users\earthdiver1\Desktop> arping 192.168.0.1

ARPing 192.168.0.1:
Reply from 00-3a-9d-80-ed-0c (as 192.168.0.1): index=1, time=26ms
Reply from 00-3a-9d-80-ed-0c (as 192.168.0.1): index=2, time=2ms
Reply from 00-3a-9d-80-ed-0c (as 192.168.0.1): index=3, time=1ms
Reply from 00-3a-9d-80-ed-0c (as 192.168.0.1): index=4, time=2ms

Ping statistics for 192.168.0.1/arp:
    Packets: Sent=4, Received=4, Lost=0 (0% loss)
Approximate round trip times in milli-seconds:
    Minimum = 1ms, Maximum = 26ms, Average = 8ms

 
 
クリエイティブ・コモンズ 表示 - 継承 4.0 国際