Skip to content

Instantly share code, notes, and snippets.

@galaxy001
Created August 2, 2017 10:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save galaxy001/cecf38c2fd2caedbb9bd9c7a824f6d5a to your computer and use it in GitHub Desktop.
Save galaxy001/cecf38c2fd2caedbb9bd9c7a824f6d5a to your computer and use it in GitHub Desktop.
dns2https.swoole.php
<?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