osnosn

  博客园 :: 首页 :: 博问 :: 闪存 :: :: 联系 :: 订阅 订阅 :: 管理 ::

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 -o addr show|grep 'inet [^f:]'|sed -nr 's#.+? +inet ([0-9.]+)/[0-9]+ brd [0-9./]+ scope global .*#\1#p'
/bin/ip -o addr show |/bin/grep -v deprecated|/bin/grep ' inet6 [^f:]'|/bin/sed -nr 's#^.+? +inet6 ([a-f0-9:]+)/.+? scope global .*? valid_lft ([0-9]+sec) .*#\2 \1#p'|/bin/grep 'ff:fe'|/usr/bin/sort -nr|/usr/bin/head -n1|/usr/bin/cut -d' ' -f2

显示ipv6的脚本执行步骤是,

  • ip -o addr show 或 ip -o addr show XXX
  • 去除 deprecated 地址
  • 挑出inet6地址
  • 把expired时间和ipv6地址,通过正则找出来。超时时间在前,ip在后。
  • 过滤出eui64地址
  • 根据时间的长短,反向排序
  • 输出第一行
  • 输出第二列(ip)

写法二:

#!/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,就这样写。

更新成功就不再更新。除非ip变化,或者超过24小时,才再次更新。

#!/bin/sh
hostname='myhostname.dynv6.net'
token='mytoken_mytoken'
service_name='ddns_myhostname'
##------------------
# 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')
#ipv4=$(echo -n $ipv4|sed -e 's/^\s*//' -e 's/\s*$//g')   #trim()
ipv6=$(/bin/ip -o addr show |/bin/grep -v deprecated|/bin/grep ' inet6 [^f:]'|/bin/sed -nr 's#^.+? +inet6 ([a-f0-9:]+)/.+? scope global .*? valid_lft ([0-9]+sec) .*#\2 \1#p'|/bin/grep 'ff:fe'|/usr/bin/sort -nr|/usr/bin/head -n1|/usr/bin/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 

updateurl='dynv6.com/api/update'
selfpath=$(/usr/bin/dirname $(/bin/readlink -f -- $0))  #脚本所在的目录
countfile=$selfpath/ipv6_count_${hostname}.stat   #记录文件
if [ -f $countfile ]; then
   read old_ipv4 old_ipv6 chg4_count chg6_count < $countfile
fi
old_ipv4=${old_ipv4:-'noipv4'}
old_ipv6=${old_ipv6:-'noipv6'}
chg4_count=${chg4_count:-'1'}
chg6_count=${chg6_count:-'1'}

if [ $ipv4 != $old_ipv4 ]; then  #ipv4是否变化了
   old_ipv4=$ipv4
   chg4_count='1'
else
   chg4_count=$(($chg4_count+1))
fi
if [ $ipv6 != $old_ipv6 ]; then  #ipv6是否变化了
   old_ipv6=$ipv6
   chg6_count='1'
else
   chg6_count=$(($chg6_count+1))
fi

if [ $chg4_count -lt 9 ];then   #变化后,只尝试更新9次
   # 如果路由器有公网ipv4,可以让ddns根据来源ip自动检测
   update4_res=$(wget -4 --no-check-certificate -q -O - 'https://${updateurl}?hostname=${hostname}&token=${token}&ipv4=auto')
   exitcode=$?
   if [ -n "$(echo $update4_res | grep 'nochg\|unchange\|updated\|good')" -a $chg4_count -gt 2 ]; then
      chg4_count='10'  #更新成功,就不再更新了
   fi
   echo $(/bin/date '+%F_%T%z_%w_%a') v4 ${servce_name} "$update4_res" "$exitcode"   >> ${countfile}.log
elif [ $chg4_count -gt 160 ]; then   #10分钟一次,一天24*6=144
   chg4_count='2'  #约24小时后,再重新更新一次
fi

if [ $chg6_count -lt 9 ];then   #变化后,只尝试更新9次
   update6_res=$(wget --no-check-certificate -q -O - 'https://${updateurl}?hostname=${hostname}&token=${token}&ipv6='$ipv6)
   exitcode=$?
   if [ -n "$(echo $update6_res | grep 'nochg\|unchange\|updated\|good')" -a $chg6_count -gt 2 ]; then
      chg6_count='10'  #更新成功,就不再更新了
   fi
   echo $(/bin/date '+%F_%T%z_%w_%a') v6 ${servce_name} "$update6_res" "$exitcode"   >> ${countfile}.log
elif [ $chg6_count -gt 160 ]; then   #10分钟一次,一天24*6=144
   chg6_count='2'  #约24小时后,再重新更新一次
fi

echo $old_ipv4  $old_ipv6  $chg4_count  $chg6_count   > $countfile  #保存记录

python3 的脚本例子。

ip变化之后,更新10次,无论是否成功。10次后,就不再更新,除非IP变化。

#!/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).
    • 有些什么接口。用命令 ip address 查看。
#!/bin/sh
ip addr show pppoe-wan|grep -A1 'inet [^f:]'|sed -nr 's#^ +inet ([0-9.]+)/[0-9]+ brd [0-9./]+ scope global .*#\1#p'
ip addr show pppoe-wan|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
## 或者 ##
ip -o addr show pppoe-wan|grep 'inet [^f:]'|sed -nr 's#.+? +inet ([0-9.]+)/[0-9]+ brd [0-9./]+ scope global .*#\1#p'
ip -o addr show pppoe-wan|grep -v deprecated|grep ' inet6 [^f:]'|sed -nr 's#^.+? +inet6 ([a-f0-9:]+)/.+? scope global .*? valid_lft ([0-9]+sec) .*#\2 \1#p'|grep 'ff:fe'|sort -nr|head -n1|cut -d' ' -f2

方法2

  • 这个脚本获取的是路由器本身WAN口的ipv4和ipv6(非eui64).
    • 有些什么接口。用命令 ip address 查看。
#!/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.."'")

方法3

  • openWRT 中还有个ifstatus命令,如ifstatus lan, ifstatus wan, ifstatus wan_6 会返回对应网口的json数据。
    • 有些什么接口。用命令 ubus list network.interface.* 查看。
    • "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

  • 根据 ubus list network.interface.* 看看有些什么接口。比如 wan_6, vwan1_6 之类的。用于下面的脚本。
  • 脚本内容 getMY_ipv6.lua 如下,记得要chmod +x getMY_ipv6.lua设置脚本执行权限
#!/usr/bin/lua
require("luci.sys")
require("luci.jsonc")
-- 看看有些什么接口名称
-- print(luci.sys.exec("ubus list network.interface.*"))
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")
-- 这个是路由器自身的ipv6地址。
-- print(bb["ipv6-address"][1]["address"])

---end---

转载注明来源: 本文链接 https://www.cnblogs.com/osnosn/p/11813096.html 来自osnosn的博客

posted on 2019-11-07 17:21  osnosn  阅读(9587)  评论(2编辑  收藏  举报