.NETアプリケーションがHTTP通信に使用したプロキシ設定をメモリダンプから確認する

## はじめに
本稿では.NETアプリケーションがHTTP通信に使用したプロキシ設定をメモリダンプから確認する方法の一例をご紹介します。
WinDbgの一般的な使用方法に触れるため、やや遠回りしている部分もありますがご容赦ください。

## 開始前のインプット
### 対象となるアプリケーション
本稿の内容は.NET Core 3.0, Core 3.1, 5, 6, 7, 8等の比較的新しいランタイムで動作するアプリケーションを前提としています。
これは本稿の主眼であるHttpClient.DefaultProxyプロパティがそれ以前のランタイムではサポートされていないためです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3331558/362f51bd-dc3e-c963-e640-f945272d4f5f.png)

参考: 
[HttpClient.DefaultProxy Property > Applies to](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.defaultproxy)

### HttpClientクラスとHttpClient.DefaultProxyプロパティ
HttpClientクラスは.NETアプリケーションがHTTP通信のために使用する標準のクラスです。
HttpClient.DefaultProxyプロパティはHttpClientクラスがHTTP通信のために使用するプロキシの情報を保持する**static変数**です。
定義は次のものです。

```
public static System.Net.IWebProxy DefaultProxy { get; set; }
```
参考: 
[HttpClient.DefaultProxy Property > Definition](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.defaultproxy)


HttpClient.DefaultProxyプロパティの値は環境変数またはシステムのプロキシ設定から自動的に読み込まれます。
この読み込みの挙動は複雑なものとなっているためここでは解説を割愛します。
詳細については次のガイドを参照してください。

参考: 
[HttpClient.DefaultProxy Property > Remarks](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.defaultproxy)


## 事前準備

### 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](https://github.com/dotnet/runtime/blob/5a5ab5962e20d10718b326dda56dfc9285be9acf/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpNoProxy.cs)

- System.Net.Http.HttpEnvironmentProxy
[HttpEnvironmentProxy.cs](https://github.com/dotnet/runtime/blob/5a5ab5962e20d10718b326dda56dfc9285be9acf/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpEnvironmentProxy.cs)
[HttpEnvironmentProxy.Windows.cs](https://github.com/dotnet/runtime/blob/5a5ab5962e20d10718b326dda56dfc9285be9acf/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpEnvironmentProxy.Windows.cs)
- System.Net.Http.HttpWindowsProxy
[HttpWindowsProxy.cs](https://github.com/dotnet/runtime/blob/5a5ab5962e20d10718b326dda56dfc9285be9acf/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/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に対応します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3331558/3da343e0-73c2-8f3c-044e-d72ca97160a6.png)


_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](https://github.com/dotnet/runtime/blob/5a5ab5962e20d10718b326dda56dfc9285be9acf/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/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.

参考: 
[HttpClient.DefaultProxy Property > Remarks](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.defaultproxy)