profile
viewpoint
Matt Layher mdlayher @fastly Kalamazoo, MI https://mdlayher.com/ Software Engineer. Go, Linux, and open source software enthusiast. On and ever upward.

coredhcp/coredhcp 532

Fast, multithreaded, modular and extensible DHCP server written in Go

insomniacslk/dhcp 302

DHCPv6 and DHCPv4 packet library, client and server written in Go

mdlayher/arp 234

Package arp implements the ARP protocol, as described in RFC 826. MIT Licensed.

mdlayher/ethernet 210

Package ethernet implements marshaling and unmarshaling of IEEE 802.3 Ethernet II frames and IEEE 802.1Q VLAN tags. MIT Licensed.

mdlayher/corerad 76

CoreRAD is an extensible and observable IPv6 Neighbor Discovery Protocol router advertisement daemon. Apache 2.0 Licensed.

mdlayher/dhcp6 68

Package dhcp6 implements a DHCPv6 server, as described in RFC 3315. MIT Licensed.

mdlayher/apcupsd_exporter 58

Prometheus exporter that exposes metrics from apcupsd's NIS. MIT Licensed.

jsimonetti/rtnetlink 54

Package rtnetlink provides low-level access to the Linux rtnetlink API. MIT Licensed.

mdlayher/apcupsd 27

Package apcupsd provides a client for the apcupsd Network Information Server (NIS). MIT Licensed.

mdlayher/consrv 18

Command consrv is a SSH to serial console bridge server, originally designed for deployment on gokrazy.org devices. Apache 2.0 Licensed.

delete branch jsimonetti/rtnetlink

delete branch : dependabot/go_modules/github.com/google/go-cmp-0.5.4

delete time in 3 hours

push eventjsimonetti/rtnetlink

dependabot-preview[bot]

commit sha 11d754531775323f3383738bd64180e2106a64d2

build(deps): bump github.com/google/go-cmp from 0.5.3 to 0.5.4 Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.3 to 0.5.4. - [Release notes](https://github.com/google/go-cmp/releases) - [Commits](https://github.com/google/go-cmp/compare/v0.5.3...v0.5.4) Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

view details

Jeroen Simonetti

commit sha 8bebea019a6c93f4ce7e2978c713777f6ae06353

Merge pull request #97 from jsimonetti/dependabot/go_modules/github.com/google/go-cmp-0.5.4 build(deps): bump github.com/google/go-cmp from 0.5.3 to 0.5.4

view details

push time in 3 hours

PR merged jsimonetti/rtnetlink

build(deps): bump github.com/google/go-cmp from 0.5.3 to 0.5.4 dependencies

Bumps github.com/google/go-cmp from 0.5.3 to 0.5.4. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/google/go-cmp/releases">github.com/google/go-cmp's releases</a>.</em></p> <blockquote> <h2>v0.5.4</h2> <p>Bug fixes:</p> <p>(<a href="https://github-redirect.dependabot.com/google/go-cmp/issues/247">#247</a>) Fix non-determinism in diffing algorithm (<a href="https://github-redirect.dependabot.com/google/go-cmp/issues/248">#248</a>) Impose verbosity limit when formatting map keys</p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/google/go-cmp/commit/ec71d6d790538ad88c95a192fd059e11afb45b6f"><code>ec71d6d</code></a> Impose verbosity limit when formatting map keys (<a href="https://github-redirect.dependabot.com/google/go-cmp/issues/248">#248</a>)</li> <li><a href="https://github.com/google/go-cmp/commit/449e17c6c9daf9b0c84a35fef7d79321b9535763"><code>449e17c</code></a> Fix non-determinism in diffing algorithm (<a href="https://github-redirect.dependabot.com/google/go-cmp/issues/247">#247</a>)</li> <li><a href="https://github.com/google/go-cmp/commit/ade6b74536ea3af0d70b4ebd51c08c5d31313078"><code>ade6b74</code></a> Use GitHub actions for testing (<a href="https://github-redirect.dependabot.com/google/go-cmp/issues/246">#246</a>)</li> <li>See full diff in <a href="https://github.com/google/go-cmp/compare/v0.5.3...v0.5.4">compare view</a></li> </ul> </details> <br />

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


<details> <summary>Dependabot commands and options</summary> <br />

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot merge will merge this PR after your CI passes on it
  • @dependabot squash and merge will squash and merge this PR after your CI passes on it
  • @dependabot cancel merge will cancel a previously requested merge and block automerging
  • @dependabot reopen will reopen this PR if it is closed
  • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
  • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
  • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
  • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
  • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language
  • @dependabot badge me will comment on this PR with code to add a "Dependabot enabled" badge to your readme

Additionally, you can set the following in your Dependabot dashboard:

  • Update frequency (including time of day and day of week)
  • Pull request limits (per update run and/or open at any time)
  • Out-of-range updates (receive only lockfile updates, if desired)
  • Security updates (receive only security updates, if desired)

</details>

+2 -1

0 comment

2 changed files

dependabot-preview[bot]

pr closed time in 3 hours

startedadafruit/circuitpython

started time in 3 hours

PR opened jsimonetti/rtnetlink

build(deps): bump github.com/google/go-cmp from 0.5.3 to 0.5.4

Bumps github.com/google/go-cmp from 0.5.3 to 0.5.4. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/google/go-cmp/releases">github.com/google/go-cmp's releases</a>.</em></p> <blockquote> <h2>v0.5.4</h2> <p>Bug fixes:</p> <p>(<a href="https://github-redirect.dependabot.com/google/go-cmp/issues/247">#247</a>) Fix non-determinism in diffing algorithm (<a href="https://github-redirect.dependabot.com/google/go-cmp/issues/248">#248</a>) Impose verbosity limit when formatting map keys</p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/google/go-cmp/commit/ec71d6d790538ad88c95a192fd059e11afb45b6f"><code>ec71d6d</code></a> Impose verbosity limit when formatting map keys (<a href="https://github-redirect.dependabot.com/google/go-cmp/issues/248">#248</a>)</li> <li><a href="https://github.com/google/go-cmp/commit/449e17c6c9daf9b0c84a35fef7d79321b9535763"><code>449e17c</code></a> Fix non-determinism in diffing algorithm (<a href="https://github-redirect.dependabot.com/google/go-cmp/issues/247">#247</a>)</li> <li><a href="https://github.com/google/go-cmp/commit/ade6b74536ea3af0d70b4ebd51c08c5d31313078"><code>ade6b74</code></a> Use GitHub actions for testing (<a href="https://github-redirect.dependabot.com/google/go-cmp/issues/246">#246</a>)</li> <li>See full diff in <a href="https://github.com/google/go-cmp/compare/v0.5.3...v0.5.4">compare view</a></li> </ul> </details> <br />

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


<details> <summary>Dependabot commands and options</summary> <br />

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot merge will merge this PR after your CI passes on it
  • @dependabot squash and merge will squash and merge this PR after your CI passes on it
  • @dependabot cancel merge will cancel a previously requested merge and block automerging
  • @dependabot reopen will reopen this PR if it is closed
  • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
  • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
  • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
  • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
  • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language
  • @dependabot badge me will comment on this PR with code to add a "Dependabot enabled" badge to your readme

Additionally, you can set the following in your Dependabot dashboard:

  • Update frequency (including time of day and day of week)
  • Pull request limits (per update run and/or open at any time)
  • Out-of-range updates (receive only lockfile updates, if desired)
  • Security updates (receive only security updates, if desired)

</details>

+2 -1

0 comment

2 changed files

pr created time in 6 hours

created repositorytv42/bella

Bella renders text to graphics and prints it on a label maker using IPP/CUPS.

created time in 12 hours

issue commentinetaf/netaddr

proposal: add additional methods to support IPv6 expansion

Maybe func (ip IP) StringExpanded() string method for the first one. I'm not sure we need (or want) the PTR record one. It seems esoteric enough that callers can write it themselves in their calling code: https://play.golang.org/p/_xcPmX4s4bo

func ptr(ip netaddr.IP) string {
	var s strings.Builder
	s.Grow(64)
	for _, b := range ip.As16() {
		fmt.Fprintf(&s, ".%x.%x", b>>4, b&0b1111)
	}
	return s.String()[1:]
}
jimmystewpot

comment created time in 14 hours

issue commentinetaf/netaddr

proposal: add additional methods to support IPv6 expansion

I've seen some systems that don't handle the compressed format properly and think it'd be reasonable to add the first method to expand ::, but I would also wonder if you'd want to expand :0: to :0000: in each segment. Not sure.

I agree that the implementation should use the full quad format.

I'm curious how often they are actually necessar in practice

I am only aware of the full dotted syntax being used in DNS PTR records for IPv6. If you are interested here is an example from an RFC. I am only aware of the expanded quad format being used in enterprise security appliances however I am sure there are other cases I am unaware of. I agree that these are not capabilities that are in high demand. There have been requests to expand on the existing capabilities in the main go std libraries. https://github.com/golang/go/issues/30264 is one example that came up when I started looking.

Thinking further about my initial example and taking your first comment on board would the following make more sense?

ip, _ := netaddr.ParseIP("2404:6800:4006:809::200e")
ip.As16Expanded() // 2404:6800:4006:0809:0000:0000:0000:200e
ip.As16ExpandedDotted() // 2.4.0.4.6.8.0.0.4.0.0.6.0.8.0.9.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.e
jimmystewpot

comment created time in 14 hours

created repositoryrogpeppe/csv2json

Command to convert annotated CSV to JSON

created time in 19 hours

startedmdlayher/unifi_exporter

started time in a day

issue openedinetaf/netaddr

proposal: add additional methods to support IPv6 expansion

Hi There,

I saw this package and thought that it overlapped with some work that I have been doing recently. With IPv6 the addresses are usually shortened, have you thought about adding a feature where you can print out the expanded IPv6 format?

Example

ip, _ := netaddr.ParseIP("2404:6800:4006:809::200e")
ip.As16ExpandedShort() // 2404:6800:4006:0809:0:0:0:200e
ip.As16ExpandedLong() // 2.4.0.4.6.8.0.0.4.0.0.6.0.8.0.9.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.e

The use case for this is the automatic creation of DNS IPv6 PTR records however I am sure that there are other use cases. Is this something that you have thought about as an addition to this package or do you see it as out of scope?

If it is in scope I am happy to do the work and provide you with a PR, I just wanted to check if its aligned with where you want to take your package.

created time in a day

Pull request review commentinetaf/netaddr

Add IPRange, IPRangeSet & friends to work on sets of prefixes/ranges

 func (p *IPPrefix) UnmarshalText(text []byte) error { func (p IPPrefix) String() string { 	return fmt.Sprintf("%s/%d", p.IP, p.Bits) }++// LastIP returns the last IP in the prefix.+func (p IPPrefix) LastIP() IP {+	if p.IP.IsZero() {+		return IP{}+	}+	a16 := p.IP.As16()+	var off uint8+	var bits uint8 = 128+	if p.IP.Is4() {+		off = 12+		bits = 32+	}+	for b := p.Bits; b < bits; b++ {+		byteNum, bitInByte := b/8, 7-(b%8)+		a16[off+byteNum] |= 1 << uint(bitInByte)+	}+	if p.IP.Is4() {+		return IPFrom16(a16)+	} else {+		return IPv6Raw(a16) // doesn't unmap+	}+}++// IPRange represents an inclusive range of IP addresses+// from the same address family.+type IPRange struct {+	// From is the starting IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as To+	// and be less than or equal to To.+	// From is an inclusive bound; it is included in the set.+	From IP++	// To is the ending IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as From+	// and be greater than or equal to From.+	// To is an inclusive bound; it is included in the set.+	To IP+}++// Valid reports whether r.From and r.To are both non-zero and obey+// the documented requirements: address families match, and From is+// less than or equal to To.+func (r IPRange) Valid() bool {+	return !r.From.IsZero() && !r.To.IsZero() &&+		r.From.Is4() == r.To.Is4() &&+		!r.To.Less(r.From)+}++// ip16 represents a mutable IP address, either IPv4 (in IPv6-mapped+// form) or IPv6.+type ip16 [16]byte++// bitSet reports whether the given bit in the address is set.+// (bit 0 is the most significant bit in ip[0]; bit 127 is last)+func (ip ip16) bitSet(bit uint8) bool {+	i, s := bit/8, 7-(bit%8)+	return ip[i]&(1<<s) != 0+}++func (ip *ip16) set(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] |= 1 << s+}++func (ip *ip16) clear(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] &^= 1 << s+}++// lastWithBitZero returns a copy of ip with the given bit+// cleared and the following all set.+func (ip ip16) lastWithBitZero(bit uint8) ip16 {+	ip.clear(bit)+	for ; bit < 128; bit++ {+		ip.set(bit)+	}+	return ip+}++// firstWithBitOne returns a copy of ip with the given bit+// set and the following all cleared.+func (ip ip16) firstWithBitOne(bit uint8) ip16 {+	ip.set(bit)+	for ; bit < 128; bit++ {+		ip.clear(bit)+	}+	return ip+}++// prefixMaker returns a address-family-corrected IPPrefix from ip16 and bits,+// where the input bits is always in the IPv6-mapped form for IPv4 addresses.+type prefixMaker func(ip16 ip16, bits uint8) IPPrefix++// Prefixes returns the set of IPPrefix entries that covers r.+//+// If either of r's bounds are invalid, in the wrong order, or if+// they're of different address families, then Prefixes returns nil.+func (r IPRange) Prefixes() []IPPrefix {+	if !r.Valid() {+		return nil+	}+	var makePrefix prefixMaker+	if r.From.Is4() {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPFrom16([16]byte(ip16)), bits - 12*8}+		}+	} else {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPv6Raw([16]byte(ip16)), bits}+		}+	}+	a16, b16 := ip16(r.From.As16()), ip16(r.To.As16())+	return appendRangePrefixes(nil, makePrefix, a16, b16)+}++func appendRangePrefixes(dst []IPPrefix, makePrefix prefixMaker, a16, b16 ip16) []IPPrefix {+	common := uint8(0)+	for common < 128 && a16.bitSet(common) == b16.bitSet(common) {+		common+++	}+	// See whether a16 and b16, after their common shared bits, end+	// in all zero bits or all one bits, respectively.+	aAllZero, bAllSet := true, true+	for i := common; i < 128; i++ {+		if a16.bitSet(i) {+			aAllZero = false+			break+		}+	}+	for i := common; i < 128; i++ {+		if !b16.bitSet(i) {+			bAllSet = false+			break+		}+	}+	if aAllZero && bAllSet {+		// a16 to b16 represents a whole range, like 10.50.0.0/16.+		// (a16 being 10.50.0.0 and b16 being 10.50.255.255)+		return append(dst, makePrefix(a16, common))+	}+	// Otherwise recursively do both halves.+	dst = appendRangePrefixes(dst, makePrefix, a16, a16.lastWithBitZero(common+1))+	dst = appendRangePrefixes(dst, makePrefix, b16.firstWithBitOne(common+1), b16)+	return dst+}++func addOne(a []byte, i int) bool {

It's a pity math/big.nat isn't exported, 'cause it'd be perfect for a lot of these operations. Inc, AndNot, etc.

bradfitz

comment created time in a day

Pull request review commentinetaf/netaddr

Add IPRange, IPRangeSet & friends to work on sets of prefixes/ranges

 func (p *IPPrefix) UnmarshalText(text []byte) error { func (p IPPrefix) String() string { 	return fmt.Sprintf("%s/%d", p.IP, p.Bits) }++// LastIP returns the last IP in the prefix.+func (p IPPrefix) LastIP() IP {+	if p.IP.IsZero() {+		return IP{}+	}+	a16 := p.IP.As16()+	var off uint8+	var bits uint8 = 128+	if p.IP.Is4() {+		off = 12+		bits = 32+	}+	for b := p.Bits; b < bits; b++ {+		byteNum, bitInByte := b/8, 7-(b%8)+		a16[off+byteNum] |= 1 << uint(bitInByte)+	}+	if p.IP.Is4() {+		return IPFrom16(a16)+	} else {+		return IPv6Raw(a16) // doesn't unmap+	}+}++// IPRange represents an inclusive range of IP addresses+// from the same address family.+type IPRange struct {+	// From is the starting IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as To+	// and be less than or equal to To.+	// From is an inclusive bound; it is included in the set.+	From IP++	// To is the ending IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as From+	// and be greater than or equal to From.+	// To is an inclusive bound; it is included in the set.+	To IP+}++// Valid reports whether r.From and r.To are both non-zero and obey+// the documented requirements: address families match, and From is+// less than or equal to To.+func (r IPRange) Valid() bool {+	return !r.From.IsZero() && !r.To.IsZero() &&+		r.From.Is4() == r.To.Is4() &&+		!r.To.Less(r.From)+}++// ip16 represents a mutable IP address, either IPv4 (in IPv6-mapped+// form) or IPv6.+type ip16 [16]byte++// bitSet reports whether the given bit in the address is set.+// (bit 0 is the most significant bit in ip[0]; bit 127 is last)+func (ip ip16) bitSet(bit uint8) bool {+	i, s := bit/8, 7-(bit%8)+	return ip[i]&(1<<s) != 0+}++func (ip *ip16) set(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] |= 1 << s+}++func (ip *ip16) clear(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] &^= 1 << s+}++// lastWithBitZero returns a copy of ip with the given bit+// cleared and the following all set.+func (ip ip16) lastWithBitZero(bit uint8) ip16 {+	ip.clear(bit)+	for ; bit < 128; bit++ {+		ip.set(bit)+	}+	return ip+}++// firstWithBitOne returns a copy of ip with the given bit+// set and the following all cleared.+func (ip ip16) firstWithBitOne(bit uint8) ip16 {+	ip.set(bit)+	for ; bit < 128; bit++ {+		ip.clear(bit)+	}+	return ip+}++// prefixMaker returns a address-family-corrected IPPrefix from ip16 and bits,+// where the input bits is always in the IPv6-mapped form for IPv4 addresses.+type prefixMaker func(ip16 ip16, bits uint8) IPPrefix++// Prefixes returns the set of IPPrefix entries that covers r.+//+// If either of r's bounds are invalid, in the wrong order, or if+// they're of different address families, then Prefixes returns nil.+func (r IPRange) Prefixes() []IPPrefix {+	if !r.Valid() {+		return nil+	}+	var makePrefix prefixMaker+	if r.From.Is4() {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPFrom16([16]byte(ip16)), bits - 12*8}+		}+	} else {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPv6Raw([16]byte(ip16)), bits}+		}+	}+	a16, b16 := ip16(r.From.As16()), ip16(r.To.As16())+	return appendRangePrefixes(nil, makePrefix, a16, b16)+}++func appendRangePrefixes(dst []IPPrefix, makePrefix prefixMaker, a16, b16 ip16) []IPPrefix {+	common := uint8(0)+	for common < 128 && a16.bitSet(common) == b16.bitSet(common) {+		common+++	}+	// See whether a16 and b16, after their common shared bits, end+	// in all zero bits or all one bits, respectively.+	aAllZero, bAllSet := true, true+	for i := common; i < 128; i++ {+		if a16.bitSet(i) {+			aAllZero = false+			break+		}+	}+	for i := common; i < 128; i++ {+		if !b16.bitSet(i) {+			bAllSet = false+			break+		}+	}+	if aAllZero && bAllSet {+		// a16 to b16 represents a whole range, like 10.50.0.0/16.+		// (a16 being 10.50.0.0 and b16 being 10.50.255.255)+		return append(dst, makePrefix(a16, common))+	}+	// Otherwise recursively do both halves.+	dst = appendRangePrefixes(dst, makePrefix, a16, a16.lastWithBitZero(common+1))+	dst = appendRangePrefixes(dst, makePrefix, b16.firstWithBitOne(common+1), b16)+	return dst+}++func addOne(a []byte, i int) bool {+	if v := a[i]; v < 0xff {+		a[i]+++		return true+	}+	if i == 0 {+		return false+	}+	a[i] = 0+	return addOne(a, i-1)+}++func subOne(a []byte, i int) bool {+	if v := a[i]; v > 0 {+		a[i]--+		return true+	}+	if i == 0 {+		return false+	}+	a[i] = 0xff+	return subOne(a, i-1)+}++// ipFrom16Match returns an IP address from a with address family+// matching ip.+func ipFrom16Match(ip IP, a [16]byte) IP {+	if ip.Is6() {+		return IPv6Raw(a) // doesn't unwrap+	}+	return IPFrom16(a)+}++// Next returns the IP following ip.+// If there is none, it returns the IP zero value.+func (ip IP) Next() IP {+	var ok bool+	a := ip.As16()+	if ip.Is4() {+		ok = addOne(a[12:], 3)+	} else {+		ok = addOne(a[:], 15)+	}+	if ok {+		return ipFrom16Match(ip, a)+	}+	return IP{}+}++// Prior returns the IP before ip.+// If there is none, it returns the IP zero value.+func (ip IP) Prior() IP {+	var ok bool+	a := ip.As16()+	if ip.Is4() {+		ok = subOne(a[12:], 3)+	} else {+		ok = subOne(a[:], 15)+	}+	if ok {+		return ipFrom16Match(ip, a)+	}+	return IP{}+}++// IPRangeSet represents a set of IPRanges.+//+// The zero value is a valid value representing a set of no IP ranges.+//+// The Add and Remove methods add or remove ranges to the set.  Add+// methods should be called first, as a remove operation does nothing

This makes me a bit nervous. It seems too easy to misuse as an API. Maybe make the invariant stronger: Calling Remove after calling Add causes a panic. Or put another way: You must add all desired ranges, then remove any undesired ranges.

bradfitz

comment created time in a day

Pull request review commentinetaf/netaddr

Add IPRange, IPRangeSet & friends to work on sets of prefixes/ranges

 func (p *IPPrefix) UnmarshalText(text []byte) error { func (p IPPrefix) String() string { 	return fmt.Sprintf("%s/%d", p.IP, p.Bits) }++// LastIP returns the last IP in the prefix.+func (p IPPrefix) LastIP() IP {+	if p.IP.IsZero() {+		return IP{}+	}+	a16 := p.IP.As16()+	var off uint8+	var bits uint8 = 128+	if p.IP.Is4() {+		off = 12+		bits = 32+	}+	for b := p.Bits; b < bits; b++ {+		byteNum, bitInByte := b/8, 7-(b%8)+		a16[off+byteNum] |= 1 << uint(bitInByte)+	}+	if p.IP.Is4() {+		return IPFrom16(a16)+	} else {+		return IPv6Raw(a16) // doesn't unmap+	}+}++// IPRange represents an inclusive range of IP addresses+// from the same address family.+type IPRange struct {+	// From is the starting IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as To+	// and be less than or equal to To.+	// From is an inclusive bound; it is included in the set.+	From IP++	// To is the ending IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as From+	// and be greater than or equal to From.+	// To is an inclusive bound; it is included in the set.+	To IP+}++// Valid reports whether r.From and r.To are both non-zero and obey+// the documented requirements: address families match, and From is+// less than or equal to To.+func (r IPRange) Valid() bool {+	return !r.From.IsZero() && !r.To.IsZero() &&+		r.From.Is4() == r.To.Is4() &&+		!r.To.Less(r.From)+}++// ip16 represents a mutable IP address, either IPv4 (in IPv6-mapped+// form) or IPv6.+type ip16 [16]byte++// bitSet reports whether the given bit in the address is set.+// (bit 0 is the most significant bit in ip[0]; bit 127 is last)+func (ip ip16) bitSet(bit uint8) bool {+	i, s := bit/8, 7-(bit%8)+	return ip[i]&(1<<s) != 0+}++func (ip *ip16) set(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] |= 1 << s+}++func (ip *ip16) clear(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] &^= 1 << s+}++// lastWithBitZero returns a copy of ip with the given bit+// cleared and the following all set.+func (ip ip16) lastWithBitZero(bit uint8) ip16 {+	ip.clear(bit)+	for ; bit < 128; bit++ {+		ip.set(bit)+	}+	return ip+}++// firstWithBitOne returns a copy of ip with the given bit+// set and the following all cleared.+func (ip ip16) firstWithBitOne(bit uint8) ip16 {+	ip.set(bit)+	for ; bit < 128; bit++ {+		ip.clear(bit)+	}+	return ip+}++// prefixMaker returns a address-family-corrected IPPrefix from ip16 and bits,+// where the input bits is always in the IPv6-mapped form for IPv4 addresses.+type prefixMaker func(ip16 ip16, bits uint8) IPPrefix++// Prefixes returns the set of IPPrefix entries that covers r.+//+// If either of r's bounds are invalid, in the wrong order, or if+// they're of different address families, then Prefixes returns nil.+func (r IPRange) Prefixes() []IPPrefix {+	if !r.Valid() {+		return nil+	}+	var makePrefix prefixMaker+	if r.From.Is4() {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPFrom16([16]byte(ip16)), bits - 12*8}+		}+	} else {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPv6Raw([16]byte(ip16)), bits}+		}+	}+	a16, b16 := ip16(r.From.As16()), ip16(r.To.As16())+	return appendRangePrefixes(nil, makePrefix, a16, b16)+}++func appendRangePrefixes(dst []IPPrefix, makePrefix prefixMaker, a16, b16 ip16) []IPPrefix {+	common := uint8(0)+	for common < 128 && a16.bitSet(common) == b16.bitSet(common) {+		common+++	}+	// See whether a16 and b16, after their common shared bits, end+	// in all zero bits or all one bits, respectively.+	aAllZero, bAllSet := true, true+	for i := common; i < 128; i++ {+		if a16.bitSet(i) {+			aAllZero = false+			break+		}+	}+	for i := common; i < 128; i++ {+		if !b16.bitSet(i) {+			bAllSet = false+			break+		}+	}+	if aAllZero && bAllSet {+		// a16 to b16 represents a whole range, like 10.50.0.0/16.+		// (a16 being 10.50.0.0 and b16 being 10.50.255.255)+		return append(dst, makePrefix(a16, common))+	}+	// Otherwise recursively do both halves.+	dst = appendRangePrefixes(dst, makePrefix, a16, a16.lastWithBitZero(common+1))+	dst = appendRangePrefixes(dst, makePrefix, b16.firstWithBitOne(common+1), b16)+	return dst+}++func addOne(a []byte, i int) bool {+	if v := a[i]; v < 0xff {+		a[i]+++		return true+	}+	if i == 0 {+		return false+	}+	a[i] = 0+	return addOne(a, i-1)+}++func subOne(a []byte, i int) bool {+	if v := a[i]; v > 0 {+		a[i]--+		return true+	}+	if i == 0 {+		return false+	}+	a[i] = 0xff+	return subOne(a, i-1)+}++// ipFrom16Match returns an IP address from a with address family+// matching ip.+func ipFrom16Match(ip IP, a [16]byte) IP {+	if ip.Is6() {+		return IPv6Raw(a) // doesn't unwrap+	}+	return IPFrom16(a)+}++// Next returns the IP following ip.+// If there is none, it returns the IP zero value.+func (ip IP) Next() IP {+	var ok bool+	a := ip.As16()+	if ip.Is4() {+		ok = addOne(a[12:], 3)+	} else {+		ok = addOne(a[:], 15)+	}+	if ok {+		return ipFrom16Match(ip, a)+	}+	return IP{}+}++// Prior returns the IP before ip.+// If there is none, it returns the IP zero value.+func (ip IP) Prior() IP {+	var ok bool+	a := ip.As16()+	if ip.Is4() {+		ok = subOne(a[12:], 3)+	} else {+		ok = subOne(a[:], 15)+	}+	if ok {+		return ipFrom16Match(ip, a)+	}+	return IP{}+}++// IPRangeSet represents a set of IPRanges.+//+// The zero value is a valid value representing a set of no IP ranges.+//+// The Add and Remove methods add or remove ranges to the set.  Add+// methods should be called first, as a remove operation does nothing+// on an empty set. Ranges may be fully, partially, or not+// overlapping.+type IPRangeSet struct {+	// in are the ranges in the set.+	in []IPRange++	// out are the ranges to be removed from 'in'.+	out []IPRange+}++// AddPrefix adds p's range to s.+func (s *IPRangeSet) AddPrefix(p IPPrefix) { s.AddRange(p.Range()) }++// AddRange adds r to s.+func (s *IPRangeSet) AddRange(r IPRange) {+	if !r.Valid() {+		return+	}+	// If there are any removals (s.out), then we need to compact the set+	// first to get the order right.+	if len(s.out) > 0 {+		s.in = s.Ranges()+		s.out = nil+	}+	s.in = append(s.in, r)+}++// RemovePrefix removes p's range from s.+func (s *IPRangeSet) RemovePrefix(p IPPrefix) { s.RemoveRange(p.Range()) }++// RemoveRange removes r from s.+func (s *IPRangeSet) RemoveRange(r IPRange) {+	if r.Valid() {+		s.out = append(s.out, r)+	}+}++// AddSet adds all ranges in b to s.+func (s *IPRangeSet) AddSet(b *IPRangeSet) {+	for _, r := range b.Ranges() {+		s.AddRange(r)+	}+}++// RemoveSet removes all ranges in b from s.+func (s *IPRangeSet) RemoveSet(b *IPRangeSet) {+	for _, r := range b.Ranges() {+		s.RemoveRange(r)+	}+}++// point is either the start or end of IP range of wanted or unwanted+// IPs.+type point struct {+	ip    IP+	want  bool // true for 'add', false for remove+	start bool // true for start of range, false for (inclusive) end+}++func debugLogPoints(points []point) {+	for _, p := range points {+		emo := "✅"+		if !p.want {+			emo = "❌"+		}+		if p.start {+			log.Printf(" {  %-15s %s\n", p.ip, emo)+		} else {+			log.Printf("  } %-15s %s\n", p.ip, emo)+		}+	}+}++// Ranges removes the minimum and sorted set of IP+// ranges that covers s.+func (s *IPRangeSet) Ranges() []IPRange {+	var points []point+	for _, r := range s.in {+		points = append(points, point{r.From, true, true}, point{r.To, true, false})+	}+	for _, r := range s.out {+		points = append(points, point{r.From, false, true}, point{r.To, false, false})+	}+	sort.Slice(points, func(i, j int) bool {+		pi, pj := &points[i], &points[j]+		cmp := pi.ip.Compare(pj.ip)+		if cmp != 0 {+			return cmp < 0+		}+		if pi.want != pj.want {+			// Unwanted first.+			return !pi.want+		}+		if pi.start != pj.start {+			return pi.start+		}+		return false+	})+	const debug = false+	if debug {+		log.Printf("post-sort:")+		debugLogPoints(points)+		log.Printf("merging...")+	}++	// Now build 'want', like points but with "remove" ranges removed+	// and adjancent blocks merged, and all elements alternating between+	// start and end.+	want := points[:0]+	var addDepth, removeDepth int+	for i, p := range points {+		depth := &addDepth+		if !p.want {+			depth = &removeDepth+		}+		if p.start {+			*depth+++		} else {+			*depth--+		}+		if debug {+			log.Printf("at[%d] (%+v), add=%v, remove=%v", i, p, addDepth, removeDepth)+		}+		if p.start && *depth != 1 {+			continue+		}+		if !p.start && *depth != 0 {+			continue+		}+		if !p.want && addDepth > 0 {+			if p.start {+				// If we're transitioning from a range of+				// addresses we want to ones we don't, insert+				// an end marker for the IP before the one we+				// don't.+				want = append(want, point{+					ip:    p.ip.Prior(),+					want:  true,+					start: false,+				})+			} else {+				want = append(want, point{+					ip:    p.ip.Next(),+					want:  true,+					start: true,+				})+			}+		}+		if !p.want || removeDepth > 0 {+			continue+		}+		// Merge adjacent ranges. Remove prior and skip this+		// start.+		if p.start && len(want) > 0 {+			prior := &want[len(want)-1]+			if !prior.start && prior.ip == p.ip.Prior() {+				want = want[:len(want)-1]+				continue+			}+		}+		want = append(want, p)+	}+	if debug {+		log.Printf("post-merge:")+		debugLogPoints(want)+	}++	if len(want)%2 == 1 {+		panic("internal error; odd number")+	}++	out := make([]IPRange, 0, len(want)/2)+	for i := 0; i < len(want); i += 2 {+		if !want[i].want {+			panic("internal error; non-want in range")+		}+		if !want[i].start {+			panic("internal error; odd not start")+		}+		if want[i+1].start {+			panic("internal error; even not end")+		}+		out = append(out, IPRange{+			From: want[i].ip,+			To:   want[i+1].ip,+		})+	}+	return out+}++// Prefixes returns the minimum and sorted set of IP prefixes+// that covers s.+// returning a new slice of prefixes that covers all of the given 'add'

delete two stray lines?

bradfitz

comment created time in a day

Pull request review commentinetaf/netaddr

Add IPRange, IPRangeSet & friends to work on sets of prefixes/ranges

 func (p *IPPrefix) UnmarshalText(text []byte) error { func (p IPPrefix) String() string { 	return fmt.Sprintf("%s/%d", p.IP, p.Bits) }++// LastIP returns the last IP in the prefix.+func (p IPPrefix) LastIP() IP {+	if p.IP.IsZero() {+		return IP{}+	}+	a16 := p.IP.As16()+	var off uint8+	var bits uint8 = 128+	if p.IP.Is4() {+		off = 12+		bits = 32+	}+	for b := p.Bits; b < bits; b++ {+		byteNum, bitInByte := b/8, 7-(b%8)+		a16[off+byteNum] |= 1 << uint(bitInByte)+	}+	if p.IP.Is4() {+		return IPFrom16(a16)+	} else {+		return IPv6Raw(a16) // doesn't unmap+	}+}++// IPRange represents an inclusive range of IP addresses+// from the same address family.+type IPRange struct {+	// From is the starting IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as To+	// and be less than or equal to To.+	// From is an inclusive bound; it is included in the set.+	From IP++	// To is the ending IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as From+	// and be greater than or equal to From.+	// To is an inclusive bound; it is included in the set.+	To IP+}++// Valid reports whether r.From and r.To are both non-zero and obey+// the documented requirements: address families match, and From is+// less than or equal to To.+func (r IPRange) Valid() bool {+	return !r.From.IsZero() && !r.To.IsZero() &&+		r.From.Is4() == r.To.Is4() &&+		!r.To.Less(r.From)+}++// ip16 represents a mutable IP address, either IPv4 (in IPv6-mapped+// form) or IPv6.+type ip16 [16]byte++// bitSet reports whether the given bit in the address is set.+// (bit 0 is the most significant bit in ip[0]; bit 127 is last)+func (ip ip16) bitSet(bit uint8) bool {+	i, s := bit/8, 7-(bit%8)+	return ip[i]&(1<<s) != 0+}++func (ip *ip16) set(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] |= 1 << s+}++func (ip *ip16) clear(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] &^= 1 << s+}++// lastWithBitZero returns a copy of ip with the given bit+// cleared and the following all set.+func (ip ip16) lastWithBitZero(bit uint8) ip16 {+	ip.clear(bit)+	for ; bit < 128; bit++ {+		ip.set(bit)+	}+	return ip+}++// firstWithBitOne returns a copy of ip with the given bit+// set and the following all cleared.+func (ip ip16) firstWithBitOne(bit uint8) ip16 {+	ip.set(bit)+	for ; bit < 128; bit++ {+		ip.clear(bit)+	}+	return ip+}++// prefixMaker returns a address-family-corrected IPPrefix from ip16 and bits,+// where the input bits is always in the IPv6-mapped form for IPv4 addresses.+type prefixMaker func(ip16 ip16, bits uint8) IPPrefix++// Prefixes returns the set of IPPrefix entries that covers r.+//+// If either of r's bounds are invalid, in the wrong order, or if+// they're of different address families, then Prefixes returns nil.+func (r IPRange) Prefixes() []IPPrefix {+	if !r.Valid() {+		return nil+	}+	var makePrefix prefixMaker+	if r.From.Is4() {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPFrom16([16]byte(ip16)), bits - 12*8}+		}+	} else {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPv6Raw([16]byte(ip16)), bits}+		}+	}+	a16, b16 := ip16(r.From.As16()), ip16(r.To.As16())+	return appendRangePrefixes(nil, makePrefix, a16, b16)+}++func appendRangePrefixes(dst []IPPrefix, makePrefix prefixMaker, a16, b16 ip16) []IPPrefix {+	common := uint8(0)+	for common < 128 && a16.bitSet(common) == b16.bitSet(common) {+		common+++	}+	// See whether a16 and b16, after their common shared bits, end+	// in all zero bits or all one bits, respectively.+	aAllZero, bAllSet := true, true+	for i := common; i < 128; i++ {+		if a16.bitSet(i) {+			aAllZero = false+			break+		}+	}+	for i := common; i < 128; i++ {+		if !b16.bitSet(i) {+			bAllSet = false+			break+		}+	}+	if aAllZero && bAllSet {+		// a16 to b16 represents a whole range, like 10.50.0.0/16.+		// (a16 being 10.50.0.0 and b16 being 10.50.255.255)+		return append(dst, makePrefix(a16, common))+	}+	// Otherwise recursively do both halves.+	dst = appendRangePrefixes(dst, makePrefix, a16, a16.lastWithBitZero(common+1))+	dst = appendRangePrefixes(dst, makePrefix, b16.firstWithBitOne(common+1), b16)+	return dst+}++func addOne(a []byte, i int) bool {+	if v := a[i]; v < 0xff {+		a[i]+++		return true+	}+	if i == 0 {+		return false+	}+	a[i] = 0+	return addOne(a, i-1)+}++func subOne(a []byte, i int) bool {+	if v := a[i]; v > 0 {+		a[i]--+		return true+	}+	if i == 0 {+		return false+	}+	a[i] = 0xff+	return subOne(a, i-1)+}++// ipFrom16Match returns an IP address from a with address family+// matching ip.+func ipFrom16Match(ip IP, a [16]byte) IP {+	if ip.Is6() {+		return IPv6Raw(a) // doesn't unwrap+	}+	return IPFrom16(a)+}++// Next returns the IP following ip.+// If there is none, it returns the IP zero value.+func (ip IP) Next() IP {+	var ok bool+	a := ip.As16()+	if ip.Is4() {+		ok = addOne(a[12:], 3)+	} else {+		ok = addOne(a[:], 15)+	}+	if ok {+		return ipFrom16Match(ip, a)+	}+	return IP{}+}++// Prior returns the IP before ip.+// If there is none, it returns the IP zero value.+func (ip IP) Prior() IP {+	var ok bool+	a := ip.As16()+	if ip.Is4() {+		ok = subOne(a[12:], 3)+	} else {+		ok = subOne(a[:], 15)+	}+	if ok {+		return ipFrom16Match(ip, a)+	}+	return IP{}+}++// IPRangeSet represents a set of IPRanges.+//+// The zero value is a valid value representing a set of no IP ranges.+//+// The Add and Remove methods add or remove ranges to the set.  Add+// methods should be called first, as a remove operation does nothing+// on an empty set. Ranges may be fully, partially, or not+// overlapping.+type IPRangeSet struct {+	// in are the ranges in the set.+	in []IPRange++	// out are the ranges to be removed from 'in'.+	out []IPRange+}++// AddPrefix adds p's range to s.+func (s *IPRangeSet) AddPrefix(p IPPrefix) { s.AddRange(p.Range()) }++// AddRange adds r to s.+func (s *IPRangeSet) AddRange(r IPRange) {+	if !r.Valid() {+		return+	}+	// If there are any removals (s.out), then we need to compact the set+	// first to get the order right.+	if len(s.out) > 0 {+		s.in = s.Ranges()+		s.out = nil+	}+	s.in = append(s.in, r)+}++// RemovePrefix removes p's range from s.+func (s *IPRangeSet) RemovePrefix(p IPPrefix) { s.RemoveRange(p.Range()) }++// RemoveRange removes r from s.+func (s *IPRangeSet) RemoveRange(r IPRange) {+	if r.Valid() {+		s.out = append(s.out, r)+	}+}++// AddSet adds all ranges in b to s.+func (s *IPRangeSet) AddSet(b *IPRangeSet) {+	for _, r := range b.Ranges() {+		s.AddRange(r)+	}+}++// RemoveSet removes all ranges in b from s.+func (s *IPRangeSet) RemoveSet(b *IPRangeSet) {+	for _, r := range b.Ranges() {+		s.RemoveRange(r)+	}+}++// point is either the start or end of IP range of wanted or unwanted+// IPs.+type point struct {+	ip    IP+	want  bool // true for 'add', false for remove+	start bool // true for start of range, false for (inclusive) end+}++func debugLogPoints(points []point) {+	for _, p := range points {+		emo := "✅"+		if !p.want {+			emo = "❌"+		}+		if p.start {+			log.Printf(" {  %-15s %s\n", p.ip, emo)+		} else {+			log.Printf("  } %-15s %s\n", p.ip, emo)+		}+	}+}++// Ranges removes the minimum and sorted set of IP+// ranges that covers s.+func (s *IPRangeSet) Ranges() []IPRange {+	var points []point+	for _, r := range s.in {+		points = append(points, point{r.From, true, true}, point{r.To, true, false})+	}+	for _, r := range s.out {+		points = append(points, point{r.From, false, true}, point{r.To, false, false})+	}+	sort.Slice(points, func(i, j int) bool {+		pi, pj := &points[i], &points[j]+		cmp := pi.ip.Compare(pj.ip)+		if cmp != 0 {+			return cmp < 0+		}+		if pi.want != pj.want {+			// Unwanted first.+			return !pi.want+		}+		if pi.start != pj.start {+			return pi.start+		}+		return false+	})+	const debug = false+	if debug {+		log.Printf("post-sort:")+		debugLogPoints(points)+		log.Printf("merging...")+	}++	// Now build 'want', like points but with "remove" ranges removed+	// and adjancent blocks merged, and all elements alternating between

adjacent

bradfitz

comment created time in a day

Pull request review commentinetaf/netaddr

Add IPRange, IPRangeSet & friends to work on sets of prefixes/ranges

 func (p *IPPrefix) UnmarshalText(text []byte) error { func (p IPPrefix) String() string { 	return fmt.Sprintf("%s/%d", p.IP, p.Bits) }++// LastIP returns the last IP in the prefix.+func (p IPPrefix) LastIP() IP {+	if p.IP.IsZero() {+		return IP{}+	}+	a16 := p.IP.As16()+	var off uint8+	var bits uint8 = 128+	if p.IP.Is4() {+		off = 12+		bits = 32+	}+	for b := p.Bits; b < bits; b++ {+		byteNum, bitInByte := b/8, 7-(b%8)+		a16[off+byteNum] |= 1 << uint(bitInByte)+	}+	if p.IP.Is4() {+		return IPFrom16(a16)+	} else {+		return IPv6Raw(a16) // doesn't unmap+	}+}++// IPRange represents an inclusive range of IP addresses+// from the same address family.+type IPRange struct {+	// From is the starting IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as To+	// and be less than or equal to To.+	// From is an inclusive bound; it is included in the set.+	From IP++	// To is the ending IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as From+	// and be greater than or equal to From.+	// To is an inclusive bound; it is included in the set.+	To IP+}++// Valid reports whether r.From and r.To are both non-zero and obey+// the documented requirements: address families match, and From is+// less than or equal to To.+func (r IPRange) Valid() bool {+	return !r.From.IsZero() && !r.To.IsZero() &&+		r.From.Is4() == r.To.Is4() &&+		!r.To.Less(r.From)+}++// ip16 represents a mutable IP address, either IPv4 (in IPv6-mapped+// form) or IPv6.+type ip16 [16]byte++// bitSet reports whether the given bit in the address is set.+// (bit 0 is the most significant bit in ip[0]; bit 127 is last)+func (ip ip16) bitSet(bit uint8) bool {+	i, s := bit/8, 7-(bit%8)+	return ip[i]&(1<<s) != 0+}++func (ip *ip16) set(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] |= 1 << s+}++func (ip *ip16) clear(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] &^= 1 << s+}++// lastWithBitZero returns a copy of ip with the given bit+// cleared and the following all set.+func (ip ip16) lastWithBitZero(bit uint8) ip16 {+	ip.clear(bit)+	for ; bit < 128; bit++ {+		ip.set(bit)+	}+	return ip+}++// firstWithBitOne returns a copy of ip with the given bit+// set and the following all cleared.+func (ip ip16) firstWithBitOne(bit uint8) ip16 {+	ip.set(bit)+	for ; bit < 128; bit++ {+		ip.clear(bit)+	}+	return ip+}++// prefixMaker returns a address-family-corrected IPPrefix from ip16 and bits,+// where the input bits is always in the IPv6-mapped form for IPv4 addresses.+type prefixMaker func(ip16 ip16, bits uint8) IPPrefix++// Prefixes returns the set of IPPrefix entries that covers r.+//+// If either of r's bounds are invalid, in the wrong order, or if+// they're of different address families, then Prefixes returns nil.+func (r IPRange) Prefixes() []IPPrefix {+	if !r.Valid() {+		return nil+	}+	var makePrefix prefixMaker+	if r.From.Is4() {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPFrom16([16]byte(ip16)), bits - 12*8}+		}+	} else {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPv6Raw([16]byte(ip16)), bits}+		}+	}+	a16, b16 := ip16(r.From.As16()), ip16(r.To.As16())+	return appendRangePrefixes(nil, makePrefix, a16, b16)+}++func appendRangePrefixes(dst []IPPrefix, makePrefix prefixMaker, a16, b16 ip16) []IPPrefix {+	common := uint8(0)+	for common < 128 && a16.bitSet(common) == b16.bitSet(common) {+		common+++	}+	// See whether a16 and b16, after their common shared bits, end+	// in all zero bits or all one bits, respectively.+	aAllZero, bAllSet := true, true+	for i := common; i < 128; i++ {+		if a16.bitSet(i) {+			aAllZero = false+			break+		}+	}+	for i := common; i < 128; i++ {+		if !b16.bitSet(i) {+			bAllSet = false+			break+		}+	}+	if aAllZero && bAllSet {+		// a16 to b16 represents a whole range, like 10.50.0.0/16.+		// (a16 being 10.50.0.0 and b16 being 10.50.255.255)+		return append(dst, makePrefix(a16, common))+	}+	// Otherwise recursively do both halves.+	dst = appendRangePrefixes(dst, makePrefix, a16, a16.lastWithBitZero(common+1))+	dst = appendRangePrefixes(dst, makePrefix, b16.firstWithBitOne(common+1), b16)+	return dst+}++func addOne(a []byte, i int) bool {+	if v := a[i]; v < 0xff {+		a[i]+++		return true+	}+	if i == 0 {+		return false+	}+	a[i] = 0+	return addOne(a, i-1)+}++func subOne(a []byte, i int) bool {+	if v := a[i]; v > 0 {+		a[i]--+		return true+	}+	if i == 0 {+		return false+	}+	a[i] = 0xff+	return subOne(a, i-1)+}++// ipFrom16Match returns an IP address from a with address family+// matching ip.+func ipFrom16Match(ip IP, a [16]byte) IP {+	if ip.Is6() {+		return IPv6Raw(a) // doesn't unwrap+	}+	return IPFrom16(a)+}++// Next returns the IP following ip.+// If there is none, it returns the IP zero value.+func (ip IP) Next() IP {+	var ok bool+	a := ip.As16()+	if ip.Is4() {+		ok = addOne(a[12:], 3)+	} else {+		ok = addOne(a[:], 15)+	}+	if ok {+		return ipFrom16Match(ip, a)+	}+	return IP{}+}++// Prior returns the IP before ip.+// If there is none, it returns the IP zero value.+func (ip IP) Prior() IP {+	var ok bool+	a := ip.As16()+	if ip.Is4() {+		ok = subOne(a[12:], 3)+	} else {+		ok = subOne(a[:], 15)+	}+	if ok {+		return ipFrom16Match(ip, a)+	}+	return IP{}+}++// IPRangeSet represents a set of IPRanges.+//+// The zero value is a valid value representing a set of no IP ranges.+//+// The Add and Remove methods add or remove ranges to the set.  Add

remove double space after period

bradfitz

comment created time in a day

Pull request review commentinetaf/netaddr

Add IPRange, IPRangeSet & friends to work on sets of prefixes/ranges

 func (p *IPPrefix) UnmarshalText(text []byte) error { func (p IPPrefix) String() string { 	return fmt.Sprintf("%s/%d", p.IP, p.Bits) }++// LastIP returns the last IP in the prefix.+func (p IPPrefix) LastIP() IP {+	if p.IP.IsZero() {+		return IP{}+	}+	a16 := p.IP.As16()+	var off uint8+	var bits uint8 = 128+	if p.IP.Is4() {+		off = 12+		bits = 32+	}+	for b := p.Bits; b < bits; b++ {+		byteNum, bitInByte := b/8, 7-(b%8)+		a16[off+byteNum] |= 1 << uint(bitInByte)+	}+	if p.IP.Is4() {+		return IPFrom16(a16)+	} else {+		return IPv6Raw(a16) // doesn't unmap+	}+}++// IPRange represents an inclusive range of IP addresses+// from the same address family.+type IPRange struct {+	// From is the starting IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as To+	// and be less than or equal to To.+	// From is an inclusive bound; it is included in the set.+	From IP++	// To is the ending IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as From+	// and be greater than or equal to From.+	// To is an inclusive bound; it is included in the set.+	To IP+}++// Valid reports whether r.From and r.To are both non-zero and obey+// the documented requirements: address families match, and From is+// less than or equal to To.+func (r IPRange) Valid() bool {+	return !r.From.IsZero() && !r.To.IsZero() &&+		r.From.Is4() == r.To.Is4() &&+		!r.To.Less(r.From)+}++// ip16 represents a mutable IP address, either IPv4 (in IPv6-mapped+// form) or IPv6.+type ip16 [16]byte++// bitSet reports whether the given bit in the address is set.+// (bit 0 is the most significant bit in ip[0]; bit 127 is last)+func (ip ip16) bitSet(bit uint8) bool {+	i, s := bit/8, 7-(bit%8)+	return ip[i]&(1<<s) != 0+}++func (ip *ip16) set(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] |= 1 << s+}++func (ip *ip16) clear(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] &^= 1 << s+}++// lastWithBitZero returns a copy of ip with the given bit+// cleared and the following all set.+func (ip ip16) lastWithBitZero(bit uint8) ip16 {+	ip.clear(bit)+	for ; bit < 128; bit++ {+		ip.set(bit)+	}+	return ip+}++// firstWithBitOne returns a copy of ip with the given bit+// set and the following all cleared.+func (ip ip16) firstWithBitOne(bit uint8) ip16 {+	ip.set(bit)+	for ; bit < 128; bit++ {+		ip.clear(bit)+	}+	return ip+}++// prefixMaker returns a address-family-corrected IPPrefix from ip16 and bits,+// where the input bits is always in the IPv6-mapped form for IPv4 addresses.+type prefixMaker func(ip16 ip16, bits uint8) IPPrefix++// Prefixes returns the set of IPPrefix entries that covers r.+//+// If either of r's bounds are invalid, in the wrong order, or if+// they're of different address families, then Prefixes returns nil.+func (r IPRange) Prefixes() []IPPrefix {+	if !r.Valid() {+		return nil+	}+	var makePrefix prefixMaker+	if r.From.Is4() {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPFrom16([16]byte(ip16)), bits - 12*8}+		}+	} else {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPv6Raw([16]byte(ip16)), bits}+		}+	}+	a16, b16 := ip16(r.From.As16()), ip16(r.To.As16())+	return appendRangePrefixes(nil, makePrefix, a16, b16)+}++func appendRangePrefixes(dst []IPPrefix, makePrefix prefixMaker, a16, b16 ip16) []IPPrefix {+	common := uint8(0)+	for common < 128 && a16.bitSet(common) == b16.bitSet(common) {+		common+++	}+	// See whether a16 and b16, after their common shared bits, end+	// in all zero bits or all one bits, respectively.+	aAllZero, bAllSet := true, true+	for i := common; i < 128; i++ {+		if a16.bitSet(i) {+			aAllZero = false+			break+		}+	}+	for i := common; i < 128; i++ {+		if !b16.bitSet(i) {+			bAllSet = false+			break+		}+	}+	if aAllZero && bAllSet {+		// a16 to b16 represents a whole range, like 10.50.0.0/16.+		// (a16 being 10.50.0.0 and b16 being 10.50.255.255)+		return append(dst, makePrefix(a16, common))+	}+	// Otherwise recursively do both halves.+	dst = appendRangePrefixes(dst, makePrefix, a16, a16.lastWithBitZero(common+1))+	dst = appendRangePrefixes(dst, makePrefix, b16.firstWithBitOne(common+1), b16)+	return dst+}++func addOne(a []byte, i int) bool {

This is fine as-is, but an alternative is to accept just a byte slice and use the end of the slice to locate i. Then reslice to recurse (or better make iterative). Then call sites look like addOne(a[12:15]) instead of addOne(a[12:], 3), which strikes me as a bit clearer.

bradfitz

comment created time in a day

Pull request review commentinetaf/netaddr

Add IPRange, IPRangeSet & friends to work on sets of prefixes/ranges

 func (p *IPPrefix) UnmarshalText(text []byte) error { func (p IPPrefix) String() string { 	return fmt.Sprintf("%s/%d", p.IP, p.Bits) }++// LastIP returns the last IP in the prefix.+func (p IPPrefix) LastIP() IP {+	if p.IP.IsZero() {+		return IP{}+	}+	a16 := p.IP.As16()+	var off uint8+	var bits uint8 = 128+	if p.IP.Is4() {+		off = 12+		bits = 32+	}+	for b := p.Bits; b < bits; b++ {+		byteNum, bitInByte := b/8, 7-(b%8)+		a16[off+byteNum] |= 1 << uint(bitInByte)+	}+	if p.IP.Is4() {+		return IPFrom16(a16)+	} else {+		return IPv6Raw(a16) // doesn't unmap+	}+}++// IPRange represents an inclusive range of IP addresses+// from the same address family.+type IPRange struct {+	// From is the starting IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as To+	// and be less than or equal to To.+	// From is an inclusive bound; it is included in the set.+	From IP++	// To is the ending IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as From+	// and be greater than or equal to From.+	// To is an inclusive bound; it is included in the set.+	To IP+}++// Valid reports whether r.From and r.To are both non-zero and obey+// the documented requirements: address families match, and From is+// less than or equal to To.+func (r IPRange) Valid() bool {+	return !r.From.IsZero() && !r.To.IsZero() &&+		r.From.Is4() == r.To.Is4() &&+		!r.To.Less(r.From)+}++// ip16 represents a mutable IP address, either IPv4 (in IPv6-mapped+// form) or IPv6.+type ip16 [16]byte++// bitSet reports whether the given bit in the address is set.+// (bit 0 is the most significant bit in ip[0]; bit 127 is last)+func (ip ip16) bitSet(bit uint8) bool {+	i, s := bit/8, 7-(bit%8)+	return ip[i]&(1<<s) != 0+}++func (ip *ip16) set(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] |= 1 << s+}++func (ip *ip16) clear(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] &^= 1 << s+}++// lastWithBitZero returns a copy of ip with the given bit+// cleared and the following all set.+func (ip ip16) lastWithBitZero(bit uint8) ip16 {+	ip.clear(bit)+	for ; bit < 128; bit++ {+		ip.set(bit)+	}+	return ip+}++// firstWithBitOne returns a copy of ip with the given bit+// set and the following all cleared.+func (ip ip16) firstWithBitOne(bit uint8) ip16 {+	ip.set(bit)+	for ; bit < 128; bit++ {+		ip.clear(bit)+	}+	return ip+}++// prefixMaker returns a address-family-corrected IPPrefix from ip16 and bits,+// where the input bits is always in the IPv6-mapped form for IPv4 addresses.+type prefixMaker func(ip16 ip16, bits uint8) IPPrefix++// Prefixes returns the set of IPPrefix entries that covers r.+//+// If either of r's bounds are invalid, in the wrong order, or if+// they're of different address families, then Prefixes returns nil.+func (r IPRange) Prefixes() []IPPrefix {+	if !r.Valid() {+		return nil+	}+	var makePrefix prefixMaker+	if r.From.Is4() {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPFrom16([16]byte(ip16)), bits - 12*8}+		}+	} else {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPv6Raw([16]byte(ip16)), bits}+		}+	}+	a16, b16 := ip16(r.From.As16()), ip16(r.To.As16())+	return appendRangePrefixes(nil, makePrefix, a16, b16)+}++func appendRangePrefixes(dst []IPPrefix, makePrefix prefixMaker, a16, b16 ip16) []IPPrefix {+	common := uint8(0)+	for common < 128 && a16.bitSet(common) == b16.bitSet(common) {+		common+++	}+	// See whether a16 and b16, after their common shared bits, end+	// in all zero bits or all one bits, respectively.+	aAllZero, bAllSet := true, true

This can be a single bool; you can combine the following two loops.

allZeroOne := true
for i := common; i < 128; i++ {
  if a16.bitSet(i) || !b16.bitSet(i) {
    allZeroOne = false
    break
  }
}
bradfitz

comment created time in a day

Pull request review commentinetaf/netaddr

Add IPRange, IPRangeSet & friends to work on sets of prefixes/ranges

 func (p *IPPrefix) UnmarshalText(text []byte) error { func (p IPPrefix) String() string { 	return fmt.Sprintf("%s/%d", p.IP, p.Bits) }++// LastIP returns the last IP in the prefix.+func (p IPPrefix) LastIP() IP {+	if p.IP.IsZero() {+		return IP{}+	}+	a16 := p.IP.As16()+	var off uint8+	var bits uint8 = 128+	if p.IP.Is4() {+		off = 12+		bits = 32+	}+	for b := p.Bits; b < bits; b++ {+		byteNum, bitInByte := b/8, 7-(b%8)+		a16[off+byteNum] |= 1 << uint(bitInByte)+	}+	if p.IP.Is4() {+		return IPFrom16(a16)+	} else {+		return IPv6Raw(a16) // doesn't unmap+	}+}++// IPRange represents an inclusive range of IP addresses+// from the same address family.+type IPRange struct {+	// From is the starting IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as To+	// and be less than or equal to To.+	// From is an inclusive bound; it is included in the set.+	From IP++	// To is the ending IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as From+	// and be greater than or equal to From.+	// To is an inclusive bound; it is included in the set.+	To IP+}++// Valid reports whether r.From and r.To are both non-zero and obey+// the documented requirements: address families match, and From is+// less than or equal to To.+func (r IPRange) Valid() bool {+	return !r.From.IsZero() && !r.To.IsZero() &&+		r.From.Is4() == r.To.Is4() &&+		!r.To.Less(r.From)+}++// ip16 represents a mutable IP address, either IPv4 (in IPv6-mapped+// form) or IPv6.+type ip16 [16]byte++// bitSet reports whether the given bit in the address is set.+// (bit 0 is the most significant bit in ip[0]; bit 127 is last)+func (ip ip16) bitSet(bit uint8) bool {+	i, s := bit/8, 7-(bit%8)+	return ip[i]&(1<<s) != 0+}++func (ip *ip16) set(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] |= 1 << s+}++func (ip *ip16) clear(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] &^= 1 << s+}++// lastWithBitZero returns a copy of ip with the given bit+// cleared and the following all set.+func (ip ip16) lastWithBitZero(bit uint8) ip16 {+	ip.clear(bit)+	for ; bit < 128; bit++ {+		ip.set(bit)+	}+	return ip+}++// firstWithBitOne returns a copy of ip with the given bit+// set and the following all cleared.+func (ip ip16) firstWithBitOne(bit uint8) ip16 {+	ip.set(bit)+	for ; bit < 128; bit++ {+		ip.clear(bit)+	}+	return ip+}++// prefixMaker returns a address-family-corrected IPPrefix from ip16 and bits,

an

bradfitz

comment created time in a day

startedmdlayher/schedgroup

started time in a day

CommitCommentEvent

push eventinetaf/netaddr

Brad Fitzpatrick

commit sha 70d7e82110eee1dc3e673329167b88d8cfd3998a

Update IPSet docs after rename.

view details

Brad Fitzpatrick

commit sha 3c8588dd0e81f4d142e64c91bfb02f3591888c4b

Add basic IPSet.Ranges fuzzing, fix bugs, pull out Point.Less

view details

push time in 2 days

fork klauspost/fwd

Buffered Reader/Writer

fork in 2 days

Pull request review commentinetaf/netaddr

Add IPRange, IPRangeSet & friends to work on sets of prefixes/ranges

 func (p *IPPrefix) UnmarshalText(text []byte) error { func (p IPPrefix) String() string { 	return fmt.Sprintf("%s/%d", p.IP, p.Bits) }++// LastIP returns the last IP in the prefix.+func (p IPPrefix) LastIP() IP {+	if p.IP.IsZero() {+		return IP{}+	}+	a16 := p.IP.As16()+	var off uint8+	var bits uint8 = 128+	if p.IP.Is4() {+		off = 12+		bits = 32+	}+	for b := p.Bits; b < bits; b++ {+		byteNum, bitInByte := b/8, 7-(b%8)+		a16[off+byteNum] |= 1 << uint(bitInByte)+	}+	if p.IP.Is4() {+		return IPFrom16(a16)+	} else {+		return IPv6Raw(a16) // doesn't unmap+	}+}++// IPRange represents an inclusive range of IP addresses+// from the same address family.+type IPRange struct {+	// From is the starting IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as To+	// and be less than or equal to To.+	// From is an inclusive bound; it is included in the set.+	From IP++	// To is the ending IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as From+	// and be greater than or equal to From.+	// To is an inclusive bound; it is included in the set.+	To IP+}++// Valid reports whether r.From and r.To are both non-zero and obey+// the documented requirements: address families match, and From is+// less than or equal to To.+func (r IPRange) Valid() bool {

Also IPPrefix, which can have invalid values (bits over 128) from literal construction.

bradfitz

comment created time in 2 days

push eventinetaf/netaddr

Brad Fitzpatrick

commit sha 9c9c19cb4b056d8d1dcc7513a5bfc71b9eff37c3

rename IPRangeSet to IPSet, per @danderson review comments

view details

push time in 2 days

Pull request review commentinetaf/netaddr

Add IPRange, IPRangeSet & friends to work on sets of prefixes/ranges

 func (p *IPPrefix) UnmarshalText(text []byte) error { func (p IPPrefix) String() string { 	return fmt.Sprintf("%s/%d", p.IP, p.Bits) }++// LastIP returns the last IP in the prefix.+func (p IPPrefix) LastIP() IP {+	if p.IP.IsZero() {+		return IP{}+	}+	a16 := p.IP.As16()+	var off uint8+	var bits uint8 = 128+	if p.IP.Is4() {+		off = 12+		bits = 32+	}+	for b := p.Bits; b < bits; b++ {+		byteNum, bitInByte := b/8, 7-(b%8)+		a16[off+byteNum] |= 1 << uint(bitInByte)+	}+	if p.IP.Is4() {+		return IPFrom16(a16)+	} else {+		return IPv6Raw(a16) // doesn't unmap+	}+}++// IPRange represents an inclusive range of IP addresses+// from the same address family.+type IPRange struct {+	// From is the starting IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as To+	// and be less than or equal to To.+	// From is an inclusive bound; it is included in the set.+	From IP++	// To is the ending IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as From+	// and be greater than or equal to From.+	// To is an inclusive bound; it is included in the set.+	To IP+}++// Valid reports whether r.From and r.To are both non-zero and obey+// the documented requirements: address families match, and From is+// less than or equal to To.+func (r IPRange) Valid() bool {+	return !r.From.IsZero() && !r.To.IsZero() &&+		r.From.Is4() == r.To.Is4() &&+		!r.To.Less(r.From)+}++// ip16 represents a mutable IP address, either IPv4 (in IPv6-mapped+// form) or IPv6.+type ip16 [16]byte++// bitSet reports whether the given bit in the address is set.+// (bit 0 is the most significant bit in ip[0]; bit 127 is last)+func (ip ip16) bitSet(bit uint8) bool {+	i, s := bit/8, 7-(bit%8)+	return ip[i]&(1<<s) != 0+}++func (ip *ip16) set(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] |= 1 << s+}++func (ip *ip16) clear(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] &^= 1 << s+}++// lastWithBitZero returns a copy of ip with the given bit+// cleared and the following all set.+func (ip ip16) lastWithBitZero(bit uint8) ip16 {+	ip.clear(bit)+	for ; bit < 128; bit++ {+		ip.set(bit)+	}+	return ip+}++// firstWithBitOne returns a copy of ip with the given bit+// set and the following all cleared.+func (ip ip16) firstWithBitOne(bit uint8) ip16 {+	ip.set(bit)+	for ; bit < 128; bit++ {+		ip.clear(bit)+	}+	return ip+}++// prefixMaker returns a address-family-corrected IPPrefix from ip16 and bits,+// where the input bits is always in the IPv6-mapped form for IPv4 addresses.+type prefixMaker func(ip16 ip16, bits uint8) IPPrefix++// Prefixes returns the set of IPPrefix entries that covers r.+//+// If either of r's bounds are invalid, in the wrong order, or if+// they're of different address families, then Prefixes returns nil.+func (r IPRange) Prefixes() []IPPrefix {+	if !r.Valid() {+		return nil+	}+	var makePrefix prefixMaker+	if r.From.Is4() {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPFrom16([16]byte(ip16)), bits - 12*8}+		}+	} else {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPv6Raw([16]byte(ip16)), bits}+		}+	}+	a16, b16 := ip16(r.From.As16()), ip16(r.To.As16())+	return appendRangePrefixes(nil, makePrefix, a16, b16)+}++func appendRangePrefixes(dst []IPPrefix, makePrefix prefixMaker, a16, b16 ip16) []IPPrefix {+	common := uint8(0)+	for common < 128 && a16.bitSet(common) == b16.bitSet(common) {+		common+++	}+	// See whether a16 and b16, after their common shared bits, end+	// in all zero bits or all one bits, respectively.+	aAllZero, bAllSet := true, true+	for i := common; i < 128; i++ {+		if a16.bitSet(i) {+			aAllZero = false+			break+		}+	}+	for i := common; i < 128; i++ {+		if !b16.bitSet(i) {+			bAllSet = false+			break+		}+	}+	if aAllZero && bAllSet {+		// a16 to b16 represents a whole range, like 10.50.0.0/16.+		// (a16 being 10.50.0.0 and b16 being 10.50.255.255)+		return append(dst, makePrefix(a16, common))+	}+	// Otherwise recursively do both halves.+	dst = appendRangePrefixes(dst, makePrefix, a16, a16.lastWithBitZero(common+1))+	dst = appendRangePrefixes(dst, makePrefix, b16.firstWithBitOne(common+1), b16)+	return dst+}++func addOne(a []byte, i int) bool {+	if v := a[i]; v < 0xff {+		a[i]+++		return true+	}+	if i == 0 {+		return false+	}+	a[i] = 0+	return addOne(a, i-1)+}++func subOne(a []byte, i int) bool {+	if v := a[i]; v > 0 {+		a[i]--+		return true+	}+	if i == 0 {+		return false+	}+	a[i] = 0xff+	return subOne(a, i-1)+}++// ipFrom16Match returns an IP address from a with address family+// matching ip.+func ipFrom16Match(ip IP, a [16]byte) IP {+	if ip.Is6() {+		return IPv6Raw(a) // doesn't unwrap+	}+	return IPFrom16(a)+}++// Next returns the IP following ip.+// If there is none, it returns the IP zero value.+func (ip IP) Next() IP {+	var ok bool+	a := ip.As16()+	if ip.Is4() {+		ok = addOne(a[12:], 3)+	} else {+		ok = addOne(a[:], 15)+	}+	if ok {+		return ipFrom16Match(ip, a)+	}+	return IP{}+}++// Prior returns the IP before ip.+// If there is none, it returns the IP zero value.+func (ip IP) Prior() IP {+	var ok bool+	a := ip.As16()+	if ip.Is4() {+		ok = subOne(a[12:], 3)+	} else {+		ok = subOne(a[:], 15)+	}+	if ok {+		return ipFrom16Match(ip, a)+	}+	return IP{}+}++// IPRangeSet represents a set of IPRanges.

done.

bradfitz

comment created time in 2 days

Pull request review commentinetaf/netaddr

Add IPRange, IPRangeSet & friends to work on sets of prefixes/ranges

 func (p *IPPrefix) UnmarshalText(text []byte) error { func (p IPPrefix) String() string { 	return fmt.Sprintf("%s/%d", p.IP, p.Bits) }++// LastIP returns the last IP in the prefix.+func (p IPPrefix) LastIP() IP {+	if p.IP.IsZero() {+		return IP{}+	}+	a16 := p.IP.As16()+	var off uint8+	var bits uint8 = 128+	if p.IP.Is4() {+		off = 12+		bits = 32+	}+	for b := p.Bits; b < bits; b++ {+		byteNum, bitInByte := b/8, 7-(b%8)+		a16[off+byteNum] |= 1 << uint(bitInByte)+	}+	if p.IP.Is4() {+		return IPFrom16(a16)+	} else {+		return IPv6Raw(a16) // doesn't unmap+	}+}++// IPRange represents an inclusive range of IP addresses+// from the same address family.+type IPRange struct {+	// From is the starting IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as To+	// and be less than or equal to To.+	// From is an inclusive bound; it is included in the set.+	From IP++	// To is the ending IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as From+	// and be greater than or equal to From.+	// To is an inclusive bound; it is included in the set.+	To IP+}++// Valid reports whether r.From and r.To are both non-zero and obey+// the documented requirements: address families match, and From is+// less than or equal to To.+func (r IPRange) Valid() bool {

Well, we have IPPort already that's also with two exported fields. Felt consistent?

bradfitz

comment created time in 2 days

Pull request review commentinetaf/netaddr

Add IPRange, IPRangeSet & friends to work on sets of prefixes/ranges

 func (p *IPPrefix) UnmarshalText(text []byte) error { func (p IPPrefix) String() string { 	return fmt.Sprintf("%s/%d", p.IP, p.Bits) }++// LastIP returns the last IP in the prefix.+func (p IPPrefix) LastIP() IP {+	if p.IP.IsZero() {+		return IP{}+	}+	a16 := p.IP.As16()+	var off uint8+	var bits uint8 = 128+	if p.IP.Is4() {+		off = 12+		bits = 32+	}+	for b := p.Bits; b < bits; b++ {+		byteNum, bitInByte := b/8, 7-(b%8)+		a16[off+byteNum] |= 1 << uint(bitInByte)+	}+	if p.IP.Is4() {+		return IPFrom16(a16)+	} else {+		return IPv6Raw(a16) // doesn't unmap+	}+}++// IPRange represents an inclusive range of IP addresses+// from the same address family.+type IPRange struct {+	// From is the starting IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as To+	// and be less than or equal to To.+	// From is an inclusive bound; it is included in the set.+	From IP++	// To is the ending IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as From+	// and be greater than or equal to From.+	// To is an inclusive bound; it is included in the set.+	To IP+}++// Valid reports whether r.From and r.To are both non-zero and obey+// the documented requirements: address families match, and From is+// less than or equal to To.+func (r IPRange) Valid() bool {+	return !r.From.IsZero() && !r.To.IsZero() &&+		r.From.Is4() == r.To.Is4() &&+		!r.To.Less(r.From)+}++// ip16 represents a mutable IP address, either IPv4 (in IPv6-mapped+// form) or IPv6.+type ip16 [16]byte++// bitSet reports whether the given bit in the address is set.+// (bit 0 is the most significant bit in ip[0]; bit 127 is last)+func (ip ip16) bitSet(bit uint8) bool {+	i, s := bit/8, 7-(bit%8)+	return ip[i]&(1<<s) != 0+}++func (ip *ip16) set(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] |= 1 << s+}++func (ip *ip16) clear(bit uint8) {+	i, s := bit/8, 7-(bit%8)+	ip[i] &^= 1 << s+}++// lastWithBitZero returns a copy of ip with the given bit+// cleared and the following all set.+func (ip ip16) lastWithBitZero(bit uint8) ip16 {+	ip.clear(bit)+	for ; bit < 128; bit++ {+		ip.set(bit)+	}+	return ip+}++// firstWithBitOne returns a copy of ip with the given bit+// set and the following all cleared.+func (ip ip16) firstWithBitOne(bit uint8) ip16 {+	ip.set(bit)+	for ; bit < 128; bit++ {+		ip.clear(bit)+	}+	return ip+}++// prefixMaker returns a address-family-corrected IPPrefix from ip16 and bits,+// where the input bits is always in the IPv6-mapped form for IPv4 addresses.+type prefixMaker func(ip16 ip16, bits uint8) IPPrefix++// Prefixes returns the set of IPPrefix entries that covers r.+//+// If either of r's bounds are invalid, in the wrong order, or if+// they're of different address families, then Prefixes returns nil.+func (r IPRange) Prefixes() []IPPrefix {+	if !r.Valid() {+		return nil+	}+	var makePrefix prefixMaker+	if r.From.Is4() {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPFrom16([16]byte(ip16)), bits - 12*8}+		}+	} else {+		makePrefix = func(ip16 ip16, bits uint8) IPPrefix {+			return IPPrefix{IPv6Raw([16]byte(ip16)), bits}+		}+	}+	a16, b16 := ip16(r.From.As16()), ip16(r.To.As16())+	return appendRangePrefixes(nil, makePrefix, a16, b16)+}++func appendRangePrefixes(dst []IPPrefix, makePrefix prefixMaker, a16, b16 ip16) []IPPrefix {+	common := uint8(0)+	for common < 128 && a16.bitSet(common) == b16.bitSet(common) {+		common+++	}+	// See whether a16 and b16, after their common shared bits, end+	// in all zero bits or all one bits, respectively.+	aAllZero, bAllSet := true, true+	for i := common; i < 128; i++ {+		if a16.bitSet(i) {+			aAllZero = false+			break+		}+	}+	for i := common; i < 128; i++ {+		if !b16.bitSet(i) {+			bAllSet = false+			break+		}+	}+	if aAllZero && bAllSet {+		// a16 to b16 represents a whole range, like 10.50.0.0/16.+		// (a16 being 10.50.0.0 and b16 being 10.50.255.255)+		return append(dst, makePrefix(a16, common))+	}+	// Otherwise recursively do both halves.+	dst = appendRangePrefixes(dst, makePrefix, a16, a16.lastWithBitZero(common+1))+	dst = appendRangePrefixes(dst, makePrefix, b16.firstWithBitOne(common+1), b16)+	return dst+}++func addOne(a []byte, i int) bool {+	if v := a[i]; v < 0xff {+		a[i]+++		return true+	}+	if i == 0 {+		return false+	}+	a[i] = 0+	return addOne(a, i-1)+}++func subOne(a []byte, i int) bool {+	if v := a[i]; v > 0 {+		a[i]--+		return true+	}+	if i == 0 {+		return false+	}+	a[i] = 0xff+	return subOne(a, i-1)+}++// ipFrom16Match returns an IP address from a with address family+// matching ip.+func ipFrom16Match(ip IP, a [16]byte) IP {+	if ip.Is6() {+		return IPv6Raw(a) // doesn't unwrap+	}+	return IPFrom16(a)+}++// Next returns the IP following ip.+// If there is none, it returns the IP zero value.+func (ip IP) Next() IP {+	var ok bool+	a := ip.As16()+	if ip.Is4() {+		ok = addOne(a[12:], 3)+	} else {+		ok = addOne(a[:], 15)+	}+	if ok {+		return ipFrom16Match(ip, a)+	}+	return IP{}+}++// Prior returns the IP before ip.+// If there is none, it returns the IP zero value.+func (ip IP) Prior() IP {+	var ok bool+	a := ip.As16()+	if ip.Is4() {+		ok = subOne(a[12:], 3)+	} else {+		ok = subOne(a[:], 15)+	}+	if ok {+		return ipFrom16Match(ip, a)+	}+	return IP{}+}++// IPRangeSet represents a set of IPRanges.+//+// The zero value is a valid value representing a set of no IP ranges.+//+// The Add and Remove methods add or remove ranges to the set.  Add+// methods should be called first, as a remove operation does nothing+// on an empty set. Ranges may be fully, partially, or not+// overlapping.+type IPRangeSet struct {+	// in are the ranges in the set.+	in []IPRange++	// out are the ranges to be removed from 'in'.+	out []IPRange+}++// AddPrefix adds p's range to s.+func (s *IPRangeSet) AddPrefix(p IPPrefix) { s.AddRange(p.Range()) }++// AddRange adds r to s.+func (s *IPRangeSet) AddRange(r IPRange) {+	if !r.Valid() {+		return+	}+	// If there are any removals (s.out), then we need to compact the set+	// first to get the order right.+	if len(s.out) > 0 {+		s.in = s.Ranges()+		s.out = nil+	}+	s.in = append(s.in, r)+}++// RemovePrefix removes p's range from s.+func (s *IPRangeSet) RemovePrefix(p IPPrefix) { s.RemoveRange(p.Range()) }++// RemoveRange removes r from s.+func (s *IPRangeSet) RemoveRange(r IPRange) {+	if r.Valid() {+		s.out = append(s.out, r)+	}+}++// AddSet adds all ranges in b to s.+func (s *IPRangeSet) AddSet(b *IPRangeSet) {+	for _, r := range b.Ranges() {+		s.AddRange(r)+	}+}++// RemoveSet removes all ranges in b from s.+func (s *IPRangeSet) RemoveSet(b *IPRangeSet) {+	for _, r := range b.Ranges() {+		s.RemoveRange(r)+	}+}++// point is either the start or end of IP range of wanted or unwanted+// IPs.+type point struct {+	ip    IP+	want  bool // true for 'add', false for remove+	start bool // true for start of range, false for (inclusive) end+}++func debugLogPoints(points []point) {+	for _, p := range points {+		emo := "✅"+		if !p.want {+			emo = "❌"+		}+		if p.start {+			log.Printf(" {  %-15s %s\n", p.ip, emo)+		} else {+			log.Printf("  } %-15s %s\n", p.ip, emo)+		}+	}+}++// Ranges removes the minimum and sorted set of IP

done.

bradfitz

comment created time in 2 days

Pull request review commentinetaf/netaddr

Add IPRange, IPRangeSet & friends to work on sets of prefixes/ranges

 func (p *IPPrefix) UnmarshalText(text []byte) error { func (p IPPrefix) String() string { 	return fmt.Sprintf("%s/%d", p.IP, p.Bits) }++// LastIP returns the last IP in the prefix.+func (p IPPrefix) LastIP() IP {+	if p.IP.IsZero() {+		return IP{}+	}+	a16 := p.IP.As16()+	var off uint8+	var bits uint8 = 128+	if p.IP.Is4() {+		off = 12+		bits = 32+	}+	for b := p.Bits; b < bits; b++ {+		byteNum, bitInByte := b/8, 7-(b%8)+		a16[off+byteNum] |= 1 << uint(bitInByte)+	}+	if p.IP.Is4() {+		return IPFrom16(a16)+	} else {+		return IPv6Raw(a16) // doesn't unmap+	}+}++// IPRange represents an inclusive range of IP addresses+// from the same address family.+type IPRange struct {+	// From is the starting IP address in the range.+	// It must have the same address family (IPv4 vs IPv6) as To+	// and be less than or equal to To.+	// From is an inclusive bound; it is included in the set.

updated docs a bit.

bradfitz

comment created time in 2 days

more