Skip to content

Commit

Permalink
perf: improve IsIPv4() and IsIPv6()
Browse files Browse the repository at this point in the history
  • Loading branch information
fufuok committed Mar 18, 2024
1 parent af4800e commit 6a69b5a
Show file tree
Hide file tree
Showing 2 changed files with 237 additions and 4 deletions.
140 changes: 136 additions & 4 deletions ip.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,145 @@ func IsIP(ip string) bool {
}

// IsIPv4 判断是否为合法 IPv4
func IsIPv4(ip string) bool {
return ParseIPv4(ip) != nil
// 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.
// Ref: gofiber/utils
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 || (ci > 1 && s[0] == '0') {
return false
}

s = s[ci:]
}

return len(s) == 0
}

// IsIPv6 判断是否为合法 IPv6
func IsIPv6(ip string) bool {
return ParseIPv6(ip) != nil
// 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.
// Ref: gofiber/utils
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
}

// ParseIPv4 判断是否为合法 IPv4 并解析
Expand Down
101 changes: 101 additions & 0 deletions ip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,104 @@ func TestParseIPx(t *testing.T) {
assert.Equal(t, v.isIPv6, isIPv6)
}
}

// Ref: gofiber/utils
func Test_IsIPv4(t *testing.T) {
t.Parallel()

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

assert.Equal(t, false, IsIPv4("255.255.255.256"))
assert.Equal(t, false, IsIPv4(".0.0.0"))
assert.Equal(t, false, IsIPv4("0.0.0."))
assert.Equal(t, false, IsIPv4("0.0.0"))
assert.Equal(t, false, IsIPv4(".0.0.0."))
assert.Equal(t, false, IsIPv4("0.0.0.0.0"))
assert.Equal(t, false, IsIPv4("0"))
assert.Equal(t, false, IsIPv4(""))
assert.Equal(t, false, IsIPv4("2345:0425:2CA1::0567:5673:23b5"))
assert.Equal(t, false, IsIPv4("invalid"))
assert.Equal(t, false, IsIPv4("189.12.34.260"))
assert.Equal(t, false, IsIPv4("189.12.260.260"))
assert.Equal(t, false, IsIPv4("189.260.260.260"))
assert.Equal(t, false, IsIPv4("999.999.999.999"))
assert.Equal(t, false, IsIPv4("9999.9999.9999.9999"))
}

// Ref: gofiber/utils
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)
}
assert.Equal(b, true, res)
})

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

// Ref: gofiber/utils
func Test_IsIPv6(t *testing.T) {
t.Parallel()

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

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

// Ref: gofiber/utils
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)
}
assert.Equal(b, true, res)
})

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

// go test -run=^$ -benchmem -benchtime=1s -count=2 -bench=Benchmark_IsIP
// goos: linux
// goarch: amd64
// pkg: github.com/fufuok/utils
// cpu: AMD Ryzen 7 5700G with Radeon Graphics
// Benchmark_IsIPv4/fiber-16 88282953 13.25 ns/op 0 B/op 0 allocs/op
// Benchmark_IsIPv4/fiber-16 90268933 13.23 ns/op 0 B/op 0 allocs/op
// Benchmark_IsIPv4/default-16 45390993 25.53 ns/op 0 B/op 0 allocs/op
// Benchmark_IsIPv4/default-16 46329868 25.49 ns/op 0 B/op 0 allocs/op
// Benchmark_IsIPv6/fiber-16 27761413 44.99 ns/op 0 B/op 0 allocs/op
// Benchmark_IsIPv6/fiber-16 27327100 43.97 ns/op 0 B/op 0 allocs/op
// Benchmark_IsIPv6/default-16 21168326 57.92 ns/op 0 B/op 0 allocs/op
// Benchmark_IsIPv6/default-16 20407336 57.98 ns/op 0 B/op 0 allocs/op

0 comments on commit 6a69b5a

Please sign in to comment.