Parsing IPv4 Addresses
Parsing IPv4 Address and comparing different approches
November 09 2024The simplest way to parse or check if an IPv4 address is valid is to use regex
which is not efficient.
You can find many different regex patterns for checking if a ipv4 address is valid.
With that thought I decided to do it myself without using regex.
IPv4 Address
An IPv4 address is a 32-bit number that is written in the form of four 8-bit numbers separated by dots.
For example, 192.168.0.1
The 32 bits are divided into four 8-bit numbers called octets.
A valid IPv4 address has the following format:
<octet>.<octet>.<octet>.<octet>
Where each octet is a number between 0 and 255.
Ground rules
- Octet cannot start with leading
0
unless its the only digit in the octet. e.g.192.168.000.001
is not a valid IPv4 address.192.168.0.001
is not a valid IPv4 address.192.168.0.1
is a valid IPv4 address.
I will be using Golang throughout this blog
Using Split Method
Basic length check.
if len(ip) < 7 || len(ip) > 15 {
return errors.New("invalid IP")
}
Split the IP by .
and check if there are 4 octets only.
octets := strings.Split(ip, ".")
if len(octets) != 4 {
return errors.New("invalid IP")
}
Check if each octet is valid.
for _, octet := range octets {
// 01, 012 are not a valid octet of ip address
// 0 is a valid octet of IP address
if (len(octet) > 1 && octet[0] == '0') || (len(octet) < 1 || len(octet) > 3) {
return errors.New("invalid IP")
}
n, err := strconv.ParseInt(octet, 10, 0)
if err != nil {
return errors.New("invalid IP")
}
if n < 0 || n > 255 {
return errors.New("invalid IP")
}
}
Full code
func isValidIP_Split(ip string) error {
// Basic check
if len(ip) < 7 || len(ip) > 15 {
return errors.New("invalid IP")
}
octets := strings.Split(ip, ".")
// Should only have 4 octets
if len(octets) != 4 {
return errors.New("invalid IP")
}
for _, octet := range octets {
if (len(octet) > 1 && octet[0] == '0') || (len(octet) < 1 || len(octet) > 3) {
return errors.New("invalid IP")
}
n, err := strconv.ParseInt(octet, 10, 0)
if err != nil {
return errors.New("invalid IP")
}
if n < 0 || n > 255 {
return errors.New("invalid IP")
}
}
return nil
}
Using Single Pass Method
In this approach we are trying to do all the checks in single pass.
First step is to check if the IP is valid by checking the length of the IP.
if len(ip) < 7 || len(ip) > 15 {
return errors.New("invalid IP")
}
For each character in the IP we check if its a valid digit
or .
If the character is a valid digit form a octet integer by ascii manipulation and maths.
Get the ascii code of the character subtract ascii code of
0
(48) which gives the exact value of the digit E.g.1
is49
in ascii code | 49 - 48 = 1E.g.
9
is57
in ascii code | 57 - 48 = 9
In each step we multiply the octet integer by 10 and add the calculated digit to it
E.g. Converting ‘123’ to integer
int_val = 0
int_val = int_val * 10 + 1 // val = 1
int_val = int_val * 10 + 2 // val = 12
int_val = int_val * 10 + 3 // val = 123
// Must be a valid numeric digit
if ip[i] < '0' || ip[i] > '9' {
return errors.New("invalid IP")
}
// .001, .01 -> Invalid Octet
// .0 -> Valid Octet
if octetLen == 0 && ip[i] == '0' && i+1 < end && ip[i+1] != '.' {
return errors.New("invalid IP")
}
// Basic logic to convert string to int
octet = octet*10 + int(ip[i]-'0')
octetLen += 1
// A octet length check to avoid over calcuating as its an invalid IP
if octetLen > 3 {
return errors.New("invalid Ip")
}
If a character is .
or end of the string then we check
- if the octet length is valid
- if the octet value is valid
- increment octets count
- reset octet length and octet value
- if end of the string then break else continue
if end == i || ip[i] == '.' {
octets += 1
if octetLen < 1 || octetLen > 3 {
// Invalid octet length
return errors.New("invalid IP")
}
if octet < 0 || octet > 255 {
// Invalid octet value
return errors.New("invalid IP")
}
if i == end {
break
}
octetLen = 0
octet = 0
continue // No further processing as '.'
}
Full code
func isValidIP_SinglePass(ip string) error {
if len(ip) < 7 || len(ip) > 15 {
return errors.New("invalid IP")
}
end := len(ip)
octets := 0
octet := 0
octetLen := 0
for i := 0; octets <= 4; i++ {
if end == i || ip[i] == '.' {
octets += 1
if octetLen < 1 || octetLen > 3 {
return errors.New("invalid IP")
}
if octet < 0 || octet > 255 {
return errors.New("invalid IP")
}
if i == end {
break
}
octetLen = 0
octet = 0
continue
}
if ip[i] < '0' || ip[i] > '9' {
return errors.New("invalid IP")
}
if octetLen == 0 && ip[i] == '0' && i+1 < end && ip[i+1] != '.' {
return errors.New("invalid IP")
}
octet = octet*10 + int(ip[i]-'0')
octetLen += 1
if octetLen > 3 {
return errors.New("invalid Ip")
}
}
if octets != 4 {
return errors.New("invalid IP")
}
return nil
}
Built In
net
package has a function ParseIP
that validates if a provided string is a valid ip (ipv4/ipv6)
ip := net.ParseIP("192.168.0.1")
Regex
Well there are multiple regexes the one I have used is
^((25[0-5]|(2[0-4]|1d|[1-9]|)d).?){4}$
Conclusion & Benchmark
cpu: AMD Ryzen 5 4600H with Radeon Graphics
BenchmarkIPParser_DefaultGateway_Naive-12 6722538 220.6 ns/op
BenchmarkIPParser_DefaultGateway_SinglePass-12 53629252 24.31 ns/op
BenchmarkIPParser_DefaultGateway_BuiltIn-12 41368957 26.63 ns/op
BenchmarkIPParser_DefaultGateway_Regex-12 2104970 542.4 ns/op
BenchmarkIPParser_BigString_Naive-12 29841490 46.53 ns/op
BenchmarkIPParser_BigString_SinglePass-12 22988731 49.51 ns/op
BenchmarkIPParser_BigString_BuiltIn-12 11793058 119.3 ns/op
BenchmarkIPParser_BigString_Regex-12 8999227 140.0 ns/op
BenchmarkIPParser_ValidLengthNumber_Naive-12 12907652 108.2 ns/op
BenchmarkIPParser_ValidLengthNumber_SinglePass-12 20369660 54.73 ns/op
BenchmarkIPParser_ValidLengthNumber_BuiltIn-12 15580508 87.94 ns/op
BenchmarkIPParser_ValidLengthNumber_Regex-12 6436222 186.0 ns/op
BenchmarkIPParser_MaxValid_Naive-12 5839047 241.5 ns/op
BenchmarkIPParser_MaxValid_SinglePass-12 38969929 31.07 ns/op
BenchmarkIPParser_MaxValid_BuiltIn-12 28567866 35.58 ns/op
BenchmarkIPParser_MaxValid_Regex-12 3489681 401.8 ns/op
PASS
As you can see the built in and single pass methods are the fastest, followed by the naive method. As expected, the regex is the slowest.
Well just use the built in net.ParseIP
method, it’s fast and also handles ipv6 addresses.
If you are a graph enjoyer
- BigString = “aslduasodusadiusoaiudowaiuoiuasdlsakjdlsakhdaklsjhdajsdkajsgdkasdk”
- DefaultGateway = “192.168.0.1”
- MaxValid = “255.255.255.255”
- ValidLengthNumber = “000000000”