commit feef31ca0360a73f27a40e2b803e07debff3eb07 Author: Enoch Date: Sun Dec 18 18:52:56 2022 +0800 干就完了 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1795c8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.history \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4480759 --- /dev/null +++ b/composer.json @@ -0,0 +1,13 @@ +{ + "name": "laysense/dns", + "type": "library", + "license": "MIT", + "description": "Webman plugin laysense/dns. You can making a DNS Server with Webman now", + "require": { + }, + "autoload": { + "psr-4": { + "Laysense\\Dns\\": "src" + } + } +} \ No newline at end of file diff --git a/image/readme/1671360565549.png b/image/readme/1671360565549.png new file mode 100644 index 0000000..ac2cc7a Binary files /dev/null and b/image/readme/1671360565549.png differ diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..1d346d1 --- /dev/null +++ b/readme.md @@ -0,0 +1,103 @@ +# Webman DNS + +Webman的DNS服务器插件,可以实现Webman启动时运行一个DNS服务器 + +> 注意:默认为udp53端口,需要ROOT权限 + +--- + +## 支持的DNS类型: + +* A +* AAAA +* CNAME +* SOA +* PTR +* MX +* TXT + +最新版本已经增加了CNAME+A和CNAME+AAAA方式 + +--- + +## 安装 + +```shell +composer require laysense/dns +``` + + +## 配置 + +> 配置文件位于 /config/plugin/laysense/dns/process.php + +```php + [ + 'handler' => process\DnsProcess::class, + 'listen' => 'Dns://0.0.0.0:53', #使用的端口,53端口需要root权限 + 'transport' => 'udp', + 'count' => cpu_count() * 4 #线程数量 + ], +]; +``` + + +## 使用 + +> 为了方便您的使用,本插件(不要脸地)导入了一个Controller +> +> 位于 /app/controller/DnsController.php +> +> 【如果这影响到了您的项目和您的开发习惯,请修改/process/DnsProcess.php 文件】 +> +> 安装前请先保障文件不冲突 + +> 本DNS插件只提供了一个DNS请求和响应的接口,其余的数据库、DNS查询、多级缓存、递归等需要您自行实现 + +该Controller名存实亡,其实就是一个class + +```php +ip2bin($ip); + */ + +class DnsController +{ + public function DNS($type,$name,$rip,$id,$query) + { + #输出信息 + #echo "\n Type:$type \n Domain: $name\n Client IP: $rip \n"; + + + #此处请根据业务需要,通过判断$name和$rip返回正确的数据 + #详情请参见 https://github.com/ywnsya/workerman-dns 尤其是 https://github.com/ywnsya/Workerman-DNS/blob/master/start.php 中的用法 + + $send['detail']='dns.laysense.com'; + $send['ttl']=30; + $send['type']='PTR'; + + + #此处无需修改 + $send['id']=$id; + $send['query']=$query; + $return=json_encode($send); + return $return; + } +} +``` + + +具体的使用方式请参照 [Workerman-DNS](https://www.workerman.net/a/1439) ([Github](https://github.com/ywnsya/workerman-dns)) 下的start.php + + +## 赞助(我不要脸) + +![1671360565549](image/readme/1671360565549.png) diff --git a/src/Install.php b/src/Install.php new file mode 100644 index 0000000..1347b4b --- /dev/null +++ b/src/Install.php @@ -0,0 +1,80 @@ + 'config/plugin/laysense/dns', +); + + /** + * Install + * @return void + */ + public static function install() + { + static::installByRelation(); + } + + /** + * Uninstall + * @return void + */ + public static function uninstall() + { + self::uninstallByRelation(); + } + + /** + * installByRelation + * @return void + */ + public static function installByRelation() + { + foreach (static::$pathRelation as $source => $dest) { + if ($pos = strrpos($dest, '/')) { + $parent_dir = base_path().'/'.substr($dest, 0, $pos); + if (!is_dir($parent_dir)) { + mkdir($parent_dir, 0777, true); + } + } + //symlink(__DIR__ . "/$source", base_path()."/$dest"); + copy_dir(__DIR__ . "/$source", base_path()."/$dest"); + echo "Create $dest +"; + } + copy(__DIR__ .'/resource/Dns.php',base_path().'/vendor/workerman/workerman/Protocols/Dns.php'); + echo "Create DNS Protocol Successfully"; + copy(__DIR__ .'/resource/DnsProcess.php',base_path().'/process/DnsProcess.php'); + echo "Create DNS Process Successfully"; + copy(__DIR__ .'/resource/DnsController.php',base_path().'/app/controller/DnsController.php'); + echo "Create Dns Controller Successfully"; + } + + /** + * uninstallByRelation + * @return void + */ + public static function uninstallByRelation() + { + foreach (static::$pathRelation as $source => $dest) { + $path = base_path()."/$dest"; + if (!is_dir($path) && !is_file($path)) { + continue; + } + echo "Remove $dest +"; + if (is_file($path) || is_link($path)) { + unlink($path); + continue; + } + remove_dir($path); + } + } + +} \ No newline at end of file diff --git a/src/config/plugin/laysense/dns/app.php b/src/config/plugin/laysense/dns/app.php new file mode 100644 index 0000000..8f9c426 --- /dev/null +++ b/src/config/plugin/laysense/dns/app.php @@ -0,0 +1,4 @@ + true, +]; \ No newline at end of file diff --git a/src/config/plugin/laysense/dns/process.php b/src/config/plugin/laysense/dns/process.php new file mode 100644 index 0000000..ec5ab5b --- /dev/null +++ b/src/config/plugin/laysense/dns/process.php @@ -0,0 +1,9 @@ + [ + 'handler' => process\DnsProcess::class, + 'listen' => 'Dns://0.0.0.0:53', + 'transport' => 'udp', + 'count' => cpu_count() * 4 + ], +]; \ No newline at end of file diff --git a/src/install.backup b/src/install.backup new file mode 100644 index 0000000..677cc4b --- /dev/null +++ b/src/install.backup @@ -0,0 +1,78 @@ + 'config/plugin/laysense/dns', +); + + /** + * Install + * @return void + */ + public static function install() + { + static::installByRelation(); + } + + /** + * Uninstall + * @return void + */ + public static function uninstall() + { + self::uninstallByRelation(); + } + + /** + * installByRelation + * @return void + */ + public static function installByRelation() + { + foreach (static::$pathRelation as $source => $dest) { + if ($pos = strrpos($dest, '/')) { + $parent_dir = base_path().'/'.substr($dest, 0, $pos); + if (!is_dir($parent_dir)) { + mkdir($parent_dir, 0777, true); + } + } + //symlink(__DIR__ . "/$source", base_path()."/$dest"); + copy_dir(__DIR__ . "/$source", base_path()."/$dest"); + echo "Create $dest +"; + } + copy(__DIR__ .'/resource/Dns.php',base_path().'/vendor/workerman/workerman/Protocols/Dns.php'); + echo "Create DNS Protocol Successfully"; + copy(__DIR__ .'/resource/DnsProcess.php',base_path().'/process/DnsProcess.php'); + echo "Create DNS Process Successfully"; + } + + /** + * uninstallByRelation + * @return void + */ + public static function uninstallByRelation() + { + foreach (static::$pathRelation as $source => $dest) { + $path = base_path()."/$dest"; + if (!is_dir($path) && !is_file($path)) { + continue; + } + echo "Remove $dest +"; + if (is_file($path) || is_link($path)) { + unlink($path); + continue; + } + remove_dir($path); + } + } + +} \ No newline at end of file diff --git a/src/resource/Dns.php b/src/resource/Dns.php new file mode 100644 index 0000000..f44ee5d --- /dev/null +++ b/src/resource/Dns.php @@ -0,0 +1,419 @@ +type; + switch($type){ + case 'A': + $type='0001'; + #$lenth='0004'; + $ip=$buffer->detail; + $n=0; + foreach($ip as $i){ + $nss=explode('.',$i); + $detail[$n]=''; + foreach($nss as $part){ + $tpart=str_pad(dechex($part),2,"0",STR_PAD_LEFT); + $detail[$n]=$detail[$n].$tpart; + }; + $lenth[$n]=str_pad(dechex((strlen($detail[$n])/2)),4,"0",STR_PAD_LEFT); + $n=$n+1; + }; + break; + case 'NS': + $type='0002'; + #$lenth='0004'; + $ns=$buffer->detail; + $n=0; + foreach($ns as $i){ + $nss=explode('.',$i); + $detail[$n]=''; + foreach($nss as $part){ + #$len=strlen($part); + $len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT); + $tpart=bin2hex($part); + $detail[$n]=$detail[$n].$len.$tpart; + }; + $detail[$n]=$detail[$n].'00'; + $lenth[$n]=str_pad(dechex((strlen($detail[$n])/2)),4,"0",STR_PAD_LEFT); + $n=$n+1; + }; + break; + case 'PTR': + $type='000C'; + $ns=$buffer->detail; + $nss=explode('.',$ns); + $detail[0]=''; + foreach($nss as $part){ + $len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT); + $tpart=bin2hex($part); + $detail[0]=$detail[0].$len.$tpart; + }; + $detail[0]=$detail[0].'00'; + $lenth[0]=str_pad(dechex((strlen($detail[0])/2)),4,"0",STR_PAD_LEFT); + break; + case 'CNAME': + $type='0005'; + $ns=$buffer->detail; + $n=0; + foreach($ns as $i){ + $nss=explode('.',$i); + $detail[$n]=''; + foreach($nss as $part){ + #$len=strlen($part); + $len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT); + $tpart=bin2hex($part); + $detail[$n]=$detail[$n].$len.$tpart; + }; + $detail[$n]=$detail[$n].'00'; + $lenth[$n]=str_pad(dechex((strlen($detail[$n])/2)),4,"0",STR_PAD_LEFT); + $n=$n+1; + }; + break; + case 'CNAME+A': + $type='0005'; + $ns=$buffer->detail; + $nss=explode('.',$ns); + $detail[0]=''; + foreach($nss as $part){ + $len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT); + $tpart=bin2hex($part); + $detail[0]=$detail[0].$len.$tpart; + }; + $detail[0]=$detail[0].'00'; + $lenth[0]=str_pad(dechex((strlen($detail[0])/2)),4,"0",STR_PAD_LEFT); + + $ttl=str_pad(dechex($buffer->ttl),8,"0",STR_PAD_LEFT); + + $answer=''; + $answer=$answer.'C00C'.$type.'0001'.$ttl.$lenth[0].$detail[0]; + + $ip=dns_get_record($ns,DNS_A); + $type='0001'; + $n=0; + foreach($ip as $i){ + $ttl=str_pad(dechex($i['ttl']),8,"0",STR_PAD_LEFT); + $i=$i['ip']; + $nss=explode('.',$i); + $detail[$n]=''; + foreach($nss as $part){ + $tpart=str_pad(dechex($part),2,"0",STR_PAD_LEFT); + $detail[$n]=$detail[$n].$tpart; + }; + $lenth[$n]=str_pad(dechex((strlen($detail[$n])/2)),4,"0",STR_PAD_LEFT); + $n=$n+1; + + }; + $n=0; + foreach($detail as $c){ + $rlenth=''; + $rlenth=$lenth[$n]; + $n=$n+1; + $answer=$answer.'C02B'.$type.'0001'.$ttl.$rlenth.$c; + } + + $status='8180'; + $questions='0001'; + $AuthorityRRs='0000'; + $AdditionalRRs='0000'; + + $AnswerRRs=str_pad((count((array)$ip)+1),4,"0",STR_PAD_LEFT); + + $response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query.$answer; + return hex2bin($response); + + break; + case 'CNAME+AAAA': + $type='0005'; + $ns=$buffer->detail; + $nss=explode('.',$ns); + $detail[0]=''; + foreach($nss as $part){ + $len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT); + $tpart=bin2hex($part); + $detail[0]=$detail[0].$len.$tpart; + }; + $detail[0]=$detail[0].'00'; + $lenth[0]=str_pad(dechex((strlen($detail[0])/2)),4,"0",STR_PAD_LEFT); + + $ttl=str_pad(dechex($buffer->ttl),8,"0",STR_PAD_LEFT); + + $answer=''; + $answer=$answer.'C00C'.$type.'0001'.$ttl.$lenth[0].$detail[0]; + + $ip=dns_get_record($ns,DNS_AAAA); + $type='001C'; + $n=0; + foreach($ip as $i){ + $ipv6=$i['ipv6']; + $hexstr = unpack("H*hex", inet_pton($ipv6)); + $ipv6=substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hexstr['hex']), 0, -1); + $ipv6=str_replace(':','',$ipv6); + #$ipv6= bin2hex($ipv6); + $detail[$n]="$ipv6"; + $lenth[$n]="0010"; + $n=$n+1; + }; + + $n=0; + foreach($detail as $c){ + $rlenth=''; + $rlenth=$lenth[$n]; + $n=$n+1; + $answer=$answer.'C02C'.$type.'0001'.$ttl.$rlenth.$c; + } + + $status='8180'; + $questions='0001'; + $AuthorityRRs='0000'; + $AdditionalRRs='0000'; + + $AnswerRRs=str_pad((count((array)$ip)+1),4,"0",STR_PAD_LEFT); + + $response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query.$answer; + return hex2bin($response); + + break; + case 'SOA': + $type='0006'; + $ns=$buffer->detail; + $ns=json_decode( json_encode( $ns),true); + if($ns['type']=='none'){ + $Rns=dns_get_record($ns['name'],DNS_SOA); + $Rns=$Rns[0]; + $ns=$Rns; + $buffer->ttl=$Rns['ttl']; + } + + $nss=explode('.',$ns['mname']); + $detail[0]=''; + foreach($nss as $part){ + $len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT); + $tpart=bin2hex($part); + $detail[0]=$detail[0].$len.$tpart; + }; + $detail[0]=$detail[0].'00'; + unset($nss,$len,$tpart); + $nss=explode('.',$ns['rname']); + foreach($nss as $part){ + $len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT); + $tpart=bin2hex($part); + $detail[0]=$detail[0].$len.$tpart; + }; + $detail[0]=$detail[0].'00'.str_pad(dechex($ns['serial']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['refresh']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['retry']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['expire']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['minimum-ttl']),8,"0",STR_PAD_LEFT); + + + $lenth[0]=str_pad(dechex((strlen($detail[0])/2)),4,"0",STR_PAD_LEFT); + break; + case 'AAAA': + $type='001C'; + $ip=$buffer->detail; + $n=0; + foreach($ip as $i){ + $detail[$n]="$i"; + $lenth[$n]="0010"; + $n=$n+1; + }; + break; + case 'TEXT': + $type='0010'; + $ns=$buffer->detail; + $n=0; + foreach($ns as $i){ + $detail[$n]=''; + $text=bin2hex($i); + $tlen=str_pad(dechex((strlen($text)/2)),2,"0",STR_PAD_LEFT); + $detail[$n]=$tlen.$text; + $lenth[$n]=str_pad(dechex((strlen($detail[$n])/2)),4,"0",STR_PAD_LEFT); + $n=$n+1; + }; + break; + case 'MX': + $type='000F'; + $ns=$buffer->detail; + $n=0; + + print_r($ns); + + foreach($ns as $i){ + $nss=explode('.',$i->name); + $detail[$n]=''; + foreach($nss as $part){ + #$len=strlen($part); + $len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT); + $tpart=bin2hex($part); + $detail[$n]=$detail[$n].$len.$tpart; + }; + $detail[$n]=$detail[$n].'00'; + $lenth[$n]=str_pad(dechex((strlen($detail[$n])/2)+2),4,"0",STR_PAD_LEFT).str_pad(dechex($i->pre),4,"0",STR_PAD_LEFT); + $n=$n+1; + }; + break; + case 'none': + $type='0006'; + $ns=$buffer->detail; + $url=$ns; + while(true){ + preg_match("#\.(.*)#i",$url,$match);//获取根域名 + $domin = $match[1]; + $soa=dns_get_record($domin,DNS_SOA); + if(array_key_exists('0',$soa)){ + if(array_key_exists('mname',$soa[0])){ + $qname=$domin; + $ns=$soa[0]; + break; + }else{ + $url=$domin; + } + }else{ + $url=$domin; + } + } + + $nss=explode('.',$ns['mname']); + $detail=''; + foreach($nss as $part){ + $len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT); + $tpart=bin2hex($part); + $detail=$detail.$len.$tpart; + }; + $detail=$detail.'00'; + unset($nss,$len,$tpart); + $nss=explode('.',$ns['rname']); + foreach($nss as $part){ + $len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT); + $tpart=bin2hex($part); + $detail=$detail.$len.$tpart; + }; + $detail=$detail.'00'.str_pad(dechex($ns['serial']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['refresh']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['retry']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['expire']),8,"0",STR_PAD_LEFT).str_pad(dechex($ns['minimum-ttl']),8,"0",STR_PAD_LEFT); + + + $lenth=str_pad(dechex((strlen($detail)/2)),4,"0",STR_PAD_LEFT); + $ttl=str_pad(dechex($buffer->ttl),8,"0",STR_PAD_LEFT); + $status='8183'; + $questions='0001'; + $AnswerRRs='0000'; + $AuthorityRRs='0001'; + $AdditionalRRs='0000'; + + #$qname + $nss=explode('.',$qname); + $qname=''; + foreach($nss as $part){ + #$len=strlen($part); + $len=str_pad(dechex(strlen($part)),2,"0",STR_PAD_LEFT); + $tpart=bin2hex($part); + $qname=$qname.$len.$tpart; + }; + $qname=$qname.'00'; + + $answer=''; + $answer=$answer.$qname.$type.'0001'.$ttl.$lenth.$detail; + $response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query.$answer; + return hex2bin($response); + break; + } + $ttl=str_pad(dechex($buffer->ttl),8,"0",STR_PAD_LEFT); + $status='8180'; + $questions='0001'; + $AnswerRRs=str_pad(count((array)$buffer->detail),4,"0",STR_PAD_LEFT); + $AuthorityRRs='0000'; + $AdditionalRRs='0000'; + $answer=''; + $n=0; + foreach($detail as $c){ + $rlenth=''; + $rlenth=$lenth[$n]; + $n=$n+1; + $answer=$answer.'C00C'.$type.'0001'.$ttl.$rlenth.$c; + } + $response=$buffer->id.$status.$questions.$AnswerRRs.$AuthorityRRs.$AdditionalRRs.$buffer->query.$answer; + return hex2bin($response); + } + + /** + * 解包,当接收到的数据字节数等于input返回的值(大于0的值)自动调用 + * 并传递给onMessage回调函数的$data参数 + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + $data=bin2hex($buffer); + $id=substr($data,0,4); + $type=substr($data,-8,4); + switch($type){ + case '0001': + $type='A'; + break; + case '0002': + $type='NS'; + break; + case '000c': + $type='PTR'; + break; + case '0006': + $type='SOA'; + break; + case '001c': + $type='AAAA'; + break; + case '0005': + $type='CNAME'; + break; + case '0010': + $type='TEXT'; + break; + case '000f': + $type='MX'; + break; + } + $name=substr($data,24,-8); + $namede=str_split($name,2); + $realname=''; + foreach($namede as $cha){ + $chat=hex2bin($cha); + if(!ctype_alnum($chat)){ + $chat='.'; + } + $realname=$realname.$chat; + } + $realname=substr($realname,1,-1); + $query=substr($data,24); + + #$returndata="$type".'|||'."$realname"; + $returndata= json_encode(array('type' => $type, 'name' => "$realname", 'id'=>"$id", 'query'=>"$query")); + + return $returndata; + } +} \ No newline at end of file diff --git a/src/resource/DnsController.php b/src/resource/DnsController.php new file mode 100644 index 0000000..8c3d2af --- /dev/null +++ b/src/resource/DnsController.php @@ -0,0 +1,33 @@ +ip2bin($ip); + */ + +class DnsController +{ + public function DNS($type,$name,$rip,$id,$query) + { + #输出信息 + #echo "\n Type:$type \n Domain: $name\n Client IP: $rip \n"; + + + #此处请根据业务需要,通过判断$name和$rip返回正确的数据 + #详情请参见 https://github.com/ywnsya/workerman-dns 尤其是 https://github.com/ywnsya/Workerman-DNS/blob/master/start.php 中的用法 + + $send['detail']='dns.laysense.com'; + $send['ttl']=30; + $send['type']='PTR'; + + + #此处无需修改 + $send['id']=$id; + $send['query']=$query; + $return=json_encode($send); + return $return; + } +} \ No newline at end of file diff --git a/src/resource/DnsProcess.php b/src/resource/DnsProcess.php new file mode 100644 index 0000000..dbfe9b4 --- /dev/null +++ b/src/resource/DnsProcess.php @@ -0,0 +1,27 @@ +send($data); + } + + public function onClose(TcpConnection $connection) + { + echo "onClose\n"; + } +} \ No newline at end of file diff --git a/src/resource/ipv6.php b/src/resource/ipv6.php new file mode 100644 index 0000000..d686a34 --- /dev/null +++ b/src/resource/ipv6.php @@ -0,0 +1,204 @@ + + */ +class ipv6 { + function addr($addr=null) { + // 常规获取IPv6地址或格式化IP地址为IPv6格式 + !$addr && ($addr = $_SERVER['REMOTE_ADDR']); + $type = self::type($addr); + if ( $type === 6 && self::ipv6_check($addr) ) return $addr; + elseif ( $type === 4 ) return self::ip426($addr); + else return 'Unknown'; + } + + /** + * realip()因为在Webman(Workerman)下无效,为防止冲突已删除 + */ + + function cut($addr) { + // 压缩IPv6地址 + if (!self::ipv6_check($addr)) return $addr; + $addr = self::fill($addr); + $arr = explode(':',$addr); + foreach ($arr as $a) { + $arr2[] = preg_replace('/^0{1,3}(\w+)/','\1',$a); + } + $addr = join(':',$arr2); + $olen = strlen($addr); + for($i=6;$i>0;$i--){ + // 初步压缩 + $addr = preg_replace('/:(0\:){'.$i.'}/','::',$addr,1); + if (strlen($addr) < $olen ) break; + } + $addr = preg_replace('/^0\:\:/','::',$addr); + $addr = preg_replace('/\:\:0$/','::',$addr); + return $addr; + } + + function fill($addr) { + // 标准IPv6格式 + if (!self::ipv6_check($addr)) return $addr; + $addr = self::_fix_v4($addr); + $arr = explode(':',$addr); + foreach ($arr as $a) { + $l = strlen($a); + if ( $l > 0 && $l < 4 ) + $arr2[] = str_repeat('0', 4-$l).$a; + else $arr2[] = $a; + } + $addr = join(':',$arr2); + $fil = ':'.str_repeat('0000:', 9-count($arr)); + $addr = str_replace('::',$fil,$addr); + $addr = preg_replace('/^\:/','0000:',$addr); + $addr = preg_replace('/\:$/',':0000',$addr); + return $addr; + } + + function ip2bin($addr) { + $type = self::type($addr); + if ( $type === 0 ) return false; + elseif ( $type === 4 ) $addr = self::ip426($addr); + else $addr = self::fill($addr); + $hexstr = str_replace(':','',$addr); + return pack('H*', $hexstr); + } + + function bin2ip($bin) { + if ( strlen($bin) !== 16 ) return false; + $arr = str_split(join('',unpack('H*', $bin)), 4); + $addr = join(':',$arr); + return $addr; + } + + function ip426($addr) { + // IPv4 to IPv6 + if (!self::ipv4_check($addr)) return $addr; + $hex = dechex(self::ip2long($addr)); + $hex = str_repeat('0', 8-strlen($hex)).$hex; + $ipv6 = '0000:0000:0000:0000:0000:0000:'; + $ipv6 .= substr($hex,0,4) . ':' . substr($hex,4,4); + return $ipv6; + } + + function type($addr) { + if ( self::ipv6_check($addr) ) return 6; + elseif ( self::ipv4_check($addr) ) return 4; + else return 0; + } + + function ipv4_check($addr) { + $arr = explode('.', $addr); + $l = count($arr); + for ( $i=0;$i<$l;$i++ ) { + if ( strlen($arr[$i]) > 3 ) return false; + if ( !is_numeric($arr[$i]) ) return false; + $a = intval($arr[$i], 10); + if ($a > 255 || $a <0) return false; + } + return true; + } + + function ipv6_check($addr) { + $addr = self::_fix_v4($addr); + if ( strpos($addr, '.') ) return false; + $l1 = count(explode('::',$addr)); + if ( $l1 > 2 ) return false; + $l2 = count(explode(':',$addr)); + if ( $l2 < 3 || $l2 > 8 ) return false; + if ( $l2 < 8 && $l1 !== 2 ) return false; + preg_match('/^([0-9a-f]{0,4}\:)+[0-9a-f]{0,4}$/i',$addr,$arr); + if ( !$arr[0] ) return false; + return true; + } + + function ip2long($addr) { + $arr = explode('.', $addr); + $l = count($arr); + $long = 0; + for ( $i=0;$i<$l;$i++ ) { + if ( strlen($arr[$i]) > 3 ) return false; + if ( !is_numeric($arr[$i]) ) return false; + $a = intval($arr[$i], 10); + if ($a > 255 || $a <0) return false; + $long += $a * pow(2, 24-$i*8); + } + return $long; + } + + function wan_ip($addr) { + // 检查外网可用地址 + if ( self::ipv6_check($addr) ) { + $addr = self::fill($addr); + // IPv4类地址处理 + $v4p = substr($addr,0,29); + if ( $v4p == '0000:0000:0000:0000:0000:0000' + || strtolower($v4p) == 'ffff:0000:0000:0000:0000:0000' ) { + $t = str_replace($v4p,'',$addr); + $t = str_replace(':','',$t); + $ipv4 = long2ip(hexdec($t)); + return self::_wan_ipv4($ipv4); + } + // 取前16位进行比较 + $v6p = substr($addr,0,4); + $bin = decbin(hexdec($v6p)); + $p = str_repeat(0, 16-strlen($bin)).$bin; + if ( (($p&'1110000000000000')=='0010000000000000') //2000::/3 + || (($p&'1111111000000000')=='1111110000000000') //FC00::/7 + || (($p&'1111111111000000')=='1111111010000000') //FE80::/10 + || (($p&'1111111100000000')=='1111111100000000') //FF00::/8 + ) return false; + return true; + } else { + return self::_wan_ipv4($addr); + } + } + + private function _wan_ipv4($addr){ + if ( !self::ipv4_check($addr) ) return false; + $arr = explode('.',$addr); + $bin = decbin($arr[0]*256+$arr[1]); + $p = str_repeat(0, 16-strlen($bin)).$bin; + $p8 = $p & '1111111100000000'; + $p16 = &$p; + if ( ($p8 == '0000000000000000') // 0/8 + || ($p8 == '0000010100000000') // 5/8 + || ($p8 == '0000101000000000') // 10/8 + || ($p8 == '0001011100000000') // 23/8 + || ($p8 == '0010010000000000') // 36/8 + || ($p8 == '0010010100000000') // 37/8 + || ($p8 == '0010011100000000') // 39/8 + || ($p8 == '0010101000000000') // 42/8 + || ($p8 == '0110010000000000') // 100/8 + || ($p8 == '0110011000000000') // 102/8 + || ($p8 == '0110011100000000') // 103/8 + || ($p8 == '0110100000000000') // 104/8 + || ($p8 == '0110100100000000') // 105/8 + || ($p8 == '0110101000000000') // 106/8 + || ($p8 == '0111111100000000') // 127/8 + || ($p16 == '1010100111111110') // 169.254/16 + || (($p&'1111111111110000')=='1010110000010000') // 172.16/12 + || ($p8 == '1011001100000000') // 179/8 + || ($p8 == '1011100100000000') // 185/8 + || ($p16 == '1100000010101000') // 192.168/16 + || (($p&'1110000000000000')=='1110000000000000') // 224/8-255/8 + ) return false; + return true; + } + + private function _fix_v4($addr) { + // 修正IPv4位址类IPv6格式为标准IPv6格式,不验证合法性 + if ( !strpos($addr, '.') ) return $addr; + preg_match('/(\d+\.){3}\d+$/',$addr,$arr); + if ( !self::ipv4_check($arr[0]) ) return $addr; + $hex = dechex(self::ip2long($arr[0])); + $hex = str_repeat('0', 8-strlen($hex)).$hex; + $v4p = substr($hex,0,4) . ':' . substr($hex,4,4); + $p1 = str_replace($arr[0],'',$addr); + strtolower($p1) === 'ffff:' && $p1 = '::'.$p1; + $addr = $p1 . $v4p; + return $addr; + } +} +?> \ No newline at end of file