Когда я читаю/слушаю о побитовых операциях, часто говорят: “Да, это полезно знать, но в повседневной работе инженера-программиста с этим приходится сталкиваться нечасто”. Особенно когда вы работаете в приложениях более высокого уровня. Возможно, это и так, но есть один случай использования побитовых операций, который, на мой взгляд, очень полезен, и это когда нужно проверить, находится ли 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)
}
Приятного времяпровождения и хорошего дня 🙂 !