IPv4/IPv6 双栈 ddns (DNSPod版) - 简书


本站和网页 https://www.jianshu.com/p/daad472357bc 的作者无关,不对其内容负责。快照谨为网络故障时之索引,不代表被搜索网站的即时页面。

IPv4/IPv6 双栈 ddns (DNSPod版) - 简书登录注册写文章首页下载APP会员IT技术IPv4/IPv6 双栈 ddns (DNSPod版)Chaos_John关注赞赏支持IPv4/IPv6 双栈 ddns (DNSPod版)欢迎转载,但请在开头或结尾注明原文出处【blog.chaosjohn.com】
前言
笔者很早以前就有一个梦想,即:我所有的联网设备,都要能随时随地访问到。比如在公司访问我家里的电脑和 NAS,以及在家里访问公司的工作电脑。
于是笔者开始了漫长的折腾之路:
最开始,家里和公司都是电信宽带,我就不厌其烦地骚扰电信客服,申请公网IP。当路由器分配到了公网IP后,配合 DMZ 和 端口映射,就能将路由器下设备的某些端口暴露在公网上。缺点:如果设备和需要暴露到公网的服务比较多,不拿个记事本把 什么服务暴露在什么端口 记录下来,用到的时候总是会记混淆。
后来搬家后宽带从电信换成了移动,获取公网IP就成了奢望(我还真上过工信部投诉,结果移动致电过来表示可以给我安排停服退款,我怂了,继续用吧)。然后就买了一个花生棒,即花生壳的内网穿透版,因免费版只给两条映射,所以笔者只能贡献给常用机的 SSH(22端口) 和 VNC(5900端口)。缺点:奈何能用的端口只有两个,以及每月只有2G的流量,笔者撑了一段时间还是放弃了它。
后来逐渐接触到各种内网穿透方案,先是 ngork,后来是 frp,将服务端部署在了自己的云主机上后,端口数量限制再也不是问题了,想配多少就配多少。缺点:国内云主机的带宽实在太贵,以阿里云为例,1M带宽23元每月,2M带宽46元,3M带宽71元,4M带宽96元,5M带宽125元,带宽越大单价越贵;国外云主机带宽虽然足,但是延迟太高了,真的很难取舍。
再后来,又接触到了 n2n,个人的云主机上搭建一个节点服务器 supernode,不同设备都连接 supernode,协商后直接进行点对点(P2P)通信,即真正的设备间数据传输是直连的而不经过 supernode。优点:全端口可用,速度不受限。缺点:因为是 P2P 方案,所以需要设备都安装客户端,但它把 Windows / Linux / macOS / Android 都支持遍了,就是没有 iOS 的客户端。
17年下半年的时候,笔者突然发现了神器 ZeroTier。它就是一个增强版的 n2n,不同的是,它默认使用官方的节点服务器(当然个人也可以自建),更为重要的是,几乎所有的系统平台,它都支持,就连各类NAS也能安装使用。笔者直到今天,还在深度使用它。举个例子证明笔者有多爱它:官网是 zerotier.com,笔者于 2018-01-12 在腾讯云注册了域名 zerotier.cn,专门用来解析 ZeroTier 分配给设备的 内网IP,并续费至今。(如果用 whois 查一下该域名,还能发现笔者的真名和邮箱哦哈哈哈哈哈)缺点:在设备点对点之间的线路优化好之前,丢包比较严重,甚至连不上。但只要线路逐渐优化好之后,速度几乎能跑满带宽。
19年下旬的时候,笔者突然发现,家庭宽带和手机蜂窝网络,居然都原生支持 IPv6 了,即无论是路由器下的设备,还是插了SIM卡的移动设备,都能分配到 IPv6地址 了。这意味着,通过某设备的 IPv6 地址,可以直接访问到该设备的所有端口(80/443端口是否被禁得看各地运营商政策)。
IPv6 的问题
用 IPv6 实现所有设备的连通,这点非常棒,就只差一个问题需要解决:适用于 IPv6 的 ddns 方案。
什么是 ddns 呢?它的全称为 dynamic dns,即 动态域名解析。
使用场景:家庭宽带下,IP地址 会被运营商定期更换,在自身 IP地址 发生变化后,将新的IP地址提交给 dns解析服务商,让约定的域名解析更新为新的IP地址。
上面 折腾之路[1],通过电信宽带分配的公网IP访问服务,也是需要用 ddns 来更新 IP地址 到自己的域名上,不过一般路由器都内置了 ddns 功能,傻瓜式配置起来也很简单。
但是:
这些成熟且内置的 ddns 方案,都只适用于 IPv4
对于 IPv4,局域网内只需有一台设备(可以是路由器本身,也可以是下属设备)配置 ddns 就可以了,因为整个局域网都共用一个公网IP;而对于 IPv6,每个设备都有自己的公网IP,即需要公网被访问的每台设备都要单独配置 ddns
解决 IPv6 的 ddns
因笔者的域名几乎都是在 腾讯云 上购买的,并且使用 DNSPod 进行解析,故以下所有的方案都是聚焦于 DNSPod。
第一版方案
去 DNSPod 开源社区寻找 IPv6-ddns 的解决方案。不得不说,很多工具都写的很棒,代码写的很漂亮,但是都不适用笔者,主要原因为,笔者的设备太多了,所搭载的系统也太杂了,导致那些工具即使为了提高兼容性,采用 纯Python 和 纯Shell 编写,也无法满足所有设备,比如 ESXi,这家伙虽然底层是 Linux,但是魔改了很多,无论是 Shell 还是 Python 环境,都是阉割过的,实测大部分工具都无法正常运行;另一方面,如果遇到多网卡(含虚拟网卡)的场景,那些工具很难获取到准确的且公网可达的本地 IPv6地址。
第二版方案
为了解决 第一版方案 的两个痛点:
如何获取到准确的 IPv6 地址
如何能在 几乎 所有系统上都适用(除开Windows,因为笔者几乎不用)
针对 痛点1:既然通过读取网卡信息无法做到准确地获取公网可达的 IPv6地址,那就更换思路,网上不是有很多查询自身 IPv6地址 吗,比如 api6.ipify.org
$ curl api6.ipify.org
240e:47b:1660:4939:881f:1234:5678:9abc
因为几乎所有的系统都预装了 curl 或者 wget(比如 ESXi 无 curl 但内置了阉割版的 wget),所以 痛点1 完美解决。
同时也给解决 痛点2 提供了思路:如果我在自己的云主机上部署一个服务,设备定期将自己的 IPv6地址 和与其绑定的域名通过 curl / wget 提交给该服务,让该服务在云主机上代理进行 ddns,将新解析提交给 DNSPod,不就解决了吗?
于是笔者:
参考 DNSPod API 用 NodeJS 写了一个 ddns代理 的服务部署在云主机上(用 NodeJS 进行编写的原因仅仅是当时的工作内容和 NodeJS 相关,顺手而已)
各设备上用 cron 或 while + sleep 定时(笔者设定的是1分钟)先从 api6.ipify.org 获取本机 IPv6地址 再向该 ddns代理服务 提交。
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
配置文件(其中 token 是 DNSPod 密钥管理里的 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]
代码笔者已经上传到 Github,如何部署请移步 ChaosJohn/ddns-dnspod 参考哈(喜欢的话给个 Star 呗)
最后
所有联网设备都可随时随地访问 这个梦想最终通过 IPv6 完美达成了。
期间经历了多个方案,终于在 折腾之路 上告一段落了。
如果后期有更优解,笔者还会回来的!
推荐阅读更多精彩内容IPv6应用之借力CloudFlare使群晖同时支持IPv4和IPv6访问大家好啊,我是楚风不语。这是我2020年的第四篇更文,编号2020-04。本文主要介绍利用CloudFlare网站...楚风Jacky阅读 15,670评论 5赞 11关于IPV6、IPV4那些事1. IPV6出现的背景 IP是因特网的核心协议,目前使用最多的IPV4是在20世纪70年代末期设计的,因特网经过...简单_977d阅读 1,898评论 0赞 1IPv6系列-初学者的10个常见困扰本文是《IPv6系列》文章的第二篇《常见困扰》,紧接《入门指南》,用于解答IPv6的10个常见困扰。 小慢哥的原创...小慢哥Linux运维阅读 1,603评论 5赞 50“非正常”创业,这家不追风口的美妆品牌服务商如何赢得阿里青睐?5月以来,哪怕对市场风向再不敏感的人,也感觉到阵阵凉意。二级市场连续下挫,一级市场融资环境恶化,不论企业融资数量还...钱皓_0d80阅读 5,492评论 1赞 62018-07-18 先发影响力推荐指数: 6.0 书籍主旨关键词:特权、焦点、注意力、语言联想、情景联想 观点: 1.统计学现在叫数据分析,社会...Jenaral阅读 5,288评论 0赞 5汽车追尾以后 昨天,在回家的路上,坐在车里悠哉悠哉地看着三毛的《撒哈拉沙漠的故事》,我被里面的内容深深吸引住了,尽管上学时...夜阑晓语阅读 3,387评论 2赞 8三槐堂书画院推荐:印玲师父佛教书画,敬请转发,共种福田弘扬佛法三槐堂书画院阅读 2,027评论 0赞 4被自己以前的脑洞震惊到了!一月四号的大沙有个想法。从昨晚到现在就一直围绕在脑子里。或许深受那些小说的影响,或许真的就是我自己脑子或者精神么有...一個人的大沙阅读 3,547评论 4赞 4评论0赞11赞2赞赞赏更多好文