iid

package
v1.0.8 Latest Latest
Warning

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

Go to latest
Published: Dec 24, 2023 License: MIT Imports: 7 Imported by: 1

README

iid

Documentation CircleCI Go Report Card Coverage Status

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

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

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.

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, if this happens an iid.ErrIIDAddressCollision will be returned. If so counter should be incremented and the function re-run.

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 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

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:

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"
}

Documentation

Overview

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

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrIIDAddressCollision = errors.New("proposed IID collides with IANA reserved IID list")
)

Errors that may be returned by functions in this package

View Source
var Registry []*Reservation

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

Functions

func GenerateRFC7217Addr

func GenerateRFC7217Addr(ip net.IP, hw net.HardwareAddr, counter int64, netid, secret []byte, htype crypto.Hash, scope Scope) (net.IP, error)

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 MakeEUI64Addr

func MakeEUI64Addr(ip net.IP, hw net.HardwareAddr, scope Scope) net.IP

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 MakeOpaqueAddr

func MakeOpaqueAddr(ip net.IP, hw net.HardwareAddr, counter int64, netid, secret []byte) (net.IP, error)

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

Types

type Reservation

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
}

Reservation describes an entry in the IANA IP Special Registry

func GetReservationsForIP

func GetReservationsForIP(ip net.IP) *Reservation

GetReservationsForIP returns a list of any IANA reserved networks that the supplied IP is part of

type Scope

type Scope int

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

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
)

Jump to

Keyboard shortcuts

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