为pfSense的DDNS增加腾讯云的实现

前几天刚换了电信宽带,终于有了公网IP,摆脱了移动的大内网,打通家里网络的方案又多几种选择。以前用过花生壳之类的免费DDNS,是挺方便的,但得用服务商的二级域名,没自己的域名用起来自由。家里的路由用的pfSense有DDNS服务,但不支持腾讯云。想着pfSense是开源的,之前申请Let’s encrypt泛域名证书的时候又调试过腾讯云的API了,改造改造难度应该不大,虽然没研究过pfSense的代码,但还是有信心约估个半天可以完成的。于是乎开工:读代码、定位功能模块还算顺利,但调试还是掉坑了,为了把php的调试日志调出来昨晚研究到近2点,最后不得不放弃。今天早上单独调试好模块后,再三确认嵌入的代码,运行后还好能正常工作了,哪天有空再回头研究调试的事情吧。


1.准备

1)工作流程分析

配置DDNS服务->pfSense监测WANIP变化->调用API修改”A”记录->最后通过域名解析服务器获取最新IP

2)需要改造内容

  • 前台的编辑页面:增加腾讯云类型的选项、显示相关的输入选项
  • 后台的处理逻辑:增加腾讯云类型的处理分支、相关的更新记录方法

3)复用之前的腾讯云云解析接口

文章传送门QcloudDns类下载

2.过程

2.1. 前端页面

  • 修改/usr/local/www/services_dyndns_edit.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # 增加qcloud类型客户端的需要显示的选项
    function setVisible(service) {
    switch (service) {
    # ……省略……
    case "qcloud":
    hideGroupInput('domainname', false); # 域名
    hideInput('resultmatch', true);
    hideInput('updateurl', true);
    hideInput('requestif', true);
    hideCheckbox('curl_ipresolve_v4', true);
    hideCheckbox('curl_ssl_verifypeer', true);
    hideInput('host', false); # 主机名
    hideInput('mx', false); # mx值
    hideCheckbox('wildcard', true);
    hideCheckbox('proxied', true);
    hideInput('zoneid', true);
    hideInput('ttl', false); # ttl值
    break;
    # ……省略……
    }
    }

    INFO:该功能函数作用是根据不同类型的客户端,在页面显示不同的配置选项。另外用户名、密码选项默认显示,不需要设置

2.2. 后端处理

  • 修改/etc/inc/services.inc

    1
    2
    3
    4
    5
    # DYNDNS_PROVIDER_VALUES、DYNDNS_PROVIDER_DESCRIPTIONS两个字符串常量的定义中,加入qcloud。注意要加入分隔符,并且保证位置序号一致。
    # ……省略……
    define('DYNDNS_PROVIDER_VALUES', 'all-inkl azure azurev6 citynetwork cloudflare cloudflare-v6 cloudns custom custom-v6 digitalocean dnsexit dnsimple dnsmadeeasy dnsomatic dreamhost dreamhost-v6 duiadns duiadns-v6 dyndns dyndns-custom dyndns-static dyns easydns eurodns freedns freedns-v6 glesys godaddy godaddy-v6 googledomains gratisdns he-net he-net-v6 he-net-tunnelbroker hover loopia namecheap noip noip-free ods opendns ovh-dynhost qcloud route53 route53-v6 selfhost spdyn spdyn-v6 zoneedit');
    define('DYNDNS_PROVIDER_DESCRIPTIONS', 'All-Inkl.com,Azure DNS,Azure DNS (v6),City Network,Cloudflare,Cloudflare (v6),ClouDNS,Custom,Custom (v6),DigitalOcean,DNSexit,DNSimple,DNS Made Easy,DNS-O-Matic,DreamHost,Dreamhost (v6),DuiaDns.net,DuiaDns.net (v6),DynDNS (dynamic),DynDNS (custom),DynDNS (static),DyNS,easyDNS,Euro Dns,freeDNS,freeDNS (v6),GleSYS,GoDaddy,GoDaddy (v6),Google Domains,GratisDNS,HE.net,HE.net (v6),HE.net Tunnelbroker,Hover,Loopia,Namecheap,No-IP,No-IP (free),ODS.org,OpenDNS,OVH DynHOST,qcloud,Route 53,Route 53 (v6),SelfHost,SPDYN,SPDYN (v6),ZoneEdit');
    # ……省略……

    INFO:services_dyndns_edit.php页面的根据这两个常量生成的类型列表。

  • 修改/etc/inc/dyndns.class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # 第一个switch ($this->_dnsService),在'azurev6'分支前加入'qcloud'分支
    function updatedns{
    # ……省略……
    switch ($this->_dnsService){
    # ……省略……
    case 'qcloud': # 增加这个即可,会执行$this->_update();
    case 'azurev6':
    $this->_update();
    if ($this->_dnsDummyUpdateDone == true) {
    // If a dummy update was needed, then sleep a while and do the update again to put the proper address back.
    // Some providers (e.g. No-IP free accounts) need to have at least 1 address change every month.
    // If the address has not changed recently, or the user did "Force Update", then the code does
    // a dummy address change for providers like this.
    sleep(10);
    $this->_update();
    }
    break;
    # ……省略……
    }
    # ……省略……
    }

    INFO:该处判断服务并出发更新操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
 # 第二个switch ($this->_dnsService),加入'qcloud'分支,位置在'default'之前即可。
function _update() {
# ……省略……
switch ($this->_dnsService){
# ……省略……
case 'qcloud':
/* tencent cloud dyndns */
$secretId = $this->_dnsUser; #
$secretKey = $this->_dnsPass; #
$domain = $this->_dnsDomain; #
$subDomain = $this->_dnsHost; #
$recordType = 'A';
$ip = $this->_dnsIP;
$ttl = $this->_dnsTTL;
$mx = $this->_dnsMX;
$obj = new QcloudDns($secretId, $secretKey);
$data = $obj->ListRecords($domain);
$is_newRecord = true;
if($data["code"] == 0){
$data = $data["data"]["records"];
if (is_array($data)) {
foreach ($data as $v) {
if ($v["name"] == $subDomain) {
$recordId = $v["id"];
$result = $obj->UpdateRecord($domain, $recordId, $subDomain, $recordType, $recordLine = '默认', $ip, $ttl = 600, $mx = 0);
$is_newRecord = false;
}
}
if($is_newRecord){
$result = $obj->CreateRecord($domain, $subDomain, $recordType, $recordLine = '默认', $ip, $ttl = 600, $mx = 0);
}
$this->_checkStatus(0, $result, null);
}
}else{
$this->_checkStatus(0, $data,null);
}
break;
default:
break;
}
# ……省略……
}
# ……省略……
> __INFO:该处实现获取腾讯云API配置信息、更新IP操作。__
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 第三个switch ($this->_dnsService),加入'qcloud'分支,位置在'default'之前即可。
function _checkStatus($ch, $data, $header) {
# ……省略……
switch ($this->_dnsService){
# ……省略……
case 'qcloud':
$data = json_encode($data);
if (preg_match("/Success/i", $data)) {
$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
$successful_update = true;
} else {
$status = $status_intro . "(" . gettext("Unknown Response") . ")";
log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
$this->_debug($data);
}
break;
# ……省略……
}
# ……省略……
}
> __INFO:该处根据腾讯云返回的信息判断是否更新成功并输出日志。为了简单点,这里不对错误原因一一分析。__
1
2
3
4
5
6
# 将所有的if($this->_dnsService != 'ods')判断语句,加入'qcloud'类型
# ……省略……
if (!in_array($this->_dnsService, array('ods','qcloud'))){
# ……省略……
}
# ……省略……
> __INFO:原来代码'ods'类型的服务使用特殊的处理流程,跳过通用的curl构造处理流程。新加的'qcloud'直接使用QcloudDns类方法,也不需要用到原来的curl处理流程,所以也加进忽略列表。__
1
2
3
4
# 最后加入QcloudDns类定义代码
class QcloudDns {
# ……省略……
}
> __INFO:也可以做个文件引用。__

3.小结

  • php的配置文件php.ini由/etc/rc.php_ini_setup脚本生成,每次重启会覆盖。
  • sshd的配置文件sshd_config由/etc/sshd脚本生成,每次重启会覆盖;secureCRT7版本不支持默认的钥匙交换协议,可以在配置文件增加旧方法,简单点就升级secureCRT版本。