今回はAndroidのTermuxの記事です。
Termuxは、Androidで使えるLinuxシェルっぽいものの中では最も「軽い」部類に属するもので(多分)、個人的にはこういうrootなし環境で試行錯誤するのが結構楽しいのでお気に入りです。世間的にはもう少しまともな(かわりに少し重い)Linuxであるprootを使うほうが人気な気もするのですが…
TailscaleはVPNを構築してくれるサービスです。しかしAndroidではVPNを同時に1つしか実行できません。仕事用プロファイルを入れれば2つにはできますが、ちょっと大げさな感じです。なのでTermuxで動くといいのですが、Tailscaleのコマンド(CLI)はAndroidをサポートしていません。まあTermuxにはrootが無い上にlibcとかもちょっと特殊なので普通の形でTailscaleを動かすのは難しいです。
しかしTailscaleにはuserspace networking modeというのもあり、これは何かというとrootがない環境でもSOCKS5プロキシやHTTPプロキシの形でTailscaleのネットワーク(tailnet)への接続を利用できるという、制限された動作モードです。これであれば本質的にrootを必要としないのでTermuxでもできるはずです。
実際、TailscaleはgoでAPIライブラリが公開されていて、特にtsnetというのを使うと普通のnetと同じような感じでtailnetにむけてDialとかListenとかができます。
そこでこれを使ってts-proxyという、Tailscale経由でのポート転送、SOCKS5プロキシ転送ができるソフトウェアを実装しました。AndroidだけでなくLinux・Windows・macOSでも動くはずです(Linux以外でまだ試していません)。
ts-proxyの使い方
$ ts-proxy -h
Usage of ts-proxy:
-debug
enable debug mode
-fwd-socks value
Forward SOCKS: 'bind_addr=tailscale_addr'
-hostname string
Tailscale device hostname (default "ts-proxy")
-serve-socks value
Serve SOCKS: 'tailscale_addr[,outaddr_config...]'
-tcp value
TCP forward rule: 'bind_addr=[TLS=]connect_addr'
-tcp-timeout int
TCP timeout in seconds (default 1100)
-tsnet-dir string
Directory for Tailscale credentials
-udp value
UDP forward rule: 'bind_addr=connect_addr'
-udp-timeout int
UDP timeout in seconds (default 330)
usageはこんな感じになっています。
基本的に①TCPポート転送②UDPポート転送③SOCKSプロキシ転送の3つの機能があります。①②についてはbind_addr=connect_addrという書式で、前者にアクセスがあった場合に後者に転送します。bind_addrとconnect_addrには通常の(ローカルの)アドレス(とポート)あるいはTailnet上のアドレス(とポート)をいずれも指定できます。もちろん両方にローカルを指定するなどする場合はsocatなど既存のものを使うほうがいいと思いますが。
SOCKSプロキシ転送についてはローカル→Tailnetの場合(-fwd-socks)とTailnet→ローカルの場合(-serve-socks)でオプションを分けてあります。統一すると使い方も実装もわかりづらくなるからです。
アドレスの書式など詳しくはGitHubを見て下さい。。その他雑多なオプションが指定できます。
起動すると、TS_AUTHKEY変数を設定していない場合、普通のTailscaleのログイン用のURLが出力されるので(←これがtsnetの標準的な挙動)、認証して接続するとポート転送などが開始されます。
Androidでのuserspace networking modeサポートに関するfeature request(FR: Userspace networking mode support for Android client · Issue #10126 · tailscale/tailscale · GitHub)のところでも一応宣伝しておきました。
anetについて
最近のAndroidで動かすにあたって、Goの実装が微妙なためnet.Interfaces()やnet.InterfaceAddrs() というネットワーク構成を取得するための関数がうまく動かないという問題が発生します。これに対処するライブラリとしてanetというのがあり、これを利用させるようにTailscaleのGoライブラリ側にパッチを当てました。
TLSについて
TCPポート転送では"TLS="と書くことにより接続をTLSでラップすることができます。このTLSはTailscaleの機能で、コントロールパネルから有効にしておけば、TLSでアクセスしたときに勝手にLet's Encryptで証明書を取ってきて、必要なら更新もしてくれるという優れものです。しかしTailscaleのGoライブラリの実装がおかしく、AndroidだとLet's Encryptにアクセスする部分のAPIが潰されてしまっています。これもパッチを当てました。
使ってみる
実際にcode-server(なんちゃってVS Codeみたいなやつ)で接続してみることができました。
クリップボードなど一部の操作はHTTPSがないと有効化されないのですが、そこも問題なくできるようになっています。
ターミナルなども使えて便利です。これがあれば十分なのでsshは無効化しました。
その他にも自宅鯖経由のSOCKS5プロキシとかも建てました。
Phantom Process kill問題
Termuxを実用するにあたって、phantom process killという重要な問題があります。これはAndroid全体でプロセス数の上限として32(デフォルト)が設定されており、これよりプロセス数が多くなると使ってなさそうor古いプロセスから順に不規則にkillされてしまうという問題です。
これによりTermux上で何かを常駐させるときには是が非でもプロセスの数を少なくする必要が生じます。
自分の場合、go製の常駐プログラム的なものを3つくらい動かしていたのですがそれらを全て単一のバイナリに統合して動かすことにしました。ts-proxyもこのようなライブラリとしての利用も想定した形にしてあります。
プロセスが1個増えることにはなりますがwatchdog的なものを立てておくのもありだと思います。体感、軽めのプロセスはkillされにくい印象があります。
シェルスクリプトとかで書くとプロセス数が増えてしまうのでそのへんもgoとかで書かざるを得ないのがちょっと面倒なところです。