備忘録

備忘録

C#でHttpClientにSOCKS Proxyを利用する方法

Ⅰ. はじめに

タイトルの通り「C#でHttpClientにSOCKS プロキシを利用する方法」です。

各ライブラリの違いは次のとおりです。

# ライブラリ名 メモ
1 なし .NET 6以上
2 HttpToSocks5Proxy SOCKS5のみ
3 DotNet4.SocksProxy SOCKS4/5 に対応
4 SocksSharp SOCKS4/4a/5 に対応。バグが多い

Ⅱ. やり方(.NET6以上の場合)

1. サンプルプログラム
// .NET 6以上必須
// .NET6以上でSocks4, 4a, 5がサポートされた
// https://github.com/dotnet/runtime/pull/48883

static async Task Main()
{
  var handler = new HttpClientHandler
  {
    // 認証なし
    Proxy = new WebProxy(new Uri("socks5://127.0.0.1:9050"))

    // 認証あり
    // Proxy = new WebProxy(new Uri("socks5://127.0.0.1:9050"), true, null, new NetworkCredential("user", "pass"))
  };
  var httpClient = new HttpClient(handler);
  var str = await httpClient.GetStringAsync("https://api.ipify.org/").ConfigureAwait(false);
  Console.WriteLine(str);
}
2. 実行結果

省略

Ⅲ. やり方(HttpToSocks5Proxyを利用する方法)

HttpToSocks5Proxy を利用します。
HttpToSocks5Proxy はSOCKS5のみに対応しています。(2018/09/17時点)

1. NuGetから HttpToSocks5Proxy をインストールする
Install-Package HttpToSocks5Proxy
2. サンプルプログラム
var proxy = new HttpToSocks5Proxy("46.x.x.x", 1080);
var handler = new HttpClientHandler();
handler.Proxy = proxy;
handler.UseProxy = true;
var client = new HttpClient(handler);
var str  = await client.GetStringAsync("https://api.ipify.org");
Console.WriteLine(str);
3. 実行結果

f:id:kagasu:20180917063524p:plain

Ⅳ. やり方(DotNet4.SocksProxyを利用する方法)

DotNet4.SocksProxy を利用します。
DotNet4.SocksProxy は SOCKS4/5 に対応しています。(2018/02/21時点)

1. NuGetから DotNet4.SocksProxy をインストールする
Install-Package DotNet4.SocksProxy
2. サンプルプログラム
using com.LandonKey.SocksWebProxy;
using com.LandonKey.SocksWebProxy.Proxy;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;

namespace Test
{
  class Program
  {
    private static int GetNextFreePort()
    {
      var listener = new TcpListener(IPAddress.Loopback, 0);
      listener.Start();
      var port = ((IPEndPoint)listener.LocalEndpoint).Port;
      listener.Stop();

      return port;
    }

    static void Main(string[] args)
    {
      var proxy = new SocksWebProxy(new ProxyConfig(
        IPAddress.Parse("127.0.0.1"),
        GetNextFreePort(),
        IPAddress.Parse("127.0.0.1"),
        9050,
        ProxyConfig.SocksVersion.Five));

      var handler = new HttpClientHandler();
      handler.Proxy = proxy;
      var client = new HttpClient(handler);

      Console.WriteLine(client.GetStringAsync("https://api.ipify.org").Result);
    }
  }
}
3. 実行結果

f:id:kagasu:20180221085227p:plain

Ⅴ. やり方(SocksSharpを利用する方法)

SocksSharp を利用します。
SocksSharp は SOCKS4/4a/5 に対応しています。(2018/02/21時点)
POSTが正常に出来ない、Streamに対応していない等バグが多いです。(2018/02/21時点)

1. NuGetから SocksSharp をインストールする
Install-Package SocksSharp
2. サンプルプログラム
using SocksSharp;
using SocksSharp.Proxy;
using System;
using System.Net.Http;

namespace HttpClientTest
{
  class Program
  {
    static void Main(string[] args)
    {
      var settings = new ProxySettings()
      {
        Host = "127.0.0.1",
        Port = 1080
      };

      var handler = new ProxyClientHandler<Socks5>(settings);
      var client = new HttpClient(handler);
      Console.WriteLine(client.GetStringAsync("https://api.ipify.org").Result);
    }
  }
}
3. 実行結果

f:id:kagasu:20170826174528p:plain

CentOSでSOCKSサーバ(Dante)を構築する

Ⅰ. はじめに

Inferno Nettverk A/S によって開発されているSOCKS4,5サーバ Dante の構築方法です。

UbuntuでSOCKSサーバ(Dante)を構築する方法はこちら
https://kagasu.hatenablog.com/entry/2020/09/28/075504

Ⅱ. 環境

$ cat /etc/redhat-release 
CentOS Linux release 7.3.1611 (Core) 
$ uname -a
Linux xxx 3.10.0-514.6.2.el7.x86_64 #1 SMP Thu Feb 23 03:04:39 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

Ⅲ. サーバ構築方法

1. ソースコードをダウンロードする
wget https://www.inet.no/dante/files/dante-1.4.2.tar.gz
2. ビルドに必要なものをインストールする
yum install -y gcc make rpm-build bison flex pam-devel
3. ビルドする(ソースコードからrpmを作る)
rpmbuild -ta dante-1.4.2.tar.gz
4. インストールする
rpm -ivh rpmbuild/RPMS/x86_64/dante-1.4.2-1.el7.x86_64.rpm
rpm -ivh rpmbuild/RPMS/x86_64/dante-server-1.4.2-1.el7.x86_64.rpm
5. 設定する
vim /etc/sockd.conf
vim /etc/socks.conf

以下のサンプルは「全IPアドレスから認証なしでアクセス可能」になっています。

6. ポートを開放する
firewall-cmd --zone=public --add-port=1080/tcp --permanent
firewall-cmd --reload

7. サーバを起動する

systemctl restart sockd

C# 7.1 非同期Mainを使う

Ⅰ. はじめに

C# 7.1の新機能の1つに「非同期Main」があります。
C# 7までは Main関数で async/await は利用不可でした。

C# 7 まで
static void Main(string[] args) => MainAsync().Wait();

static async Task MainAsync()
{
  await Task.Delay(1);
  Console.WriteLine("Hello MainAsync");
}
C# 7.1 の「非同期Main」
static async Task Main(string[] args)
{
  await Task.Delay(1);
  Console.WriteLine("Hello MainAsync");
}

Ⅱ. 非同期Mainを使う方法

C#7.1 を有効にする必要があります。
.csproj を開き <LangVersion> を追加するだけです。

<Project>
  <PropertyGroup>
    ...
    <LangVersion>7.1</LangVersion>
    ...
  </PropertyGroup>
</Project>

※2017/12/14追記
VisualStudioの画面で、プロジェクトのプロパティ→ビルド→詳細設定→言語バージョン→C# 7.1を選択
でも設定できます。

Ⅲ. 実行結果

f:id:kagasu:20170820211938p:plain

C# Zlibで圧縮、展開する

Ⅰ. はじめに

Zlibのマジックナンバーは2バイトです。
「78 DA」「78 01」「78 9C」等がメジャーです。
詳しくはRFC 1950で定義されています。
https://www.ietf.org/rfc/rfc1950.txt

Ⅱ. やり方

NuGetパッケージをインストールする

Install-Package Iconic.Zlib.Netstandard

圧縮

bytes = ZlibStream.CompressBuffer(bytes);

展開

bytes = ZlibStream.UncompressBuffer(bytes);

HttpClient で任意のHTTPヘッダを指定した時 FormatException が発生する

Ⅰ. はじめに

通常HttpClientでHTTPヘッダを指定する場合は以下のように書くことが出来ます。

var client = new HttpClient();
client.DefaultRequestHeaders.Add("User-Agent", "hoge");

しかし、User-AgnetやAuthorization等のよく知られたヘッダは自動的にパース、検証が行われ*1、誤った値を指定するとSystem.FormatExceptionが発生します。

static void Main(string[] args)
{
  var client = new HttpClient();
  client.DefaultRequestHeaders.Add("User-Agent", "foo=bar");

  var statusCode = client.GetAsync("http://example.com").Result.StatusCode;
  Console.WriteLine(statusCode);
}

実行結果

Unhandled Exception:
  System.FormatException:
    The format of value 'foo=bar' is invalid.

Ⅱ. 検証を回避する方法

Add の代わりに TryAddWithoutValidation*2 を使います。

static void Main(string[] args)
{
  var client = new HttpClient();
  client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "foo=bar");

  var statusCode = client.GetAsync("http://example.com").Result.StatusCode;
  Console.WriteLine(statusCode);
}

実行結果

OK