天天看點

Linux中擷取本機的最新IPv6位址_更新ddns的腳本_擷取openwrt的IP位址

Linux中擷取本機的最新IPv6位址_更新ddns的腳本_擷取openwrt的IP位址

轉載注明來源: 本文連結 來自osnosn的部落格,寫于 2019-11-07.

營運商提供ipv6位址。

路由器後有台linux機器,通過eui64方式自動配置ipv6位址。

并配置防火牆,允許轉發指定的ipv6連接配接。見【設定openwrt路由器的防火牆_允許從外網通路_ipv6服務】

不是eui64,需要改為eui64,見【Linux_ipv6_無狀态_設定為_eui64_有狀态ipv6更改字尾】

但是,營運商會定時強制路由器重撥,導緻ipv6的字首(prefix)變化。

雖然linux會馬上自動配置新的ipv6位址。但舊的ipv6位址不會馬上消失。

舊ipv6需要等逾時expired後才删除,有時要等2000多秒(30多分鐘)。

這段時間主機會有兩個ipv6位址。如果不能正确找出新的ipv6位址去更新ddns,則這段時間無法通路主機。

通過檢視

ip addr show

發現每個ip後面一行給出了expired時間。

新ip的expired時間總是比舊ip大。這樣就可以找出最新的ipv6位址了。

如果你的機器中有很多網口,可以指定網口檢視ip,如

ip addr show eth0

,

ip addr show br0

另:【202108_支援ipv6的_DDNS】

以下是shell腳本,會顯示出ipv4位址,和最新的ipv6(eui64)位址。

#!/bin/sh
ip addr show|grep -A1 \'inet [^f:]\'|sed -nr \'s#^ +inet ([0-9.]+)/[0-9]+ brd [0-9./]+ scope global .*#\1#p\'
ip addr show|grep -v deprecated|grep -A1 \'inet6 [^f:]\'|grep -v ^--|sed -nr \':a;N;s#^ +inet6 ([a-f0-9:]+)/.+? scope global .*? valid_lft ([0-9]+sec) .*#\2 \1#p;Ta\'|grep \'ff:fe\'|sort -nr|head -n1|cut -d\' \' -f2
           

顯示ipv6的腳本執行步驟是,

  • ip addr show 或 ip addr show XXX
  • 去除 deprecated 位址
  • 挑出inet6位址,并同時顯示下一行
  • 去掉分組間隔 "--" (如果有的話)
  • 把expired時間和ipv6位址,通過正則找出來,并顯示在同一行。逾時時間在前,ip在後。
  • 過濾出eui64位址
  • 根據時間的長短,反向排序
  • 輸出第一行
  • 輸出第二列(ip)

如果要把這個位址儲存到變量,用來更新ddns,就這樣寫。

#!/bin/sh
# ipv4=\'auto\'
# ipv4=$(wget -4 -qO- https://ident.me)    #擷取自己的外網ipv4位址
ipv4=$(ip addr show|grep -A1 \'inet [^f:]\'|sed -nr \'s#^ +inet ([0-9.]+)/[0-9]+ brd [0-9./]+ scope global .*#\1#p\')
ipv6=$(ip addr show|grep -v deprecated|grep -A1 \'inet6 [^f:]\'|grep -v ^--|sed -nr \':a;N;s#^ +inet6 ([a-f0-9:]+)/.+? scope global .*? valid_lft ([0-9]+sec) .*#\2 \1#p;Ta\'|grep \'ff:fe\'|sort -nr|head -n1|cut -d\' \' -f2)
#echo "my ipv6 is:" $ipv6
regex=\'^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$\'
if [ "$ipv4" = \'auto\' ];then
   isipv4=1
else
   isipv4=$(echo $ipv4 | egrep $regex | wc -l)
fi
if [  $isipv4 -eq 0 -o ${#ipv6} -lt 8 -o ${#ipv4} -gt 50 ];then
   # 防止沒有抓到IP位址,或抓取錯誤
   echo \'ERROR\'
   exit
fi 

selfpath=$(/usr/bin/dirname $(/bin/readlink -f -- $0))  #腳本所在的目錄
countfile=$selfpath/ipv6_count.log   #記錄檔案
if [ -f $countfile ]; then
   read old_ipv4 old_ipv6 chg_count < $countfile
fi
old_ipv4=${old_ipv4:-\'noipv4\'}
old_ipv6=${old_ipv6:-\'noipv6\'}
chg_count=${chg_count:-\'0\'}

if [ $ipv4 != $old_ipv4 -o  $ipv6 != $old_ipv6 ]; then  #IP是否變化了
   old_ipv4=$ipv4
   old_ipv6=$ipv6
   chg_count=\'1\'
else
   chg_count=$(($chg_count+1))
fi

if [ $chg_count -lt 10 ];then   #變化後,隻嘗試更新10次
   # 如果路由器有公網ipv4,可以讓ddns根據來源ip自動檢測
   update_res=$(wget --no-check-certificate -q -O - \'https://ipv4.dynv6.com/api/update?hostname=myhostname.dynv6.net&token=mytoken_mytoken&ipv4=auto&ipv6=\'$ipv6)
   if [ -n "$(echo $update_res | grep \'nochg\|updated\')" -a $chg_count -gt 2 ]; then
      chg_count=\'10\'  #更新成功,就不再更新了
   fi
   echo $(/bin/date \'+%F_%T%z %w %a\')  $update_res   >> ${countfile}.stat
elif [ $chg_count -gt 160 ]; then   #10分鐘一次,一天24*6=144
   chg_count=\'2\'  #約24小時後,再重新更新一次
fi

echo $old_ipv4  $old_ipv6  $chg_count   > $countfile  #儲存記錄
           
  • 如果覺得系統中跑的定時任務太多,不想擠在一起,同時執行。

    可以參考【Openwrt_Linux_crontab任務_順序執行腳本】

python3 的腳本例子。

#!/usr/bin/python3
# -*- coding: utf-8 -*- #

import urllib.request
import ssl
import subprocess
import re

def getIP():
   #interface=\'eth0\'
   interface=\'\'
   output=subprocess.getoutput(\'/sbin/ip addr show \'+interface+\'|grep -v deprecated\')
   ipv4=re.findall(r\' inet ([\d.]+)/(\d+) brd [\d./]+ scope global \',output,re.M|re.I)
   ipv6=re.findall(r\' inet6 ([^f:][\da-f:]+)/(\d+) scope global .+?\n.+? valid_lft (\d+)sec \',output,re.M|re.I)

   # eui64的ipv6位址按逾時時間排序,其他的排前面
   def my_key(a):
      if a[0].find(\'ff:fe\')>4:
         return int(a[2])
      else:
         return -1
   ipv6.sort(key=my_key,reverse=True) #反向排序
   #print(\'ipv4=\',ipv4)
   #print(\'ipv6=\',ipv6)

   return (ipv4,ipv6)

def updateddns(ipv4,ipv6):
   context = ssl._create_unverified_context()
   opener = urllib.request.build_opener(urllib.request.HTTPSHandler(context=context)) #不驗證證書
   header={
       \'Accept\':\'*/*\',
       \'User-Agent\':\'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\',
       \'Accept-Encoding\':\'none\',
   }
   #host=\'https://dynv6.com/api/update?hostname=myhostname.dynv6.net&token=mytoken_mytoken&ipv4=\'+ipv4+\'&ipv6=\'+ipv6
   #如果路由器有公網ipv4,可以讓ddns根據來源ip自動檢測
   host=\'https://ipv4.dynv6.com/api/update?hostname=myhostname.dynv6.net&token=mytoken_mytoken&ipv4=auto&ipv6=\'+ipv6
   req=urllib.request.Request(host, b\'\',header)
   content=opener.open(req, timeout=20)
   print(content.read().decode(\'utf8\'))
   return True

if __name__==\'__main__\':
    filename = \'ipv6_addr.txt\'
    if  not os.path.isfile(filename):
        old_addr = [\'no ipv4\',\'no ipv6\',\'0\']
    else:
        with open(filename,\'r\',encoding=\'utf8\') as fp:  #讀取上次的記錄
            old_addr = fp.readlines()
    for kk in range(len(old_addr)):
        old_addr[kk] = old_addr[kk].strip()

    ipv4,ipv6=getIP()

    if old_addr[0] != ipv4[0][0] or old_addr[1] != ipv6[0][0]: #IP是否變化
        old_addr[0] = ipv4[0][0]
        old_addr[1] = ipv6[0][0]
        old_addr[2] = \'1\'
    else:
        old_addr[2] = str(1+int(old_addr[2]))
    with open(\'ipv6_addr.txt\',\'w\') as fp:  #儲存IP,計數
        fp.write(\'\n\'.join(old_addr)+\'\n\')

    if int(old_addr[2]) < 10:  #ip變化之後,隻更新10次
        updateddns(ipv4[0][0],ipv6[0][0])
           

這個是OpenWRT路由器中的lua腳本。

如果你的路由器是OpenWRT,可以用這個腳本直接跑在路由器中。

  • 【方法1】這個腳本擷取的是路由器本身WAN口的ipv4和ipv6(非eui64).
#!/usr/bin/lua

require("luci.sys")
local a=luci.sys.exec(\'/sbin/ip addr show pppoe-wan\')
local ipv4=string.match(a," inet ([%d%.]+) peer ")
local ipv6=string.match(a," inet6 ([%a%d:]+)/[0-9]+ scope global ")
-- lua的注釋為兩個減号
--print(ipv4,ipv6)

cc=luci.sys.exec("/bin/wget --no-check-certificate -q -O - \'https://dynv6.com/api/update?hostname=myhostname.dynv6.net&token=mytoken_mytoken&ipv4="..ipv4.."&ipv6="..ipv6.."\'")
           
  • 【方法2】openWRT 中還有個ifstatus指令,如

    ifstatus lan

    ,

    ifstatus wan

    ,

    ifstatus wan_6

    會傳回對應網口的json資料。
    • "ipv4-address":[{"address":"...","ptpaddress":"...","mask":32}]

      "route":[{"nexthop":"..."}]

      ,

      "dns-server":["...","..."]

    • "ipv6-address":[{"address":"...","mask":64}]

      ,

      "ipv6-prefix":[{"address":"...","mask":56}]

      "route":[{"target":"::","nexthop":"..."},{..},...]

      ,

      "dns-server":["...","..."]

      如果wan口沒有擷取到ipv6位址,"ipv6-address" 這個key不存在。
#!/usr/bin/lua
-- example,例子
require("luci.sys")
require("luci.jsonc")

local aa=luci.sys.exec("/sbin/ifstatus wan")
local bb=luci.jsonc.parse(aa)
print(bb["ipv4-address"][1]["address"])  -- 100.64.xx.xx  WAN口的ipv4

aa=luci.sys.exec("/sbin/ifstatus wan_6")
bb=luci.jsonc.parse(aa)
print(bb["ipv6-address"][1]["address"])  -- 240e:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx  WAN口的ipv6
print(bb["ipv6-prefix"][1]["address"].."/"..bb["ipv6-prefix"][1]["mask"])  -- 240e:xxx:xxx:xxxx::/56  ipv6字首
-- 有了ipv6的prefix, 直接拼接客戶機的eui64字尾, 就可以去更新ddns了。比如字尾為 1234:1234:1234:1234
print(string.sub(bb["ipv6-prefix"][1]["address"],1,-2).."1234:1234:1234:1234")  -- 240e:xxx:xxx:xxxx:1234:1234:1234:1234
           

用openWRT中的ddns應用,為内網機器更新ipv6域名

安裝ddns應用,

opkg install luci-i18n-ddns-en

Linux中擷取本機的最新IPv6位址_更新ddns的腳本_擷取openwrt的IP位址
  • 腳本内容 getMY_ipv6.lua 如下,記得要

    chmod +x getMY_ipv6.lua

    設定腳本執行權限。
#!/usr/bin/lua
require("luci.sys")
require("luci.jsonc")

aa=luci.sys.exec("/sbin/ifstatus wan_6")
bb=luci.jsonc.parse(aa)
-- 這個5205:a2ff:fe34:8311就是内網機器的ipv6字尾,自己按需修改
print(string.sub(bb["ipv6-prefix"][1]["address"],1,-2).."5205:a2ff:fe34:8311")
           

---end---

轉載注明來源: 本文連結 來自osnosn的部落格

Linux中擷取本機的最新IPv6位址_更新ddns的腳本_擷取openwrt的IP位址