Skip to content

Commit 5482b75

Browse files
committed
IsIPv4/v6 util
1 parent 77200da commit 5482b75

File tree

2 files changed

+232
-0
lines changed

2 files changed

+232
-0
lines changed

ips.go

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

ips_test.go

+87
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)