动态IP解析

本文介绍两种方便获取主机动态IP的方式(DDNS,IP报告网页),并给出相应的代码实现.
shell脚本获取本机IP,执行上传操作和更新DNS操作.定期执行通过crontab或者systemd等服务.

应用场景

远程访问具有动态IP的公网或内网主机时,如果通过ip进行访问,由于公网IP总是在变化,我们不得不每次去查看新的ip地址,往往这个重复的过程比较麻烦.
远程主机联网的方式有所不同,主要有以下几种情形:

  1. 远程主机是通过PPPoE拨号上网,通常获取到动态的私有网络(内网)地址
  2. 远程主机直接获取到的是动态公网ipv4地址.
  3. 在教育网中通常还能获得动态的公网ipv6地址.

又可以简易地分成两类: 配有公网ip的主机与仅配置内网ip的主机.
内网主机访问方式

  1. 反向隧道
    对于躲在NAT之后的内网主机,比较方便的方式是在内网主机建立到公网主机的反向隧道,命令行建立反向隧道工具有:ssh,ngrok,tmate等,参考我之前的反向隧道的文章. 这些工具往往都能在ip发生改变后自动重建连接.
    缺点是我们需要一台拥有公网ip的主机,并且时刻保持隧道长连接,另外由于远程访问内网主机需要经过这个公网主机中转,速度变慢.

  2. 内网穿透

    通过公网服务器得到内网主机在NAT设备的转码地址,然后可以建立p2p的连接.QQ,TeamViewer即是类似原理.前提是内网容易穿透.

  3. 路由器端口映射
    外网IP和端口映射到内网:在路由器的「转发规则」页面添加外网的端口到内网某主机端口的映射.

本着只要有不断重复的麻烦事就用脚本实现的原则,我们通过一些脚本来方便我们的工作.

DDNS (动态域名IP解析)

IP报告/收集脚本

IP报告脚本

通过linux的ip命令获取到本机的公网ip以及通过网站获取本机的外网ip,然后上传到自建的php服务器上.

脚本使用了本地文件记录前一次变更的ip地址,当ip发生变化才执行网络操作.文件保存在内存文件系统或临时文件中.
reportIP.sh

#!/bin/bash
# __author__ = fyk
# get global ipv6 & ipv4 address,
# note that both ipv6 & ipv4 addr may have more than 1.

# grep -v to exclude temporary ipv6 privacy addr.
ip6s=$(ip -6 addr |grep 'global'|grep -v 'tmpaddr'|awk '{print $2}'|sed 's/\/.*//' | uniq)
ip4s_local=$(ip -4 a | grep global |awk '{print $2}' | uniq) # local v4 ips ,public or prive ips that behind NAT
ips_pub=$(curl -s ifconfig.me) # there are lots of websites supplying IP echo services
ip_data=$ips_pub' '${ip6s}' '${ip4s_local}
#echo $ip_data
IP_FILE='/dev/shm/lastip97451' # or in /tmp .etc
ip_data_old=$(cat $IP_FILE 2> /dev/null) # for non-exist file,content is null
if [ "$ip_data" != "$ip_data_old" ];then
        echo 'IP changed,push to remote.'
        #echo $ip_data > $IP_FILE # update the file
        params='k=fyk'
        cnt=0
        for ip in $ip_data;do
                echo $ip
                params=$params"&ip$cnt=$ip" # shell will handle & specially,so we first trans & to %26
                ((cnt=cnt+1))
        done
        params="n=$cnt&$params"
        sever_addr="http://x.makefile.tk/"
        #echo ${sever_addr}
        curl -G -d "$params" "$sever_addr"

else echo 'IP unchanged.'
fi
# TODO:增加断网重连

php收集脚本
提供的服务地址形式是:http://x.makefile.tk/?k=password&n=2&p0=x.x.x.x&p1=x.x.x.x,其中n是ip地址个数,p0,p1,...分别是单独的ip,参数k为了简单地防止一些人捣乱.直接访问http://x.makefile.tk/将能看到上一次保存的ip地址.

下面是index.php代码,通过文件来记录ip地址,不能够并发写入.同shell报告脚本一样可以使用内存文件来加快读写速度.

<html>
<body>
<?php
if ( !function_exists('sys_get_temp_dir')) {
  function sys_get_temp_dir() {
    if (!empty($_ENV['TMP'])) { return realpath($_ENV['TMP']); }
    if (!empty($_ENV['TMPDIR'])) { return realpath( $_ENV['TMPDIR']); }
    if (!empty($_ENV['TEMP'])) { return realpath( $_ENV['TEMP']); }
    $tempfile=tempnam(uniqid(rand(),TRUE),'');
    if (file_exists($tempfile)) {
    unlink($tempfile);
    return realpath(dirname($tempfile));
    }
  }
}
?>

<?php 
//echo "QUERY_STRING: " . $_SERVER['QUERY_STRING'];
//echo "<br>";
//$temp_file = tempnam(sys_get_temp_dir(), 'Tux');
//$temp_file = sys_get_temp_dir() . 'ip97845';
//$temp_file = '/dev/shm/ip97845';//seems to be frequently erased by cloud host.
$temp_file = 'ip97845';
//if has param of n,then save ip to file
if(isset($_GET['n'])){
    $n = $_GET['n'];
    if(isset($_GET['k'])){
        $key = $_GET['k'];//for simple security
        if($key == 'password'){
            $myfile = fopen($temp_file, "w") or die("Unable to open file!");
            for ($x=0; $x<$n; $x++) {
                $ip_idx = 'ip' . $x;
                $line = $ip_idx . "=$_GET[$ip_idx]<br>";
                echo $line;
                fwrite($myfile, $line);
            }
            fclose($myfile);
            echo 'save ip ok!<br>' ;
        }else echo 'key error';
    }else echo 'no key error';
    
}else{ // read from file
    $myfile = fopen($temp_file, "r") or die("Unable to open file!");
    echo fread($myfile,filesize($temp_file));
    fclose($myfile);
}


echo "<br><br>";
echo "Your INFO:<br>";
echo "IP: " . $_SERVER['REMOTE_ADDR'];
echo "<br>";
echo "UA: " . $_SERVER['HTTP_USER_AGENT'];
echo "<br>";

?>

</body>
</html>

对于这种web应用,使用网络上各种php建站即可.

公用DNS服务

准备:购买公网域名,域名设置DNS解析服务为Dnspod或CloudFlare.本文的代码使用CloudFlare的API动态修改DNS记录.

思路是修改IP报告脚本,将更新的IP更新的公共的DNS服务上.

ipv6-dns.sh 代码:

#!/bin/bash
# __author__ = fyk
# get global ipv6 & ipv4 address,
# note that both ipv6 & ipv4 addr may have more than 1.

# get variables in dns.conf which includes cloudflare info
# source dns.conf
if [ -z "$1" ] ;then
        echo 'please specify conf file'
        exit 0
else
        source $1
fi

# grep -v to exclude temporary ipv6 privacy addr.
ip6s=$(ip -6 addr |grep 'global'|grep -v 'tmpaddr'|awk '{print $2}'|sed 's/\/.*//')
#for my own needs,i only use ipv6
#ip4s=$(ip -4 a | grep global |awk '{print $2}')
for ip in $ip6s;do
        ip_data=$ip
        break # only use first one
done
#ip_data=${ip6s}' '${ip4s}
#echo $ip_data

API_URL="https://api.cloudflare.com/client/v4"
CURL="curl -s \
  -H Content-Type:application/json \
  -H X-Auth-Key:$AUTH_KEY \
  -H X-Auth-Email:$AUTH_EMAIL "

update_dns(){
UPDATE_DATA=$(cat << EOF
{ "type": "AAAA",
  "name": "$DOMAIN_NAME",
  "content": "$2",
  "proxied": false }
EOF
)
        #"ttl": 1, # let it be Automatic
        echo "update dns: $DOMAIN_NAME -> $2"
        $CURL -X PUT "$API_URL/zones/$ZONE_ID/dns_records/$1" -d "$UPDATE_DATA" > /tmp/cloudflare-ddns.json
}
# get current IP
get_dns_ip(){
        RECS=$($CURL "$API_URL/zones/$ZONE_ID/dns_records?name=$DOMAIN_NAME")
        IP=$(echo "$RECS" | sed -e 's/[{}]/\n/g' | sed -e 's/,/\n/g' | grep '"content":"' | cut -d'"' -f4)
        echo $IP
}

IP_FILE='/dev/shm/lastip9745' # or in /tmp .etc
ip_data_old=$(cat $IP_FILE 2> /dev/null) # for non-exist file,content is null
if [ "$ip_data" == "$ip_data_old" ];then
        echo 'IP unchanged.'
        exit 0
fi
echo 'IP changed,push to remote.'
if [ -z "$REC_ID" ] ; then
        RECS=$($CURL "$API_URL/zones/$ZONE_ID/dns_records?name=$DOMAIN_NAME")
        echo $RECS
        REC_ID=$(echo "$RECS" | sed -e 's/[{}]/\n/g' | sed -e 's/,/\n/g' | grep '"id":"' | cut -d'"' -f4)
        echo "REC_ID=$REC_ID"
fi
update_dns "$REC_ID" "$ip_data"
cur_ip=$(get_dns_ip)
if [ "$cur_ip"=="$ip_data" ];then
        echo $ip_data > $IP_FILE # update the file
else
        echo 'update dns failed.'
fi

脚本中通过source dns.conf读取了配置信息:

# this is Cloudflare api info for DDNS
# !!do not leave space around =
AUTH_EMAIL=<cloudflare-auth-email>
#This is your *Global API Key* under Cloudflare account settings
AUTH_KEY=<cloudflare-auth-key>
#Zone ID:can be find out there: <https://www.cloudflare.com/a/overview/>
ZONE_ID=<DNS Zone>
#your sub domain name
DOMAIN_NAME="ip.example.com"

具体API使用方法查阅https://api.cloudflare.com

自建DNS服务

准备:外网服务器B,搭建bind9服务用来提供DNS服务

借助于IP报告/收集脚本,在服务器B上不断更新域名解析.

客户端机器C,手动设置DNS服务地址为B的IP.这种方式的优点是域名想怎么写就怎么写.

示意图:

DDNS

通过DNS服务可以实现与著名的花生壳相类似的服务,而且成本低,“自主、可控”:) 。

关于域名解析ttl

TTL是英语Time-To-Live的简称,意思为一条域名解析记录在DNS服务器中的存留时间。当各地的DNS服务器接受到解析请求时,就会向域名指定的NS服务器发出解析请求从而获得解析记录;在获得这个记录之后,记录会在DNS服务器中保存一段时间,这段时间内如果再接到这个域名的解析请求,DNS服务器将不再向NS服务器发出请求,而是直接返回刚才获得的记录;而这个记录在DNS服务器上保留的时间,就是TTL值。

如果域名的IP经常变更,那么减小TTL的值,如果很少改变,调大成几个小时都行.
将TTL设为1,表示'Automatic',如Cloudflare的DNS会在约5分钟内push出去.

IP地址变更事件通知

得到网络变化的方式有多种:

  1. C语言使用linux下的rtnetlink的NETLINK_ROUTE socket,监听之后会收到消息.要求写的程序一直在运行.参考.
  2. 如果网络管理器使用的Gnome的NetworkManager,那么会通过D-Bus广播事件(浏览器等常通过这种方式切换在线/离线模式).
  3. Debian系统中网络接口up或down时会执行/etc/network下的相关脚本.
  4. ifplugd 当网线被拔掉或接入时会执行相应脚本.

如果网络是使用NetworkManager(Ubuntu等系统默认的网络管理器)进行DHCP获取动态IP,比较方便的方式是将我们的脚本添加到事件响应脚本中. 参考man手册,在/etc/NetworkManager/dispatcher.d中添加脚本.

#!/bin/bash
# put this script in /etc/NetworkManager/dispatcher.d

IF=$1
STATUS=$2

case "$STATUS" in
        down)
        #logger -s "NM Script down $IF triggered"
        ;;
        dhcp6-change|up) # 
                #if [ $IP6_NUM_ADDRESSES > 0 ];then
                #       echo $IP6_ADDRESS_0 //0,1,2,...
                #fi
				# msg logged to /va/log/syslog
                logger "IP6_ADDRESS_0 = $IP6_ADDRESS_0"
                /path/to/ipv6-dns.sh /path/to/dns.conf 2>&1  > /dev/null
        *)
        ;;
esac

cron定时执行

执行crontab -e将会编辑用户的crontab文件,其创建/tmp下的临时文件进行编辑,保存后将会提交到系统目录下(/var/spool/cron),这种设计方式类似visudo,目的是先检查用户的输入,防止错误的输入带来的破坏.系统重启后/tmp下的文件会删除,而crontab不会丢失.
也可以使用自定义的crontab文件导入到系统任务中:`crontab /path/to/cronfile
文件内容如下,注意使用绝对路径:

0 */1 * * * /home/s05/fyk/ip/ipv6-dns.sh /home/s05/fyk/ip/dns.conf

cron job的执行命令情况可以在/var/log/syslog中看到.
使用logger 'msg'可以将msg记录到syslog文件中.
crontab无需重启会立即生效.

邮件通知

发送邮件.适合于ip更新不太频繁的情形,通过代码发送邮件的代码很简便,Python,Java等语言均有方便的实现.

More

本文代码地址: https://github.com/makefile/CharmScript/tree/master/DDNS

其它资源:

posted @ 2017-10-13 22:56  康行天下  阅读(4727)  评论(1编辑  收藏  举报