Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .github/workflows/ipconv-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ jobs:
test:
strategy:
matrix:
go-version: [1.18.x, 1.20.x, 1.22.x]
go-version: [1.22.x, 1.23.x]
os: [ubuntu-latest, macos-latest, windows-latest]
Comment thread
praserx marked this conversation as resolved.
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v4
- name: Test
run: go test ./...
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v4
- name: Test
run: go test ./...
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# v1.3.0
## 31-05-2026

1. [](#new)
* Fix crash in IPv6ToInt and IPv6ToBigInt when given nil or invalid IP addresses
* Cache To4() result in IPv4ToInt to avoid scanning twice
* Write directly to the target slice in IntToIPv6 to eliminate extra allocations
* Use big.Int.FillBytes in BigIntToIPv6 for cleaner, faster conversions
* Bump Go version to 1.22 in go.mod
* Update GitHub Actions configuration to test against Go 1.22 and 1.23
2. [](#bugfix)
* Fix typos ("addres" -> "address") in ErrNotIPv4Address and ErrNotIPv6Address messages
* Fix compiling and runtime errors in README.md example code

# v1.2.2
## 25-07-2024

Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ package main

import (
"fmt"
"net"
"github.com/praserx/ipconv"
)

func main() {
if ip, version, err := ipconv.ParseIP("192.168.1.1"); err != nil && version == 4 {
fmt.Println(ipconv.IPv4ToInt(ip))
ip, version, err := ipconv.ParseIP("192.168.1.1")
if err == nil && version == 4 {
val, _ := ipconv.IPv4ToInt(ip)
fmt.Println(val)
}
Comment on lines +19 to 22
}
```
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/praserx/ipconv

go 1.17
go 1.22
69 changes: 32 additions & 37 deletions ipconv.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,36 @@ import (
)

var ErrInvalidIPAddress = errors.New("invalid ip address")
var ErrNotIPv4Address = errors.New("not an IPv4 addres")
var ErrNotIPv6Address = errors.New("not an IPv6 addres")
var ErrNotIPv4Address = errors.New("not an IPv4 address")
var ErrNotIPv6Address = errors.New("not an IPv6 address")

// IPv4ToInt converts IP address of version 4 from net.IP to uint32
// representation.
func IPv4ToInt(ipaddr net.IP) (uint32, error) {
if ipaddr.To4() == nil {
ip4 := ipaddr.To4()
if ip4 == nil {
return 0, ErrNotIPv4Address
}
return binary.BigEndian.Uint32(ipaddr.To4()), nil
return binary.BigEndian.Uint32(ip4), nil
}

// IPv6ToInt converts IP address of version 6 from net.IP to uint64 array
// representation. Return value contains high integer value on the first
// place and low integer value on second place.
func IPv6ToInt(ipaddr net.IP) ([2]uint64, error) {
if ipaddr.To16()[0:8] == nil || ipaddr.To16()[8:16] == nil {
if ipaddr == nil {
return [2]uint64{0, 0}, ErrInvalidIPAddress
}

ip16 := ipaddr.To16()
if ip16 == nil || ip16.To4() != nil {
return [2]uint64{0, 0}, ErrNotIPv6Address
}
Comment thread
praserx marked this conversation as resolved.

// Get two separates values of integer IP
ip := [2]uint64{
binary.BigEndian.Uint64(ipaddr.To16()[0:8]), // IP high
binary.BigEndian.Uint64(ipaddr.To16()[8:16]), // IP low
binary.BigEndian.Uint64(ip16[0:8]), // IP high
binary.BigEndian.Uint64(ip16[8:16]), // IP low
}

return ip, nil
Expand All @@ -47,9 +53,14 @@ func IPv6ToBigInt(ipaddr net.IP) (*big.Int, error) {
return nil, ErrInvalidIPAddress
}

ip16 := ipaddr.To16()
if ip16 == nil || ip16.To4() != nil {
return nil, ErrNotIPv6Address
}
Comment thread
praserx marked this conversation as resolved.

// Initialize value as bytes
var ip big.Int
ip.SetBytes(ipaddr)
ip.SetBytes(ip16)

return &ip, nil
}
Expand All @@ -70,21 +81,9 @@ func IntToIPv4(ipaddr uint32) net.IP {
func IntToIPv6(high, low uint64) net.IP {
ip := make(net.IP, net.IPv6len)

// Allocate 8 bytes arrays for IPs
ipHigh := make([]byte, 8)
ipLow := make([]byte, 8)

// Proceed conversion
binary.BigEndian.PutUint64(ipHigh, high)
binary.BigEndian.PutUint64(ipLow, low)

for i := 0; i < net.IPv6len; i++ {
if i < 8 {
ip[i] = ipHigh[i]
} else if i >= 8 {
ip[i] = ipLow[i-8]
}
}
// Direct write to the target slice (no extra temporary allocations).
binary.BigEndian.PutUint64(ip[0:8], high)
binary.BigEndian.PutUint64(ip[8:16], low)

return ip
}
Expand All @@ -93,18 +92,13 @@ func IntToIPv6(high, low uint64) net.IP {
// representation.
func BigIntToIPv6(ipaddr big.Int) net.IP {
ip := make(net.IP, net.IPv6len)

ipBytes := ipaddr.Bytes()
ipBytesLen := len(ipBytes)

for i := 0; i < net.IPv6len; i++ {
if i < net.IPv6len-ipBytesLen {
ip[i] = 0x0
} else {
ip[i] = ipBytes[ipBytesLen-net.IPv6len+i]
}
if ipaddr.BitLen() > 128 {
var mask big.Int
mask.SetBit(&mask, 128, 1)
mask.Sub(&mask, big.NewInt(1))
ipaddr.And(&ipaddr, &mask)
}

ipaddr.FillBytes(ip)
return ip
Comment thread
praserx marked this conversation as resolved.
}

Expand All @@ -115,8 +109,9 @@ func ParseIP(s string) (net.IP, int, error) {
pip := net.ParseIP(s)
if pip == nil {
return nil, 0, ErrInvalidIPAddress
} else if strings.Contains(s, ".") {
return pip, 4, nil
}
return pip, 16, nil
if strings.Contains(s, ":") {
return pip, 16, nil
}
return pip, 4, nil
}
44 changes: 44 additions & 0 deletions ipconv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,36 @@ func TestIPv6ToBigInt(t *testing.T) {
}
}

func TestIPv6ToIntError(t *testing.T) {
for _, c := range []struct {
in net.IP
wantErr error
}{
{nil, ErrInvalidIPAddress},
{net.IP{1, 2, 3}, ErrNotIPv6Address},
} {
got, err := IPv6ToInt(c.in)
if err == nil || err != c.wantErr {
t.Errorf("IPv6ToInt(%q) == %v, %v, want error %v", c.in, got, err, c.wantErr)
}
}
}

func TestIPv6ToBigIntError(t *testing.T) {
for _, c := range []struct {
in net.IP
wantErr error
}{
{nil, ErrInvalidIPAddress},
{net.IP{1, 2, 3}, ErrNotIPv6Address},
} {
got, err := IPv6ToBigInt(c.in)
if err == nil || err != c.wantErr {
t.Errorf("IPv6ToBigInt(%q) == %v, %v, want error %v", c.in, got, err, c.wantErr)
}
}
}

func TestIntToIPv4(t *testing.T) {
for _, c := range []struct {
in uint32
Expand Down Expand Up @@ -160,3 +190,17 @@ func GetBigInt(bi string) *big.Int {
bigInt.SetString(bi, 10)
return bigInt
}

func BenchmarkIntToIPv6(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = IntToIPv6(2306204062558715904, 34952)
}
}

func BenchmarkBigIntToIPv6(b *testing.B) {
bigInt := GetBigInt("42540488161975842760550637899214225665")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = BigIntToIPv6(*bigInt)
}
}
Loading