はじめに
本稿では.NETアプリケーションがHTTP通信に使用したプロキシ設定をメモリダンプから確認する方法の一例をご紹介します。
WinDbgの一般的な使用方法に触れるため、やや遠回りしている部分もありますがご容赦ください。
開始前のインプット
対象となるアプリケーション
本稿の内容は.NET Core 3.0, Core 3.1, 5, 6, 7, 8等の比較的新しいランタイムで動作するアプリケーションを前提としています。
これは本稿の主眼であるHttpClient.DefaultProxyプロパティがそれ以前のランタイムではサポートされていないためです。
参考:
HttpClient.DefaultProxy Property > Applies to
HttpClientクラスとHttpClient.DefaultProxyプロパティ
HttpClientクラスは.NETアプリケーションがHTTP通信のために使用する標準のクラスです。
HttpClient.DefaultProxyプロパティはHttpClientクラスがHTTP通信のために使用するプロキシの情報を保持するstatic変数です。
定義は次のものです。
public static System.Net.IWebProxy DefaultProxy { get; set; }
参考:
HttpClient.DefaultProxy Property > Definition
HttpClient.DefaultProxyプロパティの値は環境変数またはシステムのプロキシ設定から自動的に読み込まれます。
この読み込みの挙動は複雑なものとなっているためここでは解説を割愛します。
詳細については次のガイドを参照してください。
参考:
HttpClient.DefaultProxy Property > Remarks
事前準備
WinDbg
WinDbgのインストールが必要です。
https://www.microsoft.com/store/apps/9pgjgd53tn86
ProcDump
ProcDumpで対象のアプリケーションのダンプファイルを取得しておきます。
今回取得するダンプはクラッシュダンプである必要はありません。
HttpClientクラスが調査の対象となるHTTP通信を行った後であればいつダンプを取得しても問題ありません。
https://learn.microsoft.com/ja-jp/sysinternals/downloads/procdump
メモリダンプ取得のためのコマンドは例えば次のようなものです。
procdump -ma -accepteula 対象のプロセス名
調査手順
WinDbgでダンプファイルを開きます。
ここからの手順は全てWinDbg上で操作となり、文章中のコマンドとコードブロックはWinDbgで実行するコマンドとその結果を示します。
ヒープからHttpClientのインスタンスを列挙する
実行するコマンド:
!dumpheap -type System.Net.Http.HttpClient
結果:
0:000> !dumpheap -type System.Net.Http.HttpClient
Address MT Size
000002260291d8d8 00007ffb8925b698 24
0000022602d31ba0 00007ffb8b12e310 192
0000022602d32368 00007ffb8b12e310 192
0000022603cbf378 00007ffb8a5e5598 32
0000022603cbfd80 00007ffb8925a7c8 80
0000022603cbfe00 00007ffb8925a7c8 80
0000022603cbfe50 00007ffb8a5e5598 32
0000022603d05658 00007ffb8a5e5598 32
0000022603d05810 00007ffb8925a7c8 80
0000022604373d48 00007ffb8a5e5598 32
0000022604373f00 00007ffb8925a7c8 80
000002260489e718 00007ffb8a5e5598 32
000002260489eaa8 00007ffb8925a7c8 80
00000226048b8dd8 00007ffb8a5e5598 32
00000226048b9100 00007ffb8925a7c8 80
0000022604b5b880 00007ffb8925a7c8 80
0000022604b5b8d0 00007ffb8a5e5598 32
0000022604b6df30 00007ffb8b12e310 192
0000022604e3dcc8 00007ffb8b12e310 192
0000022604f71c20 00007ffb8b12e310 192
0000022604f76f38 00007ffb8b12e310 192
0000022604f9a330 00007ffb8b12e310 192
0000022604f9dc18 00007ffb8b12e310 192
0000022605043750 00007ffb8b12e310 192
0000022605204a60 00007ffb8b12e310 192
00000226053934a0 00007ffb8b12e310 192
00000226054e6b08 00007ffb8b12e310 192
Statistics:
MT Count TotalSize Class Name
00007ffb8925b698 1 24 System.Net.Http.HttpClient+<>c
00007ffb8a5e5598 7 224 System.Net.Http.HttpClientHandler
00007ffb8925a7c8 7 560 System.Net.Http.HttpClient
00007ffb8b12e310 12 2304 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Net.Http.HttpResponseMessage, System.Net.Http],[System.Net.Http.HttpClient+<<SendAsync>g__Core|83_0>d, System.Net.Http]]
Total 27 objects
この結果から、System.Net.Http.HttpClientのMT(Method Table)は00007ffb8925a7c8であることがわかりました。Countからそのオブジェクトの個数は7個です。
MTからオブジェクトをダンプする
!DumpHeap /d -mt 00007ffb8925a7c8
0:000> !DumpHeap /d -mt 00007ffb8925a7c8
Address MT Size
0000022603cbfd80 00007ffb8925a7c8 80
0000022603cbfe00 00007ffb8925a7c8 80
0000022603d05810 00007ffb8925a7c8 80
0000022604373f00 00007ffb8925a7c8 80
000002260489eaa8 00007ffb8925a7c8 80
00000226048b9100 00007ffb8925a7c8 80
0000022604b5b880 00007ffb8925a7c8 80
これがオブジェクトのアドレスの一覧です。通常、アドレスが大きい方が新しいため、一番下の0000022604b5b880を選んで調査を進めます。
オブジェクトの詳細を確認する
!DumpObj /d 0000022604b5b880
0:000> !DumpObj /d 0000022604b5b880
Name: System.Net.Http.HttpClient
MethodTable: 00007ffb8925a7c8
EEClass: 00007ffb89286600
Tracked Type: false
Size: 80(0x50) bytes
File: C:\Program Files\UiPath\Studio\shared\Microsoft.NETCore.App\6.0.7\System.Net.Http.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007ffb882cbf28 400019c 10 System.Boolean 1 instance 0 _disposed
00007ffb882cbf28 400019d 11 System.Boolean 1 instance 1 _disposeHandler
00007ffb88d6a730 400019e 8 ...ttpMessageHandler 0 instance 0000022604b5b8d0 _handler
00007ffb882cbf28 400012d 12 System.Boolean 1 instance 1 _operationStarted
00007ffb882cbf28 400012e 13 System.Boolean 1 instance 0 _disposed
00007ffb892efd90 400012f 18 ...lationTokenSource 0 instance 0000022604b5ba88 _pendingRequestsCts
00007ffb8a61cac8 4000130 20 ...ttpRequestHeaders 0 instance 0000000000000000 _defaultRequestHeaders
00007ffb884f06b8 4000131 28 System.Version 0 instance 0000022603cbfbb8 _defaultRequestVersion
00007ffb8925a660 4000132 14 System.Int32 1 instance 0 _defaultVersionPolicy
00007ffb885ccbe0 4000133 30 System.Uri 0 instance 0000000000000000 _baseAddress
00007ffb88465568 4000134 40 System.TimeSpan 1 instance 0000022604b5b8c0 _timeout
00007ffb88369480 4000135 38 System.Int32 1 instance 2147483647 _maxResponseContentBufferSize
00007ffb887227a8 4000129 98 System.Net.IWebProxy 0 static 000002260291f460 s_defaultProxy
00007ffb88465568 400012a 80 System.TimeSpan 1 static 000002261a5b8b50 s_defaultTimeout
00007ffb88465568 400012b 88 System.TimeSpan 1 static 000002261a5b8b58 s_maxTimeout
00007ffb88465568 400012c 90 System.TimeSpan 1 static 000002261a5b8b60 s_infiniteTimeout
定義からHttpClient.DefaultProxyの型はSystem.Net.IWebProxyであり、またstatic変数であるため、s_defaultProxyという名前のフィールドが目的のものです。
s_defaultProxyのValueの指すアドレス000002260291f460を元に調査を進めます。
HttpClient.DefaultProxyの詳細を確認する
!DumpObj /d 000002260291f460
0:000> !DumpObj /d 000002260291f460
Name: System.Net.Http.HttpWindowsProxy
MethodTable: 00007ffb8925e078
EEClass: 00007ffb89287138
Tracked Type: false
Size: 152(0x98) bytes
File: C:\Program Files\UiPath\Studio\shared\Microsoft.NETCore.App\6.0.7\System.Net.Http.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007ffb8925dfc8 4000782 40 ...t.Http.MultiProxy 1 instance 000002260291f4a0 _insecureProxy
00007ffb8925dfc8 4000783 68 ...t.Http.MultiProxy 1 instance 000002260291f4c8 _secureProxy
00007ffb8925f9a8 4000784 8 ....FailedProxyCache 0 instance 000002260291f590 _failedProxies
00007ffb884de680 4000785 10 ...Private.CoreLib]] 0 instance 000002260291fa68 _bypass
00007ffb882cbf28 4000786 38 System.Boolean 1 instance 1 _bypassLocal
00007ffb8925fa38 4000787 18 ....Net.Primitives]] 0 instance 00000226029206c8 _localIp
00007ffb89258cf0 4000788 20 ....Net.ICredentials 0 instance 000002260291d868 _credentials
00007ffb8925e2d8 4000789 28 ...inInetProxyHelper 0 instance 000002260291db38 _proxyHelper
0000000000000000 400078a 30 ...SafeWinHttpHandle 0 instance 0000000000000000 _sessionHandle
00007ffb882cbf28 400078b 39 System.Boolean 1 instance 0 _disposed
ここで重要な情報としてNameがSystem.Net.Http.HttpWindowsProxyとなっています。ここからプロキシが環境変数をもとに設定されたことがわかります。
先に「HttpClient.DefaultProxyプロパティの値は環境変数またはシステムのプロキシ設定から自動的に読み込まれます」と述べましたが、プロキシを使用しないケースも含め、HttpClient.DefaultProxyに設定された変数の実際の型から、変数がどのソースを元に設定されたかが判別できます。
型 | 設定の読み込み元 |
---|---|
System.Net.Http.HttpNoProxy | - |
System.Net.Http.HttpEnvironmentProxy | 環境変数 |
System.Net.Http.HttpWindowsProxy | システム設定 |
それぞれの型の詳細については次のコードを参照してください。
-
System.Net.Http.HttpNoProxy
HttpNoProxy.cs -
System.Net.Http.HttpEnvironmentProxy
HttpEnvironmentProxy.cs
HttpEnvironmentProxy.Windows.cs -
System.Net.Http.HttpWindowsProxy
HttpWindowsProxy.cs
注: Windows以外のシステム(MacおよびLinux)に関してはここでは省略しています。
HttpEnvironmentProxyとHttpWindowsProxyの重要なフィールド
_credentials
_credentialsにはプロキシサーバーへアクセスする際の認証情報が保存されています。
これまでと同様に!DumpObj /d 000002260291d868
のように辿っていくことで詳細を確認することができます。
_bypass, _bypassLocal, _localIp
これらの値にはプロキシを使用せず通信を行う対象の情報が保存されています。
これまでと同様に!DumpObj /d 000002260291fa68
のように辿っていくことで詳細を確認することができます。
_insecureProxy, _secureProxy
型がHttpEnvironmentProxyとHttpWindowsProxyのいずれの場合も重要なフィールドとして_insecureProxyと_secureProxyがあります。
これらのフィールドにはプロキシサーバーの情報が保存されています。
注意すべき点としてはプロキシを通じた接続先のURLがhttp://
から始まるものである場合には_insecureProxyが使用され、https://
から始まる場合には_secureProxyが使用されるということです。
環境変数での設定とこれらの変数の対応は次の通りです。
環境変数 | 設定先 |
---|---|
HTTP_PROXY | _insecureProxy |
HTTPS_PROXY | _secureProxy |
ALL_PROXY | _insecureProxy, _secureProxy |
システム設定に関しても次の箇所が_insecureProxyと_secureProxyに対応します。
_insecureProxyと_secureProxyの詳細は次の手順で確認できます。
ここでは_insecureProxyを例に取ります。
!DumpVC /d 00007ffb8925dfc8 000002260291f4a0
Name: System.Net.Http.MultiProxy
MethodTable: 00007ffb8925dfc8
EEClass: 00007ffb892872a0
Size: 56(0x38) bytes
File: C:\Program Files\UiPath\Studio\shared\Microsoft.NETCore.App\6.0.7\System.Net.Http.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007ffb8925f9a8 4000714 0 ....FailedProxyCache 0 instance 000002260291f590 _failedProxyCache
00007ffb89292418 4000715 8 System.Uri[] 0 instance 000002260291fa48 _uris
00007ffb8837d698 4000716 10 System.String 0 instance 0000000000000000 _proxyConfig
00007ffb882cbf28 4000717 24 System.Boolean 1 instance 0 _secure
00007ffb88369480 4000718 20 System.Int32 1 instance 0 _currentIndex
00007ffb885ccbe0 4000719 18 System.Uri 0 instance 0000000000000000 _currentUri
00007ffb88468bf0 4000713 378 System.Char[] 0 static 000002260291f990 s_proxyDelimiters
これらは型としてはMultiProxyです。詳細については次のコードを参照してください。
MultiProxy.cs
プロキシサーバーの設定を_urisから辿っていきます。
!DumpObj /d 000002260291fa48
0:000> !DumpObj /d 000002260291fa48
Name: System.Uri[]
MethodTable: 00007ffb89292418
EEClass: 00007ffb882cb4e0
Tracked Type: false
Size: 32(0x20) bytes
Array: Rank 1, Number of elements 1, Type CLASS (Print Array)
Fields:
None
System.Uriが1つ格納された配列であることがわかります。
配列の中身を確認していきます。
!DumpArray /d 000002260291fa48
0:000> !DumpArray /d 000002260291fa48
Name: System.Uri[]
MethodTable: 00007ffb89292418
EEClass: 00007ffb882cb4e0
Size: 32(0x20) bytes
Array: Rank 1, Number of elements 1, Type CLASS
Element Methodtable: 00007ffb885ccbe0
[0] 000002260291fa10
配列内の唯一の要素を確認します。
!DumpObj /d 000002260291fa10
0:000> !DumpObj /d 000002260291fa10
Name: System.Uri
MethodTable: 00007ffb885ccbe0
EEClass: 00007ffb885e4f18
Tracked Type: false
Size: 56(0x38) bytes
File: C:\Program Files\UiPath\Studio\shared\Microsoft.NETCore.App\6.0.7\System.Private.Uri.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007ffb8837d698 4000027 8 System.String 0 instance 000002260291f9b8 _string
00007ffb8837d698 4000028 10 System.String 0 instance 0000000000000000 _originalUnicodeString
00007ffb885ce1f0 4000029 18 System.UriParser 0 instance 00000226025b8b10 _syntax
00007ffb885cca88 400002a 28 System.UInt64 1 instance 17181114395 _flags
00007ffb885f08c8 400002b 20 System.Uri+UriInfo 0 instance 0000000000000000 _info
00007ffb8837d698 4000016 18 System.String 0 static 00000226025b8980 UriSchemeFile
00007ffb8837d698 4000017 20 System.String 0 static 00000226025b8960 UriSchemeFtp
00007ffb8837d698 4000018 28 System.String 0 static 00000226027ecaf8 UriSchemeSftp
00007ffb8837d698 4000019 30 System.String 0 static 00000226027ecb18 UriSchemeFtps
00007ffb8837d698 400001a 38 System.String 0 static 00000226025b89a0 UriSchemeGopher
00007ffb8837d698 400001b 40 System.String 0 static 00000226025b88e0 UriSchemeHttp
00007ffb8837d698 400001c 48 System.String 0 static 00000226025b8900 UriSchemeHttps
00007ffb8837d698 400001d 50 System.String 0 static 00000226025b8920 UriSchemeWs
00007ffb8837d698 400001e 58 System.String 0 static 00000226025b8940 UriSchemeWss
00007ffb8837d698 400001f 60 System.String 0 static 00000226025b8a08 UriSchemeMailto
00007ffb8837d698 4000020 68 System.String 0 static 00000226025b89e8 UriSchemeNews
00007ffb8837d698 4000021 70 System.String 0 static 00000226025b89c8 UriSchemeNntp
00007ffb8837d698 4000022 78 System.String 0 static 00000226027ecb38 UriSchemeSsh
00007ffb8837d698 4000023 80 System.String 0 static 00000226025b8a50 UriSchemeTelnet
00007ffb8837d698 4000024 88 System.String 0 static 00000226025b8a98 UriSchemeNetTcp
00007ffb8837d698 4000025 90 System.String 0 static 00000226025b8ac0 UriSchemeNetPipe
00007ffb8837d698 4000026 98 System.String 0 static 00000226027c0058 SchemeDelimiter
00007ffb88468bf0 400002c a0 System.Char[] 0 static 00000226027ecb58 s_pathDelims
_stringに目的のデータが保存されています。
!DumpObj /d 000002260291f9b8
0:000> !DumpObj /d 000002260291f9b8
Name: System.String
MethodTable: 00007ffb8837d698
EEClass: 00007ffb88359d60
Tracked Type: false
Size: 86(0x56) bytes
File: C:\Program Files\UiPath\Studio\shared\Microsoft.NETCore.App\6.0.7\System.Private.CoreLib.dll
String: http://test-proxy-server:3128
Fields:
MT Field Offset Type VT Attr Value Name
00007ffb88369480 40002f2 8 System.Int32 1 instance 32 _stringLength
00007ffb882cea10 40002f3 c System.Char 1 instance 68 _firstChar
00007ffb8837d698 40002f1 e8 System.String 0 static 00000226025913a0 Empty
これで_insecureProxyにはhttp://test-proxy-server:3128
が設定されていたことがわかりました。
_secureProxyについても同様の手順を行い、設定値を確認することができます。
おわりに
以上がWinDbgで.NETアプリケーションが通信に使用したプロキシをメモリダンプから確認する方法の一例となります。
アプリケーションからのログ出力等からではトラブルシュートが難しい場合も、メモリダンプの解析を行うことでより核心に迫る調査が可能になります。
本稿をご覧いただきありがとうございました。
よいダンプ解析を!
補記: 環境変数からHttpClient.DefaultProxyプロパティの値を推測する
次のコマンドでProcess Environment Blockを表示し、Environmentセクションの内容からプロセスに与えられた全ての環境変数を確認することができます。
!peb
または、次のコマンドである環境変数の設定値を確認することもできます。
!envvar 環境変数名
HttpClient.DefaultProxyプロパティの仕様として、プロキシに関連した環境変数が存在すればその値がHttpClient.DefaultProxyプロパティに読み込まれ、システム設定からは読み込まれません。つまり、それらの環境変数が設定されていればHttpClient.DefaultProxyプロパティにはそれをもとに設定が行われていると推測することができます。
For Windows: Reads proxy configuration from environment variables or, if those are not defined, from the user's proxy settings.
そのため、環境変数が設定されているかどうかを先にチェックすることで本稿の手順をスキップし、迅速にトラブルシュートを進めることも可能です。
ただし、次のような見落としやすい点もあり、正確なトラブルシュートのためには実際にダンプの調査を行うことが確実です。
On systems where environment variables are case-sensitive, the variable names may be all lowercase or all uppercase. The lowercase names are checked first.