更好地支持IPv6

This commit is contained in:
刘祥超
2024-04-06 09:12:17 +08:00
parent 7c5c600e31
commit e54cfa7765
22 changed files with 1237 additions and 137 deletions

216
pkg/iputils/ip.go Normal file
View File

@@ -0,0 +1,216 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package iputils
import (
"encoding/binary"
"fmt"
"math"
"math/big"
"net"
"strconv"
"sync"
)
type IP struct {
rawIP net.IP
bigInt *big.Int
}
var uint32BigInt = big.NewInt(int64(math.MaxUint32))
func ParseIP(ipString string) IP {
return NewIP(net.ParseIP(ipString))
}
func NewIP(rawIP net.IP) IP {
if rawIP == nil {
return IP{}
}
if rawIP.To4() == nil {
var bigInt = big.NewInt(0)
bigInt.SetBytes(rawIP.To16())
bigInt.Add(bigInt, uint32BigInt)
return IP{
rawIP: rawIP,
bigInt: bigInt,
}
}
return IP{
rawIP: rawIP,
}
}
func IsIPv4(ipString string) bool {
var rawIP = net.ParseIP(ipString)
return rawIP != nil && rawIP.To4() != nil
}
func IsIPv6(ipString string) bool {
var rawIP = net.ParseIP(ipString)
return rawIP != nil && rawIP.To4() == nil && rawIP.To16() != nil
}
func CompareLong(i1 string, i2 string) int {
if i1 == "" {
i1 = "0"
}
if i2 == "" {
i2 = "0"
}
var l = len(i1) - len(i2)
if l > 0 {
return 1
}
if l < 0 {
return -1
}
if i1 > i2 {
return 1
}
if i1 < i2 {
return -1
}
return 0
}
var bigIntPool = &sync.Pool{
New: func() any {
return big.NewInt(0)
},
}
func ToLong(ip string) string {
var rawIP = net.ParseIP(ip)
if rawIP == nil {
return "0"
}
var i4 = rawIP.To4()
if i4 != nil {
return strconv.FormatUint(uint64(binary.BigEndian.Uint32(i4)), 10)
}
var bigInt = bigIntPool.Get().(*big.Int)
bigInt.SetBytes(rawIP.To16())
bigInt.Add(bigInt, uint32BigInt)
var s = bigInt.String()
bigIntPool.Put(bigInt)
return s
}
func ToHex(ip string) string {
var rawIP = net.ParseIP(ip)
if rawIP == nil {
return ""
}
if rawIP.To4() != nil {
return fmt.Sprintf("%x", rawIP.To4())
}
return fmt.Sprintf("%x", rawIP.To16())
}
func ToLittleLong(ip string) string {
var rawIP = net.ParseIP(ip)
if rawIP == nil {
return "0"
}
var i4 = rawIP.To4()
if i4 != nil {
return strconv.FormatUint(uint64(binary.BigEndian.Uint32(i4)), 10)
}
var bigInt = bigIntPool.Get().(*big.Int)
bigInt.SetBytes(rawIP.To16())
var s = bigInt.String()
bigIntPool.Put(bigInt)
return s
}
func (this IP) ToLong() string {
if this.rawIP == nil {
return "0"
}
if this.bigInt != nil {
return this.bigInt.String()
}
return strconv.FormatUint(uint64(binary.BigEndian.Uint32(this.rawIP.To4())), 10)
}
func (this IP) Mod(d int) int {
if this.rawIP == nil {
return 0
}
if this.bigInt != nil {
return int(this.bigInt.Mod(this.bigInt, big.NewInt(int64(d))).Int64())
}
return int(binary.BigEndian.Uint32(this.rawIP.To4()) % uint32(d))
}
func (this IP) Compare(anotherIP IP) int {
if this.rawIP == nil {
if anotherIP.rawIP == nil {
return 0
}
return -1
} else if anotherIP.rawIP == nil {
return 1
}
if this.bigInt != nil {
if anotherIP.bigInt == nil {
return 1 // IPv6 always greater than IPv4
}
return this.bigInt.Cmp(anotherIP.bigInt)
}
if anotherIP.bigInt == nil {
var i1 = binary.BigEndian.Uint32(this.rawIP.To4())
var i2 = binary.BigEndian.Uint32(anotherIP.rawIP.To4())
if i1 > i2 {
return 1
}
if i1 < i2 {
return -1
}
return 0
}
return -1
}
func (this IP) Between(ipFrom IP, ipTo IP) bool {
return ipFrom.Compare(this) <= 0 && ipTo.Compare(this) >= 0
}
func (this IP) IsIPv4() bool {
return this.rawIP != nil && this.bigInt == nil
}
func (this IP) IsIPv6() bool {
return this.bigInt != nil
}
func (this IP) IsValid() bool {
return this.rawIP != nil
}
func (this IP) Raw() net.IP {
return this.rawIP
}
func (this IP) String() string {
if this.rawIP == nil {
return ""
}
return this.rawIP.String()
}

230
pkg/iputils/ip_test.go Normal file
View File

@@ -0,0 +1,230 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package iputils_test
import (
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/iwind/TeaGo/assert"
"runtime"
"testing"
)
func TestIP_ParseIP(t *testing.T) {
var a = assert.NewAssertion(t)
{
var i = iputils.ParseIP("127.0.0.1")
a.IsTrue(i.IsIPv4())
a.IsFalse(i.IsIPv6())
a.IsTrue(i.IsValid())
a.IsTrue(iputils.IsIPv4("127.0.0.1"))
a.IsFalse(iputils.IsIPv6("127.0.0.1"))
t.Log(i.String(), i.ToLong())
t.Log("raw:", i.Raw())
}
{
var i = iputils.ParseIP("0.0.0.1")
a.IsTrue(i.IsIPv4())
a.IsFalse(i.IsIPv6())
t.Log(i.String(), i.ToLong())
}
for j := 0; j < 3; j++ /** repeat test **/ {
var i = iputils.ParseIP("::1")
a.IsFalse(i.IsIPv4())
a.IsTrue(i.IsIPv6())
a.IsTrue(i.IsValid())
t.Log(i.String(), i.ToLong())
}
{
{
var i = iputils.ParseIP("2001:db8:0:1::1:101")
t.Log(i.String(), i.ToLong())
a.IsFalse(i.IsIPv4())
a.IsTrue(i.IsIPv6())
a.IsFalse(iputils.IsIPv4("2001:db8:0:1::1:101"))
a.IsTrue(iputils.IsIPv6("2001:db8:0:1::1:101"))
a.IsTrue(i.IsValid())
}
{
var i = iputils.ParseIP("2001:db8:0:1::1:102")
t.Log(i.String(), i.ToLong())
a.IsFalse(i.IsIPv4())
a.IsTrue(i.IsIPv6())
a.IsTrue(i.IsValid())
}
{
var i = iputils.ParseIP("2001:db8:0:1::2:101")
t.Log(i.String(), i.ToLong())
a.IsFalse(i.IsIPv4())
a.IsTrue(i.IsIPv6())
a.IsTrue(i.IsValid())
}
}
{
var i = iputils.ParseIP("WRONG IP")
t.Log(i.String(), i.ToLong())
a.IsFalse(i.IsIPv4())
a.IsFalse(i.IsIPv6())
a.IsFalse(i.IsValid())
a.IsFalse(iputils.IsIPv4("WRONG IP"))
a.IsFalse(iputils.IsIPv6("WRONG IP"))
}
}
func TestIP_Mod(t *testing.T) {
for _, ip := range []string{
"127.0.0.1",
"::1",
"2001:db8:0:1::1:101",
"2001:db8:0:1::1:102",
"WRONG IP",
} {
var i = iputils.ParseIP(ip)
t.Log(ip, "=>", i.ToLong(), "=>", i.Mod(5))
}
}
func TestIP_Compare(t *testing.T) {
var a = assert.NewAssertion(t)
{
var i1 = iputils.ParseIP("127.0.0.1")
var i2 = iputils.ParseIP("127.0.0.1")
a.IsTrue(i1.Compare(i2) == 0)
}
{
var i1 = iputils.ParseIP("127.0.0.1")
var i2 = iputils.ParseIP("127.0.0.2")
a.IsTrue(i1.Compare(i2) == -1)
}
{
var i1 = iputils.ParseIP("127.0.0.2")
var i2 = iputils.ParseIP("127.0.0.1")
a.IsTrue(i1.Compare(i2) == 1)
}
{
var i1 = iputils.ParseIP("2001:db8:0:1::101")
var i2 = iputils.ParseIP("127.0.0.1")
a.IsTrue(i1.Compare(i2) == 1)
}
{
var i1 = iputils.ParseIP("127.0.0.1")
var i2 = iputils.ParseIP("2001:db8:0:1::101")
a.IsTrue(i1.Compare(i2) == -1)
}
{
var i1 = iputils.ParseIP("2001:db8:0:1::101")
var i2 = iputils.ParseIP("2001:db8:0:1::101")
a.IsTrue(i1.Compare(i2) == 0)
}
{
var i1 = iputils.ParseIP("2001:db8:0:1::101")
var i2 = iputils.ParseIP("2001:db8:0:1::102")
a.IsTrue(i1.Compare(i2) == -1)
}
{
var i1 = iputils.ParseIP("2001:db8:0:1::102")
var i2 = iputils.ParseIP("2001:db8:0:1::101")
a.IsTrue(i1.Compare(i2) == 1)
}
{
var i1 = iputils.ParseIP("2001:db8:0:1::2:100")
var i2 = iputils.ParseIP("2001:db8:0:1::1:101")
a.IsTrue(i1.Compare(i2) == 1)
}
}
func TestIP_Between(t *testing.T) {
var a = assert.NewAssertion(t)
a.IsTrue(iputils.ParseIP("127.0.0.2").Between(iputils.ParseIP("127.0.0.1"), iputils.ParseIP("127.0.0.3")))
a.IsTrue(iputils.ParseIP("127.0.0.1").Between(iputils.ParseIP("127.0.0.1"), iputils.ParseIP("127.0.0.3")))
a.IsFalse(iputils.ParseIP("127.0.0.2").Between(iputils.ParseIP("127.0.0.3"), iputils.ParseIP("127.0.0.4")))
a.IsFalse(iputils.ParseIP("127.0.0.5").Between(iputils.ParseIP("127.0.0.3"), iputils.ParseIP("127.0.0.4")))
a.IsFalse(iputils.ParseIP("127.0.0.2").Between(iputils.ParseIP("127.0.0.3"), iputils.ParseIP("127.0.0.1")))
}
func TestIP_ToLong(t *testing.T) {
for _, ip := range []string{
"127.0.0.1",
"192.168.1.100",
"::1",
"fd00:6868:6868:0:10ac:d056:3bf6:7452",
"fd00:6868:6868:0:10ac:d056:3bf6:7453",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"2001:db8:0:1::101",
"2001:db8:0:2::101",
"wrong ip",
} {
var goIP = iputils.ParseIP(ip)
t.Log(ip, "=>", "\n", goIP.String(), "\n", "=>", "\n", "long1:", goIP.ToLong(), "\n", "long2:", iputils.ToLong(ip), "\n", "little long:", iputils.ToLittleLong(ip))
}
}
func TestIP_CompareLong(t *testing.T) {
var a = assert.NewAssertion(t)
a.IsTrue(iputils.CompareLong("1", "2") == -1)
a.IsTrue(iputils.CompareLong("11", "2") == 1)
a.IsTrue(iputils.CompareLong("11", "22") == -1)
a.IsTrue(iputils.CompareLong("22", "101") == -1)
a.IsTrue(iputils.CompareLong("33", "22") == 1)
a.IsTrue(iputils.CompareLong("101", "22") == 1)
a.IsTrue(iputils.CompareLong("22", "22") == 0)
}
func TestIP_Memory(t *testing.T) {
var list []iputils.IP
var stat1 = &runtime.MemStats{}
runtime.ReadMemStats(stat1)
for i := 0; i < 1_000_000; i++ {
list = append(list, iputils.ParseIP("fd00:6868:6868:0:10ac:d056:3bf6:7452"))
}
//runtime.GC()
var stat2 = &runtime.MemStats{}
runtime.ReadMemStats(stat2)
t.Log((stat2.Alloc-stat1.Alloc)>>10, "KB", (stat2.HeapInuse-stat1.HeapInuse)>>10, "KB")
// hold the memory
for _, v := range list {
_ = v
}
}
func BenchmarkParse(b *testing.B) {
for i := 0; i < b.N; i++ {
iputils.ParseIP("fd00:6868:6868:0:10ac:d056:3bf6:7452")
}
}
func BenchmarkToLongV4(b *testing.B) {
for i := 0; i < b.N; i++ {
iputils.ToLong("192.168.2.100")
}
}
func BenchmarkToLongV6(b *testing.B) {
runtime.GOMAXPROCS(1)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
iputils.ToLong("fd00:6868:6868:0:10ac:d056:3bf6:7452")
}
}