これは、なにをしたくて書いたもの?
最近、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
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