對於網管或系統管理人員來說,看懂 172.28.16.0/20 CIDR (Classless Inter-Domain Routing) 格式,搞懂 Net Mask 網段概念是基本要求,不然路由器、防火牆都不用玩了。

不過即使是資訊從業人員,平時沒碰不到網路、網站設定,網路不通時有工具人可用,不需下海自射茶包,看不懂 CIDR 好像也是正常的。

前幾天被問了一個問題(對方也是資訊單位),說網頁上的防火牆資料判別 172.28.17.X 跟 172.28.16.0/20 在同一網段,是不是寫錯了?

對非網管人員,CIDR 確實有點難懂,如果是 16/24 切齊 Byte 直接前兩節、前三節數字還好,遇到 20/22 一個 Byte 只用部分位元,得做 16 進位計算才知道 IP 是否屬於該網站,就算是網管人員,要不按計算機得到答案也是個挑戰。

對一般人來說,172.28.16.0 ~ 172.28.31.255 才是給人看的東西,比 172.28.16.0/20 友善多了。於是我有個好點子,何不在 CIDR 自動加個展開鈕,點下去展開成 IP 範圍;IP 範圍旁則有收合鈕,點下去再變回 CIDR。魔法師跟麻瓜挑自己習慣的格式看,豈不兩全其美?

最終呈現效果像這樣:

直覺上是個兩三行 JavaScript 可搞定的小需求,但我想試試用純 CSS 實現。原理跟圖形化 ON/OFF 開關很像,放一個隱藏的 <input type="checkbox" id="...">,展開收合鈕包成 <label for="..."> 讓它被點擊時會勾選或取消勾選 CheckBox 元素。而 CSS 選擇器有 :checked 可為 CheckBox 勾選及未勾選狀態設定不同樣式,於是我們一開始將 CIDR 設 display:inline,IP 範圍文字設 display:none, 點展開鈕所在的 <label> 觸發 CheckBox 被勾選,:checked 選擇器指定的樣式會將 CIDR 換成 display:none,IP 範圍變成 display:inline,如此便實現了切換顯示效果。

而原本 CIDR 被包含網頁的一般文字中,我想透過事後加工方式,將文字中的 CIDR 文字置換成可動態切換顯示 CIDR 或 IP 範圍的區塊。做法不難,用 Regular Expression 尋找 \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2},計算其對映的起始及結束 IP 地址,組裝 HTML 內容。由於 <input type="checkbox" id="XXX"><label for="XXX"> 的 id 識別必須成對且不跟其他元素重複,我用 Math.random() 產生亂數名稱(順便分享 Copilot 教我一招,用 Number.toString(36) 可以直接將數字轉成 36 進位[0-9a-z],超方便)配合自動跳號,確保各 CIDR 區塊 ID 不打架。

document.querySelectorAll(selector).forEach(e => {
    const idPrefix = "_" + Math.random().toString(36).substring(2);
    let idx = 0;
    e.innerHTML = e.innerHTML.replace(
        /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/g,
        cidr => {
            const [start, end] = cidrToRange(cidr);
            if (start === end) {
                return cidr;
            }
            const id = idPrefix + (idx++);
            return `<span class="cidr">
                <input type="checkbox" id="${id}" class="toggle">
                <span class="cidr-c"><label for="${id}">+</label>${cidr}</span>
                <span class="cidr-e"><label for="${id}">–</label>${start} ~ ${end}</span>
            </span>`;
        });
});

至於 CIDR 轉 IP 範團,算是計算機概論的 2 進位及 AND/OR 練習題,不想動腦也可以丟給 Copliot 寫,但請務必看懂:

function cidrToRange(cidr) {
    const [ip, mask] = cidr.split('/');
    const ipParts = ip.split('.').map(Number); // 轉成四個數字
    const maskParts = [0, 0, 0, 0]; // Net Mask 先預設四個 0
    const maskLength = Number(mask);
    for (let i = 0; i < maskLength; i++) {
        // i >> 3 相當於除以 8,計算是 maskParts 的第 0, 1, 2 還是第 3 段
        // 由 i 決定 1 要放第個 Bit,依序產生 10000000, 01000000, 00100000... 跟 maskParts[..] 做 OR 運算
        maskParts[i >> 3] |= 1 << (7 - i % 8);
    }
    // IP 的每一段跟 maskParts 每一段做 AND 決定開始地址
    const start = ipParts.map((part, i) => part & maskParts[i]).join('.');
    // IP 的每一段跟 maskParts 每一段的補數(0與1互換)做 OR 決定結束地址
    const end = ipParts.map((part, i) => part | ~maskParts[i] & 255).join('.');
    return [start, end];
}

最後將全部組裝在一起:線上展示

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CIDR / Range Toggle Display</title>
    <style>
        html {
            font-family: Arial, sans-serif;
            font-size: 11pt;
            line-height: 150%;
        }
    </style>
</head>
<body>

    <div class="sample">
        <ul>
            <li>from 127.0.0.1/32 to 172.28.16.0/20</li>
            <li>from 192.168.1.1 to 0.0.0.0/0</li>
            <li>subnet 192.168.1.0/24</li>
        </ul>
    </div>
    <style>
        .cidr {
            input[type="checkbox"] {
                display: none;
            }

            label {
                opacity: 0.2;
                cursor: pointer;
                border: 1px solid #444;
                background-color: #ccc;
                display: inline-block;
                width: 9pt;
                height: 9pt;
                font-size: 9pt;
                line-height: 9.2pt;
                text-align: center;
                vertical-align: middle;
                margin-right: 2px;

                &:hover {
                    opacity: 1;
                }
            }
            > span {
                background-color: #eee;
                padding: 2px 6px;
            }
            .cidr-e {
                display: none;
            }

            .toggle:checked+.cidr-c {
                display: none;
            }

            .toggle:checked+.cidr-c+.cidr-e {
                display: inline;
            }
        }
    </style>
    <script>
        function cidrToRange(cidr) {
            const [ip, mask] = cidr.split('/');
            const ipParts = ip.split('.').map(Number);
            const maskParts = [0, 0, 0, 0];
            const maskLength = Number(mask);
            for (let i = 0; i < maskLength; i++) {
                maskParts[i >> 3] |= 1 << (7 - i % 8);
            }
            const start = ipParts.map((part, i) => part & maskParts[i]).join('.');
            const end = ipParts.map((part, i) => part | ~maskParts[i] & 255).join('.');
            return [start, end];
        }

        function injectExpandButton(selector) {
            document.querySelectorAll(selector).forEach(e => {
                const idPrefix = "_" + Math.random().toString(36).substring(2);
                let idx = 0;
                e.innerHTML = e.innerHTML.replace(
                    /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/g,
                    cidr => {
                        const [start, end] = cidrToRange(cidr);
                        if (start === end) {
                            return cidr;
                        }
                        const id = idPrefix + (idx++);
                        return `<span class="cidr">
                            <input type="checkbox" id="${id}" class="toggle">
                            <span class="cidr-c"><label for="${id}">+</label>${cidr}</span>
                            <span class="cidr-e"><label for="${id}">–</label>${start} ~ ${end}</span>
                        </span>`;
                    });
            });
        }
        injectExpandButton('.sample');
    </script>
</body>

</html>

收工。

This article discusses a method for converting CIDR notation to IP range using pure CSS and JavaScript. The solution involves toggling between CIDR and IP range views using hidden checkboxes and CSS selectors, making it more user-friendly for non-network administrators.


Comments

Be the first to post a comment

Post a comment