双方向通信をPiping Server上で実現するCLIとGoライブラリ(E2E暗号化付き)
やりたいこと
動機としては、リモートでの作業が増えてる中、安全な通信を確保したい。 記事の最後はポート開放せずにHTTP上でSSHを実現している。 以下で開発している。CLIとして、ライブラリとして使えその使い方などをこのページ書く。 https://gh-card.dev/repos/nwtgck/go-piping-duplex.svg https://github.com/nwtgck/go-piping-duplex
インストール
シンプルな例
以下のようにして使う。IDは任意で衝突を避けるため長くすることができる。
$ piping-duplex 自分のID 通信相手のID
自分の標準入力が相手の標準出力に、
相手の標準入力が自分の標準出力に流れる。
https://gyazo.com/d35f52b37079b8d5baef8b58732255b4
これだけでもリアルタイムに流れる簡易チャットのようになる。
無限
無限のバイトストリームが扱える。
例えば、AさんとBさんが、以下のコマンドを打つ例。
Aさん:
$ cat /dev/urandom | ./piping-duplex aaa bbb
Bさん:
$ seq inf | ./piping-duplex bbb aaa | base64
https://gyazo.com/d65a9efaf7a96a613c120fd785855de2
Bさんから流れるseq infも無限に流れるがそれもAさんに届く。
テキストやランダムバイト列に限らず、意味のあるプロトコルを流すこともできる。
以下をSSHサーバーが立っているサーバーで叩く。
$ socat 'EXEC:./piping-duplex aaa bbb' TCP:127.0.0.1:22
以下をSSHクライアント側で叩く。
$ socat TCP-LISTEN:31376 'EXEC:.piping-duplex bbb aaa'
以下をSSHクライアント側の新しいターミナルで叩く。
$ ssh -p 31376 localhost
E2E暗号化したいときは、-cオプションまたは--symmetricオプションをつけてパスワードで端末間で暗号化できる。 手軽に暗号化できるように、オプションも作った。
https://gyazo.com/ea0ab0da2afce70e1b854ac0b4ea6dbc
経由させるサーバーを指定
-sまたは--serverオプションで変更可能。
$ docker run -p 8181:8080 nwtgck/piping-server
ライブラリとして使う例や応用例
github.com/nwtgck/go-piping-duplex をライブラリとして使う例。
code:go
// v0.2.0時点での使い方
import (
"github.com/nwtgck/go-piping-duplex"
"os"
"strings"
"io"
)
func main() {
aId := "aaa"
bId := "bbb"
go piping_duplex.Duplex(server, aId, bId, strings.NewReader("hello, world\n"))
r, _ := piping_duplex.Duplex(server, bId, aId, strings.NewReader("hoge"))
// A -> B に送られた "hello, world\n" を標準出力に表示
io.Copy(os.Stdout, r)
}
piping_duplex.Duplex(..., ..., ..., r)は相手に届けたいio.Reader rを渡して、相手からの応答 io.Readerが戻り値になる関数。
例えば、自分の音声をマイクから集音したio.Readerを渡して、相手の音声のio.Readerをスピーカーに出力すると原理的には通話ができる。エコーキャンセラがあるとより実用的になる印象だった。 音に限らず、カメラ映像を送って、相手のカメラ映像を画面に出力するとビデオ会議的なものが実現できるはず。
$ rec -t wav -c 1 -r 16k - | ./piping-duplex aaa bbb | play -t wav -
仕組み
つまり、
AさんはPOST /aaaとGET /bbbをして、
BさんはPOST /bbbとGET /aaaをしている。
得体の知れないプロトコルではなく、よく知られたHTTPの上で動くという安心感はある。 まず、2つリクエストを送っていてもHTTP/2に対応しているため、単一のTCPコネクションにまとまる。 また、HTTP/2だとネイティブでストリームに対応していて、Transfer-Encoding: chunked もより最適になるという風に理解している。 HTTPがオーバースペックだと言われるのは、ヘッダが大きいという理由が多いと思う。Piping Duplexの使い方はとしては接続しっぱなしの2つのリクエストだけ、最初にヘッダ送られた後無限のボディが続く。 HTTPの接続がどれだけ続くのかに関しての疑問も出てきそう。それに関しては、以下が一つの答えかも知れない。 87日間同じHTTPリクエストが継続して、データ的には45TB送れているという実験。この手の実験は何回かやっている。
3ヶ月近く同じHTTPリクエストできいる。普通電話を同じ人と3ヶ月間することも少ない。HTTP/3でUDP上で動くようになったり、コネクションマイグレーションでIPアドレスの変更があっても接続が維持されたりするらしい。HTTPは枯れた安定した技術でありながら、進化もしていて、生HTTPにも可能性を感じている。