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
以下是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
- 脚本内容 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---