はじめに
きっかけは@Pyromaniaさんのこのツイートから。
Test-Connection でQuiet オプションつけないと、ICMPのあとにNetBIOS Name Query なげててLinux系の鯖だと時間かかるっぽいという知見をえた。
— Pyromania 🏢〜☁️ (@Pyromaniaxxx) 2016年6月15日
困った時のWiresharkさまさまやでー(
ツイートではLinuxサーバーについて触れていますが時間がかかるのはWindowsに対しても同様です。
本エントリではPowerShellにおけるPingであるTest-Connection
コマンドレットの動作が遅い理由とその対策について触れていきます。
Test-Connectionの実装
ILSpy等でTest-Connection
の実装を調べてみると、Test-Connection
はWMIのWin32_PingStatusクラスを使ってPingを行っており、厳密には一致していませんが以下の様なWQLを内部で発行しています。
SELECT * FROM Win32_PingStatus WHERE Address = `[-Destination]` //複数宛先ある場合はORで連結 AND TimeToLive = [-TimeToLive] AND BufferSize = [-BufferSize]
単純にWin32_PingStatus
を使うだけであればICMPプロトコルしか使わないため、最初に触れた様なNetBIOS Name Queryは発行されません。
コマンドレットの実装だけを見ると遅くなる要因が無い様に見受けられます。
Test-Connectionが遅い理由
ではどこでNetBIOS Name Queryが発行されているのかというと、その原因はTest-Connection
の戻り値にあります。
Test-Connection
では-Quiet
パラメーターを指定しない場合はWin32_PingStatus
クラス*1のオブジェクトをそのまま返します。
この戻り値に対してPowerShell側でIPV4Address
およびIPV6Address
というScriptPropertyが付与されており、Get-Member
を使って定義を確認してみると、
PS C:\> Test-Connection 192.168.133.12 -Count 1 | Get-Member -View Extended | Format-List ・・・(中略)・・・ TypeName : System.Management.ManagementObject#root\cimv2\Win32_PingStatus Name : IPV4Address MemberType : ScriptProperty Definition : System.Object IPV4Address {get=$iphost = [System.Net.Dns]::GetHostEntry($this.address) $iphost.AddressList | ?{ $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork } | select -first 1;} TypeName : System.Management.ManagementObject#root\cimv2\Win32_PingStatus Name : IPV6Address MemberType : ScriptProperty Definition : System.Object IPV6Address {get=$iphost = [System.Net.Dns]::GetHostEntry($this.address) $iphost.AddressList | ?{ $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork V6 } | select -first 1;}
とプロパティ内で[System.Net.Dns]::GetHostEntry()
メソッドを発行しIPアドレスの逆引きをしていることがわかります。
このメソッドは内部でgethostbyaddr()
関数を使用していますのでNetBIOS Name Queryを含めた名前解決*2が実行され、その結果待ちにより処理が遅くなってしまうのです。
この問題は、
Test-Connection Performance With IP and Output
Votes from Connect: 3
Original Date Submitted: 7/23/2015 12:20:40 AM
Description:
********Contact Information********
Handle: John.Bevan
Site Name: PowerShell
Feedback ID: 1578010
***************************************
Frequency: PowerShell ISE
Regression: Run the below code / vie...
と既にフィードバックされているのですが、あまり対応される空気を感じません...
Test-Connection
は普通に使うと遅いものと割り切るのが現実的な気がします。
対策方法
Test-Connection
コマンドレットの遅さに対しては幾つかの対策を行う事ができますので以下に記載していきます。
1. Pingコマンドを使う
身も蓋もない方法ですが、単純にコンソール上からPingの結果だけ見たいのであればTest-Connection
を使わずに従来通りPing
コマンドを使うのが一番手っ取り早いでしょう。
2. -Quietオプションを使う
スクリプト中でPingによる疎通確認を行いその結果だけが必要であれば-Quiet
オプションを指定するのが現実的です。
-Quiet
オプションを指定した場合のTest-Connection
の戻り値はBoolean
型になりますので遅延の原因であるIPV4Address
、IPV6Address
プロパティを気にせずに済みます。
3. コマンドレットの結果を変数に代入する
Test-Connection
の結果を使いつつ遅延を防ぎたい場合は、実行結果を一度変数に代入するのが効果的です。
Test-Connection
ではIPV4Address
、IPV6Address
プロパティがコンソール上での表示対象になっており、表示の際にプロパティへのアクセスが発生して[System.Net.Dns]::GetHostEntry()
メソッドが呼び出されてしまいます。
一旦結果を変数に代入すればIPV4Address
、IPV6Address
プロパティへのアクセスを抑止できます。
4. Select-Objectで出力するプロパティを絞る
前項の方法と考え方は同じです。
コンソール上でTest-Connection
を使う場合、Select-Object
を使ってコンソールに表示するプロパティを絞ることでIPV4Address
、IPV6Address
プロパティへのアクセスを抑えることができます。
コンソールに表示させなければ良いので、Select-Object
の代わりにFormat-*
なコマンドレットで絞っても同様の効果を得ることができます。
【2016/06/17追記】5. Remove-TypeDataを使う
本エントリを公開後、どうにかしてIPV4Address
、IPV6Address
プロパティを削除できないか調べたところ、Remove-TypeData
が使えることがわかりました。
Remove-TypeData
はPowerShellで独自に追加された型データの情報を削除するコマンドレットになります。
削除はそのセッション中のみ有効でpowershell.exeを再起動するなどして新しいセッションができると型データの情報は復活します。
最初にWin32_PingStatus
クラスの型System.Management.ManagementObject#root\cimv2\Win32_PingStatus
の型データの情報を確認してみると、
PS C:\> (Get-TypeData System.Management.ManagementObject#root\cimv2\Win32_PingStatus).Members | ft -AutoSize Key Value --- ----- IPV4Address System.Management.Automation.Runspaces.ScriptPropertyData IPV6Address System.Management.Automation.Runspaces.ScriptPropertyData
の様にIPV4Address
、IPV6Address
プロパティがあることがわかります。
これに対して、Remove-TypeData
を以下の様に実行します。
PS C:\> Remove-TypeData System.Management.ManagementObject#root\cimv2\Win32_PingStatus
これでSystem.Management.ManagementObject#root\cimv2\Win32_PingStatus
に対する型データは消えるのでTest-Connection
を実行してもIPV4Address
、IPV6Address
プロパティが付与されることは無くなります。
【2016/06/20追記】
あえとすさんよりUpdate-FormatDataを使った方法を指摘して頂きました。
上のエントリ内で指摘されている様にIPV4Address
、IPV6Address
プロパティに依存する処理があった場合Remove-TypeData
を使う方法ではエラーとなってしまうので対応としては確かに乱暴だと思います。
Remove-TypeData
は極力使わずUpdate-FormatData
を使う方が良いでしょう。
【追記ここまで】
実際に確認してみる
簡単な環境で動作を確認してみましたのでその結果を記載します。
2台のWindows Server 2012 R2(PowerShell 4.0)の仮想マシン、fromsv(192.168.133.11)
からwindestsv(192.168.133.12)
に対してPing
およびTest-Connection
を実行し、その結果をWiresharkでキャプチャしました。
簡単のためにIPV6は無効にし、DNSの設定*3もしていません。
検証環境として雑だと自分でも思っていますので結果については軽く見てもらえると助かります。
0. Test-Connectionを使った場合
まずはTest-Connection
を普通に使った場合を見てみます。
試行回数はデフォルトの4回を一応明示しています。DNSは設定していませんが念のためにipconfig /flushdns
をしています。
ipconfig /flushdns Test-Connection 192.168.133.12 -Count 4
実行結果はこんな感じです。
IPV4Address
、IPV6Address
プロパティがコンソールに表示されアクセスされるのが遅延の原因であるためMeasure-Command
による時刻計測はしていません。
処理時間はパケットキャプチャの結果で判断しています。
キャプチャの結果は以下となります。
(中略)
ICMPパケット(赤色の部分)の合間にNetBIOS Name Queryのパケットが流れていることが分かります。
4回目のPingが終わった最後のパケットでは約22秒も経過していることがわかります。
圧倒的な遅さです(
1. Pingコマンドを使う
ここから各対策の結果を記載していきます。
最初はPing
コマンドの結果です。
ipconfig /flushdns ping 192.168.133.12 -n 4
当然ですがICMPパケットしかありません。
時間は約3秒でこれがユーザーが期待する普通の結果でしょう。
2. -Quietオプションを使う
次に-Quiet
オプションを使った場合です。
ipconfig /flushdns Test-Connection 192.168.133.12 -Count 4 -Quiet
こちらもICMPパケットしか飛ばさないのでPingコマンドと同等の時間となっています。
3. コマンドレットの結果を変数に代入する
コマンドの結果を変数に代入した場合です。
ipconfig /flushdns $Results = Test-Connection 192.168.133.12 -Count 4
この場合もICMPパケットしか飛ばしていません。
ここで以下の様にプロパティにアクセスしてみると、
$Results[0].ResponseTime $Results[0].IPV4Address
IPV4Address
プロパティにアクセスした時点で以下の様にNetBIOS Name Queryが発行されました。
4. Select-Objectで出力するプロパティを絞る
最後は出力するプロパティを絞った場合です。
画面表示の都合、Select-Object
の代わりにFormat-Table
を使いました。
ipconfig /flushdns Test-Connection 192.168.133.12 -Count 4 | ft Address,ResponseTime -AutoSize
こちらも対策の効果が出ていることがわかります。
【2016/06/17追記】5. Remove-TypeDataを使う
Remove-TypeData
を使ってIPV4Address
、IPV6Address
プロパティを削除した場合の結果です。
ipconfig /flushdns Remove-TypeData System.Management.ManagementObject#root\cimv2\Win32_PingStatus Test-Connection 192.168.133.12 -Count 4
コンソール上IPV4Address
、IPV6Address
の表示欄はありますが値が設定されておらず、キャプチャの結果もICMPパケットしか飛んでいないことがわかります。
もちろん処理時間も改善されています。
最後に
とりあえずこんな感じです。
率直に言ってIPV4Address
、IPV6Address
ScriptPropertyを付けたのは万死に値するレベルの失策だと思います。
互換性を考えると実現は難しでしょうが、このプロパティを無くすかデフォルトの表示項目から外してほしい感じです。