本文介紹兩種方便獲取主機動態IP的方式(DDNS,IP報告網頁),並給出相應的代碼實現.
shell腳本獲取本機IP,執行上傳操作和更新DNS操作.定期執行通過crontab或者systemd等服務.
應用場景
遠程訪問具有動態IP的公網或內網主機時,如果通過ip進行訪問,由於公網IP總是在變化,我們不得不每次去查看新的ip地址,往往這個重復的過程比較麻煩.
遠程主機聯網的方式有所不同,主要有以下幾種情形:
- 遠程主機是通過PPPoE撥號上網,通常獲取到動態的私有網絡(內網)地址
- 遠程主機直接獲取到的是動態公網ipv4地址.
- 在教育網中通常還能獲得動態的公網ipv6地址.
又可以簡易地分成兩類: 配有公網ip的主機與僅配置內網ip的主機.
內網主機訪問方式
-
反向隧道
對於躲在NAT之后的內網主機,比較方便的方式是在內網主機建立到公網主機的反向隧道
,命令行建立反向隧道工具有:ssh,ngrok,tmate等,參考我之前的反向隧道的文章. 這些工具往往都能在ip發生改變后自動重建連接.
缺點是我們需要一台擁有公網ip的主機,並且時刻保持隧道長連接,另外由於遠程訪問內網主機需要經過這個公網主機中轉,速度變慢. -
內網穿透
通過公網服務器得到內網主機在NAT設備的轉碼地址,然后可以建立p2p的連接.QQ,TeamViewer即是類似原理.前提是內網容易穿透.
-
路由器端口映射
外網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.這種方式的優點是域名想怎么寫就怎么寫.
示意圖:
通過DNS服務可以實現與著名的花生殼
相類似的服務,而且成本低,“自主、可控”:) 。
關於域名解析ttl
TTL是英語Time-To-Live的簡稱,意思為一條域名解析記錄在DNS服務器中的存留時間。當各地的DNS服務器接受到解析請求時,就會向域名指定的NS服務器發出解析請求從而獲得解析記錄;在獲得這個記錄之后,記錄會在DNS服務器中保存一段時間,這段時間內如果再接到這個域名的解析請求,DNS服務器將不再向NS服務器發出請求,而是直接返回剛才獲得的記錄;而這個記錄在DNS服務器上保留的時間,就是TTL值。
如果域名的IP經常變更,那么減小TTL的值,如果很少改變,調大成幾個小時都行.
將TTL設為1,表示'Automatic',如Cloudflare的DNS會在約5分鍾內push出去.
IP地址變更事件通知
得到網絡變化的方式有多種:
- C語言使用linux下的rtnetlink的NETLINK_ROUTE socket,監聽之后會收到消息.要求寫的程序一直在運行.參考.
- 如果網絡管理器使用的Gnome的NetworkManager,那么會通過D-Bus廣播事件(瀏覽器等常通過這種方式切換在線/離線模式).
- Debian系統中網絡接口up或down時會執行/etc/network下的相關腳本.
- 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
其它資源: