
Convert IP range to classless addressing (CIDR) and back to Go
During the next interview, they asked me a small test task, write a network scanner of open ports on Go. The task, in principle, is not difficult, but one of the conditions was that both the IP address and the range of network addresses in the form of a network mask can be passed as a parameter: 192.168.8.0/21.
The topic is most likely very trivial for network engineers and administrators, and probably even boring. My goal is simply to outline an algorithm for translating the IP range into a netmask (hereinafter referred to as CIDR) and back from CIDR to the address range.
A little theory, but for those who are already familiar with the terms, you can skip and go straight to the algorithm.
And so, what is IP, I think everyone understands and does not need to explain. Now what is CIDR (or netmask). Imagine that we have IP: "192.168.11.10". In fact, these are 8-bit values separated by dots, and each individual part is a so-called octet . It is clear that IP can be represented as a 32-bit number.

It is this number that is transmitted in the IP packet. Now let's imagine that we have a subnet consisting of 8 hosts - 192.168.11.0 to 192.168.11.7 (Note: 192.168.11.0 cannot be used as the address of any network interface, since this address is used as the identifier of the subnet, so in fact the addresses will be 7, but for our example this does not matter yet.)
It is clear that there can be several subnets within the same large network and you need to send a packet inside your subnet directly, but let’s say if you want to forward the packet to another subnet on IP: 192.168.11.22, then you need to forward it to a network router that redirects this packet to another subnet. Storing all the addresses of your subnet on the sender host is unprofitable and pointless, so we just store the network mask and for the above designated network of 8 hosts it will be - 255.255.255.248. Now if we decompose the netmask into bits, we get 29 units and 3 zeros.

The subnet mask can never mix “1” and “0”, so always the sequence “1” comes first, and then the sequence “0”. Now the range above the designated addresses 192.168.11.0 to 192.168.11.7 can be represented as/ <Subnet mask>, i.e. 192.168.11.0/29. This is Classless Inter-Domain Routing (CIDR) and in this compact form you can imagine any range of IP addresses. Regarding the correct subnetting and routing, it makes sense to refer to specialized literature and is not the purpose of this article - I hope network administrators will forgive me.
And so the algorithm itself with comments is given below. I must say right away that for the range 216.58.192.12 - 216.58.192.206, there is no way to split into one subnet and the algorithm will immediately break the range into several subnets:
{
"216.58.192.12/30",
"216.58.192.16/28",
"216.58.192.32 / 27 ",
" 216.58.192.64/26 ",
" 216.58.192.128/26 ",
" 216.58.192.192/29 ",
“216.58.192.200/30”,
“216.58.192.204/31”,
“216.58.192.206/32”
}
Now for the inverse algorithm, when you need to translate the same range of classless addressing back to IP. Personally, I didn’t need this algorithm in my test task, and maybe some of the network administrators will comment on how much it is needed in real life, but I decided to write both at once. The algorithm to the elementary is simple, you should add its dimension to the initial IP subnet.
Note: only for Go developers: the algorithm can be made even more productive if you return data in the format - (IP, * IPNet, error), but for universality I return the data as a string.
Network scanner code is here: GitHub . If you put an asterisk, I will be grateful, but only if I deserve it :))
Links:
The topic is most likely very trivial for network engineers and administrators, and probably even boring. My goal is simply to outline an algorithm for translating the IP range into a netmask (hereinafter referred to as CIDR) and back from CIDR to the address range.
A little theory, but for those who are already familiar with the terms, you can skip and go straight to the algorithm.
And so, what is IP, I think everyone understands and does not need to explain. Now what is CIDR (or netmask). Imagine that we have IP: "192.168.11.10". In fact, these are 8-bit values separated by dots, and each individual part is a so-called octet . It is clear that IP can be represented as a 32-bit number.

It is this number that is transmitted in the IP packet. Now let's imagine that we have a subnet consisting of 8 hosts - 192.168.11.0 to 192.168.11.7 (Note: 192.168.11.0 cannot be used as the address of any network interface, since this address is used as the identifier of the subnet, so in fact the addresses will be 7, but for our example this does not matter yet.)
It is clear that there can be several subnets within the same large network and you need to send a packet inside your subnet directly, but let’s say if you want to forward the packet to another subnet on IP: 192.168.11.22, then you need to forward it to a network router that redirects this packet to another subnet. Storing all the addresses of your subnet on the sender host is unprofitable and pointless, so we just store the network mask and for the above designated network of 8 hosts it will be - 255.255.255.248. Now if we decompose the netmask into bits, we get 29 units and 3 zeros.

The subnet mask can never mix “1” and “0”, so always the sequence “1” comes first, and then the sequence “0”. Now the range above the designated addresses 192.168.11.0 to 192.168.11.7 can be represented as
And so the algorithm itself with comments is given below. I must say right away that for the range 216.58.192.12 - 216.58.192.206, there is no way to split into one subnet and the algorithm will immediately break the range into several subnets:
{
"216.58.192.12/30",
"216.58.192.16/28",
"216.58.192.32 / 27 ",
" 216.58.192.64/26 ",
" 216.58.192.128/26 ",
" 216.58.192.192/29 ",
“216.58.192.200/30”,
“216.58.192.204/31”,
“216.58.192.206/32”
}
Convert IPv4 range into CIDR
// Convert IPv4 range into CIDR
func iPv4RangeToCIDR(ipStart string, ipEnd string) (CIDRs []string, err error) {
cidr2mask := []uint32{
0x00000000, 0x80000000, 0xC0000000,
0xE0000000, 0xF0000000, 0xF8000000,
0xFC000000, 0xFE000000, 0xFF000000,
0xFF800000, 0xFFC00000, 0xFFE00000,
0xFFF00000, 0xFFF80000, 0xFFFC0000,
0xFFFE0000, 0xFFFF0000, 0xFFFF8000,
0xFFFFC000, 0xFFFFE000, 0xFFFFF000,
0xFFFFF800, 0xFFFFFC00, 0xFFFFFE00,
0xFFFFFF00, 0xFFFFFF80, 0xFFFFFFC0,
0xFFFFFFE0, 0xFFFFFFF0, 0xFFFFFFF8,
0xFFFFFFFC, 0xFFFFFFFE, 0xFFFFFFFF,
}
// Переведем IP в беззнаковые целые числа.
ipStartUint32 := iPv4ToUint32(ipStart)
ipEndUint32 := iPv4ToUint32(ipEnd)
// Если диапазон задан неверно, просто вернем ошибку.
if ipStartUint32 > ipEndUint32 {
log.Fatalf("start IP:%s must be less than end IP:%s", ipStart, ipEnd)
}
for ipEndUint32 >= ipStartUint32 {
maxSize := 32
// Определим максимальную маску подсети доступную для текущего IP адреса.
for maxSize > 0 {
maskedBase := ipStartUint32 & cidr2mask[maxSize - 1]
if maskedBase != ipStartUint32 {
break
}
maxSize--
}
// Проверим, если маска превышает диапазон указанный в конечном IP адресе. И если превышает, проведем коррекцию.
x := math.Log(float64(ipEndUint32 - ipStartUint32 + 1)) / math.Log(2)
maxDiff := 32 - int(math.Floor(x))
if maxSize < maxDiff {
maxSize = maxDiff
}
// Сохраним CIDR
CIDRs = append(CIDRs, uInt32ToIPv4(ipStartUint32) + "/" + strconv.Itoa(maxSize))
// Увеличим диапазон на размерность подсети и повторим цикл.
ipStartUint32 += uint32(math.Exp2(float64(32 - maxSize)))
}
return CIDRs, err
}
Now for the inverse algorithm, when you need to translate the same range of classless addressing back to IP. Personally, I didn’t need this algorithm in my test task, and maybe some of the network administrators will comment on how much it is needed in real life, but I decided to write both at once. The algorithm to the elementary is simple, you should add its dimension to the initial IP subnet.
Convert CIDR to IPv4 range
// Convert CIDR to IPv4 range
func CIDRRangeToIPv4Range(CIDRs []string) (ipStart string, ipEnd string, err error) {
var ip uint32 // ip address
var ipS uint32 // Start IP address range
var ipE uint32 // End IP address range
for _, CIDR := range CIDRs {
cidrParts := strings.Split(CIDR, "/")
ip = iPv4ToUint32(cidrParts[0])
bits, _ := strconv.ParseUint(cidrParts[1], 10, 32)
if ipS == 0 || ipS > ip {
ipS = ip
}
ip = ip | (0xFFFFFFFF >> bits)
if ipE < ip {
ipE = ip
}
}
ipStart = uInt32ToIPv4(ipS)
ipEnd = uInt32ToIPv4(ipE)
return ipStart, ipEnd, err
}
Note: only for Go developers: the algorithm can be made even more productive if you return data in the format - (IP, * IPNet, error), but for universality I return the data as a string.
Network scanner code is here: GitHub . If you put an asterisk, I will be grateful, but only if I deserve it :))
Links: