2012年07月27日
何故iPhone(iOS)+HTML5+WebSocketで脱獄無しにテザリング出来るのか?
t.freeいざという時に助かりますし、不思議・面白いですよね。

こういったツールやサービスがWebSocketを使って、iPhoneの標準的な機能の範囲(脱獄無し)でテザリングを実現していることまでは容易に想像できますが、具体的な仕組についてはどうなってるんでしょうか?
僕も前々から興味をもっておりまして、今日は簡易的ですがgitでオープンソースとして公開されており同等の機能を提供するiOS-HTML5-Tetheringを使って勉強してみたいと思います。
spoletto/iOS-HTML5-Tethering · GitHub

こういったツールやサービスがWebSocketを使って、iPhoneの標準的な機能の範囲(脱獄無し)でテザリングを実現していることまでは容易に想像できますが、具体的な仕組についてはどうなってるんでしょうか?
僕も前々から興味をもっておりまして、今日は簡易的ですがgitでオープンソースとして公開されており同等の機能を提供するiOS-HTML5-Tetheringを使って勉強してみたいと思います。
iOS-HTML5-TetheringのソースコードからわかるWebSocket(HTML5)テザリングの仕組み
iOS-HTML5-Tetheringのソースコードは大きくサーバ側、クライアント側の2つに分かれ、その全体像は次の通りです。
※クリックで拡大
まずクライアント側ですが、OSが提供する仮想ネットワーク・カーネル・ドライバであるTUN/TAPを利用し、仮想的ネットワーク・インタフェース(TUN)をデフォルト・ゲートウェイ(10.0.0.1)に設定します。

また、クライアント側WebアプリケーションはTUNインターフェースで受信したIPパケット(Ethernetデータ・フィールド)を、WebSocket経由でiPhoneブラウザ(Safari)に送信します。iPhone側では受信と同時に中継先に送信するWebSocketブリッジ(中継器)として動きます。

ブラウザのブリッジ接続・中継処理は最初にiPhoneがアクセスするサーバ側index.html(HTML5アプリケーション)にJavascriptで記述されています。
サーバ側ではWebSocket経由で受信したIPパケットをデコード、送信元IPを自身のインターネット側IPアドレスに変換・インターネットへ送信します。

また、インターネット側から受信したパケットをモニタリング(Sniffer)し、送信元IPアドレス、ポートが代理送信時に宛先に指定したIPパケットであれば、このパケットの宛先IPアドレスをローカル側のTUNインタフェース(10.0.0.1)に置換し、WebSocket経由でiPhoneブラウザに送信します。
※ただし、受信したEthernetフレームにIP、TCPレイヤーが含まれていなければ破棄しています。
3G(グローバル)側WebSocketコネクションでデータを受信したiPhoneブラウザはローカル側WebSocketサーバに転送し、それを受信したローカル側WebSocketサーバBはTUNデバイス(仮想ゲートウェイ)に書き込みます。
以上の流れを繰り返す事でラップトップPC上のアプリケーションは外部ネットワークと通信しています。
サーバ側アプリケーションのインストール・設定・起動(CentOS 6.x版)
さあさあ、仕組みはわかったところで、実際に動かしてみましょうか。
gitに公開されているiOS-HTML5-TetheringではUbuntu(Debian)を前提にインストール・スクリプトが用意されていますが、僕の使っているVPSはCentOS 6.x。こちらでインターネット接続サーバを動かします。
まずはVPSにNode.jsをインストール。
$ wget http://nodejs.org/dist/node-v0.6.9.tar.gz
$ tar xzf node-v0.6.9.tar.gz
$ cd node-v0.6.9
$ sudo ./configure --prefix=/usr
$ sudo make install
openssl-develが無いとcofigureに失敗するので、次のコマンドでインストールしましょう。
$ sudo yum install openssl-devel
WebSocketパッケージをインストールしたいので、先にnpm(Nodeパッケージ管理)をインストールします。
$ git clone http://github.com/isaacs/npm.git
$ cd npm/
$ sudo make install
npmでwebsocket-server、expressをインストール。
$ npm install websocket-server
$ npm install express
Python環境も用意します。
$ sudo yum install python-setuptools
$ sudo easy_install tornado
$ wget http://www.secdev.org/projects/scapy/files/scapy-latest.tar.gz
$ tar xzf scapy-latest.tar.gz
$ cd scapy-2.1.0/
$ sudo python setup.py install
最後にテザリング・サーバとなるiOS-HTML5-Tetheringをダウンロードし、付属のiptables_config.shまで実行しておきます(gitコマンドが無い、用意が面倒な人はトップページからzipファイルをダウンロードしましょう)。
$ git clone git://github.com/spoletto/iOS-HTML5-Tethering.git
$ sudo sh ./iOS-HTML5-Tethering/server/iptables_config.sh
ちなみにiptables_config.shの中身はこの通り。
--- iptables_config.sh ---
#!/bin/bash
# Before executing Scapy scripts, it is necessary to disable
# the linux kernel’s own responses. If the linux kernel is allowed
# to answer arriving network packets, it will answer with RST
# during the TCP handshake, because the kernel believes that
# no port is open. For TCP, disable the kernel’s response with:
sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP
-------
これでサーバ・サイドのインストールはおしまい。引き続き設定変更しましょう。
まずは、iOS-HTML5-Tethering/server/public/index.htmlでhost.remoteとして定義されているWebSocketサーバのURLを変更します。
host.remote = "ws://my-tether-server:8080/websocket/";
※必要に応じてポート番号も変更
続いて、iOS-HTML5-Tethering/server/ws-server.py。これがWebSocketサーバ・サイドの本体。
ETH_0_IP_ADDRの値を自身のグローバルIPに変更し、index.html/host.remoteのURLでポート番号を変更した場合は、待ち受けするポート番号も8080から変更します。
wsServer = HTTPServer(Application([("/websocket/", Handler)]))
print "Server started."
wsServer.listen(8080)
IOLoop.instance().start()
また、ws-server.pyではWebSocket経由で受信したローカル側ネットワークのデータがIP/TCPという前提のコードになっていますが、実際にはUDPも送られてくるので上手く動かないという問題が含まれています。
これを回避する為、Handler::manipulate_outgoing_packet()メソッド内にif文を一行追加してあげます。
class Handler(WebSocketHandler):
def open(self):
global connection
print "New connection opened."
connection = self
def manipulate_outgoing_packet(self, message):
# Add the original packet to the NAT table.
global outbound_packets
ipPacket = IP(message)
if ipPacket[0].haslayer(IP) and ipPacket[0].haslayer(TCP):
outbound_packets[(ipPacket.dst, ipPacket.dport)] += 1
# Modify the source IP address and recalculate the checksum.
ipPacket.src = ETH_0_IP_ADDR
del ipPacket[TCP].chksum
del ipPacket[IP].chksum
send(ipPacket)
最後は、iOS-HTML5-Tethering/server/fs-server.js。この中でWebサーバに80ポートを利用していますが、既に80ポートは使っている!という場合は、ここで変更しましょう。
# app.listen(); # default
app.listen(8081);
ちなみにiPhoneが最初にアクセスするのがここで起動したWebサーバ。
ここまで出来たら、次のコマンドで2つのサービスを起動します。
$ sudo python iOS-HTML5-Tethering/server/ws-server.py
$ sudo node iOS-HTML5-Tethering/server/fs-server.js
クライアント側アプリケーションのインストール・設定・起動
クライアント側にはTUN/TAPドライバが必要です。もし、インストールされていない場合はTUN/TAPドライバをインストールしましょう。
ちなみにt.freeをインストールするとTUN/TAPドライバもインストールされるので、簡単ですよ。
準備が出来たら、iOS-HTML5-Tethering/client/client-ws-server.pyを編集します。
WiFiインタフェースはMacであれば通常en1になるので、特に変更は必要無いはずです。
WIFI_DEVICE_NAME = 'en1'
注意が必要なのはtunデバイス名。t.freeやVPNアプリケーションを使っていると既にtun0が使われている可能性があります(ifconfigで確認)。このような場合、client-ws-server.pyに記述されているtun0をtun1以降に変更します。
tun = os.open('/dev/tun1', os.O_RDWR)
・・・
subprocess.check_call('sudo ifconfig tun1 10.0.0.1 10.0.0.1 netmask 255.255.255.0 up', shell=True)
このアプリケーションを起動する前にWiFiネットワークを作成しましょう。
WiFiメニュー(Mac)からネットワークを作成します。

作成したら、このアクセスアクセス・ポイントにiPhoneから接続します。

ここでclient-ws-server.pyを実行。
$ sudo iOS-HTML5-Tethering/client/python client-ws-server.py
delete net default
add net default: gateway 10.0.0.1
Server started.
さあ、準備は出来ました!iPhoneからhttp://my-tether-server:8081にアクセスしてみましょう。
2つのWebSocket接続が確立できればServer Status、Computer Statusともにグリーン表示になります。

おっと、言い忘れましたが、iOS-HTML5-TetheringではIP/TCPしかパケットを通過させないので、通常はDNSも使えません。ですので、事前に動作確認用サイトのグローバルIPアドレスを控えておき、ラップトップPCからアクセス・動作確認する必要があります。
使ってみる!、その感想は?
かなり遅いですね・・・Googleのトップページを表示するのに10秒ぐらい要します。
サーバ側のスペックも影響しているんでしょうが、あくまで簡易的な実装なのでiOS-HTML5-Tetheringのコード自体にも限界もあります。
サーバ側がIP/TCPしか取り扱わない割にTUNデバイスで受信(Read)したデータは全てWebSocketで送信しているので、ローカルPC(MacBook)側にもscapyをインストールし、TCP以外は上位回線に送信しないようにしてみましたが劇的な変化はありませんでした。
from scapy.layers.inet import UDP,IP,TCP
・・・
def run(self):
global connection
global tun
while (True):
dataFromTun = os.read(tun, 1500)
ippkt = IP(dataFromTun)
if not ippkt[0].haslayer(TCP):
continue
if connection:
connection.write_message(dataFromTun.encode('base64'))
サーバ側をもっと工夫しないと駄目ですね。
コード量が少ないので短時間に全体像を把握でき、かつ、色々な可能性を感じさせてくれる大変たためになるプロジェクトでしたよ。
GOAL ZERO ポータブルソーラー発電機 NOMAD7 GZ-12301
posted with amazlet at 12.07.27
GOAL ZERO (2011-08-16)
売り上げランキング: 817
売り上げランキング: 817
Pythonプロフェッショナルプログラミング
posted with amazlet at 12.07.27
ビープラウド
秀和システム
売り上げランキング: 101330
秀和システム
売り上げランキング: 101330