Created
August 2, 2017 10:25
-
-
Save galaxy001/cecf38c2fd2caedbb9bd9c7a824f6d5a to your computer and use it in GitHub Desktop.
dns2https.swoole.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
tcp dns client for google dns over https (https://dns.google.com) | |
ubuntu上使用: | |
在/etc/rc.local里加/usr/bin/php /home/<your_name>/dns2https.php | |
执行: | |
sysv-rc-conf unbound off | |
sysv-rc-conf dnscrypt-proxy off | |
sysv-rc-conf dnsmasq off | |
在/etc/resolv.conf里加: | |
nameserver 127.0.0.1 | |
在/etc/network/interfaces正在使用的iface下加: | |
dns-nameservers 127.0.0.1 | |
firefox需要在about:config里设: | |
network.dnsCacheEntries=1 #默认400条, 改成1条 | |
network.dnsCacheExpiration=1 #默认60秒, 改成1秒 | |
network.dnsCacheExpirationGracePeriod=1 #默认60秒, 改成1秒 | |
只上面3个似乎无效(关掉dsn2https.php刷网页仍能到达),看DNS Flusher这个addon, 还要services.cache2.clear()才真正清掉dns cache | |
chrome需要在chrome://net-internals/#dns里: | |
右上角黑箭头/Clear cache 及 Flush sockets 才真正清掉dns cache, 只点击其中一个按钮似乎无效 | |
编辑/etc/security/limits.conf里: * soft nofile 65535 及 * hard nofile 65535 | |
*/ | |
//thanks to: | |
//https://github.com/mikepultz/netdns2/ | |
//https://github.com/yswery/PHP-DNS-SERVER/ | |
/* | |
开发时录制模仿系统的dns请求: | |
//mkfifo fifo | |
//nc -lk localhost 53 <fifo | tee -a a.txt | nc 8.8.4.4 53 | tee -a b.txt >fifo | |
$data=file_get_contents('a.txt'); | |
var_export(header_parse(substr($data, 2))); | |
$q=question_parse(substr($data, 14)); | |
var_export($q); | |
var_export(answer_parse(substr($data, 14+$q['length']), 1, substr($data, 0, 14+$q['length']))); | |
exit(); | |
*/ | |
$is_tcp=0; //1支持tcp, 0则支持udp | |
$timeout=3; //每个并发域名查询的超时时间 | |
$concurrent=50; //并发数量, 公司内网每个工位出口连接数可能有限制, 用ab -v 2 -n 6 -c 6 -s 10 ...连接一个自己用nginx+php搞的长连接就可以看到(php-fpm.conf的pm.max_children先要调大), 到第6个就一直在等待header都不会返回, 杀掉前面的第6个马上开始返回 | |
$force_ttl=3600; //设0则采用真实ttl | |
$last_time=0; | |
$google_domains=array(//Server: sffe | |
'ytimg.com'=>array('query'=>'/yts', 'reg'=>'%Server: sffe%'), | |
'gstatic.com'=>array('query'=>'/', 'reg'=>'%Server: sffe%'), | |
'ajax.googleapis.com'=>array('query'=>'/', 'reg'=>'%Location: https://developers.google.com%'), | |
'drive-thirdparty.googleusercontent.com'=>array('query'=>'/', 'reg'=>'%Server: sffe%'), | |
//Server: gvs | |
//'googlevideo.com'=>array('query'=>'/videoplayback?keepalive=no', 'reg'=>'%403 Status%'), //扫到"Server: gvs 1.0"但报400的不行, 受swoole限制"403 Forbidden"只能写成"403 Status" | |
//gvs的ip总数大概只有500多个, 从20万个里命中太难, 不应参与随机扫描 | |
//r[6-20]---sn-ibj[...].googlevideo.com 组(6到20个)与组之间都是互相独立的, 否则400 | |
'redirector.googlevideo.com'=>array('query'=>'/', 'reg'=>'%Server: ClientMapServer%'), | |
//'s.youtube.com'=>array('query'=>'/api/stats/qoe', 'reg'=>'%Server: Video Stats Server%'), | |
//Server: YouTubeFrontEnd | |
'm.youtube.com'=>array('query'=>'/', 'reg'=>'%Server: YouTubeFrontEnd%'), | |
//Server: Google Frontend | |
'cloud.google.com'=>array('query'=>'/', 'reg'=>'%Server: Google Frontend%'), | |
//Server: HTTP server (unknown) | |
'manifest.googlevideo.com'=>array('query'=>'/api/manifest/', 'reg'=>'%Server: HTTP server (unknown)%'), //"/api/manifest/"最后的"/"不能少 | |
//Server: gws | |
'encrypted.google.com'=>array('query'=>'/', 'reg'=>'%Server: gws%'), | |
//Server: Search-History HTTP Server | |
'myactivity.google.com'=>array('query'=>'/', 'reg'=>'%Server: Search-History HTTP Server%'), | |
//Server: GSE | |
'client-channel.google.com'=>array('query'=>'/client-channel/client', 'reg'=>'%403 unknown client type%'), //'%Server: GSE%' | |
'clients4.google.com'=>array('query'=>'/invalidation/lcs/client', 'reg'=>'%invalidation.XpcSenderServer%'), //'%Server: GSE%' | |
'clients6.google.com'=>array('query'=>'/static/proxy.html', 'reg'=>'%gapi.loader.OBJECT_CREATE_TEST_OVERRIDE%'), //'%Server: GSE%' | |
'payments.google.com'=>array('query'=>'/', 'reg'=>'%Server: GSE%'), | |
'uds.googleusercontent.com'=>array('query'=>'/', 'reg'=>'%Server: GSE%'), | |
//Server: AvailabilityCollection | |
'clients2.google.com'=>array('query'=>'/availability', 'reg'=>'%Server: AvailabilityCollection 1.0%'), | |
//Server: fife | |
'ggpht.com'=>array('query'=>'/', 'reg'=>'%Server: fife%'), | |
'lh3.google.com'=>array('query'=>'/', 'reg'=>'%Server: fife%'), | |
'googleusercontent.com'=>array('query'=>'/', 'reg'=>'%Server: fife%'), | |
//Server: ESF | |
'apis.google.com'=>array('query'=>'/', 'reg'=>'%Server: ESF%'), | |
'fonts.googleapis.com'=>array('query'=>'/css', 'reg'=>'%The requested font families are not available%'), | |
'voice.google.com'=>array('query'=>'/', 'reg'=>'%Server: ESF%'), //'%Location: https://voice.google.com%' | |
'people-pa.clients6.google.com'=>array('query'=>'/v2/people', 'reg'=>'%401 Unauthorized%'), //'%Server: ESF%' | |
'cloudconsole-pa.clients6.google.com'=>array('query'=>'/v2/people', 'reg'=>'%Server: ESF%'), | |
'ogs.google.com'=>array('query'=>'/u/0/_/notifications/count', 'reg'=>'%Server: ESF%'), | |
'drive.google.com'=>array('query'=>'/', 'reg'=>'%Server: ESF%'), | |
//redirect to google accounts | |
'play.google.com'=>array('query'=>'/', 'reg'=>'%Location: https://play.google.com%'), | |
'accounts.google.com'=>array('query'=>'/', 'reg'=>'%Location: https://accounts.google.com%'), | |
'docs.google.com'=>array('query'=>'/', 'reg'=>'%accounts.google.com%'), | |
'chatenabled.mail.google.com'=>array('query'=>'/', 'reg'=>'%Location: https://www.google.com%'), | |
'chrome.google.com'=>array('query'=>'/', 'reg'=>'%Location: https://www.google.com%'), | |
'mail.google.com'=>array('query'=>'/', 'reg'=>'%/mail/%'), | |
'www.google.com'=>array('query'=>'/ncr', 'reg'=>'%Location: https://www.google.com/%'), | |
'ipv4.google.com'=>array('query'=>'/', 'reg'=>'%_rd=cr|/url\?%'), | |
'dns.google.com'=>array('query'=>'/', 'reg'=>'%Google Public DNS%'), | |
'googlesource.com'=>array('query'=>'/', 'reg'=>'%Git at Google%'), | |
'cs.chromium.org'=>array('query'=>'/', 'reg'=>'%Chromium Code Search%'), | |
); | |
$cache=array(//'twitter.com'=>array('time'=>time(), 'ip'=>'127.0.0.1', 'ttl'=>3600*24), | |
//'ton.twitter.com'=>array('time'=>time(), 'ip'=>'127.0.0.1', 'ttl'=>3600*24), | |
//'api.twitter.com'=>array('time'=>time(), 'ip'=>'127.0.0.1', 'ttl'=>3600*24), | |
//'*.twimg.com'=>array('time'=>time(), 'ip'=>'127.0.0.1', 'ttl'=>3600*24), | |
//'t.co'=>array('time'=>time(), 'ip'=>'127.0.0.1', 'ttl'=>3600*24), | |
//'*.cloudfront.net'=>array('time'=>time(), 'ip'=>'127.0.0.1', 'ttl'=>3600*24), | |
//'*.amazonaws.com'=>array('time'=>time(), 'ip'=>'127.0.0.1', 'ttl'=>3600*24), | |
'pan.baidu.com'=>array('time'=>time(), 'ip'=>'180.149.145.241', 'ttl'=>3600*24), | |
'd.pcs.baidu.com'=>array('time'=>time(), 'ip'=>'220.181.7.165', 'ttl'=>3600*24), | |
'*.baidu.com'=>array('time'=>time(), 'ip'=>'255.255.255.255', 'ttl'=>3600*24) | |
); | |
$ipsock_cons=array(); | |
$ipsock_con_datas=array(); | |
$running_domain_tasks=array(); | |
$statis=eval('return '.file_get_contents('statis.txt').';'); | |
$google_ip_ranges=explode('|', '64.18.0.0/20|64.233.160.0/19|66.102.0.0/20|66.249.80.0/20|72.14.192.0/18|74.125.0.0/16|108.177.8.0/21|173.194.0.0/16|207.126.144.0/20|209.85.128.0/17|216.58.192.0/19|216.239.32.0/19|172.217.0.0/19'); | |
foreach ($google_ip_ranges as $cidr) | |
{ | |
$range=cidr2range($cidr); | |
for ($i=$range[0]; $i<=$range[1]; ++$i) | |
{ | |
$google_ips[]=$i; | |
} | |
//echo count($google_ips)."\n"; | |
} | |
$google_ips=array_flip($google_ips); | |
//1.处理监听连接 | |
//2.处理监听收信, 给向第三方发信创建任务 | |
//3.1.执行第三方发信任务 | |
/**********************************************主循环************************************************/ | |
$ipsock=new swoole_server('0.0.0.0', 53, SWOOLE_BASE, $is_tcp?SWOOLE_SOCK_TCP:SWOOLE_SOCK_UDP); | |
$ipsock->on('receive', function ($ipsock, $ipsock_con, $from_id, $data) use(&$ipsock_con_datas, &$cache) | |
{ | |
//print_r($ipsock_con);echo " connected\n"; | |
@$ipsock_cons[$ipsock_con]=$ipsock_con; //加入连接池 | |
//@$ipsock_con_datas[$ipsock_con]['last_time']=time(); //记录首次活动时间 | |
//$ipsock->send($ipsock_con, 'Swoole: '.$data); | |
//$ipsock->close($ipsock_con); | |
@$ipsock_con_datas[$ipsock_con]['last_time']=time(); //记录末次活动时间 | |
$reqs=get_domain_and_packet_id($data); | |
foreach ($reqs as $req) | |
{ | |
if ($req['packet_id']==='') //包头都不完整时跳过, 不断开连接, 有新请求过来就接着处理 | |
{ | |
continue; | |
} | |
@$ipsock_con_datas[$ipsock_con]['domain_packs'][@$req['domain'].':'.@$req['packet_id']]=array('domain'=>@$req['domain'], 'packet_id'=>@$req['packet_id']); | |
$this_con_packs=array($ipsock_con.':'.$req['packet_id']=>array('con'=>$ipsock_con, 'packet_id'=>$req['packet_id'])); | |
if (!preg_match('/[A-Za-z0-9_\-\.]+/', $req['domain']) //不支持含有非英语数字及三符号的域名查询 | |
|| strpos($req['domain'], '.')===false //不支持本地域名查询 | |
|| strpos($req['domain'], 'in-addr')!==false //不支持逆向域名查询inverse address | |
|| $req['type']!=1 //不支持非type==A的查询 | |
) | |
{ | |
$RR_TYPES=array('A'=>1, | |
'NS'=>2, | |
'CNAME'=>5, | |
'MX'=>15, | |
'TXT'=>16, | |
'AAAA'=>28 | |
); | |
$types=array_flip($RR_TYPES); | |
echo "answer: not support (".$req['domain'].':'.$types[$req['type']].")\n"; | |
//直接返回, 不创建domain task | |
send_answer($this_con_packs, $req['domain'], array('Answer'=>array(array('type'=>@$req['type'], 'data'=>'', 'TTL'=>'')))); | |
continue; | |
} | |
if (preg_match('/\d+\.\d+\.\d+\.\d+/', $req['domain'])) | |
{ | |
send_answer($this_con_packs, $req['domain'], array('Answer'=>array(array('type'=>@$req['type'], 'data'=>$req['domain'], 'TTL'=>0)))); | |
continue; | |
} | |
//已有缓存的域名且未过期 | |
if (@$cache[$req['domain']] && time()-$cache[$req['domain']]['time']<$cache[$req['domain']]['ttl']) | |
{ | |
//echo 'matched ';var_export($cache[$req['domain']]); | |
//直接返回, 不创建domain task | |
send_answer($this_con_packs, $req['domain'], array('Answer'=>array(array('type'=>1, 'data'=>$cache[$req['domain']]['ip'], 'TTL'=>$cache[$req['domain']]['ttl']-time()+$cache[$req['domain']]['time'])))); | |
continue; | |
} | |
//支持泛域名默认配置 | |
$tmp='*.'.implode('.', array_slice(explode('.', $req['domain']), -2)); | |
if (@$cache[$tmp]) | |
{ | |
//echo 'matched ';var_export($cache); | |
//直接返回, 不创建domain task | |
send_answer($this_con_packs, $req['domain'], array('Answer'=>array(array('type'=>1, 'data'=>$cache[$tmp]['ip'], 'TTL'=>$cache[$tmp]['ttl']-time()+$cache[$tmp]['time'])))); | |
continue; | |
} | |
unset($cache[$req['domain']]); //过期的缓存要清除 | |
create_query_tasks($ipsock_con, $req['packet_id'], $req['domain']); | |
} | |
} | |
); | |
$ipsock->on('workerstart', function($ipsock, $worker_id) | |
{ | |
if ($worker_id==0) | |
{ | |
$ipsock->tick(100, 'tick_process'); | |
} | |
} | |
); | |
$ipsock->set(array('worker_num'=>1)); //好像全局变量不能跨worker, 只能设成1个worker | |
$ipsock->start(); | |
function tick_process() | |
{ | |
global $ipsock_cons, $ipsock_con_datas, $last_time, $running_domain_tasks, $statis, $timeout, $cache; | |
//运行日志: | |
if (($cnt=time()-@$last_time)>0) | |
{ | |
echo 'cycle alert: cached domains:'.count($cache).', ' | |
.'available google ips:'.count($statis).', ' | |
.'ipsock_cons:'.count($ipsock_cons).', ' | |
.'running_domain_tasks:'.count($running_domain_tasks).', ' | |
.'memory:'.number_format(memory_get_usage(true)/1000, 0, '.', '').', ' | |
.'rss:'.getrusage()['ru_maxrss']."\n"; | |
gc_collect_cycles(); | |
} | |
$last_time=time(); | |
//结束超时的client connection | |
foreach ($ipsock_cons as $con) //根据末次活动时间判断连接池里的连接是否客户端意外断开(收不到feof信号) | |
{ | |
if (time()-@$ipsock_con_datas[$con]['last_time']>60) | |
{ | |
echo "connection timeout(".$con.")!\n"; | |
end_ipsock_con($con); | |
} | |
} | |
//结束超时的domain task | |
foreach ($running_domain_tasks as $domain=>$task) | |
{ | |
//echo 'running_domain_tasks:'.time()."|".$task['time']; | |
if (time()-$task['time']>$timeout+2) | |
{ | |
echo "domain task timeout(".$domain.")!\n"; | |
$tmp_domain_last_2node=implode('.', array_slice(explode('.', $domain), -2)); | |
$tmp_domain_last_3node=implode('.', array_slice(explode('.', $domain), -3)); | |
$tmp_domain_last_4node=implode('.', array_slice(explode('.', $domain), -4)); | |
$is_google_domain=isset($google_domains[$tmp_domain_last_2node]) || isset($google_domains[$tmp_domain_last_3node]) || isset($google_domains[$tmp_domain_last_4node]); | |
if ($is_google_domain) | |
{ | |
//超时如果是google域名则指向本地转发vps的sniproxy | |
send_answer($task['con_packs'], $domain, array('Answer'=>array(array('type'=>1, 'data'=>'127.0.0.1', 'TTL'=>'60*2')))); | |
} | |
else | |
{ | |
send_answer($task['con_packs'], $domain, array('Answer'=>array(array('type'=>1, 'data'=>'', 'TTL'=>'')))); | |
} | |
//创建的domain task也要清理 | |
end_domain_task($domain); | |
} | |
} | |
} | |
/*********************************************后面都是用到的函数*********************************************/ | |
function end_ipsock_con($con) | |
{ | |
global $ipsock_cons, $ipsock_con_datas, $running_domain_tasks, $ipsock; | |
foreach (@$ipsock_con_datas[$con]['domain_packs'] as $domain_pack) | |
{ | |
unset($running_domain_tasks[$domain_pack['domain']]['con_packs'][$con.':'.$domain_pack['packet_id']]); //等domain task回来后就少一个客户端连接需要回信了 | |
} | |
@$ipsock->close($con); //客户端断掉的连接(有feof信号)或者客户端意外中断(没有feof信号)服务端都要再断一次 | |
unset($ipsock_cons[$con]); //从轮询队列里移除 | |
unset($ipsock_con_datas[$con]); //从连接关联数据里移除 | |
//echo print_r($con, 1)." closed, left: ".print_r($ipsock_cons, 1)."\n"; | |
} | |
function end_domain_task($domain) | |
{ | |
global $queue, $running_domain_tasks, $ipsock; | |
echo date('i:s').'('.(time()-$running_domain_tasks[$domain]['time']).'s) end domain task: '.$domain."\n"; | |
//取消所有兄弟handle任务并关闭所有兄弟handles, 清理该域名的running domain task | |
foreach ($running_domain_tasks[$domain]['handles'] as &$handle) | |
{ | |
if (!isset($handle->statusCode)) | |
{ | |
$handle->close(); | |
} | |
} | |
unset($running_domain_tasks[$domain]); | |
} | |
function send_answer($con_packs, $domain, $rtn) | |
{ | |
global $is_tcp, $cache, $ipsock; | |
$answers=@$rtn['Answer']; | |
$answers=$answers?$answers:array(); | |
$ip=''; | |
$ttl=''; | |
foreach ($answers as $answer) | |
{ | |
if ($answer['type']===1) //多个answer里第一个type为ip地址的就返回 | |
{ | |
$ip=@$answer['data']; | |
$ttl=@$answer['TTL']; | |
break; | |
} | |
} | |
echo date('i:s').' answer: '.$ip."\n"; | |
//记到缓存 | |
if ($ip && !@$cache[$domain]) | |
{ | |
$cache[$domain]=array('time'=>time(), 'ip'=>$ip, 'ttl'=>$ttl); | |
//echo 'cached '; print_r($cache); | |
} | |
//如果还有需要回信的client connections就回信 | |
foreach ($con_packs as $con_pack) | |
{ | |
//发给客户端 | |
$res=header_data($con_pack['packet_id'], 1, $ip?1:0).question_data($domain, $ip?1:@$rtn['Answer'][0]['type']).($ip?answer_data_a($ip, $ttl):''); | |
$ipsock->send($con_pack['con'], ($is_tcp?pack('n', strlen($res)):'').$res); | |
//file_put_contents('b.txt', ($is_tcp?pack('n', strlen($res)):'').$res."\n======================\n", FILE_APPEND); | |
} | |
} | |
function on_query_task_response($ch) | |
{ | |
global $google_domains, $statis, $force_ttl, $running_domain_tasks; | |
$success_google_ip=$ch->host; | |
//echo @$task_handle_domain[$done['handle']].":".strlen($content)."==============================\n"; | |
//parse_body_headers($content, $body, $rsp_headers); | |
//var_export($ch); //'errCode' =>0, 'statusCode' => 301, 'type' => 1537, ... | |
$content=build_body_headers($ch->statusCode, $ch->body, $ch->headers); | |
$body=$ch->body; $rsp_headers=$ch->headers; | |
$tmp_domain_last_2node=implode('.', array_slice(explode('.', $ch->domain), -2)); | |
$tmp_domain_last_3node=implode('.', array_slice(explode('.', $ch->domain), -3)); | |
$tmp_domain_last_4node=implode('.', array_slice(explode('.', $ch->domain), -4)); | |
$is_google_domain=isset($google_domains[$tmp_domain_last_2node]) || isset($google_domains[$tmp_domain_last_3node]) || isset($google_domains[$tmp_domain_last_4node]); | |
$args_get['name']=$ch->domain; | |
if ($is_google_domain) | |
{ | |
//echo $success_google_ip.' '.json_encode($rsp_headers).' '.$body."=========================\n"; | |
$body=''; | |
if (@$google_domains[$tmp_domain_last_4node]) | |
{ | |
if (preg_match($google_domains[$tmp_domain_last_4node]['reg'], $content)) | |
{ | |
$body=array('Answer'=>array(array('type'=>1, 'data'=>$success_google_ip, 'TTL'=>60*2))); //google ip容易被封, 写死2分钟过期 | |
} | |
} | |
else if (@$google_domains[$tmp_domain_last_3node]) | |
{ | |
if (preg_match($google_domains[$tmp_domain_last_3node]['reg'], $content)) | |
{ | |
$body=array('Answer'=>array(array('type'=>1, 'data'=>$success_google_ip, 'TTL'=>60*2))); //google ip容易被封, 写死2分钟过期 | |
} | |
} | |
else if (@$google_domains[$tmp_domain_last_2node]) | |
{ | |
//if ($tmp_domain_last_2node=='googlevideo.com') {echo '========>'.$success_google_ip.":\n".$content."<=========\n";} | |
if (preg_match($google_domains[$tmp_domain_last_2node]['reg'], $content)) | |
{ | |
$body=array('Answer'=>array(array('type'=>1, 'data'=>$success_google_ip, 'TTL'=>60*2))); //google ip容易被封, 写死2分钟过期 | |
} | |
} | |
} | |
else | |
{ | |
$body=@json_decode($body, 1); | |
} | |
if ($body) | |
{ | |
echo "===========\nfastest: ".$success_google_ip."\n"; | |
$statis[$success_google_ip]=@$statis[$success_google_ip]+1; | |
arsort($statis); | |
file_put_contents('statis.txt', var_export($statis, 1)); | |
//过滤掉非A记录答案 | |
$body['Answer']=is_array(@$body['Answer'])?$body['Answer']:array(); | |
foreach ($body['Answer'] as $k=>$answer) | |
{ | |
if ($answer['type']===1) //多个answer里第一个type为ip地址的就返回 | |
{ | |
$body['Answer'][$k]['TTL']=($force_ttl && !$is_google_domain)?$force_ttl:intval(@$answer['TTL']); | |
} | |
else | |
{ | |
unset($body['Answer'][$k]); | |
} | |
} | |
//发送答案给所有请求该域名的客户端 | |
send_answer($running_domain_tasks[$args_get['name']]['con_packs'], $args_get['name'], $body); | |
//创建的domain task也要清理 | |
end_domain_task($args_get['name']); | |
} | |
} | |
function create_query_tasks($con, $packet_id, $domain) | |
{ | |
global $google_ips, $statis, $timeout, $queue, $running_domain_tasks, $google_domains, $task_handle_domain, $concurrent; | |
if (!@$running_domain_tasks[$domain]) //没有正在运行的该domain task才去创建domain task | |
{ | |
echo 'new domain task: '.$domain.'('.$con.")\n"; | |
$running_domain_tasks[$domain]=array('con_packs'=>array(), 'handles'=>array(), 'time'=>time()); | |
$half_concurrent=floor($concurrent/2); | |
$half_google_ips=array_rand($google_ips, $half_concurrent>count($google_ips)?count($google_ips):$half_concurrent); | |
$google_ips_init=array_keys($statis); | |
foreach ($google_ips_init as $k=>$v) | |
{ | |
$google_ips_init[$k]=ip2long($v); | |
} | |
$google_ips_init=array_flip($google_ips_init); | |
$half_google_ips_init=array_rand($google_ips_init, $half_concurrent>count($google_ips_init)?count($google_ips_init):$half_concurrent); | |
$selected_google_ips=array_merge($half_google_ips, $half_google_ips_init); | |
//用单独的crontab: * * * * * /usr/local/bin/php /home/malcolm/dns2https.interval.php 里面4组: shell_exec('dig +tcp test.google.com @127.0.0.1');sleep(5); | |
//避免跟statis.txt已有的响应快的google ip竞争失败而扩展不了google ips列表 | |
if ($domain=='test.google.com') | |
{ | |
/*$selected_google_ips=$half_google_ips;*/ | |
$selected_google_ips=$half_google_ips_init; | |
} | |
//var_export($selected_google_ips); | |
foreach ($selected_google_ips as $google_ip) | |
{ | |
$google_ip=long2ip($google_ip); | |
$tmp_domain_last_2node=implode('.', array_slice(explode('.', $domain), -2)); | |
$tmp_domain_last_3node=implode('.', array_slice(explode('.', $domain), -3)); | |
$tmp_domain_last_4node=implode('.', array_slice(explode('.', $domain), -4)); | |
$is_google_domain=isset($google_domains[$tmp_domain_last_2node]) || isset($google_domains[$tmp_domain_last_3node]) || isset($google_domains[$tmp_domain_last_4node]); | |
if ($is_google_domain) | |
{ | |
if (@$google_domains[$tmp_domain_last_4node]) | |
{ | |
$query=$google_domains[$tmp_domain_last_4node]['query']; | |
} | |
else if (@$google_domains[$tmp_domain_last_3node]) | |
{ | |
$query=$google_domains[$tmp_domain_last_3node]['query']; | |
} | |
else //if (@$google_domains[$tmp_domain_last_2node]) | |
{ | |
$query=$google_domains[$tmp_domain_last_2node]['query']; | |
} | |
$url='https://'.$google_ip.$query; | |
$host=$domain.':443'; | |
} | |
else | |
{ | |
$query='/resolve?name='.$domain.'&type=A&dnssec=false&ecs=202.96.209.5'; | |
$url='https://'.$google_ip.$query; | |
//curl -k "https://173.194.203.93/resolve?name=news.163.com&type=A&dnssec=false&ecs=202.96.209.5" -H "Host: dns.google.com" | |
$host='dns.google.com:443'; | |
} | |
$ch=new swoole_http_client($google_ip, 443, true); | |
$ch->set(['timeout'=>$timeout]); | |
$ch->setHeaders(array('Host'=>$host)); | |
$ch->domain=$domain; | |
$running_domain_tasks[$domain]['handles'][]=$ch; //记下一个domain task的10个handles, 后面有一个handle返回就要close掉所有兄弟handle | |
//@$task_handle_domain[$ch->sock]=$domain; //记录改task handle的domain, 因为CURLINFO_EFFECTIVE_URL无法在hash里带上参数 | |
echo $google_ip.', '; | |
$ch->get($query, 'on_query_task_response'); | |
} | |
echo "parallelly sent...\r\n"; | |
} | |
else | |
{ | |
echo 'duplicated domain task: '.$domain.'('.$con.':'.$packet_id.")\n"; | |
} | |
$running_domain_tasks[$domain]['con_packs'][$con.':'.$packet_id]=array('con'=>$con, 'packet_id'=>$packet_id); //往该domain task服务的客户端连接池里加一个客户端连接, task返回时向多个客户端(比如浏览器的多个线程)返回结果 | |
} | |
function get_domain_and_packet_id($data) | |
{ | |
global $is_tcp; | |
$RR_TYPES=array('A'=>1, | |
'NS'=>2, | |
'CNAME'=>5, | |
'MX'=>15, | |
'TXT'=>16, | |
'AAAA'=>28 | |
); | |
$types=array_flip($RR_TYPES); | |
$rtn=array(); | |
while ($data) | |
{ | |
$tmp=array('domain'=>'', 'packet_id'=>'', 'type'=>''); | |
if (strlen($data)<12) //忽略客户端不完整包或非查询包 | |
{ | |
$rtn[]=$tmp; | |
$data=''; | |
continue; | |
} | |
$data=$is_tcp?substr($data, 2):$data; | |
$data_header=header_parse($data); | |
//var_export($data_header);echo "\n"; | |
$tmp['packet_id']=$data_header['id']; | |
$data_question=substr($data, 12); | |
$data=strstr($data_question, "\0"); | |
$tmp['type']=hexdec(bin2hex(substr($data, 1, 2))); | |
$data_question=@substr(expand(strstr($data_question, "\0", 1)), 1); | |
echo "===============\n".date('i:s')." query: ".$data_question.':'.@$types[$tmp['type']]."\n"; | |
$tmp['domain']=strtolower($data_question); | |
$rtn[]=$tmp; | |
$data=@substr($data, 5); | |
} | |
return $rtn; | |
} | |
function myfwrite($fd,$buf) { | |
$i=0; | |
while ($buf != "" and is_resource($fd)) { | |
$i=fwrite ($fd,$buf,strlen($buf)); | |
if ($i==false) { | |
if (!feof($fd)) continue; | |
break; | |
} | |
$buf=substr($buf,$i); | |
} | |
return $i; | |
} | |
//header | |
function header_data($packet_id=1, $qr=0, $c_ans=0) | |
{$packet_id=$packet_id; | |
$qr=$qr; //Query or Response | |
$oc=0; //Op Code | |
$aa=0; //Authoritative Answer | |
$tc=0; //TrunCation | |
$rd=1; //Recursion Desired | |
$ra=1; //Recursion Available | |
$z=0; //reserved | |
$rc=0; //Response Code | |
$c_quest=1; //items count in question | |
$c_ans=$c_ans; //items count in answer | |
$c_auth=0; //items count in authority | |
$c_add=0; //items count in additional | |
$data_put=pack('n6', | |
$packet_id, | |
($qr&0x1)<<15|($oc&0xf)<<11|($aa&0x1)<<10|($tc&0x1)<<9|($rd&0x1)<<8|($ra&0x1)<<7|($z&0x7)<<4|($rc &0xf), | |
$c_quest, | |
$c_ans, | |
$c_auth, | |
$c_add | |
); | |
return $data_put; | |
} | |
//question | |
function question_data($dn, $type=1) | |
{$RR_TYPES=array('A'=>1, | |
'NS'=>2, | |
'CNAME'=>5, | |
'MX'=>15, | |
'TXT'=>16, | |
'AAAA'=>28 | |
); | |
$RR_CLASS_IN=1; //IN means internet | |
$data_put=/*preg_replace(array("/\.([^.]+)/e"), | |
array('chr(strlen("\1"))."\1"'), | |
'.'.$dn | |
)*/ | |
preg_replace_callback_array(array("/\.([^.]+)/"=>function($matches) {return chr(strlen($matches[1])).$matches[1];}), | |
'.'.$dn | |
) | |
."\0" | |
.pack('n2', | |
$type, | |
$RR_CLASS_IN | |
); | |
return $data_put; | |
} | |
//answer 'A' | |
function answer_data_a($ip, $ttl) | |
{$RR_TYPES=array('A'=>1, | |
'NS'=>2, | |
'CNAME'=>5, | |
'MX'=>15, | |
'TXT'=>16, | |
'AAAA'=>28 | |
); | |
$RR_CLASS_IN=1; //IN means internet | |
return "\xC0"."\x0C".pack('nnNn', $RR_TYPES['A'], $RR_CLASS_IN, $ttl, '4').@inet_pton($ip); //"C00C"表示是针对第一域名的答案 | |
} | |
//parse header | |
function header_parse($data) | |
{$data_get=array(); | |
$offset=0; | |
$data_get['id']=ord($data[$offset]) <<8 | ord($data[++$offset]); | |
++$offset; | |
$data_get['qr']=(ord($data[$offset]) >>7) & 0x1; | |
$data_get['oc']=(ord($data[$offset]) >>3) & 0xf; | |
$data_get['aa']=(ord($data[$offset]) >>2) & 0x1; | |
$data_get['tc']=(ord($data[$offset]) >>1) & 0x1; | |
$data_get['rd']=ord($data[$offset]) & 0x1; | |
++$offset; | |
$data_get['ra']=(ord($data[$offset]) >>7) & 0x1; | |
$data_get['z']=0; | |
$data_get['rc']=ord($data[$offset]) & 0xf; | |
$data_get['c_quest']=ord($data[++$offset]) <<8 | ord($data[++$offset]); | |
$data_get['c_ans']=ord($data[++$offset]) <<8 | ord($data[++$offset]); | |
$data_get['c_auth']=ord($data[++$offset]) <<8 | ord($data[++$offset]); | |
$data_get['c_add']= ord($data[++$offset]) <<8 | ord($data[++$offset]); | |
return $data_get; | |
} | |
//parse question | |
function question_parse($pkt) | |
{$name_length=strpos($pkt, "\0"); | |
$qname = substr(expand(substr($pkt, 0, $name_length)), 1); | |
$tmp = unpack('nqtype/nqclass', substr($pkt, $name_length+1, 4)); | |
$tmp['qname'] = $qname; | |
$tmp['length']=$name_length+5; | |
return $tmp; | |
} | |
//parse answer | |
function answer_parse($data, $cnt, $data_before) | |
{$rtn=array(); | |
$offset=-1; | |
for ($i=0;$i<$cnt;++$i) | |
{if (strlen($data)>=16) | |
{$offset+=2; //前两个字节'CXXX', 表示改答案对应整个response的'XXX'偏移的那个域名, 比如第一域名是"C00C" | |
$type=ord($data[++$offset]) <<8 | ord($data[++$offset]); | |
$class=ord($data[++$offset]) <<8 | ord($data[++$offset]); | |
} | |
$ttl=ord($data[++$offset]) <<24 | ord($data[++$offset]) <<16 | ord($data[++$offset]) <<8 | ord($data[++$offset]); | |
$length=ord($data[++$offset]) << 8 | ord($data[++$offset]); | |
if ($type==1) //'A' | |
{if ($length>4) | |
{$offset+=$length; | |
continue; | |
} | |
$rtn[]=array('ttl'=>$ttl, | |
'ip'=>ord($data[++$offset]).'.'.ord($data[++$offset]).'.'.ord($data[++$offset]).'.'.ord($data[++$offset]) | |
); | |
} | |
else if ($type==2) //'NS' | |
{$ndsname=expand(substr($data, $offset+1, $length)); | |
/*$ndsname=preg_replace('/>>>(.)<<</e', | |
'expand(substr($data_before, ord("\1")))', | |
$ndsname | |
);*/ | |
$ndsname=preg_replace_callback_array(array('/>>>(.)<<</'=>function($matches) use ($data_before) {return expand(substr($data_before, ord($matches[1])));}), | |
$ndsname | |
); | |
$ndsname=trim($ndsname, '.'); | |
$rtn[]=array('ttl'=>$ttl, | |
'nsdname'=>$ndsname | |
); | |
$offset+=$length; | |
} | |
} | |
return $rtn; | |
} | |
function expand($result) | |
{/*return preg_replace('/(.)(.*)/se', //加/s因为为长度为10时没匹配到 | |
'($cnt=@ord("\1")) | |
?($cnt===192?">>>":".").substr(($str=@strtr(\'\2\', array(\'\"\'=>\'"\', \'\\\\0\'=>"\x00"))), 0, $cnt).($cnt===192?"<<<":"").expand(substr($str, $cnt)) | |
:"" | |
', | |
$result | |
);*/ | |
//加/s因为为长度为10时没匹配到 | |
return preg_replace_callback_array(array('/(.)(.*)/s'=>function($matches) {return ($cnt=@ord($matches[1])) | |
?($cnt===192?">>>":".").substr(($str=@strtr($matches[2], array('\"'=>'"', '\\0'=>"\x00"))), 0, $cnt).($cnt===192?"<<<":"").expand(substr($str, $cnt)) | |
:""; | |
} | |
), | |
$result | |
); | |
} | |
function &curl_to_host($method, $url, $headers, $data, &$resp_headers, $total_timeout=20) | |
{$ch=curl_init($url); | |
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 800); //目前到美国uploaded第一个byte基本都需要800ms | |
//CURLOPT_CONNECTTIMEOUT是包含dns query time的, 如果已拿到ip, 可以用CURLOPT_CONNECTTIMEOUT_MS, 并且设成略大于ping的时间就行, 也是需要curl大等于7.16.2 | |
//感觉CURLOPT_CONNECTTIMEOUT还包含从connected到uploaded第一个byte的时间, 因为经常看到curl_getinfo($done['handle'])['connect_time']远小于CURLOPT_CONNECTTIMEOUT仍然会立即断开连接 | |
curl_setopt($ch, CURLOPT_TIMEOUT, $total_timeout); //phpinfo()里看到cURL Information里的版本号大等于7.16.2, 就可以用CURLOPT_TIMEOUT_MS设置毫秒总超时 | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //必须加该参数, 要不然会直接在当前页面输出curl的response body | |
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); | |
curl_setopt($ch, CURLOPT_NOSIGNAL, true); //skip slow dns resolve | |
curl_setopt($ch, CURLOPT_HEADER, 1); | |
if (stripos($url, 'https')===0) | |
{curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); | |
//if you want to verify host: | |
//curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); | |
//curl_setopt($ch, CURLOPT_CAINFO, '/etc/ssl/certs/ca-certificates.crt'); | |
//curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); | |
} | |
if ($method=='POST') | |
{curl_setopt($ch, CURLOPT_POST, true); | |
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); | |
} | |
foreach ($headers as $k=>$v) | |
{$headers[$k]=str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $k)))).': '.$v; | |
} | |
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); | |
return $ch; | |
} | |
function parse_body_headers($rtn, &$body, &$rsp_headers) | |
{$rtn=explode("\r\n\r\nHTTP/", $rtn, 2); //to deal with "HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK...\r\n\r\n..." header | |
$rtn=(count($rtn)>1 ? 'HTTP/' : '').array_pop($rtn); | |
@list($str_resp_headers, $body)=explode("\r\n\r\n", $rtn, 2); | |
$str_resp_headers=explode("\r\n", $str_resp_headers); | |
array_shift($str_resp_headers); //get rid of "HTTP/1.1 200 OK" | |
$rsp_headers=array(); | |
foreach ($str_resp_headers as $k=>$v) | |
{$v=explode(': ', $v, 2); | |
$rsp_headers[$v[0]]=$v[1]; | |
} | |
} | |
function build_body_headers($status, $body, $headers) | |
{ | |
$rtn='HTTP/1.1 '.$status." Status\r\n"; | |
foreach ($headers as $k=>$v) | |
{ | |
$rtn.=str_replace(' ', '-', ucwords(strtolower(str_replace('-', ' ', $k)))).': '.$v."\r\n"; | |
} | |
$rtn.="\r\n".$body; | |
return $rtn; | |
} | |
function cidr2range($cidr) | |
{ | |
$range = array(); | |
$cidr = explode('/', $cidr); | |
$range[0] = (ip2long($cidr[0])) & ((-1 << (32 - (int)$cidr[1]))); | |
$range[1] = (ip2long($cidr[0])) + pow(2, (32 - (int)$cidr[1])) - 1; | |
return $range; | |
} | |
if (!function_exists('preg_replace_callback_array')) { | |
function preg_replace_callback_array (array $patterns_and_callbacks, $subject, $limit=-1, &$count=NULL) { | |
$count = 0; | |
foreach ($patterns_and_callbacks as $pattern => &$callback) { | |
$subject = preg_replace_callback($pattern, $callback, $subject, $limit, $partial_count); | |
$count += $partial_count; | |
} | |
return preg_last_error() == PREG_NO_ERROR ? $subject : NULL; | |
} | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment