Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: c-robinson/iplib
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.0.8
Choose a base ref
...
head repository: c-robinson/iplib
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v2.0.0
Choose a head ref
  • 7 commits
  • 20 files changed
  • 3 contributors

Commits on Dec 24, 2023

  1. V2 uint128 (#15)

    * Change from *big.Int to uint128.Uint128
    
    Migrate all IPv6 arithmatic operations from `*big.Int`, which is sloooooooow,
    `lukechampine.com/uint128`. This has lead to the following improvements:
    
    | Benchmark | *big.Int | Uint128 |
    | --- | --- | --- |
    | Benchmark_DeltaIP6 | 79.27 ns/op | 2.809 ns/op |
    | BenchmarkDecrementIP6By | 50.54 ns/op | 13.88 ns/op |
    | BenchmarkIncrementIP6By | 50.48 ns/op | 13.92 ns/op |
    | BenchmarkNet_Count6 | 122.2 ns/op | 11.26 ns/op |
    
    * add subnet_v6 benchmark
    
    * fix func docs to reflect improved v6 times
    
    * update README for v2
    
    * fix longstanding typo
    
    * avoid index out of bounds in IP4ToARPA function (#14)
    
    * Fix edge case in DecrementIP6WithinHostmask
    
     - Fix a problem where DecrementIP6WithinHostmask could panic if
       the hostmask had an underflow
    
     - Added a benchmark for subnetting IPv6
    
    * Change from *big.Int to uint128.Uint128
    
    Migrate all IPv6 arithmatic operations from `*big.Int`, which is sloooooooow,
    `lukechampine.com/uint128`. This has lead to the following improvements:
    
    | Benchmark | *big.Int | Uint128 |
    | --- | --- | --- |
    | Benchmark_DeltaIP6 | 79.27 ns/op | 2.809 ns/op |
    | BenchmarkDecrementIP6By | 50.54 ns/op | 13.88 ns/op |
    | BenchmarkIncrementIP6By | 50.48 ns/op | 13.92 ns/op |
    | BenchmarkNet_Count6 | 122.2 ns/op | 11.26 ns/op |
    
    * add subnet_v6 benchmark
    
    ---------
    
    Co-authored-by: pikachu <40382944+pic4xiu@users.noreply.github.com>
    c-robinson and pic4xiu authored Dec 24, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    90df905 View commit details

Commits on Dec 26, 2023

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a40c9ce View commit details

Commits on Dec 27, 2023

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d68a6c8 View commit details
  2. add go.sum

    c-robinson committed Dec 27, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    f20274e View commit details
  3. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    089e210 View commit details
  4. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    e95463c View commit details
  5. remove indirect

    c-robinson committed Dec 27, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    7dd8551 View commit details
Showing with 384 additions and 204 deletions.
  1. +24 −3 README.md
  2. +24 −6 example_test.go
  3. +4 −2 go.mod
  4. +2 −0 go.sum
  5. +87 −74 hostmask.go
  6. +27 −28 hostmask_test.go
  7. +5 −0 iana/go.mod
  8. +1 −0 iana/go.sum
  9. +1 −1 iana/iana.go
  10. +1 −1 iana/iana_test.go
  11. +5 −0 iid/go.mod
  12. +1 −0 iid/go.sum
  13. +1 −1 iid/iid.go
  14. +1 −1 iid/iid_test.go
  15. +97 −31 iplib.go
  16. +43 −27 iplib_bench_test.go
  17. +35 −6 iplib_test.go
  18. +3 −4 net4.go
  19. +18 −15 net6.go
  20. +4 −4 net6_test.go
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
# IPLib
# IPLib
[![Documentation](https://godoc.org/github.com/c-robinson/iplib?status.svg)](http://godoc.org/github.com/c-robinson/iplib)
[![CircleCI](https://circleci.com/gh/c-robinson/iplib/tree/main.svg?style=svg)](https://circleci.com/gh/c-robinson/iplib/tree/main)
[![Go Report Card](https://goreportcard.com/badge/github.com/c-robinson/iplib)](https://goreportcard.com/report/github.com/c-robinson/iplib)
[![Coverage Status](https://coveralls.io/repos/github/c-robinson/iplib/badge.svg?branch=main)](https://coveralls.io/github/c-robinson/iplib?branch=main)

**ATTENTION** version 2.0.0 is a breaking change from previous versions for
handling IPv6 addresses (functions for IPv4 are unchanged). Calls that result
in arithmatic operations against IPv6 now use [uint128.Uint128](https://lukechampine.com/uint128)
instead of `*big.Int`. Until now this library restricted itself to using the
standard library, but `math/big` is sloooooooooow and the performance gains
from switching were too large to ignore:

| Benchmark | *big.Int | uint128.Uint128 |
| --- | --- |-----------------|
| Benchmark_DeltaIP6 | 79.27 ns/op | 2.809 ns/op |
| BenchmarkDecrementIP6By | 50.54 ns/op | 13.88 ns/op |
| BenchmarkIncrementIP6By | 50.48 ns/op | 13.92 ns/op |
| BenchmarkNet_Count6 | 122.2 ns/op | 11.26 ns/op |

It would be fantastic to remove this external dependency in some future v3
that switched to a native `uint128` but for that to happen [this proposal](https://github.com/golang/go/issues/9455)
(or something similar) would need to be adopted.

**Okay you can stop paying attention now**

I really enjoy Python's [ipaddress](https://docs.python.org/3/library/ipaddress.html)
library and Ruby's [ipaddr](https://ruby-doc.org/stdlib-2.5.1/libdoc/ipaddr/rdoc/IPAddr.html),
I think you can write a lot of neat software if some of the little problems
@@ -114,7 +134,8 @@ func main() {
Addresses that require or return a count default to using `uint32`, which is
sufficient for working with the entire IPv4 space. As a rule these functions
are just lowest-common wrappers around IPv4- or IPv6-specific functions. The
IPv6-specific variants use `big.Int` so they can access the entire v6 space.
IPv6-specific variants use `uint128.Uint128` so they can access the entire v6
space.

## The iplib.Net interface

@@ -152,7 +173,7 @@ fmt.Println(n.Supernet(0)) // 192.168.0.0/15 <nil>

## Using iplib.Net6

`Net6` represents and IPv6 network. In some ways v6 is simpler than v4, as
`Net6` represents an IPv6 network. In some ways v6 is simpler than v4, as
it does away with the special behavior of addresses at the front and back of
the netblock. For IPv6 the primary problem is the sheer size of the thing:
there are 2^128th addresses in IPv6, which translates to 340 undecillion!
30 changes: 24 additions & 6 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -4,6 +4,8 @@ import (
"fmt"
"math/big"
"net"

"lukechampine.com/uint128"
)

func ExampleBigintToIP6() {
@@ -30,16 +32,16 @@ func ExampleDecrementIP4By() {
}

func ExampleDecrementIP6By() {
z := big.NewInt(16777215)
z := uint128.New(16777215, 0)
ip := net.ParseIP("2001:db8::ffff:ffff")
fmt.Println(DecrementIP6By(ip, z))
// Output: 2001:db8::ff00:0
}

func ExampleDecrementIP6WithinHostmask() {
ip := net.ParseIP("2001:db8:1000::")
ip1, _ := DecrementIP6WithinHostmask(ip, NewHostMask(0), big.NewInt(1))
ip2, _ := DecrementIP6WithinHostmask(ip, NewHostMask(56), big.NewInt(1))
ip1, _ := DecrementIP6WithinHostmask(ip, NewHostMask(0), uint128.New(1, 0))
ip2, _ := DecrementIP6WithinHostmask(ip, NewHostMask(56), uint128.New(1, 0))
fmt.Println(ip1)
fmt.Println(ip2)
// Output:
@@ -94,23 +96,28 @@ func ExampleIncrementIP4By() {
}

func ExampleIncrementIP6By() {
z := big.NewInt(16777215)
z := uint128.New(16777215, 0)
ip := net.ParseIP("2001:db8::ff00:0")
fmt.Println(IncrementIP6By(ip, z))
// Output: 2001:db8::ffff:ffff
}

func ExampleIncrementIP6WithinHostmask() {
ip := net.ParseIP("2001:db8:1000::")
ip1, _ := IncrementIP6WithinHostmask(ip, NewHostMask(0), big.NewInt(1))
ip2, _ := IncrementIP6WithinHostmask(ip, NewHostMask(56), big.NewInt(1))
ip1, _ := IncrementIP6WithinHostmask(ip, NewHostMask(0), uint128.New(1, 0))
ip2, _ := IncrementIP6WithinHostmask(ip, NewHostMask(56), uint128.New(1, 0))
fmt.Println(ip1)
fmt.Println(ip2)
// Output:
// 2001:db8:1000::1
// 2001:db8:1000:0:100::
}

func ExampleIPToBigint() {
fmt.Println(IPToBigint(net.ParseIP("2001:db8:85a3::8a2e:370:7334")))
// Output: 42540766452641154071740215577757643572
}

func ExampleIP4ToARPA() {
fmt.Println(IP4ToARPA(net.ParseIP("192.168.1.1")))
// Output: 1.1.168.192.in-addr.arpa
@@ -126,6 +133,11 @@ func ExampleIP4ToUint32() {
// Output: 3232235777
}

func ExampleIP6ToUint128() {
fmt.Println(IP6ToUint128(net.ParseIP("2001:db8:85a3::8a2e:370:7334")))
// Output: 42540766452641154071740215577757643572
}

func ExampleIPToBinaryString() {
fmt.Println(IPToBinaryString(net.ParseIP("192.168.1.1")))
fmt.Println(IPToBinaryString(net.ParseIP("2001:db8::ffff:ffff")))
@@ -173,6 +185,12 @@ func ExampleUint32ToIP4() {
// Output: 192.168.1.1
}

func ExampleUint128ToIP6() {
z, _ := uint128.FromString("42540766452641154071740215577757643572")
fmt.Println(Uint128ToIP6(z))
// Output: 2001:db8:85a3::8a2e:370:7334
}

func ExampleVersion() {
fmt.Println(Version(ForceIP4(net.ParseIP("192.168.1.1"))))
fmt.Println(Version(net.ParseIP("::ffff:c0a8:101")))
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/c-robinson/iplib
module github.com/c-robinson/iplib/v2

go 1.12
go 1.20

require lukechampine.com/uint128 v1.3.0
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
161 changes: 87 additions & 74 deletions hostmask.go
Original file line number Diff line number Diff line change
@@ -2,8 +2,9 @@ package iplib

import (
"encoding/hex"
"math/big"
"net"

"lukechampine.com/uint128"
)

// HostMask is a mask that can be applied to IPv6 addresses to mask out bits
@@ -127,17 +128,14 @@ func (m HostMask) String() string {
// portion of the supplied net.IP by the supplied integer value. If the
// input or output value fall outside the boundaries of the hostmask a
// ErrAddressOutOfRange will be returned
func DecrementIP6WithinHostmask(ip net.IP, hm HostMask, count *big.Int) (net.IP, error) {
xcount := new(big.Int) // do not manipulate 'count'
xcount.Set(count)

func DecrementIP6WithinHostmask(ip net.IP, hm HostMask, count uint128.Uint128) (net.IP, error) {
bb, bbpos := hm.BoundaryByte()
if bbpos == 0 {
return net.IP{}, ErrBadMaskLength
}

if bbpos == -1 {
return DecrementIP6By(ip, xcount), nil
return DecrementIP6By(ip, count), nil
}

// check if ip is outside of hostmask already
@@ -152,8 +150,8 @@ func DecrementIP6WithinHostmask(ip net.IP, hm HostMask, count *big.Int) (net.IP,
}
}

bb = decrementBoundaryByte(bb, ip[bbpos], xcount)
xip := decrementUnmaskedBytes(ip[:bbpos], xcount)
count, bb = decrementBoundaryByte(bb, ip[bbpos], count)
xip := decrementUnmaskedBytes(ip[:bbpos], count)
if len(xip) == 0 {
return xip, ErrAddressOutOfRange
}
@@ -173,16 +171,14 @@ func DecrementIP6WithinHostmask(ip net.IP, hm HostMask, count *big.Int) (net.IP,
// unmasked portion of the supplied net.IP by the supplied integer value. If
// the input or output value fall outside the boundaries of the hostmask a
// ErrAddressOutOfRange will be returned
func IncrementIP6WithinHostmask(ip net.IP, hm HostMask, count *big.Int) (net.IP, error) {
xcount := getCloneBigInt(count)

func IncrementIP6WithinHostmask(ip net.IP, hm HostMask, count uint128.Uint128) (net.IP, error) {
bb, bbpos := hm.BoundaryByte()
if bbpos == 0 {
return net.IP{}, ErrBadMaskLength
}

if bbpos == -1 {
return IncrementIP6By(ip, xcount), nil
return IncrementIP6By(ip, count), nil
}

// check if ip is outside of hostmask already
@@ -192,8 +188,8 @@ func IncrementIP6WithinHostmask(ip net.IP, hm HostMask, count *big.Int) (net.IP,
}
}

bb = incrementBoundaryByte(bb, ip[bbpos], xcount)
xip := incrementUnmaskedBytes(ip[:bbpos], xcount)
count, bb = incrementBoundaryByte(bb, ip[bbpos], count)
xip := incrementUnmaskedBytes(ip[:bbpos], count)

if len(xip) > bbpos {
return net.IP{}, ErrAddressOutOfRange
@@ -202,7 +198,9 @@ func IncrementIP6WithinHostmask(ip net.IP, hm HostMask, count *big.Int) (net.IP,
xip = append(xip, bb)

xip = append(xip, make([]byte, 15-bbpos)...)

if CompareIPs(xip, ip) < 0 {
return net.IP{}, ErrAddressOutOfRange
}
return xip, nil
}

@@ -268,91 +266,106 @@ func PreviousIP6WithinHostmask(ip net.IP, hm HostMask) (net.IP, error) {
// decrementBoundaryByte takes a boundary-byte, a boundary-value and a count
// as input and returns a modified boundary byte and count for further
// processing. bb is used to calculate the maximum value for bv and then the
// count + bv is divided by that max. The function will return the modulus
// as a byte value, and the pointer to count will have the quotient
func decrementBoundaryByte(bb, bv byte, count *big.Int) byte {
bmax := 256 - int(bb) // max value of unmasked byte as int
if v := count.Cmp(big.NewInt(0)); v <= 0 {
return bv
// count + bv is divided by that max. The function returns a new count and
// boundary-byte
func decrementBoundaryByte(bb, bv byte, count uint128.Uint128) (uint128.Uint128, byte) {
if count.IsZero() {
return count, bv
}
bigbmax := big.NewInt(int64(bmax))
bigmod := new(big.Int)

count.DivMod(count, bigbmax, bigmod)
mod64 := bigmod.Int64()
mod := byte(mod64)
if mod > bv {
count.Add(count, big.NewInt(1))
return (byte(bmax) + bv) - mod

byteMax := uint128.From64(256 - uint64(bb)) // max value of unmasked bits in the byte
byteVal := uint128.From64(uint64(bv)) // cur value of unmasked bits in the byte

mod := uint128.New(0, 0)

count, mod = count.QuoRem(byteMax)

// extract the actual modulus into bmod
rb := make([]byte, 16)
mod.PutBytesBE(rb)
bmod := rb[15]

if bmod > bv {
count = count.Add64(1)

byteVal = byteVal.Add(byteMax)
byteVal = byteVal.Sub(mod)

// convert to byte
byteVal.PutBytesBE(rb)

return count, rb[15]
}
return bv - mod
return count, bv - bmod
}

// decrementUnmaskedBytes decrements an arbitrary []byte by count and returns
// a []byte of the same length
func decrementUnmaskedBytes(nb []byte, count *big.Int) []byte {
z := new(big.Int)
z.SetBytes(nb)
z.Sub(z, count)
func decrementUnmaskedBytes(nb []byte, count uint128.Uint128) []byte {
if count.IsZero() {
return nb

if v := z.Sign(); v < 0 {
return []byte{}
}

zb := z.Bytes()
if len(zb) < len(nb) {
zb = append(make([]byte, len(nb)-len(zb)), zb...)
// convert the []byte to a uint128, which requires a [16]byte
pnb := append(make([]byte, 16-len(nb)), nb...)
n := uint128.FromBytesBE(pnb)

if count.Cmp(n) > 0 {
return []byte{}
}

return zb
n = n.Sub(count)

// convert the uint128 back to a []byte
xb := make([]byte, 16)
n.PutBytesBE(xb)

// return only as many elements as were passed in
return xb[16-len(nb):]
}

// incrementBoundaryByte takes a boundary-byte, a boundary-value and a count
// as input and returns a modified boundary byte and count for further
// processing. bb is used to calculate the maximum value for bv and then the
// count + bv is divided by that max. The function will return the modulus
// as a byte value, and the pointer to count will have the quotient
func incrementBoundaryByte(bb, bv byte, count *big.Int) byte {
if v := count.Cmp(big.NewInt(0)); v <= 0 {
return bv
func incrementBoundaryByte(bb, bv byte, count uint128.Uint128) (uint128.Uint128, byte) {
if count.IsZero() {
return count, bv
}
byteMax := uint128.From64(256 - uint64(bb)) // max value of unmasked bits in the byte
byteVal := uint128.From64(uint64(bv)) // cur value of unmasked bits in the byte

bigbmax := big.NewInt(256 - int64(bb)) // max value of unmasked byte as an int
bigbval := big.NewInt(int64(bv)) // cur value of unmasked byte as an int

count.Add(count, bigbval)

// if count is less than bmax, we're done
if v := count.Cmp(bigbmax); v < 0 {
b := count.Bytes()
count.Set(big.NewInt(0))
if len(b) == 0 {
return 0
}
return b[0]
count = count.Add(byteVal)
if count.Cmp(byteMax) < 0 {
return uint128.Uint128{}, byte(count.Lo)
}

bigmod := new(big.Int)

count.DivMod(count, bigbmax, bigmod)
mod := bigmod.Bytes()
if len(mod) == 0 {
return byte(0)
}
return mod[0]
mod := uint128.New(0, 0)
count, mod = count.QuoRem(byteMax)
rb := make([]byte, 16)
mod.PutBytesBE(rb)
return count, rb[15]
}

// incrementUnmaskedBytes increments an arbitrary []byte by count and returns
// a []byte of the same length
func incrementUnmaskedBytes(nb []byte, count *big.Int) []byte {
z := new(big.Int)
z.SetBytes(nb)
z.Add(z, count)

zb := z.Bytes()
if len(zb) < len(nb) {
zb = append(make([]byte, len(nb)-len(zb)), zb...)
func incrementUnmaskedBytes(nb []byte, count uint128.Uint128) []byte {
if count.IsZero() {
return nb
}

return zb
// convert the []byte to a uint128, which requires a [16]byte
pnb := append(make([]byte, 16-len(nb)), nb...)
n := uint128.FromBytesBE(pnb)

n = n.Add(count)

// convert the uint128 back to a []byte
xb := make([]byte, 16)
n.PutBytesBE(xb)

// return only as many elements as were passed in
return xb[16-len(nb):]
}
Loading