This repository has been archived by the owner on May 13, 2020. It is now read-only.
/
rfc4193.go
193 lines (167 loc) · 5.58 KB
/
rfc4193.go
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
package rfc4193
import (
"crypto/rand"
"crypto/sha1"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"time"
)
// ula is the IPv6 Unique Local Address prefix.
var ula = &net.IPNet{
IP: net.ParseIP("fc00::"),
Mask: net.CIDRMask(7, 128),
}
// A Prefix represents a Local IPv6 Unicast Address prefix, as described in
// RFC 4193, section 3.1.
type Prefix struct {
// Local indicates if the prefix is locally assigned.
Local bool
// GlobalID stores an identifier for a globally unique prefix.
GlobalID [5]byte
// SubnetID identifies an individual /64 subnet within a Prefix.
SubnetID uint16
// mask is the Prefix's length. It is set to either /48 or /64 when IPNet or
// Subnet is called, depending on whether the Prefix was created by Generate
// or manually by the caller.
mask net.IPMask
}
// IPNet produces a *net.IPNet prefix value from a Prefix.
func (p *Prefix) IPNet() *net.IPNet {
// Finalize the computation started by Generate:
//
// "6) Concatenate FC00::/7, the L bit set to 1, and the 40-bit Global
// ID to create a Local IPv6 address prefix."
ip := net.IP{0: 0xfc, 15: 0x00}
if p.Local {
ip[0] |= 0x01
}
copy(ip[1:6], p.GlobalID[:])
// Also set the subnet ID portion. If this Prefix was produced by Generate
// and no subnet ID and mask were previously assigned, we will produce a /48.
//
// However, if Prefix.Subnet was called, all subsequent calls will produce
// a /64 subnet within the parent /48 prefix.
binary.BigEndian.PutUint16(ip[6:8], p.SubnetID)
if p.mask == nil {
if p.SubnetID == 0 {
p.mask = net.CIDRMask(48, 128)
} else {
p.mask = net.CIDRMask(64, 128)
}
}
return &net.IPNet{
IP: ip,
Mask: p.mask,
}
}
// Subnet produces a /64 Prefix with the specified subnet ID.
//
// If p is a /48 Prefix, the new /64 Prefix will be a child of that parent
// /48 Prefix.
//
// If p is a /64 Prefix (typically produced through a previous call to Subnet),
// the new /64 Prefix will be a sibling /64 Prefix of the source /64 Prefix.
func (p *Prefix) Subnet(id uint16) *Prefix {
// Make a copy of p's parameters and produce a /64 prefix.
pp := *p
pp.SubnetID = id
pp.mask = net.CIDRMask(64, 128)
return &pp
}
// String returns the CIDR notation string for a Prefix.
func (p *Prefix) String() string { return p.IPNet().String() }
// Parse parses a /48 or /64 Prefix from a CIDR notation string. If s is not a
// /48 or /64 IPv6 Unique Local Address prefix, it returns an error.
func Parse(s string) (*Prefix, error) {
ip, cidr, err := net.ParseCIDR(s)
if err != nil {
return nil, err
}
// Only accept IPv6 ULA /48 or /64 prefixes.
if ip.To16() == nil || ip.To4() != nil {
return nil, fmt.Errorf("rfc4193: invalid IPv6 address: %s", s)
}
ones, _ := cidr.Mask.Size()
if !cidr.IP.Equal(ip) || !ula.Contains(ip) || (ones != 48 && ones != 64) {
return nil, fmt.Errorf("rfc4193: must specify a Unique Local Address /48 or /64 IPv6 prefix: %s", s)
}
p := Prefix{
Local: ip[0]&0x01 == 1,
SubnetID: binary.BigEndian.Uint16(ip[6:8]),
mask: net.CIDRMask(ones, 128),
}
copy(p.GlobalID[:], ip[1:6])
return &p, nil
}
// Generate produces a /48 Prefix by using mac (typically the MAC address of a
// network interface) as a seed. It uses the algorithm specified in RFC 4193,
// section 3.2.2.
//
// mac must either be nil or a 6-byte EUI-48 format MAC address. If mac is nil,
// cryptographically-secure random bytes will be used as a seed.
func Generate(mac net.HardwareAddr) (*Prefix, error) {
// Generate a Prefix using real timestamps and crypto/rand.Reader.
return (&generator{
now: time.Now,
cr: rand.Reader,
}).generate(mac)
}
// A generator backs the logic for Generate. Its fields can be modified to
// generate deterministic output for tests.
type generator struct {
now func() time.Time
cr io.Reader
}
// generate generates a Prefix using the configured generator and seed.
func (g *generator) generate(seed net.HardwareAddr) (*Prefix, error) {
// Store a timestamp and 8-byte value for hash input.
in := make([]byte, 16)
// "1) Obtain the current time of day in 64-bit NTP format [NTP]."
binary.BigEndian.PutUint64(in[:8], uint64(g.now().UnixNano()))
// Produce an 8-byte value:
//
// "2) Obtain an EUI-64 identifier from the system running this
// algorithm. If an EUI-64 does not exist, one can be created from
// a 48-bit MAC address as specified in [ADDARCH]. If an EUI-64
// cannot be obtained or created, a suitably unique identifier,
// local to the node, should be used (e.g., system serial number)."
//
// And combine it with the timestamp:
//
// "3) Concatenate the time of day with the system-specific identifier
// in order to create a key."
switch {
case len(seed) == 6:
// EUI-48 input; produce an EUI-64 value as input.
// Reference: https://packetlife.net/blog/2008/aug/4/eui-64-ipv6/.
in[11] = 0xff
in[12] = 0xfe
copy(in[8:], seed[:3])
copy(in[13:], seed[3:])
in[8] ^= 0x02
case seed == nil:
// No seed; so we will use an io.Reader (usually crypto/rand.Reader) to
// produce the "suitably unique identifier".
if _, err := io.ReadFull(g.cr, in[8:]); err != nil {
return nil, err
}
default:
return nil, errors.New("rfc4193: expected an EUI-48 format MAC address or nil MAC address")
}
// Always produce a /48 with the local flag set, per the
p := &Prefix{
// Always set to true, per RFC 4193, section 3.2.2.
Local: true,
mask: net.CIDRMask(48, 128),
}
// "4) Compute an SHA-1 digest on the key as specified in [FIPS, SHA1];
// the resulting value is 160 bits.""
//
// "5) Use the least significant 40 bits as the Global ID."
out := sha1.Sum(in)
copy(p.GlobalID[:], out[15:])
return p, nil
}