iprange

package module
v0.2.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 11, 2024 License: MIT Imports: 7 Imported by: 1

README

iprange

GoDoc codecov

Package iprange parses IPv4/IPv6 addresses from strings in IP range format.

Supported IP range formats

  • 172.18.0.1 / fd00::1
  • 172.18.0.0/24 / fd00::/64
  • 172.18.0.1-10 / fd00::1-a
  • 172.18.0.1-172.18.1.10 / fd00::1-fd00::1:a

Example

package main

import (
	"fmt"
	"log"

	"github.com/iiiceoo/iprange"
)

func main() {
	// Parse IP ranges.
	ranges, err := iprange.Parse(
		"172.18.0.1",
		"172.18.0.0/24",
		"172.18.0.1-10",
		"172.18.1.1-172.18.1.3",
	)
	if err != nil {
		log.Fatalf("failed to parse IP ranges: %v\n", err)
	}
	fmt.Printf("%s IP ranges: %s\n", ranges.Version(), ranges)

	// Merge IP ranges.
	merged := ranges.Merge()
	fmt.Printf("Merged IP ranges: %s\n", merged)

	// Interval mathematics of IP ranges.
	another, _ := iprange.Parse("172.18.0.0/24")
	diff := merged.Diff(another)
	fmt.Printf("The difference between two IP ranges: %s\n", diff)

	// Convert IP ranges to IP addresses.
	fmt.Printf("Iterate through all %d IP addresses:\n", diff.Size())
	ipIter := diff.IPIterator()
	for {
		ip := ipIter.Next()
		if ip == nil {
			break
		}
		fmt.Println(ip)
	}
}

License

Package iprange is MIT-Licensed.

Documentation

Overview

Package iprange parses IPv4/IPv6 addresses from strings in IP range format.

The following IP range formats are supported:

172.18.0.1              fd00::1
172.18.0.0/24           fd00::/64
172.18.0.1-10           fd00::1-a
172.18.0.1-172.18.1.10  fd00::1-fd00::1:a

It takes a set of IP range strings, and returns a list of start-end IP address pairs, which can then be automatically extended and normalized, for instance:

v4Ranges, err := iprange.Parse("172.18.0.1", "172.18.0.0/24")  // √
v6Ranges, err := iprange.Parse("fd00::1", "fd00::/64")         // √
invalid, err := iprange.Parse("Invalid IP range string")       // ×
dual, err := iprange.Parse("172.18.0.1", "fd00::/64")          // ×

When parsing an invalid IP range string, error errInvalidIPRangeFormat will be returned, and dual-stack IP ranges are not allowed because this approach is too complex and confusing. Use the following functions to assert the errors:

func IsInvalidIPRangeFormat(err error) bool
func IsDualStackIPRanges(err error) bool

Use the interval methods of IPRanges to calculate the union, difference or intersection of two IPRanges. They do not change the original parameters (rr and rs), just calculate, and return the results.

func (rr *IPRanges) Union(rs *IPRanges) *IPRanges
func (rr *IPRanges) Diff(rs *IPRanges) *IPRanges
func (rr *IPRanges) Intersect(rs *IPRanges) *IPRanges

However, do not attempt to perform calculations on two IPRanges with different IP versions, it won't work:

res := v4Ranges.Diff(v6Ranges)  // res will be equal to v4Ranges.

The IPRanges can be converted into multiple net.IP (i.e. IP addresses) or *net.IPNet (i.e. subnets) through their own iterators. Continuously call the method Next() until nil is returned:

ipIter := ranges.IPIterator()
for {
	ip := ipIter.Next()
	if ip == nil {
		break
	}
	// Do someting.
}

cidrIter := ranges.CIDRIterator()
for {
	cidr := cidrIter.Next()
	if cidr == nil {
		break
	}
	// Do someting.
}

Finally, the inspiration for writing this package comes from

CNI plugins:      https://github.com/containernetworking/plugins
malfunkt/iprange: https://github.com/malfunkt/iprange
netaddr/netaddr:  https://github.com/netaddr/netaddr

both of which are great!

Index

Examples

Constants

View Source
const (
	Unknown family = iota
	IPv4
	IPv6
)

Standard IP version 4 or 6. Unknown represents an invalid IP version, which is commonly used in the zero value of an IPRanges struct or to distinguish an invalid xIP.

Variables

This section is empty.

Functions

func IsDualStackIPRanges

func IsDualStackIPRanges(err error) bool

IsDualStackIPRanges asserts whether the err is errDualStackIPRanges.

func IsInvalidIPRangeFormat

func IsInvalidIPRangeFormat(err error) bool

IsInvalidIPRangeFormat asserts whether the err is errInvalidIPRangeFormat.

Types

type IPRanges

type IPRanges struct {
	// contains filtered or unexported fields
}

IPRanges is a set of ipRange that uses the starting and ending IP addresses to represent any IP range of any size. The following IP range formats are valid:

172.18.0.1              fd00::1
172.18.0.0/24           fd00::/64
172.18.0.1-10           fd00::1-a
172.18.0.1-172.18.1.10  fd00::1-fd00::1:a

Dual-stack IP ranges are not allowed, The IP version of an IPRanges can only be IPv4, IPv6, or unknown (zero value).

func Parse

func Parse(rs ...string) (*IPRanges, error)

Parse parses a set of IP range format strings as IPRanges, the slice of ipRange with the same IP version, which records the starting and ending IP addresses.

The error errInvalidIPRangeFormat wiil be returned when one of IP range string is invalid. And dual-stack IP ranges are not allowed, the error errDualStackIPRanges occurs when parsing a set of IP range strings, where there are both IPv4 and IPv6 addresses.

Example
package main

import (
	"fmt"
	"log"

	"github.com/iiiceoo/iprange"
)

func main() {
	v4Ranges, err := iprange.Parse("172.18.0.1", "172.18.0.0/24")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}
	v6Ranges, err := iprange.Parse("fd00::1-a", "fd00::1-fd00::1:a")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}

	fmt.Println(v4Ranges)
	fmt.Println(v6Ranges)
}
Output:

[172.18.0.1 172.18.0.0/24]
[fd00::1-fd00::a fd00::1-fd00::1:a]

func (*IPRanges) BlockIterator added in v0.2.0

func (rr *IPRanges) BlockIterator(blockSize *big.Int) *blockIterator

BlockIterator generates a new iterator for scanning IP blocks. blockSize should be at least 1, which is somewhat equivalent to IPIterator.

Example
package main

import (
	"fmt"
	"log"
	"math/big"

	"github.com/iiiceoo/iprange"
)

func main() {
	ranges, err := iprange.Parse("172.18.0.0-4")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}

	iter := ranges.BlockIterator(big.NewInt(2))
	for {
		ip := iter.Next()
		if ip == nil {
			break
		}
		fmt.Println(ip)
	}

	iter.Reset()
	n := big.NewInt(3)
	for {
		ip := iter.NextN(n)
		if ip == nil {
			break
		}
		fmt.Println(ip)
	}

}
Output:

172.18.0.0/31
172.18.0.2/31
172.18.0.4
172.18.0.4

func (*IPRanges) CIDRIterator added in v0.0.6

func (rr *IPRanges) CIDRIterator() *cidrIterator

CIDRIterator generates a new iterator for scanning CIDR.

Example
package main

import (
	"fmt"
	"log"

	"github.com/iiiceoo/iprange"
)

func main() {
	ranges, err := iprange.Parse("172.18.0.0-255", "172.18.0.1-3")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}

	iter := ranges.CIDRIterator()
	for {
		cidr := iter.Next()
		if cidr == nil {
			break
		}
		fmt.Println(cidr)
	}
}
Output:

172.18.0.0/24
172.18.0.1/32
172.18.0.2/31

func (*IPRanges) Contains

func (rr *IPRanges) Contains(ip net.IP) bool

Contains reports whether IPRanges rr contain net.IP ip. If rr is IPv4 and ip is IPv6, then it is also considered not contained, and vice versa.

Example
package main

import (
	"fmt"
	"log"
	"net"

	"github.com/iiiceoo/iprange"
)

func main() {
	ranges, err := iprange.Parse("172.18.0.0/24")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}

	fmt.Println(ranges.Contains(net.ParseIP("172.18.0.1")))
	fmt.Println(ranges.Contains(net.ParseIP("172.19.0.1")))
	fmt.Println(ranges.Contains(net.ParseIP("fd00::1")))
}
Output:

true
false
false

func (*IPRanges) DeepCopy added in v0.2.0

func (rr *IPRanges) DeepCopy() *IPRanges

func (*IPRanges) Diff

func (rr *IPRanges) Diff(rs *IPRanges) *IPRanges

Diff calculates the difference of IPRanges rr and rs with the same IP version. The result is always merged (ordered and deduplicated).

Input:  [172.18.0.20-30, 172.18.0.1-25] - [172.18.0.5-25]
Output: [172.18.0.1-4, 172.18.0.26-30]
Example
package main

import (
	"fmt"
	"log"

	"github.com/iiiceoo/iprange"
)

func main() {
	ranges1, err := iprange.Parse("172.18.0.20-30", "172.18.0.0-25")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}
	ranges2, err := iprange.Parse("172.18.0.4-26")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}

	fmt.Println(ranges1.Diff(ranges2))
}
Output:

[172.18.0.0/30 172.18.0.27-172.18.0.30]

func (*IPRanges) Equal

func (rr *IPRanges) Equal(rr2 *IPRanges) bool

Equal reports whether IPRanges rr is equal to rr2.

Example
package main

import (
	"fmt"
	"log"

	"github.com/iiiceoo/iprange"
)

func main() {
	ranges1, err := iprange.Parse("172.18.0.0/24")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}
	ranges2, err := iprange.Parse("172.18.0.0-255")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}
	ranges3, err := iprange.Parse("172.18.0.100-255", "172.18.0.0-200")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}

	fmt.Println(ranges1.Equal(ranges2))
	fmt.Println(ranges1.Equal(ranges3))
}
Output:

true
false

func (*IPRanges) IPIterator added in v0.0.6

func (rr *IPRanges) IPIterator() *ipIterator

IPIterator generates a new iterator for scanning IP addresses.

Example
package main

import (
	"fmt"
	"log"
	"math/big"

	"github.com/iiiceoo/iprange"
)

func main() {
	ranges, err := iprange.Parse("172.18.0.1-3")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}

	iter := ranges.IPIterator()
	for {
		ip := iter.Next()
		if ip == nil {
			break
		}
		fmt.Println(ip)
	}

	iter.Reset()
	n := big.NewInt(2)
	for {
		ip := iter.NextN(n)
		if ip == nil {
			break
		}
		fmt.Println(ip)
	}

}
Output:

172.18.0.1
172.18.0.2
172.18.0.3
172.18.0.2

func (*IPRanges) Intersect

func (rr *IPRanges) Intersect(rs *IPRanges) *IPRanges

Intersect calculates the intersection of IPRanges rr and rs with the same IP version. The result is always merged (ordered and deduplicated).

Input:  [172.18.0.20-30, 172.18.0.1-25] ∩ [172.18.0.5-25]
Output: [172.18.0.5-25]
Example
package main

import (
	"fmt"
	"log"

	"github.com/iiiceoo/iprange"
)

func main() {
	ranges1, err := iprange.Parse("172.18.0.20-30", "172.18.0.1-25")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}
	ranges2, err := iprange.Parse("172.18.0.5-25")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}

	fmt.Println(ranges1.Intersect(ranges2))
}
Output:

172.18.0.5-172.18.0.25

func (*IPRanges) IsOverlap added in v0.1.1

func (rr *IPRanges) IsOverlap() bool

IsOverlap reports whether IPRanges rr have overlapping parts.

Example
package main

import (
	"fmt"
	"log"

	"github.com/iiiceoo/iprange"
)

func main() {
	ranges1, err := iprange.Parse("172.18.0.20-30", "172.18.0.25")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}
	ranges2, err := iprange.Parse("172.18.0.0/16")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}

	fmt.Println(ranges1.IsOverlap())
	fmt.Println(ranges2.IsOverlap())
}
Output:

true
false

func (*IPRanges) Merge

func (rr *IPRanges) Merge() *IPRanges

Merge merges the duplicate parts of multiple ipRanges in rr and sort them by their respective starting xIP.

Example
package main

import (
	"fmt"
	"log"

	"github.com/iiiceoo/iprange"
)

func main() {
	ranges, err := iprange.Parse("172.18.0.201", "172.18.0.100-200", "172.18.0.1-150")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}

	fmt.Println(ranges)
	fmt.Println(ranges.Merge())
}
Output:

[172.18.0.201 172.18.0.100-172.18.0.200 172.18.0.1-172.18.0.150]
172.18.0.1-172.18.0.201

func (*IPRanges) MergeEqual

func (rr *IPRanges) MergeEqual(rr2 *IPRanges) bool

MergeEqual reports whether IPRanges rr is equal to rr2, but both rr and rr2 are pre-merged, which means they are both ordered and deduplicated.

Example
package main

import (
	"fmt"
	"log"

	"github.com/iiiceoo/iprange"
)

func main() {
	ranges1, err := iprange.Parse("172.18.0.0/24")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}
	ranges2, err := iprange.Parse("172.18.0.100-255", "172.18.0.0-200")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}

	fmt.Println(ranges1.MergeEqual(ranges2))
}
Output:

true

func (*IPRanges) Size added in v0.0.2

func (rr *IPRanges) Size() *big.Int

Size calculates the total number of IP addresses that pertain to IPRanges rr.

Example
package main

import (
	"fmt"
	"log"

	"github.com/iiiceoo/iprange"
)

func main() {
	ranges, err := iprange.Parse("172.18.0.0/24")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}
	zero := iprange.IPRanges{}

	fmt.Println(ranges.Size())
	fmt.Println(zero.Size())
}
Output:

256
0

func (*IPRanges) Slice added in v0.2.0

func (rr *IPRanges) Slice(start, end *big.Int) *IPRanges

Slice returns a slice of IPRanges, supporting negative indexes.

Example
package main

import (
	"fmt"
	"log"
	"math/big"

	"github.com/iiiceoo/iprange"
)

func main() {
	ranges, err := iprange.Parse("172.18.0.0-3", "172.18.0.10-14")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}

	fmt.Println(ranges.Slice(big.NewInt(2), big.NewInt(-2)))
}
Output:

[172.18.0.2/31 172.18.0.10-172.18.0.13]

func (*IPRanges) String added in v0.0.4

func (rr *IPRanges) String() string

String implements fmt.Stringer.

func (*IPRanges) Strings added in v0.1.4

func (rr *IPRanges) Strings() []string

Strings returns a slice of the string representations of the IPRanges rr.

func (*IPRanges) Union

func (rr *IPRanges) Union(rs *IPRanges) *IPRanges

Union calculates the union of IPRanges rr and rs with the same IP version. The result is always merged (ordered and deduplicated).

Input:  [172.18.0.20-30, 172.18.0.1-25] U [172.18.0.5-25]
Output: [172.18.0.1-30]
Example
package main

import (
	"fmt"
	"log"

	"github.com/iiiceoo/iprange"
)

func main() {
	ranges1, err := iprange.Parse("172.18.0.20-30", "172.18.0.1-25")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}
	ranges2, err := iprange.Parse("172.18.0.5-25")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}

	fmt.Println(ranges1.Union(ranges2))
}
Output:

172.18.0.1-172.18.0.30

func (*IPRanges) Version added in v0.0.4

func (rr *IPRanges) Version() family

Version returns the IP version of IPRanges:

1: IPv4
2: IPv6
0: zero value of IPRanges

Do not compare family with a regular int value, which is confusing. Use predefined const such as IPv4, IPv6, or Unknown.

Example
package main

import (
	"fmt"
	"log"

	"github.com/iiiceoo/iprange"
)

func main() {
	v4Ranges, err := iprange.Parse("172.18.0.1", "172.18.0.0/24")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}
	v6Ranges, err := iprange.Parse("fd00::1-a", "fd00::1-fd00::1:a")
	if err != nil {
		log.Fatalf("error parsing IP ranges: %v", err)
	}
	zero := iprange.IPRanges{}

	fmt.Println(v4Ranges.Version())
	fmt.Println(v6Ranges.Version())
	fmt.Println(zero.Version())
}
Output:

IPv4
IPv6
Unknown

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL