Skip to content

Commit

Permalink
Merge pull request #32 from sadfun/master
Browse files Browse the repository at this point in the history
🔥 Add IsIPv4/v6 util
  • Loading branch information
jozsefsallai authored Oct 17, 2022
2 parents 77200da + 74a0299 commit 8898e89
Show file tree
Hide file tree
Showing 2 changed files with 228 additions and 0 deletions.
141 changes: 141 additions & 0 deletions ips.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package utils

import "net"

// IsIPv4 works the same way as net.ParseIP,
// but without check for IPv6 case and without returning net.IP slice, whereby IsIPv4 makes no allocations.
func IsIPv4(s string) bool {
for i := 0; i < net.IPv4len; i++ {
if len(s) == 0 {
return false
}

if i > 0 {
if s[0] != '.' {
return false
}
s = s[1:]
}

n, ci := 0, 0

for ci = 0; ci < len(s) && '0' <= s[ci] && s[ci] <= '9'; ci++ {
n = n*10 + int(s[ci]-'0')
if n >= 0xFF {
return false
}
}

if ci == 0 || n > 0xFF || (ci > 1 && s[0] == '0') {
return false
}

s = s[ci:]
}

return len(s) == 0
}

// IsIPv6 works the same way as net.ParseIP,
// but without check for IPv4 case and without returning net.IP slice, whereby IsIPv6 makes no allocations.
func IsIPv6(s string) bool {
ellipsis := -1 // position of ellipsis in ip

// Might have leading ellipsis
if len(s) >= 2 && s[0] == ':' && s[1] == ':' {
ellipsis = 0
s = s[2:]
// Might be only ellipsis
if len(s) == 0 {
return true
}
}

// Loop, parsing hex numbers followed by colon.
i := 0
for i < net.IPv6len {
// Hex number.
n, ci := 0, 0

for ci = 0; ci < len(s); ci++ {
if '0' <= s[ci] && s[ci] <= '9' {
n *= 16
n += int(s[ci] - '0')
} else if 'a' <= s[ci] && s[ci] <= 'f' {
n *= 16
n += int(s[ci]-'a') + 10
} else if 'A' <= s[ci] && s[ci] <= 'F' {
n *= 16
n += int(s[ci]-'A') + 10
} else {
break
}
if n > 0xFFFF {
return false
}
}
if ci == 0 || n > 0xFFFF {
return false
}

if ci < len(s) && s[ci] == '.' {
if ellipsis < 0 && i != net.IPv6len-net.IPv4len {
return false
}
if i+net.IPv4len > net.IPv6len {
return false
}

if !IsIPv4(s) {
return false
}

s = ""
i += net.IPv4len
break
}

// Save this 16-bit chunk.
i += 2

// Stop at end of string.
s = s[ci:]
if len(s) == 0 {
break
}

// Otherwise must be followed by colon and more.
if s[0] != ':' || len(s) == 1 {
return false
}
s = s[1:]

// Look for ellipsis.
if s[0] == ':' {
if ellipsis >= 0 { // already have one
return false
}
ellipsis = i
s = s[1:]
if len(s) == 0 { // can be at end
break
}
}
}

// Must have used entire string.
if len(s) != 0 {
return false
}

// If didn't parse enough, expand ellipsis.
if i < net.IPv6len {
if ellipsis < 0 {
return false
}
} else if ellipsis >= 0 {
// Ellipsis must represent at least one 0 group.
return false
}
return true
}
87 changes: 87 additions & 0 deletions ips_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
// 🤖 Github Repository: https://github.com/gofiber/fiber
// 📌 API Documentation: https://docs.gofiber.io

package utils

import (
"net"
"testing"

"github.com/stretchr/testify/require"
)

func Test_IsIPv4(t *testing.T) {
t.Parallel()

require.Equal(t, true, IsIPv4("174.23.33.100"))
require.Equal(t, true, IsIPv4("127.0.0.1"))
require.Equal(t, true, IsIPv4("0.0.0.0"))

require.Equal(t, false, IsIPv4(".0.0.0"))
require.Equal(t, false, IsIPv4("0.0.0."))
require.Equal(t, false, IsIPv4("0.0.0"))
require.Equal(t, false, IsIPv4(".0.0.0."))
require.Equal(t, false, IsIPv4("0.0.0.0.0"))
require.Equal(t, false, IsIPv4("0"))
require.Equal(t, false, IsIPv4(""))
require.Equal(t, false, IsIPv4("2345:0425:2CA1::0567:5673:23b5"))
require.Equal(t, false, IsIPv4("invalid"))
}

// go test -v -run=^$ -bench=UnsafeString -benchmem -count=2

func Benchmark_IsIPv4(b *testing.B) {
ip := "174.23.33.100"
var res bool

b.Run("fiber", func(b *testing.B) {
for n := 0; n < b.N; n++ {
res = IsIPv4(ip)
}
require.Equal(b, true, res)
})

b.Run("default", func(b *testing.B) {
for n := 0; n < b.N; n++ {
res = net.ParseIP(ip) != nil
}
require.Equal(b, true, res)
})
}

func Test_IsIPv6(t *testing.T) {
t.Parallel()

require.Equal(t, true, IsIPv6("9396:9549:b4f7:8ed0:4791:1330:8c06:e62d"))
require.Equal(t, true, IsIPv6("2345:0425:2CA1::0567:5673:23b5"))
require.Equal(t, true, IsIPv6("2001:1:2:3:4:5:6:7"))

require.Equal(t, false, IsIPv6("1.1.1.1"))
require.Equal(t, false, IsIPv6("2001:1:2:3:4:5:6:"))
require.Equal(t, false, IsIPv6(":1:2:3:4:5:6:"))
require.Equal(t, false, IsIPv6("1:2:3:4:5:6:"))
require.Equal(t, false, IsIPv6(""))
require.Equal(t, false, IsIPv6("invalid"))
}

// go test -v -run=^$ -bench=UnsafeString -benchmem -count=2

func Benchmark_IsIPv6(b *testing.B) {
ip := "9396:9549:b4f7:8ed0:4791:1330:8c06:e62d"
var res bool

b.Run("fiber", func(b *testing.B) {
for n := 0; n < b.N; n++ {
res = IsIPv6(ip)
}
require.Equal(b, true, res)
})

b.Run("default", func(b *testing.B) {
for n := 0; n < b.N; n++ {
res = net.ParseIP(ip) != nil
}
require.Equal(b, true, res)
})
}

0 comments on commit 8898e89

Please sign in to comment.