IPv4/IPv6 双栈 ddns (DNSPod版)

笔者很早以前就有一个梦想,即:我所有的联网设备,都要能随时随地访问到。比如在公司访问我家里的电脑和 NAS,以及在家里访问公司的工作电脑。

于是笔者开始了漫长的折腾之路:

IPv6 的问题

IPv6 实现所有设备的连通,这点非常棒,就只差一个问题需要解决:适用于 IPv6ddns 方案。

什么是 ddns 呢?它的全称为 dynamic dns,即 动态域名解析

使用场景:家庭宽带下,IP地址 会被运营商定期更换,在自身 IP地址 发生变化后,将新的IP地址提交给 dns解析服务商,让约定的域名解析更新为新的IP地址。

上面 折腾之路[1],通过电信宽带分配的公网IP访问服务,也是需要用 ddns 来更新 IP地址 到自己的域名上,不过一般路由器都内置了 ddns 功能,傻瓜式配置起来也很简单。

但是:

  • 这些成熟且内置的 ddns 方案,都只适用于 IPv4
  • 对于 IPv4,局域网内只需有一台设备(可以是路由器本身,也可以是下属设备)配置 ddns 就可以了,因为整个局域网都共用一个公网IP;而对于 IPv6,每个设备都有自己的公网IP,即需要公网被访问的每台设备都要单独配置 ddns

解决 IPv6ddns

因笔者的域名几乎都是在 腾讯云 上购买的,并且使用 DNSPod 进行解析,故以下所有的方案都是聚焦于 DNSPod

去 DNSPod 开源社区寻找 IPv6-ddns 的解决方案。不得不说,很多工具都写的很棒,代码写的很漂亮,但是都不适用笔者,主要原因为,笔者的设备太多了,所搭载的系统也太杂了,导致那些工具即使为了提高兼容性,采用 纯Python纯Shell 编写,也无法满足所有设备,比如 ESXi,这家伙虽然底层是 Linux,但是魔改了很多,无论是 Shell 还是 Python 环境,都是阉割过的,实测大部分工具都无法正常运行;另一方面,如果遇到多网卡(含虚拟网卡)的场景,那些工具很难获取到准确的且公网可达的本地 IPv6地址

为了解决 第一版方案 的两个痛点:

针对 痛点1:既然通过读取网卡信息无法做到准确地获取公网可达的 IPv6地址,那就更换思路,网上不是有很多查询自身 IPv6地址 吗,比如 api6.ipify.org

$ curl api6.ipify.org 240e:47b:1660:4939:881f:1234:5678:9abc 

因为几乎所有的系统都预装了 curl 或者 wget(比如 ESXicurl 但内置了阉割版的 wget),所以 痛点1 完美解决。

同时也给解决 痛点2 提供了思路:如果我在自己的云主机上部署一个服务,设备定期将自己的 IPv6地址 和与其绑定的域名通过 curl / wget 提交给该服务,让该服务在云主机上代理进行 ddns,将新解析提交给 DNSPod,不就解决了吗?

于是笔者:

Perfect!

第二版 方案用的挺舒心的,但是运行了一段时间发现,出问题了,我好多设备都每分钟请求一次,还挺频繁的,导致 api6.ipify.org 限流,拒绝返回。

换用其他的 IPv6地址查询服务,也都一样,隔一段时间就可能被限流。

所以笔者萌生了自己写一个 IPv6地址查询服务,只为自己提供服务,限不限流我自己说了算!

这回为了造轮子能造的快点,放弃了 NodeJS 改用了 PHP,直接上代码:

PHP 代码

$ cat /home/chaos/IPChecker/index.php <?php echo $_SERVER['REMOTE_ADDR']; 

Nginx 配置文件

$ cat /etc/nginx/conf.d/ip-check.conf server { server_name ip.example.com ipv6.example.com; listen 80; listen [::]:80; root /home/chaos/IPChecker; location / { try_files $uri $uri/ /index.php; fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } 

是不是很简单,PHP 代码就只有一行代码 echo $_SERVER['REMOTE_ADDR'];,即直接输出请求的来源地址,而且同时支持 IPv4/IPv6

  • curl ip.example.com 返回 IPv4 公网地址
  • curl ipv6.example.com 返回 IPv6 公网地址

(注:该服务只能部署在有 IPv6 地址的云主机上,因为 IPv6 只能访问 IPv6

完成一次 ddns,要先从 ipv6.chaosjohn.com 获取 IPv6地址,再向 ddns代理服务 提交,得两步操作,太麻烦了。

所以笔者决定把两项服务合二为一,依旧废话不多说,上代码:

项目根目录

$ ls -alh /home/chaos/ddns-dnspod total 88K drwxrwxr-x 4 chaos chaos 4.0K Nov 3 06:57 . drwxr-xr-x 23 chaos chaos 4.0K Dec 16 15:30 .. -rw-rw-r-- 1 chaos chaos 75 Nov 2 09:24 composer.json -rw-rw-r-- 1 chaos chaos 2.7K Nov 2 09:24 composer.lock -rw-rw-r-- 1 chaos chaos 71 Nov 3 01:46 config.ini -rw-rw-r-- 1 chaos chaos 53 Nov 3 05:54 config-sample.ini drwxrwxr-x 8 chaos chaos 4.0K Nov 3 06:57 .git -rw-rw-r-- 1 chaos chaos 524 Nov 3 05:57 .gitignore -rw-rw-r-- 1 chaos chaos 2.7K Nov 3 02:59 index.php -rw-rw-r-- 1 chaos chaos 35K Nov 3 06:02 LICENSE -rw-r--r-- 1 chaos chaos 358 Nov 2 14:04 nginx.conf -rw-r--r-- 1 chaos chaos 362 Nov 3 05:53 nginx-sample.conf -rw-rw-r-- 1 chaos chaos 941 Nov 3 06:57 README.md drwxrwxr-x 4 chaos chaos 4.0K Nov 2 09:24 vendor 

配置文件(其中 tokenDNSPod 密钥管理里的 ID,token,形如 30345,ac0000000918368b1cfa16f4fc6e28cd

$ cat /home/chaos/ddns-dnspod/config.ini [config] token = 'YOUR-DNSPOD-TOKEN' key = 'YOUR-KEY' 

PHP 代码

$ cat /home/chaos/ddns-dnspod/index.php <?php require __DIR__ . '/vendor/autoload.php'; use Curl\Curl; $curl = new Curl(); $ini_array = parse_ini_file('config.ini', true); $token = $ini_array['config']['token']; // dnspod token $required_key = $ini_array['config']['key']; //echo $_SERVER['HTTP_X_REAL_IP']; $address = $_SERVER['REMOTE_ADDR']; $isIPv6 = strpos($address, ':') > -1; $type = $isIPv6 ? 'AAAA' : 'A'; $domain = $_GET['domain']; $sub = $_GET['sub']; $key = $_GET['key']; if (!$domain || !$sub || 0 != strcmp($key, $required_key)) exit; $output = [ 'domain' => $domain, 'sub' => $sub, 'type' => $type, 'address' => $address ]; $form = "login_token=${token}&format=json&domain=${domain}&sub_domain=${sub}&record_type=${type}&record_line_id=0"; $response = $curl->post('https://dnsapi.cn/Record.List', $form); if ($curl->error) { $output['msg'] = 'Record.List Error' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; } else { $records = $response->records; if (null == $records) $records = []; $records = array_filter($records, function($record) use($type) { return 0 == strcmp($record->type, $type); }); if (count($records) > 0) { // if record exists $record = $records[0]; if (0 == strcmp($record->value, $address)) { // skip if same $output['msg'] = 'Same record. Skipped'; echo json_encode($output); exit; } // update record $record_id = $record->id; $form = "login_token=${token}&format=json&domain=${domain}&record_id=${record_id}&sub_domain=${sub}&value=${address}&record_type=${type}&record_line_id=0"; $response = $curl->post('https://dnsapi.cn/Record.Modify', $form); if ($curl->error) { $output['msg'] = 'Record.Modify Error' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; } else { if (null != $response->status && null != $response->status->code && '1' == $response->status->code) { $output['msg'] = 'Modification succeeded'; } else { $output = $response; $output->action = 'Record.Modify'; } } } else { // record not exists => create $form = "login_token=${token}&format=json&domain=${domain}&sub_domain=${sub}&record_type=${type}&record_line_id=0&value=${address}"; $response = $curl->post('https://dnsapi.cn/Record.Create', $form); if ($curl->error) { $output['msg'] = 'Record.Create Error' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; } else { if (null != $response->status && null != $response->status->code && '1' == $response->status->code) { $output['msg'] = 'Creation succeeded'; } else { $output = $response; $output->action = 'Record.Create'; } } } } echo json_encode($output, JSON_UNESCAPED_UNICODE); 

Nginx 配置文件

$ cat /etc/nginx/conf.d/ddns-dnspod.conf server { server_name ddns.example.com ddns6.example.com; listen 80; listen [::]:80; root /home/chaos/ddns-dnspod; location / { try_files $uri $uri/ /index.php; fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } 

具体使用:

  • cron 的话,在 crontab 里添加一行:
* * * * * logfile='/tmp/ddns6.log'; echo "\n$(date)" >> $logfile && curl 'http://ddns6.example.com?key=YOUR-KEY&domain=DOMAIN&sub=SUBDOMAIN' >> $logfile 
  • cron 的话,使用 while + sleep
while true; do logfile='/tmp/ddns6.log'; echo "\n$(date)" >> $logfile && curl 'http://ddns6.example.com?key=YOUR-KEY&domain=DOMAIN&sub=SUBDOMAIN' >> $logfile; sleep 1; done 
  • 如果没有 curl,只有 wget 的话,将 curl [url] 替换为 wget -q -O - [url]

所有联网设备都可随时随地访问 这个梦想最终通过 IPv6 完美达成了。

期间经历了多个方案,终于在 折腾之路 上告一段落了。

如果后期有更优解,笔者还会回来的!

原文链接:https://www.jianshu.com/p/daad472357bc

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享