CLOVER🍀

That was when it all began.

socat+ncコマンドでTCP通信を転送しつつ、フォワードプロキシ越しにアクセスする

これは、なにをしたくて書いたもの?

最近、TCPプロキシサーバーを立ててみたり、プレーンなTCP通信をSSL/TLS化してフォワードプロキシ越しにトンネリングしたりして
遊んでいるのですが、

socatでTCPプロキシサーバーを立てる - CLOVER🍀

stunnelを使って、バックエンドにSSL/TLS通信しつつ、フォワードプロキシ越しにアクセスする - CLOVER🍀

そういえば、ふつうにTCP通信をプロキシする際に、フォワードプロキシ越しに転送するパターンをやってないなと思い。

やってみますか、と。

socatコマンドとncコマンドの組み合わせ、フォワードプロキシはApacheで実現してみました。

環境

今回の環境は、こちらです。Ubuntu Linux 18.04 LTSです。

$ uname -srvmpio
Linux 4.15.0-96-generic #97-Ubuntu SMP Wed Apr 1 03:25:46 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux


$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:    18.04
Codename:   bionic

利用するホストは、以下の4つ。

  • 192.168.33.1 … クライアント(curl/telnet)
  • 192.168.33.10 … TCPプロキシ
  • 192.168.33.11 … フォワードプロキシ(Apache)
  • 192.168.33.12 … Echoサーバー

1番奥にEchoサーバーを立て、クライアントからはTCPプロキシサーバーにアクセスすると、フォワードプロキシをHTTP CONNECTで
中継して1番奥のEchoサーバーに転送するということをやってみます。

Echoサーバー

まずは、1番奥のEchoサーバーを立てます。これは、socatで立てることにしましょう。

socatのインストール。

$ sudo apt install socat

バージョン。

$ socat -V
socat by Gerhard Rieger and contributors - see www.dest-unreach.org
socat version 1.7.3.2 on Apr  4 2018 10:06:49
   running on Linux version #97-Ubuntu SMP Wed Apr 1 03:25:46 UTC 2020, release 4.15.0-96-generic, machine x86_64
features:
  #define WITH_STDIO 1
  #define WITH_FDNUM 1
  #define WITH_FILE 1
  #define WITH_CREAT 1
  #define WITH_GOPEN 1
  #define WITH_TERMIOS 1
  #define WITH_PIPE 1
  #define WITH_UNIX 1
  #define WITH_ABSTRACT_UNIXSOCKET 1
  #define WITH_IP4 1
  #define WITH_IP6 1
  #define WITH_RAWIP 1
  #define WITH_GENERICSOCKET 1
  #define WITH_INTERFACE 1
  #define WITH_TCP 1
  #define WITH_UDP 1
  #define WITH_SCTP 1
  #define WITH_LISTEN 1
  #define WITH_SOCKS4 1
  #define WITH_SOCKS4A 1
  #define WITH_PROXY 1
  #define WITH_SYSTEM 1
  #define WITH_EXEC 1
  #undef WITH_READLINE
  #define WITH_TUN 1
  #define WITH_PTY 1
  #define WITH_OPENSSL 1
  #undef WITH_FIPS
  #define WITH_LIBWRAP 1
  #define WITH_SYCLS 1
  #define WITH_FILAN 1
  #define WITH_RETRY 1
  #define WITH_MSGLEVEL 0 /*debug*/

Echoサーバーは、catコマンドを使用して実現します。TCPポート5000でリッスンして、受け取った内容はcatで処理するのでEchoになりますね。

$ socat tcp-listen:5000,fork,reuseaddr exec:/bin/cat

ローカルで確認。

$ curl telnet://localhost:5000
Hello World!!
Hello World!!
foo 
foo
bar
bar

これで、Echoサーバーの準備は完了です。

フォワードプロキシサーバー(Apache)

次は、フォワードプロキシサーバーを立てます。Apacheで実現するので、Apacheをインストール。

$ sudo apt install apache2

mod_proxyおよびmod_proxy_connectを有効化します。

$ sudo a2enmod proxy proxy_connect

フォワードプロキシの設定は、こんな感じで。
/etc/apache2/sites-enabled/000-default.conf

Listen 8080
<VirtualHost *:8080>
  ProxyRequests On
  ProxyVia On

  AllowCONNECT 443 5000

  <Proxy *>
    Require host localhost
    Require ip 192.168.33.0/24
  </Proxy>

  ErrorLog ${APACHE_LOG_DIR}/proxy_error.log
  CustomLog ${APACHE_LOG_DIR}/proxy_access.log combined
</VirtualHost>

Echoサーバーはポート5000でリッスンしているので、AllowCONNECTを設定しています。また、アクセス元はローカルと同じサブネットの
ホストに限定しています。

Apacheを再起動して、準備完了

$ sudo systemctl restart apache2

TCPプロキシサーバーを立てる

最後は、TCPプロキシサーバーを立てます。どうやって実現しましょうか、と。

ncコマンドで、「-X」オプションでプロキシを使う時のプロトコルを指定できるので「connect」を指定し、「-x」オプションで
プロキシサーバーを指定できるので、これでApacheを指定します。最後に書いているのは、バックエンドのEchoサーバーの接続先ですね。
この状態で、まずは確認。

$ echo 'Hello World!!' | nc -Xconnect -x192.168.33.11:8080 192.168.33.12 5000
Hello World!!

結果が返ってきました。

Apacheのアクセスログを見ると、HTTP CONNECTを使ってアクセスできたことが確認できます。

192.168.33.10 - - [12/Apr/2020:05:37:25 +0000] "CONNECT 192.168.33.12:5000 HTTP/1.0" 200 90 "-" "-"

で、これをデーモンにしたいわけですが、ncコマンドでリッスンを行う「-l」オプションと、複数接続を受け付ける「-k」オプションを
指定すると、「プロキシと一緒には使えない」と言われます。

$ nc -l -k -Xconnect -x192.168.33.11:8080 192.168.33.12 5000
nc: no proxy support for listen

さて、どうしたものでしょうと思ったところで、socatを使ってncコマンドに流せばいいんじゃないかなぁと思い、socatコマンドを
インストール。

$ sudo apt install socat

さっそく、execでncコマンドを指定して実行してみます。

$ socat tcp-listen:8000,fork,reuseaddr exec:'/bin/nc -Xconnect -x192.168.33.11:8080 192.168.33.12 5000'

ですが、この状態でアクセスすると、「exec」に引数が多いと怒られます。

2020/04/12 05:55:56 socat[2487] E "exec:/bin/nc -Xconnect -x192.168.33.11": wrong number of parameters (2 instead of 1)

引数が多いのであれば、ncコマンドの部分をシェルスクリプトにしますか、と。
echo-proxy.sh

#!/bin/bash

nc -Xconnect -x192.168.33.11:8080 192.168.33.12 5000

execに作成したスクリプトを指定して、起動。

$ socat tcp-listen:8000,fork,reuseaddr exec:'./echo-proxy.sh'

クライアントからアクセス。今度はうまくいきます。

$ curl telnet://192.168.33.10:8000
Hello World!!
Hello World!!
foo
foo
bar
bar

Apacheのアクセスログも、確認。

192.168.33.10 - - [12/Apr/2020:05:57:47 +0000] "CONNECT 192.168.33.12:5000 HTTP/1.0" 200 90 "-" "-"

とりあえず、やりたいことは達成できましたね。

なお、このsocatで立ち上げたサーバーですが、全ネットワークインターフェースにバインドして公開されているので、アクセス制限などには
firewalldなどで対処しましょう。

$ ss -tnl | grep 8000
LISTEN   0         5                   0.0.0.0:8000             0.0.0.0:* 

オマケ:systemdに組み込む

最後に、作成したTCPプロキシサーバーを、systemdに組み込んでみましょう。

できる限り最小構成で、サービスユニット定義ファイルを作成。
/etc/systemd/system/echo-proxy.service

[Unit]
Description=echo proxy
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=root
Group=root
ExecStart=/usr/bin/socat tcp-listen:8000,fork,reuseaddr exec:'/home/vagrant/echo-proxy.sh'

[Install]
WantedBy=multi-user.target

反映。

$ sudo systemctl enable echo-proxy

これで、systemdに組み込んで起動できるようになりました、と。

$ sudo systemctl start echo-proxy