summaryrefslogtreecommitdiffhomepage
path: root/dhcpv6/dhcpv6.go
blob: 45025ab8c19f33ec1dc8526e6c683fd417429529 (plain)
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
// Package dhcpv6 provides encoding and decoding of DHCPv6 messages and
// options.
package dhcpv6

import (
	"fmt"
	"net"

	"github.com/u-root/uio/uio"
)

type DHCPv6 interface {
	Type() MessageType
	ToBytes() []byte
	String() string
	Summary() string
	LongString(indent int) string
	IsRelay() bool

	// GetInnerMessage returns the innermost encapsulated DHCPv6 message.
	//
	// If it is already a message, it will be returned. If it is a relay
	// message, the encapsulated message will be recursively extracted.
	GetInnerMessage() (*Message, error)

	GetOption(code OptionCode) []Option
	GetOneOption(code OptionCode) Option
	AddOption(Option)
	UpdateOption(Option)
}

// Modifier defines the signature for functions that can modify DHCPv6
// structures. This is used to simplify packet manipulation
type Modifier func(d DHCPv6)

// MessageFromBytes parses a DHCPv6 message from a byte stream.
func MessageFromBytes(data []byte) (*Message, error) {
	buf := uio.NewBigEndianBuffer(data)
	messageType := MessageType(buf.Read8())

	if messageType == MessageTypeRelayForward || messageType == MessageTypeRelayReply {
		return nil, fmt.Errorf("wrong message type")
	}

	d := &Message{
		MessageType: messageType,
	}
	buf.ReadBytes(d.TransactionID[:])
	if buf.Error() != nil {
		return nil, fmt.Errorf("failed to parse DHCPv6 header: %w", buf.Error())
	}
	if err := d.Options.FromBytes(buf.Data()); err != nil {
		return nil, err
	}
	return d, nil
}

// RelayMessageFromBytes parses a relay message from a byte stream.
func RelayMessageFromBytes(data []byte) (*RelayMessage, error) {
	buf := uio.NewBigEndianBuffer(data)
	messageType := MessageType(buf.Read8())

	if messageType != MessageTypeRelayForward && messageType != MessageTypeRelayReply {
		return nil, fmt.Errorf("wrong message type")
	}

	d := &RelayMessage{
		MessageType: messageType,
		HopCount:    buf.Read8(),
	}
	d.LinkAddr = net.IP(buf.CopyN(net.IPv6len))
	d.PeerAddr = net.IP(buf.CopyN(net.IPv6len))

	if buf.Error() != nil {
		return nil, fmt.Errorf("Error parsing RelayMessage header: %v", buf.Error())
	}
	// TODO: fail if no OptRelayMessage is present.
	if err := d.Options.FromBytes(buf.Data()); err != nil {
		return nil, err
	}
	return d, nil
}

// FromBytes reads a DHCPv6 message from a byte stream.
func FromBytes(data []byte) (DHCPv6, error) {
	buf := uio.NewBigEndianBuffer(data)
	messageType := MessageType(buf.Read8())
	if buf.Error() != nil {
		return nil, buf.Error()
	}

	if messageType == MessageTypeRelayForward || messageType == MessageTypeRelayReply {
		return RelayMessageFromBytes(data)
	} else {
		return MessageFromBytes(data)
	}
}

// NewMessage creates a new DHCPv6 message with default options
func NewMessage(modifiers ...Modifier) (*Message, error) {
	tid, err := GenerateTransactionID()
	if err != nil {
		return nil, err
	}
	msg := &Message{
		MessageType:   MessageTypeSolicit,
		TransactionID: tid,
	}
	// apply modifiers
	for _, mod := range modifiers {
		mod(msg)
	}
	return msg, nil
}

// DecapsulateRelay extracts the content of a relay message. It does not recurse
// if there are nested relay messages. Returns the original packet if is not not
// a relay message
func DecapsulateRelay(l DHCPv6) (DHCPv6, error) {
	if !l.IsRelay() {
		return l, nil
	}
	if rm := l.(*RelayMessage).Options.RelayMessage(); rm != nil {
		return rm, nil
	}
	return nil, fmt.Errorf("malformed Relay message: no embedded message found")
}

// DecapsulateRelayIndex extracts the content of a relay message. It takes an
// integer as index (e.g. if 0 return the outermost relay, 1 returns the
// second, etc, and -1 returns the last). Returns the original packet if
// it is not not a relay message.
func DecapsulateRelayIndex(l DHCPv6, index int) (DHCPv6, error) {
	if !l.IsRelay() {
		return l, nil
	}
	if index < -1 {
		return nil, fmt.Errorf("Invalid index: %d", index)
	} else if index == -1 {
		for {
			d, err := DecapsulateRelay(l)
			if err != nil {
				return nil, err
			}
			if !d.IsRelay() {
				return l, nil
			}
			l = d
		}
	}
	for i := 0; i <= index; i++ {
		d, err := DecapsulateRelay(l)
		if err != nil {
			return nil, err
		}
		l = d
	}
	return l, nil
}

// EncapsulateRelay creates a RelayMessage message containing the passed DHCPv6
// message as payload. The passed message type must be  either RELAY_FORW or
// RELAY_REPL
func EncapsulateRelay(d DHCPv6, mType MessageType, linkAddr, peerAddr net.IP) (*RelayMessage, error) {
	if mType != MessageTypeRelayForward && mType != MessageTypeRelayReply {
		return nil, fmt.Errorf("Message type must be either RELAY_FORW or RELAY_REPL")
	}
	outer := RelayMessage{
		MessageType: mType,
		LinkAddr:    linkAddr,
		PeerAddr:    peerAddr,
	}
	if d.IsRelay() {
		relay := d.(*RelayMessage)
		outer.HopCount = relay.HopCount + 1
	} else {
		outer.HopCount = 0
	}
	outer.AddOption(OptRelayMessage(d))
	return &outer, nil
}

// GetTransactionID returns a transactionID of a message or its inner message
// in case of relay
func GetTransactionID(packet DHCPv6) (TransactionID, error) {
	m, err := packet.GetInnerMessage()
	if err != nil {
		return TransactionID{0, 0, 0}, err
	}
	return m.TransactionID, nil
}