はじめに
YAMAHAルーターでネットボランチDNSサービス以外のDDNSを使用したく思い、Luaスクリプトでどうにかできないか調べたところ NVR-500でDynDNSの登録更新をしてみた という記事を見つけたのでインスパイアされてみました。
以下の記録はすべて NVR700W でのものとなります。
元ネタが NVR500 向けなのでネットボランチ系のルーターであればおそらく同様に動きますが、HTTPクライアント機能に対応したバージョンのファームウエア(_RT_LUA_VERSION_NUM >= 102)を搭載している必要があります。
動作
ルーターの起動時にLuaスクリプトをスケジュール実行して常駐します。
syslogwatch 経由でPPPoEのlocal側 IPアドレスを監視し、変動があれば取得したIPアドレスでDDNSを更新します(IPアドレスが同じであれば更新しません)。DDNSサービスによっては長期間更新がないとホストの登録を抹消されることがあるので
WatchInterval * UpdateInterval(秒)
で設定された期間、IPアドレスが変更されない場合は強制的に更新を行います。
処理結果を syslog に出力します。
使用方法
雛形を ddns-update.lua
等のファイル名でルーターにアップロードしてください。
/lua のようにルート直下にディレクトリを作成しておくとアクセスしやすいです。
ルーターにコンソールでログインして動作確認を行ってください。
Luaスクリプトが問題なく実行され、DDNSが更新されるのを確認できたらLuaスクリプトをスケジュール登録します。
schedule at XXX startup * lua /lua/ddns-update.lua
XXX
の部分にはスケジュールIDを適宜指定してください。
(既にスケジュールの登録がある場合は上書きに注意してください)
ルーターを再起動後にLuaスクリプトが動いていることを確認してください。
show status lua
で、動作状況と履歴を確認できます。
スケジュール登録したLuaスクリプトが running 状態のままであれば成功です。
テンプレート
雛形はNO-IP用に記述されていますが、 UpHost を書き換えることでそのままDynDNSにも使用できます。他のDDNSサービス向けに使用する場合は、各サービスに応じた UpType / UpHost / UpUrl パラメータを記述してください。
--[[
DDNSのIPアップデート
*** YAMAHAルーター専用 ***
PPPoEで設定されたlocal側のIPアドレスを取得し、既存IPアドレスと異なれば取得したIPアドレスでDDNSを更新する。
IPアドレスが同じであれば更新はしない。WatchInterval * UpdateInterval(秒)で設定された期間更新されない場合、
強制的にIPアドレスを更新する。
※下記サンプルはNO-IP用に記述されているが、UpHostを書き換えることでそのままDynDNSにも使用可能。
他のDDNSサービスに使用する場合は、サービスに応じたUpType/UpHost/UpUrlパラメータを記述すること。
]]
----------------------## 設定 ここから ##----------------
-- debug(tru | false で指定)
debug = false
-- DDNS service user account
username = "Enter your username"
password = "Enter your password"
ddnshost = "Enter your ddns hostname"
-- IP取得するPP番号
pp_num = 1
-- 実行指定時間(hh:mm)
schedule = "01:00"
-- 実行間隔秒数と強制アップデート実施カウンターのしきい値
--
-- IP取得リトライ回数と間隔(秒数)
interval_retrypp = 3
interval_timespp = 10
-- syslogwatch time 86400/day, max 864000(10days)
-- interval_watch * interval_update
interval_watch = 86400
interval_update = 7
-- syslogwatch タイマー誤差の補正値(NVR500の実績でNVR700Wでは不要の模様)
-- for NVR500 (-90秒)
--correction_time = -90
-- for NVR700W (補正不要)
correction_time = 0
-- DDNS update api address
--
-- UpUrl の末尾にIPが自動付加されるのでパラメータ構成に注意
-- ポート指定が必要な場合は UpHost パラメータを "FQDN:8080" 形式で記述のこと。
UpType = (_RT_LUA_VERSION_NUM >= 108) and "https" or "http"
UpHost = "dynupdate.no-ip.com"
UpUrl = UpType .. "://" .. UpHost .. "/nic/update?hostname=" .. ddnshost .. "&myip="
----------------------## 設定 ここまで ##----------------
------------------------------------------------------------
-- syslog出力関数 --
------------------------------------------------------------
function putSyslog(msg)
rt.syslog("info", "[LUA] ddns-update.lua DDNS " .. msg)
end
------------------------------------------------------------
-- 指定した時間までの秒数を返す関数 --
------------------------------------------------------------
function getDiffTime(t_dest)
local t_adjust, t_diff
local tmp = {string.split(t_dest, /:/)}
local now = os.time()
local time = os.date("*t", now)
time.hour = tmp[1]
time.min = tmp[2]
time.sec = 0
tmp = os.time(time)
if os.difftime(tmp, now) <= 60 then
time = os.date("*t", tmp + interval_watch)
end
t_diff = os.difftime(os.time(time), now)
t_adjust = (t_diff * correction_time) / interval_watch
return (t_diff + t_adjust)
end
------------------------------------------------------------
-- 指定されたPPの local IP 取得関数 --
------------------------------------------------------------
function getLocalIp(id)
local rtn, str, ipadr
local cmd = "show status pp " .. tostring(id)
local ptn = "PP IP Address Local:%s+(%d+%.%d+%.%d+%.%d+)"
rtn, str = rt.command(cmd)
if (rtn) and (str) then
ipadr = str:match(ptn)
if (ipadr == nil) then
rtn = false
ipadr = "PPP not linked up"
end
else
rtn = false
ipadr = cmd .. "Can not obtain IPaddr (command exec failure: " .. cmd .. ")\r\n"
end
return rtn, ipadr
end
------------------------------------------------------------
-- IPアドレスの変化検出関数 --
------------------------------------------------------------
function isNew(ip)
local blip
blip = os.getenv("GLOBALIP")
if (blip) then
if (blip == ip) then
return false
else
rt.command("set GLOBALIP=" .. ip)
return true
end
else
rt.command("set GLOBALIP=" .. ip)
return true
end
end
------------------------------------------------------------
-- DDNS Update 関数 --
------------------------------------------------------------
function UpdateDDNS(ip)
local url = UpUrl .. ip
local resp_table
local req_table = {
url = url,
method = "GET",
auth_type = "basic",
auth_name = username,
auth_pass = password
}
putSyslog("update url = " .. url)
resp_table = rt.httprequest(req_table)
if (resp_table.rtn1) then
putSyslog("new IP = " .. ip)
else
putSyslog("update failed - " .. "\nrtn1-> " .. tostring(resp_table.rtn1) .. "\nrtn2-> " .. tostring(resp_table.rtn2) .. "\nerr-> " .. tostring(resp_table.err) .. "\ncode-> " .. tostring(resp_table.code) .. "\nheader-> " .. tostring(resp_table.header) .. "\nbody-> " .. tostring(resp_table.body))
end
end
------------------------------------------------------------
-- main --
------------------------------------------------------------
local rtn, str, lip, cnt, sec
cnt = 0
putSyslog("updater starting (lua = " .. _RT_LUA_VERSION .. " , using " .. UpType .. ") ...")
rtn, str = getLocalIp(pp_num)
-- IP取得できない場合(リンクアップ待ち、等)は指定の回数・間隔でリトライ
if (not rtn) then
cnt = interval_retrypp
repeat
putSyslog("try to specify target ... retry " .. math.abs(cnt - interval_retrypp - 1))
rt.sleep(interval_timespp)
rtn, str = getLocalIp(pp_num)
cnt = cnt - 1
until (rtn or cnt < 1)
end
if (rtn) then
putSyslog("target is (if = " .. string.format("PP[%02d]:", pp_num) .. str .. ")")
lip = str
if (isNew(lip)) then
if (not debug) then UpdateDDNS(lip) else print("-- UpdateDDNS(lip)") end
end
else
putSyslog("target specify failure. aborted: " .. str)
os.exit(1)
end
cnt = 0
sec = getDiffTime(schedule)
putSyslog("next ip-check is after " .. sec .. " seconds")
while (true) do
local rtn, str, lip, gip
rtn, str = rt.syslogwatch(string.format("PP%%[%02d%%]", pp_num) .. " PPP/IPCP up%s+%(Local:%s+(%d+%.%d+%.%d+%.%d+)", 1, sec)
if (rtn == 0) then
gip = os.getenv("GLOBALIP") or ""
cnt = cnt + 1
if (cnt == interval_update) then
cnt = 0
putSyslog("force update, new ip = " .. gip)
if (not debug) then
UpdateDDNS(gip)
else
print("Skip counter = " .. tostring(interval_update) .. ", DDNS update ip = " .. gip)
end
end
else
lip = string.match(str[rtn], "Local:%s+(%d+%.%d+%.%d+%.%d+)")
putSyslog("detected new ip and update (old/new ip): " .. gip .. "/" .. lip)
if (isNew(lip)) then
if (not debug) then
UpdateDDNS(lip)
else
print("DDNS update called in while loop\n")
end
cnt = 0
end
end
sec = getDiffTime(schedule)
putSyslog("next ip-check is after " .. sec .. " seconds")
end