前置き
ヤマハルーターはブラウザからコマンドを実行するメニューもありますが、ping や traceroute は実行できません。禁止と表示されてしまいます。
カスタムGUI からも実行できないコマンドとして ping, ping6, traceroute, traceroute6 が入っています1。
カスタムGUI を利用するのが前提ですが、やや強引に pingなど を実行して結果を取得するコードを書いてみました。
仕組み
カスタムGUIから ping は実行できませんが、luaコマンド経由の ping は禁止されていないので、luaスクリプトでrt.command("ping ~")
を実行します。
ただし、そのままでは戻り値が取得できず結果は不明になります2。
そのためLUAの実行結果は rt.syslogコマンドで残した後に、カスタムGUIからsyslogを取り込むことにしました。
実装
ルーターコマンド
カスタムGUIの設定
login user sample *
httpd custom-gui use on
httpd custom-gui user sample directory=/gui/sample index=index.html
HTML
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>GUI Sample</title>
<script type="text/javascript" src="/custom/custom_gui_lib.js"></script>
<script type="text/javascript">
<!--
function createHttpRequest() {
if (window.ActiveXObject) {
try {
return new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
return new ActiveXObject("Microsoft.XMLHTTP");
} catch (e2) {
return null;
}
}
}
else if (window.XMLHttpRequest)
return new XMLHttpRequest();
else
return null;
}
function sendRequest(data, async) {
return new Promise((resolve, reject) => {
let httpoj = createHttpRequest();
httpoj.open("POST", "/custom/execute", async);
httpoj.onreadystatechange = function() {
if (httpoj.readyState == 4) {
if (httpoj.status == 200) {
resolve(httpoj);
} else {
reject(new Error("/custom/execute 実行失敗"));
}
}
};
httpoj.send(data);
});
}
function inputCheck() {
const command = document.getElementById("command").value.trim();
if ( command === "") {
alert("コマンドを選択してください。");
document.getElementById("command").focus();
return false;
}
execUserCommand();
}
function startLoadingAnimation() {
let dots = '';
const resultElement = document.getElementById('result');
let counter = 0;
const intervalId = setInterval(() => {
dots += '.';
counter++;
resultElement.innerHTML = `コマンド実行中 ${dots}`;
if (counter > 5) {
dots = '';
counter = 0;
}
}, 500);
return intervalId;
}
async function execUserCommand() {
const loadingIntervalId = startLoadingAnimation();
try {
const command = document.getElementById("command").value.trim();
const terget = document.getElementById("terget").value.trim();
document.getElementById("execButton").disabled = true;
document.getElementById("result").innerHTML = "コマンド実行中 .";
const sessionId = getSessionId();
const execId = sessionId + Math.random().toString(36).substring(2, 7);
let order = "#" + sessionId + "\r\n";
order += "lua /lua/command.int " + execId + " " + command + " " + terget + "\r\n";
await sendRequest(order, true);
order = "#" + sessionId + "\r\n";
order += "show log\r\n";
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const syslog = await sleep(['traceroute', 'traceroute6'].includes(command) ? 30000 : 3000)
.then(() => sendRequest(order, true));
const syslogArray = syslog.responseText.split('\r\n');
const keyword = "[" + execId + "]";
let sessionLog = ""
for (let i = 0; i < syslogArray.length; i++) {
const line = syslogArray[i];
if (!line.includes(keyword)) continue;
sessionLog += line.replace(keyword, "")
.replace(/Error: \/lua\/command\.lua:\d+: /, "")
+ "<br>\r\n";
}
if (sessionLog.length === 0) {
sessionLog = "待機時間内に応答が返りませんでした";
}
clearInterval(loadingIntervalId);
document.getElementById("execButton").disabled = false;
document.getElementById("result").innerHTML = sessionLog;
} catch (error) {
clearInterval(loadingIntervalId);
document.getElementById("execButton").disabled = false;
console.error(error);
}
}
//-->
</script>
</head>
<body>
<form name="formCommand" action="#">
<label for="command">コマンド: </label>
<select id="command">
<option value="" selected>選択してください</option>
<option value="ping">ping</option>
<option value="ping6">ping6</option>
<option value="traceroute">traceroute</option>
<option value="traceroute6">traceroute6</option>
<option value="nslookup">nslookup</option>
</select>
<label for="terget">対象: </label>
<input id="terget" name="terget" type="text">
<input id="execButton" type="button" value="実行" onclick="inputCheck();"/>
<div id="result"></div>
</form>
</body>
</html>
次の行のところで、pingやtraceroute実行後の待ち時間設定を行っていますが、設定内容は適当に決め打ちです。
const syslog = await sleep(['traceroute', 'traceroute6'].includes(command) ? 30000 : 3000)
.then(() => sendRequest(order, true));
LUAスクリプト
local COMMANDS = {
ping = {
cmd = "ping",
args = {"-c", "3"}
},
ping6 = {
cmd = "ping6",
args = {"-c", "3"}
},
traceroute = {
cmd = "traceroute",
args = {"noresolv"}
},
traceroute6 = {
cmd = "traceroute6",
args = {"noresolv"}
},
nslookup = {
cmd = "nslookup",
args = {}
}
}
local function validateInput(command, destination)
if not command or not destination then
error("Usage: script.lua <id> <command> <destination>")
end
if not COMMANDS[command] then
error(string.format("Invalid command: %s. Supported commands: %s",
command, table.concat(getTableKeys(COMMANDS), ", ")))
end
if destination:match("[;|`&$]") then
error("Invalid destination: contains forbidden characters")
end
end
local function getTableKeys(t)
local keys = {}
for k in pairs(t) do
table.insert(keys, k)
end
return keys
end
local function buildCommand(command, destination)
local cmdConfig = COMMANDS[command]
local cmd = cmdConfig.cmd
local args = cmdConfig.args
if command == "traceroute" or command == "traceroute6" then
return string.format("%s %s %s", cmd, destination, table.concat(args, " "))
end
return string.format("%s %s %s", cmd, table.concat(args, " "), destination)
end
local function executeCommand(order)
local success, flag, outputs = pcall(function()
return rt.command(order)
end)
if not success then
error("Command execution failed: " .. tostring(flag))
end
if not flag then
error(string.format("Command execution failed with status false: %s", order))
end
return outputs
end
local function logOutput(sessionId, result)
for line in result:gmatch("[^\r\n]+") do
if line and line:trim() ~= "" then
rt.syslog("info", "[" .. sessionId .. "] " .. line)
end
end
end
string.trim = function(s)
return s:match("^%s*(.-)%s*$")
end
local function main()
local sessionId = arg[1]
local command = arg[2]
local destination = arg[3]
local function logError(err)
rt.syslog("info", "[" .. sessionId .. "] Error: " .. tostring(err))
end
local status, err = pcall(validateInput, command, destination)
if not status then
logError(err)
os.exit(1)
end
local order = buildCommand(command, destination)
local status, result = pcall(executeCommand, order)
if not status then
logError(result)
os.exit(1)
end
logOutput(sessionId, result)
end
main()
実行画面
後書き
先日、セキュリティの都合上 Webインターフェイスしか触れない端末で通信調査をする際に困ったので、pingは実行できるといいですね。カスタムGUIの設定を入れておくのも面倒ですが、Qiitaのネタとして作成しました。
補足ですが、有料サービスのYNO3のインターフェイスからpingの実行はできました。もちろんコマンドラインからのpingも実行できます。
nslookupも勢いでつけてしまいましたが、禁止コマンドではなかったのでLUAを通す意味は薄いかもしれません。
-
即座にレスポンスが返ってきていた。 ↩