使用Perl来对dnspod进行记录自动更新
2014-03-25 21:50:20 阿炯

在家外想访问家中主机上的资源,这就需要至少能访问到家中的网络设备。但adsl拨号得到的公网ip会隔一段时间发生变化,因此我还需要得到在我的adsl的公网ip发生变化知道。

当然,你还要要adsl上开启nat转发,这样才可能访问内部的服务,这里有一篇文章:如何在adsl得到最高权限来开启此类设置

在我进行不定时更新dns记录前,我了解到一种动态dns解析的网络服务,也了解到一些服务提供商(一般的adsl上就支持自动更新到相应的dns服务商),但我最终还是放弃了这种方式。在老牌的动态dns服务商dyndns的网站上,可能是英语差,没有找到免费的功能块;国内的就更离谱了,邮箱还不够,还要手机和身份证。

求人不求己,我有域名,完全可以分配一个主机名,及时更新指向就行。但需要dns服务商支持这种自我更新的操作接口(api),还好本站的域名虽注册地在国外,但解析是停靠在国内,放在dnspod.cn上。

在起先转入时,曾经注意到他们可以提供api进行域名的操作。在其官方主页发仔细发现确实有详细的api文档,还提供了一些语言的sdk,不过没有关于Perl的只言片语,不过提供的是标准的http请求,Perl表示毫无鸭梨。

本脚本要实现下面的目的:找出本机的公网ip,并与上一次所得到的公网ip相比较,若发生改变则需要更新到dnspod,更新后通知我,需要定期执行。代码如下(该代码已经过时,请直接参考后面的代码):

use feature qw(:5.18);
use Mojo::UserAgent;
use Storable;
use Encode;
use Mojo::JSON qw(decode_json encode_json);
use Data::Dumper;
use Net::SMTP_auth;
binmode(STDOUT,':encoding(utf8)');

#Storable database iphs means storable db hash
my ($sdb,$iphs)=('/tmp/dns.sdb',());
$iphs=retrieve($sdb) if(-e $sdb);

my $ua = Mojo::UserAgent->new();
my $json = Mojo::JSON->new;
$ua->cookie_jar(0);

#get my public ip address(从ip.cn上取得公网ip地址)
my $cip=$ua->get('http://ip.cn')->res->dom->find('code')->text;
chomp($cip);

#define dns modify post hash(提交到dnspod api的语句块,这些信息可以通过其它的api来取得,当然你得有登录信息和真实的域名才行)
my $padata={login_email=>'freeoa@freeoa.net',login_password=>'mypasswd',format=>'json',lang=>'cn','domain_id'=>1256763,record_id=>54725811,record_ty
pe=>'A',ttl=>200,record_line=>decode("utf8",'默认'),sub_domain=>'iamhere',value=>"$cip"};

sub change_dns_ip{
my $tx=$ua->post('https://dnsapi.cn/Record.Modify'=>form=>$padata);
if(my $res=$tx->success){
 my $rc=$json->decode($res->body);
 say Dumper($rc);
 #say $rc->{status}->{message};
}else{
 my ($err, $code) = $tx->error;
 say $code ? "$code response: $err" : "Connection error: $err";
}
}

#handle result and update dns
if(-e $sdb){
 if($cip ne $iphs->{ip}){
  say "public ip is changed to:$cip";
  $iphs->{ip}=$cip;
  change_dns_ip;
  mail_sender('Home public ip chaned',"The new ip is:$cip");
 }else{
  say "ip not change:$cip";
 }
}else{
 #for first time run,must update dns record
 $iphs->{ip}=$cip;
 change_dns_ip;
 mail_sender('Home public ip discover',"The ip is:$cip");
}
#邮件发送函数
sub mail_sender{
my ($subject,$mesg)=(@_);
my $smtp=Net::SMTP_auth->new('mail.fmail.com',Hello => 'mail.fmail.com',Debug=>1);
$smtp->auth('LOGIN','monitor@fmail.com','mpaswd');
#mail from
$smtp->mail('monitor@fmail.com');
#mail to
$smtp->to('zheng@xd-tech.cn');
$smtp->send_and_mail('monitor@fmail.com');
#####
$smtp->data();
$smtp->datasend("From: monitor\@fmail.com\n");
$smtp->datasend("To: zheng\@fmail.com\n");
$smtp->datasend("Subject:$subject \n");
#mail_to
#$smtp->datasend("monitor\@fmail.com");
#注意这里要两个换行,不然不会显示邮件正文内容。。。
$smtp->datasend("\n");
$smtp->datasend("\n");
$smtp->datasend("$mesg \n");
$smtp->dataend();
$smtp->quit;
}## mail send fun end

END{
 #save to storage db
 store $iphs,$sdb;
}


本文是采用Mojolicious的Mojo::UserAgent模块来实现http访问和提交,个人感觉简单的http应用还是HTTP::Tiny 模块好用些。

脚本使用系统的crontab来五分钟调用一次,在dnspod上可以将该记录ttl时间设定的小一些,这样不同的地方缓存该记录的时间更短。


############### 更新于2018年10月 ###############


更新于2018年10月,最新的dnspod主机名的更新办法。有如下一些改进:
1、dnspod.cn启用的新的login_token方式,取代了之前的登录名及密码,生成此种登录方式需要自行进行后台设置生成。
2、官方提供了一个通过socket方式取得客户机公网ip地址的接口,客户不需要从第三方或其它渠道获取其公网地址。
3、Mojo::UserAgent的api调用方式有所改变。

use v5.16;
use Encode;
use Storable;
use Data::Dumper;
use Net::SMTP_auth;
use Mojo::UserAgent;
use IO::Socket::INET;
use Mojo::JSON qw(decode_json encode_json);
binmode(STDOUT,':encoding(utf8)');

my $ua = Mojo::UserAgent->new;

#Storable database iphs means storable db hash
my ($sdb,$iphs,$cip)=('/tmp/dns.sdb');
$iphs=retrieve($sdb) if(-e $sdb);

# auto-flush on socket
$| = 1;
 
# create a connecting socket
my $socket = new IO::Socket::INET(PeerHost => 'ns1.dnspod.net',PeerPort => '6666',Proto => 'tcp');
exit && warn "cannot connect to the server $!" unless $socket;

# data to send to a server
my $size = $socket->send('hello dnspod.');
#print "sent data of length $size\n";

# notify server that request has been sent
shutdown($socket, 1);
 
# receive a response of up to 1024 characters from server
my $res;
$socket->recv($res, 1024);
$cip=$res;

$socket->close();#关闭连接

#say "My public ip is:$cip" if(length($cip)>6);

#define dns modify post hash(提交到dnspod api的语句块,这些信息可以通过其它的api来取得,当然你得有登录信息和真实的域名才行)

my $padata={login_token=>'709nn,39493b4d1bce485f646240fd9bdabcdef',format=>'json',lang=>'cn','domain_id'=>69318880,record_id=>387354261,

record_type=>'A',ttl=>600,record_line_id=>0,sub_domain=>'mysite',value=>"$cip"};

#handle result and update dns
if(-e $sdb){
 if($cip ne $iphs->{ip}){
  #say "public ip is changed to:$cip";
  $iphs->{ip}=$cip;
  change_dns_ip();
 }else{
  #say "ip not change:$cip";
 }
}else{
 #for first time run,must update dns record
 $iphs->{ip}=$cip;
 change_dns_ip();
}

sub change_dns_ip{
    my $tx=$ua->post('https://dnsapi.cn/Record.Modify'=>form=>$padata);
    my $srs=decode_json($tx->res->body);
    if($srs->{status}->{code} != 1){
        #修改失败
        mail_sender('dnspod modify failed!',$srs->{status}->{message});
    }else{
        #修改成功
        mail_sender('office public ip changed',$cip);
    }
}

#邮件发送函数
sub mail_sender{
#同上
}

END{
 #save to storage db
 store $iphs,$sdb;
}



该文章最后由 阿炯 于 2018-10-29 20:42:16 更新,目前是第 2 版。