くまがいです。CROSS2016 ご来場ありがとうございました。大盛況で楽しかったですね。会場無線 LAN を CONBU で提供していましたので、繋がらない報告がないか Twitter を追っていたら、こんな投稿を見つけました。
WiFi繋いだら現在地が秋葉原になってる #cross2016
CROSS2016 は横浜ですので、位置情報がおかしいですね。
Wi-Fi測位のしくみ
スマホが現在位置を取得する方法のひとつとして、Wi-Fi の BSSID(MACアドレス) を利用したシステムがあります。端末は、周囲に見えている Wi-Fi の BSSID をサーバに送りつけます。するとその BSSID に対応した位置が返ってきます。
簡単な仕組みですが、これを世界中で実現させるには途方もない労力が必要です。位置とそれに対応する BSSID のデータベースを構築するには、測位したい全ての場所へ行って無線 LAN を受信する必要があり、今後も更新していかなければなりません。
スマホが Wi-Fi AP の位置情報を収集している
Skyhook Wireless(2010年頃以前の iOS での利用で有名)は、ウォードライビング、車で移動しながら無線 LAN を受信することによってデータベースを構築したといわれていますが、現在 Google や Apple は、この情報収集をスマホユーザ自身にやらせています。
たとえば iOS では、近くの Wi-Fi アクセスポイントの情報とその位置を端末に保存しておき、定期的に Apple へ送信しています。
Apple は Wi-Fi 測位に関する API や、誤った位置情報の修正、オプトアウトの手段などの情報を公開していません。iOS 8 および iOS 9 のプライバシーと位置情報サービスについて の「Wi-Fi と基地局の位置情報のクラウドソーシングデータベース」で何をやっているのか書かれていますが、これ以外の公式情報は見つけられませんでした。
本当のところ何を送ってるんだろう
ここまでは比較的広く知られていることかもしれません。が、具体的にどのような情報がやりとりされているのかはあまり知られていません。
試しに、手元の iPhone がどんな情報を送っているのか観察してみようと思います。使ったのは (iPhone5s, iOS8.2.1), (iPhone5, iOS9.2.1), Burp Suite 1.6.32 です。ちょっと古い理由は特になく、たまたま手元にあったからです。
観察・収集編
我々の iPhone で収集された Wi-Fi と位置データが Apple のサーバへ送信されていく様子です。

おっ、何か MAC アドレスっぽいものを送っているのを見つけた!?

うわー! バイナリかよ、解散~ と思いましたがこれは Protocol Buffers でシリアライズされた何かのようです。JSON や XML のような人間にやさしいエンコーディングではないため一瞬諦めそうになりました。
Protocol Buffers はバイナリエンコーディングのシリアライズフォーマットで、データ構造はエンコーディングに含まれません。データ構造は別途 IDL(インタフェース定義言語) の .proto で定義して使うもので、エンコードされたデータからはデータ構造がよくわからないのです。
ちょっと .proto をでっち上げてみました。中を見ても何だか分からない項目がいっぱいあります。いくつかは iOS7 か 8 で増えたようです。
message WiFiAccessPoint {
required string bssid = 1;
optional int32 channel = 2;
optional int64 signal_dbm = 3;
message Location {
required double latitude = 1;
required double longitude = 2;
optional int32 unknown3 = 3;
optional int32 unknown4 = 4;
optional int32 unknown5 = 5;
optional int32 unknown6 = 6;
optional int32 unknown7 = 7;
optional int32 unknown8 = 8;
optional int64 unknown9 = 9;
optional int64 unknown10 = 10;
optional int32 unknown11 = 11;
optional int32 unknown12 = 12;
}
optional Location location = 4;
optional string application = 5;
optional int32 unknown7 = 7;
}
message SmartphoneToAppleRequest {
repeated WiFiAccessPoint ap = 3;
}&
実をいうと、このメッセージは2013年頃(iOS6.0)に一度解析したことがあります。それを使って、今回のブログで気楽なネタにしようと思ったら、内容が丸ごと変わっていて全然気楽じゃありませんでした!!!
この .proto でデコードしてみたところ、以下のような内容を Apple へ送信しているようです。
ap {
bssid: "74:3:bd:28:xx:xx"
channel: 4
signal_dbm: -89
location {
latitude: 35.6897295965
longitude: 139.770113602
unknown10: -1
unknown11: 7
unknown12: 0
}
application: "com.google.Maps"
unknown7: 0
}
このケースでは、収集した約300個の BSSID が Apple へ送信されたのを観察できました。無線LANについては BSSID, チャネル、電波強度らしきものが送信されていて、SSID は送信されていないようです。_nomap は考慮されていないようで、ssid_nomap という SSID を出してみたところ普通に送信されていました。Apple に関しては _nomap は効いていないようです。
lat/lng の座標以外には、位置情報をリクエストしたであろうアプリの Bundle ID らしきものが入っています。自分の場合は他に com.protogeo.Moves や com.google.ingress などが入っていました。
その他に、端末の機種名と OS のバージョンも送っていますね。端末を個別に識別するようなものはなさそうです。HTTP ヘッダにも怪しいものはありません。認証もなく、匿名で突然送りつけているようです。
位置情報が狂う原因
AP を持ってオフィスの引越しをすると、引越し前の位置が表示されてしまうことがあります。オフィスに設置していた AP の BSSID に対応する位置情報が更新・収集されていないのが原因ですが、オフィスはともかく、最近は個人宅ではあまり聞かない現象のように思います。なぜオフィスの引越しでよく起こるのでしょうか?
オフィスには複数の AP があり、複数の BSSID が受信できるとします。すると、引越し以前から近隣にある AP よりそれらを優先して使われてしまうのかもしれません。また、都心のオフィスでは空が開けていない場所が多く、GPS の電波が受信しにくいため、BSSID と位置情報が更新されにくいのかもしれません。
更新に関しても、端末の識別もなく収集しているため、ある程度のデータが溜まって確信度が高くならないと有効にならないはずです。
収集されたデータがどのように運用されているのかは調べようがなく、曖昧な記述になってしまいましたが、きっと大きく外してはいないと思います。
観察・利用編
普通に現在位置を取得するパターンです。iOS で現在位置を取得するアプリ(ここでは Google Maps)を起動してみます。

BSSID らしきものを送っていますね。これらの BSSID は確かに現在、周囲に見えているものです。

位置情報が含まれたレスポンスが返ってきました。結構大きいようです。送った BSSID の他にも大量に何かがくっついています。例によってバイナリですが、これも Protocol Buffers ですので解析してみます。
でっちあげた .proto です。
message WiFiAccessPointResp {
required string bssid = 1;
message Location {
required int64 latitude = 1; // * 10e-9
required int64 longitude = 2; // * 10e-9
optional int32 unknown3 = 3;
optional int32 unknown4 = 4;
optional int32 unknown5 = 5;
optional int32 unknown6 = 6;
optional int32 unknown7 = 7;
optional int32 unknown8 = 8;
optional int32 unknown9 = 9;
optional int32 unknown10 = 10;
optional int32 unknown11 = 11;
optional int32 unknown12 = 12;
}
optional Location location = 2;
optional int32 channel = 21;
}
message AppleToSmartphoneRequest {
optional int32 unknown1 = 1;
optional WiFiAccessPointResp ap = 2;
optional int32 unknown3 = 3;
optional int32 unknwon4 = 4;
}
message AppleToSmartphoneResponse {
repeated WiFiAccessPointResp ap = 2;
}
収集編での構造と同じかと思いきや微妙に違う………。lat/lng は float じゃなくて固定小数点に変わっているしタグもずれています。。これも 2013 年頃解析したものとは違います。
デコード結果は以下です。
ap {
bssid: "10:6f:3f:6:xx:xx"
location {
latitude: 3565078352
longitude: 13972113022
unknown3: 42
unknown4: 0
unknown5: 21
unknown6: 14
unknown11: 62
unknown12: 69
}
channel: 3
}
位置情報を取得するのに送信した周囲の BSSID の位置情報は、このようにして返ってきました。lat/lng 以外にも何かついてきているようです。
このケースでは、リクエスト時に BSSID を複数(12個)送信しています。レスポンスには、リクエスト時に送信した BSSID 以外にも周辺の BSSID と位置情報が100個ほど含まれていました。iOS, locationd がこのレスポンスから実際の位置情報をどのように計算しているかはわかりません。
BSSID から位置情報を取得したいならもっと簡単な方法がある
Apple のこれを使えば BSSID から位置情報を取得できる!と思うかもしれませんが、そんな面倒でグレーなことをしなくても、もっと簡単で正当な方法があります。
Google Maps Geolocation API で BSSID から位置情報を取得できます。

ブラウザからも使われています。
プライバシーの問題
Wi-FiのMACアドレスはもはや住所と考えるしかない 高木浩光@自宅の日記 2011年11月26日
個人で設置している AP の MACアドレス(BSSID) から位置情報が引けるとなると、BSSID は住所に相当するのではないかという指摘があります。これに対し、Google Maps Geolocation API は「2つ以上のBSSIDが含まれないリクエストでは位置情報を取得できない」としています。
The request body’s wifiAccessPoints array must contain two or more WiFi access point objects. macAddress is required; all other fields are optional.
特定の BSSID から位置を調べようとした場合、その近隣の BSSID なんて事前に分からないので、位置情報が取得できません。これで多少は安全になります。一つの AP が複数の BSSID を吹いているなど、推測されやすい状況はありえますが。
結論
一般的に AP が設置されてもすぐに位置情報に反映されるわけではありませんが、カンファレンスやイベントなどで一時的に設置された AP でも、その情報がたくさんの来場者のスマホから送信されてしまい、それなりに確信度の高い位置情報として扱われてしまうのかもしれません。AP は複数台設置されるため、測位の場合でも優先されてしまうのかもしれません。
今回は Wi-Fi 測位のみを扱いましたが、その他 GPS(GNSS)や携帯電話基地局での測位とハイブリッドになっていることがほとんどであり、その挙動は多様です。大きく位置が狂うことが有り得るのは Wi-Fi 測位の特徴といえます。
他の測位手法については、他の機会に触れたいと思います。
それと Android も調べたかったのですが力尽きました。