pax_global_header00006660000000000000000000000064141401464370014516gustar00rootroot0000000000000052 comment=ea866a9992fe0c4647aa4621114237946e505733 iplib-1.0.3/000077500000000000000000000000001414014643700126165ustar00rootroot00000000000000iplib-1.0.3/.circleci/000077500000000000000000000000001414014643700144515ustar00rootroot00000000000000iplib-1.0.3/.circleci/config.yml000066400000000000000000000012141414014643700164370ustar00rootroot00000000000000version: 2 jobs: build: docker: - image: circleci/golang:latest working_directory: /go/src/github.com/c-robinson/iplib environment: TEST_RESULTS: /tmp/test-results steps: - checkout - run: mkdir -p $TEST_RESULTS - run: go get github.com/mattn/goveralls - run: name: Run unit tests command: | go test -v -cover -race -coverprofile=${TEST_RESULTS}/coverage.out ./... - run: name: Upload coverage results command: | goveralls -coverprofile=${TEST_RESULTS}/coverage.out -service=circle-ci -repotoken=$COVERALLS_TOKEN iplib-1.0.3/.gitignore000066400000000000000000000007011414014643700146040ustar00rootroot00000000000000# Created by .ignore support plugin (hsz.mobi) ### Go template # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test .idea # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof # Output of the go coverage tool, specifically when used with LiteIDE *.out # external packages folder vendor/ iplib-1.0.3/LICENSE000066400000000000000000000020701414014643700136220ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2021 Chad Robinson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. iplib-1.0.3/README.md000066400000000000000000000202031414014643700140720ustar00rootroot00000000000000# 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) 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 around manipulating IP addresses and netblocks are taken care of for you, so I set out to write something like them for my language of choice, Go. This is what I've come up with. [IPLib](http://godoc.org/github.com/c-robinson/iplib) is a hopefully useful, aspirationally full-featured library built around and on top of the address primitives found in the [net](https://golang.org/pkg/net/) package, it seeks to make them more accessible and easier to manipulate. It includes: ##### net.IP tools Some simple tools for performing common tasks against IP objects: - compare two addresses - get the delta between two addresses - sort - decrement or increment addresses - print addresses as binary or hexadecimal strings, or print their addr.ARPA DNS name - print v6 in fully expanded form - convert between net.IP and integer values - get the version of a v4 address or force a IPv4-mapped IPv6address to be a v4 address ##### iplib.Net An enhancement of `net.IPNet`, `iplib.Net` is an interface with two, version- specific implementations providing features such as: - retrieve the first and last usable address - retrieve the wildcard mask - enumerate all or part of a netblock to `[]net.IP` - decrement or increment addresses within the boundaries of the netblock - return the supernet of a netblock - allocate subnets within the netblock - return next- or previous-adjacent netblocks ##### Net4 and Net6 implementations of Net The two address versions behave differently in both large and subtle ways, and the version-specific implementations seek to account for this. For example the Net4 implementation omits the network and broadcast addresses from consideration during enumeration; while the Net6 implementation introduces the concept of a HostMask, which blocks usable addresses off from the right in the same way that a netmask constrains them from the left Additional version-specific considerations described in the [Net4](#using-iplibnet4) and [Net6](#using-iplibnet6) sections below. ## Sub-modules - [iana](https://github.com/c-robinson/iplib/tree/main/iana) - a module for referencing IP netblocks against the [Internet Assigned Numbers Authority's](https://www.iana.org/) Special IP Address Registry - [iid](https://github.com/c-robinson/iplib/tree/main/iid) - a module for generating and validating IPv6 Interface Identifiers, including [RFC4291](https://tools.ietf.org/html/rfc4291) modified EUI64 and [RFC7217](https://tools.ietf.org/html/rfc7217) Semantically Opaque addresses ## Installing ```sh go get -u github.com/c-robinson/iplib ``` ## Using iplib There are a series of functions for working with v4 or v6 `net.IP` objects: ```go package main import ( "fmt" "net" "sort" "github.com/c-robinson/iplib" ) func main() { ipa := net.ParseIP("192.168.1.1") ipb := iplib.IncrementIPBy(ipa, 15) // ipb is 192.168.1.16 ipc := iplib.NextIP(ipa) // ipc is 192.168.1.2 fmt.Println(iplib.CompareIPs(ipa, ipb)) // -1 fmt.Println(iplib.DeltaIP(ipa, ipb)) // 15 fmt.Println(iplib.IPToHexString(ipc)) // "c0a80102" iplist := []net.IP{ ipb, ipc, ipa } sort.Sort(iplib.ByIP(iplist)) // []net.IP{ipa, ipc, ipb} fmt.Println(iplib.IP4ToUint32(ipa)) // 3232235777 fmt.Println(iplib.IPToBinaryString(ipa)) // 11000000.10101000.00000001.00000001 fmt.Println(iplib.IP4ToARPA(ipa)) // 1.1.168.192.in-addr.arpa } ``` 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. ## The iplib.Net interface `Net` describes an `iplib.Net` object, the exposed functions are those that are required for comparison, sorting, generic initialization and for ancillary functions such as those found in this package's submodules. ## Using iplib.Net4 `Net4` represents an IPv4 network. Since the first and last addresses of a v4 network are typically not allocated for use these will be omitted by `Enumerate()`, `NextIP()` and `PreviousIP()`; they wont show up in `Count()`; and `FirstAddress()` and `LastAddress()` show the 2nd and 2nd-to-the-last addresses respectively. The v4-specific method `NetworkAddress()` returns the first address, while `BroadcastAddress()` returns the last. There is an exception made for `Net4` networks defined with a 31-bit netmask, since these are assumed to be for [RFC3021](https://datatracker.ietf.org/doc/html/rfc3021) point-to-point links. Additionally `Net4` contains a `Wildcard()` method which will return the network's [wildcard address](https://en.wikipedia.org/wiki/Wildcard_mask). ```go n := iplib.NewNet4(net.ParseIP("192.168.0.0"), 16) fmt.Println(n.Count()) // 65534 (note: not 65536) fmt.Println(n.Enumerate(2, 1024)) // [192.168.4.1 192.168.4.2] fmt.Println(n.IP()) // 192.168.0.0 fmt.Println(n.FirstAddress()) // 192.168.0.1 fmt.Println(n.LastAddress()) // 192.168.255.254 fmt.Println(n.BroadcastAddress()) // 192.168.255.255 fmt.Println(n.Wildcard()) // 0000ffff fmt.Println(n.Subnet(0)) // [192.168.0.0/17 192.168.128.0/17] fmt.Println(n.Supernet(0)) // 192.168.0.0/15 ``` ## Using iplib.Net6 `Net6` represents and 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! ```go n := iplib.NewNet6(net.ParseIP("2001:db8::"), 56, 0) fmt.Println(n.Count()) // 4722366482869645213696 fmt.Println(n.Enumerate(2, 1024)) // [2001:db8::400 2001:db8::401] fmt.Println(n.FirstAddress()) // 2001:db8:: fmt.Println(n.NextIP(n.FirstAddress())) // 2001:db8::1 fmt.Println(n.LastAddress()) // 2001:db8:0:ff:ffff:ffff:ffff:ffff fmt.Println(n.Subnet(0, 0)) // [2001:db8::/57 2001:db8:0:80::/57] fmt.Println(n.Supernet(0, 0)) // 2001:db8::/55 ``` ### HostMasks with Net6 To manage the address space, `Net6` introduces `HostMask`. This optional constraint can be used to block addresses on the right-side of a netblock somewhat like Netmasks do on the left. `Hostmask` must be specified at initialization time and, if set, will affect the behavior of `Count()`, `Enumerate()`, `LastAddress()`, `NextIP()` and `PreviousIP()`. `Subnet()` and `Supernet()` generate objects that inherit the hostmask of their parent, while a hostmask must be specified for `NextNet()` and `PreviousNet()`. ```go // this is the same as the previous example, except with a hostmask set n := NewNet6(net.ParseIP("2001:db8::"), 56, 60) fmt.Println(n.Count()) // 4096 fmt.Println(n.Enumerate(2, 1024)) // [2001:db8:0:40:: 2001:db8:0:40:100::] fmt.Println(n.FirstAddress()) // 2001:db8:: fmt.Println(n.NextIP(n.FirstAddress())) // 2001:db8:0:0:100:: fmt.Println(n.LastAddress()) // 2001:db8:0:ff:f00:: fmt.Println(n.Mask().String()) // ffffffffffffff000000000000000000 fmt.Println(n.Hostmask.String()) // 0000000000000000f0ffffffffffffff fmt.Println(n.Subnet(0, 60)) // [2001:db8::/57 2001:db8:0:80::/57] fmt.Println(n.Supernet(0, 60)) // 2001:db8::/55 ``` iplib-1.0.3/example_net4_test.go000066400000000000000000000026771414014643700166050ustar00rootroot00000000000000package iplib import ( "fmt" "net" ) func ExampleNet4_Contains() { n := NewNet4(net.ParseIP("192.168.1.0"), 24) fmt.Println(n.Contains(net.ParseIP("192.168.1.111"))) fmt.Println(n.Contains(net.ParseIP("10.14.0.1"))) // Output: // true // false } func ExampleNet4_ContainsNet() { n1 := NewNet4(net.ParseIP("192.168.0.0"), 16) n2 := NewNet4(net.ParseIP("192.168.1.0"), 24) fmt.Println(n1.ContainsNet(n2)) fmt.Println(n2.ContainsNet(n1)) // Output: // true // false } func ExampleNet4_Count() { n := NewNet4(net.ParseIP("192.168.0.0"), 16) fmt.Println(n.Count()) // Output: 65534 } func ExampleNet4_Enumerate() { n := NewNet4(net.ParseIP("192.168.0.0"), 16) fmt.Println(n.Enumerate(2, 100)) // Output: [192.168.0.101 192.168.0.102] } func ExampleNet4_NextNet() { n := NewNet4(net.ParseIP("192.168.1.0"), 24) fmt.Println(n.NextNet(24)) // Output: 192.168.2.0/24 } func ExampleNet4_PreviousNet() { n := NewNet4(net.ParseIP("192.168.1.0"), 24) fmt.Println(n.PreviousNet(24)) // Output: 192.168.0.0/24 } func ExampleNet4_Subnet() { n := NewNet4(net.ParseIP("192.168.0.0"), 16) sub, _ := n.Subnet(17) fmt.Println(sub) // Output: [192.168.0.0/17 192.168.128.0/17] } func ExampleNet4_Supernet() { n := NewNet4(net.ParseIP("192.168.1.0"), 24) n2, _ := n.Supernet(22) fmt.Println(n2) // Output: 192.168.0.0/22 } func ExampleNet4_Wildcard() { n := NewNet4(net.ParseIP("192.168.0.0"), 16) fmt.Println(n.Wildcard()) // Output: 0000ffff } iplib-1.0.3/example_net6_test.go000066400000000000000000000063231414014643700165770ustar00rootroot00000000000000package iplib import ( "fmt" "net" ) func ExampleNet6_Contains() { n := NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 0) fmt.Println(n.Contains(net.ParseIP("2001:db8:1234:5678::1"))) fmt.Println(n.Contains(net.ParseIP("2001:db8:1234::"))) // Output: // true // false } func ExampleNet6_Count() { // without hostmask n := NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 0) fmt.Println(n.Count()) // with hostmask set to 56, leaving 8 usable bytes between the two masks n = NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 56) fmt.Println(n.Count()) // Output: // 18446744073709551616 // 256 } func ExampleNet6_LastAddress() { // without hostmask n := NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 0) fmt.Println(n.LastAddress()) // with hostmask set to 56, leaving 8 usable bytes between the two masks n = NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 56) fmt.Println(n.LastAddress()) // Output: // 2001:db8:1234:5678:ffff:ffff:ffff:ffff // 2001:db8:1234:5678:ff00:: } func ExampleNet6_NextIP() { // without hostmask n := NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 0) fmt.Println(n.NextIP(net.ParseIP("2001:db8:1234:5678::"))) // with hostmask set to 56, leaving 8 usable bytes between the two masks n = NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 56) fmt.Println(n.NextIP(net.ParseIP("2001:db8:1234:5678::"))) // as above, but trying to scan past the end of the netblock fmt.Println(n.NextIP(net.ParseIP("2001:db8:1234:5678:ff00::"))) // Output: // 2001:db8:1234:5678::1 // 2001:db8:1234:5678:100:: // address is not a part of this netblock } func ExampleNet6_NextNet() { n := NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 0) fmt.Println(n.NextNet(0)) // Output: 2001:db8:1234:5679::/64 } func ExampleNet6_PreviousIP() { // without hostmask n := NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 0) fmt.Println(n.PreviousIP(net.ParseIP("2001:db8:1234:5678:ff00::"))) // as above, but trying to scan past the end of the netblock fmt.Println(n.PreviousIP(net.ParseIP("2001:db8:1234:5678::"))) // with hostmask set to 56, leaving 8 usable bytes between the two masks n = NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 56) fmt.Println(n.PreviousIP(net.ParseIP("2001:db8:1234:5678:ff00::"))) // Output: // 2001:db8:1234:5678:feff:ffff:ffff:ffff // address is not a part of this netblock // 2001:db8:1234:5678:fe00:: } func ExampleNet6_PreviousNet() { n := NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 0) // at the same netmask fmt.Println(n.PreviousNet(0)) // at a larger netmask (result encompasses the starting network) fmt.Println(n.PreviousNet(62)) // Output: // 2001:db8:1234:5677::/64 // 2001:db8:1234:5674::/62 } func ExampleNet6_Subnet() { n := NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 0) for _, i := range []int{65, 66} { sub, _ := n.Subnet(i, 0) fmt.Println(sub) } // Output: // [2001:db8:1234:5678::/65 2001:db8:1234:5678:8000::/65] // [2001:db8:1234:5678::/66 2001:db8:1234:5678:4000::/66 2001:db8:1234:5678:8000::/66 2001:db8:1234:5678:c000::/66] } func ExampleNet6_Supernet() { n := NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 0) fmt.Println(n.Supernet(0, 0)) // Output: 2001:db8:1234:5678::/63 } iplib-1.0.3/example_test.go000066400000000000000000000110731414014643700156410ustar00rootroot00000000000000package iplib import ( "fmt" "math/big" "net" ) func ExampleBigintToIP6() { z := big.Int{} z.SetString("42540766452641154071740215577757643572", 10) fmt.Println(BigintToIP6(&z)) // Output: 2001:db8:85a3::8a2e:370:7334 } func ExampleCompareIPs() { fmt.Println(CompareIPs(net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.1"))) fmt.Println(CompareIPs(net.ParseIP("10.0.0.0"), net.ParseIP("10.0.0.0"))) fmt.Println(CompareIPs(net.ParseIP("2001:db8::100"), net.ParseIP("2001:db8::99"))) // Output: // -1 // 0 // 1 } func ExampleDecrementIP4By() { ip := net.ParseIP("192.168.2.0") fmt.Println(DecrementIP4By(ip, 255)) // Output: 192.168.1.1 } func ExampleDecrementIP6By() { z := big.NewInt(16777215) 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)) fmt.Println(ip1) fmt.Println(ip2) // Output: // 2001:db8:fff:ffff:ffff:ffff:ffff:ffff // 2001:db8:fff:ffff:ff00:: } func ExampleDeltaIP4() { ipa := net.ParseIP("192.168.1.1") ipb := net.ParseIP("192.168.2.0") fmt.Println(DeltaIP4(ipa, ipb)) // Output: 255 } func ExampleDeltaIP6() { ipa := net.ParseIP("2001:db8::ffff:ffff") ipb := net.ParseIP("2001:db8::ff00:0") fmt.Println(DeltaIP6(ipa, ipb)) // Output: 16777215 } func ExampleEffectiveVersion() { fmt.Println(EffectiveVersion(net.ParseIP("192.168.1.1"))) fmt.Println(EffectiveVersion(net.ParseIP("::ffff:c0a8:101"))) fmt.Println(EffectiveVersion(net.ParseIP("2001:db8::c0a8:101"))) // Output: // 4 // 4 // 6 } func ExampleExpandIP6() { fmt.Println(ExpandIP6(net.ParseIP("2001:db8::1"))) // Output: 2001:0db8:0000:0000:0000:0000:0000:0001 } func ExampleForceIP4() { fmt.Println(len(ForceIP4(net.ParseIP("::ffff:c0a8:101")))) // Output: 4 } func ExampleHexStringToIP() { ip := HexStringToIP("c0a80101") fmt.Println(ip.String()) // Output: 192.168.1.1 } func ExampleIncrementIP4By() { ip := net.ParseIP("192.168.1.1") fmt.Println(IncrementIP4By(ip, 255)) // Output: 192.168.2.0 } func ExampleIncrementIP6By() { z := big.NewInt(16777215) 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)) fmt.Println(ip1) fmt.Println(ip2) // Output: // 2001:db8:1000::1 // 2001:db8:1000:0:100:: } func ExampleIP4ToARPA() { fmt.Println(IP4ToARPA(net.ParseIP("192.168.1.1"))) // Output: 1.1.168.192.in-addr.arpa } func ExampleIP6ToARPA() { fmt.Println(IP6ToARPA(net.ParseIP("2001:db8::ffff:ffff"))) // Output: f.f.f.f.f.f.f.f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa } func ExampleIP4ToUint32() { fmt.Println(IP4ToUint32(net.ParseIP("192.168.1.1"))) // Output: 3232235777 } func ExampleIPToBinaryString() { fmt.Println(IPToBinaryString(net.ParseIP("192.168.1.1"))) fmt.Println(IPToBinaryString(net.ParseIP("2001:db8::ffff:ffff"))) // Output: // 11000000.10101000.00000001.00000001 // 00100000.00000001.00001101.10111000.00000000.00000000.00000000.00000000.00000000.00000000.00000000.00000000.11111111.11111111.11111111.11111111 } func ExampleIPToHexString() { fmt.Println(IPToHexString(net.ParseIP("192.168.1.1"))) // Output: // c0a80101 } func ExampleNextIP() { fmt.Println(NextIP(net.ParseIP("192.168.1.1"))) fmt.Println(NextIP(net.ParseIP("2001:db8::ffff:fffe"))) // Output: // 192.168.1.2 // 2001:db8::ffff:ffff } func ExampleNextIP6WithinHostmask() { ip, _ := NextIP6WithinHostmask(net.ParseIP("2001:db8:1234:5678::"), NewHostMask(56)) fmt.Println(ip) // Output: 2001:db8:1234:5678:100:: } func ExamplePreviousIP() { fmt.Println(PreviousIP(net.ParseIP("192.168.1.2"))) fmt.Println(PreviousIP(net.ParseIP("2001:db8::ffff:ffff"))) // Output: // 192.168.1.1 // 2001:db8::ffff:fffe } func ExamplePreviousIP6WithinHostmask() { ip, _ := PreviousIP6WithinHostmask(net.ParseIP("2001:db8:1234:5678::"), NewHostMask(56)) fmt.Println(ip) // Output: 2001:db8:1234:5677:ff00:: } func ExampleUint32ToIP4() { fmt.Println(Uint32ToIP4(3232235777)) // Output: 192.168.1.1 } func ExampleVersion() { fmt.Println(Version(ForceIP4(net.ParseIP("192.168.1.1")))) fmt.Println(Version(net.ParseIP("::ffff:c0a8:101"))) fmt.Println(Version(net.ParseIP("2001:db8::c0a8:101"))) // Output: // 4 // 6 // 6 } iplib-1.0.3/go.mod000066400000000000000000000000541414014643700137230ustar00rootroot00000000000000module github.com/c-robinson/iplib go 1.12 iplib-1.0.3/hostmask.go000066400000000000000000000241071414014643700150020ustar00rootroot00000000000000package iplib import ( "encoding/hex" "math/big" "net" ) // HostMask is a mask that can be applied to IPv6 addresses to mask out bits // from the right side of the address instead of the left (which is the // purview of a netmask), the intended use-case is for situations where there // is a desire to reserve a portion of the address for some other purpose and // only allow iplib to manage the remainder. A concrete example would be // IPv6 Interface Identifiers as described in RFC4291, RFC4941 or RFC7217 in // which the final 64bits of the address are used to construct a unique host // identifier and the allocator only has control of the first 64bits. So the // next IP from 2001:db8:1234:5678:: would be 2001:db8:1234:5679 instead of // 2001:db8:1234:5678::1. Here is a Net6 object eing initialized without a // hostmask: // // n := NewNet6(2001:db8::, 56, 0) // Address 2001:db8:: // Netmask ffff:ffff:ffff:ff00:0000:0000:0000:0000 // Hostmask 0000:0000:0000:0000:0000:0000:0000:0000 // First 2001:0db8:0000:0000:0000:0000:0000:0000 // Last 2001:0db8:0000:00ff:ffff:ffff:ffff:ffff // Count 4722366482869645213696 // // This creates a block with 4.7 sextillion usable addresses. Below is he same // block with a hostmask of /60. The mask is applied from the rightmost byte, // leaving 12 unmasked bits for a total of 4096 allocatable addresses: // // n:= NewNet6(2001:db8::, 56, 60) // Address 2001:db8:: // Netmask ffff:ffff:ffff:ff00:0000:0000:0000:0000 // Hostmask 0000:0000:0000:0000:0fff:ffff:ffff:ffff // First 2001:0db8:0000:0000:0000:0000:0000:0000 // Last 2001:0db8:0000:00ff:f000:0000:0000:0000 // Count 4096 // // In the first example the second IP address of the netblock is 2001:db8::1, // in the second example it is 2001:db8:0:1:: // // One important note: even though bytes are filled in from the right the bits // within those bytes are still blocked out left-to-right, so that address // incrementing/decrementing makes sense to the end user, as shown here: // // BINARY Base16 Base10 Example Max16 Max10 // 0000 0000 0x00 0 /56 0xFF 255 // 1000 0000 0x80 128 /57 0x7F 127 // 1100 0000 0xC0 192 /58 0x3F 63 // 1110 0000 0xE0 224 /59 0x1F 31 // 1111 0000 0xF0 240 /60 0x0F 15 // 1111 1000 0xF8 248 /61 0x07 7 // 1111 1100 0xFC 252 /62 0x03 3 // 1111 1110 0xFE 254 /63 0x01 1 // // A hostmask of /1 will block out the left-most bit of the 16th byte // while a /8 will block the entire 16th byte. // // To initialize a hostmask you must give it an integer value between 1 and // 128, which represent the number of bits in the mask. type HostMask []byte // NewHostMask returns a HostMask initialized to masklen func NewHostMask(masklen int) HostMask { mask := make([]byte, 16) if masklen == 0 { return mask } for i := 15; i >= 0; i-- { if masklen < 8 { mask[i] = ^byte(0xff >> uint(masklen)) break } mask[i] = 0xff masklen -= 8 } return mask } // BoundaryByte returns the rightmost byte in the mask in which any bits fall // inside the hostmask, as well as the position of that byte. For example a // masklength of 58 would return "0xc0, 8" while 32 would return "0xff, 12". // If the hostmask is unset "0x00, -1" will be returned func (m HostMask) BoundaryByte() (byte, int) { hmlen, _ := m.Size() // will be between 0 and 128, where 0 is "no mask" if hmlen == 0 { return 0x00, -1 } quo, mod := hmlen/8, hmlen%8 if mod == 0 { quo-- } pos := 15 - quo return m[pos], pos } // Size returns the number of ones and total bits in the mask func (m HostMask) Size() (int, int) { ones := 0 bits := 128 for i := len(m) - 1; i >= 0; i-- { b := m[i] if b == 0xff { ones += 8 continue } for b&0x80 != 0 { ones++ b <<= 1 } break } return ones, bits } // String returns the hexadecimal form of m, with no punctuation func (m HostMask) String() string { return hex.EncodeToString(m) } // DecrementIP6WithinHostmask returns a net.IP that is less than the 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 DecrementIP6WithinHostmask(ip net.IP, hm HostMask, count *big.Int) (net.IP, error) { xcount := new(big.Int) // do not manipulate 'count' xcount.Set(count) bb, bbpos := hm.BoundaryByte() if bbpos == 0 { return net.IP{}, ErrBadMaskLength } if bbpos == -1 { return DecrementIP6By(ip, xcount), nil } // check if ip is outside of hostmask already for _, b := range ip[bbpos+1:] { if b > 0 { return net.IP{}, ErrAddressOutOfRange } } if ip[bbpos]+bb < bb { return net.IP{}, ErrAddressOutOfRange } bb = decrementBoundaryByte(bb, ip[bbpos], xcount) xip := decrementUnmaskedBytes(ip[:bbpos], xcount) if len(xip) == 0 { return xip, ErrAddressOutOfRange } if len(xip) > bbpos { return net.IP{}, ErrAddressOutOfRange } xip = append(xip, bb) xip = append(xip, make([]byte, 15-bbpos)...) return xip, nil } // IncrementIP6WithinHostmask returns a net.IP that is greater than the // 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) bb, bbpos := hm.BoundaryByte() if bbpos == 0 { return net.IP{}, ErrBadMaskLength } if bbpos == -1 { return IncrementIP6By(ip, xcount), nil } // check if ip is outside of hostmask already for _, b := range ip[bbpos+1:] { if b > 0 { return net.IP{}, ErrAddressOutOfRange } } bb = incrementBoundaryByte(bb, ip[bbpos], xcount) xip := incrementUnmaskedBytes(ip[:bbpos], xcount) if len(xip) > bbpos { return net.IP{}, ErrAddressOutOfRange } xip = append(xip, bb) xip = append(xip, make([]byte, 15-bbpos)...) return xip, nil } // NextIP6WithinHostmask takes a net.IP and Hostmask as arguments and attempts // to increment the IP by one, within the boundary of the hostmask. If bits // inside the hostmask are set, an empty net.IP{} and an ErrAddressOutOfRange // will be returned func NextIP6WithinHostmask(ip net.IP, hm HostMask) (net.IP, error) { xip := getCloneIP(ip) for i := len(xip) - 1; i >= 0; i-- { if hm[i] == 0xff { if xip[i] > 0 { return net.IP{}, ErrAddressOutOfRange } continue } if (xip[i] | hm[i]) == 0xff { // xip[i] is the boundary byte, and the accessible bits are at max xip[i] = 0 continue } xip[i]++ if xip[i] > 0 { return xip, nil } } return net.IP{}, ErrAddressOutOfRange } // PreviousIP6WithinHostmask takes a net.IP and Hostmask as arguments and // attempts to decrement the IP by one, within the boundary of the hostmask. // If bits inside the hostmask are set, an empty net.IP{} and an // ErrAddressOutOfRange will be returned func PreviousIP6WithinHostmask(ip net.IP, hm HostMask) (net.IP, error) { xip := getCloneIP(ip) bb, bbpos := hm.BoundaryByte() bbmax := 0xff - bb for i := len(xip) - 1; i >= 0; i-- { if hm[i] == 0xff { if xip[i] > 0 { return net.IP{}, ErrAddressOutOfRange } continue } xip[i]-- if xip[i] != 255 { if i == bbpos-1 { // if we underflowed into the boundary byte we need to adjust // it to it's actual max, not 0xff xip[bbpos] = bbmax } return xip, nil } } return net.IP{}, ErrAddressOutOfRange } // 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 } 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 } return bv - mod } // 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) 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...) } return zb } // 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 } 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] } bigmod := new(big.Int) count.DivMod(count, bigbmax, bigmod) mod := bigmod.Bytes() if len(mod) == 0 { return byte(0) } return mod[0] } // 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...) } return zb } iplib-1.0.3/hostmask_test.go000066400000000000000000000227051414014643700160430ustar00rootroot00000000000000package iplib import ( "bytes" "math/big" "net" "testing" ) var hostMaskTests = []struct { masklen int mask net.IPMask bpos int bvalue byte }{ {0, net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, -1, 0x00}, {1, net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80}, 15, 0x80}, {2, net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0}, 15, 0xc0}, {3, net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe0}, 15, 0xe0}, {4, net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf0}, 15, 0xf0}, {5, net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf8}, 15, 0xf8}, {6, net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfc}, 15, 0xfc}, {7, net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe}, 15, 0xfe}, {8, net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff}, 15, 0xff}, {16, net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff}, 14, 0xff}, {32, net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff}, 12, 0xff}, {64, net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 8, 0xff}, {58, net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 8, 0xc0}, {127, net.IPMask{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0, 0xfe}, {128, net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0, 0xff}, } func TestNewHostMask(t *testing.T) { for i, tt := range hostMaskTests { mask := NewHostMask(tt.masklen) v := bytes.Compare(mask, tt.mask) if v != 0 { t.Errorf("[%d] got wrong mask value for masklen %d: want %v got %v", i, tt.masklen, tt.mask, mask) } masklen, _ := mask.Size() if masklen != tt.masklen { t.Errorf("[%d] got wrong mask length: want %d got %d", i, tt.masklen, masklen) } bvalue, bpos := mask.BoundaryByte() if bvalue != tt.bvalue { t.Errorf("[%d] got wrong boundary byte value: want '%x' got '%x'", i, tt.bvalue, bvalue) } if bpos != tt.bpos { t.Errorf("[%d] got wrong boundary byte position: want %d got %d", i, tt.bpos, bpos) } } } var boundaryByteDeltaTests = []struct { bb byte bv byte count *big.Int decrval byte decrcount *big.Int incrval byte incrcount *big.Int }{ {0x00, 0x00, big.NewInt(0), 0x00, big.NewInt(0), 0x00, big.NewInt(0)}, {0x00, 0x00, big.NewInt(1), 0xff, big.NewInt(1), 0x01, big.NewInt(0)}, {0x00, 0x01, big.NewInt(0), 0x01, big.NewInt(0), 0x01, big.NewInt(0)}, {0x00, 0x01, big.NewInt(1), 0x00, big.NewInt(0), 0x02, big.NewInt(0)}, {0x00, 0x09, big.NewInt(1024), 0x09, big.NewInt(4), 0x09, big.NewInt(4)}, {0x80, 0x09, big.NewInt(1024), 0x09, big.NewInt(8), 0x09, big.NewInt(8)}, {0x40, 0x09, big.NewInt(1024), 0x89, big.NewInt(6), 0x49, big.NewInt(5)}, {0x20, 0x09, big.NewInt(1024), 0x69, big.NewInt(5), 0x89, big.NewInt(4)}, {0x10, 0x09, big.NewInt(1024), 0xb9, big.NewInt(5), 0x49, big.NewInt(4)}, {0x08, 0x09, big.NewInt(1024), 0xe1, big.NewInt(5), 0x29, big.NewInt(4)}, } func Test_decrementBoundaryByte(t *testing.T) { for i, tt := range boundaryByteDeltaTests { xcount := getCloneBigInt(tt.count) decrval := decrementBoundaryByte(tt.bb, tt.bv, xcount) if decrval != tt.decrval { t.Errorf("[%d] got wrong output byte: want 0x%02x, got 0x%02x", i, tt.decrval, decrval) } if v := xcount.Cmp(tt.decrcount); v != 0 { t.Errorf("[%d] got wrong output count: want %d, got %d", i, tt.decrcount, xcount) } } } func Test_incrementBoundaryByte(t *testing.T) { for i, tt := range boundaryByteDeltaTests { xcount := getCloneBigInt(tt.count) incrval := incrementBoundaryByte(tt.bb, tt.bv, xcount) if incrval != tt.incrval { t.Errorf("[%d] got wrong output byte: want 0x%02x, got 0x%02x", i, tt.incrval, incrval) } if v := xcount.Cmp(tt.incrcount); v != 0 { t.Errorf("[%d] got wrong output count: want %d, got %d", i, tt.incrcount, xcount) } } } var unmaskedBytesDeltaTest = []struct { inval []byte incount *big.Int decrval []byte incrval []byte }{ {[]byte{0, 255}, big.NewInt(255), []byte{0, 0}, []byte{1, 254}}, {[]byte{254, 1}, big.NewInt(1), []byte{254, 0}, []byte{254, 2}}, {[]byte{0, 255}, big.NewInt(1), []byte{0, 254}, []byte{1, 0}}, } func Test_decrementUnmaskedBytes(t *testing.T) { for i, tt := range unmaskedBytesDeltaTest { decrval := decrementUnmaskedBytes(tt.inval, tt.incount) if v := bytes.Compare(tt.decrval, decrval); v != 0 { t.Errorf("[%d] got wrong output array: want %+v, got %+v", i, tt.decrval, decrval) } } } func Test_incrementUnmaskedBytes(t *testing.T) { for i, tt := range unmaskedBytesDeltaTest { incrval := incrementUnmaskedBytes(tt.inval, tt.incount) if v := bytes.Compare(tt.incrval, incrval); v != 0 { t.Errorf("[%d] got wrong output array: want %+v, got %+v", i, tt.incrval, incrval) } } } var IPHostmaskDeltaTests = []struct { ipaddr net.IP hostmask int decr net.IP decrErr error incr net.IP incrErr error prev net.IP prevErr error next net.IP nextErr error }{ { // 0 net.ParseIP("2001:db8:1234:5678::"), 0, net.ParseIP("2001:db8:1234:5677:ffff:ffff:ffff:fc18"), nil, net.ParseIP("2001:db8:1234:5678::3e8"), nil, net.ParseIP("2001:db8:1234:5677:ffff:ffff:ffff:ffff"), nil, net.ParseIP("2001:db8:1234:5678::1"), nil, }, { // 1 net.ParseIP("2001:db8:1234:5678:9900::"), 56, net.ParseIP("2001:db8:1234:5674:b100::"), nil, net.ParseIP("2001:db8:1234:567c:8100::"), nil, net.ParseIP("2001:db8:1234:5678:9800::"), nil, net.ParseIP("2001:db8:1234:5678:9a00::"), nil, }, { // 2 net.ParseIP("2001:db8:1234:5678:ff00::"), 56, net.ParseIP("2001:db8:1234:5675:1700::"), nil, net.ParseIP("2001:db8:1234:567c:e700::"), nil, net.ParseIP("2001:db8:1234:5678:fe00::"), nil, net.ParseIP("2001:db8:1234:5679::"), nil, }, { // 3 net.ParseIP("::"), 56, net.ParseIP(""), ErrAddressOutOfRange, net.ParseIP("::3:e800:0:0:0"), nil, net.IP{}, ErrAddressOutOfRange, net.ParseIP("::100:0:0:0"), nil, }, { // 4 net.ParseIP("ffff:ffff:ffff:ffff:ff00::"), 56, net.ParseIP("ffff:ffff:ffff:fffc:1700::"), nil, net.IP{}, ErrAddressOutOfRange, net.ParseIP("ffff:ffff:ffff:ffff:fe00::"), nil, net.IP{}, ErrAddressOutOfRange, }, { // 5 net.ParseIP("2001:db8:1234:5678:9906::"), 53, net.ParseIP("2001:db8:1234:5678:1c06::"), nil, net.ParseIP("2001:db8:1234:5679:1606::"), nil, net.ParseIP("2001:db8:1234:5678:9905::"), nil, net.ParseIP("2001:db8:1234:5678:9907::"), nil, }, { // 6 net.ParseIP("2001:db8:1234:5678:9907::"), 53, net.ParseIP("2001:db8:1234:5678:1c07::"), nil, net.ParseIP("2001:db8:1234:5679:1607::"), nil, net.ParseIP("2001:db8:1234:5678:9906::"), nil, net.ParseIP("2001:db8:1234:5678:9a00::"), nil, }, { // 7 net.ParseIP("2001:db8:1234:5678:9908::"), 53, net.ParseIP(""), ErrAddressOutOfRange, net.ParseIP("2001:db8:1234:5679:1700::"), nil, net.ParseIP("2001:db8:1234:5678:9907::"), nil, net.ParseIP("2001:db8:1234:5678:9909::"), nil, }, { // 8 net.ParseIP("2001:db8:1234:5678:ff::"), 56, net.ParseIP(""), ErrAddressOutOfRange, net.ParseIP("2001:db8:1234:567c:e700::"), nil, net.ParseIP(""), ErrAddressOutOfRange, net.ParseIP(""), ErrAddressOutOfRange, }, } func TestDecrementIP6WithinHostmask(t *testing.T) { for i, tt := range IPHostmaskDeltaTests { count := big.NewInt(1000) hm := NewHostMask(tt.hostmask) decr, err := DecrementIP6WithinHostmask(tt.ipaddr, hm, count) if e := compareErrors(err, tt.decrErr); len(e) > 0 { t.Errorf("[%d] %s (%s)", i, e, decr) } else { x := CompareIPs(decr, tt.decr) if x != 0 { t.Errorf("[%d] expected %s got %s", i, tt.decr, decr) } } } } func TestIncrementIP6WithinHostmask(t *testing.T) { for i, tt := range IPHostmaskDeltaTests { count := big.NewInt(1000) hm := NewHostMask(tt.hostmask) incr, err := IncrementIP6WithinHostmask(tt.ipaddr, hm, count) if e := compareErrors(err, tt.incrErr); len(e) > 0 { t.Errorf("[%d] %s (%s)", i, e, incr) } else { x := CompareIPs(incr, tt.incr) if x != 0 { t.Errorf("[%d] expected %s got %s", i, tt.incr, incr) } } } } func TestNextIPWithinHostmask(t *testing.T) { for i, tt := range IPHostmaskDeltaTests { next, err := NextIP6WithinHostmask(tt.ipaddr, NewHostMask(tt.hostmask)) if e := compareErrors(err, tt.nextErr); len(e) > 0 { t.Errorf("[%d] %s (%s)", i, e, next) } else { x := CompareIPs(next, tt.next) if x != 0 { t.Errorf("[%d] expected %s got %s", i, tt.next, next) } } } } func TestPreviousIPWithinHostmask(t *testing.T) { for i, tt := range IPHostmaskDeltaTests { prev, err := PreviousIP6WithinHostmask(tt.ipaddr, NewHostMask(tt.hostmask)) if e := compareErrors(err, tt.prevErr); len(e) > 0 { t.Errorf("[%d] %s (%s)", i, e, prev) } else { x := CompareIPs(prev, tt.prev) if x != 0 { t.Errorf("[%d] expected %s got %s", i, tt.prev, prev) } } } } func compareErrors(got, want error) string { if got == nil && want == nil { return "" } if got == nil && want != nil { return "wanted error, but got none" } if got != nil && want == nil { return "got unexpected error: " + got.Error() } if got.Error() != want.Error() { return "got wrong error: " + got.Error() } return "" } iplib-1.0.3/iana/000077500000000000000000000000001414014643700135265ustar00rootroot00000000000000iplib-1.0.3/iana/README.md000066400000000000000000000047301414014643700150110ustar00rootroot00000000000000# iana [![Documentation](https://godoc.org/github.com/c-robinson/iplib?status.svg)](http://godoc.org/github.com/c-robinson/iplib/iana) [![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) This package imports the [Internet Assigned Number Authority (IANA)](https://www.iana.org/) Special IP Address Registry for [IPv4](https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml) and [IPv6](https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml) and exposes it as a data structure. Functions allow a caller to compare the registry against `net.IP` and `iplib.Net` objects to see if they contain or are contained within an reserved IP address block. ## Installing ```sh go get -u github.com/c-robinson/iplib/iana ``` ## Using IANA Here are examples comparing against both an address and a network. Note that in the network case it is entirely possible for a broad-enough network to contain multiple reservations. If this is the case all reservations will be returned. ```go package main import ( "fmt" "net" "github.com/c-robinson/iplib" "github.com/c-robinson/iplib/iana" ) func main() { ipa := net.ParseIP("144.21.21.21") ipb := net.ParseIP("192.168.12.5") res := iana.GetReservationsForIP(ipa) fmt.Println(len(res)) // 0 res = iana.GetReservationsForIP(ipb) fmt.Println(len(res)) // 1 fmt.Println(res[0].Title) // "Private-Use" fmt.Println(res[0].RFC) // ["RFC1918"] _, neta, _ := iplib.ParseCIDR("2001::/16") res = iana.GetReservationsForNetwork(neta) fmt.Println(len(res)) // 10 fmt.Println(iana.IsForwardable(neta)) // false fmt.Println(iana.IsGlobal(neta)) // false fmt.Println(iana.IsReserved(neta)) // true fmt.Println(iana.GetRFCsForNetwork(neta)) // all relevant RFCs, in this case: // [RFC1752,RFC2928,RFC3849,RFC4380, // RFC5180,RFC7343,RFC7450,RFC7535, // RFC7723,RFC7954,RFC8155,RFC8190] } ``` iplib-1.0.3/iana/iana.go000066400000000000000000000223371414014643700147740ustar00rootroot00000000000000/* Package iana imports the Internet Assigned Numbers Authority (IANA) IP Special Registries as a data structure and implements functions to compare the reserved networks against iplib.Net objects. The IANA registry is used to reserve portions of IP network space for special use, examples being the IPv4 Private Use blocks (10.0.0/8, 172.16.0.0/12 and 192.168.0.0/16) and the IPv6 netblock set aside for documentation purposes (2001:db8::/32). Note that this package does not contain historical reservations. So IPv6 2001:10::/28 (ORCHIDv1) is listed in the document as "deprecated" and not present in this library. The data-set for the IANA registries is available from: - https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml - https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml */ package iana import ( "net" "sort" "github.com/c-robinson/iplib" ) // Registry holds the aggregated network list from IANA's v4 and v6 registries. // Only the following fields were imported: Address Block, Name, RFC, // Forwardable, Globally Reachable and Reserved-by-Protocol var Registry []*Reservation // Reservation describes an entry in the IANA IP Special Registry type Reservation struct { // Network is the reserved network Network iplib.Net // Title is a name given to the reservation Title string // RFC is the list of relevant RFCs RFC []string // true if a router may forward packets bound for this network between // external interfaces Forwardable bool // true if a router may pass packets bound for this network outside of // a private network Global bool // true if an IP implementation must implement this policy in order to // be compliant Reserved bool } func init() { Registry = []*Reservation{ {getFromCIDR("0.0.0.0/8"), "This host on this network", []string{"RFC1122"}, false, false, true}, {getFromCIDR("10.0.0.0/8"), "Private-Use", []string{"RFC1918"}, true, false, false}, {getFromCIDR("100.64.0.0/10"), "Shared Address Space", []string{"RFC6598"}, false, false, true}, {getFromCIDR("127.0.0.0/8"), "Loopback", []string{"RFC1122"}, false, false, true}, {getFromCIDR("169.254.0.0/16"), "Link Local", []string{"RFC3927"}, false, false, true}, {getFromCIDR("172.16.0.0/12"), "Private-Use", []string{"RFC1918"}, true, false, false}, {getFromCIDR("192.0.0.0/24"), "IETF Protocol Assignments", []string{"RFC6890"}, false, false, false}, {getFromCIDR("192.0.0.0/29"), "IPv4 Service Continuity Prefix", []string{"RFC7335"}, true, false, false}, {getFromCIDR("192.0.0.8/32"), "IPv4 dummy address", []string{"RFC7600"}, false, false, false}, {getFromCIDR("192.0.0.9/32"), "Port Control Protocol Anycast", []string{"RFC7723"}, true, true, true}, {getFromCIDR("192.0.0.10/32"), "Traversal Using Relays around NAT Anycast", []string{"RFC8155"}, true, true, false}, {getFromCIDR("192.0.0.170/32"), "NAT64/DNS64 Discovery", []string{"RFC7050"}, false, false, true}, {getFromCIDR("192.0.0.171/32"), "NAT64/DNS64 Discovery", []string{"RFC7050"}, false, false, true}, {getFromCIDR("192.0.2.0/24"), "Documentation (TEST-NET-1)", []string{"RFC5737"}, false, false, false}, {getFromCIDR("192.31.196.0/24"), "AS112-v4", []string{"RFC7535"}, true, true, false}, {getFromCIDR("192.52.193.0/24"), "AMT", []string{"RFC7450"}, true, true, false}, {getFromCIDR("192.168.0.0/16"), "Private-Use", []string{"RFC1918"}, true, false, false}, {getFromCIDR("192.175.48.0/24"), "Direct Delegation AS112 Service", []string{"RFC7534"}, true, true, false}, {getFromCIDR("198.18.0.0/15"), "Benchmarking", []string{"RFC2544"}, true, false, false}, {getFromCIDR("198.51.100.0/24"), "Documentation (TEST-NET-2)", []string{"RFC5737"}, false, false, false}, {getFromCIDR("203.0.113.0/24"), "Documentation (TEST-NET-3)", []string{"RFC5737"}, false, false, false}, {getFromCIDR("240.0.0.0/4"), "Reserved", []string{"RFC1112"}, false, false, true}, {getFromCIDR("255.255.255.255/32"), "Limited Broadcast", []string{"RFC8190", "RFC919"}, false, false, true}, {getFromCIDR("::1/128"), "Loopback Address", []string{"RFC4291"}, false, false, true}, {getFromCIDR("::/128"), "Unspecified Address", []string{"RFC4291"}, false, false, true}, {getFromCIDR("::ffff:0:0/96"), "IPv4-mapped Address", []string{"RFC4291"}, false, false, true}, {getFromCIDR("64:ff9b::/96"), "IPv4-IPv6 Translation", []string{"RFC6052"}, true, true, false}, {getFromCIDR("64:ff9b:1::/48"), "IPv4-IPv6 Translation", []string{"RFC8215"}, true, false, false}, {getFromCIDR("100::/64"), "Discard-Only Address Block", []string{"RFC6666"}, true, false, false}, {getFromCIDR("2001::/23"), "IETF Protocol Assignments", []string{"RFC2928"}, false, false, false}, {getFromCIDR("2001::/32"), "TEREDO", []string{"RFC4380", "RFC8190"}, true, true, false}, {getFromCIDR("2001:1::1/128"), "Port Control Protocol Anycast", []string{"RFC7723"}, true, true, false}, {getFromCIDR("2001:1::2/128"), "Traversal Using Relays around NAT Anycast", []string{"RFC8155"}, true, true, false}, {getFromCIDR("2001:2::/48"), "Benchmarking", []string{"RFC5180", "RFC1752"}, true, false, false}, {getFromCIDR("2001:3::/32"), "AMT", []string{"RFC7450"}, true, true, false}, {getFromCIDR("2001:4:112::/48"), "AS112-v6", []string{"RFC7535"}, true, true, false}, {getFromCIDR("2001:5::/32"), "EID Space for LISP (Managed by RIPE NCC)", []string{"RFC7954"}, true, true, true}, {getFromCIDR("2001:20::/28"), "ORCHIDv2", []string{"RFC7343"}, true, true, false}, {getFromCIDR("2001:db8::/32"), "Documentation", []string{"RFC3849"}, false, false, false}, {getFromCIDR("2002::/16"), "6to4", []string{"RFC3056"}, true, true, false}, {getFromCIDR("2620:4f:8000::/48"), "Direct Delegation AS112 Service", []string{"RFC7534"}, true, true, false}, {getFromCIDR("fc00::/7"), "Unique-Local", []string{"RFC4193", "RFC8190"}, true, false, false}, {getFromCIDR("fe80::/10"), "Link-Local Unicast", []string{"RFC4291"}, false, false, true}, } } // GetReservationsForNetwork returns a list of any IANA reserved networks // that are either part of the supplied network or that the supplied network // is part of func GetReservationsForNetwork(n iplib.Net) []*Reservation { reservations := []*Reservation{} for _, r := range Registry { if iplib.EffectiveVersion(r.Network.IP()) != iplib.EffectiveVersion(n.IP()) { continue } if r.Network.ContainsNet(n) || n.ContainsNet(r.Network) { reservations = append(reservations, r) } if r.Title == "IPv4-mapped Address" { if n4, ok := n.(iplib.Net4); ok { if n4.Is4in6() { reservations = append(reservations, r) } } } } return reservations } // GetReservationsForIP returns a list of any IANA reserved networks that // the supplied IP is part of func GetReservationsForIP(ip net.IP) []*Reservation { reservations := []*Reservation{} for _, r := range Registry { if r.Network.Contains(ip) { if iplib.EffectiveVersion(ip) == 4 && r.Title == "IPv4-mapped Address" { continue } reservations = append(reservations, r) } } return reservations } // GetRFCsForNetwork returns a list of all RFCs that apply to the given // network func GetRFCsForNetwork(n iplib.Net) []string { rfclist := []string{} reservations := GetReservationsForNetwork(n) if len(reservations) > 0 { for _, r := range reservations { LOOP: for _, rfc := range r.RFC { for _, xrfc := range rfclist { if xrfc == rfc { continue LOOP } } rfclist = append(rfclist, rfc) } } sort.Strings(rfclist) } return rfclist } // IsForwardable will return false if the given iplib.Net contains or is // contained in a network that is marked not-forwardable in the IANA registry. // IANA defines a forwardable network as one where "...a router may forward an // IP datagram whose destination address is drawn from the allocated special- // purpose address block between external interfaces." The default is 'true' func IsForwardable(n iplib.Net) bool { reservations := GetReservationsForNetwork(n) for _, r := range reservations { if r.Forwardable == false { return false } } return true } // IsGlobal will return false if the given iplib.Net contains or is contained // in a network that is marked not-global in the IANA registry. IANA defines a // global network as one where "...an IP datagram whose destination address is // drawn from the allocated special-purpose address block is forwardable // beyond a specified administrative domain." The default is 'true' func IsGlobal(n iplib.Net) bool { reservations := GetReservationsForNetwork(n) for _, r := range reservations { if r.Global == false { return false } } return true } // IsReserved will return true if the given iplib.Net contains or is // contained in a network that is marked reserved-by-protocol in the IANA // registry. IANA defines a reserved network as one where "...the RFC that // created the special-purpose address block requires all compliant IP // implementations to behave in a special way when processing packets either // to or from addresses contained by the address block." The default is 'false' func IsReserved(n iplib.Net) bool { reservations := GetReservationsForNetwork(n) for _, r := range reservations { if r.Reserved == true { return true } } return false } func getFromCIDR(s string) iplib.Net { _, n, _ := iplib.ParseCIDR(s) return n } iplib-1.0.3/iana/iana_test.go000066400000000000000000000072371414014643700160350ustar00rootroot00000000000000package iana import ( "github.com/c-robinson/iplib" "net" "testing" ) var IPTests = []struct { name string address string resCount int }{ { "NotReservedv4", "144.21.1.19", 0, }, { "Reservedv4", "192.168.123.49", 1, }, { "NotReservedv6", "25:100:200::195:16", 0, }, { "Reservedv6", "2001:db8:1::250:3", 1, }, } func TestGetReservationsForIP(t *testing.T) { for _, tt := range IPTests { ip := net.ParseIP(tt.address) r := GetReservationsForIP(ip) if len(r) != tt.resCount { t.Errorf("'%s' want %d reservations, got %d", tt.name, tt.resCount, len(r)) for _, v := range r { t.Logf("%s, %s", v.Title, v.Network.String()) } } } } var NetTests = []struct { name string resCount int network string rfcList []string valForwardable bool valGlobal bool valReserved bool }{ { "NotReservedv4", 0, "1.0.0.0/8", []string{}, true, true, false, }, { "Reservedv4", 1, "10.0.0.0/8", []string{"RFC1918"}, true, false, false, }, { "ContainedInReservedv4", 1, "10.44.1.0/24", []string{"RFC1918"}, true, false, false, }, { "ContainsReservedv4", 1, "192.168.0.0/16", []string{"RFC1918"}, true, false, false, }, { "MultipleReservationsv4", 8, "192.0.0.0/12", []string{"RFC5737", "RFC6890", "RFC7050", "RFC7335", "RFC7600", "RFC7723", "RFC8155"}, false, false, true, }, { "NotReservedv6", 0, "2001:db7::/32", []string{}, true, true, false, }, { "Reservedv6", 1, "2001:db8::/32", []string{"RFC3849"}, false, false, false, }, { "ContainedInReservedv6", 1, "2001:db8:100::/40", []string{"RFC3849"}, false, false, false, }, { "ContainsReservedv6", 1, "2001:d0::/28", []string{"RFC2928"}, false, false, false, }, { "MultipleReservationsv6", 10, "2001::/16", []string{"RFC1752", "RFC2928", "RFC3849", "RFC4380", "RFC5180", "RFC7343", "RFC7450", "RFC7535", "RFC7723", "RFC7954", "RFC8155", "RFC8190"}, false, false, true, }, { "KnowsAbout4in6", 1, "::ffff:c0a9:0101/16", []string{"RFC4291"}, false, false, true, }, } func TestGetReservationsForNetwork(t *testing.T) { for _, tt := range NetTests { _, n, _ := iplib.ParseCIDR(tt.network) r := GetReservationsForNetwork(n) if len(r) != tt.resCount { t.Errorf("'%s' want %d reservations, got %d", tt.name, tt.resCount, len(r)) for _, v := range r { t.Logf("%s, %s", v.Title, v.Network.String()) } } } } func TestGetRFCsForNetwork(t *testing.T) { for _, tt := range NetTests { _, n, _ := iplib.ParseCIDR(tt.network) rfclist := GetRFCsForNetwork(n) if v := equalList(rfclist, tt.rfcList); v != true { t.Errorf("'%s' (%s) want %v, got %v", tt.name, tt.network, tt.rfcList, rfclist) } } } func TestIsForwardable(t *testing.T) { for _, tt := range NetTests { _, n, _ := iplib.ParseCIDR(tt.network) if tt.valForwardable != IsForwardable(n) { t.Errorf("'%s' (%s) want %t, got %t", tt.name, tt.network, tt.valForwardable, IsForwardable(n)) } } } func TestIsGlobal(t *testing.T) { for _, tt := range NetTests { _, n, _ := iplib.ParseCIDR(tt.network) if tt.valGlobal != IsGlobal(n) { t.Errorf("'%s' (%s) want %t, got %t", tt.name, tt.network, tt.valGlobal, IsGlobal(n)) } } } func TestIsReserved(t *testing.T) { for _, tt := range NetTests { _, n, _ := iplib.ParseCIDR(tt.network) if tt.valReserved != IsReserved(n) { t.Errorf("'%s' (%s) want %t, got %t", tt.name, tt.network, tt.valReserved, IsReserved(n)) } } } func equalList(a, b []string) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true } iplib-1.0.3/iid/000077500000000000000000000000001414014643700133635ustar00rootroot00000000000000iplib-1.0.3/iid/README.md000066400000000000000000000123501414014643700146430ustar00rootroot00000000000000# iid [![Documentation](https://godoc.org/github.com/c-robinson/iplib?status.svg)](http://godoc.org/github.com/c-robinson/iplib/iid) [![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) This package implements functions for generating and validating IPv6 Interface Identifiers (IID's) for use in link-local, global unicast and Stateless Address Autoconfiguration (SLAAC). For the purposes of this module an IID is an IPv6 address constructed, somehow, from information which uniquely identifies a given interface on a network, and is unique _within_ that network. ## Installing ```sh go get -u github.com/c-robinson/iplib/iid ``` ## Using IID This library contains functions for uniting `net.IP` and `net.HardwareAddr` addresses in order to generate globally unique IPv6 addresses. The simplest of which is the "Modified EUI-64 address" described in [RFC4291 section 2.5.1](https://tools.ietf.org/html/rfc4291#section-2.5.1) ```go package main import ( "fmt" "net" "github.com/c-robinson/iplib/iid" ) func main() { ip := net.ParseIP("2001:db8:1111:2222::") hw, _ := net.ParseMAC("99:88:77:66:55:44") myiid := iid.MakeEUI64Addr(ip, hw, iid.ScopeGlobal) fmt.Println(myiid) // will be "2001:db8:1111:2222:9b88:77ff:fe66:5544" } ``` EUI64 is fine for a local subnet, but since it is tied to a hardware address and guessable by design it is a privacy nightmare as outlined in [RFC4941](https://tools.ietf.org/html/rfc4941). [RFC7217](https://tools.ietf.org/html/rfc7217) defines an algorithm to create "semantically opaque" IID's based on the local interface by hashing the address with a secret key, a counter, and some optional additional data. The resulting IID is pseudo-random (the same inputs will result in the same outputs) so care must be taken while generating it. This function has some requirements: - `secret` a `[]byte` that is a closely-held secret key - `counter` an `int64`, this is what provides the address its mutability. The RFC specifies that this counter should be incremented every time the same ipaddr/hwaddr pair is used as input and should be stored in non-volatile memory to preserve it - `netid` is an optional parameter to improve the privacy of the results, it is suggested that this be some other bit of information from the local network such as an 802.11 SSID. NOTE: it is possible, though very unlikely, that an address generated this way might collide with the [IANA Reserved Interface Identifier List](https://www.iana.org/assignments/ipv6-interface-ids/ipv6-interface-ids.xhtml), if this happens an `iid.ErrIIDAddressCollision` will be returned. If so `counter` should be incremented and the function re-run. ```go package main import ( "fmt" "net" "github.com/c-robinson/iplib/iid" ) func main() { ip := net.ParseIP("2001:db8::") hw, _ := net.ParseMAC("77:88:99:aa:bb:cc") counter := int64(1) netid := []byte("01234567") secret := []byte("secret") myiid, err := iid.MakeOpaqueAddr(ip, hw, counter, netid, secret) if err != nil { fmt.Println("a very unlikely collision occurred!") } fmt.Println(myiid) // will be "2001:db8::c6fa:ba02:41ab:282c" } ``` `MakeOpaqueIID()` is an implementation of the RFC's specified function using its' preferred [SHA256](https://golang.org/pkg/crypto/sha256/) hashing algorithm and a `iid.ScopeGlobal` scope. If either of these is not to your liking you can roll your own by calling the underlying function. NOTE: if you use any hashing algorithm other than SHA224 or SHA256 you will need to import both `"crypto"` _and_ the crypto submodule with your specific implementation first (e.g. `_ "golang.org/x/crypto/blake2s"`. Also note that the RFC _specifically prohibits_ MD5 as being too insecure for use. Here's an example using [SHA512](https://golang.org/pkg/crypto/sha512/) ```go package main import ( "crypto" _ "crypto/sha512" "fmt" "net" "github.com/c-robinson/iplib/iid" ) func main() { ip := net.ParseIP("2001:db8::") hw, _ := net.ParseMAC("77:88:99:aa:bb:cc") counter := int64(1) netid := []byte("01234567") secret := []byte("secret") myiid, err := iid.GenerateRFC7217Addr(ip, hw, counter, netid, secret, crypto.SHA384, iid.ScopeGlobal) if err != nil { fmt.Println("a very unlikely collision occurred!") } fmt.Println(myiid) // will be "2001:db8::51b3:c6b0:4e14:3519" } ``` Finally, to be entirely RFC7217-compliant a function _should_ check it's results to make sure they don't collide with the IANA Reserved Interface Identifier List. In the name of "using every part of the buffalo" the function is exposed for the extremely unlikely case where anyone needs it: ```go package main import ( "fmt" "net" "github.com/c-robinson/iplib/iid" ) func main() { ip := net.ParseIP("2001:db8::0200:5EFF:FE00:5211") res := iid.GetReservationsForIP(ip) fmt.Println(res.RFC) // will be "RFC4291" fmt.Println(res.Title) // "Reserved IPv6 Interface Identifiers corresponding to the IANA Ethernet Block" } ``` iplib-1.0.3/iid/iid.go000066400000000000000000000207101414014643700144570ustar00rootroot00000000000000/* Package iid provides functions for generating and validating IPv6 Interface Identifiers (IID's). For the purposes of this module an IID is an IPv6 address constructed, somehow, from information which uniquely identifies a given interface on a network, and is unique within that network. As part of validation this package imports the Internet Assigned Numbers Authority (IANA) Reserved IPv6 Interface Identifiers as a data structure and implements functions to compare the reserved networks against IID's to avoid conflicts. The data set for the IANA registry is available from: - https://www.iana.org/assignments/ipv6-interface-ids/ipv6-interface-ids.xhtml */ package iid import ( "bytes" "crypto" // imported for the MakeOpaqueAddr implemenation of GenerateRFC7217Addr _ "crypto/sha256" "encoding/binary" "errors" "net" "github.com/c-robinson/iplib" ) // Scope describes the availability of an IPv6 IID and determines how IID- // generating functions treat the 7th bit in the 9th octet of the address // (the 'X' bit in the EUI-64 format, or the 'u' bit in RFC4291) // // NOTE: there is some ambiguity to the RFC here. Most discussions I've seen // on the topic say that the bit should _always_ be inverted, but the RFC // reads like the IPv6 EUI64 format uses the _inverse signal_ from the IEEE // EUI64 format; so where the IEEE uses 0 for global scoping, the IPv6 IID // should use 1. This module punts on the question and provides for all // interpretations via the scope parameter but recommends passing an explicit // ScopeGlobal or ScopeLocal type Scope int const ( // ScopeNone is an undefined IID scope, the X bit will not be modified ScopeNone Scope = iota // ScopeInvert will cause the X bit to be inverted, setting 0 to 1 and 1 // to 0. This behavior is widely interpreted as the correct behavior ScopeInvert // ScopeGlobal will cause the X bit to be set to 1, indicating that the // IID should be globally scoped ScopeGlobal // ScopeLocal will cause the X bit to be set to 0, indicating that the IID // should only be locally scoped ScopeLocal ) // Errors that may be returned by functions in this package var ( ErrIIDAddressCollision = errors.New("proposed IID collides with IANA reserved IID list") ) // Registry holds the aggregated network list from IANA's "Reserved IPv6 // Interface Identifiers" as specified in RFC5453. In order to be compliant // with RFC7217's algorithm for "Semantically Opaque Interface Identifiers" // addresses should be checked against this registry to make sure there are // no conflicts var Registry []*Reservation // Reservation describes an entry in the IANA IP Special Registry type Reservation struct { // FirstRes is the first address in the reservation FirstRes []byte // LastRes is the last address in the reservation LastRes []byte // Title is a name given to the reservation Title string // RFC is the list of relevant RFCs RFC string } func init() { Registry = []*Reservation{ { []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, "Subnet-Router Anycast", "RFC4291", }, { []byte{0x02, 0x00, 0x5e, 0xff, 0xfe, 0x00, 0x00, 0x00}, []byte{0x02, 0x00, 0x5e, 0xff, 0xfe, 0x00, 0x52, 0x12}, "Reserved IPv6 Interface Identifiers corresponding to the IANA Ethernet Block", "RFC4291", }, { []byte{0x02, 0x00, 0x5e, 0xff, 0xfe, 0x00, 0x52, 0x13}, []byte{0x02, 0x00, 0x5e, 0xff, 0xfe, 0x00, 0x52, 0x13}, "Proxy Mobile IPv6", "RFC6543", }, { []byte{0x02, 0x00, 0x5e, 0xff, 0xfe, 0x00, 0x52, 0x14}, []byte{0x02, 0x00, 0x5e, 0xff, 0xfe, 0xff, 0xff, 0xff}, "Reserved IPv6 Interface Identifiers corresponding to the IANA Ethernet Block (2)", "RFC4291", }, { []byte{0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80}, []byte{0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, "Reserved Subnet Anycast Addresses", "RFC2526", }, } } // GenerateRFC7217Addr generates a pseudo-random IID from supplied input // parameters in compliance with RFC7217. The signature of this function // deviates from the one specified in that RFC only insomuch as is necessary // to conform to the implementing language. In most cases this function is // overkill and the function MakeOpaqueAddr, in this package, should be used // instead. // // The input fields are: // // ip: v6 net.IP, only the first 64bits will be used // // hw: 48- or 64-bit net.HardwareAddr, typically of the interface that // this address will be assigned to // // counter: a monotonically incrementing number read from some non-volatile // local storage. This variable provides the velocity to the entire algorithm // and should be incremented after each use. There is no guarantee that a // generated address wont accidentally fall within the range of reserved IPv6 // IIDs and, should this happen, an ErrIIDAddressCollision will be returned. // This is harmless and if it happens counter should be incremented and the // function called again // // netid: some piece of information identifying the local subnet, such as an // 802.11 SSID. RFC6059 lists other interesting options. This field may be // left blank ([]byte{}) // // secret: a local, closely held, secret key. This is the sauce that makes the // address opaque // // htype: a crypto.Hash function to use when generating the IID. // // scope: the scope of the IID // // NOTE that MD5 is specifically prohibited for being too easily guessable. // // NOTE that unless you use sha256 you will need to import the hash function // you intend to use, (e.g. import _ "crypto/sha512") func GenerateRFC7217Addr(ip net.IP, hw net.HardwareAddr, counter int64, netid, secret []byte, htype crypto.Hash, scope Scope) (net.IP, error) { bs := make([]byte, 8) binary.LittleEndian.PutUint64(bs, uint64(counter)) bs = append(hw, bs...) bs = append(bs, netid...) bs = append(bs, secret...) f := htype.New() ipiid := make([]byte, 16) copy(ipiid, ip) f.Write(bs) rid := f.Sum(nil) rid = setScopeBit(rid, scope) copy(ipiid[8:], rid[0:8]) if r := GetReservationsForIP(ipiid); r != nil { return nil, ErrIIDAddressCollision } return ipiid, nil } // GetReservationsForIP returns a list of any IANA reserved networks that // the supplied IP is part of func GetReservationsForIP(ip net.IP) *Reservation { if iplib.EffectiveVersion(ip) != 6 { return nil } for _, r := range Registry { f := bytes.Compare(ip[8:], r.FirstRes) l := bytes.Compare(ip[8:], r.LastRes) if f >= 0 && l <= 0 { return r } } return nil } // MakeEUI64Addr takes an IPv6 address, a hardware MAC address and a scope as // input and uses them to generate an Interface Identifier suitable for use // in link local, global unicast and Stateless Address Autoconfiguration // (SLAAC) addresses (but see RFC4941 for problems with this last case). // // The IP is assumed to be a /64, and the hardware address must be either 48 // or 64 bits. The hardware address will be appended to the IP address as per // RFC4291 section 2.5.1 and be modified as follows: // // * the 7th bit of the first octet (the 'X' bit in the EUI-64 format) may be // modified per the definition for each constant. // // * if the address is 48 bits, the octets 0xFFFE are inserted in the middle // of the address to pad it to 64 bits func MakeEUI64Addr(ip net.IP, hw net.HardwareAddr, scope Scope) net.IP { tag := []byte{0xff, 0xfe} if iplib.EffectiveVersion(ip) != 6 { return nil } if len(hw) < 6 || len(hw) > 8 { return nil } eui64 := make([]byte, 16) copy(eui64, ip) hwi := make([]byte, len(hw)) copy(hwi, hw) if len(hwi) == 6 { hwi = append(hwi[:3], append(tag, hwi[3:]...)...) } copy(eui64[8:], hwi) return setScopeBit(eui64, scope) } // MakeOpaqueAddr offers one implementation of RFC7217's algorithm for // generating a "semantically opaque interface identifier". The caller must // supply a counter and secret and MAY supply an additional "netid". // Ultimately this function calls GenerateRFC7217Addr() with scope set to // "global" and an htype of SHA256, but please see the documentation in that // function for an explanation of all the input fields func MakeOpaqueAddr(ip net.IP, hw net.HardwareAddr, counter int64, netid, secret []byte) (net.IP, error) { return GenerateRFC7217Addr(ip, hw, counter, netid, secret, crypto.SHA256, ScopeGlobal) } func setScopeBit(ip net.IP, scope Scope) net.IP { switch scope { case ScopeGlobal: ip[8] |= 1 << 1 // set 0 or 1 -> 1 case ScopeLocal: ip[8] &^= 1 << 1 // set 0 or 1 -> 0 case ScopeInvert: ip[8] ^= 1 << 1 // set 0 -> 1 or 1 -> 0 default: } return ip } iplib-1.0.3/iid/iid_test.go000066400000000000000000000134361414014643700155250ustar00rootroot00000000000000package iid import ( "crypto" _ "crypto/sha512" "net" "testing" "github.com/c-robinson/iplib" ) var RFC7217AddrTests = []struct { netid string secret string counter int64 htype crypto.Hash scope Scope out string err error }{ { "01234567", "secret", 1, crypto.SHA256, ScopeGlobal, "2001:db8::c6fa:ba02:41ab:282c", nil, }, { "01234567", "secret", 1, crypto.SHA384, ScopeGlobal, "2001:db8::51b3:c6b0:4e14:3519", nil, }, { "76543210", "secret", 1, crypto.SHA384, ScopeGlobal, "2001:db8::703d:9ce9:741a:80f1", nil, }, { "01234567", "terces", 1, crypto.SHA384, ScopeGlobal, "2001:db8::97dd:7cba:9f02:c412", nil, }, { "01234567", "secret", 1, crypto.SHA384, ScopeLocal, "2001:db8::51b3:c6b0:4e14:3519", nil, }, { "01234567", "secret", 2, crypto.SHA384, ScopeGlobal, "2001:db8::606a:57c0:dacf:706", nil, }, } func TestGenerateRFC7217Addr(t *testing.T) { ip := net.ParseIP("2001:db8::") hw, _ := net.ParseMAC("77:88:99:aa:bb:cc") for i, tt := range RFC7217AddrTests { out, err := GenerateRFC7217Addr(ip, hw, tt.counter, []byte(tt.netid), []byte(tt.secret), tt.htype, tt.scope) if tt.err == nil && err != nil { t.Errorf("[%d] got unexpected error: %s", i, err.Error()) } else if tt.err != nil && err == nil { t.Errorf("[%d] expected error, got none", i) } else { ttout := net.ParseIP(tt.out) v := iplib.CompareIPs(ttout, out) if v != 0 { t.Errorf("[%d] wrong address. Expected '%s' got '%s'", i, ttout, out) } } } } var IPTests = []struct { name string address string res bool rfc string }{ { "Broken", "192.168.1.1", false, "", }, { "NotReserved", "25:100:200::0200:5EFF:FF00:521A", false, "", }, { "ReservedAnycast", "::", true, "RFC4291", }, { "ReservedEthernet", "aaaa:bbbb:cccc:dddd:0200:5EFF:FE00:5211", true, "RFC4291", }, { "ReservedProxyMobile", "aaaa:bbbb:cccc:dddd:0200:5EFF:FE00:5213", true, "RFC6543", }, } func TestGetReservationsForIP(t *testing.T) { for _, tt := range IPTests { ip := net.ParseIP(tt.address) r := GetReservationsForIP(ip) if tt.res == false { if r != nil { t.Errorf("%s: expected no results, got '%s'", tt.name, r.Title) } } else { if r == nil { t.Errorf("%s: got no result but one was expected", tt.name) } else { if r.RFC != tt.rfc { t.Errorf("%s got wrong reservation, expected '%s' got %s", tt.name, tt.rfc, r.RFC) } } } } } var EUI64Tests = []struct { inaddr string hwaddr string outGlobal string outLocal string outInvert string outNone string }{ { "192.168.1.1", "bb:aa:cc:dd:ee:ff", "", "", "", "", }, { "2001:db8:1111:2222::", "bb:aa:cc:dd:ee", "", "", "", "", }, { "2001:db8:1111:2222::", "bb:aa:cc:dd:ee:ff", "2001:db8:1111:2222:bbaa:ccff:fedd:eeff", "2001:db8:1111:2222:b9aa:ccff:fedd:eeff", "2001:db8:1111:2222:b9aa:ccff:fedd:eeff", "2001:db8:1111:2222:bbaa:ccff:fedd:eeff", }, { "2001:db8:1111:2222::", "99:88:77:66:55:44", "2001:db8:1111:2222:9b88:77ff:fe66:5544", "2001:db8:1111:2222:9988:77ff:fe66:5544", "2001:db8:1111:2222:9b88:77ff:fe66:5544", "2001:db8:1111:2222:9988:77ff:fe66:5544", }, { "2001:db8:1111:2222::", "bb:aa:cc:dd:dd:cc:aa:bb", "2001:db8:1111:2222:bbaa:ccdd:ddcc:aabb", "2001:db8:1111:2222:b9aa:ccdd:ddcc:aabb", "2001:db8:1111:2222:b9aa:ccdd:ddcc:aabb", "2001:db8:1111:2222:bbaa:ccdd:ddcc:aabb", }, { "2001:db8:1111:2222::", "99:88:77:66:55:44:33:22", "2001:db8:1111:2222:9b88:7766:5544:3322", "2001:db8:1111:2222:9988:7766:5544:3322", "2001:db8:1111:2222:9b88:7766:5544:3322", "2001:db8:1111:2222:9988:7766:5544:3322", }, } func TestMakeEUI64Addr(t *testing.T) { for i, tt := range EUI64Tests { inaddr := net.ParseIP(tt.inaddr) hwaddr, _ := net.ParseMAC(tt.hwaddr) if iplib.EffectiveVersion(inaddr) == 4 || len(hwaddr) < 4 { out := MakeEUI64Addr(inaddr, hwaddr, ScopeGlobal) if out != nil { t.Errorf("[%d] expected got '%s'", i, out) } continue } out := MakeEUI64Addr(inaddr, hwaddr, ScopeGlobal) if val := iplib.CompareIPs(out, net.ParseIP(tt.outGlobal)); val != 0 { t.Errorf("[%d] '%s' outGlobal: expected %s got %s", i, tt.hwaddr, tt.outGlobal, out) } out = MakeEUI64Addr(inaddr, hwaddr, ScopeLocal) if val := iplib.CompareIPs(out, net.ParseIP(tt.outLocal)); val != 0 { t.Errorf("[%d] '%s' outLocal: expected %s got %s", i, tt.hwaddr, tt.outLocal, out) } out = MakeEUI64Addr(inaddr, hwaddr, ScopeInvert) if val := iplib.CompareIPs(out, net.ParseIP(tt.outInvert)); val != 0 { t.Errorf("[%d] '%s' outInvert: expected %s got %s", i, tt.hwaddr, tt.outInvert, out) } out = MakeEUI64Addr(inaddr, hwaddr, ScopeNone) if val := iplib.CompareIPs(out, net.ParseIP(tt.outNone)); val != 0 { t.Errorf("[%d] '%s' outNone: expected %s got %s", i, tt.hwaddr, tt.outNone, out) } } } var OpaqueAddrTests = []struct { netid string secret string counter int64 out string }{ { "01234567", "secret", 1, "2001:db8::c6fa:ba02:41ab:282c", }, { "76543210", "secret", 1, "2001:db8::8377:6cc:7e00:a088", }, { "", "secret", 1, "2001:db8::f67e:7072:5572:d4bc", }, { "01234567", "terces", 1, "2001:db8::5a42:5d26:73bc:28a6", }, { "01234567", "secret", 2, "2001:db8::ab77:a9d1:2391:5994", }, } func TestMakeOpaqueAddr(t *testing.T) { ip := net.ParseIP("2001:db8::") hw, _ := net.ParseMAC("77:88:99:aa:bb:cc") for i, tt := range OpaqueAddrTests { out, err := MakeOpaqueAddr(ip, hw, tt.counter, []byte(tt.netid), []byte(tt.secret)) if err != nil { t.Errorf("[%d] got unexpected error: %s", i, err) } ttout := net.ParseIP(tt.out) v := iplib.CompareIPs(ttout, out) if v != 0 { t.Errorf("[%d] wrong address. Expected '%s' got '%s'", i, ttout, out) } } } iplib-1.0.3/iplib.go000066400000000000000000000363541414014643700142570ustar00rootroot00000000000000/* Package iplib provides enhanced tools for working with IP networks and addresses. These tools are built upon and extend the generic functionality found in the Go "net" package. The main library comes in two parts: a series of utilities for working with net.IP (sort, increment, decrement, delta, compare, convert to binary or hex- string, convert between net.IP and integer) and an enhancement of net.IPNet called iplib.Net that can calculate the first and last IPs of a block as well as enumerating the block into []net.IP, incrementing and decrementing within the boundaries of the block and creating sub- or super-nets of it. For most features iplib exposes a v4 and a v6 variant to handle each network properly, but in all cases there is a generic function that handles any IP and routes between them. One caveat to this is those functions that require or return an integer value representing the address, in these cases the IPv4 variants take an int32 as input while the IPv6 functions require a *big.Int in order to work with the 128bits of address. For managing the complexity of IPv6 address-spaces, this library adds a new mask, called a Hostmask, as an optional constraint on iplib.Net6 networks, please see the type-documentation for more information on using it. For functions where it is possible to exceed the address-space the rule is that underflows return the version-appropriate all-zeroes address while overflows return the all-ones. There are also two submodules under iplib: the iplib/iid module contains functions for generating RFC 7217-compliant IPv6 Interface ID addresses, and iplib/iana imports the IANA IP Special Registries and exposes functions for comparing IP addresses against those registries to determine if the IP is part of a special reservation (for example RFC 1918 private networks or the RFC 3849 documentation network). */ package iplib import ( "bytes" "encoding/binary" "encoding/hex" "errors" "fmt" "math/big" "net" "strings" ) const ( // MaxIPv4 is the max size of a uint32, also the IPv4 address space MaxIPv4 = 1<<32 - 1 // IP4Version is the label returned by IPv4 addresses IP4Version = 4 // IP6Version is the label returned by IPv6 addresses IP6Version = 6 ) // Errors that may be returned by functions in this package var ( ErrAddressOutOfRange = errors.New("address is not a part of this netblock") ErrBadMaskLength = errors.New("illegal mask length provided") ErrBroadcastAddress = errors.New("address is the broadcast address of this netblock (and not considered usable)") ErrNetworkAddress = errors.New("address is the network address of this netblock (and not considered usable)") ErrNoValidRange = errors.New("no netblock can be found between the supplied values") ) // ByIP implements sort.Interface for net.IP addresses type ByIP []net.IP // Len implements sort.interface Len(), returning the length of the // ByIP array func (bi ByIP) Len() int { return len(bi) } // Swap implements sort.interface Swap(), swapping two elements in our array func (bi ByIP) Swap(a, b int) { bi[a], bi[b] = bi[b], bi[a] } // Less implements sort.interface Less(), given two elements in the array it // returns true if the LHS should sort before the RHS. For details on the // implementation, see CompareIPs() func (bi ByIP) Less(a, b int) bool { val := CompareIPs(bi[a], bi[b]) if val == -1 { return true } return false } // BigintToIP6 converts a big.Int to an ip6 address and returns it as a net.IP func BigintToIP6(z *big.Int) net.IP { b := z.Bytes() if len(b) > 16 { return generateNetLimits(6, 255) } if v := z.Sign(); v <= 0 { return generateNetLimits(6, 0) } // for cases where the resulting []byte isn't long enough if len(b) < 16 { for i := 15 - len(b); i >= 0; i-- { b = append([]byte{0}, b...) } } return b } // CompareIPs is just a thin wrapper around bytes.Compare, but is here for // completeness as this is a good way to compare two IP objects. Since it uses // bytes.Compare the return value is identical: 0 if a==b, -1 if ab func CompareIPs(a, b net.IP) int { return bytes.Compare(a.To16(), b.To16()) } // CompareNets compares two iplib.Net objects by evaluating their network // address (the first address in a CIDR range) and, if they're equal, // comparing their netmasks (smallest wins). This means that if a network is // compared to one of its subnets, the enclosing network sorts first. func CompareNets(a, b Net) int { val := bytes.Compare(a.IP(), b.IP()) if val != 0 { return val } am, _ := a.Mask().Size() bm, _ := b.Mask().Size() if am == bm { return 0 } if am < bm { return -1 } return 1 } // DecrementIPBy returns a net.IP that is lower than the supplied net.IP by // the supplied integer value. If you underflow the IP space it will return // the zero address. func DecrementIPBy(ip net.IP, count uint32) net.IP { if EffectiveVersion(ip) == IP4Version { return DecrementIP4By(ip, count) } z := big.NewInt(int64(count)) return DecrementIP6By(ip, z) } // DecrementIP4By returns a v4 net.IP that is lower than the supplied net.IP // by the supplied integer value. If you underflow the IP space it will return // 0.0.0.0 func DecrementIP4By(ip net.IP, count uint32) net.IP { i := IP4ToUint32(ip) d := i - count // check for underflow if d > i { return generateNetLimits(4, 0) } return Uint32ToIP4(d) } // DecrementIP6By returns a net.IP that is lower than the supplied net.IP by // the supplied integer value. If you underflow the IP space it will return // :: func DecrementIP6By(ip net.IP, count *big.Int) net.IP { z := IPToBigint(ip) z.Sub(z, count) return BigintToIP6(z) } // DeltaIP takes two net.IP's as input and returns the difference between them // up to the limit of uint32. func DeltaIP(a, b net.IP) uint32 { if EffectiveVersion(a) == IP4Version && EffectiveVersion(b) == IP4Version { return DeltaIP4(a, b) } m := big.NewInt(int64(MaxIPv4)) z := DeltaIP6(a, b) if v := z.Cmp(m); v > 0 { return MaxIPv4 } return uint32(z.Uint64()) } // DeltaIP4 takes two net.IP's as input and returns a total of the number of // addresses between them, up to the limit of uint32. func DeltaIP4(a, b net.IP) uint32 { ai := IP4ToUint32(a) bi := IP4ToUint32(b) if ai > bi { return ai - bi } return bi - ai } // DeltaIP6 takes two net.IP's as input and returns a total of the number of // addressed between them as a big.Int. It will technically work on v4 as well // but is considerably slower than DeltaIP4. func DeltaIP6(a, b net.IP) *big.Int { ai := IPToBigint(a) bi := IPToBigint(b) i := big.NewInt(0) return i.Sub(ai, bi).Abs(i) } // EffectiveVersion returns 4 if the net.IP either contains a v4 address or if // it contains the v4-encapsulating v6 address range ::ffff. Note that the // second example below is a v6 address but reports as v4 because it is in the // 4in6 block. This mirrors how Go's `net` package would treat the address func EffectiveVersion(ip net.IP) int { if ip == nil { return 0 } if len(ip) == 4 { return IP4Version } if Is4in6(ip) { return IP4Version } return IP6Version } // ExpandIP6 takes a net.IP containing an IPv6 address and returns a string of // the address fully expanded func ExpandIP6(ip net.IP) string { var h []byte var s string h = make([]byte, hex.EncodedLen(len(ip.To16()))) hex.Encode(h, ip) for i, c := range h { if i%4 == 0 { s = s + ":" } s = s + string(c) } return s[1:] } // ForceIP4 takes a net.IP containing an RFC4291 IPv4-mapped IPv6 address and // returns only the encapsulated v4 address. func ForceIP4(ip net.IP) net.IP { if len(ip) == 16 { return ip[12:] } return ip } // HexStringToIP converts a hexadecimal string to an IP address. If the given // string cannot be converted nil is returned. Input strings may contain '.' // or ':' func HexStringToIP(s string) net.IP { normalize := func(c rune) rune { if strings.IndexRune(":.", c) == -1 { return c } return -1 } s = strings.Map(normalize, s) if len(s) != 8 && len(s) != 32 { return nil } h, err := hex.DecodeString(s) if err != nil { return nil } return h } // IPToARPA takes a net.IP as input and returns a string of the version- // appropriate ARPA DNS name func IPToARPA(ip net.IP) string { if EffectiveVersion(ip) == IP4Version { return IP4ToARPA(ip) } return IP6ToARPA(ip) } // IP4ToARPA takes a net.IP containing an IPv4 address and returns a string of // the address represented as dotted-decimals in reverse-order and followed by // the IPv4 ARPA domain "in-addr.arpa" func IP4ToARPA(ip net.IP) string { ip = ForceIP4(ip) return fmt.Sprintf("%d.%d.%d.%d.in-addr.arpa", ip[3], ip[2], ip[1], ip[0]) } // IP6ToARPA takes a net.IP containing an IPv6 address and returns a string of // the address represented as a sequence of 4-bit nibbles in reverse order and // followed by the IPv6 ARPA domain "ip6.arpa" func IP6ToARPA(ip net.IP) string { var domain = "ip6.arpa" var h []byte var s string h = make([]byte, hex.EncodedLen(len(ip))) hex.Encode(h, ip) for i := len(h) - 1; i >= 0; i-- { s = s + string(h[i]) + "." } return s + domain } // IPToBigint converts a net.IP to big.Int. func IPToBigint(ip net.IP) *big.Int { z := new(big.Int) z.SetBytes(ip) return z } // IPToBinaryString returns the given net.IP as a binary string func IPToBinaryString(ip net.IP) string { var sa []string if len(ip) > 4 && EffectiveVersion(ip) == 4 { ip = ForceIP4(ip) } for _, b := range ip { sa = append(sa, fmt.Sprintf("%08b", b)) } return strings.Join(sa, ".") } // IPToHexString returns the given net.IP as a hexadecimal string. This is the // default stringer format for v6 net.IP func IPToHexString(ip net.IP) string { if EffectiveVersion(ip) == IP4Version { return hex.EncodeToString(ForceIP4(ip)) } return ip.String() } // IP4ToUint32 converts a net.IPv4 to a uint32. func IP4ToUint32(ip net.IP) uint32 { if EffectiveVersion(ip) != IP4Version { return 0 } return binary.BigEndian.Uint32(ForceIP4(ip)) } // IP6ToUint64 converts a net.IPv6 to a uint64, but only the first 64bits of // address are considered meaningful (any information in the last 64bits will // be lost). To work with entire IPv6 addresses use IPToBigint() func IP6ToUint64(ip net.IP) uint64 { if EffectiveVersion(ip) != IP6Version { return 0 } ipn := make([]byte, 8) copy(ipn, ip[:8]) return binary.BigEndian.Uint64(ipn) } // IncrementIPBy returns a net.IP that is greater than the supplied net.IP by // the supplied integer value. If you overflow the IP space it will return // the all-ones address func IncrementIPBy(ip net.IP, count uint32) net.IP { if EffectiveVersion(ip) == IP4Version { return IncrementIP4By(ip, count) } z := big.NewInt(int64(count)) return IncrementIP6By(ip, z) } // IncrementIP4By returns a v4 net.IP that is greater than the supplied // net.IP by the supplied integer value. If you overflow the IP space it // will return 255.255.255.255 func IncrementIP4By(ip net.IP, count uint32) net.IP { i := IP4ToUint32(ip) d := i + count // check for overflow if d < i { return generateNetLimits(4, 255) } return Uint32ToIP4(d) } // IncrementIP6By returns a net.IP that is greater than the supplied net.IP by // the supplied integer value. If you overflow the IP space it will return the // (meaningless in this context) all-ones address func IncrementIP6By(ip net.IP, count *big.Int) net.IP { z := IPToBigint(ip) z.Add(z, count) return BigintToIP6(z) } // Is4in6 returns true if the supplied net.IP is an IPv4 address encapsulated // in an IPv6 address. It is very common for the net library to re-write v4 // addresses into v6 addresses prefixed 0000:0000:0000:0000:ffff. When this // happens net.IP will have a 16-byte array but always return a v4 address (in // fact there is no way to force it to behave as a v6 address), which has lead // to many confused message board comments func Is4in6(ip net.IP) bool { if len(ip) < 16 { return false } if ip[0] == 0x00 && ip[1] == 0x00 && ip[2] == 0x00 && ip[3] == 0x00 && ip[4] == 0x00 && ip[5] == 0x00 && ip[6] == 0x00 && ip[7] == 0x00 && ip[8] == 0x00 && ip[9] == 0x00 && ip[10] == 0xff && ip[11] == 0xff { return true } return false } // IsAllOnes returns true if the supplied net.IP is the all-ones address, // if given a 4-in-6 address this function will treat it as IPv4 func IsAllOnes(ip net.IP) bool { if EffectiveVersion(ip) == 4 { ip = ForceIP4(ip) } for _, b := range ip { if b != 255 { return false } } return true } // IsAllZeroes returns true if the supplied net.IP is the all-zero address, if // given a 4-in-6 address this function will treat it as IPv4 func IsAllZeroes(ip net.IP) bool { if EffectiveVersion(ip) == 4 { ip = ForceIP4(ip) } for _, b := range ip { if b != 0 { return false } } return true } // NextIP returns a net.IP incremented by one from the input address. This // function is roughly as fast for v4 as IncrementIP4By(1) but is consistently // 4x faster on v6 than IncrementIP6By(1). The bundled tests provide // benchmarks doing so, as well as iterating over the entire v4 address space. func NextIP(ip net.IP) net.IP { var xip []byte if EffectiveVersion(ip) == IP4Version { xip = getCloneIP(ForceIP4(ip)) } else { xip = getCloneIP(ip) } for i := len(xip) - 1; i >= 0; i-- { xip[i]++ if xip[i] > 0 { return xip } } return ip // if we're already at the end of range, don't wrap } // PreviousIP returns a net.IP decremented by one from the input address. This // function is roughly as fast for v4 as DecrementIP4By(1) but is consistently // 4x faster on v6 than DecrementIP6By(1). The bundled tests provide // benchmarks doing so, as well as iterating over the entire v4 address space. func PreviousIP(ip net.IP) net.IP { var xip []byte if EffectiveVersion(ip) == IP4Version { xip = getCloneIP(ForceIP4(ip)) } else { xip = getCloneIP(ip) } for i := len(xip) - 1; i >= 0; i-- { xip[i]-- if xip[i] != 255 { return xip } } return ip // if we're already at beginning of range, don't wrap } // Uint32ToIP4 converts a uint32 to an ip4 address and returns it as a net.IP func Uint32ToIP4(i uint32) net.IP { ip := make([]byte, 4) binary.BigEndian.PutUint32(ip, i) return ip } // Uint64ToIP6 converts a uint64 to an IPv6 address, but only the left-most // half of a (128bit) IPv6 address can be accessed in this way, the back half // of the address is lost. To manipulate the entire address, see BigintToIP6() func Uint64ToIP6(i uint64) net.IP { ip := make([]byte, 16) binary.BigEndian.PutUint64(ip, i) return ip } // Version returns 4 if the net.IP contains a v4 address. It will return 6 for // any v6 address, including the v4-encapsulating v6 address range ::ffff. // Contrast with EffectiveVersion above and note that in the provided example // ForceIP4() is used because, by default, net.ParseIP() stores IPv4 addresses // as 4in6 encapsulating v6 addresses. One consequence of which is that // it is impossible to use a 4in6 address as a v6 address func Version(ip net.IP) int { if ip == nil { return 0 } if len(ip) == 4 { return IP4Version } return IP6Version } func generateNetLimits(version int, filler byte) net.IP { var b []byte if version == IP6Version { version = 16 } b = make([]byte, version) for i := range b { b[i] = filler } return b } func getCloneBigInt(z *big.Int) *big.Int { nz := new(big.Int) return nz.Set(z) } func getCloneIP(ip net.IP) net.IP { var xip []byte xip = make([]byte, len(ip)) copy(xip, ip) return xip } iplib-1.0.3/iplib_bench_test.go000066400000000000000000000120301414014643700164360ustar00rootroot00000000000000package iplib import ( "math/big" "net" "testing" ) func BenchmarkParseCIDR4(b *testing.B) { for i := 0; i < b.N; i++ { ParseCIDR("10.0.0.0/24") } } func BenchmarkParseCIDR6(b *testing.B) { for i := 0; i < b.N; i++ { ParseCIDR("2001:db8::/98") } } func BenchmarkNewNet(b *testing.B) { xip := net.IP{10, 0, 0, 0} for i := 0; i < b.N; i++ { NewNet(xip, 24) } } func Benchmark_DeltaIP4(b *testing.B) { var xip = net.IP{10, 255, 255, 255} var zip = net.IP{192, 168, 23, 5} for i := 0; i < b.N; i++ { _ = DeltaIP4(xip, zip) } } func Benchmark_DeltaIP6(b *testing.B) { var xip = net.IP{32, 1, 13, 184, 133, 163, 0, 0, 0, 0, 138, 46, 3, 112, 115, 52} var zip = net.IP{32, 1, 13, 184, 133, 255, 0, 0, 0, 10, 0, 15, 0, 0, 19, 0} for i := 0; i < b.N; i++ { _ = DeltaIP6(xip, zip) } } func BenchmarkPreviousIP4(b *testing.B) { var xip = net.IP{10, 255, 255, 255} for i := 0; i < b.N; i++ { xip = PreviousIP(xip) } } func BenchmarkDecrementIP4By(b *testing.B) { var xip = net.IP{10, 255, 255, 255} for i := 0; i < b.N; i++ { xip = DecrementIP4By(xip, 1) } } func BenchmarkDecrementIPBy_v4(b *testing.B) { var xip = net.IP{10, 255, 255, 255} for i := 0; i < b.N; i++ { xip = DecrementIPBy(xip, 1) } } func BenchmarkPreviousIP6(b *testing.B) { var xip = net.IP{32, 1, 13, 184, 133, 163, 0, 0, 0, 0, 138, 46, 3, 112, 115, 52} for i := 0; i < b.N; i++ { xip = PreviousIP(xip) } } func BenchmarkDecrementIP6By(b *testing.B) { var xip = net.IP{32, 1, 13, 184, 133, 163, 0, 0, 0, 0, 138, 46, 3, 112, 115, 52} count := big.NewInt(1) b.ResetTimer() for i := 0; i < b.N; i++ { xip = DecrementIP6By(xip, count) } } func BenchmarkDecrementIPBy_v6(b *testing.B) { var xip = net.IP{32, 1, 13, 184, 133, 163, 0, 0, 0, 0, 138, 46, 3, 112, 115, 52} for i := 0; i < b.N; i++ { xip = DecrementIPBy(xip, 1) } } func BenchmarkNextIP4(b *testing.B) { var xip = net.IP{10, 0, 0, 0} for i := 0; i < b.N; i++ { xip = NextIP(xip) } } func BenchmarkIncrementIP4By(b *testing.B) { var xip = net.IP{10, 0, 0, 0} for i := 0; i < b.N; i++ { xip = IncrementIP4By(xip, 1) } } func BenchmarkIncrementIPBy_v4(b *testing.B) { var xip = net.IP{10, 0, 0, 0} for i := 0; i < b.N; i++ { xip = IncrementIPBy(xip, 1) } } func BenchmarkNextIP6(b *testing.B) { var xip = net.IP{32, 1, 13, 184, 133, 163, 0, 0, 0, 0, 138, 46, 3, 112, 115, 52} for i := 0; i < b.N; i++ { xip = NextIP(xip) } } func BenchmarkIncrementIP6By(b *testing.B) { var xip = net.IP{32, 1, 13, 184, 133, 163, 0, 0, 0, 0, 138, 46, 3, 112, 115, 52} count := big.NewInt(1) b.ResetTimer() for i := 0; i < b.N; i++ { xip = IncrementIP6By(xip, count) } } func BenchmarkIncrementIPBy_v6(b *testing.B) { var xip = net.IP{32, 1, 13, 184, 133, 163, 0, 0, 0, 0, 138, 46, 3, 112, 115, 52} for i := 0; i < b.N; i++ { xip = IncrementIPBy(xip, 1) } } func BenchmarkNet_Count4(b *testing.B) { _, n, _ := ParseCIDR("192.168.0.0/24") n4 := n.(Net4) b.StartTimer() for i := 0; i < b.N; i++ { _ = n4.Count() } } func BenchmarkNet_Count6(b *testing.B) { _, n, _ := ParseCIDR("2001:db8::/98") n6 := n.(Net6) b.StartTimer() for i := 0; i < b.N; i++ { _ = n6.Count() } } func BenchmarkNet_Subnet_v4(b *testing.B) { _, n, _ := ParseCIDR("192.168.0.0/24") n4 := n.(Net4) b.StartTimer() for i := 0; i < b.N; i++ { _, _ = n4.Subnet(25) } } func BenchmarkNet_Subnet_v6(b *testing.B) { _, n, _ := ParseCIDR("2001:db8::/98") n4 := n.(Net4) b.StartTimer() for i := 0; i < b.N; i++ { _, _ = n4.Subnet(99) } } func BenchmarkNet_PreviousNet_v4(b *testing.B) { _, n, _ := ParseCIDR("192.168.0.0/24") n4 := n.(Net4) b.StartTimer() for i := 0; i < b.N; i++ { _ = n4.PreviousNet(24) } } func BenchmarkNet_PreviousNet_v6(b *testing.B) { _, n, _ := ParseCIDR("2001:db8::/98") n6 := n.(Net6) b.StartTimer() for i := 0; i < b.N; i++ { _ = n6.PreviousNet(24) } } func BenchmarkNet_NextNet_v4(b *testing.B) { _, n, _ := ParseCIDR("192.168.0.0/24") n4 := n.(Net4) b.StartTimer() for i := 0; i < b.N; i++ { _ = n4.NextNet(24) } } func BenchmarkNet_NextNet_v6(b *testing.B) { _, n, _ := ParseCIDR("2001:db8::/98") n6 := n.(Net6) b.StartTimer() for i := 0; i < b.N; i++ { _ = n6.NextNet(24) } } func BenchmarkNewNetBetween_v4(b *testing.B) { ipa := net.IP{10, 0, 0, 0} ipb := net.IP{10, 0, 0, 255} for i := 0; i < b.N; i++ { _, _, _ = NewNetBetween(ipa, ipb) } } func BenchmarkNewNetBetween_v6(b *testing.B) { ipa, _, _ := net.ParseCIDR("::") ipb, _, _ := net.ParseCIDR("ffff::") for i := 0; i < b.N; i++ { _, _, _ = NewNetBetween(ipa, ipb) } } func BenchmarkNet6_nextIPWithinHostmask(b *testing.B) { var xip = net.IP{32, 1, 13, 184, 133, 163, 0, 0, 0, 0, 138, 46, 3, 112, 115, 52} hm := NewHostMask(8) b.ResetTimer() for i := 0; i < b.N; i++ { xip, _ = NextIP6WithinHostmask(xip, hm) } } func BenchmarkNet6_incrementIP6WithinHostmask(b *testing.B) { var xip = net.IP{32, 1, 13, 184, 133, 163, 0, 0, 0, 0, 138, 46, 3, 112, 115, 52} count := big.NewInt(1) hm := NewHostMask(8) b.ResetTimer() for i := 0; i < b.N; i++ { xip, _ = IncrementIP6WithinHostmask(xip, hm, count) } } iplib-1.0.3/iplib_test.go000066400000000000000000000321131414014643700153030ustar00rootroot00000000000000package iplib import ( "math/big" "net" "sort" "testing" ) var IPTests = []struct { ipaddr net.IP next net.IP prev net.IP intval uint32 hexval string inarpa string binval string }{ { net.ParseIP("10.1.2.3"), net.ParseIP("10.1.2.4"), net.ParseIP("10.1.2.2"), 167838211, "0a010203", "3.2.1.10.in-addr.arpa", "00001010.00000001.00000010.00000011", }, { net.ParseIP("10.1.2.255"), net.ParseIP("10.1.3.0"), net.ParseIP("10.1.2.254"), 167838463, "0a0102ff", "255.2.1.10.in-addr.arpa", "00001010.00000001.00000010.11111111", }, { net.ParseIP("10.1.2.0"), net.ParseIP("10.1.2.1"), net.ParseIP("10.1.1.255"), 167838208, "0a010200", "0.2.1.10.in-addr.arpa", "00001010.00000001.00000010.00000000", }, { net.ParseIP("255.255.255.255"), net.ParseIP("255.255.255.255"), net.ParseIP("255.255.255.254"), 4294967295, "ffffffff", "255.255.255.255.in-addr.arpa", "11111111.11111111.11111111.11111111", }, { net.ParseIP("0.0.0.0"), net.ParseIP("0.0.0.1"), net.ParseIP("0.0.0.0"), 0, "00000000", "0.0.0.0.in-addr.arpa", "00000000.00000000.00000000.00000000", }, } func TestNextIP(t *testing.T) { for i, tt := range IPTests { x := CompareIPs(tt.next, NextIP(tt.ipaddr)) if x != 0 { t.Errorf("[%d] want %s, got %s", i, tt.next, NextIP(tt.ipaddr)) } } } func TestPrevIP(t *testing.T) { for i, tt := range IPTests { x := CompareIPs(tt.prev, PreviousIP(tt.ipaddr)) if x != 0 { t.Errorf("[%d] want %s got %s", i, tt.prev, PreviousIP(tt.ipaddr)) } } } func TestIP4ToUint32(t *testing.T) { for i, tt := range IPTests { z := IP4ToUint32(tt.ipaddr) if z != tt.intval { t.Errorf("[%d] want %d got %d", i, tt.intval, z) } } } func TestIPToHexString(t *testing.T) { for i, tt := range IPTests { s := IPToHexString(tt.ipaddr) if s != tt.hexval { t.Errorf("[%d] want %s got %s", i, tt.hexval, s) } } } func TestIPToBinaryString(t *testing.T) { for i, tt := range IPTests { s := IPToBinaryString(tt.ipaddr) if s != tt.binval { t.Errorf("[%d] expected %s, got %s", i, tt.binval, s) } } } func TestHexStringToIP(t *testing.T) { for i, tt := range IPTests { ip := HexStringToIP(tt.hexval) x := CompareIPs(tt.ipaddr, ip) if x != 0 { t.Errorf("[%d] want %s got %s", i, tt.ipaddr, ip) } } } func TestHexStringToIPBadVals(t *testing.T) { ip := HexStringToIP("placebo") if ip != nil { t.Errorf("non-ip word should return nil") } ip = HexStringToIP("2001:db8::/24") if ip != nil { t.Errorf("network address should return nil") } } func TestUint32ToIP4(t *testing.T) { for i, tt := range IPTests { ip := Uint32ToIP4(tt.intval) x := CompareIPs(ip, tt.ipaddr) if x != 0 { t.Errorf("[%d] want %s got %s", i, tt.ipaddr, ip) } } } func TestIP4ToARPA(t *testing.T) { for i, tt := range IPTests { s := IPToARPA(tt.ipaddr) if s != tt.inarpa { t.Errorf("[%d] want %s got %s", i, tt.inarpa, s) } } } var IP6Tests = []struct { ipaddr string next string prev string bigintval string int64val uint64 hostbits string hexval string expand string inarpa string binval string }{ { "2001:db8:85a3::8a2e:370:7334", "2001:db8:85a3::8a2e:370:7335", "2001:db8:85a3::8a2e:370:7333", "42540766452641154071740215577757643572", 2306139570357600256, "2001:db8:85a3::", "2001:db8:85a3::8a2e:370:7334", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "4.3.3.7.0.7.3.0.e.2.a.8.0.0.0.0.0.0.0.0.3.a.5.8.8.b.d.0.1.0.0.2.ip6.arpa", "00100000.00000001.00001101.10111000.10000101.10100011.00000000.00000000.00000000.00000000.10001010.00101110.00000011.01110000.01110011.00110100", }, { "::", "::1", "::", "0", 0, "::", "::", "0000:0000:0000:0000:0000:0000:0000:0000", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa", "00000000.00000000.00000000.00000000.00000000.00000000.00000000.00000000.00000000.00000000.00000000.00000000.00000000.00000000.00000000.00000000", }, { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe", "340282366920938463463374607431768211455", 18446744073709551615, "ffff:ffff:ffff:ffff::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.ip6.arpa", "11111111.11111111.11111111.11111111.11111111.11111111.11111111.11111111.11111111.11111111.11111111.11111111.11111111.11111111.11111111.11111111", }, } func TestNextIP6(t *testing.T) { for i, tt := range IP6Tests { x := CompareIPs(net.ParseIP(tt.next), NextIP(net.ParseIP(tt.ipaddr))) if x != 0 { t.Errorf("[%d] want %s got %s", i, tt.next, NextIP(net.ParseIP(tt.ipaddr))) } } } func TestPrevIP6(t *testing.T) { for i, tt := range IP6Tests { x := CompareIPs(net.ParseIP(tt.prev), PreviousIP(net.ParseIP(tt.ipaddr))) if x != 0 { t.Errorf("[%d] want %s got %s", i, tt.prev, PreviousIP(net.ParseIP(tt.ipaddr))) } } } func TestIP6ToBigint(t *testing.T) { for _, tt := range IP6Tests { i := IPToBigint(net.ParseIP(tt.ipaddr)) if i.String() != tt.bigintval { t.Errorf("[%d] want %s got %s", i, tt.bigintval, i.String()) } } } func TestIP6ToUint64(t *testing.T) { for i, tt := range IP6Tests { z := IP6ToUint64(net.ParseIP(tt.ipaddr)) if z != tt.int64val { t.Errorf("[%d] want %d got %d", i, tt.int64val, z) } } } func TestUint64ToIP6(t *testing.T) { for i, tt := range IP6Tests { ip := Uint64ToIP6(tt.int64val) x := CompareIPs(ip, net.ParseIP(tt.hostbits)) if x != 0 { t.Errorf("[%d] want %s got %s", i, tt.hostbits, ip) } } } func TestIP6ToBinaryString(t *testing.T) { for i, tt := range IP6Tests { s := IPToBinaryString(net.ParseIP(tt.ipaddr)) if s != tt.binval { t.Errorf("[%d] want %s, got %s", i, tt.binval, s) } } } func TestIP6ToHexString(t *testing.T) { for i, tt := range IP6Tests { s := IPToHexString(net.ParseIP(tt.ipaddr)) if s != tt.hexval { t.Errorf("[%d] want %s got %s", i, tt.hexval, s) } } } func TestBigintToIP6(t *testing.T) { for i, tt := range IP6Tests { z := big.Int{} z.SetString(tt.bigintval, 10) ip := BigintToIP6(&z) x := CompareIPs(ip, net.ParseIP(tt.ipaddr)) if x != 0 { t.Errorf("[%d] want %s got %s", i, tt.ipaddr, ip) } } } func TestExpandIP6(t *testing.T) { for i, tt := range IP6Tests { s := ExpandIP6(net.ParseIP(tt.ipaddr)) if s != tt.expand { t.Errorf("[%d] want %s got %s", i, tt.expand, s) } } } func TestIP6ToARPA(t *testing.T) { for i, tt := range IP6Tests { s := IPToARPA(net.ParseIP(tt.ipaddr)) if s != tt.inarpa { t.Errorf("[%d] want %s got %s", i, tt.inarpa, s) } } } var IPDeltaTests = []struct { ipaddr net.IP decr net.IP incr net.IP intval uint32 incres uint32 decres uint32 }{ { net.ParseIP("192.168.2.2"), net.ParseIP("192.168.1.1"), net.ParseIP("192.168.3.3"), 257, 257, 257, }, { net.ParseIP("10.0.0.0"), net.ParseIP("9.0.0.0"), net.ParseIP("11.0.0.0"), 16777216, 16777216, 16777216, }, { net.ParseIP("255.255.255.0"), net.ParseIP("255.255.252.0"), net.ParseIP("255.255.255.255"), 768, 255, 768, }, { net.ParseIP("0.0.0.255"), net.ParseIP("0.0.0.0"), net.ParseIP("0.0.3.255"), 768, 768, 255, }, { net.ParseIP("2001:db8:85a3::8a2e:370:7334"), net.ParseIP("2001:db8:85a3::8a2e:370:731c"), net.ParseIP("2001:db8:85a3::8a2e:370:734c"), 24, 24, 24, }, } func TestDeltaIP(t *testing.T) { for i, tt := range IPDeltaTests { z := DeltaIP(tt.ipaddr, tt.incr) if z != tt.incres { t.Errorf("[%d] on increment: want %d got %d", i, tt.incres, z) } z = DeltaIP(tt.ipaddr, tt.decr) if z != tt.decres { t.Errorf("[%d]on decrement: want %d got %d", i, tt.decres, z) } } } func TestDeltaIPMaxValue(t *testing.T) { i := DeltaIP(net.ParseIP("2001:db8::"), net.ParseIP("2001:db8:1234:5678::")) if i != MaxIPv4 { t.Errorf("want %d got %d", MaxIPv4, i) } } func TestDecrementIPBy(t *testing.T) { for i, tt := range IPDeltaTests { ip := DecrementIPBy(tt.ipaddr, tt.intval) x := CompareIPs(ip, tt.decr) if x != 0 { t.Errorf("[%d] want %s got %s", i, tt.decr, ip) } } } func TestIncrementIPBy(t *testing.T) { for i, tt := range IPDeltaTests { ip := IncrementIPBy(tt.ipaddr, tt.intval) x := CompareIPs(ip, tt.incr) if x != 0 { t.Errorf("[%d] want %s got %s", i, tt.incr, ip) } } } var IPDelta6Tests = []struct { ipaddr net.IP decr net.IP incr net.IP intval string incres string decres string }{ { net.ParseIP("2001:db8:85a3::8a2e:370:7334"), net.ParseIP("2001:db8:85a3::8a2d:370:7334"), net.ParseIP("2001:db8:85a3::8a2f:370:7334"), "4294967296", "4294967296", "4294967296", }, { net.ParseIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00"), net.ParseIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fb00"), net.ParseIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), "1024", "255", "1024", }, { net.ParseIP("::ff"), net.ParseIP("::"), net.ParseIP("::4ff"), "1024", "1024", "255", }, } func TestDeltaIP6(t *testing.T) { for i, tt := range IPDelta6Tests { z := DeltaIP6(tt.ipaddr, tt.incr) if z.String() != tt.incres { t.Errorf("[%d] on increment: want %s got %s", i, tt.incres, z) } z = DeltaIP6(tt.ipaddr, tt.decr) if z.String() != tt.decres { t.Errorf("[%d] on decrement: want %s got %s", i, tt.decres, z) } } } func TestDecrementIP6By(t *testing.T) { for i, tt := range IPDelta6Tests { z := big.Int{} z.SetString(tt.intval, 10) ip := DecrementIP6By(tt.ipaddr, &z) x := CompareIPs(ip, tt.decr) if x != 0 { t.Errorf("[%d] want %s got %s", i, tt.decr, ip) } } } func TestIncrementIP6By(t *testing.T) { for i, tt := range IPDelta6Tests { z := big.Int{} z.SetString(tt.intval, 10) ip := IncrementIP6By(tt.ipaddr, &z) x := CompareIPs(ip, tt.incr) if x != 0 { t.Errorf("[%d] want %s got %s", i, tt.incr, ip) } } } var IPVersionTests = []struct { ipaddr net.IP version int eversion int }{ // ParseIP() *always* returns 4-in-6 addresses, so we specify exactly what // we want here {net.IP{0, 0, 0, 0}, 4, 4}, {net.IP{192, 168, 1, 1}, 4, 4}, {net.IP{255, 255, 255, 255}, 4, 4}, {net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 6, 6}, {net.IP{32, 1, 13, 184, 133, 163, 0, 0, 0, 0, 138, 46, 3, 112, 115, 52}, 6, 6}, {net.IP{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 6, 6}, // these are the 4-in-6 versions of the first 3 test cases {net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0}, 6, 4}, {net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 192, 168, 1, 1}, 6, 4}, {net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255}, 6, 4}, } func Test_Version(t *testing.T) { for i, tt := range IPVersionTests { version := Version(tt.ipaddr) if version != tt.version { t.Errorf("[%d] want %d got %d", i, tt.version, version) } } } func Test_EffectiveVersion(t *testing.T) { for i, tt := range IPVersionTests { eversion := EffectiveVersion(tt.ipaddr) if eversion != tt.eversion { t.Errorf("[%d] want %d got %d", i, tt.eversion, eversion) } } } func Test_EffectiveVersionNil(t *testing.T) { eversion := EffectiveVersion(nil) if eversion != 0 { t.Errorf("want 0, got %d", eversion) } } var compareIPTests = []struct { pos int ipaddr net.IP status int }{ {8, net.ParseIP("192.168.2.3"), -1}, {1, net.ParseIP("10.0.0.3"), 1}, {0, net.ParseIP("10.0.0.1"), 1}, {10, net.ParseIP("192.168.3.255"), -1}, {9, net.ParseIP("192.168.3.1"), -1}, {2, net.ParseIP("10.0.1.0"), 1}, {7, net.ParseIP("192.168.1.1"), -1}, {3, net.ParseIP("44.0.0.1"), 1}, {4, net.ParseIP("44.0.1.0"), 0}, {5, net.ParseIP("44.1.0.0"), -1}, {6, net.ParseIP("170.1.12.1"), -1}, } func TestCompareIPs(t *testing.T) { a := compareIPTests[8] a1 := []net.IP{} for i, b := range compareIPTests { a1 = append(a1, b.ipaddr) val := CompareIPs(a.ipaddr, b.ipaddr) if val != b.status { t.Errorf("[%d] want %d got %d", i, b.status, val) } } sort.Sort(ByIP(a1)) for i, b := range compareIPTests { if a1[b.pos].String() != b.ipaddr.String() { t.Errorf("[%d] want %s at position %d got %s", i, b.ipaddr, b.pos, a1[b.pos]) } } } var isAllTests = []struct { ipaddr net.IP isones bool iszero bool is4in6 bool }{ {net.IP{0, 0, 0, 0}, false, true, false}, {net.IP{255, 255, 255, 255}, true, false, false}, {net.IP{192, 168, 1, 1}, false, false, false}, {net.ParseIP("::ffff:0:0"), false, true, true}, {net.ParseIP("::ffff:ffff:ffff"), true, false, true}, {net.ParseIP("::ffff:c0a8:0101"), false, false, true}, {net.ParseIP("::"), false, true, false}, {net.ParseIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), true, false, false}, {net.ParseIP("2001:db8::1"), false, false, false}, } func TestIs4in6(t *testing.T) { for i, tt := range isAllTests { v := Is4in6(tt.ipaddr) if v != tt.is4in6 { t.Errorf("[%d] want %t got %t", i, tt.is4in6, v) } } } func TestIsAllOnes(t *testing.T) { for i, tt := range isAllTests { v := IsAllOnes(tt.ipaddr) if v != tt.isones { t.Errorf("[%d] want %t got %t", i, tt.isones, v) } } } func TestIsAllZeroes(t *testing.T) { for i, tt := range isAllTests { v := IsAllZeroes(tt.ipaddr) if v != tt.iszero { t.Errorf("[%d] want %t got %t", i, tt.iszero, v) } } } iplib-1.0.3/net.go000066400000000000000000000066771414014643700137530ustar00rootroot00000000000000package iplib import ( "net" "strings" ) // Net describes an iplib.Net object, the enumerated functions are those that // are required for comparison, sorting, generic initialization and for // ancillary functions such as those found in the iid and iana submodules type Net interface { Contains(ip net.IP) bool ContainsNet(network Net) bool FirstAddress() net.IP IP() net.IP LastAddress() net.IP Mask() net.IPMask String() string Version() int } // NewNet returns a new Net object containing ip at the specified masklen. In // the Net6 case the hostbits value will be set to 0. If the masklen is set // to an insane value (greater than 32 for IPv4 or 128 for IPv6) an empty Net // will be returned func NewNet(ip net.IP, masklen int) Net { if EffectiveVersion(ip) == 6 { return NewNet6(ip, masklen, 0) } return NewNet4(ip, masklen) } // NewNetBetween takes two net.IP's as input and will return the largest // netblock that can fit between them (exclusive of the IP's themselves). // If there is an exact fit it will set a boolean to true, otherwise the bool // will be false. If no fit can be found (probably because a >= b) an // ErrNoValidRange will be returned. func NewNetBetween(a, b net.IP) (Net, bool, error) { if CompareIPs(a, b) != -1 { return nil, false, ErrNoValidRange } if EffectiveVersion(a) != EffectiveVersion(b) { return nil, false, ErrNoValidRange } return fitNetworkBetween(NextIP(a), PreviousIP(b), 1) } // ByNet implements sort.Interface for iplib.Net based on the // starting address of the netblock, with the netmask as a tie breaker. So if // two Networks are submitted and one is a subset of the other, the enclosing // network will be returned first. type ByNet []Net // Len implements sort.interface Len(), returning the length of the // ByNetwork array func (bn ByNet) Len() int { return len(bn) } // Swap implements sort.interface Swap(), swapping two elements in our array func (bn ByNet) Swap(a, b int) { bn[a], bn[b] = bn[b], bn[a] } // Less implements sort.interface Less(), given two elements in the array it // returns true if the LHS should sort before the RHS. For details on the // implementation, see CompareNets() func (bn ByNet) Less(a, b int) bool { val := CompareNets(bn[a], bn[b]) if val == -1 { return true } return false } // ParseCIDR returns a new Net object. It is a passthrough to net.ParseCIDR // and will return any error it generates to the caller. There is one major // difference between how net.IPNet manages addresses and how ipnet.Net does, // and this function exposes it: net.ParseCIDR *always* returns an IPv6 // address; if given a v4 address it returns the RFC4291 IPv4-mapped IPv6 // address internally, but treats it like v4 in practice. In contrast // iplib.ParseCIDR will re-encode it as a v4 func ParseCIDR(s string) (net.IP, Net, error) { ip, ipnet, err := net.ParseCIDR(s) if err != nil { return ip, nil, err } masklen, _ := ipnet.Mask.Size() if strings.Contains(s, ".") { return ForceIP4(ip), NewNet4(ForceIP4(ip), masklen), err } if EffectiveVersion(ip) == 4 && masklen <= 32 { return ip, NewNet4(ip, masklen), err } return ip, NewNet6(ip, masklen, 0), err } func fitNetworkBetween(a, b net.IP, mask int) (Net, bool, error) { xnet := NewNet(a, mask) va := CompareIPs(xnet.FirstAddress(), a) vb := CompareIPs(xnet.LastAddress(), b) if va >= 0 && vb < 0 { return xnet, false, nil } if va == 0 && vb == 0 { return xnet, true, nil } return fitNetworkBetween(a, b, mask + 1) }iplib-1.0.3/net4.go000066400000000000000000000226321414014643700140240ustar00rootroot00000000000000package iplib import ( "math" "net" "sync" ) // Net4 is an implementation of Net intended for IPv4 netblocks. It has // functions to return the broadcast address and wildcard mask not present in // the IPv6 implementation type Net4 struct { net.IPNet is4in6 bool } // NewNet4 returns an initialized Net4 object at the specified masklen. If // mask is greater than 32, or if a v6 address is supplied, an empty Net4 // will be returned func NewNet4(ip net.IP, masklen int) Net4 { var maskMax = 32 if masklen > maskMax { return Net4{IPNet: net.IPNet{}} } mask := net.CIDRMask(masklen, maskMax) n := net.IPNet{IP: ForceIP4(ip).Mask(mask), Mask: mask} return Net4{IPNet: n, is4in6: Is4in6(ip)} } // Net4FromStr takes a string which should be a v4 address in CIDR notation // and returns an initialized Net4. If the string isn't parseable an empty // Net4 will be returned func Net4FromStr(s string) Net4 { _, n, err := ParseCIDR(s) if err != nil { return Net4{} } if n4, ok := n.(Net4); ok { return n4 } return Net4{} } // BroadcastAddress returns the broadcast address for the represented network. // In the context of IPv6 broadcast is meaningless and the value will be // equivalent to LastAddress(). func (n Net4) BroadcastAddress() net.IP { xip, _ := n.finalAddress() return xip } // Contains returns true if ip is contained in the represented netblock func (n Net4) Contains(ip net.IP) bool { return n.IPNet.Contains(ip) } // ContainsNet returns true if the given Net is contained within the // represented block func (n Net4) ContainsNet(network Net) bool { l1, _ := n.Mask().Size() l2, _ := network.Mask().Size() return l1 <= l2 && n.Contains(network.IP()) } // Count returns the total number of usable IP addresses in the represented // network.. func (n Net4) Count() uint32 { ones, all := n.Mask().Size() exp := all - ones if exp == 1 { return uint32(2) // special handling for RFC3021 /31 } if exp == 0 { return uint32(1) // special handling for /32 } return uint32(math.Pow(2, float64(exp))) - 2 } // Enumerate generates an array of all usable addresses in Net up to the // given size starting at the given offset. If size=0 the entire block is // enumerated. // // NOTE: RFC3021 defines a use case for netblocks of /31 for use in point-to- // point links. For this reason enumerating networks at these lengths will // return a 2-element array even though it would naturally return none. // // For consistency, enumerating a /32 will return the IP in a 1 element array func (n Net4) Enumerate(size, offset int) []net.IP { if n.IP() == nil { return nil } count := int(n.Count()) // offset exceeds total, return an empty array if offset > count { return []net.IP{} } // size is greater than the number of addresses that can be returned, // adjust the size of the slice but keep going if size > (count-offset) || size == 0 { size = count - offset } // Handle edge-case mask sizes if count == 1 { // Count() returns 1 if host-bits == 0 return []net.IP{getCloneIP(n.IPNet.IP)} } addrs := make([]net.IP, size) netu := IP4ToUint32(n.FirstAddress()) netu += uint32(offset) fip := Uint32ToIP4(netu) limit := 65535 pos := 0 wg := sync.WaitGroup{} for pos < size { incr := limit if limit > (size - pos) { incr = size - pos } wg.Add(1) go func(fip net.IP, pos, count int) { defer wg.Done() addrs[pos] = IncrementIP4By(fip, uint32(pos)) for i := 1; i < count; i++ { pos++ addrs[pos] = NextIP(addrs[pos-1]) } }(fip, pos, incr) pos = pos + incr } wg.Wait() return addrs } // FirstAddress returns the first usable address for the represented network func (n Net4) FirstAddress() net.IP { ones, _ := n.Mask().Size() // if it's either a single IP or RFC 3021, return the network address if ones >= 31 { return n.IPNet.IP } return NextIP(n.IP()) } // Is4in6 will return true if this Net4 object or any of its parents were // explicitly initialized with a 4in6 address (::ffff:xxxx.xxx) func (n Net4) Is4in6() bool { return n.is4in6 } // LastAddress returns the last usable address for the represented network func (n Net4) LastAddress() net.IP { xip, ones := n.finalAddress() // if it's either a single IP or RFC 3021, return the last address if ones >= 31 { return xip } return PreviousIP(xip) } // Mask returns the netmask of the netblock func (n Net4) Mask() net.IPMask { return n.IPNet.Mask } // IP returns the network address for the represented network, e.g. // the lowest IP address in the given block func (n Net4) IP() net.IP { return n.IPNet.IP } // NetworkAddress returns the network address for the represented network, e.g. // the lowest IP address in the given block func (n Net4) NetworkAddress() net.IP { return n.IPNet.IP } // NextIP takes a net.IP as an argument and attempts to increment it by one. // If the resulting address is outside of the range of the represented network // it will return an empty net.IP and an ErrAddressOutOfRange. If the result // is the broadcast address, the address _will_ be returned, but so will an // ErrBroadcastAddress, to indicate that the address is technically // outside the usable scope func (n Net4) NextIP(ip net.IP) (net.IP, error) { if !n.Contains(ip) { return net.IP{}, ErrAddressOutOfRange } xip := NextIP(ip) if !n.Contains(xip) { return net.IP{}, ErrAddressOutOfRange } // if this is the broadcast address, return it but warn the caller via error if n.BroadcastAddress().Equal(xip) { return xip, ErrBroadcastAddress } return xip, nil } // NextNet takes a CIDR mask-size as an argument and attempts to create a new // Net object just after the current Net, at the requested mask length func (n Net4) NextNet(masklen int) Net4 { return NewNet4(NextIP(n.BroadcastAddress()), masklen) } // PreviousIP takes a net.IP as an argument and attempts to decrement it by // one. If the resulting address is outside of the range of the represented // network it will return an empty net.IP and an ErrAddressOutOfRange. If the // result is the network address, the address _will_ be returned, but so will // an ErrNetworkAddress, to indicate that the address is technically outside // the usable scope func (n Net4) PreviousIP(ip net.IP) (net.IP, error) { if !n.Contains(ip) { return net.IP{}, ErrAddressOutOfRange } xip := PreviousIP(ip) if !n.Contains(xip) { return net.IP{}, ErrAddressOutOfRange } // if this is the network address, return it but warn the caller via error if n.IP().Equal(xip) { return xip, ErrNetworkAddress } return xip, nil } // PreviousNet takes a CIDR mask-size as an argument and creates a new Net // object just before the current one, at the requested mask length. If the // specified mask is for a larger network than the current one then the new // network may encompass the current one, e.g.: // // iplib.Net{192.168.4.0/22}.Subnet(21) -> 192.168.0.0/21 // // In the above case 192.168.4.0/22 is part of 192.168.0.0/21 func (n Net4) PreviousNet(masklen int) Net4 { return NewNet4(PreviousIP(n.IP()), masklen) } // String returns the CIDR notation of the enclosed network e.g. 192.168.0.1/24 func (n Net4) String() string { return n.IPNet.String() } // Subnet takes a CIDR mask-size as an argument and carves the current Net // object into subnets of that size, returning them as a []Net. The mask // provided must be a larger-integer than the current mask. If set to 0 Subnet // will carve the network in half func (n Net4) Subnet(masklen int) ([]Net4, error) { ones, all := n.Mask().Size() if masklen == 0 { masklen = ones + 1 } if ones > masklen || masklen > all { return nil, ErrBadMaskLength } mask := net.CIDRMask(masklen, all) netlist := []Net4{{IPNet: net.IPNet{IP: n.IP(), Mask: mask}, is4in6: n.is4in6}} for CompareIPs(netlist[len(netlist)-1].BroadcastAddress(), n.BroadcastAddress()) == -1 { ng := net.IPNet{IP: NextIP(netlist[len(netlist)-1].BroadcastAddress()), Mask: mask} netlist = append(netlist, Net4{ng, n.is4in6}) } return netlist, nil } // Supernet takes a CIDR mask-size as an argument and returns a Net object // containing the supernet of the current Net at the requested mask length. // The mask provided must be a smaller-integer than the current mask. If set // to 0 Supernet will return the next-largest network // // Examples: // Net{192.168.1.0/24}.Supernet(0) -> Net{192.168.0.0/23} // Net{192.168.1.0/24}.Supernet(22) -> Net{Net{192.168.0.0/22} func (n Net4) Supernet(masklen int) (Net4, error) { ones, all := n.Mask().Size() if ones < masklen { return Net4{}, ErrBadMaskLength } if masklen == 0 { masklen = ones - 1 } mask := net.CIDRMask(masklen, all) ng := net.IPNet{IP: n.IP().Mask(mask), Mask: mask} return Net4{ng, n.is4in6}, nil } // Version returns the version of IP for the enclosed netblock, 4 in this case func (n Net4) Version() int { return IP4Version } // Wildcard will return the wildcard mask for a given netmask func (n Net4) Wildcard() net.IPMask { wc := make([]byte, len(n.Mask())) for pos, b := range n.Mask() { wc[pos] = 0xff - b } return wc } // finalAddress returns the last address in the network. It is private // because both LastAddress() and BroadcastAddress() rely on it, and both use // it differently. It returns the last address in the block as well as the // number of masked bits as an int. func (n Net4) finalAddress() (net.IP, int) { xip := make([]byte, len(n.IP())) ones, _ := n.Mask().Size() // apply wildcard to network, byte by byte wc := n.Wildcard() for pos, b := range []byte(n.IP()) { xip[pos] = b + wc[pos] } return xip, ones } iplib-1.0.3/net4_test.go000066400000000000000000000353641414014643700150710ustar00rootroot00000000000000package iplib import ( "net" "sort" "testing" ) var NewNet4Tests = []struct { s string addr net.IP masklen int is4in6 bool isEmpty bool }{ {"192.168.0.0/16", ForceIP4(net.ParseIP("192.168.0.0")), 16, false, false}, {"192.168.0.0/16", net.ParseIP("::ffff:c0a8:0000"), 16, true, false}, {"", net.ParseIP("192.168.0.0"), 33, false, true}, } func TestNewNet4(t *testing.T) { for i, tt := range NewNet4Tests { ipn := NewNet4(tt.addr, tt.masklen) if (tt.isEmpty == true && ipn.IP() != nil) || (tt.isEmpty == false && ipn.IP() == nil) { t.Errorf("[%d] expect isEmpty == %t, but is not", i, tt.isEmpty) } else if tt.isEmpty == false { if ipn.String() != tt.s { t.Errorf("[%d] Net4 want %s got %s", i, tt.s, ipn.String()) } if ipn.is4in6 != tt.is4in6 { t.Errorf("[%d] is4in6 want %t got %t", i, tt.is4in6, ipn.is4in6) } } } } var Net4FromStrTests = []struct { ins string outs string isEmpty bool }{ {"192.168.0.0/16", "192.168.0.0/16", false}, {"notanaddress!!", "", true}, {"::ffff:c0a8:0000/16", "192.168.0.0/16", false}, {"2001:db8::/32", "", true}, } func TestNet4FromStr(t *testing.T) { for i, tt := range Net4FromStrTests { ipn := Net4FromStr(tt.ins) if (tt.isEmpty == true && ipn.IP() != nil) || (tt.isEmpty == false && ipn.IP() == nil) { t.Errorf("[%d] expect isEmpty == %t, but is not", i, tt.isEmpty) } else if tt.isEmpty == false { if tt.outs != ipn.String() { t.Errorf("[%d] want %s got %s", i, tt.outs, ipn.String()) } } } } var Net4Tests = []struct { ip net.IP network net.IP netmask net.IPMask wildcard net.IPMask broadcast net.IP firstaddr net.IP lastaddr net.IP masklen int count uint32 }{ { net.ParseIP("10.1.2.3"), net.ParseIP("10.0.0.0"), net.IPMask{255, 0, 0, 0}, net.IPMask{0, 255, 255, 255}, net.ParseIP("10.255.255.255"), net.ParseIP("10.0.0.1"), net.ParseIP("10.255.255.254"), 8, 16777214, }, { net.ParseIP("192.168.1.1"), net.ParseIP("192.168.0.0"), net.IPMask{255, 255, 254, 0}, net.IPMask{0, 0, 1, 255}, net.ParseIP("192.168.1.255"), net.ParseIP("192.168.0.1"), net.ParseIP("192.168.1.254"), 23, 510, }, { net.ParseIP("192.168.1.61"), net.ParseIP("192.168.1.0"), net.IPMask{255, 255, 255, 192}, net.IPMask{0, 0, 0, 63}, net.ParseIP("192.168.1.63"), net.ParseIP("192.168.1.1"), net.ParseIP("192.168.1.62"), 26, 62, }, { net.ParseIP("192.168.1.66"), net.ParseIP("192.168.1.64"), net.IPMask{255, 255, 255, 192}, net.IPMask{0, 0, 0, 63}, net.ParseIP("192.168.1.127"), net.ParseIP("192.168.1.65"), net.ParseIP("192.168.1.126"), 26, 62, }, { net.ParseIP("192.168.1.1"), net.ParseIP("192.168.1.0"), net.IPMask{255, 255, 255, 252}, net.IPMask{0, 0, 0, 3}, net.ParseIP("192.168.1.3"), net.ParseIP("192.168.1.1"), net.ParseIP("192.168.1.2"), 30, 2, }, { net.ParseIP("192.168.1.1"), net.ParseIP("192.168.1.0"), net.IPMask{255, 255, 255, 254}, net.IPMask{0, 0, 0, 1}, net.ParseIP("192.168.1.1"), net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.1"), 31, 2, }, { net.ParseIP("192.168.1.15"), net.ParseIP("192.168.1.15"), net.IPMask{255, 255, 255, 255}, net.IPMask{0, 0, 0, 0}, net.ParseIP("192.168.1.15"), net.ParseIP("192.168.1.15"), net.ParseIP("192.168.1.15"), 32, 1, }, } func TestNet4_BroadcastAddress(t *testing.T) { for i, tt := range Net4Tests { ipn := NewNet4(tt.ip, tt.masklen) if addr := ipn.BroadcastAddress(); !tt.broadcast.Equal(addr) { t.Errorf("[%d] want %v got %v", i, tt.broadcast, addr) } } } func TestNet4_Version(t *testing.T) { for i, tt := range Net4Tests { ipn := NewNet4(tt.ip, tt.masklen) if ipn.Version() != IP4Version { t.Errorf("[%d] want version 4, got %d", i, ipn.Version()) } } } func TestNet4_Count(t *testing.T) { for i, tt := range Net4Tests { ipn := NewNet4(tt.ip, tt.masklen) if ipn.Count() != tt.count { t.Errorf("[%d] want %d got %d", i, tt.count, ipn.Count()) } } } func TestNet4_FirstAddress(t *testing.T) { for i, tt := range Net4Tests { ipn := NewNet4(tt.ip, tt.masklen) if addr := ipn.FirstAddress(); !tt.firstaddr.Equal(addr) { t.Errorf("[%d] want %s got %s", i, tt.firstaddr, addr) } } } func TestNet4_finalAddress(t *testing.T) { for i, tt := range Net4Tests { ipn := NewNet4(tt.ip, tt.masklen) if addr, ones := ipn.finalAddress(); !tt.broadcast.Equal(addr) { t.Errorf("[%d] want %s got %s (%d))", i, tt.broadcast, addr, ones) } } } func TestNet4_LastAddress(t *testing.T) { for i, tt := range Net4Tests { ipn := NewNet4(tt.ip, tt.masklen) if addr := ipn.LastAddress(); !tt.lastaddr.Equal(addr) { t.Errorf("[%d] want %s got %s", i, tt.lastaddr, addr) } } } func TestNet4_NetworkAddress(t *testing.T) { for i, tt := range Net4Tests { ipn := NewNet4(tt.ip, tt.masklen) if addr := ipn.IP(); !tt.network.Equal(addr) { t.Errorf("[%d] want %s got %s", i, tt.network, addr) } } } func TestWildcard(t *testing.T) { for i, tt := range Net4Tests { ipn := NewNet4(tt.ip, tt.masklen) if ipn.Wildcard().String() != tt.wildcard.String() { t.Errorf("[%d] want %s got %s", i, tt.wildcard, ipn.Wildcard()) } } } var enumerate4Tests = []struct { incidr string total int last net.IP }{ {"192.168.0.0/22", 1022, net.ParseIP("192.168.3.254")}, {"192.168.0.0/23", 510, net.ParseIP("192.168.1.254")}, {"192.168.0.0/24", 254, net.ParseIP("192.168.0.254")}, {"192.168.0.0/25", 126, net.ParseIP("192.168.0.126")}, {"192.168.0.0/26", 62, net.ParseIP("192.168.0.62")}, {"192.168.0.0/27", 30, net.ParseIP("192.168.0.30")}, {"192.168.0.0/28", 14, net.ParseIP("192.168.0.14")}, {"192.168.0.0/29", 6, net.ParseIP("192.168.0.6")}, {"192.168.0.0/30", 2, net.ParseIP("192.168.0.2")}, {"192.168.0.0/31", 2, net.ParseIP("192.168.0.1")}, {"192.168.0.0/32", 1, net.ParseIP("192.168.0.0")}, } func TestNet4_Enumerate(t *testing.T) { for i, tt := range enumerate4Tests { _, ipn, _ := ParseCIDR(tt.incidr) ipn4 := ipn.(Net4) addrlist := ipn4.Enumerate(0, 0) if len(addrlist) != tt.total { t.Errorf("[%d] want size %d got %d", i, tt.total, len(addrlist)) } x := CompareIPs(tt.last, addrlist[tt.total-1]) if x != 0 { t.Errorf("[%d] want last address %s, got %s", i, tt.last, addrlist[tt.total-1]) } } } var enumerate4VariableTests = []struct { offset int size int total int first net.IP last net.IP }{ { 0, 0, 1022, net.ParseIP("192.168.0.1"), net.ParseIP("192.168.3.254"), }, { 1, 0, 1021, net.ParseIP("192.168.0.2"), net.ParseIP("192.168.3.254"), }, { 256, 0, 766, net.ParseIP("192.168.1.1"), net.ParseIP("192.168.3.254"), }, { 0, 128, 128, net.ParseIP("192.168.0.1"), net.ParseIP("192.168.0.128"), }, { 20, 128, 128, net.ParseIP("192.168.0.21"), net.ParseIP("192.168.0.148"), }, { 1000, 100, 22, net.ParseIP("192.168.3.233"), net.ParseIP("192.168.3.254"), }, { 1023, 0, 0, net.ParseIP("192.168.3.233"), net.ParseIP("192.168.3.254"), }, } func TestNet4_EnumerateWithVariables(t *testing.T) { _, ipn, _ := ParseCIDR("192.168.0.0/22") ipn4 := ipn.(Net4) for i, tt := range enumerate4VariableTests { addrlist := ipn4.Enumerate(tt.size, tt.offset) if len(addrlist) != tt.total { t.Errorf("[%d] size: want %d got %d", i, tt.total, len(addrlist)) } if len(addrlist) > 0 { x := CompareIPs(tt.first, addrlist[0]) if x != 0 { t.Errorf("[%d] first member: want %s got %s", i, tt.first, addrlist[0]) } y := CompareIPs(tt.last, addrlist[len(addrlist)-1]) if y != 0 { t.Errorf("[%d] last member: want %s got %s", i, tt.last, addrlist[len(addrlist)-1]) } } } } func TestNet4_EnumerateRFC3021(t *testing.T) { ipn := NewNet4(net.ParseIP("192.168.1.0"), 31) addrlist := ipn.Enumerate(0, 0) if len(addrlist) != 2 { t.Errorf("want 2, got %d", len(addrlist)) } } var incr4Tests = []struct { inaddr string thisaddr net.IP nextaddr net.IP err error }{ { "192.168.1.0/23", net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.1"), nil, }, { "192.168.1.0/24", net.ParseIP("192.168.1.254"), net.ParseIP("192.168.1.255"), ErrBroadcastAddress, }, { "192.168.2.0/24", net.ParseIP("192.168.2.1"), net.ParseIP("192.168.2.2"), nil, }, { "192.168.3.0/24", net.ParseIP("192.168.3.0"), net.ParseIP("192.168.3.1"), nil, }, { "192.168.4.0/24", net.ParseIP("192.168.5.1"), net.IP{}, ErrAddressOutOfRange, }, { "192.168.1.0/31", net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.1"), ErrBroadcastAddress, }, { "192.168.1.0/32", net.ParseIP("192.168.1.0"), net.IP{}, ErrAddressOutOfRange, }, } func TestNet4_NextIP(t *testing.T) { for i, tt := range incr4Tests { _, ipn, _ := ParseCIDR(tt.inaddr) ipn4 := ipn.(Net4) nextaddr, err := ipn4.NextIP(tt.thisaddr) if e := compareErrors(err, tt.err); len(e) > 0 { t.Errorf("[%d] %s (%s)", i, e, tt.thisaddr) } else { if !nextaddr.Equal(tt.nextaddr) { t.Errorf("For %s expected %v, got %v", tt.thisaddr, tt.nextaddr, nextaddr) } } } } var incr4SubnetTests = []struct { netblock Net4 netmask int next Net4 }{ {Net4FromStr("192.168.0.0/24"), 24, Net4FromStr("192.168.1.0/24")}, {Net4FromStr("192.168.0.0/24"), 25, Net4FromStr("192.168.1.0/25")}, {Net4FromStr("192.168.0.0/24"), 23, Net4FromStr("192.168.0.0/23")}, {Net4FromStr("255.255.255.0/24"), 24, Net4FromStr("255.255.255.0/24")}, } func TestNet4_NextNet(t *testing.T) { for i, tt := range incr4SubnetTests { next := tt.netblock.NextNet(tt.netmask) if v := CompareNets(next, tt.next); v != 0 { t.Errorf("[%d] want %v got %v", i, tt.next, next) } } } var decr4Tests = []struct { inaddr string thisaddr net.IP prevaddr net.IP preverr error }{ { "192.168.1.0/23", net.IP{192, 168, 1, 0}, net.IP{192, 168, 0, 255}, nil, }, { "192.168.1.0/24", net.IP{192, 168, 1, 254}, net.IP{192, 168, 1, 253}, nil, }, { "192.168.2.0/24", net.IP{192, 168, 2, 1}, net.IP{192, 168, 2, 0}, ErrNetworkAddress, }, { "192.168.3.0/24", net.IP{192, 168, 3, 0}, net.IP{}, ErrAddressOutOfRange, }, { "192.168.4.0/24", net.IP{192, 168, 5, 1}, net.IP{}, ErrAddressOutOfRange, }, { "192.168.1.1/31", net.IP{192, 168, 1, 1}, net.IP{192, 168, 1, 0}, ErrNetworkAddress, }, { "192.168.1.0/32", net.IP{192, 168, 1, 0}, net.IP{}, ErrAddressOutOfRange, }, } func TestNet4_PreviousIP(t *testing.T) { for i, tt := range decr4Tests { _, ipn, _ := ParseCIDR(tt.inaddr) ipn4 := ipn.(Net4) prevaddr, err := ipn4.PreviousIP(tt.thisaddr) if e := compareErrors(err, tt.preverr); len(e) > 0 { t.Errorf("[%d] %s (%s)", i, e, prevaddr) } else { if !prevaddr.Equal(tt.prevaddr) { t.Errorf("[%d] want %s, got %s", i, tt.prevaddr, prevaddr) } } } } var decr4SubnetTests = []struct { netblock Net4 netmask int prev Net4 }{ {Net4FromStr("192.168.1.0/24"), 24, Net4FromStr("192.168.0.0/24")}, {Net4FromStr("192.168.1.0/24"), 25, Net4FromStr("192.168.0.128/25")}, {Net4FromStr("192.168.1.0/24"), 23, Net4FromStr("192.168.0.0/23")}, {Net4FromStr("0.0.0.0/24"), 24, Net4FromStr("0.0.0.0/24")}, } func TestNet4_PreviousNet(t *testing.T) { for i, tt := range decr4SubnetTests { prev := tt.netblock.PreviousNet(tt.netmask) if v := CompareNets(prev, tt.prev); v != 0 { t.Errorf("[%d] want %s got %s", i, tt.prev, prev) } } } var subnet4Tests = []struct { netblock Net4 netmask int subnets []string err error }{ { Net4FromStr("192.168.0.0/24"), 0, []string{"192.168.0.0/25", "192.168.0.128/25"}, nil, }, { Net4FromStr("192.168.0.0/24"), 25, []string{"192.168.0.0/25", "192.168.0.128/25"}, nil, }, { Net4FromStr("192.168.0.0/24"), 26, []string{"192.168.0.0/26", "192.168.0.64/26", "192.168.0.128/26", "192.168.0.192/26"}, nil, }, { Net4FromStr("192.168.0.0/24"), 23, []string{}, ErrBadMaskLength, }, { Net4FromStr("192.168.0.0/32"), 0, []string{}, ErrBadMaskLength, }, } func TestNet4_Subnet(t *testing.T) { for i, tt := range subnet4Tests { subnets, err := tt.netblock.Subnet(tt.netmask) if e := compareErrors(err, tt.err); len(e) > 0 { t.Errorf("[%d] %s", i, e) } else { v := compareNet4ArraysToStringRepresentation(subnets, tt.subnets) if v == false { t.Errorf("[%d] want len %d got %d: %v", i, len(tt.subnets), len(subnets), subnets) } } } } var supernet4Tests = []struct { in Net4 masklen int out Net4 err error }{ { Net4FromStr("192.168.1.0/24"), 23, Net4FromStr("192.168.0.0/23"), nil, }, { Net4FromStr("192.168.1.0/24"), 0, Net4FromStr("192.168.0.0/23"), nil, }, { Net4FromStr("192.168.1.0/24"), 22, Net4FromStr("192.168.1.0/22"), nil, }, { Net4FromStr("192.168.1.4/30"), 24, Net4FromStr("192.168.1.0/24"), nil, }, { Net4FromStr("192.168.0.0/24"), 25, Net4{}, ErrBadMaskLength, }, } func TestNet4_Supernet(t *testing.T) { for i, tt := range supernet4Tests { out, err := tt.in.Supernet(tt.masklen) if e := compareErrors(err, tt.err); len(e) > 0 { t.Errorf("[%d] %s", i, e) } else { if v := CompareNets(out, tt.out); v != 0 { t.Errorf("[%d] want %s got %s", i, tt.out, out) } } } } func TestCompareNets(t *testing.T) { net4map := map[int]Net4{ 0: Net4FromStr("192.168.0.0/16"), 1: Net4FromStr("192.168.0.0/23"), 2: Net4FromStr("192.168.1.0/24"), 3: Net4FromStr("192.168.1.0/24"), 4: Net4FromStr("192.168.3.0/26"), 5: Net4FromStr("192.168.3.64/26"), 6: Net4FromStr("192.168.3.128/25"), 7: Net4FromStr("192.168.4.0/24"), } net4list := ByNet{} for _, ipn := range net4map { net4list = append(net4list, ipn) } sort.Sort(ByNet(net4list)) for pos, ipn := range net4map { if v := CompareNets(net4list[pos], ipn); v != 0 { for i, aipn := range net4list { if v := CompareNets(ipn, aipn); v == 0 { t.Errorf("subnet %s want position %d got %d", ipn, pos, i) break } } } } } var containsNet4Tests = []struct { ipn1 Net4 ipn2 Net4 result bool }{ {Net4FromStr("192.168.0.0/16"), Net4FromStr("192.168.45.0/24"), true}, {Net4FromStr("192.168.45.0/24"), Net4FromStr("192.168.45.0/26"), true}, {Net4FromStr("192.168.45.0/24"), Net4FromStr("192.168.46.0/26"), false}, {Net4FromStr("10.1.1.1/24"), Net4FromStr("10.0.0.0/8"), false}, } func TestNet4_ContainsNet(t *testing.T) { for i, tt := range containsNet4Tests { result := tt.ipn1.ContainsNet(tt.ipn2) if result != tt.result { t.Errorf("[%d] want %t got %t", i, tt.result, result) } } } func TestNet4_Is4in6(t *testing.T) { nf := Net4FromStr("192.168.0.0./16") //nf := NewNet4(ForceIP4(net.ParseIP("192.168.0.0")), 16) if nf.Is4in6() != false { t.Errorf("should be false for '192.168.0.0/16'") } nt := NewNet4(net.ParseIP("::ffff:c0a8:0000"), 16) if nt.Is4in6() != true { t.Errorf("should be true for '::ffff:c0a8:0000/16'") } } func compareNet4ArraysToStringRepresentation(a []Net4, b []string) bool { if len(a) != len(b) { return false } for i, n := range a { if n.String() != b[i] { return false } } return true } iplib-1.0.3/net6.go000066400000000000000000000250741414014643700140310ustar00rootroot00000000000000package iplib import ( "math" "math/big" "net" "sort" "sync" ) // Net6 is an implementation of Net that supports IPv6 operations. To // initialize a Net6 you must supply a network address and mask prefix as // with Net4, but you may also optionally supply an integer value between // 0 and 128 that Net6 will mask out from the right, via a HostMask (see the // documentation for HostMask in this library). If "0" HostMask will be // ignored. The sum of netmask prefix and hostmask must be less than 128. // // Hostmask affects Count, Enumerate, LastAddress, NextIP and PreviousIP; it // also affects NextNet and PreviousNet which will inherit the hostmask from // their parent. Subnet and Supernet both require a hostmask in their function // calls type Net6 struct { net.IPNet Hostmask HostMask } // NewNet6 returns an initialized Net6 object at the specified netmasklen with // the specified hostmasklen. If netmasklen or hostmasklen is greater than 128 // it will return an empty object; it will also return an empty object if the // sum of the two masks is 128 or greater. If a v4 address is supplied it // will be treated as a RFC4291 v6-encapsulated-v4 network (which is the // default behavior for net.IP) func NewNet6(ip net.IP, netmasklen, hostmasklen int) Net6 { var maskMax = 128 if Version(ip) != IP6Version { return Net6{IPNet: net.IPNet{}, Hostmask: HostMask{}} } if (netmasklen == 127 || netmasklen == 128) && hostmasklen == 0 { netmask := net.CIDRMask(netmasklen, maskMax) n := net.IPNet{IP: ip.Mask(netmask), Mask: netmask} return Net6{IPNet: n, Hostmask: NewHostMask(0)} } if netmasklen+hostmasklen >= maskMax { return Net6{IPNet: net.IPNet{}, Hostmask: HostMask{}} } netmask := net.CIDRMask(netmasklen, maskMax) n := net.IPNet{IP: ip.Mask(netmask), Mask: netmask} return Net6{IPNet: n, Hostmask: NewHostMask(hostmasklen)} } // Net6FromStr takes a string which should be a v6 address in CIDR notation // and returns an initialized Net6. If the string isn't parseable an empty // Net6 will be returned func Net6FromStr(s string) Net6 { _, n, err := ParseCIDR(s) if err != nil { return Net6{} } if n6, ok := n.(Net6); ok { return n6 } return Net6{} } // Contains returns true if ip is contained in the represented netblock func (n Net6) Contains(ip net.IP) bool { return n.IPNet.Contains(ip) } // ContainsNet returns true if the given Net is contained within the // represented block func (n Net6) ContainsNet(network Net) bool { l1, _ := n.Mask().Size() l2, _ := network.Mask().Size() return l1 <= l2 && n.Contains(network.IP()) } // Controls returns true if ip is within the scope of the represented block, // meaning that it is both inside of the netmask and outside of the hostmask. // In other words this function will return true if ip would be enumerated by // this Net6 instance func (n Net6) Controls(ip net.IP) bool { if !n.Contains(ip) { return false } if !n.contained(ip) { return false } return true } // Count returns the number of IP addresses in the represented netblock func (n Net6) Count() *big.Int { ones, all := n.Mask().Size() // first check if this is an RFC6164 point-to-point subnet exp := all - ones if exp == 1 { return big.NewInt(2) // special handling for RFC6164 /127 } if exp == 0 { return big.NewInt(1) // special handling for /128 } oneser, _ := n.Hostmask.Size() exp -= oneser var z, e = big.NewInt(2), big.NewInt(int64(exp)) return z.Exp(z, e, nil) } // Enumerate generates an array of all usable addresses in Net up to the // given size starting at the given offset, so long as the result is less than // MaxUint32. If size=0 the entire block is enumerated (again, so long as the // result is less than MaxUint32). // // For consistency, enumerating a /128 will return the IP in a 1 element array func (n Net6) Enumerate(size, offset int) []net.IP { if n.IP() == nil { return nil } count := getEnumerationCount(size, offset, n.Count()) // Handle edge-case mask sizes ones, _ := n.Mask().Size() if ones == 128 { return []net.IP{n.FirstAddress()} } if count < 1 { return []net.IP{} } addrs := make([]net.IP, count) fip := n.FirstAddress() if offset != 0 { fip, _ = IncrementIP6WithinHostmask(fip, n.Hostmask, big.NewInt(int64(offset))) } // for large requests ( >250 million) response times are very similar // across a wide-array of goroutine counts. Limiting the per-goroutine // workload in this way simply ensures that we [a] can dynamically expand // our worker-pool based on request size; and [b] don't have to worry // about exhausting some upper bound of goroutines -- enumerate requests // are limited to MaxInt32, so we won't generate more than 32768 limit := uint32(65535) pos := uint32(0) wg := sync.WaitGroup{} for pos < count { incr := limit if limit > count - pos { incr = count - pos } wg.Add(1) go func(fip net.IP, pos, count uint32) { defer wg.Done() addrs[pos], _ = IncrementIP6WithinHostmask(fip, n.Hostmask, big.NewInt(int64(pos))) for i := uint32(1); i < count; i++ { pos++ addrs[pos], _ = NextIP6WithinHostmask(addrs[pos-1], n.Hostmask) } }(fip, pos, incr) pos = pos + incr } wg.Wait() return addrs } // FirstAddress returns the first usable address for the represented network func (n Net6) FirstAddress() net.IP { return getCloneIP(n.IP()) } // LastAddress returns the last usable address for the represented network func (n Net6) LastAddress() net.IP { xip := make([]byte, len(n.IPNet.IP)) wc := n.wildcard() for pos := range n.IP() { xip[pos] = n.IP()[pos] + (wc[pos] - n.Hostmask[pos]) } return xip } // Mask returns the netmask of the netblock func (n Net6) Mask() net.IPMask { return n.IPNet.Mask } // IP returns the network address for the represented network, e.g. // the lowest IP address in the given block func (n Net6) IP() net.IP { return n.IPNet.IP } // NextIP takes a net.IP as an argument and attempts to increment it by one // within the boundary of allocated network-bytes. If the resulting address is // outside of the range of the represented network it will return an empty // net.IP and an ErrAddressOutOfRange func (n Net6) NextIP(ip net.IP) (net.IP, error) { xip, _ := NextIP6WithinHostmask(ip, n.Hostmask) if !n.Contains(xip) { return net.IP{}, ErrAddressOutOfRange } return xip, nil } // NextNet takes a CIDR mask-size as an argument and attempts to create a new // Net object just after the current Net, at the requested mask length and // with the same hostmask as the current Net func (n Net6) NextNet(masklen int) Net6 { hmlen, _ := n.Hostmask.Size() if masklen == 0 { masklen, _ = n.Mask().Size() } nn := NewNet6(n.IP(), masklen, hmlen) xip, _ := NextIP6WithinHostmask(nn.LastAddress(), n.Hostmask) return NewNet6(xip, masklen, hmlen) } // PreviousIP takes a net.IP as an argument and attempts to decrement it by // one within the boundary of the allocated network-bytes. If the resulting // address is outside the range of the represented netblock it will return an // empty net.IP and an ErrAddressOutOfRange func (n Net6) PreviousIP(ip net.IP) (net.IP, error) { xip, _ := PreviousIP6WithinHostmask(ip, n.Hostmask) if !n.Contains(xip) { return net.IP{}, ErrAddressOutOfRange } return xip, nil } // PreviousNet takes a CIDR mask-size as an argument and creates a new Net // object just before the current one, at the requested mask length. If the // specified mask is for a larger network than the current one then the new // network may encompass the current one func (n Net6) PreviousNet(masklen int) Net6 { hmlen, _ := n.Hostmask.Size() if masklen == 0 { masklen, _ = n.Mask().Size() } nn := NewNet6(n.IP(), masklen, hmlen) xip, _ := PreviousIP6WithinHostmask(nn.IP(), n.Hostmask) return NewNet6(xip, masklen, hmlen) } // String returns the CIDR notation of the enclosed network e.g. 2001:db8::/16 func (n Net6) String() string { return n.IPNet.String() } // Subnet takes a CIDR mask-size as an argument and carves the current Net // object into subnets of that size, returning them as a []Net. The mask // provided must be a larger-integer than the current mask. If set to 0 Subnet // will carve the network in half. Hostmask must be provided if desired func (n Net6) Subnet(netmasklen, hostmasklen int) ([]Net6, error) { ones, all := n.Mask().Size() if netmasklen == 0 { netmasklen = ones + 1 } if ones > netmasklen || (hostmasklen+netmasklen) > all { return nil, ErrBadMaskLength } mask := net.CIDRMask(netmasklen, all) netlist := []Net6{{IPNet: net.IPNet{IP: n.IP(), Mask: mask}, Hostmask: NewHostMask(hostmasklen)}} for CompareIPs(netlist[len(netlist)-1].LastAddress(), n.LastAddress()) == -1 { xip, _ := NextIP6WithinHostmask(netlist[len(netlist)-1].LastAddress(), n.Hostmask) if len(xip) == 0 || xip == nil { return netlist, nil } ng := net.IPNet{IP: xip, Mask: mask} netlist = append(netlist, Net6{ng, NewHostMask(hostmasklen)}) } return netlist, nil } // Supernet takes a CIDR mask-size as an argument and returns a Net object // containing the supernet of the current Net at the requested mask length. // The mask provided must be a smaller-integer than the current mask. If set // to 0 Supernet will return the next-largest network func (n Net6) Supernet(netmasklen, hostmasklen int) (Net6, error) { ones, all := n.Mask().Size() if ones < netmasklen { return Net6{}, ErrBadMaskLength } if netmasklen == 0 { netmasklen = ones - 1 } mask := net.CIDRMask(netmasklen, all) ng := net.IPNet{IP: n.IP().Mask(mask), Mask: mask} return Net6{ng, NewHostMask(hostmasklen)}, nil } // Version returns the version of IP for the enclosed netblock as an int. 6 // in this case func (n Net6) Version() int { return IP6Version } // return true if 'ip' is within the hostmask of n func (n Net6) contained(ip net.IP) bool { b, pos := n.Hostmask.BoundaryByte() if pos == -1 { return true } if ip[pos] > b { return false } for i := len(ip) - 1; i > pos; i-- { if ip[i] > 0 { return false } } return true } func (n Net6) wildcard() net.IPMask { wc := make([]byte, len(n.Mask())) for i, b := range n.Mask() { wc[i] = 0xff - b } return wc } // getEnumerationCount returns the size of the array needed to satisfy an // Enumerate request. Mostly split out to ease testing of larger values func getEnumerationCount(reqSize, offset int, count *big.Int) uint32 { sizes := []uint32{math.MaxUint32} if count.IsInt64() { realCount := uint32(0) if int(count.Int64()) > offset { realCount = uint32(count.Int64()) - uint32(offset) } sizes = append(sizes, realCount) } if uint32(reqSize) != 0 { sizes = append(sizes, uint32(reqSize)) } sort.Slice(sizes, func(i, j int) bool { return sizes[i] < sizes[j] }) return sizes[0] } iplib-1.0.3/net6_test.go000066400000000000000000000467611414014643700150760ustar00rootroot00000000000000package iplib import ( "math/big" "net" "sort" "testing" ) var NewNet6Tests = []struct { s string addr net.IP netmasklen int hostmasklen int isEmpty bool }{ {"2001:db8::/32", net.ParseIP("2001:db8::"), 32, 0, false}, {"2001:db8::/32", net.ParseIP("2001:db8::"), 32, 16, false}, {"", net.ParseIP("2001:db8::"), 33, 96, true}, {"", net.ParseIP("2001:db8::"), 112, 17, true}, {"2001:db8::/112", net.ParseIP("2001:db8::"), 112, 15, false}, {"2001:db8::/127", net.ParseIP("2001:db8::"), 127, 0, false}, {"2001:db8::/128", net.ParseIP("2001:db8::"), 128, 0, false}, } func TestNewNet6(t *testing.T) { for i, tt := range NewNet6Tests { ipn := NewNet6(tt.addr, tt.netmasklen, tt.hostmasklen) if (tt.isEmpty == true && ipn.IP() != nil) || (tt.isEmpty == false && ipn.IP() == nil) { t.Errorf("[%d] expect isEmpty == %t, but is not", i, tt.isEmpty) } else if tt.isEmpty == false { if ipn.String() != tt.s { t.Errorf("[%d] Net6 want %s got %s", i, tt.s, ipn.String()) } hostmasklen, _ := ipn.Hostmask.Size() netmasklen, _ := ipn.Mask().Size() if tt.hostmasklen != hostmasklen { t.Errorf("[%d] want hostmask size %d got %d", i, tt.hostmasklen, hostmasklen) } if tt.netmasklen != netmasklen { t.Errorf("[%d] want netmask size %d got %d", i, tt.netmasklen, netmasklen) } } } } var Net6FromStrTests = []struct { ins string outs string isEmpty bool }{ {"2001:db8::/64", "2001:db8::/64", false}, {"notanaddress!!", "", true}, {"::ffff:c0a8:0000/16", "", true}, } func TestNet6FromStr(t *testing.T) { for i, tt := range Net6FromStrTests { ipn := Net6FromStr(tt.ins) if (tt.isEmpty == true && ipn.IP() != nil) || (tt.isEmpty == false && ipn.IP() == nil) { t.Errorf("[%d] expect isEmpty == %t, but is not", i, tt.isEmpty) } else if tt.isEmpty == false { if tt.outs != ipn.String() { t.Errorf("[%d] want %s got %s", i, tt.outs, ipn.String()) } } } } var Net6Tests = []struct { ip string firstaddr string lastaddr string hostmask int hostmaskpos int netmasklen int count string // converted to big.Int }{ // 0-7 hostmask applied on byte boundaries { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:db8:1234:5678:9a00::", "2001:0db8:1234:5678:9aff:ffff:ffff:ffff", 0, -1, 72, "72057594037927936", }, { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678::", "2001:0db8:1234:5678:ffff:ffff:ffff:ff00", 8, 15, 64, "72057594037927936", }, { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678::", "2001:0db8:1234:5678:ffff:ffff:ffff:0", 16, 14, 64, "281474976710656", }, { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678::", "2001:0db8:1234:5678:ffff:ffff:ff00:0", 24, 13, 64, "1099511627776", }, { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678::", "2001:0db8:1234:5678:ffff:ffff::", 32, 12, 64, "4294967296", }, { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678::", "2001:0db8:1234:5678:ffff:ff00::", 40, 11, 64, "16777216", }, { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678::", "2001:0db8:1234:5678:ffff::", 48, 10, 64, "65536", }, { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678::", "2001:0db8:1234:5678:ff00::", 56, 9, 64, "256", }, // 7-15: hostmask applied within a byte { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678::", "2001:0db8:1234:5678:7f00::", 57, 8, 64, "128", }, { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678::", "2001:0db8:1234:5678:3f00::", 58, 8, 64, "64", }, { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678::", "2001:0db8:1234:5678:1f00::", 59, 8, 64, "32", }, { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678::", "2001:0db8:1234:5678:0f00::", 60, 8, 64, "16", }, { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678::", "2001:0db8:1234:5678:0700::", 61, 8, 64, "8", }, { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678::", "2001:0db8:1234:5678:0300::", 62, 8, 64, "4", }, { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678::", "2001:0db8:1234:5678:0100::", 63, 8, 64, "2", }, { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678::", "", 64, -1, 64, "0", }, // entire address masked // 16-19: hostmask and netmask applied on same byte { "2001:0db8:1234:5678:9abc:def0::", "2001:db8:1234:5678:9abc:8000::", "2001:0db8:1234:5678:9abc:ff00::", 40, 11, 81, "128", }, { "2001:0db8:1234:5678:9abc:def0::", "2001:db8:1234:5678:9abc:c000::", "2001:0db8:1234:5678:9abc:7f00::", 41, 10, 82, "32", }, { "2001:0db8:1234:5678:9abc:def0::", "2001:db8:1234:5678:9abc:c000::", "2001:0db8:1234:5678:9abc:1f00::", 42, 10, 83, "8", }, { "2001:0db8:1234:5678:9abc:def0::", "2001:db8:1234:5678:9abc:d000::", "2001:0db8:1234:5678:9abc:ff00::", 43, 10, 84, "2", }, // 20-21, address with /128 and /127 respectively { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678:9abc:def0:1234:5678", 0, -1, 128, "1", }, { "2001:0db8:1234:5678:9abc:def0:1234:5678", "2001:db8:1234:5678:9abc:def0:1234:5678", "2001:0db8:1234:5678:9abc:def0:1234:5679", 0, -1, 127, "2", }, // 22, address with no netmask or hostmask { "2001:0db8::", "::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 0, -1, 0, "340282366920938463463374607431768211456", }, } func TestNet6_Version(t *testing.T) { for i, tt := range Net6Tests { ipn := NewNet6(net.ParseIP(tt.ip), tt.netmasklen, tt.hostmask) if ipn.Version() != IP6Version { t.Errorf("[%d] want version 6, got %d", i, ipn.Version()) } } } func TestNet6_Count(t *testing.T) { for i, tt := range Net6Tests { ipn := NewNet6(net.ParseIP(tt.ip), tt.netmasklen, tt.hostmask) ttcount := new(big.Int) ttcount.SetString(tt.count, 10) if ipn.IPNet.IP == nil { if tt.count != "0" { t.Fatalf("[%d] produced nil Net6{}, but should not have", i) } continue } if v := ttcount.Cmp(ipn.Count()); v != 0 { t.Errorf("[%d] count: want %s got %d", i, tt.count, ipn.Count()) } } } func TestNet6_FirstAddress(t *testing.T) { for i, tt := range Net6Tests { firstAddr := net.ParseIP(tt.firstaddr) ipn := NewNet6(net.ParseIP(tt.ip), tt.netmasklen, tt.hostmask) if ipn.IPNet.IP == nil { if tt.count != "0" { t.Fatalf("[%d] produced nil Net6{}, but should not have", i) } continue } if v := CompareIPs(firstAddr, ipn.IP()); v != 0 { t.Errorf("[%d] network address: want %s got %s", i, firstAddr, ipn.IP()) } if v := CompareIPs(firstAddr, ipn.FirstAddress()); v != 0 { t.Errorf("[%d] first address: want %s got %s", i, firstAddr, ipn.FirstAddress()) } } } func TestNet6_LastAddress(t *testing.T) { for i, tt := range Net6Tests { lastAddr := net.ParseIP(tt.lastaddr) ipn := NewNet6(net.ParseIP(tt.ip), tt.netmasklen, tt.hostmask) la := ipn.LastAddress() if v := CompareIPs(lastAddr, la); v != 0 { t.Errorf("[%d] last address: want %s got %s", i, lastAddr, la) } } } func TestNet6_BoundaryByte(t *testing.T) { for i, tt := range Net6Tests { ipn := NewNet6(net.ParseIP(tt.ip), tt.netmasklen, tt.hostmask) _, bpos := ipn.Hostmask.BoundaryByte() if bpos != tt.hostmaskpos { t.Errorf("[%d] boundary position: want %d got %d", i, tt.hostmaskpos, bpos) } } } func TestNewNet6WrongVersion(t *testing.T) { n := NewNet6(ForceIP4(net.ParseIP("10.0.0.0")), 8, 0) if v := CompareIPs(n.IP(), nil); v != 0 { t.Errorf("Expected empty Net6, got %s", n.IP()) } } var enumerate6Tests = []struct { inaddr net.IP hostmasklen int netmasklen int total int last net.IP }{ { // one element network returns itself net.ParseIP("2001:db8:1000:2000:3000:4000::"), 0, 128, 1, net.ParseIP("2001:db8:1000:2000:3000:4000::"), }, { // no address in list net.ParseIP("2001:db8:1000:2000:3000:4000::"), 64, 64, 0, net.ParseIP("::"), }, { // RFC6164 net.ParseIP("2001:db8:1000:2000:3000:4000::"), 0, 127, 2, net.ParseIP("2001:db8:1000:2000:3000:4000::1"), }, { net.ParseIP("2001:db8:1000:2000:3000:4000::"), 48, 64, 65536, net.ParseIP("2001:db8:1000:2000:ffff::"), }, } func TestNet6_Enumerate(t *testing.T) { for i, tt := range enumerate6Tests { n := NewNet6(tt.inaddr, tt.netmasklen, tt.hostmasklen) addrlist := n.Enumerate(0, 0) if len(addrlist) != tt.total { t.Errorf("[%d] total want %d got %d", i, tt.total, len(addrlist)) } if len(addrlist) > 0 { if v := CompareIPs(addrlist[len(addrlist)-1], tt.last); v != 0 { t.Errorf("[%d] last address: want %s got %s", i, tt.last, addrlist[len(addrlist)-1]) } } } } var enumerate6VariableTests = []struct { hostmasklen int netmasklen int offset int size int total int first net.IP last net.IP }{ { // no offset, enumerate entire block 56, 56, 0, 0, 65536, net.ParseIP("2001:db8:1000:2000::"), net.ParseIP("2001:db8:1000:20ff:ff00::"), }, { // enumerate the entire back half 56, 56, 32768, 0, 32768, net.ParseIP("2001:db8:1000:2080::"), net.ParseIP("2001:db8:1000:20ff:ff00::"), }, { // enumerate half of the back half 56, 56, 32768, 16384, 16384, net.ParseIP("2001:db8:1000:2080::"), net.ParseIP("2001:db8:1000:20bf:ff00::"), }, { // enumerate past the boundary 56, 56, 65000, 5000, 536, net.ParseIP("2001:db8:1000:20fd:e800::"), net.ParseIP("2001:db8:1000:20ff:ff00::"), }, { // enumerate starting after the boundary 56, 56, 65537, 16, 0, net.ParseIP("2001:db8:1000:2080::"), net.ParseIP("2001:db8:1000:20bf:ff00::"), }, } func TestNet6_EnumerateWithVariables(t *testing.T) { ip := net.ParseIP("2001:db8:1000:2000:3000:4000::") for i, tt := range enumerate6VariableTests { n := NewNet6(ip, tt.netmasklen, tt.hostmasklen) addrlist := n.Enumerate(tt.size, tt.offset) if len(addrlist) != tt.total { t.Errorf("[%d] size: want %d got %d", i, tt.total, len(addrlist)) } if len(addrlist) > 0 { x := CompareIPs(tt.first, addrlist[0]) if x != 0 { t.Errorf("[%d] first member: want %s got %s", i, tt.first, addrlist[0]) } y := CompareIPs(tt.last, addrlist[len(addrlist)-1]) if y != 0 { t.Errorf("[%d] last member: want %s got %s", i, tt.last, addrlist[len(addrlist)-1]) } } } } var incr6Tests = []struct { netmask int hostmask int thisaddr net.IP nextaddr net.IP err error }{ { // address not in the netblock 64, 0, net.ParseIP("2001:db8:123:4567::"), nil, ErrAddressOutOfRange, }, { // address outside of hostmask 64, 56, net.ParseIP("2001:db8:1234:5678:ff::1"), nil, ErrAddressOutOfRange, }, { // increment from 1st to second address, no hostmask 64, 0, net.ParseIP("2001:db8:1234:5678::"), net.ParseIP("2001:db8:1234:5678::1"), nil, }, { // increment from 1st to second address, with hostmask 64, 56, net.ParseIP("2001:db8:1234:5678::"), net.ParseIP("2001:db8:1234:5678:100::"), nil, }, { // increment from last address, no hostmask 64, 0, net.ParseIP("2001:db8:1234:5678:ffff:ffff:ffff:ffff"), nil, ErrAddressOutOfRange, }, { // increment from last address, no hostmask 64, 56, net.ParseIP("2001:db8:1234:5678:ff00::"), nil, ErrAddressOutOfRange, }, } func TestNet6_NextIP(t *testing.T) { netaddr := net.ParseIP("2001:db8:1234:5678::") for i, tt := range incr6Tests { ipn := NewNet6(netaddr, tt.netmask, tt.hostmask) nextaddr, err := ipn.NextIP(tt.thisaddr) if e := compareErrors(err, tt.err); len(e) > 0 { t.Errorf("[%d] %s", i, e) } else { if !nextaddr.Equal(tt.nextaddr) { t.Errorf("[%d] want %s got %s", i, tt.nextaddr, nextaddr) } } } } func TestNet6_NextIPBadStartAddress(t *testing.T) { ipn := NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 56) ip, err := ipn.NextIP(net.ParseIP("2001:db8:1234:5678::12")) if e := compareErrors(err, ErrAddressOutOfRange); len(e) > 0 { t.Errorf("expected out-of-range error, got IP '%s', error '%s'", ip, err) } ip, err = ipn.NextIP(net.ParseIP("2001:db8:1234:5677::")) if e := compareErrors(err, ErrAddressOutOfRange); len(e) > 0 { t.Errorf("expected out-of-range error, got IP '%s', error '%s'", ip, err) } } var incr6SubnetTests = []struct { netmasklen int // hostmasklen int next Net6 }{ {64, NewNet6(net.ParseIP("2001:db8:1234:5679::"), 64, 56)}, {0, NewNet6(net.ParseIP("2001:db8:1234:5679::"), 64, 56)}, {48, NewNet6(net.ParseIP("2001:db8:1235::"), 48, 56)}, {1, NewNet6(net.ParseIP("8000::"), 1, 56)}, {72, Net6{}}, // netmask + hostnask == 128 } func TestNet6_NextNet(t *testing.T) { ipn := NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 63) for i, tt := range incr6SubnetTests { next := ipn.NextNet(tt.netmasklen) if v := CompareNets(next, tt.next); v != 0 { // WHY IS COMPARE NOT WORKING?! t.Errorf("[%d] want %s got %s", i, tt.next, next) } } } var decr6Tests = []struct { netmask int hostmask int thisaddr net.IP prevaddr net.IP err error }{ { // address not in the netblock 64, 0, net.ParseIP("2001:db8:123:4567::"), nil, ErrAddressOutOfRange, }, { // address outside of hostmask 64, 56, net.ParseIP("2001:db8:1234:5678:ff::10"), nil, ErrAddressOutOfRange, }, { // decrement from last address, no hostmask 64, 0, net.ParseIP("2001:db8:1234:5678:ffff:ffff:ffff:ffff"), net.ParseIP("2001:db8:1234:5678:ffff:ffff:ffff:fffe"), nil, }, { // decrement from last address, with hostmask 64, 56, net.ParseIP("2001:db8:1234:5678:ff00::"), net.ParseIP("2001:db8:1234:5678:fe00::"), nil, }, { // decrement from first address, no hostmask 64, 0, net.ParseIP("2001:db8:1234:5678::"), nil, ErrAddressOutOfRange, }, { // decrement from first address, no hostmask 64, 56, net.ParseIP("2001:db8:1234:5678::"), nil, ErrAddressOutOfRange, }, } func TestNet6_PreviousIP(t *testing.T) { netaddr := net.ParseIP("2001:db8:1234:5678::") for i, tt := range decr6Tests { ipn := NewNet6(netaddr, tt.netmask, tt.hostmask) prevaddr, err := ipn.PreviousIP(tt.thisaddr) if e := compareErrors(err, tt.err); len(e) > 0 { t.Errorf("[%d] %s", i, e) } else { if !prevaddr.Equal(tt.prevaddr) { t.Errorf("[%d] wamt %s got %s", i, tt.prevaddr, prevaddr) } } } } func TestNet6_PreviousIPBadStartAddress(t *testing.T) { ipn := NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 56) ip, err := ipn.PreviousIP(net.ParseIP("2001:db8:1234:5678::12")) if e := compareErrors(err, ErrAddressOutOfRange); len(e) > 0 { t.Errorf("expected out-of-range error, got IP '%s', error '%s'", ip, err) } ip, err = ipn.PreviousIP(net.ParseIP("2001:db8:1234:5677::")) if e := compareErrors(err, ErrAddressOutOfRange); len(e) > 0 { t.Errorf("expected out-of-range error, got IP '%s', error '%s'", ip, err) } } var decr6SubnetTests = []struct { netmasklen int prev Net6 }{ {64, NewNet6(net.ParseIP("2001:db8:1234:5677::"), 64, 56)}, {0, NewNet6(net.ParseIP("2001:db8:1234:5677::"), 64, 56)}, {48, NewNet6(net.ParseIP("2001:db8:1233::"), 48, 56)}, {5, NewNet6(net.ParseIP("1800::"), 5, 56)}, {72, Net6{}}, // netmask + hostnask == 128 } func TestNet6_PreviousNet(t *testing.T) { ipn := NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, 56) for i, tt := range decr6SubnetTests { prev := ipn.PreviousNet(tt.netmasklen) if v := CompareNets(prev, tt.prev); v != 0 { t.Errorf("[%d] want %v got %v", i, tt.prev, prev) } } } var subnet6Tests = []struct { netmasklen int hostmasklen int subnets []Net6 err error }{ { 0, 0, []Net6{ NewNet6(net.ParseIP("2001:db8:1234:5678::"), 65, 0), NewNet6(net.ParseIP("2001:db8:1234:5678:8000::"), 65, 0), }, nil, }, { 68, 61, []Net6{}, ErrBadMaskLength, }, { 65, 0, []Net6{ NewNet6(net.ParseIP("2001:db8:1234:5678::"), 65, 0), NewNet6(net.ParseIP("2001:db8:1234:5678:8000::"), 65, 0), }, nil, }, { 66, 0, []Net6{ NewNet6(net.ParseIP("2001:db8:1234:5678::"), 66, 0), NewNet6(net.ParseIP("2001:db8:1234:5678:4000::"), 66, 0), NewNet6(net.ParseIP("2001:db8:1234:5678:8000::"), 66, 0), NewNet6(net.ParseIP("2001:db8:1234:5678:c000::"), 66, 0), }, nil, }, { 63, 0, nil, ErrBadMaskLength, }, } func TestNet6_Subnet(t *testing.T) { for i, tt := range subnet6Tests { ipn := NewNet6(net.ParseIP("2001:db8:1234:5678::"), 64, tt.hostmasklen) subnets, err := ipn.Subnet(tt.netmasklen, tt.hostmasklen) if e := compareErrors(err, tt.err); len(e) > 0 { t.Errorf("[%d] %s", i, e) } else { if v := compareNet6Arrays(subnets, tt.subnets); v == false { t.Errorf("[%d] want len %d got %d: %v", i, len(tt.subnets), len(subnets), subnets) } } } } var supernet6Tests = []struct { in Net6 netmasklen int out Net6 err error }{ { Net6FromStr("2001:db8:1234:5678::/64"), 60, Net6FromStr("2001:db8:1234:5670::/60"), nil, }, { Net6FromStr("2001:db8:1234:5671::/64"), 63, Net6FromStr("2001:db8:1234:5670::/63"), nil, }, { Net6FromStr("2001:db8:1234:5671::/64"), 0, Net6FromStr("2001:db8:1234:5670::/63"), nil, }, { Net6FromStr("2001:db8:1234:5678::/64"), 65, Net6{}, ErrBadMaskLength, }, { Net6FromStr("::/0"), 0, Net6{}, nil, }, } func TestNet6_Supernet(t *testing.T) { for i, tt := range supernet6Tests { out, err := tt.in.Supernet(tt.netmasklen, 0) if e := compareErrors(err, tt.err); len(e) > 0 { t.Errorf("[%d] %s", i, e) } else { if v := CompareNets(out, tt.out); v != 0 { t.Errorf("[%d] want %s got %s", i, tt.out, out) } } } } func TestCompareNets6(t *testing.T) { net6map := map[int]Net6{ 0: Net6FromStr("::/0"), 1: Net6FromStr("::/128"), 2: Net6FromStr("2001:db8::/96"), 3: Net6FromStr("2001:db8:12::/64"), 4: Net6FromStr("2001:db8:1234::/64"), 5: Net6FromStr("2001:db8:1234:5678::/63"), 6: Net6FromStr("2001:db8:1234:5678::/64"), 7: Net6FromStr("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128"), } net6list := ByNet{} for _, ipn := range net6map { net6list = append(net6list, ipn) } sort.Sort(ByNet(net6list)) for pos, ipn := range net6map { if v := CompareNets(net6list[pos], ipn); v != 0 { for i, aipn := range net6list { if v := CompareNets(ipn, aipn); v == 0 { t.Errorf("subnet %s want position %d got %d", ipn, pos, i) break } } } } } var containsNet6Tests = []struct { netblock1 string netblock2 string result bool }{ {"2001:db8:1000:2000::/64", "2001:db8:1000:2000:3000::/72", true}, {"2001:db8:1000:2000::/64", "2001:db8:1000:2000:3000:4000:5000:6000/127", true}, {"2001:db8:1000:2000:3000::/72", "2001:db8:1000:2000::/64", false}, {"2001:db8:1000:2000::/64", "2001:db8:1000:3000::/64", false}, {"2001:db8:1000:2000::/64", "2001:db8:1000:2000::/64", true}, } func TestNet6_ContainsNet(t *testing.T) { for i, tt := range containsNet6Tests { _, ipn, _ := ParseCIDR(tt.netblock1) _, sub, _ := ParseCIDR(tt.netblock2) result := ipn.ContainsNet(sub) if result != tt.result { t.Errorf("[%d] For \"%s contains %s\" want %v got %v", i, tt.netblock1, tt.netblock2, tt.result, result) } } } var controlsTests = []struct { ipn Net6 addrs map[string]bool }{ { NewNet6(net.ParseIP("2001:db8:1::"), 56, 64), map[string]bool{ "2001:db8:1:1::": true, "2001:db8:2::": false, "2001:db8:1:ff:1::": false, }, }, } func TestNet6_Controls(t *testing.T) { for _, tt := range controlsTests { for ip, v := range tt.addrs { if tt.ipn.Controls(net.ParseIP(ip)) != v { t.Errorf("Net6 '%s' ip '%s' want %t got %t", tt.ipn, ip, v, tt.ipn.Controls(net.ParseIP(ip))) } } } } func compareNet6Arrays(a []Net6, b []Net6) bool { if len(a) != len(b) { return false } for i, n := range a { if v := CompareNets(n, b[i]); v != 0 { return false } } return true } iplib-1.0.3/net_test.go000066400000000000000000000100021414014643700147630ustar00rootroot00000000000000package iplib import ( "fmt" "net" "testing" ) var NewNetTests = []struct { ip net.IP masklen int out string }{ { net.ParseIP("192.168.0.0"), 32, "192.168.0.0/32", }, { net.ParseIP("192.168.0.0"), 24, "192.168.0.0/24", }, { net.ParseIP("192.168.0.7"), 32, "192.168.0.7/32", }, { net.ParseIP("192.168.0.7"), 24, "192.168.0.0/24", }, { net.ParseIP("2001:db8::"), 64, "2001:db8::/64", }, { net.ParseIP("::ffff:c0a8:0101"), 16, "192.168.0.0/16", }, } func TestNewNet(t *testing.T) { for i, tt := range NewNetTests { xnet := NewNet(tt.ip, tt.masklen) _, pnet, _ := net.ParseCIDR(tt.out) if xnet.String() != pnet.String() { t.Errorf("[%d] NewNet(%s, %d) expected %s got %s", i, tt.ip.String(), tt.masklen, pnet.String(), xnet.String()) } } } var NewNetBetweenTests = []struct { start net.IP end net.IP xnet string exact bool err error }{ { net.ParseIP("192.168.0.255"), net.ParseIP("192.168.2.0"), "192.168.1.0/24", false, nil, }, { net.ParseIP("192.168.0.255"), net.ParseIP("10.0.0.0"), "", false, ErrNoValidRange, }, { net.ParseIP("192.168.0.255"), net.ParseIP("2001:db8:0:1::"), "", false, ErrNoValidRange, }, { net.ParseIP("2001:db8:0:1::"), net.ParseIP("192.168.0.255"), "", false, ErrNoValidRange, }, { net.ParseIP("192.168.0.255"), net.ParseIP("192.168.0.255"), "", false, ErrNoValidRange, }, { net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.1"), "192.168.1.0/32", true, nil, }, { net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.2"), "192.168.1.1/32", true, nil, }, { net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.2"), "192.168.1.0/31", true, nil, }, { net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.3"), "192.168.1.0/31", false, nil, }, { net.ParseIP("192.168.1.0"), net.ParseIP("192.168.1.3"), "192.168.1.0/30", true, nil, }, { net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.4"), "192.168.1.0/30", false, nil, }, { net.ParseIP("192.168.0.255"), net.ParseIP("192.168.1.5"), "192.168.1.0/30", false, nil, }, { net.ParseIP("192.168.0.254"), net.ParseIP("192.168.2.0"), "192.168.0.255/32", false, nil, }, { net.ParseIP("192.168.0.255"), net.ParseIP("192.168.2.0"), "192.168.1.0/24", false, nil, }, { net.ParseIP("2001:db7:ffff:ffff:ffff:ffff:ffff:ffff"), net.ParseIP("2001:db8:0:1::"), "2001:db8::/64", true, nil, }, } func TestNewNetBetween(t *testing.T) { for i, tt := range NewNetBetweenTests { xnet, exact, err := NewNetBetween(tt.start, tt.end) if e := compareErrors(err, tt.err); len(e) > 0 { t.Errorf("[%d] NewNetBetween(%s, %s) expected error '%v', got '%v'", i, tt.start, tt.end, tt.err, err) } else if tt.err == nil { if xnet == nil && len(tt.xnet) != 0 { t.Fatalf("[%d] should not be nil!", i) } if xnet.String() != tt.xnet { t.Errorf("[%d] NewNetBetween(%s, %s) expected '%s', got '%s'", i, tt.start, tt.end, tt.xnet, xnet.String()) } if exact != tt.exact { t.Errorf("[%d] NewNetBetween(%s, %s) expected '%t', got '%t'", i, tt.start, tt.end, tt.exact, exact) } } } } var ParseCIDRTests = []struct { s string xnet string err error ver int }{ {"not.legit/22", "", fmt.Errorf("invalid CIDR address: not.legit/22"), 0}, {"192.168.1.1", "", fmt.Errorf("invalid CIDR address: 192.168.1.1"), 0}, {"192.168.1.0/24", "192.168.1.0/24", nil, 4}, {"2001:db8::/64", "2001:db8::/64", nil, 6}, {"::ffff:c0a8:0101/32", "192.168.1.1/32", nil, 4}, {"::ffff:c0a9:0101/16", "192.169.0.0/16", nil, 4}, {"::ffff:c0a8:0101/64", "::/64", nil, 6}, } func TestParseCIDR(t *testing.T) { for i, tt := range ParseCIDRTests { _, n, err := ParseCIDR(tt.s) if e := compareErrors(err, tt.err); len(e) > 0 { t.Errorf("[%d] ParseCIDR(%s) expected error '%v', got '%v'", i, tt.s, tt.err, err) } else if tt.err == nil { if n.Version() != tt.ver { t.Errorf("[%d] expected IPNet version '%d' got '%d'", i, tt.ver, n.Version()) } if n.String() != tt.xnet { fmt.Println(n) t.Errorf("[%d] expected '%s' for '%s'", i, tt.xnet, n.String()) } } } }