{"id":130,"date":"2026-09-06T22:49:04","date_gmt":"2026-09-06T13:49:04","guid":{"rendered":"https:\/\/haibara.us\/?p=130"},"modified":"2026-09-07T00:21:23","modified_gmt":"2026-09-06T15:21:23","slug":"icmp","status":"publish","type":"post","link":"https:\/\/haibara.us\/?p=130","title":{"rendered":"OVH Hillsboro VPS Looking Glass"},"content":{"rendered":"\n<div id=\"looking-glass\">\n  <style>\n    #looking-glass {\n      max-width: 860px;\n      margin: 30px auto;\n      font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\",\n                   Roboto, Helvetica, Arial, sans-serif;\n      color: #e5e7eb;\n    }\n\n    #looking-glass * {\n      box-sizing: border-box;\n    }\n\n    .lg-card {\n      background: #111827;\n      border: 1px solid #263244;\n      border-radius: 14px;\n      padding: 24px;\n      box-shadow: 0 12px 35px rgba(0, 0, 0, 0.25);\n    }\n\n    .lg-title {\n      font-size: 26px;\n      font-weight: 700;\n      margin-bottom: 6px;\n      color: #ffffff;\n    }\n\n    .lg-subtitle {\n      color: #9ca3af;\n      font-size: 14px;\n      margin-bottom: 22px;\n    }\n\n    .lg-grid {\n      display: grid;\n      grid-template-columns: 1fr 190px;\n      gap: 12px;\n    }\n\n    .lg-input,\n    .lg-select {\n      width: 100%;\n      border: 1px solid #374151;\n      background: #0b1220;\n      color: #ffffff;\n      padding: 12px 14px;\n      border-radius: 8px;\n      outline: none;\n      font-size: 15px;\n      transition: border-color 0.2s ease;\n    }\n\n    .lg-input:focus,\n    .lg-select:focus {\n      border-color: #3b82f6;\n    }\n\n    .lg-input::placeholder {\n      color: #6b7280;\n    }\n\n    .lg-actions {\n      margin-top: 14px;\n      display: flex;\n      gap: 10px;\n      flex-wrap: wrap;\n    }\n\n    .lg-btn {\n      border: 0;\n      padding: 11px 20px;\n      border-radius: 8px;\n      cursor: pointer;\n      font-weight: 600;\n      font-size: 14px;\n      transition: all 0.2s ease;\n    }\n\n    .lg-btn-primary {\n      background: #2563eb;\n      color: #ffffff;\n    }\n\n    .lg-btn-primary:hover {\n      background: #1d4ed8;\n    }\n\n    .lg-btn-secondary {\n      background: #374151;\n      color: #ffffff;\n    }\n\n    .lg-btn-secondary:hover {\n      background: #4b5563;\n    }\n\n    .lg-btn:disabled {\n      opacity: 0.5;\n      cursor: not-allowed;\n    }\n\n    .lg-status-row {\n      margin-top: 20px;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      gap: 12px;\n      font-size: 13px;\n    }\n\n    .lg-status {\n      color: #9ca3af;\n    }\n\n    .lg-target-info {\n      color: #6b7280;\n      text-align: right;\n      word-break: break-all;\n    }\n\n    .lg-output {\n      margin-top: 10px;\n      background: #020617;\n      border: 1px solid #253044;\n      border-radius: 10px;\n      padding: 18px;\n      min-height: 250px;\n      max-height: 550px;\n      overflow: auto;\n      white-space: pre-wrap;\n      word-break: break-word;\n      color: #86efac;\n      font-family: \"SFMono-Regular\", Consolas,\n                   \"Liberation Mono\", Menlo, monospace;\n      font-size: 13px;\n      line-height: 1.7;\n    }\n\n    .lg-output.lg-error {\n      color: #fca5a5;\n    }\n\n    .lg-footer {\n      margin-top: 14px;\n      color: #6b7280;\n      font-size: 12px;\n      line-height: 1.6;\n    }\n\n    @media (max-width: 650px) {\n      .lg-grid {\n        grid-template-columns: 1fr;\n      }\n\n      .lg-card {\n        padding: 18px;\n      }\n\n      .lg-status-row {\n        display: block;\n      }\n\n      .lg-target-info {\n        text-align: left;\n        margin-top: 5px;\n      }\n\n      .lg-output {\n        min-height: 220px;\n        font-size: 12px;\n      }\n    }\n  <\/style>\n\n  <div class=\"lg-card\">\n\n    <div class=\"lg-title\">\n      Looking Glass\n    <\/div>\n\n    <div class=\"lg-subtitle\">\n      ICMP Ping \/ Traceroute Network Test\n    <\/div>\n\n    <div class=\"lg-grid\">\n\n      <input id=\"lg-target\" class=\"lg-input\" type=\"text\" placeholder=\"IPv4 \/ IPv6 \/ Domain, e.g. 1.1.1.1\" maxlength=\"253\" autocomplete=\"off\" spellcheck=\"false\">\n\n      <select id=\"lg-test\" class=\"lg-select\">\n        <option value=\"ping\">\n          ICMP Ping\n        <\/option>\n\n        <option value=\"traceroute\">\n          Traceroute\n        <\/option>\n      <\/select>\n\n    <\/div>\n\n    <div class=\"lg-actions\">\n\n      <button id=\"lg-run\" class=\"lg-btn lg-btn-primary\" type=\"button\">\n        Run Test\n      <\/button>\n\n      <button id=\"lg-clear\" class=\"lg-btn lg-btn-secondary\" type=\"button\">\n        Clear\n      <\/button>\n\n    <\/div>\n\n    <div class=\"lg-status-row\">\n\n      <div id=\"lg-status\" class=\"lg-status\">\n        Ready\n      <\/div>\n\n      <div id=\"lg-target-info\" class=\"lg-target-info\"><\/div>\n\n    <\/div>\n\n    <pre id=\"lg-output\" class=\"lg-output\">Looking Glass ready.\n\nEnter an IPv4 address, IPv6 address, or domain, then choose a test type.\n\nExamples:\n1.1.1.1\n8.8.8.8\ngoogle.com<\/pre>\n\n    <div class=\"lg-footer\">\n      Tests are performed from this server and show the network path from this server to the target.\n    <\/div>\n\n  <\/div>\n\n  <script>\n    (function () {\n\n      \/*\n       * ==========================================================\n       * WordPress REST API\n       *\n       * This site uses Plain Permalinks:\n       *\n       * https:\/\/haibara.us\/?p=130\n       *\n       * Therefore, this page uses ?rest_route= to call the WordPress REST API.\n       * ==========================================================\n       *\/\n\n      const API = {\n        ping:\n          window.location.origin +\n          \"\/?rest_route=\/lg\/v1\/ping\",\n\n        traceroute:\n          window.location.origin +\n          \"\/?rest_route=\/lg\/v1\/traceroute\"\n      };\n\n\n      \/*\n       * ==========================================================\n       * Elements\n       * ==========================================================\n       *\/\n\n      const targetInput =\n        document.getElementById(\"lg-target\");\n\n      const testSelect =\n        document.getElementById(\"lg-test\");\n\n      const runButton =\n        document.getElementById(\"lg-run\");\n\n      const clearButton =\n        document.getElementById(\"lg-clear\");\n\n      const output =\n        document.getElementById(\"lg-output\");\n\n      const status =\n        document.getElementById(\"lg-status\");\n\n      const targetInfo =\n        document.getElementById(\"lg-target-info\");\n\n\n      \/*\n       * ==========================================================\n       * Output\n       * ==========================================================\n       *\/\n\n      function setOutput(text, isError = false) {\n\n        output.textContent = text;\n\n        if (isError) {\n          output.classList.add(\"lg-error\");\n        } else {\n          output.classList.remove(\"lg-error\");\n        }\n\n      }\n\n\n      \/*\n       * ==========================================================\n       * Validate Target\n       *\n       * Note:\n       * This is only client-side validation for user experience.\n       * The real security validation is handled by the WordPress plugin backend.\n       * ==========================================================\n       *\/\n\n      function validateTarget(target) {\n\n        if (!target) {\n          return \"Please enter a target IP address or domain.\";\n        }\n\n        if (target.length > 253) {\n          return \"The target is too long.\";\n        }\n\n        \/*\n         * Not allowed:\n         *\n         * http:\/\/example.com\n         * https:\/\/example.com\n         *\/\n\n        if (\n          target.startsWith(\"http:\/\/\") ||\n          target.startsWith(\"https:\/\/\")\n        ) {\n          return \"Please enter only an IP address or domain. Do not include http:\/\/ or https:\/\/\";\n        }\n\n        \/*\n         * Block obviously unsafe characters.\n         *\/\n\n        const forbidden =\n          \/[;&|`$<>{}\\[\\]\\\\\\n\\r\"' ]\/;\n\n        if (forbidden.test(target)) {\n          return \"The target contains unsupported characters.\";\n        }\n\n        return null;\n\n      }\n\n\n      \/*\n       * ==========================================================\n       * Run Test\n       * ==========================================================\n       *\/\n\n      async function runTest() {\n\n        const target =\n          targetInput.value.trim();\n\n        const type =\n          testSelect.value;\n\n        const error =\n          validateTarget(target);\n\n\n        \/*\n         * Validation\n         *\/\n\n        if (error) {\n\n          status.textContent =\n            \"Invalid Target\";\n\n          targetInfo.textContent = \"\";\n\n          setOutput(\n            \"ERROR\\n\\n\" + error,\n            true\n          );\n\n          return;\n\n        }\n\n\n        \/*\n         * Lock UI\n         *\/\n\n        runButton.disabled = true;\n        targetInput.disabled = true;\n        testSelect.disabled = true;\n\n        targetInfo.textContent =\n          \"Target: \" + target;\n\n\n        if (type === \"ping\") {\n\n          status.textContent =\n            \"Running ICMP Ping...\";\n\n          setOutput(\n            \"$ ping \" +\n            target +\n            \"\\n\\nRunning...\"\n          );\n\n        } else {\n\n          status.textContent =\n            \"Running Traceroute...\";\n\n          setOutput(\n            \"$ traceroute \" +\n            target +\n            \"\\n\\nRunning...\"\n          );\n\n        }\n\n\n        \/*\n         * API Request\n         *\/\n\n        try {\n\n          const response =\n            await fetch(\n              API[type],\n              {\n                method: \"POST\",\n\n                headers: {\n                  \"Content-Type\":\n                    \"application\/json\",\n\n                  \"Accept\":\n                    \"application\/json\"\n                },\n\n                cache: \"no-store\",\n\n                body: JSON.stringify({\n                  target: target\n                })\n              }\n            );\n\n\n          \/*\n           * Read the raw server response first.\n           *\n           * This lets us inspect the real error if the server returns:\n           *\n           * 403 HTML\n           * 404 HTML\n           * 500 HTML\n           * a Cloudflare page\n           *\n           * we can still see the actual error\n           * instead of only showing:\n           *\n           * \"The API did not return valid JSON\"\n           *\/\n\n          const rawResponse =\n            await response.text();\n\n\n          \/*\n           * Parse JSON\n           *\/\n\n          let data;\n\n          try {\n\n            data =\n              JSON.parse(rawResponse);\n\n          } catch (parseError) {\n\n            console.error(\n              \"Looking Glass Raw Response:\",\n              rawResponse\n            );\n\n            throw new Error(\n              \"The API did not return valid JSON.\\n\\n\" +\n\n              \"HTTP Status: \" +\n              response.status +\n              \"\\n\" +\n\n              \"Content-Type: \" +\n              (\n                response.headers.get(\n                  \"content-type\"\n                ) || \"unknown\"\n              ) +\n\n              \"\\n\\nRaw server response:\\n\\n\" +\n\n              rawResponse.substring(\n                0,\n                2000\n              )\n            );\n\n          }\n\n\n          \/*\n           * WordPress\/API Error\n           *\/\n\n          if (!response.ok) {\n\n            throw new Error(\n              data.message ||\n              data.error ||\n              (\n                \"HTTP Error \" +\n                response.status\n              )\n            );\n\n          }\n\n\n          if (\n            data.success === false\n          ) {\n\n            throw new Error(\n              data.message ||\n              data.error ||\n              \"The test failed.\"\n            );\n\n          }\n\n\n          \/*\n           * Successful Output\n           *\/\n\n          status.textContent =\n            \"Completed\";\n\n\n          \/*\n           * Show the resolved IP returned by the backend plugin\n           *\/\n\n          if (data.resolved) {\n\n            targetInfo.textContent =\n              \"Target: \" +\n              target +\n              \" \u2192 \" +\n              data.resolved;\n\n          }\n\n\n          if (data.output) {\n\n            let finalOutput = \"\";\n\n            if (\n              data.target &&\n              data.resolved &&\n              data.target !== data.resolved\n            ) {\n\n              finalOutput +=\n                \"Target: \" +\n                data.target +\n                \"\\n\";\n\n              finalOutput +=\n                \"Resolved: \" +\n                data.resolved +\n                \"\\n\\n\";\n\n            }\n\n            finalOutput +=\n              data.output;\n\n            setOutput(\n              finalOutput,\n              false\n            );\n\n          } else {\n\n            \/*\n             * If the API returns JSON\n             * but does not include output,\n             * display the complete JSON response.\n             *\/\n\n            setOutput(\n              JSON.stringify(\n                data,\n                null,\n                2\n              ),\n              false\n            );\n\n          }\n\n        } catch (error) {\n\n          status.textContent =\n            \"Failed\";\n\n          console.error(\n            \"Looking Glass Error:\",\n            error\n          );\n\n          setOutput(\n            \"ERROR\\n\\n\" +\n            error.message,\n            true\n          );\n\n        } finally {\n\n          \/*\n           * Unlock UI\n           *\/\n\n          runButton.disabled = false;\n          targetInput.disabled = false;\n          testSelect.disabled = false;\n\n          targetInput.focus();\n\n        }\n\n      }\n\n\n      \/*\n       * ==========================================================\n       * Run Button\n       * ==========================================================\n       *\/\n\n      runButton.addEventListener(\n        \"click\",\n        function () {\n          runTest();\n        }\n      );\n\n\n      \/*\n       * ==========================================================\n       * Clear Button\n       * ==========================================================\n       *\/\n\n      clearButton.addEventListener(\n        \"click\",\n        function () {\n\n          targetInput.value = \"\";\n\n          status.textContent =\n            \"Ready\";\n\n          targetInfo.textContent =\n            \"\";\n\n          setOutput(\n            \"Looking Glass ready.\\n\\n\" +\n            \"Enter an IPv4 address, IPv6 address, or domain, then choose a test type.\\n\\n\" +\n            \"Examples:\\n\" +\n            \"1.1.1.1\\n\" +\n            \"8.8.8.8\\n\" +\n            \"google.com\"\n          );\n\n          targetInput.focus();\n\n        }\n      );\n\n\n      \/*\n       * ==========================================================\n       * Enter = Run\n       * ==========================================================\n       *\/\n\n      targetInput.addEventListener(\n        \"keydown\",\n        function (event) {\n\n          if (event.key === \"Enter\") {\n\n            event.preventDefault();\n\n            if (!runButton.disabled) {\n              runTest();\n            }\n\n          }\n\n        }\n      );\n\n\n      \/*\n       * ==========================================================\n       * Console Debug\n       * ==========================================================\n       *\/\n\n      console.log(\n        \"Looking Glass API:\",\n        API\n      );\n\n    })();\n  <\/script>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Looking Glass ICMP Ping \/ Traceroute Network Test ICMP Ping Traceroute Run Test Clear Ready Looking Glass ready. Enter an IPv4 address, IPv6 address, or domain, then choose a test type. Examples: 1.1.1.1 8.8.8.8 google.com Tests are performed from this server and show the network path from this server to the target.<\/p>\n","protected":false},"author":1,"featured_media":137,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[],"class_list":["post-130","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tech"],"_links":{"self":[{"href":"https:\/\/haibara.us\/index.php?rest_route=\/wp\/v2\/posts\/130","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/haibara.us\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/haibara.us\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/haibara.us\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/haibara.us\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=130"}],"version-history":[{"count":5,"href":"https:\/\/haibara.us\/index.php?rest_route=\/wp\/v2\/posts\/130\/revisions"}],"predecessor-version":[{"id":136,"href":"https:\/\/haibara.us\/index.php?rest_route=\/wp\/v2\/posts\/130\/revisions\/136"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/haibara.us\/index.php?rest_route=\/wp\/v2\/media\/137"}],"wp:attachment":[{"href":"https:\/\/haibara.us\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=130"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/haibara.us\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=130"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/haibara.us\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=130"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}