[Utility.php] 携帯電話を判別する。IPアドレスから。

PHPで、携帯電話からの判別をします。
偽装が可能なユーザーエージェントじゃなくって、リモートホストのIPアドレスで判別。

今、会員制のサイトを作ってまして・・・
携帯電話各社は、携帯電話1つ1つに固有の端末IDを用意してくれているから、それをキーに会員認証をしようと思ったのです。でも、PCなどから端末IDを偽装できないこともないのよね。

ってことで、リモートホストが、docomoかYahooかauかってそこまで判別して、その上で端末IDを使おうって魂胆。

さて、ここから、PHPコードを書くんだけど、そのまえに・・・

携帯電話を判別するための各社のIPアドレス帯域

これらのページから、HTMLを解析して自動でIPアドレス帯域の更新処理を作るなんて面倒。。。
なので、IPアドレス帯域の変更などが発表された時は、手動で行う必要があります。
(何か良い方法をご存知の方は教えてください。 Net-CIDR-MobileJP 以外で・・)
http://d.hatena.ne.jp/tomisima/20070903/1188836400
↑ のは、Pythonだけど、やっぱりこういうことなのか。。。私の知らないところでリストとして配布されているのかと思ってたけど、HTMLページからIPアドレスの箇所を抜き取らなきゃなんですね。
下記のコードで、 $arrCidr でIPアドレス帯の配列を作っているところを、↑の方法で配列を作り直せば良いんだけど、今日のところは面倒なので。。。

ということで、携帯電話判別用のfunctionを作成するためのプログラムを作成します。
IPアドレス帯域の変更が発表されたときは、下記のコードのIPアドレスの箇所を修正して、実行しなおすことにします。

下記のコードを実行すると、こんなふうにfunctionが作成されます。

※【追記】ここで作成したコードの気に入らないところを修正した【確定版】もご確認ください。

 

<?php

// 定数の宣言
define("C_RETURN_VALUE_DOCOMO", "docomo");
define("C_RETURN_VALUE_AU", "au");
define("C_RETURN_VALUE_YAHOO", "yahoo");
define("C_RETURN_VALUE_OTHER", "other");

define("C_LF", "n");
define("C_TAB1", " ");
define("C_TAB2", C_TAB1 . C_TAB1);
define("C_TAB3", C_TAB1 . C_TAB2);
define("C_TAB4", C_TAB2 . C_TAB2);
define("C_TAB5", C_TAB2 . C_TAB3);
define("C_TAB6", C_TAB3 . C_TAB3);
define("C_TAB7", C_TAB3 . C_TAB4);

// キャリアとIPアドレス範囲を配列にする
$arrCidr = array(
// http://www.nttdocomo.co.jp/service/imode/make/content/ip/#ip
array(
C_RETURN_VALUE_DOCOMO // docomo
, array(
'210.153.84.0/24'
, '210.153.86.0/24'
, '210.153.87.0/24'
, '210.136.161.0/24'
, '124.146.174.0/24'
, '124.146.175.0/24'
)
)
,
// http://www.au.kddi.com/ezfactory/tec/spec/ezsava_ip.html
array(
C_RETURN_VALUE_AU // au
, array(
'210.169.40.0/24'
, '210.196.3.192/26'
, '210.196.5.192/26'
, '210.230.128.0/24'
, '210.230.141.192/26'
, '210.234.105.32/29'
, '210.234.108.64/26'
, '210.251.1.192/26'
, '210.251.2.0/27'
, '211.5.1.0/24'
, '211.5.2.128/25'
, '211.5.7.0/24'
, '218.222.1.0/24'
, '61.117.0.0/24'
, '61.117.1.0/24'
, '61.117.2.0/26'
, '61.202.3.0/24'
, '219.108.158.0/26'
, '219.125.148.0/24'
, '222.5.63.0/24'
, '222.7.56.0/24'
, '222.5.62.128/25'
, '222.7.57.0/24'
, '59.135.38.128/25'
, '219.108.157.0/25'
, '219.125.151.128/25'
, '219.125.145.0/25'
, '121.111.231.0/25'
, '121.111.231.160/27'
, '121.111.227.0/25'
, '121.111.227.160/27'
)
)
,
// http://creation.mb.softbank.jp/web/web_ip.html
array(
C_RETURN_VALUE_YAHOO // yahoo
, array(
'123.108.236.0/24'
, '123.108.237.0/27'
, '202.179.204.0/24'
, '202.253.96.224/27'
, '210.146.7.192/26'
, '210.146.60.192/26'
, '210.151.9.128/26'
, '210.169.130.112/28'
, '210.175.1.128/25'
, '210.228.189.0/24'
, '211.8.159.128/25'
, '123.108.237.240/28'
, '202.253.96.0/28'
)
)
);

// IPアドレスの範囲を数値化して、配列を作り直す
$bufList = array();
foreach ($arrCidr as $list){
$carrier = $list[0];
foreach ($list[1] as $cidr){
$arr = getRangeAndValue($cidr, $carrier);
array_push($bufList, $arr);
}
}
$arrCidr = null;

// IPアドレスの最小値で昇順にソート
usort($bufList, "cmp");
function cmp($va, $vb){
$a = $va[0];
$b = $vb[0];

if ($a == $b) return 0;
return ($a < $b) ? -1 : 1;
}

// IPアドレス範囲が連続する箇所を連結して配列を作り直す
$list = array();
$cnt = count($bufList);
$prev = array(0, 0, 'none', '0.0.0.0/32');
$j = 0;
for ($i = 0; $i < $cnt; $i++){
if (($prev[1] + 1) == $bufList[$i][0]){ // 直前の最後の番号に続いている
if ($prev[2] == $bufList[$i][2]){ // 同じキャリア
$j--;
$list[$j][1] = $bufList[$i][1];
$list[$j][3] .= ', ' . $bufList[$i][3];
} else {
$list[$j] = $bufList[$i];
}
} else {
$list[$j] = $bufList[$i];
}
$j++;
$prev = $bufList[$i];
}
$bufList = null;

// $list 配列の上限・下限の中央の添え字を取得する
$cnt = count($list);
$upper = $cnt - 1;
$harf = ($upper - ($upper % 2)) / 2;

// 出力用の変数に条件式をセット
$outputHTML = getCode($harf, 0, $upper, 0);

/*
* IPアドレスを二分探索する条件式を作る
*/
function getCode($harf, $lower, $upper, $kaisou){
global $list;
$ret = "";

// 階層に合わせて、行の先頭にタブ文字を挿入
$tab = C_TAB2;
for ($i = 0; $i = ' . $list[$harf][0] . '){ // $list[' . $harf . ' to ' . $upper . ']' . C_LF;
$ret .= $tab . C_TAB1 . 'if ($ip 0){
$n = ($n + ($n % 2)) / 2 + $harf2;
$ret .= $tab . C_TAB1 . '} else {' . C_LF;
$ret .= getCode($n, $harf2, $upper, $kaisou + 1);
} else if ($n == 0){
$ret .= $tab . C_TAB1 . '} else {' . C_LF;
$ret .= $tab . C_TAB2 . 'if ($ip >= ' . $list[$harf2][0] . '){' . C_LF;
$ret .= $tab . C_TAB3 . 'if ($ip 0){
$n = ($n - ($n % 2)) / 2 + $lower;
$ret .= $tab . '} else { // $list[' . $lower . ' to ' . $harf2 . ']' . C_LF;
$ret .= getCode($n, $lower, $harf2, $kaisou);
}
$ret .= $tab . '}' . C_LF;
return $ret;
}

/*
* CIDRから、IPアドレスの上限・下限とその他を配列で返す
*/
function getRangeAndValue($cidr, $carrier){
$buf = explode('/', $cidr);
$start = sprintf('%u', ip2long($buf[0]));
$end = $start;
if (isset($buf[1])){
$end += ipRange($buf[1]) - 1;
}
return array(
(float)$start
, (float)$end
, $carrier
, $cidr
);
}

/*
* IPアドレスの範囲の個数を返す
*/
function ipRange($range){
if ($range > 32){
return 0;
} else if ($range

携帯電話のIPアドレス判別

<!-- body, textarea{ font-size : 12px; line-height:135%; font-family:"MS Pゴシック", sans-serif; } textarea{ width:600px; height:500px; } -->

<form><textarea><?php<br /><br />
echo isMobile('192.168.0.1');<br /><br />
/*<br /><br />
* IPアドレスから、携帯キャリアを判別する<br /><br />
* IPアドレス帯域は追加・削除されることがあります。<br /><br />
* 携帯電話各社の発表事項に注意してください。<br /><br />
*/<br /><br />
function isMobile($ipAddress){<br /><br />
$ip = sprintf('%u', ip2long($ipAddress));</p><br />
<p>return '';<br /><br />
}<br /><br />
</textarea></form>

ちなみに、このプログラムの発想のキッカケは、http://d.hatena.ne.jp/tasukuchan/20071231/1199105717が目に入ったからだったりします。隣接したIPアドレスとか、二分探索など参考にさせて頂きました。