これは、なにをしたくて書いたもの?
socatを使って、プロキシサーバーが立てられそうだったので、試してみようかなと。
socatとは
socatとは、2つのストリーム間のデータ転送を行うコマンドラインツールです。
ストリームとしては、TCP、UDP、UNIXドメインソケット、ファイル、コマンドラインツールを扱ったりできます。SSL/TLSも扱えます。
環境
今回の環境は、こちらです。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
socatをインストールする
まずは、socatをインストールしましょう。Ubuntu Linuxの場合、aptでインストールできます。
$ sudo apt install socat
今回は、バージョン1.7.3.2がインストールされました。
$ 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*/
ヘルプを表示してみます。
$ socat -h socat by Gerhard Rieger and contributors - see www.dest-unreach.org Usage: socat [options] <bi-address> <bi-address> options: -V print version and feature information to stdout, and exit -h|-? print a help text describing command line options and addresses -hh like -h, plus a list of all common address option names -hhh like -hh, plus a list of all available address option names -d increase verbosity (use up to 4 times; 2 are recommended) -D analyze file descriptors before loop -ly[facility] log to syslog, using facility (default is daemon) -lf<logfile> log to file -ls log to stderr (default if no other log) -lm[facility] mixed log mode (stderr during initialization, then syslog) -lp<progname> set the program name used for logging -lu use microseconds for logging timestamps -lh add hostname to log messages -v verbose data traffic, text -x verbose data traffic, hexadecimal -b<size_t> set data buffer size (8192) -s sloppy (continue on error) -t<timeout> wait seconds before closing second channel -T<timeout> total inactivity timeout in seconds -u unidirectional mode (left to right) -U unidirectional mode (right to left) -g do not check option groups -L <lockfile> try to obtain lock, or fail -W <lockfile> try to obtain lock, or wait -4 prefer IPv4 if version is not explicitly specified -6 prefer IPv6 if version is not explicitly specified bi-address: pipe[,<opts>] groups=FD,FIFO <single-address>!!<single-address> <single-address> single-address: <address-head>[,<opts>] address-head: abstract-client:<filename> groups=FD,SOCKET,RETRY,UNIX abstract-connect:<filename> groups=FD,SOCKET,RETRY,UNIX abstract-listen:<filename> groups=FD,SOCKET,LISTEN,CHILD,RETRY,UNIX abstract-recv:<filename> groups=FD,SOCKET,RETRY,UNIX abstract-recvfrom:<filename> groups=FD,SOCKET,CHILD,RETRY,UNIX abstract-sendto:<filename> groups=FD,SOCKET,RETRY,UNIX create:<filename> groups=FD,REG,NAMED exec:<command-line> groups=FD,FIFO,SOCKET,EXEC,FORK,TERMIOS,PTY,PARENT,UNIX fd:<num> groups=FD,FIFO,CHR,BLK,REG,SOCKET,TERMIOS,UNIX,IP4,IP6,UDP,TCP,SCTP gopen:<filename> groups=FD,FIFO,CHR,BLK,REG,SOCKET,NAMED,OPEN,TERMIOS,UNIX interface:<interface> groups=FD,SOCKET ip-datagram:<host>:<protocol> groups=FD,SOCKET,RANGE,IP4,IP6 ip-recv:<protocol> groups=FD,SOCKET,RANGE,IP4,IP6 ip-recvfrom:<protocol> groups=FD,SOCKET,CHILD,RANGE,IP4,IP6 ip-sendto:<host>:<protocol> groups=FD,SOCKET,IP4,IP6 ip4-datagram:<host>:<protocol> groups=FD,SOCKET,RANGE,IP4 ip4-recv:<protocol> groups=FD,SOCKET,RANGE,IP4 ip4-recvfrom:<protocol> groups=FD,SOCKET,CHILD,RANGE,IP4 ip4-sendto:<host>:<protocol> groups=FD,SOCKET,IP4 ip6-datagram:<host>:<protocol> groups=FD,SOCKET,RANGE,IP6 ip6-recv:<protocol> groups=FD,SOCKET,RANGE,IP6 ip6-recvfrom:<protocol> groups=FD,SOCKET,CHILD,RANGE,IP6 ip6-sendto:<host>:<protocol> groups=FD,SOCKET,IP6 open:<filename> groups=FD,FIFO,CHR,BLK,REG,NAMED,OPEN,TERMIOS openssl:<host>:<port> groups=FD,SOCKET,CHILD,RETRY,IP4,IP6,TCP,OPENSSL openssl-listen:<port> groups=FD,SOCKET,LISTEN,CHILD,RETRY,RANGE,IP4,IP6,TCP,OPENSSL pipe:<filename> groups=FD,FIFO,NAMED,OPEN proxy:<proxy-server>:<host>:<port> groups=FD,SOCKET,CHILD,RETRY,IP4,IP6,TCP,HTTP pty groups=FD,NAMED,TERMIOS,PTY sctp-connect:<host>:<port> groups=FD,SOCKET,CHILD,RETRY,IP4,IP6,SCTP sctp-listen:<port> groups=FD,SOCKET,LISTEN,CHILD,RETRY,RANGE,IP4,IP6,SCTP sctp4-connect:<host>:<port> groups=FD,SOCKET,CHILD,RETRY,IP4,SCTP sctp4-listen:<port> groups=FD,SOCKET,LISTEN,CHILD,RETRY,RANGE,IP4,SCTP sctp6-connect:<host>:<port> groups=FD,SOCKET,CHILD,RETRY,IP6,SCTP sctp6-listen:<port> groups=FD,SOCKET,LISTEN,CHILD,RETRY,RANGE,IP6,SCTP socket-connect:<domain>:<protocol>:<remote-address> groups=FD,SOCKET,CHILD,RETRY socket-datagram:<domain>:<type>:<protocol>:<remote-address> groups=FD,SOCKET,RANGE socket-listen:<domain>:<protocol>:<local-address> groups=FD,SOCKET,LISTEN,CHILD,RETRY,RANGE socket-recv:<domain>:<type>:<protocol>:<local-address> groups=FD,SOCKET,RANGE socket-recvfrom:<domain>:<type>:<protocol>:<local-address> groups=FD,SOCKET,CHILD,RANGE socket-sendto:<domain>:<type>:<protocol>:<remote-address> groups=FD,SOCKET socks4:<socks-server>:<host>:<port> groups=FD,SOCKET,CHILD,RETRY,IP4,IP6,TCP,SOCKS4 socks4a:<socks-server>:<host>:<port> groups=FD,SOCKET,CHILD,RETRY,IP4,IP6,TCP,SOCKS4 stderr groups=FD,FIFO,CHR,BLK,REG,SOCKET,TERMIOS,UNIX,IP4,IP6,UDP,TCP,SCTP stdin groups=FD,FIFO,CHR,BLK,REG,SOCKET,TERMIOS,UNIX,IP4,IP6,UDP,TCP,SCTP stdio groups=FD,FIFO,CHR,BLK,REG,SOCKET,TERMIOS,UNIX,IP4,IP6,UDP,TCP,SCTP stdout groups=FD,FIFO,CHR,BLK,REG,SOCKET,TERMIOS,UNIX,IP4,IP6,UDP,TCP,SCTP system:<shell-command> groups=FD,FIFO,SOCKET,EXEC,FORK,TERMIOS,PTY,PARENT,UNIX tcp-connect:<host>:<port> groups=FD,SOCKET,CHILD,RETRY,IP4,IP6,TCP tcp-listen:<port> groups=FD,SOCKET,LISTEN,CHILD,RETRY,RANGE,IP4,IP6,TCP tcp4-connect:<host>:<port> groups=FD,SOCKET,CHILD,RETRY,IP4,TCP tcp4-listen:<port> groups=FD,SOCKET,LISTEN,CHILD,RETRY,RANGE,IP4,TCP tcp6-connect:<host>:<port> groups=FD,SOCKET,CHILD,RETRY,IP6,TCP tcp6-listen:<port> groups=FD,SOCKET,LISTEN,CHILD,RETRY,RANGE,IP6,TCP tun[:<ip-addr>/<bits>] groups=FD,CHR,NAMED,OPEN,INTERFACE udp-connect:<host>:<port> groups=FD,SOCKET,IP4,IP6,UDP udp-datagram:<host>:<port> groups=FD,SOCKET,RANGE,IP4,IP6,UDP udp-listen:<port> groups=FD,SOCKET,LISTEN,CHILD,RANGE,IP4,IP6,UDP udp-recv:<port> groups=FD,SOCKET,RANGE,IP4,IP6,UDP udp-recvfrom:<port> groups=FD,SOCKET,CHILD,RANGE,IP4,IP6,UDP udp-sendto:<host>:<port> groups=FD,SOCKET,IP4,IP6,UDP udp4-connect:<host>:<port> groups=FD,SOCKET,IP4,UDP udp4-datagram:<remote-address>:<port> groups=FD,SOCKET,RANGE,IP4,UDP udp4-listen:<port> groups=FD,SOCKET,LISTEN,CHILD,RANGE,IP4,UDP udp4-recv:<port> groups=FD,SOCKET,RANGE,IP4,UDP udp4-recvfrom:<host>:<port> groups=FD,SOCKET,CHILD,RANGE,IP4,UDP udp4-sendto:<host>:<port> groups=FD,SOCKET,IP4,UDP udp6-connect:<host>:<port> groups=FD,SOCKET,IP6,UDP udp6-datagram:<host>:<port> groups=FD,SOCKET,RANGE,IP6,UDP udp6-listen:<port> groups=FD,SOCKET,LISTEN,CHILD,RANGE,IP6,UDP udp6-recv:<port> groups=FD,SOCKET,RANGE,IP6,UDP udp6-recvfrom:<port> groups=FD,SOCKET,CHILD,RANGE,IP6,UDP udp6-sendto:<host>:<port> groups=FD,SOCKET,IP6,UDP unix-client:<filename> groups=FD,SOCKET,NAMED,RETRY,UNIX unix-connect:<filename> groups=FD,SOCKET,NAMED,RETRY,UNIX unix-listen:<filename> groups=FD,SOCKET,NAMED,LISTEN,CHILD,RETRY,UNIX unix-recv:<filename> groups=FD,SOCKET,NAMED,RETRY,UNIX unix-recvfrom:<filename> groups=FD,SOCKET,NAMED,CHILD,RETRY,UNIX unix-sendto:<filename> groups=FD,SOCKET,NAMED,RETRY,UNIX
使い方
最初の紹介に、socatは2つのストリームをつなげるコマンドラインツールです。
ヘルプにちらっと書いているのですが、「bi-address」の部分にストリームを定義していきます。
socat [options] <bi-address> <bi-address>
「bi-address」の書式。
bi-address: pipe[,<opts>] groups=FD,FIFO <single-address>!!<single-address> <single-address> single-address: <address-head>[,<opts>]
「address-head」の例。TCP関連です。
tcp-connect:<host>:<port> groups=FD,SOCKET,CHILD,RETRY,IP4,IP6,TCP tcp-listen:<port> groups=FD,SOCKET,LISTEN,CHILD,RETRY,RANGE,IP4,IP6,TCP tcp4-connect:<host>:<port> groups=FD,SOCKET,CHILD,RETRY,IP4,TCP tcp4-listen:<port> groups=FD,SOCKET,LISTEN,CHILD,RETRY,RANGE,IP4,TCP tcp6-connect:<host>:<port> groups=FD,SOCKET,CHILD,RETRY,IP6,TCP tcp6-listen:<port> groups=FD,SOCKET,LISTEN,CHILD,RETRY,RANGE,IP6,TCP
address-headにはオプションを指定することができ、どのようなものが指定できるかはgroupsと突き合わせて見ることになります。
-h|-? print a help text describing command line options and addresses -hh like -h, plus a list of all common address option names -hhh like -hh, plus a list of all available address option names
たとえば、forkというオプションはCHILDに属し、intervalというオプションはRETRYに属します。
fork groups=CHILD phase=PASTACCEPT type=BOOL interval groups=RETRY phase=INIT type=STRUCT-TIMESPEC
なので、先ほど挙げたTCP関連のaddress-headで使うことができますね。
オプションの説明は、こちらのページを見たらよさそうです。
https://linux.die.net/man/1/socat
fork、intervalの例だと、CWfork、CWintervalとかですね。
とまあ、説明はこれくらいにして使っていってみましょう。
TCPサーバーを立ててみる
最初は、簡単なTCPサーバーを立ててみましょう。
以下で、「tcp-listen」を使い、指定したTCP ポート(今回は8000)でリッスンして、受信した内容を標準出力(stdout)に流すサーバーに
なります。
$ socat tcp-listen:8000 stdout
コマンドとしては、このままフォアグラウンドで受信待ちになります。
telnetで、メッセージを送信してみます。
$ curl telnet://localhost:8000 Hello World!!
socat側。送信されてきたメッセージが標準出力に現れたことが確認できました。
$ socat tcp-listen:8000 stdout Hello World!!
ところで、このsocatのプロセスですが、telnetの接続を切ると、終了してしまいます。
この動きを変更して、複数の接続を受け付けられるようにするには「fork」オプションを指定します。
$ socat tcp-listen:8000,fork stdout
これで、telnetを切断しても終了しなくなりました。
あとは再起動時に同じアドレスへのバインドがすぐにできるように、reuseaddrもつけておくと良いでしょう。
$ socat tcp-listen:8000,fork,reuseaddr stdout
ところで、tcp-listenがバインドするアドレスは、「0.0.0.0」みたいですね。
$ ss -tnl | grep 8000 LISTEN 0 5 0.0.0.0:8000 0.0.0.0:*
プロキシサーバーを立ててみる
次は、socatを使って別のサーバーに対するプロキシサーバーを立ててみましょう。
socatを使っているサーバーとは、別のサーバーを用意してApacheをインストールします。
$ sudo apt install apache2
このサーバーのIPアドレスは、「192.168.33.11」とします。
socatで、このApacheに対するTCPプロキシサーバーを立ててみます。「tcp-listen」で接続を待ち受け、送信先のサーバーは「tcp-connect」で
指定します。
$ socat tcp-listen:8000,fork,reuseaddr tcp-connect:192.168.33.11:80
「tcp-connect」の場合は、送信先のIPアドレスを指定することができます。「tcp-listen」の場合は指定できません。
ヘルプで記載されていた、「address-head」の定義がこうでしたからね。
tcp-connect:<host>:<port> groups=FD,SOCKET,CHILD,RETRY,IP4,IP6,TCP tcp-listen:<port> groups=FD,SOCKET,LISTEN,CHILD,RETRY,RANGE,IP4,IP6,TCP
socat側で確認。
$ curl -I localhost:8000 HTTP/1.1 200 OK Date: Sat, 11 Apr 2020 05:07:09 GMT Server: Apache/2.4.29 (Ubuntu) Last-Modified: Sat, 11 Apr 2020 05:05:41 GMT ETag: "2aa6-5a2fccd2ca128" Accept-Ranges: bytes Content-Length: 10918 Vary: Accept-Encoding Content-Type: text/html
OKそうですね。
ところで、こういうのを使う時には無条件に転送するのも良くないケースはあると思うので(tcp-listenは全ネットワークインターフェースに
バインドしてますし)、firewalld(Ubuntu Linuxだとufw)などで適宜接続元を制限するなどしましょう。
systemdに組み込んでみる
オマケとして、このサーバーをsystemdに組み込んでdaemon化してみましょう。
サービスユニット定義ファイルを用意。ほぼ最小です。
/etc/systemd/system/socat-http-proxy.service
[Unit] Description=socat http 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 tcp-connect:192.168.33.11:80 [Install] WantedBy=multi-user.target
反映。
$ sudo systemctl enable socat-http-proxy
起動。
$ sudo systemctl start socat-http-proxy
確認。
$ curl -I localhost:8000 HTTP/1.1 200 OK Date: Sat, 11 Apr 2020 05:19:13 GMT Server: Apache/2.4.29 (Ubuntu) Last-Modified: Sat, 11 Apr 2020 05:05:41 GMT ETag: "2aa6-5a2fccd2ca128" Accept-Ranges: bytes Content-Length: 10918 Vary: Accept-Encoding Content-Type: text/html
停止。
$ sudo systemctl stop socat-http-proxy
こんなところですね。