Побитовые операции для вычислений в IPv4

Когда я читаю/слушаю о побитовых операциях, часто говорят: “Да, это полезно знать, но в повседневной работе инженера-программиста с этим приходится сталкиваться нечасто”. Особенно когда вы работаете в приложениях более высокого уровня. Возможно, это и так, но есть один случай использования побитовых операций, который, на мой взгляд, очень полезен, и это когда нужно проверить, находится ли ip-адрес в данной подсети или нет. Чтобы это работало, вы используете биты маски подсети и начального адреса для проверки битов ip-адреса с помощью побитовых операций &. Запутались? Что ж, я тоже 😅 Всегда лучше описывать такие вещи шаг за шагом, что я сейчас и попробую сделать и объяснить это немного подробнее:

Допустим, у нас есть очень маленькая сеть с маской подсети 255.255.255.192 и начальным адресом 192.168.50.65. Далее возьмем два ip-адреса 192.168.50.20 и 192.168.50.80.
Да, я знаю, что на глаз довольно очевидно, какой адрес находится в сети, а какой нет, но для примера и простоты сойдет 🙂 И, конечно, мы хотим сделать это в коде, а не на глаз 😅.

Итак, сначала мы посмотрим на наши примеры адресов и на то, как они выглядят, если представить их в виде битов:

Теперь, как вы видите, маска подсети всегда имеет 1 бит в секции идентификатора сети и 0 бит в секции идентификатора хоста. Такое расположение битов применимо ко всем возможным маскам подсети, и именно это поведение мы можем использовать для достижения нашей цели.

Если вы запутались в том, что такое ID сети и ID хоста, то позвольте мне просто сказать об этом:

Идентификатор сети

Идентификатор сети или NetID – это фрагмент IP-адреса.
который классифицирует сеть для определенного хоста, т.е,
он сообщает нам, к какой сети принадлежит хост.

Идентификатор хоста

Это фрагмент IP-адреса, который однозначно
классифицирует хост в определенной сети TCP/IP.

Источник этой цитаты и более подробную информацию вы можете найти здесь

Возвращаясь к нашей задаче, давайте теперь используем биты нашей маски подсети и начального адреса для создания своего рода проверочных битов с помощью побитовой операции &:

Если вы забыли, как работает побитовая операция &, вы можете посмотреть здесь. Или просто запомните 1 & 1 = 1, а все остальные возможности = 0 😉.

А теперь мы можем проделать то же самое для двух ip-адресов и сравнить результат с нашими проверочными битами, и если они совпадают, то можно сказать, что ip находится в данной сети.

Начнем с первого ip 192.168.50.20:

Вы можете видеть, что биты проверки не совпадают с результатом, где я отметил его красным цветом. Значит, этого ip нет в нашей сети. Если мы сделаем то же самое для второго адреса:

Он совпадает 🎉.

Теперь, прежде чем я продолжу с примером кода, чтобы реализовать это. Если вы хотите попробовать сделать это самостоятельно, вы можете попрактиковаться в реализации в codewars kata, которую я создал здесь. Он все еще находится в бета-версии, но c#, js и golang уже готовы к использованию 😊.

В своем примере я собираюсь использовать golang, который является языком, который я изучаю в данный момент, и где я могу использовать некоторую практику 😅.

Прежде всего, мы не можем сравнивать биты непосредственно в нашем коде. Поэтому нам нужно разобрать наши биты до значения int или точнее uint, так как ip не имеет отрицательных значений.

На самом деле это довольно просто и готово к копированию из golang playground здесь. Для простоты в нашем примере мы не возвращаем ошибку в этой функции и просто делаем panic непосредственно, если вставлена неправильная строка ip.

func Ip2long(ipAddr string) (uint32) {
  ip := net.ParseIP(ipAddr)
  if ip == nil {
    panic("wrong ipAddr format")
  }
  ip = ip.To4()
  return binary.BigEndian.Uint32(ip)
}
Вход в полноэкранный режим Выход из полноэкранного режима

Далее нам нужна функция сравнения или проверки, которая просто возвращает значение bool, является ли ip в или не в данной сети. Побитовая операция & выполняется в return, где видно, что мы используем для обоих адресов, ip и начального адреса, маску подсети, чтобы создать наши биты проверки (как uint) и затем сравнить их друг с другом.

func IsInSubnet(ip string, startAddress string, mask string) bool {
  uIntIp :=  Ip2long(ip)
  uIntStartAdress := Ip2long(startAddress)
  uIntMask := Ip2long(mask)
  return (uIntIp & uIntMask) == (uIntStartAdress & uIntMask)  
}
Вход в полноэкранный режим Выход из полноэкранного режима

Собрав все вместе, вы можете использовать это для игры:

package main

import (
    "encoding/binary"
    "fmt"
    "net"
)

func main() {
    mask := "255.255.255.192"
    startAddress := "192.168.50.65"
    ip1 := "192.168.50.20"
    ip2 := "192.168.50.80"

    fmt.Println("IP1 Result: ", IsInSubnet(ip1, startAddress, mask))
    fmt.Println("IP2 Result: ", IsInSubnet(ip2, startAddress, mask))
}

func IsInSubnet(ip string, startAddress string, mask string) bool {
    uIntIp := Ip2long(ip)
    uIntStartAdress := Ip2long(startAddress)
    uIntMask := Ip2long(mask)
    return (uIntIp & uIntMask) == (uIntStartAdress & uIntMask)
}

func Ip2long(ipAddr string) uint32 {
    ip := net.ParseIP(ipAddr)
    if ip == nil {
        panic("wrong ipAddr format")
    }
    ip = ip.To4()
    return binary.BigEndian.Uint32(ip)
}

Вход в полноэкранный режим Выход из полноэкранного режима

Приятного времяпровождения и хорошего дня 🙂 !

Оцените статью
Procodings.ru
Добавить комментарий