Skip to content

Commit 8898e89

Browse files
authored
Merge pull request #32 from sadfun/master
🔥 Add IsIPv4/v6 util
2 parents 77200da + 74a0299 commit 8898e89

File tree

2 files changed

+228
-0
lines changed

2 files changed

+228
-0
lines changed

ips.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package utils
2+
3+
import "net"
4+
5+
// IsIPv4 works the same way as net.ParseIP,
6+
// but without check for IPv6 case and without returning net.IP slice, whereby IsIPv4 makes no allocations.
7+
func IsIPv4(s string) bool {
8+
for i := 0; i < net.IPv4len; i++ {
9+
if len(s) == 0 {
10+
return false
11+
}
12+
13+
if i > 0 {
14+
if s[0] != '.' {
15+
return false
16+
}
17+
s = s[1:]
18+
}
19+
20+
n, ci := 0, 0
21+
22+
for ci = 0; ci < len(s) && '0' <= s[ci] && s[ci] <= '9'; ci++ {
23+
n = n*10 + int(s[ci]-'0')
24+
if n >= 0xFF {
25+
return false
26+
}
27+
}
28+
29+
if ci == 0 || n > 0xFF || (ci > 1 && s[0] == '0') {
30+
return false
31+
}
32+
33+
s = s[ci:]
34+
}
35+
36+
return len(s) == 0
37+
}
38+
39+
// IsIPv6 works the same way as net.ParseIP,
40+
// but without check for IPv4 case and without returning net.IP slice, whereby IsIPv6 makes no allocations.
41+
func IsIPv6(s string) bool {
42+
ellipsis := -1 // position of ellipsis in ip
43+
44+
// Might have leading ellipsis
45+
if len(s) >= 2 && s[0] == ':' && s[1] == ':' {
46+
ellipsis = 0
47+
s = s[2:]
48+
// Might be only ellipsis
49+
if len(s) == 0 {
50+
return true
51+
}
52+
}
53+
54+
// Loop, parsing hex numbers followed by colon.
55+
i := 0
56+
for i < net.IPv6len {
57+
// Hex number.
58+
n, ci := 0, 0
59+
60+
for ci = 0; ci < len(s); ci++ {
61+
if '0' <= s[ci] && s[ci] <= '9' {
62+
n *= 16
63+
n += int(s[ci] - '0')
64+
} else if 'a' <= s[ci] && s[ci] <= 'f' {
65+
n *= 16
66+
n += int(s[ci]-'a') + 10
67+
} else if 'A' <= s[ci] && s[ci] <= 'F' {
68+
n *= 16
69+
n += int(s[ci]-'A') + 10
70+
} else {
71+
break
72+
}
73+
if n > 0xFFFF {
74+
return false
75+
}
76+
}
77+
if ci == 0 || n > 0xFFFF {
78+
return false
79+
}
80+
81+
if ci < len(s) && s[ci] == '.' {
82+
if ellipsis < 0 && i != net.IPv6len-net.IPv4len {
83+
return false
84+
}
85+
if i+net.IPv4len > net.IPv6len {
86+
return false
87+
}
88+
89+
if !IsIPv4(s) {
90+
return false
91+
}
92+
93+
s = ""
94+
i += net.IPv4len
95+
break
96+
}
97+
98+
// Save this 16-bit chunk.
99+
i += 2
100+
101+
// Stop at end of string.
102+
s = s[ci:]
103+
if len(s) == 0 {
104+
break
105+
}
106+
107+
// Otherwise must be followed by colon and more.
108+
if s[0] != ':' || len(s) == 1 {
109+
return false
110+
}
111+
s = s[1:]
112+
113+
// Look for ellipsis.
114+
if s[0] == ':' {
115+
if ellipsis >= 0 { // already have one
116+
return false
117+
}
118+
ellipsis = i
119+
s = s[1:]
120+
if len(s) == 0 { // can be at end
121+
break
122+
}
123+
}
124+
}
125+
126+
// Must have used entire string.
127+
if len(s) != 0 {
128+
return false
129+
}
130+
131+
// If didn't parse enough, expand ellipsis.
132+
if i < net.IPv6len {
133+
if ellipsis < 0 {
134+
return false
135+
}
136+
} else if ellipsis >= 0 {
137+
// Ellipsis must represent at least one 0 group.
138+
return false
139+
}
140+
return true
141+
}

ips_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
2+
// 🤖 Github Repository: https://github.com/gofiber/fiber
3+
// 📌 API Documentation: https://docs.gofiber.io
4+
5+
package utils
6+
7+
import (
8+
"net"
9+
"testing"
10+
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func Test_IsIPv4(t *testing.T) {
15+
t.Parallel()
16+
17+
require.Equal(t, true, IsIPv4("174.23.33.100"))
18+
require.Equal(t, true, IsIPv4("127.0.0.1"))
19+
require.Equal(t, true, IsIPv4("0.0.0.0"))
20+
21+
require.Equal(t, false, IsIPv4(".0.0.0"))
22+
require.Equal(t, false, IsIPv4("0.0.0."))
23+
require.Equal(t, false, IsIPv4("0.0.0"))
24+
require.Equal(t, false, IsIPv4(".0.0.0."))
25+
require.Equal(t, false, IsIPv4("0.0.0.0.0"))
26+
require.Equal(t, false, IsIPv4("0"))
27+
require.Equal(t, false, IsIPv4(""))
28+
require.Equal(t, false, IsIPv4("2345:0425:2CA1::0567:5673:23b5"))
29+
require.Equal(t, false, IsIPv4("invalid"))
30+
}
31+
32+
// go test -v -run=^$ -bench=UnsafeString -benchmem -count=2
33+
34+
func Benchmark_IsIPv4(b *testing.B) {
35+
ip := "174.23.33.100"
36+
var res bool
37+
38+
b.Run("fiber", func(b *testing.B) {
39+
for n := 0; n < b.N; n++ {
40+
res = IsIPv4(ip)
41+
}
42+
require.Equal(b, true, res)
43+
})
44+
45+
b.Run("default", func(b *testing.B) {
46+
for n := 0; n < b.N; n++ {
47+
res = net.ParseIP(ip) != nil
48+
}
49+
require.Equal(b, true, res)
50+
})
51+
}
52+
53+
func Test_IsIPv6(t *testing.T) {
54+
t.Parallel()
55+
56+
require.Equal(t, true, IsIPv6("9396:9549:b4f7:8ed0:4791:1330:8c06:e62d"))
57+
require.Equal(t, true, IsIPv6("2345:0425:2CA1::0567:5673:23b5"))
58+
require.Equal(t, true, IsIPv6("2001:1:2:3:4:5:6:7"))
59+
60+
require.Equal(t, false, IsIPv6("1.1.1.1"))
61+
require.Equal(t, false, IsIPv6("2001:1:2:3:4:5:6:"))
62+
require.Equal(t, false, IsIPv6(":1:2:3:4:5:6:"))
63+
require.Equal(t, false, IsIPv6("1:2:3:4:5:6:"))
64+
require.Equal(t, false, IsIPv6(""))
65+
require.Equal(t, false, IsIPv6("invalid"))
66+
}
67+
68+
// go test -v -run=^$ -bench=UnsafeString -benchmem -count=2
69+
70+
func Benchmark_IsIPv6(b *testing.B) {
71+
ip := "9396:9549:b4f7:8ed0:4791:1330:8c06:e62d"
72+
var res bool
73+
74+
b.Run("fiber", func(b *testing.B) {
75+
for n := 0; n < b.N; n++ {
76+
res = IsIPv6(ip)
77+
}
78+
require.Equal(b, true, res)
79+
})
80+
81+
b.Run("default", func(b *testing.B) {
82+
for n := 0; n < b.N; n++ {
83+
res = net.ParseIP(ip) != nil
84+
}
85+
require.Equal(b, true, res)
86+
})
87+
}

0 commit comments

Comments
 (0)