PySkyWiFi: 長距離フライトで無料の低速Wi-Fiを作る
(robertheaton.com)- PySkyWiFiは、決済前でも開ける航空マイレージアカウントのプロフィール欄を一時ストレージとして使い、長距離フライト中にHTTPリクエストとレスポンスを非常に低速で往復させる実験
- 初期プロトタイプは、アカウントの名前フィールドを更新・ポーリングしてチャットを作り、その後、地上デーモンが
STOCKPRICE: APPLのようなリクエストを読み取って結果を返す方式へ拡張された - 最終構成は、機内のsky proxyとインターネット接続されたground daemonに分かれ、ローカルの
curlリクエストのX-PySkyWiFiヘッダーで対象URLを渡す - 転送はHTTPリクエストを小さなチャンクに分割し、
DATA、ACK、ENDセグメントと6桁のシーケンス番号で順序を合わせる方式 - 実際の航空会社アカウントに自動リクエストを送り続ける方式は避け、最終テストはGitHub Gistでブログのホームページを
curlで呼び出し、数分後にHTMLレスポンスを受け取るレベルにとどまった
決済前のWi-Fiポータルで見つけた迂回経路
- 機内Wi-Fiのログインページは決済を要求したが、決済前でも航空マイレージアカウントへのログインは無料で許可されていた
- プロフィール編集画面の名前フィールドを見て、この値を外部とやり取りする小さな通信チャネルのように使えると考えた
- 実装言語は最初Goを考えたが、Pythonを使えばツール名をPySkyWiFiにできるためPythonを選んだ
- ソースコードはPySkyWiFiで公開されている
プロトタイプ1: 名前フィールドで作ったインスタントメッセージング
- 同じ航空マイレージアカウントに2つのクライアントがログインし、名前フィールドを交互に更新して読み取ることでメッセージをやり取りする
- 一方が名前を
Hello how are youに変えると、もう一方がそれを読む - 次に
Im fine how are youへ変更すると、最初の側が返信を確認する
- 一方が名前を
- UIで直接名前を変える方式は面倒なので、コマンドラインツールで自動化した
- ツールはWebサイトにログインし、テストアカウントの名前フィールドをメッセージで更新する
- 数秒おきに名前フィールドをポーリングし、値が変われば相手の返信として表示する
- 実際の航空マイレージアカウントにはこれ以上自動データを送らないことにし、名前を高速で約10回更新してすべて成功するかだけ確認した
- この結果は、そのアカウントがリクエスト速度や回数に制限をかけていない可能性を示している
- その後の開発は、GitHub Gistsとローカルファイルで同じ原理を再現して進めた
- 航空マイレージアカウントよりも反復開発が速く簡単だったため
プロトタイプ2: 地上デーモンで外部情報を取得する
- 次の段階では、地上またはクラウドのインターネット接続されたコンピュータでデーモンを実行する
- デーモンは航空マイレージアカウントの名前フィールドを継続的にポーリングし、機内から送られた構造化リクエストを探す
- 例:
STOCKPRICE: APPL - 例:
SCORE: MANUNITED
- 例:
- 新しいリクエストを見つけると、デーモンがそれをパースし、関連APIから情報を取得したうえで、航空マイレージアカウント経由で機内側へ返す
- この段階から、単なるチャットを超えて株価やスポーツのスコアのようなリアルタイム情報の取得が可能になった
sky proxyとground daemonで構成した全体アーキテクチャ
- PySkyWiFiはHTTPリクエスト全体を航空マイレージアカウントに押し込み、地上コンピュータが実際のHTTPリクエストを実行した後、レスポンスを同じ方法で返送するTCP/IP風プロトコルである
- 構成要素は2つ
- sky proxy: 機内のノートPCで動作するプロキシ
- ground daemon: 自宅またはクラウドのインターネット接続されたコンピュータで動作するデーモン
- 利用前にまず地上デーモンを起動し、機内でWi-Fiに接続したあとノートPCでsky proxyを起動する
- ユーザーは
curlのようなツールでローカルプロキシにHTTPリクエストを送る- 例:
curl localhost:1234 -H "X-PySkyWiFi: example.com" - 実際の対象URLは
X-PySkyWiFiカスタムヘッダーに入れる
- 例:
- 地上デーモンは
X-PySkyWiFiヘッダーを取り除き、それを対象Webサイトへのルーティングに使う- リクエストボディと他のヘッダーはそのまま転送する
- すべての処理が終わると通常のインターネットリクエストのようにHTTPレスポンスを受け取れるが、機内ネットワークの状態によってはリクエストが数分間停止し、速度も毎秒数バイト程度になることがある
チャンク、ACK、第2アカウントでHTTPを往復させる
- sky proxyは
curlリクエストを受け取り、HTTPリクエスト全体をチャンクに分割する- 航空マイレージアカウントの名前フィールドに一度に入れられる量が少ないため
- 各チャンクは最初の航空マイレージアカウントの名前フィールドに順番に書き込まれる
- ground daemonはアカウントをポーリングし、新しいチャンクを見つけるとそれを読み取り、次のチャンクを送ってよいという受信確認を残す
- HTTPリクエスト全体が再構成されると、ground daemonがインターネットへ実際のリクエストを送る
- HTTPレスポンスは逆方向に同じ手順をたどる
- ground daemonがレスポンスをチャンクに分割して送る
- 実装を単純にするため、レスポンスチャンクには第2の航空マイレージアカウントを使う
- sky proxyがチャンクを読み取ってHTTPレスポンスを再構成し、元の
curl呼び出しに返す
トランスポート層とネットワーク層の役割分離
- PySkyWiFiの通信ロジックはトランスポート層とネットワーク層に分かれている
- トランスポート層は、どのデータをどの順序で送るかを決める
- 長いメッセージを小さなチャンクに分ける方法
- 受信側が次のチャンクを受け取る準備ができたことを知らせる方法
- TCPに非常に緩く似た役割
- ネットワーク層は、トランスポート層が決めたデータを実際のストレージに書き込み、読み取る
- 航空マイレージアカウント、ローカルファイル、Discordプロフィールのようなストレージになりうる
- この分離により、新しい航空会社のマイレージプラットフォームを接続しやすくなる
- 新しいネットワーク層だけ実装すれば、トランスポート層はそのまま維持できる
- テスト用ネットワーク層は航空マイレージアカウントの代わりにローカルファイルを読み書きできる
DATA、ACK、ENDで揃える転送ルール
- PySkyWiFiの転送接続は2本のパイプで構成される
- 各クライアントは書き込み用のSENDパイプと読み取り用のRECVパイプを持つ
- 航空マイレージアカウントをパイプとして使える
- PSWFクライアントは特定の瞬間に送信または受信のどちらか一方しかできない
- 送信モードでは相手が送ったデータを見られない
- 受信モードではデータを送っても相手には見えない
- TCPのようにいつでも双方向送受信する方式ではない
- 長いメッセージは
DATA、ACK、ENDセグメントで送るDATA: 文字D、6桁ゼロ埋めのシーケンス番号、実際のチャンクデータで構成される- 例:
D000451adline": "Mudslide in Wigan causes m ACK: 文字Aと確認対象の6桁シーケンス番号で構成される- 例:
A000451 END: 文字E1つでメッセージ終了を示す
- 送信側は
DATAを送ったあと、そのシーケンス番号のACKを受け取るまで待つA000451を受け取ると、000451チャンクが処理されたとみなし、000452チャンクを送る
- 受信側が
ENDを見るとメッセージ終了として扱い、送信側と受信側が役割を入れ替える
ネットワーク層実装インターフェース
- ネットワーク層はデータをストレージに書き込み、読み取るシンプルなインターフェースを提供する
- 必須操作は2つ
send(msg: str):msgをストレージに記録するrecv() -> str: ストレージからメッセージを読む
- 航空マイレージベースの実装では、この2つの操作はアカウントの名前フィールドを書き読みする動作になる
- 必須属性も2つ
sleep_for: RECVパイプをポーリングする間にトランスポート層が待機する秒単位の時間segment_data_size: 1セグメントに入れられる文字数
sleep_forはファイルのようなテスト実装では低くできるが、航空マイレージアカウント実装ではリモートサーバーに過剰なリクエストを送らないよう最低でも数秒にすべきsegment_data_sizeは使用する航空マイレージアカウントのフィールド最大サイズと同じである必要があり、例では約20文字- 任意フックは2つ
connect_send(): SENDパイプ初期化時に呼ばれ、航空マイレージ実装ではユーザー名とパスワードでログインしてCookieを取得するために使えるconnect_recv(): RECVパイプ初期化時に呼ばれる
base26エンコーディングと帯域幅改善のアイデア
- 航空マイレージのHTMLフォームは、名前にアルファベット以外の文字を許可しない可能性がある
Stephenは許可されるかもしれないGET /data?id=5は拒否されるかもしれない
- これを避けるには、ネットワーク層がセグメントを航空マイレージアカウントへ書き込む前にbase26でエンコードすればよい
- base26は文字列を
AからZまでの文字だけで表現する - バイト列を大きな数値に変換し、その後26進数のように
A〜Zで表現する - 例:
b26_encode("Hello world")はCZEZINADXFFTZEIDPKMを返す
- base26は文字列を
- トランスポート層はこのエンコーディングを知る必要がない
- ネットワーク層がバイト列をbase26に変換して記録し、読み取るときに再びバイト列へ戻す
- base26は文字列をかなり長くしてしまい、帯域幅を下げる
- 大文字と小文字の両方を使うbase52を使えばより短くできるかもしれないが、これはバージョン2の改善項目として残した
- 帯域幅はセグメントサイズを大きくすることで増やせる
- 1フィールドに20文字しか保存できなくても、20文字フィールドを5個同時に使えば、1リクエストで100文字セグメントを送れる
- 受信側は5個のフィールドを1リクエストで読み取り、再結合できる
HTTP CONNECTが残した改善余地と最終テスト
- PySkyWiFiは手動でHTTPリクエストをやり取りする代わりに、HTTP CONNECT requestsを使えばより良いトンネルを作れる
CONNECTは大半のHTTPプロキシが使う方式で、これを使えばPySkyWiFiはシステムレベルのプロキシのように動作し、ブラウザのリクエストも処理できるCONNECTを使うと、PySkyWiFiが対象Webサイトと直接TLS接続をネゴシエートするため、航空マイレージアカウントを通過するトラフィックは暗号化される- 欠点は実装作業がはるかに増えること
- 最終テストではGitHub Gistを通じてデータをトンネリングし、
curlでブログのホームページを読み込み、数分後にHTMLレスポンスを受け取った
1件のコメント
Hacker News の意見
何十年も前、配偶者が Google Voice のSMSをGmailの受信トレイから直接読んで返信する形で使っていたが、携帯電話が嫌いで、無制限3G付きの古い Kindle Keyboard を持ち歩いていた。
Kindleの簡易ブラウザで低スペック向けGmail画面を開けたので、月額料金のないSMS端末のように使えた。問題は新着SMS通知だけだった。
そこで脱獄してホーム画面にカウンターを付けようとしたが、Amazonアカウントで変更できるKindle端末名の横にカウンターを置けばよいと思いついた。
自宅サーバーからブラウザ自動化でAmazonにログインし、端末名を
My Kindle (x)に変えさせた。xは未読のGoogle Voice SMS数で、Kindleは1分以内にホーム画面の名前を更新し、数年間うまく動いた。数バイト削るだけで100万ドルくらい節約できるレベルだったと思う。
当時はほぼ最古級の動作するKindleだったはずなので、泥棒は単にバッグの中にあったものを持っていっただけだと思う。
その後フリーマーケットで20ドルのKindle Keyboardをまた見つけたが、6カ月後にバッテリーが死んだ。まだ本体は持っているので、交換バッテリーの値段が気になる。
ブラウザでGmailをかろうじて使えたし、複数のzmachine ROMから選んでテキストゲームを遊べるKindle向けのZorkサイトも作った。
いくつかのニュースサイトで紹介され、ある程度反響もあった。
Kindle名を変えるような賢い統合はなかったが、無制限3G付きのKeyboard KindleでしばらくSMSを送っていた。
Google MapsのWebサイトをかろうじて動かせた。
以前、イタリアからギリシャへ向かう約20時間のフェリーに乗ったとき、衛星インターネットベースの有料Wi-Fiがあったが、支払う気にはならなかった。
決済のためにStripeへのアクセスは開いていて、調べてみるとstripe.comの開発者ドキュメントなど全ページにアクセスできたので、画像を繰り返しダウンロードして帯域を浪費し始めた。
そのうち、Stripeが動作するには Fastly CDN へのアクセスが必要で、RedditもFastlyを使っていることを思い出し、Stripeに接続したあとHTTPの
Hostヘッダーをreddit.comに変えるとRedditを見られた。画像は
i.redd.itがFastlyではないので無理だったが、/etc/hostsでold.reddit.comをStripeのFastly IPに向け、恐ろしいTLSエラーを無視するとログインもできた。接続が遅くなって二度と買わなくなれば提供会社も被害を受けるし、Stripeのアップロード帯域コストも増やす可能性がある。
私は周囲の人に悪影響を与えないよう、セルラー回線でもダウンロードを避けるほうだ。
ブロックされた接続でどこまでアクセスできるか探る知的実験は理解できるが、わざと帯域を浪費するのは、公衆トイレの蛇口を開けっぱなしにして水を捨てたり、便器を詰まらせてあふれさせたりするのに近い。
https://en.wikipedia.org/wiki/Domain_fronting
数週間前、ショッピングモールで子どもたちを教室に送っていき、無料Wi-Fiを使おうとしたところ、ノートPCのログインは成功したがインターネットは使えないと表示された。
デフォルトゲートウェイを確認するとCiscoモデムにつながっており、診断ツールと接続機器一覧が見えたが、インターネット接続はオフになっていた。
携帯電話でそのモデルを検索すると、管理者アカウントはシリアル番号で、パスワードは空欄にする方式だと書かれており、例のパターンもあった。接続機器のうち1つの名前がまさにそのパターンのように見えた。
そのままログインして インターネットボタンをオンにすると、15秒後に緑色になり、機器のパスワードも新しく設定した。
VPNがつながらなかったので、ルーターに簡単に入れたことから MTU設定を変更したところ、すぐ正常に動いた。
こういう ハッキング は本来の意味そのままで格好よく、好きだ。
ただ最近確認したところ、skywifiではグローバルDNSは支払いなしでも普通に名前解決できるようで、自分が乗った飛行機では単なる一般的な iodineトンネル も動いたのではないかと思う。
https://github.com/yarrick/iodine
悪用報告を避けるため、サーバーの外向きトラフィックはTorを経由させ、どの列車・ホテル・空港ネットワークで動作するかのログも集め、他のトンネリングプロトコルも提供していた。
しばらくは面白いプロジェクトで、動かし続けておけばよかった。
最近は一部のDNSサーバー、特にGoogleがDNSリクエストの大文字小文字をランダム化して特定のエンコーディングをひどく壊すため、DNSトンネリングは少し難しくなっている。
アイデアと原理は面白いが、速度のせいで実戦ではほとんど使い物にならない。UDPの上にメッセンジャー程度を載せるなら別かもしれないが ;-)
「ヘッドホンの充電を忘れて、Limp Bizkit がノートPCのスピーカーから流れ始めた。幸い、機内のほかの人たちは気にしていないようだったので、みんなで楽しく盛り上がった。」
こういう人たちは基本的な公共のマナーが欠けている
周囲の多くの人は間違いなく気にしていただろうが、余計な騒音を押しつけるなと言うには礼儀正しすぎる人たちだった可能性が高い
こういう基本マナーのない人があまりに多いので笑える冗談だと思ったし、少なくとも著者が実際にそういう人ではないだろう
無料インターネットの可能性を実行可能なチャンスとして見るハッカー像を示しつつ、後半では実際に航空会社のシステムを悪用する人間ではないという形で距離を置いているので、冒頭のトーンは文体上の仕掛けに見える
そうでなければ、ものすごく無礼で冷淡な行為だ
飛行機に乗るといつも疲れてしまい、目を閉じて終わるまで待つ以外に何もできないのは自分だけなのか気になる
ノートPCや本を取り出すことを考えるだけで疲れるし、騒音も多く、ノイズキャンセリングヘッドホンもないので集中しづらい
家を出てから電車・トラム・保安検査を経て少なくとも2時間は過ごした状態で、空港の不健康な食べ物も助けにならない
天気も半年は暑すぎるか寒すぎるし、10kgのバックパックのせいでずっと汗をかいて、結局機内では何かをする気分が残らない
直感に反するが、多くのADHD脳はこう動くと聞いたことがあり、飛行機で過ごす時間が一番生産的だ
膝の上に載せるしかなかったり、前の人が座席を倒すと画面が割れる危険があり、数年前に同僚が実際にそうなった
最近はただ iPad とノイズキャンセリングヘッドホンを取り出して、映画やテレビ番組を見る
自分の体もまったく同じ反応をするし、空気中の酸素量の低下と関係があると思う
騒音の問題ではなく空気の問題で、ノイズキャンセリングヘッドホンは本当に効果が高い
ただ、まったく影響を受けずに飛行機で有用な仕事をする人たちもいる
どれほど疲れていても、睡眠不足でも、飛行機では眠れもしないし疲れもしないので、大西洋横断便がさらに苦痛で、同行者が8時間ずつ眠っているとからかわれているような気分にすらなる
飛行中は頭がひどくぼんやりして、着陸するまで目を閉じているし、本を読んだり仕事をしようとしても5〜10分後には脳がシャットダウンしてしまう
不思議なことに、機内食は毎回お腹の張りを起こすので、着陸直後に薬が必要になる
興味深いことに、ビジネスクラスに2回乗ったときは、脳がシャットダウンする前にある程度読書できた
長距離列車では進行方向を向いて座っていると実際に仕事がしやすいが、列車がかなり混んでくるとまた脳がシャットダウンする
株価、試合のスコア、天気のようなものを送ってくれる話を見て、以前 Google がテキストメッセージサービスで提供していた機能を思い出した
初めてスマートフォンを買う前に本当によく使っていて、
W[ZIP code]を 46645(GOOGL) に送ると天気をSMSで返してくれたstock:[symbol]も使えたし、今では忘れてしまったいろいろな機能があったもちろん Google は終了したが、続いている間はかなり素晴らしかった
当時Twitterが対応していたSMS DMで歌詞を数行送ると、Googleで
" lyrics"を検索し、結果をパースしてアーティスト名と曲名をSMSで返す仕組みだったうまく動いたわけではないが、かなり誇らしかった
おかげで携帯型衛星通信機 inReach Mini 2 から地球上のどこでも ChatGPT に質問でき、Death Valley のど真ん中でも聞けるのはかなり良い
Google の検索語を入力するのに時間がかかり、結果が届くまで1〜2分かかることもあったが、ないよりはましだった
主に行きたい場所の住所を検索して TomTom に入力するのに使っていて、時代は大きく変わった
1995年ごろ、Ericsson の子会社である通信研究会社で働いていたときにこうしたアイデアを思いつき、代表例はフライト状況の更新だった
数字キーパッドで1文字を入力するには何度も押さなければならないP2Pメッセージングがそこまで人気になるとは誰も想像していなかったので、初期のWebでデータが増えていく状況では、情報サービスが最も良い活用法に見えた
Teletext もまだ存在していて、それをスクレイピングする別プロジェクトもあった
だが、SMSにアクセスできた私たちと一般の人々が結局それを何に使ったのかを考えると興味深い
著者が抽象的な共有編集フィールド上のTCPにかなり近いところまで行っている点が興味深い
それ自体でも非常にクールなツールになり得る
両側で共有リソースに読み書きする方法だけを高レベルに設計すれば、残りはすべて処理して SOCKS プロキシとして提供するプロキシを想像できる
結局、無料Wi-Fiが標準になって大した話ではなくなり、今度は機内無料Wi-Fiの戦いに移ってきた
いずれ機内Wi-Fiもどこでも無料になるだろうし、すでに複数の航空会社ではそうなっている
テキストだけで画像や動画は無理だろうが、一般的なWebブラウジングには十分だったはずだ
数年前、うちの大学の学部長が「ゲームのせいで出席に影響が出ている」といった趣旨のことを言い、夜10時以降はLANネットワークを遮断することにした
実装方法は、IPアドレスで行き来するすべてのトラフィックをブロックするというものだったが、TCP自体はブロックしておらず、当時すでにそれほど新しくはなかったIPv6のことも完全に忘れていた
そこでIPv6で動作する簡単なP2Pチャットアプリを作った。唯一の問題は、友人たちにIPv6アドレスを共有し、連絡先として保持してもらう必要があることだった
うまく動いたが、後になって、ネットワーク外のコンピューターへトンネリングするほうがずっと簡単だと分かった
これは、役に立たないソフトウェアをもっと書こうという記事と、それに続くHNでの議論を思い出させる、ほぼ完璧な例のように思える
人生を変えるものか? おそらく違う
作って探求する過程は楽しかったか? ほぼ間違いなくそうだ
私たちはこういうものをもっと作るべきだ
https://ntietz.com/blog/write-more-useless-software/
https://news.ycombinator.com/item?id=37911900