[iOS]Wi-FiのHotspotの自動にゅるにゅるを打ち消す

2014.12.09

公衆無線LAN使ってますか?

最近、といわず随分前からスマートフォンユーザーにお世話になっているのが、公衆無線LANスポット。 携帯の電波の弱い地下や駅構内、ファーストフード店やはたまた路上で利用でき、近年流行りの3GByte/7GByteパケット通信制限の規制外で高速に通信できるなど、4G/3Gのバックアップ目的での使い方も流行っており、各キャリアがそれぞれHotspotを提供しています。 また、ワイヤレスゲートやモバイルBBポイントといった公衆無線LANを提供している会社もあり、一定以上のシェアが有ります。

iPhoneなどのiOSからこれらへ接続する際、まずはHotspotに接続して、そこでIDとパスワードを入力、その後認証が済んだら晴れてインターネットが接続可能になるといった動作をします。 ただし、各携帯キャリアがそれぞれオプションで持っているWiFiであれば、その契約SIMが入ってる場合等認証を自動化して接続してくれるみたいです。

但し、このホットスポットには欠点があります。 自動的に接続すると、ニュルニュルっと下からログイン用のフォームや画面が表示されます。

wi2 softbank

左側はUQ WiMAXのスポットに接続した時に表示されるもの、右側がソフトバンクWi-Fiスポットに接続した時に表示されるものです。どちらもパスワード暗号化されたスポットではなく、接続した後に自動的に表示されるものです(ヨドバシアキバ前で接続してみました)。 実際、このニュルっと出てくるのも便利です。WiMAX加入者であれば、ここにログインIDとパスワードを入力すればインターネットに接続することが出来ます。 それ以上の細かいIPの設定などは要りません。 が、ここで2つ困った問題が有ります。

1.Wi-Fi接続済なのに何もコンテンツが取得できない

ステータスの取得は、よく、Reachabilityクラスを利用します。 その際使うコードが下記になります。

Reachability *reachability = [Reachability reachabilityWithHostName:@"itunes.apple.com"];
NetworkStatus status = [reachability currentReachabilityStatus];
if (status == ReachableViaWiFi) {
    // wifi接続時
} else if (status == ReachableViaWWAN) {
    // 4G/3G接続時
} else if (status == NotReachable) {
    // 接続不可
}

この場合、FreeのWi-Fiスポットに接続された場合はReachableViaWiFiになります。 この事自体は全然問題ありません(むしろ、無線LANで接続されているのですから正解です。) しかし、コンテンツを取得するところで問題が発生します。

NSURL *url = [NSURL URLWithString:@"http://www.google.com/"];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];

一般的に見るWebコンテンツを取得する方法です。 この方法で取得した場合、本来であればログインID画面の情報が取得されます(ページのリダイレクト先が取得されます)。 が、ここでコンテンツを取得しようとしても、取得先のコンテンツは空のものが返却されます。 本来だったら接続されているわけですから、その接続されているコンテンツが知りたいわけです。 大した問題では無いかもしれませんが、「何らかの情報が取得できるか」ってのは大きな違いです。

ネットワークがWi−Fiに接続したら処理をするプログラムを作ってる際に、コンテンツが返ってこない理由が接続先サーバーのエラーなのか、それとも通信経路上でトラブルになっているのか、それともソフトウェア的な潜在的バグが存在しているのか、わかりません。

権限の移譲を受ける「CaptiveNetwork」

さてここで、このニュルっと出てくるダイアログを出させなくする方法があります。 それがこの「CaptiveNetwork」というフレームワークです。 ただ、出させなくするというわけではなく、「にゅるっと出てくる画面を出す代わりの動作を自分の中で持つよ!って宣言する」役割があります。

Boolean CNSetSupportedSSIDs ( CFArrayRef ssidArray );

CNSetSupportedSSIDs関数にCFArrayRefでssidのArrayを与えてやれば良さそうですね。 (そっくりそのまま定義を言い直してみました)

つまり、自分の中で処理するよってSSIDをarrayで渡してあげればいいわけですね。 上記の画像で言うWi2_clubや0001softbankなどになります。 といったところで実装を簡単に書いてみます。

今回使うのはSystemConfigurationのFramework内にありますので、まずはLinkします。

SystemConfigurationFramework

その後、今回はアプリの頭で制御を奪ってやる必要があるので、AppDelegate.mの中に以下の記述をします。

#import "AppDelegate.h"
#import <SystemConfiguration/CaptiveNetwork.h>

@interface AppDelegate ()
@end
@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    NSMutableArray *ar = [NSMutableArray new];
    /* 登録(抑制)したいSSID数だけ記入する */
    [ar addObject:@"Wi2_club"];
    [ar addObject:@"0001softbank"];
        
    /* CFArrayRefへとキャストする */
    CFArrayRef ssids = (__bridge CFArrayRef)ar;
    
    /* 登録をする */
    CNSetSupportedSSIDs(ssids);
    
    return YES;
}

書いてあるとおり、にゅるっと出てくるのを抑制したいだけSSIDを登録します。

これを使うと何ができるの?

これを使うと、HotSpotの様々なログインを肩代わりするアプリとか作れます。 具体的な例を挙げると海外旅行社向け(と謳いつつ実は日本の人も登録できる)「Japan Connected-free Wi-Fi」とか、海外製ではあるけれど「Easy Wi-Fi」、日本製だと「moopener」ような複数のサービスにまたがった接続ツールとか作れます。 (もちろんこれは、肩代わりするところを宣言するだけなので、裏側でログインIDとパスワードを使ってログイン処理をする所を実装しなければならないですが。)

ちなみに、この権限移譲ですが、アプリケーションを削除したら解除されます。なので、再度にゅるっとしたログイン画面を表示したくなったらアプリケーションを削除してあげてください。もしくは、SSIDのArrayを空のものを再度登録してあげて下さい。

最後に

あまり使うことのないCaptiveNetworkですが、いつもサービスとして使ってる公衆無線LANのアプリがどんな仕組みを使ってるかを知ると、また違った見方ができますね。