Kalkulator IP

Javascript
Priorytet: Normalny Szkic

Zadanie 300: Kalkulator IP

Wstęp

Adresacja IP to jeden z fundamentów działania sieci komputerowych. Zrozumienie, jak komputer przetwarza adres IP oraz maskę podsieci, jest kluczowe dla każdego, kto zajmuje się administracją sieci czy programowaniem aplikacji sieciowych. W tym zadaniu zajmiemy się stworzeniem własnego kalkulatora IP w języku JavaScript.

Podstawowe elementy kalkulatora IP

Kalkulator IP służy do wyznaczania kluczowych parametrów podsieci na podstawie podanego adresu IP i maski podsieci. Do głównych elementów, które musimy obliczyć, należą:

  1. Adres sieci (Network Address): Pierwszy adres w podsieci, reprezentujący samą sieć. Nie może być przypisany do żadnego urządzenia (hosta).
  2. Adres rozgłoszeniowy (Broadcast Address): Ostatni adres w podsieci, używany do wysyłania pakietów do wszystkich urządzeń w danej sieci naraz.
  3. Minimalny adres hosta (Min IP / First Host): Pierwszy użyteczny adres IP wewnątrz sieci, który możemy przypisać urządzeniu (Adres sieci + 1).
  4. Maksymalny adres hosta (Max IP / Last Host): Ostatni użyteczny adres IP wewnątrz sieci, który możemy przypisać urządzeniu (Adres rozgłoszeniowy - 1).
  5. Liczba komputerów w sieci (Number of Hosts): Całkowita liczba użytecznych adresów IP w danej podsieci. Obliczana jest za pomocą wzoru 2^(32 - maska) - 2. Dwa wykluczone adresy to zawsze adres sieci i powiązany z nią adres rozgłoszeniowy.

Parsowanie adresu IP i maski w JavaScript

Maszyny dokonują obliczeń sieciowych binarnie na 32-bitowych ciągach znaków. Aby ułatwić nam logikę, najpierw musimy przetworzyć (sparsować) ciąg znaków (np. "192.168.1.10") na jedną matematyczną postać liczbową, użyteczną do operacji bitowych.

1. Parsowanie adresu IP

Adres IP w wersji 4 (IPv4) składa się z 4 oktetów zapisanych dziesiętnie, oddzielonych kropkami. Najłatwiej przekształcić go na jedną 32-bitową liczbę całkowitą (integer). Użyjemy do tego wbudowanej metody split(".") w celu rozdzielenia wyrazu na cztery osobne blok-oktety. Następnie posłużymy się przesunięciem bitowym w lewo (<<) i w rezultacie połączmy za pomocą operatora bitowego OR (|) w jedną wielką liczbę.

// Przykładowa funkcja parsująca tekstowy adres IP do liczby 32-bitowej
function ipToNum(ip) {
    let octets = ip.split('.'); // tworzymy tablicę np. ["192", "168", "1", "10"]
    
    return (parseInt(octets[0]) << 24) |
           (parseInt(octets[1]) << 16) |
           (parseInt(octets[2]) << 8) |
            parseInt(octets[3]) >>> 0; 
            // `>>> 0` wymusza na końcu potraktowanie liczby jako bez znaku (unsigned)
}

2. Parsowanie maski podsieci

Maska w praktycznych ćwiczeniach często podawana jest w krótkim formacie CIDR (np. /24), oznaczającym ile początkowych bitów (od strony lewej) to jedynki, decydując o zasięgu sieci. Możemy z niej wygenerować 32-bitową liczbę:

// Zamiana maski CIDR (np. liczba 24) na liczbę 32-bitową do operacji
function cidrToMaskNum(cidr) {
    // Generowanie maski: (-1 to binarnie same jedynki)
    // Przesuwamy w lewo o "wolne miejsca", następnie ucinamy operatorami do wartości bez znaku.
    return (-1 << (32 - cidr)) >>> 0; 
}

Obliczanie poszczególnych elementów

Po przekonwertowaniu adresu IP oraz maski podsieci do wspomnianych liczb układających się bitowo, otrzymujemy pełną dowolność, aby dokonać niezbędnych obliczeń logicznych dla naszego kalkulatora:

1. Adres sieci (Network)

Adres sieci znajduje się "wycinając" tylko wspólne mianowniki z adresacji bazowej przy użyciu logicznego AND (&) między wygenerowaną liczbą z IP a z Maski.

// Mając ipNum oraz maskNum 
let networkNum = (ipNum & maskNum) >>> 0;

2. Adres rozgłoszeniowy (Broadcast)

Za wyliczenie broadcastu odpowiada dodanie przeciwnej (odwróconej) formy maski. Używamy logicznego OR (|) z odwróconą za pomocą bitowego NOT (~) maską podsieci.

let invertedMaskNum = ~maskNum;
let broadcastNum = (networkNum | invertedMaskNum) >>> 0;

3. Pierwszy oraz Ostatni host (Min IP / Max IP)

Minimalny IP i Maksymalny IP to jedynie zwykłe operatory obok skrajnych części wykluczonych na zewnątrz.

let minIpNum = networkNum + 1;
let maxIpNum = broadcastNum - 1; 

4. Liczba adresów użytecznych w sieci

Jest to matematyczne spotęgowanie wolnych bitów przestrzeni minus dwa zarezerwowane adresy brzeżne.

let numHosts = Math.pow(2, 32 - cidr) - 2;
// W normalnych podsieciach odejmujemy od potęgi cyfrę 2. (Gdy maska jest większa od 30 to zasady potrafią się zmienić).

Jak przywrócić format znaków po obliczeniach?

Mając już gotowe w wariantach liczbowych dane o np. Twoim networkNum, możemy zamaskować je od nowa dzieląc układ i wydobywając operacjami AND (& 255) reszty bajtowe spajając kropką za pomocą wbudowanego na końcu join().

function numToIp(num) {
    return [
        (num >>> 24) & 255,
        (num >>> 16) & 255,
        (num >>> 8) & 255,
        num & 255
    ].join('.');
}

// Przykład
// console.log(numToIp(networkNum)); // Zwróci np. "192.168.1.0"

Zadania dodatkowe (obowiązkowe)

Poniżej znajdziesz 5 dodatkowych wytycznych do wykonania w swoim kodzie. Traktujemy je jako podstawę tego zadania - są to instrukcje obowiązkowe do wykonania, a nie tylko ukłon dla chętnych. Rozwiąż je, dopracowując swój skrypt krok po kroku.

  1. Walidacja danych wejściowych (RegEx/If-y): Zabezpiecz kalkulator mechanizmem, który przed jakimkolwiek bitowym obliczeniem zweryfikuje, czy podany ciąg jako adres IP ma odpowiednią formę (cztery liczby w zakresie 0-255 przedzielone wyłącznie kropeczkami) oraz czy maska podsieci w notacji CIDR mieści się matematycznie w twardym spektrum od /0 do /32. Jeżeli wynik z wejścia da fałsz, program powinien przerwać pracę i wygenerować wyjątek logu np. za pomocą console.error().
  2. Zwracanie danych w postaci gotowego Obiektu (JSON): Twój kalkulator jako instrukcja globalna funkcji (np. pod klamrą kalkulatorIp()) po zakończeniu całego procesu ma agregować dane, by zamiast wylewać je prosto z czeluści terminala, finalnie zwrócił za pośrednictwem return jeden kompletny obiekt (JavaScript Object). Powinien on posiadać wyraźne klucze dla właściwości: network, broadcast, first_host, last_host oraz hosts_count (oczywiście wehikuł zwrócony stringami przez wspomnianą funkcję numToIp(...)).
  3. Obsługa masek w formie dziesiętnej (kropkowej): Zmodyfikuj początki swojego kodu parsującego tak, aby program opcjonalnie mógł zaakceptować na wejściu maskę jako pełen adres kropkowy obok IP (np. 255.255.255.0 zamiast liczby /24). Skrypt po wykryciu kropek wykona procedurę "wypluwania" notacji (zwracając, że 255.255.255.0 posiada zapalonych domyślnie "24" bitów jedynek od lewej). Wykorzystaj to, by wrócić z procesem do logiki klasycznej dla liczby CIDR.
  4. Rozpoznawanie historycznej klasy IP oraz puli prywatnej: W obiekcie JSON z punktu drugiego mają znaleźć się dwa kolejne, nowe klucze. Oznacz dla zbadanego zakresu na zasadzie klasyki domyślną Klasę Sieci (klucz np. network_class) oddając A B C D ewentulnie E; po czym dorzuć wartość informującą true/false, która zdradzi odpowiednio napisanymi po sobie poleceniami warunkowymi, czy jest to popularny "Prywatny Adres IP" (adres zza maskarady domowej puli 192.168.x.x, 10.x.x.x, 172.16-31.x.x).
  5. Budowa interfejsu przeglądarkowego HTML/CSS/DOM: Wyłuskaj esencję stworzonej logiki z terminala umieszczając skrypt w złączeniu pliku index.html. Zbuduj formularz posiadający proste kafelki dla Adresu IP oraz Maski. Dopracuj go kodem JS wprowadzając funkcję cyklicznie nasłuchującą (addEventListener), dzięki której podanie/zmiana liter na wejściu błyskawicznie – w czasie rzeczywistym zaktualizuje osadzoną statycznie tabelkę na dole okna uderzając bezpośrednio przez polecenie innerHTML. Postaraj się również ostylować graficznie swój kalkulator w skromnym stopniu używając reguł CSS.