Edited at

YAMAHAルーター DDNS更新用luaスクリプトを作ってみた

More than 1 year has passed since last update.


はじめに

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


参考情報

Lua スクリプト機能 - RTpro

スケジュールの設定 - RTpro