0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ヤマハルーターのGUIでPingを実行する

Posted at

前置き

ヤマハルーターはブラウザからコマンドを実行するメニューもありますが、ping や traceroute は実行できません。禁止と表示されてしまいます。

image.png

カスタム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スクリプト

/lua/command.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()

実行画面

Result.png

後書き

先日、セキュリティの都合上 Webインターフェイスしか触れない端末で通信調査をする際に困ったので、pingは実行できるといいですね。カスタムGUIの設定を入れておくのも面倒ですが、Qiitaのネタとして作成しました。
補足ですが、有料サービスのYNO3のインターフェイスからpingの実行はできました。もちろんコマンドラインからのpingも実行できます。

nslookupも勢いでつけてしまいましたが、禁止コマンドではなかったのでLUAを通す意味は薄いかもしれません。

  1. ヤマハネットワーク周辺機器 技術情報ページ カスタムGUI

  2. 即座にレスポンスが返ってきていた。

  3. Yamaha Network Organizer

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?