File: iid.go

package info (click to toggle)
golang-github-c-robinson-iplib 1.0.3-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 316 kB
  • sloc: makefile: 2
file content (263 lines) | stat: -rw-r--r-- 8,648 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
/*
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
}