動態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

其它資源:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM