package dhcpv4
import (
"github.com/insomniacslk/dhcp/interfaces"
)
// BindToInterface (deprecated) redirects to interfaces.BindToInterface
func BindToInterface(fd int, ifname string) error {
return interfaces.BindToInterface(fd, ifname)
}
// Package dhcpv4 provides encoding and decoding of DHCPv4 packets and options.
//
// Example Usage:
//
// p, err := dhcpv4.New(
// dhcpv4.WithClientIP(net.IP{192, 168, 0, 1}),
// dhcpv4.WithMessageType(dhcpv4.MessageTypeInform),
// )
// p.UpdateOption(dhcpv4.OptServerIdentifier(net.IP{192, 110, 110, 110}))
//
// // Retrieve the DHCP Message Type option.
// m := p.MessageType()
//
// bytesOnTheWire := p.ToBytes()
// longSummary := p.Summary()
package dhcpv4
import (
"bytes"
"context"
"errors"
"fmt"
"net"
"strings"
"time"
"github.com/insomniacslk/dhcp/iana"
"github.com/insomniacslk/dhcp/rfc1035label"
"github.com/u-root/uio/rand"
"github.com/u-root/uio/uio"
)
const (
// minPacketLen is the minimum DHCP header length.
minPacketLen = 236
// MaxHWAddrLen is the maximum hardware address length of the ClientHWAddr
// (client hardware address) according to RFC 2131, Section 2. This is the
// link-layer destination a server must send responses to.
MaxHWAddrLen = 16
// MaxMessageSize is the maximum size in bytes that a DHCPv4 packet can hold.
MaxMessageSize = 576
// Per RFC 951, the minimum length of a packet is 300 bytes.
bootpMinLen = 300
)
// RandomTimeout is the amount of time to wait until random number generation
// is canceled.
var RandomTimeout = 2 * time.Minute
// magicCookie is the magic 4-byte value at the beginning of the list of options
// in a DHCPv4 packet.
var magicCookie = [4]byte{99, 130, 83, 99}
// DHCPv4 represents a DHCPv4 packet header and options. See the New* functions
// to build DHCPv4 packets.
type DHCPv4 struct {
OpCode OpcodeType
HWType iana.HWType
HopCount uint8
TransactionID TransactionID
NumSeconds uint16
Flags uint16
ClientIPAddr net.IP
YourIPAddr net.IP
ServerIPAddr net.IP
GatewayIPAddr net.IP
ClientHWAddr net.HardwareAddr
ServerHostName string
BootFileName string
Options Options
}
// Modifier defines the signature for functions that can modify DHCPv4
// structures. This is used to simplify packet manipulation
type Modifier func(d *DHCPv4)
// IPv4AddrsForInterface obtains the currently-configured, non-loopback IPv4
// addresses for iface.
func IPv4AddrsForInterface(iface *net.Interface) ([]net.IP, error) {
if iface == nil {
return nil, errors.New("IPv4AddrsForInterface: iface cannot be nil")
}
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}
return GetExternalIPv4Addrs(addrs)
}
// GetExternalIPv4Addrs obtains the currently-configured, non-loopback IPv4
// addresses from `addrs` coming from a particular interface (e.g.
// net.Interface.Addrs).
func GetExternalIPv4Addrs(addrs []net.Addr) ([]net.IP, error) {
var v4addrs []net.IP
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPAddr:
ip = v.IP
case *net.IPNet:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue
}
v4addrs = append(v4addrs, ip)
}
return v4addrs, nil
}
// GenerateTransactionID generates a random 32-bits number suitable for use as
// TransactionID.
func GenerateTransactionID() (TransactionID, error) {
return GenerateTransactionIDWithContext(context.Background())
}
// GenerateTransactionIDWithContext generates a random 32-bits number suitable
// for use as TransactionID.
func GenerateTransactionIDWithContext(ctx context.Context) (TransactionID, error) {
var xid TransactionID
ctx, cancel := context.WithTimeout(ctx, RandomTimeout)
defer cancel()
n, err := rand.ReadContext(ctx, xid[:])
if err != nil {
return xid, fmt.Errorf("could not get random number: %v", err)
}
if n != 4 {
return xid, errors.New("invalid random sequence for transaction ID: smaller than 32 bits")
}
return xid, err
}
// New creates a new DHCPv4 structure and fill it up with default values. It
// won't be a valid DHCPv4 message so you will need to adjust its fields. See
// also NewDiscovery, NewRequest, NewAcknowledge, NewInform and NewRelease.
func New(modifiers ...Modifier) (*DHCPv4, error) {
xid, err := GenerateTransactionID()
if err != nil {
return nil, err
}
return newDHCPv4(xid, modifiers...), nil
}
// NewWithContext creates a new DHCPv4 structure and fill it up with default
// values. It won't be a valid DHCPv4 message so you will need to adjust its
// fields. See also NewDiscovery, NewRequest, NewAcknowledge, NewInform and
// NewRelease.
func NewWithContext(ctx context.Context, modifiers ...Modifier) (*DHCPv4, error) {
xid, err := GenerateTransactionIDWithContext(ctx)
if err != nil {
return nil, err
}
return newDHCPv4(xid, modifiers...), nil
}
func newDHCPv4(xid TransactionID, modifiers ...Modifier) *DHCPv4 {
d := DHCPv4{
OpCode: OpcodeBootRequest,
HWType: iana.HWTypeEthernet,
ClientHWAddr: make(net.HardwareAddr, 6),
HopCount: 0,
TransactionID: xid,
NumSeconds: 0,
Flags: 0,
ClientIPAddr: net.IPv4zero,
YourIPAddr: net.IPv4zero,
ServerIPAddr: net.IPv4zero,
GatewayIPAddr: net.IPv4zero,
Options: make(Options),
}
for _, mod := range modifiers {
mod(&d)
}
return &d
}
// NewDiscoveryForInterface builds a new DHCPv4 Discovery message, with a default
// Ethernet HW type and the hardware address obtained from the specified
// interface.
func NewDiscoveryForInterface(ifname string, modifiers ...Modifier) (*DHCPv4, error) {
iface, err := net.InterfaceByName(ifname)
if err != nil {
return nil, err
}
return NewDiscovery(iface.HardwareAddr, modifiers...)
}
// NewDiscovery builds a new DHCPv4 Discovery message, with a default Ethernet
// HW type and specified hardware address.
func NewDiscovery(hwaddr net.HardwareAddr, modifiers ...Modifier) (*DHCPv4, error) {
return New(PrependModifiers(modifiers,
WithHwAddr(hwaddr),
WithRequestedOptions(
OptionSubnetMask,
OptionRouter,
OptionDomainName,
OptionDomainNameServer,
),
WithMessageType(MessageTypeDiscover),
)...)
}
// NewInformForInterface builds a new DHCPv4 Informational message with default
// Ethernet HW type and the hardware address obtained from the specified
// interface.
func NewInformForInterface(ifname string, needsBroadcast bool) (*DHCPv4, error) {
// get hw addr
iface, err := net.InterfaceByName(ifname)
if err != nil {
return nil, err
}
// Set Client IP as iface's currently-configured IP.
localIPs, err := IPv4AddrsForInterface(iface)
if err != nil || len(localIPs) == 0 {
return nil, fmt.Errorf("could not get local IPs for iface %s", ifname)
}
pkt, err := NewInform(iface.HardwareAddr, localIPs[0])
if err != nil {
return nil, err
}
if needsBroadcast {
pkt.SetBroadcast()
} else {
pkt.SetUnicast()
}
return pkt, nil
}
// PrependModifiers prepends other to m.
func PrependModifiers(m []Modifier, other ...Modifier) []Modifier {
return append(other, m...)
}
// NewInform builds a new DHCPv4 Informational message with the specified
// hardware address.
func NewInform(hwaddr net.HardwareAddr, localIP net.IP, modifiers ...Modifier) (*DHCPv4, error) {
return New(PrependModifiers(modifiers,
WithHwAddr(hwaddr),
WithMessageType(MessageTypeInform),
WithClientIP(localIP),
)...)
}
// NewRequestFromOffer builds a DHCPv4 request from an offer.
// It assumes the SELECTING state by default, see Section 4.3.2 in RFC 2131 for more details.
func NewRequestFromOffer(offer *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) {
return New(PrependModifiers(modifiers,
WithReply(offer),
WithMessageType(MessageTypeRequest),
WithClientIP(offer.ClientIPAddr),
WithOption(OptRequestedIPAddress(offer.YourIPAddr)),
// This is usually the server IP.
WithOptionCopied(offer, OptionServerIdentifier),
WithRequestedOptions(
OptionSubnetMask,
OptionRouter,
OptionDomainName,
OptionDomainNameServer,
),
)...)
}
// NewRenewFromAck builds a DHCPv4 RENEW-style request from the ACK of a lease. RENEW requests have
// minor changes to their options compared to SELECT requests as specified by RFC 2131, section 4.3.2.
func NewRenewFromAck(ack *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) {
return New(PrependModifiers(modifiers,
WithReply(ack),
WithMessageType(MessageTypeRequest),
// The client IP must be filled in with the IP offered to the client
WithClientIP(ack.YourIPAddr),
// The renewal request must use unicast
WithBroadcast(false),
WithRequestedOptions(
OptionSubnetMask,
OptionRouter,
OptionDomainName,
OptionDomainNameServer,
),
)...)
}
// NewReplyFromRequest builds a DHCPv4 reply from a request.
func NewReplyFromRequest(request *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) {
return New(PrependModifiers(modifiers,
WithReply(request),
WithGatewayIP(request.GatewayIPAddr),
WithOptionCopied(request, OptionRelayAgentInformation),
// RFC 6842 states the Client Identifier option must be copied
// from the request if a client specified it.
WithOptionCopied(request, OptionClientIdentifier),
)...)
}
// NewReleaseFromACK creates a DHCPv4 Release message from ACK.
// default Release message without any Modifer is created as following:
// - option Message Type is Release
// - ClientIP is set to ack.YourIPAddr
// - ClientHWAddr is set to ack.ClientHWAddr
// - Unicast
// - option Server Identifier is set to ack's ServerIdentifier
func NewReleaseFromACK(ack *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) {
return New(PrependModifiers(modifiers,
WithMessageType(MessageTypeRelease),
WithClientIP(ack.YourIPAddr),
WithHwAddr(ack.ClientHWAddr),
WithBroadcast(false),
WithOptionCopied(ack, OptionServerIdentifier),
)...)
}
// FromBytes decodes a DHCPv4 packet from a sequence of bytes, and returns an
// error if the packet is not valid.
func FromBytes(q []byte) (*DHCPv4, error) {
var p DHCPv4
buf := uio.NewBigEndianBuffer(q)
p.OpCode = OpcodeType(buf.Read8())
p.HWType = iana.HWType(buf.Read8())
hwAddrLen := buf.Read8()
p.HopCount = buf.Read8()
buf.ReadBytes(p.TransactionID[:])
p.NumSeconds = buf.Read16()
p.Flags = buf.Read16()
p.ClientIPAddr = net.IP(buf.CopyN(net.IPv4len))
p.YourIPAddr = net.IP(buf.CopyN(net.IPv4len))
p.ServerIPAddr = net.IP(buf.CopyN(net.IPv4len))
p.GatewayIPAddr = net.IP(buf.CopyN(net.IPv4len))
if hwAddrLen > 16 {
hwAddrLen = 16
}
// Always read 16 bytes, but only use hwaddrlen of them.
p.ClientHWAddr = make(net.HardwareAddr, 16)
buf.ReadBytes(p.ClientHWAddr)
p.ClientHWAddr = p.ClientHWAddr[:hwAddrLen]
var sname [64]byte
buf.ReadBytes(sname[:])
length := strings.Index(string(sname[:]), "\x00")
if length == -1 {
length = 64
}
p.ServerHostName = string(sname[:length])
var file [128]byte
buf.ReadBytes(file[:])
length = strings.Index(string(file[:]), "\x00")
if length == -1 {
length = 128
}
p.BootFileName = string(file[:length])
var cookie [4]byte
buf.ReadBytes(cookie[:])
if err := buf.Error(); err != nil {
return nil, err
}
if cookie != magicCookie {
return nil, fmt.Errorf("malformed DHCP packet: got magic cookie %v, want %v", cookie[:], magicCookie[:])
}
p.Options = make(Options)
if err := p.Options.fromBytesCheckEnd(buf.Data(), true); err != nil {
return nil, err
}
return &p, nil
}
// FlagsToString returns a human-readable representation of the flags field.
func (d *DHCPv4) FlagsToString() string {
flags := ""
if d.IsBroadcast() {
flags += "Broadcast"
} else {
flags += "Unicast"
}
if d.Flags&0xfe != 0 {
flags += " (reserved bits not zeroed)"
}
return flags
}
// IsBroadcast indicates whether the packet is a broadcast packet.
func (d *DHCPv4) IsBroadcast() bool {
return d.Flags&0x8000 == 0x8000
}
// SetBroadcast sets the packet to be a broadcast packet.
func (d *DHCPv4) SetBroadcast() {
d.Flags |= 0x8000
}
// IsUnicast indicates whether the packet is a unicast packet.
func (d *DHCPv4) IsUnicast() bool {
return d.Flags&0x8000 == 0
}
// SetUnicast sets the packet to be a unicast packet.
func (d *DHCPv4) SetUnicast() {
d.Flags &= ^uint16(0x8000)
}
// GetOneOption returns the option that matches the given option code.
//
// According to RFC 3396, options that are specified more than once are
// concatenated, and hence this should always just return one option.
func (d *DHCPv4) GetOneOption(code OptionCode) []byte {
return d.Options.Get(code)
}
// DeleteOption deletes an existing option with the given option code.
func (d *DHCPv4) DeleteOption(code OptionCode) {
if d.Options != nil {
d.Options.Del(code)
}
}
// UpdateOption replaces an existing option with the same option code with the
// given one, adding it if not already present.
func (d *DHCPv4) UpdateOption(opt Option) {
if d.Options == nil {
d.Options = make(Options)
}
d.Options.Update(opt)
}
// String implements fmt.Stringer.
func (d *DHCPv4) String() string {
return fmt.Sprintf("DHCPv4(xid=%s hwaddr=%s msg_type=%s, your_ip=%s, server_ip=%s)",
d.TransactionID, d.ClientHWAddr, d.MessageType(), d.YourIPAddr, d.ServerIPAddr)
}
// SummaryWithVendor prints a summary of the packet, interpreting the
// vendor-specific info option using the given parser (can be nil).
func (d *DHCPv4) SummaryWithVendor(vendorDecoder OptionDecoder) string {
ret := fmt.Sprintf(
"DHCPv4 Message\n"+
" opcode: %s\n"+
" hwtype: %s\n"+
" hopcount: %v\n"+
" transaction ID: %s\n"+
" num seconds: %v\n"+
" flags: %v (0x%02x)\n"+
" client IP: %s\n"+
" your IP: %s\n"+
" server IP: %s\n"+
" gateway IP: %s\n"+
" client MAC: %s\n"+
" server hostname: %s\n"+
" bootfile name: %s\n",
d.OpCode,
d.HWType,
d.HopCount,
d.TransactionID,
d.NumSeconds,
d.FlagsToString(),
d.Flags,
d.ClientIPAddr,
d.YourIPAddr,
d.ServerIPAddr,
d.GatewayIPAddr,
d.ClientHWAddr,
d.ServerHostName,
d.BootFileName,
)
ret += " options:\n"
ret += d.Options.Summary(vendorDecoder)
return ret
}
// Summary prints detailed information about the packet.
func (d *DHCPv4) Summary() string {
return d.SummaryWithVendor(nil)
}
// IsOptionRequested returns true if that option is within the requested
// options of the DHCPv4 message.
func (d *DHCPv4) IsOptionRequested(requested OptionCode) bool {
rq := d.ParameterRequestList()
if rq == nil {
// RFC2131ยง3.5
// Not all clients require initialization of all parameters [...]
// Two techniques are used to reduce the number of parameters transmitted from
// the server to the client. [...] Second, in its initial DHCPDISCOVER or
// DHCPREQUEST message, a client may provide the server with a list of specific
// parameters the client is interested in.
// We interpret this to say that all available parameters should be sent if
// the parameter request list is not sent at all.
return true
}
for _, o := range rq {
if o.Code() == requested.Code() {
return true
}
}
return false
}
// In case somebody forgets to set an IP, just write 0s as default values.
func writeIP(b *uio.Lexer, ip net.IP) {
var zeros [net.IPv4len]byte
if ip == nil {
b.WriteBytes(zeros[:])
} else {
// Converting IP to 4 byte format
ip = ip.To4()
b.WriteBytes(ip[:net.IPv4len])
}
}
// ToBytes writes the packet to binary.
func (d *DHCPv4) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(make([]byte, 0, minPacketLen))
buf.Write8(uint8(d.OpCode))
buf.Write8(uint8(d.HWType))
// HwAddrLen
hlen := uint8(len(d.ClientHWAddr))
buf.Write8(hlen)
buf.Write8(d.HopCount)
buf.WriteBytes(d.TransactionID[:])
buf.Write16(d.NumSeconds)
buf.Write16(d.Flags)
writeIP(buf, d.ClientIPAddr)
writeIP(buf, d.YourIPAddr)
writeIP(buf, d.ServerIPAddr)
writeIP(buf, d.GatewayIPAddr)
copy(buf.WriteN(16), d.ClientHWAddr)
var sname [64]byte
copy(sname[:63], []byte(d.ServerHostName))
buf.WriteBytes(sname[:])
var file [128]byte
copy(file[:127], []byte(d.BootFileName))
buf.WriteBytes(file[:])
// The magic cookie.
buf.WriteBytes(magicCookie[:])
// Write all options.
d.Options.Marshal(buf)
// Finish the options.
buf.Write8(OptionEnd.Code())
// DHCP is based on BOOTP, and BOOTP messages have a minimum length of
// 300 bytes per RFC 951. This not stated explicitly, but if you sum up
// all the bytes in the message layout, you'll get 300 bytes.
//
// Some DHCP servers and relay agents care about this BOOTP legacy B.S.
// and "conveniently" drop messages that are less than 300 bytes long.
if buf.Len() < bootpMinLen {
buf.WriteBytes(bytes.Repeat([]byte{OptionPad.Code()}, bootpMinLen-buf.Len()))
}
return buf.Data()
}
// GetBroadcastAddress returns the DHCPv4 Broadcast Address value in d.
//
// The broadcast address option is described in RFC 2132, Section 5.3.
func (d *DHCPv4) BroadcastAddress() net.IP {
return GetIP(OptionBroadcastAddress, d.Options)
}
// RequestedIPAddress returns the DHCPv4 Requested IP Address value in d.
//
// The requested IP address option is described by RFC 2132, Section 9.1.
func (d *DHCPv4) RequestedIPAddress() net.IP {
return GetIP(OptionRequestedIPAddress, d.Options)
}
// ServerIdentifier returns the DHCPv4 Server Identifier value in d.
//
// The server identifier option is described by RFC 2132, Section 9.7.
func (d *DHCPv4) ServerIdentifier() net.IP {
return GetIP(OptionServerIdentifier, d.Options)
}
// Router parses the DHCPv4 Router option if present.
//
// The Router option is described by RFC 2132, Section 3.5.
func (d *DHCPv4) Router() []net.IP {
return GetIPs(OptionRouter, d.Options)
}
// ClasslessStaticRoute parses the DHCPv4 Classless Static Route option if present.
//
// The Classless Static Route option is described by RFC 3442.
func (d *DHCPv4) ClasslessStaticRoute() []*Route {
v := d.Options.Get(OptionClasslessStaticRoute)
if v == nil {
return nil
}
var routes Routes
if err := routes.FromBytes(v); err != nil {
return nil
}
return routes
}
// NTPServers parses the DHCPv4 NTP Servers option if present.
//
// The NTP servers option is described by RFC 2132, Section 8.3.
func (d *DHCPv4) NTPServers() []net.IP {
return GetIPs(OptionNTPServers, d.Options)
}
// NetBIOSNameServers parses the DHCPv4 NetBIOS Name Servers option if present.
//
// The NetBIOS over TCP/IP Name Server option is described by RFC 2132, Section 8.5.
func (d *DHCPv4) NetBIOSNameServers() []net.IP {
return GetIPs(OptionNetBIOSOverTCPIPNameServer, d.Options)
}
// DNS parses the DHCPv4 Domain Name Server option if present.
//
// The DNS server option is described by RFC 2132, Section 3.8.
func (d *DHCPv4) DNS() []net.IP {
return GetIPs(OptionDomainNameServer, d.Options)
}
// DomainName parses the DHCPv4 Domain Name option if present.
//
// The Domain Name option is described by RFC 2132, Section 3.17.
func (d *DHCPv4) DomainName() string {
return GetString(OptionDomainName, d.Options)
}
// HostName parses the DHCPv4 Host Name option if present.
//
// The Host Name option is described by RFC 2132, Section 3.14.
func (d *DHCPv4) HostName() string {
name := GetString(OptionHostName, d.Options)
return strings.TrimRight(name, "\x00")
}
// RootPath parses the DHCPv4 Root Path option if present.
//
// The Root Path option is described by RFC 2132, Section 3.19.
func (d *DHCPv4) RootPath() string {
return GetString(OptionRootPath, d.Options)
}
// BootFileNameOption parses the DHCPv4 Bootfile Name option if present.
//
// The Bootfile Name option is described by RFC 2132, Section 9.5.
func (d *DHCPv4) BootFileNameOption() string {
name := GetString(OptionBootfileName, d.Options)
return strings.TrimRight(name, "\x00")
}
// TFTPServerName parses the DHCPv4 TFTP Server Name option if present.
//
// The TFTP Server Name option is described by RFC 2132, Section 9.4.
func (d *DHCPv4) TFTPServerName() string {
name := GetString(OptionTFTPServerName, d.Options)
return strings.TrimRight(name, "\x00")
}
// ClassIdentifier parses the DHCPv4 Class Identifier option if present.
//
// The Vendor Class Identifier option is described by RFC 2132, Section 9.13.
func (d *DHCPv4) ClassIdentifier() string {
return GetString(OptionClassIdentifier, d.Options)
}
// ClientArch returns the Client System Architecture Type option.
func (d *DHCPv4) ClientArch() []iana.Arch {
v := d.Options.Get(OptionClientSystemArchitectureType)
if v == nil {
return nil
}
var archs iana.Archs
if err := archs.FromBytes(v); err != nil {
return nil
}
return archs
}
// DomainSearch returns the domain search list if present.
//
// The domain search option is described by RFC 3397, Section 2.
func (d *DHCPv4) DomainSearch() *rfc1035label.Labels {
v := d.Options.Get(OptionDNSDomainSearchList)
if v == nil {
return nil
}
labels, err := rfc1035label.FromBytes(v)
if err != nil {
return nil
}
return labels
}
// IPAddressLeaseTime returns the IP address lease time or the given
// default duration if not present.
//
// The IP address lease time option is described by RFC 2132, Section 9.2.
func (d *DHCPv4) IPAddressLeaseTime(def time.Duration) time.Duration {
v := d.Options.Get(OptionIPAddressLeaseTime)
if v == nil {
return def
}
var dur Duration
if err := dur.FromBytes(v); err != nil {
return def
}
return time.Duration(dur)
}
// IPAddressRenewalTime returns the IP address renewal time or the given
// default duration if not present.
//
// The IP address renewal time option is described by RFC 2132, Section 9.11.
func (d *DHCPv4) IPAddressRenewalTime(def time.Duration) time.Duration {
v := d.Options.Get(OptionRenewTimeValue)
if v == nil {
return def
}
var dur Duration
if err := dur.FromBytes(v); err != nil {
return def
}
return time.Duration(dur)
}
// IPAddressRebindingTime returns the IP address rebinding time or the given
// default duration if not present.
//
// The IP address rebinding time option is described by RFC 2132, Section 9.12.
func (d *DHCPv4) IPAddressRebindingTime(def time.Duration) time.Duration {
v := d.Options.Get(OptionRebindingTimeValue)
if v == nil {
return def
}
var dur Duration
if err := dur.FromBytes(v); err != nil {
return def
}
return time.Duration(dur)
}
// IPv6OnlyPreferred returns the V6ONLY_WAIT duration, and a boolean
// indicating whether this option was present.
//
// The IPv6-Only Preferred option is described by RFC 8925, Section 3.1.
func (d *DHCPv4) IPv6OnlyPreferred() (time.Duration, bool) {
v := d.Options.Get(OptionIPv6OnlyPreferred)
if v == nil {
return 0, false
}
var dur Duration
if err := dur.FromBytes(v); err != nil {
return 0, false
}
return time.Duration(dur), true
}
// MaxMessageSize returns the DHCP Maximum Message Size if present.
//
// The Maximum DHCP Message Size option is described by RFC 2132, Section 9.10.
func (d *DHCPv4) MaxMessageSize() (uint16, error) {
return GetUint16(OptionMaximumDHCPMessageSize, d.Options)
}
// AutoConfigure returns the value of the AutoConfigure option, and a
// boolean indicating if it was present.
//
// The AutoConfigure option is described by RFC 2563, Section 2.
func (d *DHCPv4) AutoConfigure() (AutoConfiguration, bool) {
v, err := GetByte(OptionAutoConfigure, d.Options)
return AutoConfiguration(v), err == nil
}
// MessageType returns the DHCPv4 Message Type option.
func (d *DHCPv4) MessageType() MessageType {
v := d.Options.Get(OptionDHCPMessageType)
if v == nil {
return MessageTypeNone
}
var m MessageType
if err := m.FromBytes(v); err != nil {
return MessageTypeNone
}
return m
}
// Message returns the DHCPv4 (Error) Message option.
//
// The message options is described in RFC 2132, Section 9.9.
func (d *DHCPv4) Message() string {
return GetString(OptionMessage, d.Options)
}
// ParameterRequestList returns the DHCPv4 Parameter Request List.
//
// The parameter request list option is described by RFC 2132, Section 9.8.
func (d *DHCPv4) ParameterRequestList() OptionCodeList {
v := d.Options.Get(OptionParameterRequestList)
if v == nil {
return nil
}
var codes OptionCodeList
if err := codes.FromBytes(v); err != nil {
return nil
}
return codes
}
// RelayAgentInfo returns options embedded by the relay agent.
//
// The relay agent info option is described by RFC 3046.
func (d *DHCPv4) RelayAgentInfo() *RelayOptions {
v := d.Options.Get(OptionRelayAgentInformation)
if v == nil {
return nil
}
var relayOptions RelayOptions
if err := relayOptions.FromBytes(v); err != nil {
return nil
}
return &relayOptions
}
// SubnetMask returns a subnet mask option contained if present.
//
// The subnet mask option is described by RFC 2132, Section 3.3.
func (d *DHCPv4) SubnetMask() net.IPMask {
v := d.Options.Get(OptionSubnetMask)
if v == nil {
return nil
}
var im IPMask
if err := im.FromBytes(v); err != nil {
return nil
}
return net.IPMask(im)
}
// UserClass returns the user class if present.
//
// The user class information option is defined by RFC 3004.
func (d *DHCPv4) UserClass() []string {
v := d.Options.Get(OptionUserClassInformation)
if v == nil {
return nil
}
var uc Strings
if err := uc.FromBytes(v); err != nil {
return []string{GetString(OptionUserClassInformation, d.Options)}
}
return uc
}
// VIVC returns the vendor-identifying vendor class option if present.
func (d *DHCPv4) VIVC() VIVCIdentifiers {
v := d.Options.Get(OptionVendorIdentifyingVendorClass)
if v == nil {
return nil
}
var ids VIVCIdentifiers
if err := ids.FromBytes(v); err != nil {
return nil
}
return ids
}
package dhcpv4
import (
"net"
"time"
"github.com/insomniacslk/dhcp/iana"
"github.com/insomniacslk/dhcp/rfc1035label"
)
// WithTransactionID sets the Transaction ID for the DHCPv4 packet
func WithTransactionID(xid TransactionID) Modifier {
return func(d *DHCPv4) {
d.TransactionID = xid
}
}
// WithClientIP sets the Client IP for a DHCPv4 packet.
func WithClientIP(ip net.IP) Modifier {
return func(d *DHCPv4) {
d.ClientIPAddr = ip
}
}
// WithYourIP sets the Your IP for a DHCPv4 packet.
func WithYourIP(ip net.IP) Modifier {
return func(d *DHCPv4) {
d.YourIPAddr = ip
}
}
// WithServerIP sets the Server IP for a DHCPv4 packet.
func WithServerIP(ip net.IP) Modifier {
return func(d *DHCPv4) {
d.ServerIPAddr = ip
}
}
// WithGatewayIP sets the Gateway IP for the DHCPv4 packet.
func WithGatewayIP(ip net.IP) Modifier {
return func(d *DHCPv4) {
d.GatewayIPAddr = ip
}
}
// WithOptionCopied copies the value of option opt from request.
func WithOptionCopied(request *DHCPv4, opt OptionCode) Modifier {
return func(d *DHCPv4) {
if val := request.Options.Get(opt); val != nil {
d.UpdateOption(OptGeneric(opt, val))
}
}
}
// WithReply fills in opcode, hwtype, xid, clienthwaddr, and flags from the given packet.
func WithReply(request *DHCPv4) Modifier {
return func(d *DHCPv4) {
if request.OpCode == OpcodeBootRequest {
d.OpCode = OpcodeBootReply
} else {
d.OpCode = OpcodeBootRequest
}
d.HWType = request.HWType
d.TransactionID = request.TransactionID
d.ClientHWAddr = request.ClientHWAddr
d.Flags = request.Flags
}
}
// WithHWType sets the Hardware Type for a DHCPv4 packet.
func WithHWType(hwt iana.HWType) Modifier {
return func(d *DHCPv4) {
d.HWType = hwt
}
}
// WithBroadcast sets the packet to be broadcast or unicast
func WithBroadcast(broadcast bool) Modifier {
return func(d *DHCPv4) {
if broadcast {
d.SetBroadcast()
} else {
d.SetUnicast()
}
}
}
// WithHwAddr sets the hardware address for a packet
func WithHwAddr(hwaddr net.HardwareAddr) Modifier {
return func(d *DHCPv4) {
d.ClientHWAddr = hwaddr
}
}
// WithOption appends a DHCPv4 option provided by the user
func WithOption(opt Option) Modifier {
return func(d *DHCPv4) {
d.UpdateOption(opt)
}
}
// WithoutOption removes the DHCPv4 option with the given code
func WithoutOption(code OptionCode) Modifier {
return func(d *DHCPv4) {
d.DeleteOption(code)
}
}
// WithUserClass adds a user class option to the packet.
// The rfc parameter allows you to specify if the userclass should be
// rfc compliant or not. More details in issue #113
func WithUserClass(uc string, rfc bool) Modifier {
// TODO let the user specify multiple user classes
return func(d *DHCPv4) {
if rfc {
d.UpdateOption(OptRFC3004UserClass([]string{uc}))
} else {
d.UpdateOption(OptUserClass(uc))
}
}
}
// WithNetboot adds bootfile URL and bootfile param options to a DHCPv4 packet.
func WithNetboot(d *DHCPv4) {
WithRequestedOptions(OptionTFTPServerName, OptionBootfileName)(d)
}
// WithMessageType adds the DHCPv4 message type m to a packet.
func WithMessageType(m MessageType) Modifier {
return WithOption(OptMessageType(m))
}
// WithRequestedOptions adds requested options to the packet.
func WithRequestedOptions(optionCodes ...OptionCode) Modifier {
return func(d *DHCPv4) {
cl := d.ParameterRequestList()
cl.Add(optionCodes...)
d.UpdateOption(OptParameterRequestList(cl...))
}
}
// WithRelay adds parameters required for DHCPv4 to be relayed by the relay
// server with given ip
func WithRelay(ip net.IP) Modifier {
return func(d *DHCPv4) {
d.SetUnicast()
d.GatewayIPAddr = ip
d.HopCount++
}
}
// WithNetmask adds or updates an OptSubnetMask
func WithNetmask(mask net.IPMask) Modifier {
return WithOption(OptSubnetMask(mask))
}
// WithLeaseTime adds or updates an OptIPAddressLeaseTime
func WithLeaseTime(leaseTime uint32) Modifier {
return WithOption(OptIPAddressLeaseTime(time.Duration(leaseTime) * time.Second))
}
// WithIPv6OnlyPreferred adds or updates an OptIPv6OnlyPreferred
func WithIPv6OnlyPreferred(v6OnlyWait uint32) Modifier {
return WithOption(OptIPv6OnlyPreferred(time.Duration(v6OnlyWait) * time.Second))
}
// WithDomainSearchList adds or updates an OptionDomainSearch
func WithDomainSearchList(searchList ...string) Modifier {
return WithOption(OptDomainSearch(&rfc1035label.Labels{
Labels: searchList,
}))
}
func WithGeneric(code OptionCode, value []byte) Modifier {
return WithOption(OptGeneric(code, value))
}
package dhcpv4
import (
"fmt"
)
// AutoConfiguration implements encoding and decoding functions for a
// byte enumeration as used in RFC 2563, Section 2.
type AutoConfiguration byte
const (
DoNotAutoConfigure AutoConfiguration = 0
AutoConfigure AutoConfiguration = 1
)
var autoConfigureToString = map[AutoConfiguration]string{
DoNotAutoConfigure: "DoNotAutoConfigure",
AutoConfigure: "AutoConfigure",
}
// ToBytes returns a serialized stream of bytes for this option.
func (o AutoConfiguration) ToBytes() []byte {
return []byte{byte(o)}
}
// String returns a human-readable string for this option.
func (o AutoConfiguration) String() string {
s := autoConfigureToString[o]
if s != "" {
return s
}
return fmt.Sprintf("UNKNOWN (%d)", byte(o))
}
// FromBytes parses a a single byte into AutoConfiguration
func (o *AutoConfiguration) FromBytes(data []byte) error {
if len(data) == 1 {
*o = AutoConfiguration(data[0])
return nil
}
return fmt.Errorf("Invalid buffer length (%d)", len(data))
}
// GetByte parses any single-byte option
func GetByte(code OptionCode, o Options) (byte, error) {
data := o.Get(code)
if data == nil {
return 0, fmt.Errorf("option not present")
}
if len(data) != 1 {
return 0, fmt.Errorf("Invalid buffer length (%d)", len(data))
}
return data[0], nil
}
// OptAutoConfigure returns a new AutoConfigure option.
//
// The AutoConfigure option is described by RFC 2563, Section 2.
func OptAutoConfigure(autoconf AutoConfiguration) Option {
return Option{Code: OptionAutoConfigure, Value: autoconf}
}
package dhcpv4
import (
"math"
"time"
"github.com/u-root/uio/uio"
)
// MaxLeaseTime is the maximum lease time that can be encoded.
var MaxLeaseTime = math.MaxUint32 * time.Second
// Duration implements the IP address lease time option described by RFC 2132,
// Section 9.2.
type Duration time.Duration
// FromBytes parses a duration from a byte stream according to RFC 2132, Section 9.2.
func (d *Duration) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
*d = Duration(time.Duration(buf.Read32()) * time.Second)
return buf.FinError()
}
// ToBytes returns a serialized stream of bytes for this option.
func (d Duration) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write32(uint32(time.Duration(d) / time.Second))
return buf.Data()
}
// String returns a human-readable string for this option.
func (d Duration) String() string {
return time.Duration(d).String()
}
// OptIPAddressLeaseTime returns a new IP address lease time option.
//
// The IP address lease time option is described by RFC 2132, Section 9.2.
func OptIPAddressLeaseTime(d time.Duration) Option {
return Option{Code: OptionIPAddressLeaseTime, Value: Duration(d)}
}
// The IP address renew time option as described by RFC 2132, Section 9.11.
func OptRenewTimeValue(d time.Duration) Option {
return Option{Code: OptionRenewTimeValue, Value: Duration(d)}
}
// The IP address rebinding time option as described by RFC 2132, Section 9.12.
func OptRebindingTimeValue(d time.Duration) Option {
return Option{Code: OptionRebindingTimeValue, Value: Duration(d)}
}
// The IPv6-Only Preferred option is described by RFC 8925, Section 3.1
func OptIPv6OnlyPreferred(d time.Duration) Option {
return Option{Code: OptionIPv6OnlyPreferred, Value: Duration(d)}
}
package dhcpv4
import (
"fmt"
)
// OptionGeneric is an option that only contains the option code and associated
// data. Every option that does not have a specific implementation will fall
// back to this option.
type OptionGeneric struct {
Data []byte
}
// ToBytes returns a serialized generic option as a slice of bytes.
func (o OptionGeneric) ToBytes() []byte {
return o.Data
}
// String returns a human-readable representation of a generic option.
func (o OptionGeneric) String() string {
return fmt.Sprintf("%v", o.Data)
}
// OptGeneric returns a generic option.
func OptGeneric(code OptionCode, value []byte) Option {
return Option{Code: code, Value: OptionGeneric{value}}
}
package dhcpv4
import (
"net"
"github.com/u-root/uio/uio"
)
// IP implements DHCPv4 IP option marshaling and unmarshaling as described by
// RFC 2132, Sections 5.3, 9.1, 9.7, and others.
type IP net.IP
// FromBytes parses an IP from data in binary form.
func (i *IP) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
*i = IP(buf.CopyN(net.IPv4len))
return buf.FinError()
}
// ToBytes returns a serialized stream of bytes for this option.
func (i IP) ToBytes() []byte {
return []byte(net.IP(i).To4())
}
// String returns a human-readable IP.
func (i IP) String() string {
return net.IP(i).String()
}
// GetIP returns code out of o parsed as an IP.
func GetIP(code OptionCode, o Options) net.IP {
v := o.Get(code)
if v == nil {
return nil
}
var ip IP
if err := ip.FromBytes(v); err != nil {
return nil
}
return net.IP(ip)
}
// OptBroadcastAddress returns a new DHCPv4 Broadcast Address option.
//
// The broadcast address option is described in RFC 2132, Section 5.3.
func OptBroadcastAddress(ip net.IP) Option {
return Option{Code: OptionBroadcastAddress, Value: IP(ip)}
}
// OptRequestedIPAddress returns a new DHCPv4 Requested IP Address option.
//
// The requested IP address option is described by RFC 2132, Section 9.1.
func OptRequestedIPAddress(ip net.IP) Option {
return Option{Code: OptionRequestedIPAddress, Value: IP(ip)}
}
// OptServerIdentifier returns a new DHCPv4 Server Identifier option.
//
// The server identifier option is described by RFC 2132, Section 9.7.
func OptServerIdentifier(ip net.IP) Option {
return Option{Code: OptionServerIdentifier, Value: IP(ip)}
}
package dhcpv4
import (
"fmt"
"net"
"strings"
"github.com/u-root/uio/uio"
)
// IPs are IPv4 addresses from a DHCP packet as used and specified by options
// in RFC 2132, Sections 3.5 through 3.13, 8.2, 8.3, 8.5, 8.6, 8.9, and 8.10.
//
// IPs implements the OptionValue type.
type IPs []net.IP
// FromBytes parses an IPv4 address from a DHCP packet as used and specified by
// options in RFC 2132, Sections 3.5 through 3.13, 8.2, 8.3, 8.5, 8.6, 8.9, and
// 8.10.
func (i *IPs) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
if buf.Len() == 0 {
return fmt.Errorf("IP DHCP options must always list at least one IP")
}
*i = make(IPs, 0, buf.Len()/net.IPv4len)
for buf.Has(net.IPv4len) {
*i = append(*i, net.IP(buf.CopyN(net.IPv4len)))
}
return buf.FinError()
}
// ToBytes marshals IPv4 addresses to a DHCP packet as specified by RFC 2132,
// Section 3.5 et al.
func (i IPs) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, ip := range i {
buf.WriteBytes(ip.To4())
}
return buf.Data()
}
// String returns a human-readable representation of a list of IPs.
func (i IPs) String() string {
s := make([]string, 0, len(i))
for _, ip := range i {
s = append(s, ip.String())
}
return strings.Join(s, ", ")
}
// GetIPs parses a list of IPs from code in o.
func GetIPs(code OptionCode, o Options) []net.IP {
v := o.Get(code)
if v == nil {
return nil
}
var ips IPs
if err := ips.FromBytes(v); err != nil {
return nil
}
return []net.IP(ips)
}
// OptRouter returns a new DHCPv4 Router option.
//
// The Router option is described by RFC 2132, Section 3.5.
func OptRouter(routers ...net.IP) Option {
return Option{
Code: OptionRouter,
Value: IPs(routers),
}
}
// WithRouter updates a packet with the DHCPv4 Router option.
func WithRouter(routers ...net.IP) Modifier {
return WithOption(OptRouter(routers...))
}
// OptNTPServers returns a new DHCPv4 NTP Server option.
//
// The NTP servers option is described by RFC 2132, Section 8.3.
func OptNTPServers(ntpServers ...net.IP) Option {
return Option{
Code: OptionNTPServers,
Value: IPs(ntpServers),
}
}
// OptNetBIOSNameServers returns a new DHCPv4 NetBIOS Name Server option.
//
// The NetBIOS over TCP/IP Name Server option is described by RFC 2132, Section 8.5.
func OptNetBIOSNameServers(netBIOSNameServers ...net.IP) Option {
return Option{
Code: OptionNetBIOSOverTCPIPNameServer,
Value: IPs(netBIOSNameServers),
}
}
// OptDNS returns a new DHCPv4 Domain Name Server option.
//
// The DNS server option is described by RFC 2132, Section 3.8.
func OptDNS(servers ...net.IP) Option {
return Option{
Code: OptionDomainNameServer,
Value: IPs(servers),
}
}
// WithDNS modifies a packet with the DHCPv4 Domain Name Server option.
func WithDNS(servers ...net.IP) Modifier {
return WithOption(OptDNS(servers...))
}
package dhcpv4
import (
"fmt"
"github.com/u-root/uio/uio"
)
// Uint16 implements encoding and decoding functions for a uint16 as used in
// RFC 2132, Section 9.10.
type Uint16 uint16
// ToBytes returns a serialized stream of bytes for this option.
func (o Uint16) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write16(uint16(o))
return buf.Data()
}
// String returns a human-readable string for this option.
func (o Uint16) String() string {
return fmt.Sprintf("%d", uint16(o))
}
// FromBytes decodes data into o as per RFC 2132, Section 9.10.
func (o *Uint16) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
*o = Uint16(buf.Read16())
return buf.FinError()
}
// GetUint16 parses a uint16 from code in o.
func GetUint16(code OptionCode, o Options) (uint16, error) {
v := o.Get(code)
if v == nil {
return 0, fmt.Errorf("option not present")
}
var u Uint16
if err := u.FromBytes(v); err != nil {
return 0, err
}
return uint16(u), nil
}
// OptMaxMessageSize returns a new DHCP Maximum Message Size option.
//
// The Maximum DHCP Message Size option is described by RFC 2132, Section 9.10.
func OptMaxMessageSize(size uint16) Option {
return Option{Code: OptionMaximumDHCPMessageSize, Value: Uint16(size)}
}
package dhcpv4
// OptMessageType returns a new DHCPv4 Message Type option.
func OptMessageType(m MessageType) Option {
return Option{Code: OptionDHCPMessageType, Value: m}
}
package dhcpv4
import (
"github.com/insomniacslk/dhcp/iana"
"github.com/insomniacslk/dhcp/rfc1035label"
)
// OptDomainSearch returns a new domain search option.
//
// The domain search option is described by RFC 3397, Section 2.
func OptDomainSearch(labels *rfc1035label.Labels) Option {
return Option{Code: OptionDNSDomainSearchList, Value: labels}
}
// OptClientArch returns a new Client System Architecture Type option.
func OptClientArch(archs ...iana.Arch) Option {
return Option{Code: OptionClientSystemArchitectureType, Value: iana.Archs(archs)}
}
// OptClientIdentifier returns a new Client Identifier option.
func OptClientIdentifier(ident []byte) Option {
return OptGeneric(OptionClientIdentifier, ident)
}
package dhcpv4
import (
"sort"
"strings"
"github.com/u-root/uio/uio"
)
// OptionCodeList is a list of DHCP option codes.
type OptionCodeList []OptionCode
// Has returns whether c is in the list.
func (ol OptionCodeList) Has(c OptionCode) bool {
for _, code := range ol {
if code == c {
return true
}
}
return false
}
// Add adds option codes in cs to ol.
func (ol *OptionCodeList) Add(cs ...OptionCode) {
for _, c := range cs {
if !ol.Has(c) {
*ol = append(*ol, c)
}
}
}
func (ol OptionCodeList) sort() {
sort.Slice(ol, func(i, j int) bool { return ol[i].Code() < ol[j].Code() })
}
// String returns a human-readable string for the option names.
func (ol OptionCodeList) String() string {
var names []string
ol.sort()
for _, code := range ol {
names = append(names, code.String())
}
return strings.Join(names, ", ")
}
// ToBytes returns a serialized stream of bytes for this option as defined by
// RFC 2132, Section 9.8.
func (ol OptionCodeList) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, req := range ol {
buf.Write8(req.Code())
}
return buf.Data()
}
// FromBytes parses a byte stream for this option as described by RFC 2132,
// Section 9.8.
func (ol *OptionCodeList) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
*ol = make(OptionCodeList, 0, buf.Len())
for buf.Has(1) {
*ol = append(*ol, optionCode(buf.Read8()))
}
return buf.FinError()
}
// OptParameterRequestList returns a new DHCPv4 Parameter Request List.
//
// The parameter request list option is described by RFC 2132, Section 9.8.
func OptParameterRequestList(codes ...OptionCode) Option {
return Option{Code: OptionParameterRequestList, Value: OptionCodeList(codes)}
}
package dhcpv4
import (
"fmt"
)
// RelayOptions is like Options, but stringifies using the Relay Agent Specific
// option space.
type RelayOptions struct {
Options
}
var relayHumanizer = OptionHumanizer{
ValueHumanizer: func(code OptionCode, data []byte) fmt.Stringer {
var d OptionDecoder
switch code {
case LinkSelectionSubOption, ServerIdentifierOverrideSubOption:
d = &IPs{}
}
if d != nil && d.FromBytes(data) == nil {
return d
}
return raiSubOptionValue{data}
},
CodeHumanizer: func(c uint8) OptionCode {
return raiSubOptionCode(c)
},
}
// String prints the contained options using Relay Agent-specific option code parsing.
func (r RelayOptions) String() string {
return "\n" + r.Options.ToString(relayHumanizer)
}
// FromBytes parses relay agent options from data.
func (r *RelayOptions) FromBytes(data []byte) error {
r.Options = make(Options)
return r.Options.FromBytes(data)
}
// OptRelayAgentInfo returns a new DHCP Relay Agent Info option.
//
// The relay agent info option is described by RFC 3046.
func OptRelayAgentInfo(o ...Option) Option {
return Option{Code: OptionRelayAgentInformation, Value: RelayOptions{OptionsFromList(o...)}}
}
type raiSubOptionValue struct {
val []byte
}
func (rv raiSubOptionValue) String() string {
return fmt.Sprintf("%q (%v)", string(rv.val), rv.val)
}
type raiSubOptionCode uint8
func (o raiSubOptionCode) Code() uint8 {
return uint8(o)
}
func (o raiSubOptionCode) String() string {
if s, ok := raiSubOptionCodeToString[o]; ok {
return s
}
return fmt.Sprintf("unknown (%d)", o)
}
// Option 82 Relay Agention Information Sub Options
const (
AgentCircuitIDSubOption raiSubOptionCode = 1 // RFC 3046
AgentRemoteIDSubOption raiSubOptionCode = 2 // RFC 3046
DOCSISDeviceClassSubOption raiSubOptionCode = 4 // RFC 3256
LinkSelectionSubOption raiSubOptionCode = 5 // RFC 3527
SubscriberIDSubOption raiSubOptionCode = 6 // RFC 3993
RADIUSAttributesSubOption raiSubOptionCode = 7 // RFC 4014
AuthenticationSubOption raiSubOptionCode = 8 // RFC 4030
VendorSpecificInformationSubOption raiSubOptionCode = 9 // RFC 4243
RelayAgentFlagsSubOption raiSubOptionCode = 10 // RFC 5010
ServerIdentifierOverrideSubOption raiSubOptionCode = 11 // RFC 5107
RelaySourcePortSubOption raiSubOptionCode = 19 // RFC 8357
VirtualSubnetSelectionSubOption raiSubOptionCode = 151 // RFC 6607
VirtualSubnetSelectionControlSubOption raiSubOptionCode = 152 // RFC 6607
)
var raiSubOptionCodeToString = map[raiSubOptionCode]string{
AgentCircuitIDSubOption: "Agent Circuit ID Sub-option",
AgentRemoteIDSubOption: "Agent Remote ID Sub-option",
DOCSISDeviceClassSubOption: "DOCSIS Device Class Sub-option",
LinkSelectionSubOption: "Link Selection Sub-option",
SubscriberIDSubOption: "Subscriber ID Sub-option",
RADIUSAttributesSubOption: "RADIUS Attributes Sub-option",
AuthenticationSubOption: "Authentication Sub-option",
VendorSpecificInformationSubOption: "Vendor Specific Sub-option",
RelayAgentFlagsSubOption: "Relay Agent Flags Sub-option",
ServerIdentifierOverrideSubOption: "Server Identifier Override Sub-option",
RelaySourcePortSubOption: "Relay Source Port Sub-option",
VirtualSubnetSelectionSubOption: "Virtual Subnet Selection Sub-option",
VirtualSubnetSelectionControlSubOption: "Virtual Subnet Selection Control Sub-option",
}
package dhcpv4
import (
"fmt"
"net"
"strings"
"github.com/u-root/uio/uio"
)
// Route is a classless static route as per RFC 3442.
type Route struct {
// Dest is the destination network.
Dest *net.IPNet
// Router is the router to use for the given destination network.
Router net.IP
}
// Marshal implements uio.Marshaler.
//
// Format described in RFC 3442:
//
// <size of mask in number of bits>
// <destination address, omitting octets that must be zero per mask>
// <route IP>
func (r Route) Marshal(buf *uio.Lexer) {
ones, _ := r.Dest.Mask.Size()
buf.Write8(uint8(ones))
// Only write the non-zero octets.
dstLen := (ones + 7) / 8
buf.WriteBytes(r.Dest.IP.To4()[:dstLen])
buf.WriteBytes(r.Router.To4())
}
// Unmarshal implements uio.Unmarshaler.
func (r *Route) Unmarshal(buf *uio.Lexer) error {
maskSize := buf.Read8()
if maskSize > 32 {
return fmt.Errorf("invalid mask length %d in route option", maskSize)
}
r.Dest = &net.IPNet{
IP: make([]byte, net.IPv4len),
Mask: net.CIDRMask(int(maskSize), 32),
}
dstLen := (maskSize + 7) / 8
buf.ReadBytes(r.Dest.IP[:dstLen])
r.Router = buf.CopyN(net.IPv4len)
return buf.Error()
}
// String prints the destination network and router IP.
func (r *Route) String() string {
return fmt.Sprintf("route to %s via %s", r.Dest, r.Router)
}
// Routes is a collection of network routes.
type Routes []*Route
// FromBytes parses routes from a set of bytes as described by RFC 3442.
func (r *Routes) FromBytes(p []byte) error {
buf := uio.NewBigEndianBuffer(p)
for buf.Has(1) {
var route Route
if err := route.Unmarshal(buf); err != nil {
return err
}
*r = append(*r, &route)
}
return buf.FinError()
}
// ToBytes marshals a set of routes as described by RFC 3442.
func (r Routes) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, route := range r {
route.Marshal(buf)
}
return buf.Data()
}
// String prints all routes.
func (r Routes) String() string {
s := make([]string, 0, len(r))
for _, route := range r {
s = append(s, route.String())
}
return strings.Join(s, "; ")
}
// OptClasslessStaticRoute returns a new DHCPv4 Classless Static Route
// option.
//
// The Classless Static Route option is described by RFC 3442.
func OptClasslessStaticRoute(routes ...*Route) Option {
return Option{
Code: OptionClasslessStaticRoute,
Value: Routes(routes),
}
}
package dhcpv4
// String represents an option encapsulating a string in IPv4 DHCP.
//
// This representation is shared by multiple options specified by RFC 2132,
// Sections 3.14, 3.16, 3.17, 3.19, and 3.20.
type String string
// ToBytes returns a serialized stream of bytes for this option.
func (o String) ToBytes() []byte {
return []byte(o)
}
// String returns a human-readable string.
func (o String) String() string {
return string(o)
}
// FromBytes parses a serialized stream of bytes into o.
func (o *String) FromBytes(data []byte) error {
*o = String(string(data))
return nil
}
// GetString parses an RFC 2132 string from o[code].
func GetString(code OptionCode, o Options) string {
v := o.Get(code)
if v == nil {
return ""
}
return string(v)
}
// OptDomainName returns a new DHCPv4 Domain Name option.
//
// The Domain Name option is described by RFC 2132, Section 3.17.
func OptDomainName(name string) Option {
return Option{Code: OptionDomainName, Value: String(name)}
}
// OptHostName returns a new DHCPv4 Host Name option.
//
// The Host Name option is described by RFC 2132, Section 3.14.
func OptHostName(name string) Option {
return Option{Code: OptionHostName, Value: String(name)}
}
// OptRootPath returns a new DHCPv4 Root Path option.
//
// The Root Path option is described by RFC 2132, Section 3.19.
func OptRootPath(name string) Option {
return Option{Code: OptionRootPath, Value: String(name)}
}
// OptBootFileName returns a new DHCPv4 Boot File Name option.
//
// The Bootfile Name option is described by RFC 2132, Section 9.5.
func OptBootFileName(name string) Option {
return Option{Code: OptionBootfileName, Value: String(name)}
}
// OptTFTPServerName returns a new DHCPv4 TFTP Server Name option.
//
// The TFTP Server Name option is described by RFC 2132, Section 9.4.
func OptTFTPServerName(name string) Option {
return Option{Code: OptionTFTPServerName, Value: String(name)}
}
// OptClassIdentifier returns a new DHCPv4 Class Identifier option.
//
// The Vendor Class Identifier option is described by RFC 2132, Section 9.13.
func OptClassIdentifier(name string) Option {
return Option{Code: OptionClassIdentifier, Value: String(name)}
}
// OptUserClass returns a new DHCPv4 User Class option.
func OptUserClass(name string) Option {
return Option{Code: OptionUserClassInformation, Value: String(name)}
}
// OptMessage returns a new DHCPv4 (Error) Message option.
func OptMessage(msg string) Option {
return Option{Code: OptionMessage, Value: String(msg)}
}
package dhcpv4
import (
"fmt"
"strings"
"github.com/u-root/uio/uio"
)
// Strings represents an option encapsulating a list of strings in IPv4 DHCP as
// specified in RFC 3004
//
// Strings implements the OptionValue type.
type Strings []string
// FromBytes parses Strings from a DHCP packet as specified by RFC 3004.
func (o *Strings) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
if buf.Len() == 0 {
return fmt.Errorf("Strings DHCP option must always list at least one String")
}
*o = make(Strings, 0)
for buf.Has(1) {
ucLen := buf.Read8()
if ucLen == 0 {
return fmt.Errorf("DHCP Strings must have length greater than 0")
}
*o = append(*o, string(buf.CopyN(int(ucLen))))
}
return buf.FinError()
}
// ToBytes marshals Strings to a DHCP packet as specified by RFC 3004.
func (o Strings) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, uc := range o {
buf.Write8(uint8(len(uc)))
buf.WriteBytes([]byte(uc))
}
return buf.Data()
}
// String returns a human-readable representation of a list of Strings.
func (o Strings) String() string {
return strings.Join(o, ", ")
}
// OptRFC3004UserClass returns a new user class option according to RFC 3004.
func OptRFC3004UserClass(v []string) Option {
return Option{
Code: OptionUserClassInformation,
Value: Strings(v),
}
}
package dhcpv4
import (
"net"
"github.com/u-root/uio/uio"
)
// IPMask represents an option encapsulating the subnet mask.
//
// This option implements the subnet mask option in RFC 2132, Section 3.3.
type IPMask net.IPMask
// ToBytes returns a serialized stream of bytes for this option.
func (im IPMask) ToBytes() []byte {
if len(im) > net.IPv4len {
return im[:net.IPv4len]
}
return im
}
// String returns a human-readable string.
func (im IPMask) String() string {
return net.IPMask(im).String()
}
// FromBytes parses im from data per RFC 2132.
func (im *IPMask) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
*im = IPMask(buf.CopyN(net.IPv4len))
return buf.FinError()
}
// OptSubnetMask returns a new DHCPv4 SubnetMask option per RFC 2132, Section 3.3.
func OptSubnetMask(mask net.IPMask) Option {
return Option{
Code: OptionSubnetMask,
Value: IPMask(mask),
}
}
package dhcpv4
import (
"bytes"
"fmt"
"github.com/insomniacslk/dhcp/iana"
"github.com/u-root/uio/uio"
)
// VIVCIdentifier implements the vendor-identifying vendor class option
// described by RFC 3925.
type VIVCIdentifier struct {
// EntID is the enterprise ID.
EntID iana.EnterpriseID
Data []byte
}
// OptVIVC returns a new vendor-identifying vendor class option.
//
// The option is described by RFC 3925.
func OptVIVC(identifiers ...VIVCIdentifier) Option {
return Option{
Code: OptionVendorIdentifyingVendorClass,
Value: VIVCIdentifiers(identifiers),
}
}
// VIVCIdentifiers implements encoding and decoding methods for a DHCP option
// described in RFC 3925.
type VIVCIdentifiers []VIVCIdentifier
// FromBytes parses data into ids per RFC 3925.
func (ids *VIVCIdentifiers) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
for buf.Has(5) {
entID := iana.EnterpriseID(buf.Read32())
idLen := int(buf.Read8())
*ids = append(*ids, VIVCIdentifier{EntID: entID, Data: buf.CopyN(idLen)})
}
return buf.FinError()
}
// ToBytes returns a serialized stream of bytes for this option.
func (ids VIVCIdentifiers) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, id := range ids {
buf.Write32(uint32(id.EntID))
buf.Write8(uint8(len(id.Data)))
buf.WriteBytes(id.Data)
}
return buf.Data()
}
// String returns a human-readable string for this option.
func (ids VIVCIdentifiers) String() string {
if len(ids) == 0 {
return ""
}
buf := bytes.Buffer{}
for _, id := range ids {
fmt.Fprintf(&buf, " %d:'%s',", id.EntID, id.Data)
}
return buf.String()[1 : buf.Len()-1]
}
package dhcpv4
import (
"errors"
"fmt"
"io"
"math"
"sort"
"strings"
"github.com/insomniacslk/dhcp/iana"
"github.com/insomniacslk/dhcp/rfc1035label"
"github.com/u-root/uio/uio"
)
var (
// ErrShortByteStream is an error that is thrown any time a short byte stream is
// detected during option parsing.
ErrShortByteStream = errors.New("short byte stream")
// ErrZeroLengthByteStream is an error that is thrown any time a zero-length
// byte stream is encountered.
ErrZeroLengthByteStream = errors.New("zero-length byte stream")
)
// OptionValue is an interface that all DHCP v4 options adhere to.
type OptionValue interface {
ToBytes() []byte
String() string
}
// Option is a DHCPv4 option and consists of a 1-byte option code and a value
// stream of bytes.
//
// The value is to be interpreted based on the option code.
type Option struct {
Code OptionCode
Value OptionValue
}
// String returns a human-readable version of this option.
func (o Option) String() string {
v := o.Value.String()
if strings.Contains(v, "\n") {
return fmt.Sprintf("%s:\n%s", o.Code, v)
}
return fmt.Sprintf("%s: %s", o.Code, v)
}
// Options is a collection of options.
type Options map[uint8][]byte
// OptionsFromList adds all given options to an options map.
func OptionsFromList(o ...Option) Options {
opts := make(Options)
for _, opt := range o {
opts.Update(opt)
}
return opts
}
// Get will attempt to get all options that match a DHCPv4 option
// from its OptionCode. If the option was not found it will return an
// empty list.
//
// According to RFC 3396, options that are specified more than once are
// concatenated, and hence this should always just return one option. This
// currently returns a list to be API compatible.
func (o Options) Get(code OptionCode) []byte {
return o[code.Code()]
}
// Has checks whether o has the given opcode.
func (o Options) Has(opcode OptionCode) bool {
_, ok := o[opcode.Code()]
return ok
}
// Del deletes the option matching the option code.
func (o Options) Del(opcode OptionCode) {
delete(o, opcode.Code())
}
// Update updates the existing options with the passed option, adding it
// at the end if not present already
func (o Options) Update(option Option) {
o[option.Code.Code()] = option.Value.ToBytes()
}
// ToBytes makes Options usable as an OptionValue as well.
//
// Used in the case of vendor-specific and relay agent options.
func (o Options) ToBytes() []byte {
return uio.ToBigEndian(o)
}
// FromBytes parses a sequence of bytes until the end and builds a list of
// options from it.
//
// The sequence should not contain the DHCP magic cookie.
//
// Returns an error if any invalid option or length is found.
func (o Options) FromBytes(data []byte) error {
return o.fromBytesCheckEnd(data, false)
}
const (
optPad = 0
optAgentInfo = 82
optEnd = 255
)
// FromBytesCheckEnd parses Options from byte sequences using the
// parsing function that is passed in as a paremeter
func (o Options) fromBytesCheckEnd(data []byte, checkEndOption bool) error {
if len(data) == 0 {
return nil
}
buf := uio.NewBigEndianBuffer(data)
var end bool
for buf.Len() >= 1 {
// 1 byte: option code
// 1 byte: option length n
// n bytes: data
code := buf.Read8()
if code == optPad {
continue
} else if code == optEnd {
end = true
break
}
length := int(buf.Read8())
// N bytes: option data
data := buf.Consume(length)
if data == nil {
return fmt.Errorf("error collecting options: %v", buf.Error())
}
data = data[:length:length]
// RFC 2131, Section 4.1 "Options may appear only once, [...].
// The client concatenates the values of multiple instances of
// the same option into a single parameter list for
// configuration."
//
// See also RFC 3396 for concatenation order and options longer
// than 255 bytes.
o[code] = append(o[code], data...)
}
// If we never read the End option, the sender of this packet screwed
// up.
if !end && checkEndOption {
return io.ErrUnexpectedEOF
}
return nil
}
// sortedKeys returns an ordered slice of option keys from the Options map, for
// use in serializing options to binary.
func (o Options) sortedKeys() []int {
// Send all values for a given key
var codes []int
var hasOptAgentInfo, hasOptEnd bool
for k := range o {
// RFC 3046 section 2.1 states that option 82 SHALL come last (ignoring End).
if k == optAgentInfo {
hasOptAgentInfo = true
continue
}
if k == optEnd {
hasOptEnd = true
continue
}
codes = append(codes, int(k))
}
sort.Ints(codes)
if hasOptAgentInfo {
codes = append(codes, optAgentInfo)
}
if hasOptEnd {
codes = append(codes, optEnd)
}
return codes
}
// Marshal writes options binary representations to b.
func (o Options) Marshal(b *uio.Lexer) {
for _, c := range o.sortedKeys() {
code := uint8(c)
// Even if the End option is in there, don't marshal it until
// the end.
// Don't write padding either, since the options are sorted
// it would always be written first which isn't useful
if code == optEnd || code == optPad {
continue
}
data := o[code]
// Ensure even 0-length options are written out
if len(data) == 0 {
b.Write8(code)
b.Write8(0)
continue
}
// RFC 3396: If more than 256 bytes of data are given, the
// option is simply listed multiple times.
for len(data) > 0 {
// 1 byte: option code
b.Write8(code)
n := len(data)
if n > math.MaxUint8 {
n = math.MaxUint8
}
// 1 byte: option length
b.Write8(uint8(n))
// N bytes: option data
b.WriteBytes(data[:n])
data = data[n:]
}
}
}
// String prints options using DHCP-specified option codes.
func (o Options) String() string {
return o.ToString(dhcpHumanizer)
}
// Summary prints options in human-readable values.
//
// Summary uses vendorParser to interpret the OptionVendorSpecificInformation option.
func (o Options) Summary(vendorDecoder OptionDecoder) string {
return o.ToString(OptionHumanizer{
ValueHumanizer: parserFor(vendorDecoder),
CodeHumanizer: func(c uint8) OptionCode {
return optionCode(c)
},
})
}
// OptionParser gives a human-legible interpretation of data for the given option code.
type OptionParser func(code OptionCode, data []byte) fmt.Stringer
// OptionHumanizer is used to interpret a set of Options for their option code
// name and values.
//
// There should be separate OptionHumanizers for each Option "space": DHCP,
// BSDP, Relay Agent Info, and others.
type OptionHumanizer struct {
ValueHumanizer OptionParser
CodeHumanizer func(code uint8) OptionCode
}
// Stringify returns a human-readable interpretation of the option code and its
// associated data.
func (oh OptionHumanizer) Stringify(code uint8, data []byte) string {
c := oh.CodeHumanizer(code)
val := oh.ValueHumanizer(c, data)
return fmt.Sprintf("%s: %s", c, val)
}
// dhcpHumanizer humanizes the set of DHCP option codes.
var dhcpHumanizer = OptionHumanizer{
ValueHumanizer: parseOption,
CodeHumanizer: func(c uint8) OptionCode {
return optionCode(c)
},
}
// ToString uses parse to parse options into human-readable values.
func (o Options) ToString(humanizer OptionHumanizer) string {
var ret string
for _, c := range o.sortedKeys() {
code := uint8(c)
v := o[code]
optString := humanizer.Stringify(code, v)
// If this option has sub structures, offset them accordingly.
if strings.Contains(optString, "\n") {
optString = strings.Replace(optString, "\n ", "\n ", -1)
}
ret += fmt.Sprintf(" %v\n", optString)
}
return ret
}
func parseOption(code OptionCode, data []byte) fmt.Stringer {
return parserFor(nil)(code, data)
}
func parserFor(vendorParser OptionDecoder) OptionParser {
return func(code OptionCode, data []byte) fmt.Stringer {
return getOption(code, data, vendorParser)
}
}
// OptionDecoder can decode a byte stream into a human-readable option.
type OptionDecoder interface {
fmt.Stringer
FromBytes([]byte) error
}
func getOption(code OptionCode, data []byte, vendorDecoder OptionDecoder) fmt.Stringer {
var d OptionDecoder
switch code {
case OptionRouter, OptionDomainNameServer, OptionNTPServers, OptionServerIdentifier:
d = &IPs{}
case OptionBroadcastAddress, OptionRequestedIPAddress:
d = &IP{}
case OptionClientSystemArchitectureType:
d = &iana.Archs{}
case OptionSubnetMask:
d = &IPMask{}
case OptionDHCPMessageType:
var mt MessageType
d = &mt
case OptionParameterRequestList:
d = &OptionCodeList{}
case OptionHostName, OptionDomainName, OptionRootPath,
OptionClassIdentifier, OptionTFTPServerName, OptionBootfileName,
OptionMessage, OptionReferenceToTZDatabase:
var s String
d = &s
case OptionRelayAgentInformation:
d = &RelayOptions{}
case OptionDNSDomainSearchList:
d = &rfc1035label.Labels{}
case OptionIPAddressLeaseTime, OptionRenewTimeValue,
OptionRebindingTimeValue, OptionIPv6OnlyPreferred, OptionArpCacheTimeout,
OptionTimeOffset:
var dur Duration
d = &dur
case OptionMaximumDHCPMessageSize:
var u Uint16
d = &u
case OptionUserClassInformation:
var s Strings
d = &s
if s.FromBytes(data) != nil {
var s String
d = &s
}
case OptionAutoConfigure:
var a AutoConfiguration
d = &a
case OptionVendorIdentifyingVendorClass:
d = &VIVCIdentifiers{}
case OptionVendorSpecificInformation:
d = vendorDecoder
case OptionClasslessStaticRoute:
d = &Routes{}
}
if d != nil && d.FromBytes(data) == nil {
return d
}
return OptionGeneric{data}
}
package dhcpv4
import (
"fmt"
"github.com/u-root/uio/uio"
)
// values from http://www.networksorcery.com/enp/protocol/dhcp.htm and
// http://www.networksorcery.com/enp/protocol/bootp/options.htm
// TransactionID represents a 4-byte DHCP transaction ID as defined in RFC 951,
// Section 3.
//
// The TransactionID is used to match DHCP replies to their original request.
type TransactionID [4]byte
// String prints a hex transaction ID.
func (xid TransactionID) String() string {
return fmt.Sprintf("0x%x", xid[:])
}
// MessageType represents the possible DHCP message types - DISCOVER, OFFER, etc
type MessageType byte
// DHCP message types
const (
// MessageTypeNone is not a real message type, it is used by certain
// functions to signal that no explicit message type is requested
MessageTypeNone MessageType = 0
MessageTypeDiscover MessageType = 1
MessageTypeOffer MessageType = 2
MessageTypeRequest MessageType = 3
MessageTypeDecline MessageType = 4
MessageTypeAck MessageType = 5
MessageTypeNak MessageType = 6
MessageTypeRelease MessageType = 7
MessageTypeInform MessageType = 8
)
// ToBytes returns the serialized version of this option described by RFC 2132,
// Section 9.6.
func (m MessageType) ToBytes() []byte {
return []byte{byte(m)}
}
// String prints a human-readable message type name.
func (m MessageType) String() string {
if s, ok := messageTypeToString[m]; ok {
return s
}
return fmt.Sprintf("unknown (%d)", byte(m))
}
// FromBytes reads a message type from data as described by RFC 2132, Section
// 9.6.
func (m *MessageType) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
*m = MessageType(buf.Read8())
return buf.FinError()
}
var messageTypeToString = map[MessageType]string{
MessageTypeDiscover: "DISCOVER",
MessageTypeOffer: "OFFER",
MessageTypeRequest: "REQUEST",
MessageTypeDecline: "DECLINE",
MessageTypeAck: "ACK",
MessageTypeNak: "NAK",
MessageTypeRelease: "RELEASE",
MessageTypeInform: "INFORM",
}
// OpcodeType represents a DHCPv4 opcode.
type OpcodeType uint8
// constants that represent valid values for OpcodeType
const (
OpcodeBootRequest OpcodeType = 1
OpcodeBootReply OpcodeType = 2
)
func (o OpcodeType) String() string {
if s, ok := opcodeToString[o]; ok {
return s
}
return fmt.Sprintf("unknown (%d)", uint8(o))
}
var opcodeToString = map[OpcodeType]string{
OpcodeBootRequest: "BootRequest",
OpcodeBootReply: "BootReply",
}
// OptionCode is a single byte representing the code for a given Option.
//
// OptionCode is an interface purely to support different stringers on options
// with the same Code value, as vendor-specific options use option codes that
// have the same value, but mean a different thing.
type OptionCode interface {
// Code is the 1 byte option code for the wire.
Code() uint8
// String returns the option's name.
String() string
}
// optionCode is a DHCP option code.
type optionCode uint8
// Code implements OptionCode.Code.
func (o optionCode) Code() uint8 {
return uint8(o)
}
// String returns an option name.
func (o optionCode) String() string {
if s, ok := optionCodeToString[o]; ok {
return s
}
return fmt.Sprintf("unknown (%d)", uint8(o))
}
// GenericOptionCode is an unnamed option code.
type GenericOptionCode uint8
// Code implements OptionCode.Code.
func (o GenericOptionCode) Code() uint8 {
return uint8(o)
}
// String returns the option's name.
func (o GenericOptionCode) String() string {
return fmt.Sprintf("unknown (%d)", uint8(o))
}
// DHCPv4 Options
const (
OptionPad optionCode = 0
OptionSubnetMask optionCode = 1
OptionTimeOffset optionCode = 2
OptionRouter optionCode = 3
OptionTimeServer optionCode = 4
OptionNameServer optionCode = 5
OptionDomainNameServer optionCode = 6
OptionLogServer optionCode = 7
OptionQuoteServer optionCode = 8
OptionLPRServer optionCode = 9
OptionImpressServer optionCode = 10
OptionResourceLocationServer optionCode = 11
OptionHostName optionCode = 12
OptionBootFileSize optionCode = 13
OptionMeritDumpFile optionCode = 14
OptionDomainName optionCode = 15
OptionSwapServer optionCode = 16
OptionRootPath optionCode = 17
OptionExtensionsPath optionCode = 18
OptionIPForwarding optionCode = 19
OptionNonLocalSourceRouting optionCode = 20
OptionPolicyFilter optionCode = 21
OptionMaximumDatagramAssemblySize optionCode = 22
OptionDefaultIPTTL optionCode = 23
OptionPathMTUAgingTimeout optionCode = 24
OptionPathMTUPlateauTable optionCode = 25
OptionInterfaceMTU optionCode = 26
OptionAllSubnetsAreLocal optionCode = 27
OptionBroadcastAddress optionCode = 28
OptionPerformMaskDiscovery optionCode = 29
OptionMaskSupplier optionCode = 30
OptionPerformRouterDiscovery optionCode = 31
OptionRouterSolicitationAddress optionCode = 32
OptionStaticRoutingTable optionCode = 33
OptionTrailerEncapsulation optionCode = 34
OptionArpCacheTimeout optionCode = 35
OptionEthernetEncapsulation optionCode = 36
OptionDefaulTCPTTL optionCode = 37
OptionTCPKeepaliveInterval optionCode = 38
OptionTCPKeepaliveGarbage optionCode = 39
OptionNetworkInformationServiceDomain optionCode = 40
OptionNetworkInformationServers optionCode = 41
OptionNTPServers optionCode = 42
OptionVendorSpecificInformation optionCode = 43
OptionNetBIOSOverTCPIPNameServer optionCode = 44
OptionNetBIOSOverTCPIPDatagramDistributionServer optionCode = 45
OptionNetBIOSOverTCPIPNodeType optionCode = 46
OptionNetBIOSOverTCPIPScope optionCode = 47
OptionXWindowSystemFontServer optionCode = 48
OptionXWindowSystemDisplayManger optionCode = 49
OptionRequestedIPAddress optionCode = 50
OptionIPAddressLeaseTime optionCode = 51
OptionOptionOverload optionCode = 52
OptionDHCPMessageType optionCode = 53
OptionServerIdentifier optionCode = 54
OptionParameterRequestList optionCode = 55
OptionMessage optionCode = 56
OptionMaximumDHCPMessageSize optionCode = 57
OptionRenewTimeValue optionCode = 58
OptionRebindingTimeValue optionCode = 59
OptionClassIdentifier optionCode = 60
OptionClientIdentifier optionCode = 61
OptionNetWareIPDomainName optionCode = 62
OptionNetWareIPInformation optionCode = 63
OptionNetworkInformationServicePlusDomain optionCode = 64
OptionNetworkInformationServicePlusServers optionCode = 65
OptionTFTPServerName optionCode = 66
OptionBootfileName optionCode = 67
OptionMobileIPHomeAgent optionCode = 68
OptionSimpleMailTransportProtocolServer optionCode = 69
OptionPostOfficeProtocolServer optionCode = 70
OptionNetworkNewsTransportProtocolServer optionCode = 71
OptionDefaultWorldWideWebServer optionCode = 72
OptionDefaultFingerServer optionCode = 73
OptionDefaultInternetRelayChatServer optionCode = 74
OptionStreetTalkServer optionCode = 75
OptionStreetTalkDirectoryAssistanceServer optionCode = 76
OptionUserClassInformation optionCode = 77
OptionSLPDirectoryAgent optionCode = 78
OptionSLPServiceScope optionCode = 79
OptionRapidCommit optionCode = 80
OptionFQDN optionCode = 81
OptionRelayAgentInformation optionCode = 82
OptionInternetStorageNameService optionCode = 83
// Option 84 returned in RFC 3679
OptionNDSServers optionCode = 85
OptionNDSTreeName optionCode = 86
OptionNDSContext optionCode = 87
OptionBCMCSControllerDomainNameList optionCode = 88
OptionBCMCSControllerIPv4AddressList optionCode = 89
OptionAuthentication optionCode = 90
OptionClientLastTransactionTime optionCode = 91
OptionAssociatedIP optionCode = 92
OptionClientSystemArchitectureType optionCode = 93
OptionClientNetworkInterfaceIdentifier optionCode = 94
OptionLDAP optionCode = 95
// Option 96 returned in RFC 3679
OptionClientMachineIdentifier optionCode = 97
OptionOpenGroupUserAuthentication optionCode = 98
OptionGeoConfCivic optionCode = 99
OptionIEEE10031TZString optionCode = 100
OptionReferenceToTZDatabase optionCode = 101
// Option 108 returned in RFC 8925
OptionIPv6OnlyPreferred optionCode = 108
// Options 102-111 returned in RFC 3679
OptionNetInfoParentServerAddress optionCode = 112
OptionNetInfoParentServerTag optionCode = 113
OptionURL optionCode = 114
// Option 115 returned in RFC 3679
OptionAutoConfigure optionCode = 116
OptionNameServiceSearch optionCode = 117
OptionSubnetSelection optionCode = 118
OptionDNSDomainSearchList optionCode = 119
OptionSIPServers optionCode = 120
OptionClasslessStaticRoute optionCode = 121
OptionCCC optionCode = 122
OptionGeoConf optionCode = 123
OptionVendorIdentifyingVendorClass optionCode = 124
OptionVendorIdentifyingVendorSpecific optionCode = 125
// Options 126-127 returned in RFC 3679
OptionTFTPServerIPAddress optionCode = 128
OptionCallServerIPAddress optionCode = 129
OptionDiscriminationString optionCode = 130
OptionRemoteStatisticsServerIPAddress optionCode = 131
Option8021PVLANID optionCode = 132
Option8021QL2Priority optionCode = 133
OptionDiffservCodePoint optionCode = 134
OptionHTTPProxyForPhoneSpecificApplications optionCode = 135
OptionPANAAuthenticationAgent optionCode = 136
OptionLoSTServer optionCode = 137
OptionCAPWAPAccessControllerAddresses optionCode = 138
OptionOPTIONIPv4AddressMoS optionCode = 139
OptionOPTIONIPv4FQDNMoS optionCode = 140
OptionSIPUAConfigurationServiceDomains optionCode = 141
OptionOPTIONIPv4AddressANDSF optionCode = 142
OptionOPTIONIPv6AddressANDSF optionCode = 143
// Options 144-149 returned in RFC 3679
OptionTFTPServerAddress optionCode = 150
OptionStatusCode optionCode = 151
OptionBaseTime optionCode = 152
OptionStartTimeOfState optionCode = 153
OptionQueryStartTime optionCode = 154
OptionQueryEndTime optionCode = 155
OptionDHCPState optionCode = 156
OptionDataSource optionCode = 157
// Options 158-174 returned in RFC 3679
OptionEtherboot optionCode = 175
OptionIPTelephone optionCode = 176
OptionEtherbootPacketCableAndCableHome optionCode = 177
// Options 178-207 returned in RFC 3679
OptionPXELinuxMagicString optionCode = 208
OptionPXELinuxConfigFile optionCode = 209
OptionPXELinuxPathPrefix optionCode = 210
OptionPXELinuxRebootTime optionCode = 211
OptionOPTION6RD optionCode = 212
OptionOPTIONv4AccessDomain optionCode = 213
// Options 214-219 returned in RFC 3679
OptionSubnetAllocation optionCode = 220
OptionVirtualSubnetAllocation optionCode = 221
// Options 222-223 returned in RFC 3679
// Options 224-254 are reserved for private use
OptionEnd optionCode = 255
)
var optionCodeToString = map[OptionCode]string{
OptionPad: "Pad",
OptionSubnetMask: "Subnet Mask",
OptionTimeOffset: "Time Offset",
OptionRouter: "Router",
OptionTimeServer: "Time Server",
OptionNameServer: "Name Server",
OptionDomainNameServer: "Domain Name Server",
OptionLogServer: "Log Server",
OptionQuoteServer: "Quote Server",
OptionLPRServer: "LPR Server",
OptionImpressServer: "Impress Server",
OptionResourceLocationServer: "Resource Location Server",
OptionHostName: "Host Name",
OptionBootFileSize: "Boot File Size",
OptionMeritDumpFile: "Merit Dump File",
OptionDomainName: "Domain Name",
OptionSwapServer: "Swap Server",
OptionRootPath: "Root Path",
OptionExtensionsPath: "Extensions Path",
OptionIPForwarding: "IP Forwarding enable/disable",
OptionNonLocalSourceRouting: "Non-local Source Routing enable/disable",
OptionPolicyFilter: "Policy Filter",
OptionMaximumDatagramAssemblySize: "Maximum Datagram Reassembly Size",
OptionDefaultIPTTL: "Default IP Time-to-live",
OptionPathMTUAgingTimeout: "Path MTU Aging Timeout",
OptionPathMTUPlateauTable: "Path MTU Plateau Table",
OptionInterfaceMTU: "Interface MTU",
OptionAllSubnetsAreLocal: "All Subnets Are Local",
OptionBroadcastAddress: "Broadcast Address",
OptionPerformMaskDiscovery: "Perform Mask Discovery",
OptionMaskSupplier: "Mask Supplier",
OptionPerformRouterDiscovery: "Perform Router Discovery",
OptionRouterSolicitationAddress: "Router Solicitation Address",
OptionStaticRoutingTable: "Static Routing Table",
OptionTrailerEncapsulation: "Trailer Encapsulation",
OptionArpCacheTimeout: "ARP Cache Timeout",
OptionEthernetEncapsulation: "Ethernet Encapsulation",
OptionDefaulTCPTTL: "Default TCP TTL",
OptionTCPKeepaliveInterval: "TCP Keepalive Interval",
OptionTCPKeepaliveGarbage: "TCP Keepalive Garbage",
OptionNetworkInformationServiceDomain: "Network Information Service Domain",
OptionNetworkInformationServers: "Network Information Servers",
OptionNTPServers: "NTP Servers",
OptionVendorSpecificInformation: "Vendor Specific Information",
OptionNetBIOSOverTCPIPNameServer: "NetBIOS over TCP/IP Name Server",
OptionNetBIOSOverTCPIPDatagramDistributionServer: "NetBIOS over TCP/IP Datagram Distribution Server",
OptionNetBIOSOverTCPIPNodeType: "NetBIOS over TCP/IP Node Type",
OptionNetBIOSOverTCPIPScope: "NetBIOS over TCP/IP Scope",
OptionXWindowSystemFontServer: "X Window System Font Server",
OptionXWindowSystemDisplayManger: "X Window System Display Manager",
OptionRequestedIPAddress: "Requested IP Address",
OptionIPAddressLeaseTime: "IP Addresses Lease Time",
OptionOptionOverload: "Option Overload",
OptionDHCPMessageType: "DHCP Message Type",
OptionServerIdentifier: "Server Identifier",
OptionParameterRequestList: "Parameter Request List",
OptionMessage: "Message",
OptionMaximumDHCPMessageSize: "Maximum DHCP Message Size",
OptionRenewTimeValue: "Renew Time Value",
OptionRebindingTimeValue: "Rebinding Time Value",
OptionClassIdentifier: "Class Identifier",
OptionClientIdentifier: "Client identifier",
OptionNetWareIPDomainName: "NetWare/IP Domain Name",
OptionNetWareIPInformation: "NetWare/IP Information",
OptionNetworkInformationServicePlusDomain: "Network Information Service+ Domain",
OptionNetworkInformationServicePlusServers: "Network Information Service+ Servers",
OptionTFTPServerName: "TFTP Server Name",
OptionBootfileName: "Bootfile Name",
OptionMobileIPHomeAgent: "Mobile IP Home Agent",
OptionSimpleMailTransportProtocolServer: "SMTP Server",
OptionPostOfficeProtocolServer: "POP Server",
OptionNetworkNewsTransportProtocolServer: "NNTP Server",
OptionDefaultWorldWideWebServer: "Default WWW Server",
OptionDefaultFingerServer: "Default Finger Server",
OptionDefaultInternetRelayChatServer: "Default IRC Server",
OptionStreetTalkServer: "StreetTalk Server",
OptionStreetTalkDirectoryAssistanceServer: "StreetTalk Directory Assistance Server",
OptionUserClassInformation: "User Class Information",
OptionSLPDirectoryAgent: "SLP DIrectory Agent",
OptionSLPServiceScope: "SLP Service Scope",
OptionRapidCommit: "Rapid Commit",
OptionFQDN: "FQDN",
OptionRelayAgentInformation: "Relay Agent Information",
OptionInternetStorageNameService: "Internet Storage Name Service",
// Option 84 returned in RFC 3679
OptionNDSServers: "NDS Servers",
OptionNDSTreeName: "NDS Tree Name",
OptionNDSContext: "NDS Context",
OptionBCMCSControllerDomainNameList: "BCMCS Controller Domain Name List",
OptionBCMCSControllerIPv4AddressList: "BCMCS Controller IPv4 Address List",
OptionAuthentication: "Authentication",
OptionClientLastTransactionTime: "Client Last Transaction Time",
OptionAssociatedIP: "Associated IP",
OptionClientSystemArchitectureType: "Client System Architecture Type",
OptionClientNetworkInterfaceIdentifier: "Client Network Interface Identifier",
OptionLDAP: "LDAP",
// Option 96 returned in RFC 3679
OptionClientMachineIdentifier: "Client Machine Identifier",
OptionOpenGroupUserAuthentication: "OpenGroup's User Authentication",
OptionGeoConfCivic: "GEOCONF_CIVIC",
OptionIEEE10031TZString: "IEEE 1003.1 TZ String",
OptionReferenceToTZDatabase: "Reference to the TZ Database",
// Option 108 returned in RFC 8925
OptionIPv6OnlyPreferred: "IPv6-Only Preferred",
// Options 102-111 returned in RFC 3679
OptionNetInfoParentServerAddress: "NetInfo Parent Server Address",
OptionNetInfoParentServerTag: "NetInfo Parent Server Tag",
OptionURL: "URL",
// Option 115 returned in RFC 3679
OptionAutoConfigure: "Auto-Configure",
OptionNameServiceSearch: "Name Service Search",
OptionSubnetSelection: "Subnet Selection",
OptionDNSDomainSearchList: "DNS Domain Search List",
OptionSIPServers: "SIP Servers",
OptionClasslessStaticRoute: "Classless Static Route",
OptionCCC: "CCC, CableLabs Client Configuration",
OptionGeoConf: "GeoConf",
OptionVendorIdentifyingVendorClass: "Vendor-Identifying Vendor Class",
OptionVendorIdentifyingVendorSpecific: "Vendor-Identifying Vendor-Specific",
// Options 126-127 returned in RFC 3679
OptionTFTPServerIPAddress: "TFTP Server IP Address",
OptionCallServerIPAddress: "Call Server IP Address",
OptionDiscriminationString: "Discrimination String",
OptionRemoteStatisticsServerIPAddress: "RemoteStatistics Server IP Address",
Option8021PVLANID: "802.1P VLAN ID",
Option8021QL2Priority: "802.1Q L2 Priority",
OptionDiffservCodePoint: "Diffserv Code Point",
OptionHTTPProxyForPhoneSpecificApplications: "HTTP Proxy for phone-specific applications",
OptionPANAAuthenticationAgent: "PANA Authentication Agent",
OptionLoSTServer: "LoST Server",
OptionCAPWAPAccessControllerAddresses: "CAPWAP Access Controller Addresses",
OptionOPTIONIPv4AddressMoS: "OPTION-IPv4_Address-MoS",
OptionOPTIONIPv4FQDNMoS: "OPTION-IPv4_FQDN-MoS",
OptionSIPUAConfigurationServiceDomains: "SIP UA Configuration Service Domains",
OptionOPTIONIPv4AddressANDSF: "OPTION-IPv4_Address-ANDSF",
OptionOPTIONIPv6AddressANDSF: "OPTION-IPv6_Address-ANDSF",
// Options 144-149 returned in RFC 3679
OptionTFTPServerAddress: "TFTP Server Address",
OptionStatusCode: "Status Code",
OptionBaseTime: "Base Time",
OptionStartTimeOfState: "Start Time of State",
OptionQueryStartTime: "Query Start Time",
OptionQueryEndTime: "Query End Time",
OptionDHCPState: "DHCP Staet",
OptionDataSource: "Data Source",
// Options 158-174 returned in RFC 3679
OptionEtherboot: "Etherboot",
OptionIPTelephone: "IP Telephone",
OptionEtherbootPacketCableAndCableHome: "Etherboot / PacketCable and CableHome",
// Options 178-207 returned in RFC 3679
OptionPXELinuxMagicString: "PXELinux Magic String",
OptionPXELinuxConfigFile: "PXELinux Config File",
OptionPXELinuxPathPrefix: "PXELinux Path Prefix",
OptionPXELinuxRebootTime: "PXELinux Reboot Time",
OptionOPTION6RD: "OPTION_6RD",
OptionOPTIONv4AccessDomain: "OPTION_V4_ACCESS_DOMAIN",
// Options 214-219 returned in RFC 3679
OptionSubnetAllocation: "Subnet Allocation",
OptionVirtualSubnetAllocation: "Virtual Subnet Selection",
// Options 222-223 returned in RFC 3679
// Options 224-254 are reserved for private use
OptionEnd: "End",
}
// 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
}
package dhcpv6
import (
"errors"
"fmt"
"net"
"strings"
"time"
"github.com/insomniacslk/dhcp/iana"
"github.com/insomniacslk/dhcp/rfc1035label"
"github.com/u-root/uio/rand"
"github.com/u-root/uio/uio"
)
const MessageHeaderSize = 4
// MessageOptions are the options that may appear in a normal DHCPv6 message.
//
// RFC 3315 Appendix B lists the valid options that can be used.
type MessageOptions struct {
Options
}
// ArchTypes returns the architecture type option.
func (mo MessageOptions) ArchTypes() iana.Archs {
opt := mo.GetOne(OptionClientArchType)
if opt == nil {
return nil
}
return opt.(*optClientArchType).Archs
}
// ClientID returns the client identifier option.
func (mo MessageOptions) ClientID() DUID {
opt := mo.GetOne(OptionClientID)
if opt == nil {
return nil
}
return opt.(*optClientID).DUID
}
// ServerID returns the server identifier option.
func (mo MessageOptions) ServerID() DUID {
opt := mo.GetOne(OptionServerID)
if opt == nil {
return nil
}
return opt.(*optServerID).DUID
}
// IANA returns all Identity Association for Non-temporary Address options.
func (mo MessageOptions) IANA() []*OptIANA {
opts := mo.Get(OptionIANA)
var ianas []*OptIANA
for _, o := range opts {
ianas = append(ianas, o.(*OptIANA))
}
return ianas
}
// OneIANA returns the first IANA option.
func (mo MessageOptions) OneIANA() *OptIANA {
ianas := mo.IANA()
if len(ianas) == 0 {
return nil
}
return ianas[0]
}
// IATA returns all Identity Association for Temporary Address options.
func (mo MessageOptions) IATA() []*OptIATA {
opts := mo.Get(OptionIATA)
var iatas []*OptIATA
for _, o := range opts {
iatas = append(iatas, o.(*OptIATA))
}
return iatas
}
// OneIATA returns the first IATA option.
func (mo MessageOptions) OneIATA() *OptIATA {
iatas := mo.IATA()
if len(iatas) == 0 {
return nil
}
return iatas[0]
}
// IAPD returns all Identity Association for Prefix Delegation options.
func (mo MessageOptions) IAPD() []*OptIAPD {
opts := mo.Get(OptionIAPD)
var ianas []*OptIAPD
for _, o := range opts {
ianas = append(ianas, o.(*OptIAPD))
}
return ianas
}
// OneIAPD returns the first IAPD option.
func (mo MessageOptions) OneIAPD() *OptIAPD {
iapds := mo.IAPD()
if len(iapds) == 0 {
return nil
}
return iapds[0]
}
// FourRD returns all 4RD options.
func (mo MessageOptions) FourRD() []*Opt4RD {
opts := mo.Get(Option4RD)
var frds []*Opt4RD
for _, o := range opts {
if m, ok := o.(*Opt4RD); ok {
frds = append(frds, m)
}
}
return frds
}
// Status returns the status code associated with this option.
func (mo MessageOptions) Status() *OptStatusCode {
opt := mo.Options.GetOne(OptionStatusCode)
if opt == nil {
return nil
}
sc, ok := opt.(*OptStatusCode)
if !ok {
return nil
}
return sc
}
// RequestedOptions returns the Options Requested Option.
func (mo MessageOptions) RequestedOptions() OptionCodes {
// Technically, RFC 8415 states that ORO may only appear once in the
// area of a DHCP message. However, some proprietary clients have been
// observed sending more than one OptionORO.
//
// So we merge them.
opt := mo.Options.Get(OptionORO)
if len(opt) == 0 {
return nil
}
var oc OptionCodes
for _, o := range opt {
if oro, ok := o.(*optRequestedOption); ok {
oc = append(oc, oro.OptionCodes...)
}
}
return oc
}
// DNS returns the DNS Recursive Name Server option as defined by RFC 3646.
func (mo MessageOptions) DNS() []net.IP {
opt := mo.Options.GetOne(OptionDNSRecursiveNameServer)
if opt == nil {
return nil
}
if dns, ok := opt.(*optDNS); ok {
return dns.NameServers
}
return nil
}
// DomainSearchList returns the Domain List option as defined by RFC 3646.
func (mo MessageOptions) DomainSearchList() *rfc1035label.Labels {
opt := mo.Options.GetOne(OptionDomainSearchList)
if opt == nil {
return nil
}
if dsl, ok := opt.(*optDomainSearchList); ok {
return dsl.DomainSearchList
}
return nil
}
// BootFileURL returns the Boot File URL option as defined by RFC 5970.
func (mo MessageOptions) BootFileURL() string {
opt := mo.Options.GetOne(OptionBootfileURL)
if opt == nil {
return ""
}
if u, ok := opt.(*optBootFileURL); ok {
return u.url
}
return ""
}
// BootFileParam returns the Boot File Param option as defined by RFC 5970.
func (mo MessageOptions) BootFileParam() []string {
opt := mo.Options.GetOne(OptionBootfileParam)
if opt == nil {
return nil
}
if u, ok := opt.(*optBootFileParam); ok {
return u.params
}
return nil
}
// UserClasses returns a list of user classes.
func (mo MessageOptions) UserClasses() [][]byte {
opt := mo.Options.GetOne(OptionUserClass)
if opt == nil {
return nil
}
if t, ok := opt.(*OptUserClass); ok {
return t.UserClasses
}
return nil
}
// VendorClasses returns the all vendor class options.
func (mo MessageOptions) VendorClasses() []*OptVendorClass {
opt := mo.Options.Get(OptionVendorClass)
if opt == nil {
return nil
}
var vo []*OptVendorClass
for _, o := range opt {
if t, ok := o.(*OptVendorClass); ok {
vo = append(vo, t)
}
}
return vo
}
// VendorClass returns the vendor class options matching the given enterprise
// number.
func (mo MessageOptions) VendorClass(enterpriseNumber uint32) [][]byte {
vo := mo.VendorClasses()
for _, v := range vo {
if v.EnterpriseNumber == enterpriseNumber {
return v.Data
}
}
return nil
}
// VendorOpts returns the all vendor-specific options.
//
// RFC 8415 Section 21.17:
//
// Multiple instances of the Vendor-specific Information option may appear in
// a DHCP message.
func (mo MessageOptions) VendorOpts() []*OptVendorOpts {
opt := mo.Options.Get(OptionVendorOpts)
if opt == nil {
return nil
}
var vo []*OptVendorOpts
for _, o := range opt {
if t, ok := o.(*OptVendorOpts); ok {
vo = append(vo, t)
}
}
return vo
}
// VendorOpt returns the vendor options matching the given enterprise number.
//
// RFC 8415 Section 21.17:
//
// Servers and clients MUST NOT send more than one instance of the
// Vendor-specific Information option with the same Enterprise Number.
func (mo MessageOptions) VendorOpt(enterpriseNumber uint32) Options {
vo := mo.VendorOpts()
for _, v := range vo {
if v.EnterpriseNumber == enterpriseNumber {
return v.VendorOpts
}
}
return nil
}
// ElapsedTime returns the Elapsed Time option as defined by RFC 3315 Section 22.9.
//
// ElapsedTime returns a duration of 0 if the option is not present.
func (mo MessageOptions) ElapsedTime() time.Duration {
opt := mo.Options.GetOne(OptionElapsedTime)
if opt == nil {
return 0
}
if t, ok := opt.(*optElapsedTime); ok {
return t.ElapsedTime
}
return 0
}
// InformationRefreshTime returns the Information Refresh Time option
// as defined by RFC 815 Section 21.23.
//
// InformationRefreshTime returns the provided default if no option is present.
func (mo MessageOptions) InformationRefreshTime(def time.Duration) time.Duration {
opt := mo.Options.GetOne(OptionInformationRefreshTime)
if opt == nil {
return def
}
if t, ok := opt.(*optInformationRefreshTime); ok {
return t.InformationRefreshtime
}
return def
}
// FQDN returns the FQDN option as defined by RFC 4704.
func (mo MessageOptions) FQDN() *OptFQDN {
opt := mo.Options.GetOne(OptionFQDN)
if opt == nil {
return nil
}
if fqdn, ok := opt.(*OptFQDN); ok {
return fqdn
}
return nil
}
// DHCP4oDHCP6Server returns the DHCP 4o6 Server Address option as
// defined by RFC 7341.
func (mo MessageOptions) DHCP4oDHCP6Server() *OptDHCP4oDHCP6Server {
opt := mo.Options.GetOne(OptionDHCP4oDHCP6Server)
if opt == nil {
return nil
}
if server, ok := opt.(*OptDHCP4oDHCP6Server); ok {
return server
}
return nil
}
// NTPServers returns the NTP server addresses contained in the
// NTP_SUBOPTION_SRV_ADDR of an OPTION_NTP_SERVER.
// If multiple NTP server options exist, the function will return all the NTP
// server addresses it finds, as defined by RFC 5908.
func (mo MessageOptions) NTPServers() []net.IP {
opts := mo.Options.Get(OptionNTPServer)
if opts == nil {
return nil
}
addrs := make([]net.IP, 0)
for _, opt := range opts {
ntp, ok := opt.(*OptNTPServer)
if !ok {
continue
}
for _, subopt := range ntp.Suboptions {
so, ok := subopt.(*NTPSuboptionSrvAddr)
if !ok {
continue
}
addrs = append(addrs, net.IP(*so))
}
}
return addrs
}
// Message represents a DHCPv6 Message as defined by RFC 3315 Section 6.
type Message struct {
MessageType MessageType
TransactionID TransactionID
Options MessageOptions
}
var randomRead = rand.Read
// GenerateTransactionID generates a random 3-byte transaction ID.
func GenerateTransactionID() (TransactionID, error) {
var tid TransactionID
n, err := randomRead(tid[:])
if err != nil {
return tid, err
}
if n != len(tid) {
return tid, fmt.Errorf("invalid random sequence: shorter than 3 bytes")
}
return tid, nil
}
// GetTime returns a time integer suitable for DUID-LLT, i.e. the current time counted
// in seconds since January 1st, 2000, midnight UTC, modulo 2^32
func GetTime() uint32 {
now := time.Since(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC))
return uint32((now.Nanoseconds() / 1000000000) % 0xffffffff)
}
// NewSolicit creates a new SOLICIT message, using the given hardware address to
// derive the IAID in the IA_NA option.
func NewSolicit(hwaddr net.HardwareAddr, modifiers ...Modifier) (*Message, error) {
duid := &DUIDLLT{
HWType: iana.HWTypeEthernet,
Time: GetTime(),
LinkLayerAddr: hwaddr,
}
m, err := NewMessage()
if err != nil {
return nil, err
}
m.MessageType = MessageTypeSolicit
m.AddOption(OptClientID(duid))
m.AddOption(OptRequestedOption(
OptionDNSRecursiveNameServer,
OptionDomainSearchList,
))
m.AddOption(OptElapsedTime(0))
if len(hwaddr) < 4 {
return nil, errors.New("short hardware addrss: less than 4 bytes")
}
l := len(hwaddr)
var iaid [4]byte
copy(iaid[:], hwaddr[l-4:l])
modifiers = append([]Modifier{WithIAID(iaid)}, modifiers...)
// Apply modifiers
for _, mod := range modifiers {
mod(m)
}
return m, nil
}
// NewAdvertiseFromSolicit creates a new ADVERTISE packet based on an SOLICIT packet.
func NewAdvertiseFromSolicit(sol *Message, modifiers ...Modifier) (*Message, error) {
if sol == nil {
return nil, errors.New("SOLICIT cannot be nil")
}
if sol.Type() != MessageTypeSolicit {
return nil, errors.New("The passed SOLICIT must have SOLICIT type set")
}
// build ADVERTISE from SOLICIT
adv := &Message{
MessageType: MessageTypeAdvertise,
TransactionID: sol.TransactionID,
}
// add Client ID
cid := sol.GetOneOption(OptionClientID)
if cid == nil {
return nil, errors.New("Client ID cannot be nil in SOLICIT when building ADVERTISE")
}
adv.AddOption(cid)
// apply modifiers
for _, mod := range modifiers {
mod(adv)
}
return adv, nil
}
// NewRequestFromAdvertise creates a new REQUEST packet based on an ADVERTISE
// packet options.
func NewRequestFromAdvertise(adv *Message, modifiers ...Modifier) (*Message, error) {
if adv == nil {
return nil, errors.New("ADVERTISE cannot be nil")
}
if adv.MessageType != MessageTypeAdvertise {
return nil, fmt.Errorf("The passed ADVERTISE must have ADVERTISE type set")
}
// build REQUEST from ADVERTISE
req, err := NewMessage()
if err != nil {
return nil, err
}
req.MessageType = MessageTypeRequest
// add Client ID
cid := adv.GetOneOption(OptionClientID)
if cid == nil {
return nil, fmt.Errorf("Client ID cannot be nil in ADVERTISE when building REQUEST")
}
req.AddOption(cid)
// add Server ID
sid := adv.GetOneOption(OptionServerID)
if sid == nil {
return nil, fmt.Errorf("Server ID cannot be nil in ADVERTISE when building REQUEST")
}
req.AddOption(sid)
// add Elapsed Time
req.AddOption(OptElapsedTime(0))
// add IA_NA
iana := adv.Options.OneIANA()
if iana == nil {
return nil, fmt.Errorf("IA_NA cannot be nil in ADVERTISE when building REQUEST")
}
req.AddOption(iana)
// add IA_PD
if iaPd := adv.GetOneOption(OptionIAPD); iaPd != nil {
req.AddOption(iaPd)
}
req.AddOption(OptRequestedOption(
OptionDNSRecursiveNameServer,
OptionDomainSearchList,
))
// add OPTION_VENDOR_CLASS, only if present in the original request
// TODO implement OptionVendorClass
vClass := adv.GetOneOption(OptionVendorClass)
if vClass != nil {
req.AddOption(vClass)
}
// apply modifiers
for _, mod := range modifiers {
mod(req)
}
return req, nil
}
// NewReplyFromMessage creates a new REPLY packet based on a
// Message. The function is to be used when generating a reply to a SOLICIT with
// rapid-commit, REQUEST, CONFIRM, RENEW, REBIND, RELEASE and INFORMATION-REQUEST
// packets.
func NewReplyFromMessage(msg *Message, modifiers ...Modifier) (*Message, error) {
if msg == nil {
return nil, errors.New("message cannot be nil")
}
switch msg.Type() {
case MessageTypeSolicit:
if msg.GetOneOption(OptionRapidCommit) == nil {
return nil, errors.New("cannot create REPLY from a SOLICIT without rapid-commit option")
}
modifiers = append([]Modifier{WithRapidCommit}, modifiers...)
case MessageTypeRequest, MessageTypeConfirm, MessageTypeRenew,
MessageTypeRebind, MessageTypeRelease, MessageTypeInformationRequest:
default:
return nil, errors.New("cannot create REPLY from the passed message type set")
}
// build REPLY from MESSAGE
rep := &Message{
MessageType: MessageTypeReply,
TransactionID: msg.TransactionID,
}
// add Client ID
cid := msg.GetOneOption(OptionClientID)
if cid == nil {
return nil, errors.New("Client ID cannot be nil when building REPLY")
}
rep.AddOption(cid)
// apply modifiers
for _, mod := range modifiers {
mod(rep)
}
return rep, nil
}
// Type returns this message's message type.
func (m Message) Type() MessageType {
return m.MessageType
}
// GetInnerMessage returns the message itself.
func (m *Message) GetInnerMessage() (*Message, error) {
return m, nil
}
// AddOption adds an option to this message.
func (m *Message) AddOption(option Option) {
m.Options.Add(option)
}
// UpdateOption updates the existing options with the passed option, adding it
// at the end if not present already
func (m *Message) UpdateOption(option Option) {
m.Options.Update(option)
}
// IsNetboot returns true if the machine is trying to netboot. It checks if
// "boot file" is one of the requested options, which is useful for
// SOLICIT/REQUEST packet types, it also checks if the "boot file" option is
// included in the packet, which is useful for ADVERTISE/REPLY packet.
func (m *Message) IsNetboot() bool {
if m.IsOptionRequested(OptionBootfileURL) {
return true
}
if optbf := m.GetOneOption(OptionBootfileURL); optbf != nil {
return true
}
return false
}
// IsOptionRequested takes an OptionCode and returns true if that option is
// within the requested options of the DHCPv6 message.
func (m *Message) IsOptionRequested(requested OptionCode) bool {
return m.Options.RequestedOptions().Contains(requested)
}
// String returns a short human-readable string for this message.
func (m *Message) String() string {
return fmt.Sprintf("Message(MessageType=%s, TransactionID=%#x, %d options)",
m.MessageType, m.TransactionID, len(m.Options.Options))
}
// Summary prints all options associated with this message.
func (m *Message) Summary() string {
return m.LongString(0)
}
// LongString prints all options associated with this message.
func (m *Message) LongString(spaceIndent int) string {
indent := strings.Repeat(" ", spaceIndent)
var s strings.Builder
s.WriteString("Message{\n")
s.WriteString(indent)
s.WriteString(fmt.Sprintf(" MessageType=%s\n", m.MessageType))
s.WriteString(indent)
s.WriteString(fmt.Sprintf(" TransactionID=%s\n", m.TransactionID))
s.WriteString(indent)
s.WriteString(" Options: ")
s.WriteString(m.Options.Options.LongString(spaceIndent + 2))
s.WriteString("\n")
s.WriteString(indent)
s.WriteString("}")
return s.String()
}
// ToBytes returns the serialized version of this message as defined by RFC
// 3315, Section 5.
func (m *Message) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write8(uint8(m.MessageType))
buf.WriteBytes(m.TransactionID[:])
buf.WriteBytes(m.Options.ToBytes())
return buf.Data()
}
// GetOption returns the options associated with the code.
func (m *Message) GetOption(code OptionCode) []Option {
return m.Options.Get(code)
}
// GetOneOption returns the first associated option with the code from this
// message.
func (m *Message) GetOneOption(code OptionCode) Option {
return m.Options.GetOne(code)
}
// IsRelay returns whether this is a relay message or not.
func (m *Message) IsRelay() bool {
return false
}
package dhcpv6
import (
"errors"
"fmt"
"net"
"strings"
"github.com/insomniacslk/dhcp/iana"
"github.com/u-root/uio/uio"
)
const RelayHeaderSize = 34
// RelayOptions are the options valid for RelayForw and RelayRepl messages.
//
// RFC 3315 Appendix B defines them to be InterfaceID and RelayMsg options; RFC
// 4649 also adds the RemoteID option.
type RelayOptions struct {
Options
}
// RelayMessage returns the message embedded.
func (ro RelayOptions) RelayMessage() DHCPv6 {
opt := ro.Options.GetOne(OptionRelayMsg)
if opt == nil {
return nil
}
if relayOpt, ok := opt.(*optRelayMsg); ok {
return relayOpt.Msg
}
return nil
}
// InterfaceID returns the interface ID of this relay message.
func (ro RelayOptions) InterfaceID() []byte {
opt := ro.Options.GetOne(OptionInterfaceID)
if opt == nil {
return nil
}
if iid, ok := opt.(*optInterfaceID); ok {
return iid.ID
}
return nil
}
// RemoteID returns the remote ID in this relay message.
func (ro RelayOptions) RemoteID() *OptRemoteID {
opt := ro.Options.GetOne(OptionRemoteID)
if opt == nil {
return nil
}
if rid, ok := opt.(*OptRemoteID); ok {
return rid
}
return nil
}
// ClientLinkLayerAddress returns the Hardware Type and
// Link Layer Address of the requesting client in this relay message.
func (ro RelayOptions) ClientLinkLayerAddress() (iana.HWType, net.HardwareAddr) {
opt := ro.Options.GetOne(OptionClientLinkLayerAddr)
if opt == nil {
return 0, nil
}
if lla, ok := opt.(*optClientLinkLayerAddress); ok {
return lla.LinkLayerType, lla.LinkLayerAddress
}
return 0, nil
}
// RelayMessage is a DHCPv6 relay agent message as defined by RFC 3315 Section
// 7.
type RelayMessage struct {
MessageType MessageType
HopCount uint8
LinkAddr net.IP
PeerAddr net.IP
Options RelayOptions
}
func write16(b *uio.Lexer, ip net.IP) {
if ip == nil || ip.To16() == nil {
var zeros [net.IPv6len]byte
b.WriteBytes(zeros[:])
} else {
b.WriteBytes(ip.To16())
}
}
// Type is this relay message's types.
func (r *RelayMessage) Type() MessageType {
return r.MessageType
}
// String prints a short human-readable relay message.
func (r *RelayMessage) String() string {
return fmt.Sprintf("RelayMessage(MessageType=%s, HopCount=%d, LinkAddr=%s, PeerAddr=%s, %d options)",
r.Type(), r.HopCount, r.LinkAddr, r.PeerAddr, len(r.Options.Options))
}
// Summary prints all options associated with this relay message.
func (r *RelayMessage) Summary() string {
return r.LongString(0)
}
// LongString prints all options associated with this message.
func (r *RelayMessage) LongString(spaceIndent int) string {
indent := strings.Repeat(" ", spaceIndent)
var s strings.Builder
s.WriteString(indent)
s.WriteString("RelayMessage{\n")
s.WriteString(indent)
s.WriteString(fmt.Sprintf(" MessageType=%s\n", r.MessageType))
s.WriteString(indent)
s.WriteString(fmt.Sprintf(" HopCount=%d\n", r.HopCount))
s.WriteString(indent)
s.WriteString(fmt.Sprintf(" LinkAddr=%s\n", r.LinkAddr))
s.WriteString(indent)
s.WriteString(fmt.Sprintf(" PeerAddr=%s\n", r.PeerAddr))
s.WriteString(indent)
s.WriteString(" Options: ")
s.WriteString(r.Options.Options.LongString(spaceIndent + 2))
s.WriteString("\n}")
return s.String()
}
// ToBytes returns the serialized version of this relay message as defined by
// RFC 3315, Section 7.
func (r *RelayMessage) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(make([]byte, 0, RelayHeaderSize))
buf.Write8(byte(r.MessageType))
buf.Write8(r.HopCount)
write16(buf, r.LinkAddr)
write16(buf, r.PeerAddr)
buf.WriteBytes(r.Options.ToBytes())
return buf.Data()
}
// GetOption returns the options associated with the code.
func (r *RelayMessage) GetOption(code OptionCode) []Option {
return r.Options.Get(code)
}
// GetOneOption returns the first associated option with the code from this
// message.
func (r *RelayMessage) GetOneOption(code OptionCode) Option {
return r.Options.GetOne(code)
}
// AddOption adds an option to this message.
func (r *RelayMessage) AddOption(option Option) {
r.Options.Add(option)
}
// UpdateOption replaces the first option of the same type as the specified one.
func (r *RelayMessage) UpdateOption(option Option) {
r.Options.Update(option)
}
// IsRelay returns whether this is a relay message or not.
func (r *RelayMessage) IsRelay() bool {
return true
}
// GetInnerMessage recurses into a relay message and extract and return the
// inner Message. Return nil if none found (e.g. not a relay message).
func (r *RelayMessage) GetInnerMessage() (*Message, error) {
var (
p DHCPv6
err error
)
p = r
for {
p, err = DecapsulateRelay(p)
if err != nil {
return nil, err
}
if m, ok := p.(*Message); ok {
return m, nil
}
}
}
// NewRelayReplFromRelayForw creates a MessageTypeRelayReply based on a
// MessageTypeRelayForward and replaces the inner message with the passed
// DHCPv6 message. It copies the OptionInterfaceID and OptionRemoteID if the
// options are present in the Relay packet.
func NewRelayReplFromRelayForw(relay *RelayMessage, msg *Message) (DHCPv6, error) {
var (
err error
linkAddr, peerAddr []net.IP
optiid []Option
optrid []Option
)
if relay == nil {
return nil, errors.New("Relay message cannot be nil")
}
if relay.Type() != MessageTypeRelayForward {
return nil, errors.New("The passed packet is not of type MessageTypeRelayForward")
}
if msg == nil {
return nil, errors.New("The passed message cannot be nil")
}
for {
linkAddr = append(linkAddr, relay.LinkAddr)
peerAddr = append(peerAddr, relay.PeerAddr)
optiid = append(optiid, relay.GetOneOption(OptionInterfaceID))
optrid = append(optrid, relay.GetOneOption(OptionRemoteID))
decap, err := DecapsulateRelay(relay)
if err != nil {
return nil, err
}
if decap.IsRelay() {
relay = decap.(*RelayMessage)
} else {
break
}
}
m := DHCPv6(msg)
for i := len(linkAddr) - 1; i >= 0; i-- {
m, err = EncapsulateRelay(m, MessageTypeRelayReply, linkAddr[i], peerAddr[i])
if err != nil {
return nil, err
}
if opt := optiid[i]; opt != nil {
m.AddOption(opt)
}
if opt := optrid[i]; opt != nil {
m.AddOption(opt)
}
}
return m, nil
}
package dhcpv6
import (
"bytes"
"fmt"
"net"
"github.com/insomniacslk/dhcp/iana"
"github.com/u-root/uio/uio"
)
// DUID is the interface that all DUIDs adhere to.
type DUID interface {
fmt.Stringer
ToBytes() []byte
FromBytes(p []byte) error
DUIDType() DUIDType
Equal(d DUID) bool
}
// DUIDLLT is a DUID based on link-layer address plus time (RFC 8415 Section 11.2).
type DUIDLLT struct {
HWType iana.HWType
Time uint32
LinkLayerAddr net.HardwareAddr
}
// String pretty-prints DUIDLLT information.
func (d DUIDLLT) String() string {
return fmt.Sprintf("DUID-LLT{HWType=%s HWAddr=%s Time=%d}", d.HWType, d.LinkLayerAddr, d.Time)
}
// DUIDType returns the DUID_LLT type.
func (d DUIDLLT) DUIDType() DUIDType {
return DUID_LLT
}
// ToBytes serializes the option out to bytes.
func (d DUIDLLT) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write16(uint16(d.DUIDType()))
buf.Write16(uint16(d.HWType))
buf.Write32(d.Time)
buf.WriteBytes(d.LinkLayerAddr)
return buf.Data()
}
// FromBytes reads the option.
func (d *DUIDLLT) FromBytes(p []byte) error {
buf := uio.NewBigEndianBuffer(p)
d.HWType = iana.HWType(buf.Read16())
d.Time = buf.Read32()
d.LinkLayerAddr = buf.ReadAll()
return buf.FinError()
}
// Equal returns true if e is a DUID-LLT with the same values as d.
func (d *DUIDLLT) Equal(e DUID) bool {
ellt, ok := e.(*DUIDLLT)
if !ok {
return false
}
if d == nil {
return d == ellt
}
return d.HWType == ellt.HWType && d.Time == ellt.Time && bytes.Equal(d.LinkLayerAddr, ellt.LinkLayerAddr)
}
// DUIDLL is a DUID based on link-layer (RFC 8415 Section 11.4).
type DUIDLL struct {
HWType iana.HWType
LinkLayerAddr net.HardwareAddr
}
// String pretty-prints DUIDLL information.
func (d DUIDLL) String() string {
return fmt.Sprintf("DUID-LL{HWType=%s HWAddr=%s}", d.HWType, d.LinkLayerAddr)
}
// DUIDType returns the DUID_LL type.
func (d DUIDLL) DUIDType() DUIDType {
return DUID_LL
}
// ToBytes serializes the option out to bytes.
func (d DUIDLL) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write16(uint16(d.DUIDType()))
buf.Write16(uint16(d.HWType))
buf.WriteBytes(d.LinkLayerAddr)
return buf.Data()
}
// FromBytes reads the option.
func (d *DUIDLL) FromBytes(p []byte) error {
buf := uio.NewBigEndianBuffer(p)
d.HWType = iana.HWType(buf.Read16())
d.LinkLayerAddr = buf.ReadAll()
return buf.FinError()
}
// Equal returns true if e is a DUID-LL with the same values as d.
func (d *DUIDLL) Equal(e DUID) bool {
ell, ok := e.(*DUIDLL)
if !ok {
return false
}
if d == nil {
return d == ell
}
return d.HWType == ell.HWType && bytes.Equal(d.LinkLayerAddr, ell.LinkLayerAddr)
}
// DUIDEN is a DUID based on enterprise number (RFC 8415 Section 11.3).
type DUIDEN struct {
EnterpriseNumber uint32
EnterpriseIdentifier []byte
}
// String pretty-prints DUIDEN information.
func (d DUIDEN) String() string {
return fmt.Sprintf("DUID-EN{EnterpriseNumber=%d EnterpriseIdentifier=%s}", d.EnterpriseNumber, d.EnterpriseIdentifier)
}
// DUIDType returns the DUID_EN type.
func (d DUIDEN) DUIDType() DUIDType {
return DUID_EN
}
// ToBytes serializes the option out to bytes.
func (d DUIDEN) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write16(uint16(d.DUIDType()))
buf.Write32(d.EnterpriseNumber)
buf.WriteBytes(d.EnterpriseIdentifier)
return buf.Data()
}
// FromBytes reads the option.
func (d *DUIDEN) FromBytes(p []byte) error {
buf := uio.NewBigEndianBuffer(p)
d.EnterpriseNumber = buf.Read32()
d.EnterpriseIdentifier = buf.ReadAll()
return buf.FinError()
}
// Equal returns true if e is a DUID-EN with the same values as d.
func (d *DUIDEN) Equal(e DUID) bool {
en, ok := e.(*DUIDEN)
if !ok {
return false
}
if d == nil {
return d == en
}
return d.EnterpriseNumber == en.EnterpriseNumber && bytes.Equal(d.EnterpriseIdentifier, en.EnterpriseIdentifier)
}
// DUIDUUID is a DUID based on UUID (RFC 8415 Section 11.5).
type DUIDUUID struct {
// Defined by RFC 6355.
UUID [16]byte
}
// String pretty-prints DUIDUUID information.
func (d DUIDUUID) String() string {
return fmt.Sprintf("DUID-UUID{%#x}", d.UUID[:])
}
// DUIDType returns the DUID_UUID type.
func (d DUIDUUID) DUIDType() DUIDType {
return DUID_UUID
}
// ToBytes serializes the option out to bytes.
func (d DUIDUUID) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write16(uint16(d.DUIDType()))
buf.WriteData(d.UUID[:])
return buf.Data()
}
// FromBytes reads the option.
func (d *DUIDUUID) FromBytes(p []byte) error {
if len(p) != 16 {
return fmt.Errorf("buffer is length %d, DUID-UUID must be exactly 16 bytes", len(p))
}
copy(d.UUID[:], p)
return nil
}
// Equal returns true if e is a DUID-UUID with the same values as d.
func (d *DUIDUUID) Equal(e DUID) bool {
euuid, ok := e.(*DUIDUUID)
if !ok {
return false
}
if d == nil {
return d == euuid
}
return d.UUID == euuid.UUID
}
// DUIDOpaque is a DUID of unknown type.
type DUIDOpaque struct {
Type DUIDType
Data []byte
}
// String pretty-prints opaque DUID information.
func (d DUIDOpaque) String() string {
return fmt.Sprintf("DUID-Opaque{Type=%d Data=%#x}", d.Type, d.Data)
}
// DUIDType returns the opaque DUID type.
func (d DUIDOpaque) DUIDType() DUIDType {
return d.Type
}
// ToBytes serializes the option out to bytes.
func (d DUIDOpaque) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write16(uint16(d.Type))
buf.WriteData(d.Data)
return buf.Data()
}
// FromBytes reads the option.
func (d *DUIDOpaque) FromBytes(p []byte) error {
d.Data = append([]byte(nil), p...)
return nil
}
// Equal returns true if e is an opaque DUID with the same values as d.
func (d *DUIDOpaque) Equal(e DUID) bool {
eopaque, ok := e.(*DUIDOpaque)
if !ok {
return false
}
if d == nil {
return d == eopaque
}
return d.Type == eopaque.Type && bytes.Equal(d.Data, eopaque.Data)
}
// DUIDType is the DUID type as defined in RFC 3315.
type DUIDType uint16
// DUID types
const (
DUID_LLT DUIDType = 1
DUID_EN DUIDType = 2
DUID_LL DUIDType = 3
DUID_UUID DUIDType = 4
)
// duidTypeToString maps a DUIDType to a name.
var duidTypeToString = map[DUIDType]string{
DUID_LL: "DUID-LL",
DUID_LLT: "DUID-LLT",
DUID_EN: "DUID-EN",
DUID_UUID: "DUID-UUID",
}
func (d DUIDType) String() string {
if dtype, ok := duidTypeToString[d]; ok {
return dtype
}
return "unknown"
}
// DUIDFromBytes parses a DUID from a byte slice.
func DUIDFromBytes(data []byte) (DUID, error) {
buf := uio.NewBigEndianBuffer(data)
if !buf.Has(2) {
return nil, fmt.Errorf("%w: have %d bytes, want 2 bytes", uio.ErrBufferTooShort, buf.Len())
}
typ := DUIDType(buf.Read16())
var d DUID
switch typ {
case DUID_LLT:
d = &DUIDLLT{}
case DUID_LL:
d = &DUIDLL{}
case DUID_EN:
d = &DUIDEN{}
case DUID_UUID:
d = &DUIDUUID{}
default:
d = &DUIDOpaque{Type: typ}
}
return d, d.FromBytes(buf.Data())
}
package dhcpv6
import (
"fmt"
"net"
)
// InterfaceAddresses is used to fetch addresses of an interface with given name
var InterfaceAddresses func(string) ([]net.Addr, error) = interfaceAddresses
func interfaceAddresses(ifname string) ([]net.Addr, error) {
iface, err := net.InterfaceByName(ifname)
if err != nil {
return nil, err
}
return iface.Addrs()
}
func getMatchingAddr(ifname string, matches func(net.IP) bool) (net.IP, error) {
ifaddrs, err := InterfaceAddresses(ifname)
if err != nil {
return nil, err
}
for _, ifaddr := range ifaddrs {
if ifaddr, ok := ifaddr.(*net.IPNet); ok && matches(ifaddr.IP) {
return ifaddr.IP, nil
}
}
return nil, fmt.Errorf("no matching address found for interface %s", ifname)
}
// GetLinkLocalAddr returns a link-local address for the interface
func GetLinkLocalAddr(ifname string) (net.IP, error) {
return getMatchingAddr(ifname, func(ip net.IP) bool {
return ip.To4() == nil && ip.IsLinkLocalUnicast()
})
}
// GetGlobalAddr returns a global address for the interface
func GetGlobalAddr(ifname string) (net.IP, error) {
return getMatchingAddr(ifname, func(ip net.IP) bool {
return ip.To4() == nil && ip.IsGlobalUnicast()
})
}
// GetMacAddressFromEUI64 will return a valid MAC address ONLY if it's a EUI-48
func GetMacAddressFromEUI64(ip net.IP) (net.HardwareAddr, error) {
if ip.To16() == nil {
return nil, fmt.Errorf("IP address shorter than 16 bytes")
}
if isEUI48 := ip[11] == 0xff && ip[12] == 0xfe; !isEUI48 {
return nil, fmt.Errorf("IP address is not an EUI48 address")
}
mac := make(net.HardwareAddr, 6)
copy(mac[0:3], ip[8:11])
copy(mac[3:6], ip[13:16])
mac[0] ^= 0x02
return mac, nil
}
// ExtractMAC looks into the inner most PeerAddr field in the RelayInfo header
// which contains the EUI-64 address of the client making the request, populated
// by the dhcp relay, it is possible to extract the mac address from that IP.
// If that fails, it looks for the MAC addressed embededded in the DUID.
// Note that this only works with type DuidLL and DuidLLT.
// If a mac address cannot be found an error will be returned.
func ExtractMAC(packet DHCPv6) (net.HardwareAddr, error) {
msg := packet
if packet.IsRelay() {
inner, err := DecapsulateRelayIndex(packet, -1)
if err != nil {
return nil, err
}
relay := inner.(*RelayMessage)
if _, mac := relay.Options.ClientLinkLayerAddress(); mac != nil {
return mac, nil
}
if mac, err := GetMacAddressFromEUI64(relay.PeerAddr); err == nil {
return mac, nil
}
msg, err = msg.(*RelayMessage).GetInnerMessage()
if err != nil {
return nil, err
}
}
duid := msg.(*Message).Options.ClientID()
if duid == nil {
return nil, fmt.Errorf("client ID not found in packet")
}
switch d := duid.(type) {
case *DUIDLL:
if d.LinkLayerAddr != nil {
return d.LinkLayerAddr, nil
}
case *DUIDLLT:
if d.LinkLayerAddr != nil {
return d.LinkLayerAddr, nil
}
}
return nil, fmt.Errorf("failed to extract MAC")
}
package dhcpv6
import (
"net"
"time"
"github.com/insomniacslk/dhcp/iana"
"github.com/insomniacslk/dhcp/rfc1035label"
)
// WithOption adds the specific option to the DHCPv6 message.
func WithOption(o Option) Modifier {
return func(d DHCPv6) {
d.UpdateOption(o)
}
}
// WithClientID adds a client ID option to a DHCPv6 packet
func WithClientID(duid DUID) Modifier {
return WithOption(OptClientID(duid))
}
// WithServerID adds a client ID option to a DHCPv6 packet
func WithServerID(duid DUID) Modifier {
return WithOption(OptServerID(duid))
}
// WithNetboot adds bootfile URL and bootfile param options to a DHCPv6 packet.
func WithNetboot(d DHCPv6) {
WithRequestedOptions(OptionBootfileURL, OptionBootfileParam)(d)
}
// WithFQDN adds a fully qualified domain name option to the packet
func WithFQDN(flags uint8, domainname string) Modifier {
return func(d DHCPv6) {
d.UpdateOption(&OptFQDN{
Flags: flags,
DomainName: &rfc1035label.Labels{
Labels: []string{domainname},
},
})
}
}
// WithUserClass adds a user class option to the packet
func WithUserClass(uc []byte) Modifier {
// TODO let the user specify multiple user classes
return func(d DHCPv6) {
ouc := OptUserClass{UserClasses: [][]byte{uc}}
d.AddOption(&ouc)
}
}
// WithArchType adds an arch type option to the packet
func WithArchType(at iana.Arch) Modifier {
return func(d DHCPv6) {
d.AddOption(OptClientArchType(at))
}
}
// WithIANA adds or updates an OptIANA option with the provided IAAddress
// options
func WithIANA(addrs ...OptIAAddress) Modifier {
return func(d DHCPv6) {
if msg, ok := d.(*Message); ok {
iana := msg.Options.OneIANA()
if iana == nil {
iana = &OptIANA{}
}
for _, addr := range addrs {
iana.Options.Add(&addr)
}
msg.UpdateOption(iana)
}
}
}
// WithIAID updates an OptIANA option with the provided IAID
func WithIAID(iaid [4]byte) Modifier {
return func(d DHCPv6) {
if msg, ok := d.(*Message); ok {
iana := msg.Options.OneIANA()
if iana == nil {
iana = &OptIANA{
Options: IdentityOptions{Options: []Option{}},
}
}
copy(iana.IaId[:], iaid[:])
d.UpdateOption(iana)
}
}
}
// WithIATA adds or updates an OptIATA option with the provided IAID,
// and IAAddress options
func WithIATA(iaid [4]byte, addrs ...OptIAAddress) Modifier {
return func(d DHCPv6) {
if msg, ok := d.(*Message); ok {
iata := msg.Options.OneIATA()
if iata == nil {
iata = &OptIATA{}
}
copy(iata.IaId[:], iaid[:])
for _, addr := range addrs {
iata.Options.Add(&addr)
}
msg.UpdateOption(iata)
}
}
}
// WithDNS adds or updates an OptDNSRecursiveNameServer
func WithDNS(dnses ...net.IP) Modifier {
return WithOption(OptDNS(dnses...))
}
// WithDomainSearchList adds or updates an OptDomainSearchList
func WithDomainSearchList(searchlist ...string) Modifier {
return func(d DHCPv6) {
d.UpdateOption(OptDomainSearchList(
&rfc1035label.Labels{
Labels: searchlist,
},
))
}
}
// WithRapidCommit adds the rapid commit option to a message.
func WithRapidCommit(d DHCPv6) {
d.UpdateOption(&OptionGeneric{OptionCode: OptionRapidCommit})
}
// WithRequestedOptions adds requested options to the packet
func WithRequestedOptions(codes ...OptionCode) Modifier {
return func(d DHCPv6) {
if msg, ok := d.(*Message); ok {
oro := msg.Options.RequestedOptions()
for _, c := range codes {
oro.Add(c)
}
d.UpdateOption(OptRequestedOption(oro...))
}
}
}
// WithDHCP4oDHCP6Server adds or updates an OptDHCP4oDHCP6Server
func WithDHCP4oDHCP6Server(addrs ...net.IP) Modifier {
return func(d DHCPv6) {
opt := OptDHCP4oDHCP6Server{
DHCP4oDHCP6Servers: addrs,
}
d.UpdateOption(&opt)
}
}
// WithIAPD adds or updates an IAPD option with the provided IAID and
// prefix options to a DHCPv6 packet.
func WithIAPD(iaid [4]byte, prefixes ...*OptIAPrefix) Modifier {
return func(d DHCPv6) {
if msg, ok := d.(*Message); ok {
opt := msg.Options.OneIAPD()
if opt == nil {
opt = &OptIAPD{}
}
copy(opt.IaId[:], iaid[:])
for _, prefix := range prefixes {
opt.Options.Add(prefix)
}
d.UpdateOption(opt)
}
}
}
// WithClientLinkLayerAddress adds or updates the ClientLinkLayerAddress
// option with provided HWType and HWAddress on a DHCPv6 packet
func WithClientLinkLayerAddress(ht iana.HWType, lla net.HardwareAddr) Modifier {
return WithOption(OptClientLinkLayerAddress(ht, lla))
}
// WithInformationRefreshTime adds an optInformationRefreshTime to the DHCPv6 packet
// using the provided duration
func WithInformationRefreshTime(irt time.Duration) Modifier {
return WithOption(OptInformationRefreshTime(irt))
}
package dhcpv6
import (
"fmt"
"net"
"github.com/u-root/uio/uio"
)
// Opt4RD represents a 4RD option. It is only a container for 4RD_*_RULE options
type Opt4RD struct {
FourRDOptions
}
// Code returns the Option Code for this option
func (op *Opt4RD) Code() OptionCode {
return Option4RD
}
// ToBytes serializes this option
func (op *Opt4RD) ToBytes() []byte {
return op.Options.ToBytes()
}
// String returns a human-readable representation of the option
func (op *Opt4RD) String() string {
return fmt.Sprintf("%s: {Options=%v}", op.Code(), op.Options)
}
// LongString returns a multi-line human-readable representation of the option
func (op *Opt4RD) LongString(indentSpace int) string {
return fmt.Sprintf("%s: Options=%v", op.Code(), op.Options.LongString(indentSpace))
}
// FromBytes builds an Opt4RD structure from a sequence of bytes.
// The input data does not include option code and length bytes
func (op *Opt4RD) FromBytes(data []byte) error {
return op.Options.FromBytes(data)
}
// FourRDOptions are options that can be encapsulated with the 4RD option.
type FourRDOptions struct {
Options
}
// MapRules returns the map rules associated with the 4RD option.
//
// "The OPTION_4RD DHCPv6 option contains at least one encapsulated
// OPTION_4RD_MAP_RULE option." (RFC 7600 Section 4.9)
func (frdo FourRDOptions) MapRules() []*Opt4RDMapRule {
opts := frdo.Options.Get(Option4RDMapRule)
var mrs []*Opt4RDMapRule
for _, o := range opts {
if m, ok := o.(*Opt4RDMapRule); ok {
mrs = append(mrs, m)
}
}
return mrs
}
// NonMapRule returns the non-map-rule associated with this option.
//
// "The OPTION_4RD DHCPv6 option contains ... a maximum of one
// encapsulated OPTION_4RD_NON_MAP_RULE option." (RFC 7600 Section 4.9)
func (frdo FourRDOptions) NonMapRule() *Opt4RDNonMapRule {
opt := frdo.Options.GetOne(Option4RDNonMapRule)
if opt == nil {
return nil
}
nmr, ok := opt.(*Opt4RDNonMapRule)
if !ok {
return nil
}
return nmr
}
// Opt4RDMapRule represents a 4RD Mapping Rule option.
//
// The option is described in RFC 7600 Section 4.9. The 4RD mapping rules are
// described in RFC 7600 Section 4.2.
type Opt4RDMapRule struct {
// Prefix4 is the IPv4 prefix mapped by this rule
Prefix4 net.IPNet
// Prefix6 is the IPv6 prefix mapped by this rule
Prefix6 net.IPNet
// EABitsLength is the number of bits of an address used in constructing the mapped address
EABitsLength uint8
// WKPAuthorized determines if well-known ports are assigned to addresses in an A+P mapping
// It can only be set if the length of Prefix4 + EABits > 32
WKPAuthorized bool
}
const (
// opt4RDWKPAuthorizedMask is the mask for the WKPAuthorized flag in its
// byte in Opt4RDMapRule
opt4RDWKPAuthorizedMask = 1 << 7
// opt4RDHubAndSpokeMask is the mask for the HubAndSpoke flag in its
// byte in Opt4RDNonMapRule
opt4RDHubAndSpokeMask = 1 << 7
// opt4RDTrafficClassMask is the mask for the TrafficClass flag in its
// byte in Opt4RDNonMapRule
opt4RDTrafficClassMask = 1 << 0
)
// Code returns the option code representing this option
func (op *Opt4RDMapRule) Code() OptionCode { return Option4RDMapRule }
// ToBytes serializes this option
func (op *Opt4RDMapRule) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
p4Len, _ := op.Prefix4.Mask.Size()
p6Len, _ := op.Prefix6.Mask.Size()
buf.Write8(uint8(p4Len))
buf.Write8(uint8(p6Len))
buf.Write8(op.EABitsLength)
if op.WKPAuthorized {
buf.Write8(opt4RDWKPAuthorizedMask)
} else {
buf.Write8(0)
}
if op.Prefix4.IP.To4() == nil {
// The API prevents us from returning an error here
// We just write zeros instead, which is pretty bad behaviour
buf.Write32(0)
} else {
buf.WriteBytes(op.Prefix4.IP.To4())
}
if op.Prefix6.IP.To16() == nil {
buf.Write64(0)
buf.Write64(0)
} else {
buf.WriteBytes(op.Prefix6.IP.To16())
}
return buf.Data()
}
// String returns a human-readable description of this option
func (op *Opt4RDMapRule) String() string {
return fmt.Sprintf("%s: {Prefix4=%s, Prefix6=%s, EA-Bits=%d, WKPAuthorized=%t}",
op.Code(), op.Prefix4.String(), op.Prefix6.String(), op.EABitsLength, op.WKPAuthorized)
}
// FromBytes builds an Opt4RDMapRule structure from a sequence of bytes.
// The input data does not include option code and length bytes.
func (op *Opt4RDMapRule) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
op.Prefix4.Mask = net.CIDRMask(int(buf.Read8()), 32)
op.Prefix6.Mask = net.CIDRMask(int(buf.Read8()), 128)
op.EABitsLength = buf.Read8()
op.WKPAuthorized = (buf.Read8() & opt4RDWKPAuthorizedMask) != 0
op.Prefix4.IP = net.IP(buf.CopyN(net.IPv4len))
op.Prefix6.IP = net.IP(buf.CopyN(net.IPv6len))
return buf.FinError()
}
// Opt4RDNonMapRule represents 4RD parameters other than mapping rules
type Opt4RDNonMapRule struct {
// HubAndSpoke is whether the network topology is hub-and-spoke or meshed
HubAndSpoke bool
// TrafficClass is an optional 8-bit tunnel traffic class identifier
TrafficClass *uint8
// DomainPMTU is the Path MTU for this 4RD domain
DomainPMTU uint16
}
// Code returns the option code for this option
func (op *Opt4RDNonMapRule) Code() OptionCode {
return Option4RDNonMapRule
}
// ToBytes serializes this option
func (op *Opt4RDNonMapRule) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
var flags uint8
var trafficClassValue uint8
if op.HubAndSpoke {
flags |= opt4RDHubAndSpokeMask
}
if op.TrafficClass != nil {
flags |= opt4RDTrafficClassMask
trafficClassValue = *op.TrafficClass
}
buf.Write8(flags)
buf.Write8(trafficClassValue)
buf.Write16(op.DomainPMTU)
return buf.Data()
}
// String returns a human-readable description of this option
func (op *Opt4RDNonMapRule) String() string {
var tClass interface{} = false
if op.TrafficClass != nil {
tClass = *op.TrafficClass
}
return fmt.Sprintf("%s: {HubAndSpoke=%t, TrafficClass=%v, DomainPMTU=%d}", op.Code(), op.HubAndSpoke, tClass, op.DomainPMTU)
}
// FromBytes builds an Opt4RDNonMapRule structure from a sequence of bytes.
// The input data does not include option code and length bytes
func (op *Opt4RDNonMapRule) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
flags := buf.Read8()
op.HubAndSpoke = flags&opt4RDHubAndSpokeMask != 0
tClass := buf.Read8()
if flags&opt4RDTrafficClassMask != 0 {
op.TrafficClass = &tClass
}
op.DomainPMTU = buf.Read16()
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"github.com/insomniacslk/dhcp/iana"
)
// OptClientArchType represents an option CLIENT_ARCH_TYPE.
//
// This module defines the OptClientArchType structure.
// https://www.ietf.org/rfc/rfc5970.txt
func OptClientArchType(a ...iana.Arch) Option {
return &optClientArchType{Archs: a}
}
type optClientArchType struct {
iana.Archs
}
func (op *optClientArchType) Code() OptionCode {
return OptionClientArchType
}
func (op optClientArchType) String() string {
return fmt.Sprintf("%s: %s", op.Code(), op.Archs)
}
func (op *optClientArchType) FromBytes(p []byte) error {
return op.Archs.FromBytes(p)
}
package dhcpv6
import (
"fmt"
"github.com/u-root/uio/uio"
)
// OptBootFileParam returns a BootfileParam option as defined in RFC 5970
// Section 3.2.
func OptBootFileParam(args ...string) Option {
return &optBootFileParam{args}
}
type optBootFileParam struct {
params []string
}
// Code returns the option code
func (optBootFileParam) Code() OptionCode {
return OptionBootfileParam
}
// ToBytes serializes the option and returns it as a sequence of bytes
func (op optBootFileParam) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, param := range op.params {
if len(param) >= 1<<16 {
// TODO: say something here instead of silently ignoring a parameter
continue
}
buf.Write16(uint16(len(param)))
buf.WriteBytes([]byte(param))
/*if err := buf.Error(); err != nil {
// TODO: description of `WriteBytes` says it could return
// an error via `buf.Error()`. But a quick look into implementation of
// `WriteBytes` at the moment of this comment showed it does not set any
// errors to `Error()` output. It's required to make a decision:
// to fix `WriteBytes` or it's description or
// to find a way to handle an error here.
}*/
}
return buf.Data()
}
func (op optBootFileParam) String() string {
return fmt.Sprintf("%s: %v", op.Code(), op.params)
}
// FromBytes builds an OptBootFileParam structure from a sequence
// of bytes. The input data does not include option code and length bytes.
func (op *optBootFileParam) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
for buf.Has(2) {
length := buf.Read16()
op.params = append(op.params, string(buf.CopyN(int(length))))
}
return buf.FinError()
}
package dhcpv6
import (
"fmt"
)
// OptBootFileURL returns a OptionBootfileURL as defined by RFC 5970.
func OptBootFileURL(url string) Option {
return &optBootFileURL{url}
}
type optBootFileURL struct {
url string
}
// Code returns the option code
func (op optBootFileURL) Code() OptionCode {
return OptionBootfileURL
}
// ToBytes serializes the option and returns it as a sequence of bytes
func (op optBootFileURL) ToBytes() []byte {
return []byte(op.url)
}
func (op optBootFileURL) String() string {
return fmt.Sprintf("%s: %s", op.Code(), op.url)
}
// FromBytes builds an optBootFileURL structure from a sequence
// of bytes. The input data does not include option code and length bytes.
func (op *optBootFileURL) FromBytes(data []byte) error {
op.url = string(data)
return nil
}
package dhcpv6
import (
"fmt"
)
// OptClientID represents a Client Identifier option as defined by RFC 3315
// Section 22.2.
func OptClientID(d DUID) Option {
return &optClientID{d}
}
type optClientID struct {
DUID
}
func (*optClientID) Code() OptionCode {
return OptionClientID
}
func (op *optClientID) String() string {
return fmt.Sprintf("%s: %s", op.Code(), op.DUID)
}
// FromBytes builds an optClientID structure from a sequence
// of bytes. The input data does not include option code and length
// bytes.
func (op *optClientID) FromBytes(data []byte) error {
var err error
op.DUID, err = DUIDFromBytes(data)
return err
}
package dhcpv6
import (
"fmt"
"net"
"github.com/insomniacslk/dhcp/iana"
"github.com/u-root/uio/uio"
)
// OptClientLinkLayerAddress implements OptionClientLinkLayerAddr option.
// https://tools.ietf.org/html/rfc6939
func OptClientLinkLayerAddress(ht iana.HWType, lla net.HardwareAddr) *optClientLinkLayerAddress {
return &optClientLinkLayerAddress{LinkLayerType: ht, LinkLayerAddress: lla}
}
type optClientLinkLayerAddress struct {
LinkLayerType iana.HWType
LinkLayerAddress net.HardwareAddr
}
// Code returns the option code.
func (op *optClientLinkLayerAddress) Code() OptionCode {
return OptionClientLinkLayerAddr
}
// ToBytes serializes the option and returns it as a sequence of bytes
func (op *optClientLinkLayerAddress) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write16(uint16(op.LinkLayerType))
buf.WriteBytes(op.LinkLayerAddress)
return buf.Data()
}
func (op *optClientLinkLayerAddress) String() string {
return fmt.Sprintf("%s: Type=%s LinkLayerAddress=%s", op.Code(), op.LinkLayerType, op.LinkLayerAddress)
}
// FromBytes deserializes from bytes to build an optClientLinkLayerAddress
// structure.
func (op *optClientLinkLayerAddress) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
op.LinkLayerType = iana.HWType(buf.Read16())
op.LinkLayerAddress = buf.ReadAll()
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"strings"
"github.com/insomniacslk/dhcp/dhcpv4"
)
// OptDHCPv4Msg represents a OptionDHCPv4Msg option
//
// This module defines the OptDHCPv4Msg structure.
// https://www.ietf.org/rfc/rfc7341.txt
type OptDHCPv4Msg struct {
Msg *dhcpv4.DHCPv4
}
// Code returns the option code
func (op *OptDHCPv4Msg) Code() OptionCode {
return OptionDHCPv4Msg
}
// ToBytes returns the option serialized to bytes.
func (op *OptDHCPv4Msg) ToBytes() []byte {
return op.Msg.ToBytes()
}
func (op *OptDHCPv4Msg) String() string {
return fmt.Sprintf("%s: %v", op.Code(), op.Msg)
}
// LongString returns a multi-line string representation of DHCPv4 data.
func (op *OptDHCPv4Msg) LongString(indent int) string {
summary := op.Msg.Summary()
ind := strings.Repeat(" ", indent+2)
if strings.Contains(summary, "\n") {
summary = strings.Replace(summary, "\n ", "\n"+ind, -1)
}
ind = strings.Repeat(" ", indent)
return fmt.Sprintf("%s: {%v%s}", op.Code(), summary, ind)
}
// FromBytes builds an OptDHCPv4Msg structure from a sequence of bytes. The
// input data does not include option code and length bytes.
func (op *OptDHCPv4Msg) FromBytes(data []byte) error {
var err error
op.Msg, err = dhcpv4.FromBytes(data)
return err
}
package dhcpv6
import (
"fmt"
"net"
"github.com/u-root/uio/uio"
)
// OptDHCP4oDHCP6Server represents a OptionDHCP4oDHCP6Server option
//
// This module defines the OptDHCP4oDHCP6Server structure.
// https://www.ietf.org/rfc/rfc7341.txt
type OptDHCP4oDHCP6Server struct {
DHCP4oDHCP6Servers []net.IP
}
// Code returns the option code
func (op *OptDHCP4oDHCP6Server) Code() OptionCode {
return OptionDHCP4oDHCP6Server
}
// ToBytes returns the option serialized to bytes.
func (op *OptDHCP4oDHCP6Server) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, addr := range op.DHCP4oDHCP6Servers {
buf.WriteBytes(addr.To16())
}
return buf.Data()
}
func (op *OptDHCP4oDHCP6Server) String() string {
return fmt.Sprintf("%s: %v", op.Code(), op.DHCP4oDHCP6Servers)
}
// FromBytes builds an OptDHCP4oDHCP6Server structure from a sequence of bytes.
// The input data does not include option code and length bytes.
func (op *OptDHCP4oDHCP6Server) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
for buf.Has(net.IPv6len) {
op.DHCP4oDHCP6Servers = append(op.DHCP4oDHCP6Servers, buf.CopyN(net.IPv6len))
}
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"net"
"github.com/u-root/uio/uio"
)
// OptDNS returns a DNS Recursive Name Server option as defined by RFC 3646.
func OptDNS(ip ...net.IP) Option {
return &optDNS{NameServers: ip}
}
type optDNS struct {
NameServers []net.IP
}
// Code returns the option code
func (op *optDNS) Code() OptionCode {
return OptionDNSRecursiveNameServer
}
// ToBytes returns the option serialized to bytes.
func (op *optDNS) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, ns := range op.NameServers {
buf.WriteBytes(ns.To16())
}
return buf.Data()
}
func (op *optDNS) String() string {
return fmt.Sprintf("%s: %v", op.Code(), op.NameServers)
}
// FromBytes builds an optDNS structure from a sequence of bytes. The input
// data does not include option code and length bytes.
func (op *optDNS) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
for buf.Has(net.IPv6len) {
op.NameServers = append(op.NameServers, buf.CopyN(net.IPv6len))
}
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"github.com/insomniacslk/dhcp/rfc1035label"
)
// OptDomainSearchList returns a DomainSearchList option as defined by RFC 3646.
func OptDomainSearchList(labels *rfc1035label.Labels) Option {
return &optDomainSearchList{DomainSearchList: labels}
}
type optDomainSearchList struct {
DomainSearchList *rfc1035label.Labels
}
func (op *optDomainSearchList) Code() OptionCode {
return OptionDomainSearchList
}
// ToBytes marshals this option to bytes.
func (op *optDomainSearchList) ToBytes() []byte {
return op.DomainSearchList.ToBytes()
}
func (op *optDomainSearchList) String() string {
return fmt.Sprintf("%s: %s", op.Code(), op.DomainSearchList)
}
// FromBytes builds an OptDomainSearchList structure from a sequence of bytes.
// The input data does not include option code and length bytes.
func (op *optDomainSearchList) FromBytes(data []byte) error {
var err error
op.DomainSearchList, err = rfc1035label.FromBytes(data)
return err
}
package dhcpv6
import (
"fmt"
"time"
"github.com/u-root/uio/uio"
)
// OptElapsedTime returns an Elapsed Time option as defined by RFC 3315 Section
// 22.9.
func OptElapsedTime(dur time.Duration) Option {
return &optElapsedTime{ElapsedTime: dur}
}
type optElapsedTime struct {
ElapsedTime time.Duration
}
func (*optElapsedTime) Code() OptionCode {
return OptionElapsedTime
}
// ToBytes marshals this option to bytes.
func (op *optElapsedTime) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write16(uint16(op.ElapsedTime.Round(10*time.Millisecond) / (10 * time.Millisecond)))
return buf.Data()
}
func (op *optElapsedTime) String() string {
return fmt.Sprintf("%s: %s", op.Code(), op.ElapsedTime)
}
// FromBytes builds an optElapsedTime structure from a sequence of bytes.
// The input data does not include option code and length bytes.
func (op *optElapsedTime) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
op.ElapsedTime = time.Duration(buf.Read16()) * 10 * time.Millisecond
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"github.com/insomniacslk/dhcp/rfc1035label"
"github.com/u-root/uio/uio"
)
// OptFQDN implements OptionFQDN option.
//
// https://tools.ietf.org/html/rfc4704
type OptFQDN struct {
Flags uint8
DomainName *rfc1035label.Labels
}
// Code returns the option code.
func (op *OptFQDN) Code() OptionCode {
return OptionFQDN
}
// ToBytes serializes the option and returns it as a sequence of bytes
func (op *OptFQDN) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write8(op.Flags)
buf.WriteBytes(op.DomainName.ToBytes())
return buf.Data()
}
func (op *OptFQDN) String() string {
return fmt.Sprintf("%s: {Flags=%d DomainName=%s}", op.Code(), op.Flags, op.DomainName)
}
// FromBytes deserializes from bytes to build a OptFQDN structure.
func (op *OptFQDN) FromBytes(data []byte) error {
var err error
buf := uio.NewBigEndianBuffer(data)
op.Flags = buf.Read8()
op.DomainName, err = rfc1035label.FromBytes(buf.ReadAll())
if err != nil {
return err
}
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"net"
"time"
"github.com/u-root/uio/uio"
)
// AddressOptions are options valid for the IAAddress option field.
//
// RFC 8415 Appendix C lists only the Status Code option as valid.
type AddressOptions struct {
Options
}
// Status returns the status code associated with this option.
func (ao AddressOptions) Status() *OptStatusCode {
opt := ao.Options.GetOne(OptionStatusCode)
if opt == nil {
return nil
}
sc, ok := opt.(*OptStatusCode)
if !ok {
return nil
}
return sc
}
// OptIAAddress represents an OptionIAAddr.
//
// This module defines the OptIAAddress structure.
// https://www.ietf.org/rfc/rfc3633.txt
type OptIAAddress struct {
IPv6Addr net.IP
PreferredLifetime time.Duration
ValidLifetime time.Duration
Options AddressOptions
}
// Code returns the option's code
func (op *OptIAAddress) Code() OptionCode {
return OptionIAAddr
}
// ToBytes serializes the option and returns it as a sequence of bytes
func (op *OptIAAddress) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
write16(buf, op.IPv6Addr)
t1 := Duration{op.PreferredLifetime}
t1.Marshal(buf)
t2 := Duration{op.ValidLifetime}
t2.Marshal(buf)
buf.WriteBytes(op.Options.ToBytes())
return buf.Data()
}
func (op *OptIAAddress) String() string {
return fmt.Sprintf("%s: {IP=%v PreferredLifetime=%v ValidLifetime=%v Options=%v}",
op.Code(), op.IPv6Addr, op.PreferredLifetime, op.ValidLifetime, op.Options)
}
// LongString returns a multi-line string representation of the OptIAAddress data.
func (op *OptIAAddress) LongString(indent int) string {
return fmt.Sprintf("%s: {IP=%v PreferredLifetime=%v ValidLifetime=%v Options=%v}",
op.Code(), op.IPv6Addr, op.PreferredLifetime, op.ValidLifetime, op.Options.LongString(indent))
}
// FromBytes builds an OptIAAddress structure from a sequence of bytes. The
// input data does not include option code and length bytes.
func (op *OptIAAddress) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
op.IPv6Addr = net.IP(buf.CopyN(net.IPv6len))
var t1, t2 Duration
t1.Unmarshal(buf)
t2.Unmarshal(buf)
op.PreferredLifetime = t1.Duration
op.ValidLifetime = t2.Duration
if err := op.Options.FromBytes(buf.ReadAll()); err != nil {
return err
}
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"time"
"github.com/u-root/uio/uio"
)
// PDOptions are options used with the IAPD (prefix delegation) option.
//
// RFC 3633 describes that IA_PD-options may contain the IAPrefix option and
// the StatusCode option.
type PDOptions struct {
Options
}
// Prefixes are the prefixes associated with this delegation.
func (po PDOptions) Prefixes() []*OptIAPrefix {
opts := po.Options.Get(OptionIAPrefix)
if len(opts) == 0 {
return nil
}
pre := make([]*OptIAPrefix, 0, len(opts))
for _, o := range opts {
if iap, ok := o.(*OptIAPrefix); ok {
pre = append(pre, iap)
}
}
return pre
}
// Status returns the status code associated with this option.
func (po PDOptions) Status() *OptStatusCode {
opt := po.Options.GetOne(OptionStatusCode)
if opt == nil {
return nil
}
sc, ok := opt.(*OptStatusCode)
if !ok {
return nil
}
return sc
}
// OptIAPD implements the identity association for prefix
// delegation option defined by RFC 3633, Section 9.
type OptIAPD struct {
IaId [4]byte
T1 time.Duration
T2 time.Duration
Options PDOptions
}
// Code returns the option code
func (op *OptIAPD) Code() OptionCode {
return OptionIAPD
}
// ToBytes serializes the option and returns it as a sequence of bytes
func (op *OptIAPD) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.WriteBytes(op.IaId[:])
t1 := Duration{op.T1}
t1.Marshal(buf)
t2 := Duration{op.T2}
t2.Marshal(buf)
buf.WriteBytes(op.Options.ToBytes())
return buf.Data()
}
// String returns a string representation of the OptIAPD data
func (op *OptIAPD) String() string {
return fmt.Sprintf("%s: {IAID=%#x T1=%v T2=%v Options=%v}",
op.Code(), op.IaId, op.T1, op.T2, op.Options)
}
// LongString returns a multi-line string representation of the OptIAPD data
func (op *OptIAPD) LongString(indentSpace int) string {
return fmt.Sprintf("%s: IAID=%#x T1=%v T2=%v Options=%v", op.Code(), op.IaId, op.T1, op.T2, op.Options.LongString(indentSpace))
}
// FromBytes builds an OptIAPD structure from a sequence of bytes. The input
// data does not include option code and length bytes.
func (op *OptIAPD) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
buf.ReadBytes(op.IaId[:])
var t1, t2 Duration
t1.Unmarshal(buf)
t2.Unmarshal(buf)
op.T1 = t1.Duration
op.T2 = t2.Duration
if err := op.Options.FromBytes(buf.ReadAll()); err != nil {
return err
}
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"net"
"time"
"github.com/u-root/uio/uio"
)
// PrefixOptions are the options valid for use with IAPrefix option field.
//
// RFC 3633 states that it's just the StatusCode option.
//
// RFC 8415 Appendix C does not list the Status Code option as valid, but it
// does say that the previous text in RFC 8415 Section 21.22 supersedes that
// table. Section 21.22 does mention the Status Code option.
type PrefixOptions struct {
Options
}
// Status returns the status code associated with this option.
func (po PrefixOptions) Status() *OptStatusCode {
opt := po.Options.GetOne(OptionStatusCode)
if opt == nil {
return nil
}
sc, ok := opt.(*OptStatusCode)
if !ok {
return nil
}
return sc
}
// OptIAPrefix implements the IAPrefix option.
//
// This module defines the OptIAPrefix structure.
// https://www.ietf.org/rfc/rfc3633.txt
type OptIAPrefix struct {
PreferredLifetime time.Duration
ValidLifetime time.Duration
Prefix *net.IPNet
Options PrefixOptions
}
func (op *OptIAPrefix) Code() OptionCode {
return OptionIAPrefix
}
// ToBytes marshals this option according to RFC 3633, Section 10.
func (op *OptIAPrefix) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
t1 := Duration{op.PreferredLifetime}
t1.Marshal(buf)
t2 := Duration{op.ValidLifetime}
t2.Marshal(buf)
if op.Prefix != nil {
// Even if Mask is nil, Size will return 0 without panicking.
length, _ := op.Prefix.Mask.Size()
buf.Write8(uint8(length))
write16(buf, op.Prefix.IP)
} else {
buf.Write8(0)
write16(buf, nil)
}
buf.WriteBytes(op.Options.ToBytes())
return buf.Data()
}
func (op *OptIAPrefix) String() string {
return fmt.Sprintf("%s: {PreferredLifetime=%v, ValidLifetime=%v, Prefix=%s, Options=%v}",
op.Code(), op.PreferredLifetime, op.ValidLifetime, op.Prefix, op.Options)
}
// FromBytes an OptIAPrefix structure from a sequence of bytes. The input data
// does not include option code and length bytes.
func (op *OptIAPrefix) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
var t1, t2 Duration
t1.Unmarshal(buf)
t2.Unmarshal(buf)
op.PreferredLifetime = t1.Duration
op.ValidLifetime = t2.Duration
length := buf.Read8()
ip := net.IP(buf.CopyN(net.IPv6len))
if length == 0 {
op.Prefix = nil
} else {
op.Prefix = &net.IPNet{
Mask: net.CIDRMask(int(length), 128),
IP: ip,
}
}
if err := op.Options.FromBytes(buf.ReadAll()); err != nil {
return err
}
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"time"
"github.com/u-root/uio/uio"
)
// OptInformationRefreshTime implements OptionInformationRefreshTime option.
// https://tools.ietf.org/html/rfc8415#section-21.23
func OptInformationRefreshTime(irt time.Duration) *optInformationRefreshTime {
return &optInformationRefreshTime{irt}
}
// optInformationRefreshTime represents an OptionInformationRefreshTime.
type optInformationRefreshTime struct {
InformationRefreshtime time.Duration
}
// Code returns the option's code
func (op *optInformationRefreshTime) Code() OptionCode {
return OptionInformationRefreshTime
}
// ToBytes serializes the option and returns it as a sequence of bytes
func (op *optInformationRefreshTime) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
irt := Duration{op.InformationRefreshtime}
irt.Marshal(buf)
return buf.Data()
}
func (op *optInformationRefreshTime) String() string {
return fmt.Sprintf("%s: %v", op.Code(), op.InformationRefreshtime)
}
// FromBytes builds an optInformationRefreshTime structure from a sequence of
// bytes. The input data does not include option code and length bytes.
func (op *optInformationRefreshTime) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
var irt Duration
irt.Unmarshal(buf)
op.InformationRefreshtime = irt.Duration
return buf.FinError()
}
package dhcpv6
import (
"fmt"
)
// OptInterfaceID returns an interface id option as defined by RFC 3315,
// Section 22.18.
func OptInterfaceID(id []byte) Option {
return &optInterfaceID{ID: id}
}
type optInterfaceID struct {
ID []byte
}
func (*optInterfaceID) Code() OptionCode {
return OptionInterfaceID
}
func (op *optInterfaceID) ToBytes() []byte {
return op.ID
}
func (op *optInterfaceID) String() string {
return fmt.Sprintf("%s: %v", op.Code(), op.ID)
}
// FromBytes builds an optInterfaceID structure from a sequence of bytes. The
// input data does not include option code and length bytes.
func (op *optInterfaceID) FromBytes(data []byte) error {
op.ID = append([]byte(nil), data...)
return nil
}
package dhcpv6
import (
"fmt"
"github.com/u-root/uio/uio"
)
// NetworkInterfaceType is the NIC type as defined by RFC 4578 Section 2.2
type NetworkInterfaceType uint8
// see rfc4578
const (
NII_LANDESK_NOPXE NetworkInterfaceType = 0
NII_PXE_GEN_I NetworkInterfaceType = 1
NII_PXE_GEN_II NetworkInterfaceType = 2
NII_UNDI_NOEFI NetworkInterfaceType = 3
NII_UNDI_EFI_GEN_I NetworkInterfaceType = 4
NII_UNDI_EFI_GEN_II NetworkInterfaceType = 5
)
func (nit NetworkInterfaceType) String() string {
if s, ok := niiToStringMap[nit]; ok {
return s
}
return fmt.Sprintf("NetworkInterfaceType(%d, unknown)", nit)
}
var niiToStringMap = map[NetworkInterfaceType]string{
NII_LANDESK_NOPXE: "LANDesk service agent boot ROMs. No PXE",
NII_PXE_GEN_I: "First gen. PXE boot ROMs",
NII_PXE_GEN_II: "Second gen. PXE boot ROMs",
NII_UNDI_NOEFI: "UNDI 32/64 bit. UEFI drivers, no UEFI runtime",
NII_UNDI_EFI_GEN_I: "UNDI 32/64 bit. UEFI runtime 1st gen",
NII_UNDI_EFI_GEN_II: "UNDI 32/64 bit. UEFI runtime 2nd gen",
}
// OptNetworkInterfaceID implements the NIC ID option for network booting as
// defined by RFC 4578 Section 2.2 and RFC 5970 Section 3.4.
type OptNetworkInterfaceID struct {
Typ NetworkInterfaceType
// Revision number
Major, Minor uint8
}
// Code implements Option.Code.
func (*OptNetworkInterfaceID) Code() OptionCode {
return OptionNII
}
// ToBytes implements Option.ToBytes.
func (op *OptNetworkInterfaceID) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write8(uint8(op.Typ))
buf.Write8(op.Major)
buf.Write8(op.Minor)
return buf.Data()
}
func (op *OptNetworkInterfaceID) String() string {
return fmt.Sprintf("%s: %s (Revision %d.%d)", op.Code(), op.Typ, op.Major, op.Minor)
}
// FromBytes builds an OptNetworkInterfaceID structure from a sequence of
// bytes. The input data does not include option code and length bytes.
func (op *OptNetworkInterfaceID) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
op.Typ = NetworkInterfaceType(buf.Read8())
op.Major = buf.Read8()
op.Minor = buf.Read8()
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"time"
"github.com/u-root/uio/uio"
)
// Duration is a duration as embedded in IA messages (IAPD, IANA, IATA).
type Duration struct {
time.Duration
}
// Marshal encodes the time in uint32 seconds as defined by RFC 3315 for IANA
// messages.
func (d Duration) Marshal(buf *uio.Lexer) {
buf.Write32(uint32(d.Duration.Round(time.Second) / time.Second))
}
// Unmarshal decodes time from uint32 seconds as defined by RFC 3315 for IANA
// messages.
func (d *Duration) Unmarshal(buf *uio.Lexer) {
t := buf.Read32()
d.Duration = time.Duration(t) * time.Second
}
// IdentityOptions implement the options allowed for IA_NA and IA_TA messages.
//
// The allowed options are identified in RFC 3315 Appendix B.
type IdentityOptions struct {
Options
}
// Addresses returns the addresses assigned to the identity.
func (io IdentityOptions) Addresses() []*OptIAAddress {
opts := io.Options.Get(OptionIAAddr)
var iaAddrs []*OptIAAddress
for _, o := range opts {
iaAddrs = append(iaAddrs, o.(*OptIAAddress))
}
return iaAddrs
}
// OneAddress returns one address (of potentially many) assigned to the identity.
func (io IdentityOptions) OneAddress() *OptIAAddress {
a := io.Addresses()
if len(a) == 0 {
return nil
}
return a[0]
}
// Status returns the status code associated with this option.
func (io IdentityOptions) Status() *OptStatusCode {
opt := io.Options.GetOne(OptionStatusCode)
if opt == nil {
return nil
}
sc, ok := opt.(*OptStatusCode)
if !ok {
return nil
}
return sc
}
// OptIANA implements the identity association for non-temporary addresses
// option.
//
// This module defines the OptIANA structure.
// https://www.ietf.org/rfc/rfc3633.txt
type OptIANA struct {
IaId [4]byte
T1 time.Duration
T2 time.Duration
Options IdentityOptions
}
func (op *OptIANA) Code() OptionCode {
return OptionIANA
}
// ToBytes serializes IANA to DHCPv6 bytes.
func (op *OptIANA) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.WriteBytes(op.IaId[:])
t1 := Duration{op.T1}
t1.Marshal(buf)
t2 := Duration{op.T2}
t2.Marshal(buf)
buf.WriteBytes(op.Options.ToBytes())
return buf.Data()
}
func (op *OptIANA) String() string {
return fmt.Sprintf("%s: {IAID=%#x T1=%v T2=%v Options=%v}",
op.Code(), op.IaId, op.T1, op.T2, op.Options)
}
// LongString returns a multi-line string representation of IANA data.
func (op *OptIANA) LongString(indentSpace int) string {
return fmt.Sprintf("%s: IAID=%#x T1=%s T2=%s Options=%s", op.Code(), op.IaId, op.T1, op.T2, op.Options.LongString(indentSpace))
}
// FromBytes builds an OptIANA structure from a sequence of bytes. The
// input data does not include option code and length bytes.
func (op *OptIANA) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
buf.ReadBytes(op.IaId[:])
var t1, t2 Duration
t1.Unmarshal(buf)
t2.Unmarshal(buf)
op.T1 = t1.Duration
op.T2 = t2.Duration
if err := op.Options.FromBytes(buf.ReadAll()); err != nil {
return err
}
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"net"
"github.com/insomniacslk/dhcp/rfc1035label"
"github.com/u-root/uio/uio"
)
// NTPSuboptionSrvAddr is NTP_SUBOPTION_SRV_ADDR according to RFC 5908.
type NTPSuboptionSrvAddr net.IP
// Code returns the suboption code.
func (n *NTPSuboptionSrvAddr) Code() OptionCode {
return NTPSuboptionSrvAddrCode
}
// ToBytes returns the byte serialization of the suboption.
func (n *NTPSuboptionSrvAddr) ToBytes() []byte {
return net.IP(*n).To16()
}
func (n *NTPSuboptionSrvAddr) String() string {
return fmt.Sprintf("Server Address: %s", net.IP(*n).String())
}
// FromBytes parses NTP server address from a byte slice p.
func (n *NTPSuboptionSrvAddr) FromBytes(p []byte) error {
buf := uio.NewBigEndianBuffer(p)
*n = NTPSuboptionSrvAddr(buf.CopyN(net.IPv6len))
return buf.FinError()
}
// NTPSuboptionMCAddr is NTP_SUBOPTION_MC_ADDR according to RFC 5908.
type NTPSuboptionMCAddr net.IP
// Code returns the suboption code.
func (n *NTPSuboptionMCAddr) Code() OptionCode {
return NTPSuboptionMCAddrCode
}
// ToBytes returns the byte serialization of the suboption.
func (n *NTPSuboptionMCAddr) ToBytes() []byte {
return net.IP(*n).To16()
}
func (n *NTPSuboptionMCAddr) String() string {
return fmt.Sprintf("Multicast Address: %s", net.IP(*n).String())
}
// FromBytes parses NTP multicast address from a byte slice p.
func (n *NTPSuboptionMCAddr) FromBytes(p []byte) error {
buf := uio.NewBigEndianBuffer(p)
*n = NTPSuboptionMCAddr(buf.CopyN(net.IPv6len))
return buf.FinError()
}
// NTPSuboptionSrvFQDN is NTP_SUBOPTION_SRV_FQDN according to RFC 5908.
type NTPSuboptionSrvFQDN struct {
rfc1035label.Labels
}
// Code returns the suboption code.
func (n *NTPSuboptionSrvFQDN) Code() OptionCode {
return NTPSuboptionSrvFQDNCode
}
// ToBytes returns the byte serialization of the suboption.
func (n *NTPSuboptionSrvFQDN) ToBytes() []byte {
return n.Labels.ToBytes()
}
func (n *NTPSuboptionSrvFQDN) String() string {
return fmt.Sprintf("Server FQDN: %s", n.Labels.String())
}
// FromBytes parses an NTP server FQDN from a byte slice p.
func (n *NTPSuboptionSrvFQDN) FromBytes(p []byte) error {
return n.Labels.FromBytes(p)
}
// NTPSuboptionSrvAddr is the value of NTP_SUBOPTION_SRV_ADDR according to RFC 5908.
const (
NTPSuboptionSrvAddrCode = OptionCode(1)
NTPSuboptionMCAddrCode = OptionCode(2)
NTPSuboptionSrvFQDNCode = OptionCode(3)
)
// parseNTPSuboption implements the OptionParser interface.
func parseNTPSuboption(code OptionCode, data []byte) (Option, error) {
var o Option
switch code {
case NTPSuboptionSrvAddrCode:
o = &NTPSuboptionSrvAddr{}
case NTPSuboptionMCAddrCode:
o = &NTPSuboptionMCAddr{}
case NTPSuboptionSrvFQDNCode:
o = &NTPSuboptionSrvFQDN{}
default:
o = &OptionGeneric{OptionCode: code}
}
return o, o.FromBytes(data)
}
// OptNTPServer is an option NTP server as defined by RFC 5908.
type OptNTPServer struct {
Suboptions Options
}
// Code returns the option code
func (op *OptNTPServer) Code() OptionCode {
return OptionNTPServer
}
// FromBytes parses a sequence of bytes into an OptNTPServer object.
func (op *OptNTPServer) FromBytes(data []byte) error {
return op.Suboptions.FromBytesWithParser(data, parseNTPSuboption)
}
// ToBytes returns the option serialized to bytes.
func (op *OptNTPServer) ToBytes() []byte {
return op.Suboptions.ToBytes()
}
func (op *OptNTPServer) String() string {
return fmt.Sprintf("NTP: %v", op.Suboptions)
}
package dhcpv6
// This module defines the optRelayMsg structure.
// https://www.ietf.org/rfc/rfc3315.txt
import (
"fmt"
)
// OptRelayMessage embeds a message in a relay option.
func OptRelayMessage(msg DHCPv6) Option {
return &optRelayMsg{Msg: msg}
}
type optRelayMsg struct {
Msg DHCPv6
}
func (op *optRelayMsg) Code() OptionCode {
return OptionRelayMsg
}
func (op *optRelayMsg) ToBytes() []byte {
return op.Msg.ToBytes()
}
func (op *optRelayMsg) String() string {
return fmt.Sprintf("%s: %v", op.Code(), op.Msg)
}
// LongString returns a multi-line string representation of the relay message data.
func (op *optRelayMsg) LongString(indent int) string {
return fmt.Sprintf("%s: %v", op.Code(), op.Msg.LongString(indent))
}
// FromBytes build an optRelayMsg structure from a sequence of bytes. The input
// data does not include option code and length bytes.
func (op *optRelayMsg) FromBytes(data []byte) error {
var err error
op.Msg, err = FromBytes(data)
return err
}
// This module defines the optRelayPort structure.
// https://www.ietf.org/rfc/rfc8357.txt
package dhcpv6
import (
"fmt"
"github.com/u-root/uio/uio"
)
// OptRelayPort specifies an UDP port to use for the downstream relay
func OptRelayPort(port uint16) Option {
return &optRelayPort{DownstreamSourcePort: port}
}
type optRelayPort struct {
DownstreamSourcePort uint16
}
func (op *optRelayPort) Code() OptionCode {
return OptionRelayPort
}
func (op *optRelayPort) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write16(op.DownstreamSourcePort)
return buf.Data()
}
func (op *optRelayPort) String() string {
return fmt.Sprintf("%s: %d", op.Code(), op.DownstreamSourcePort)
}
// FromBytes build an optRelayPort structure from a sequence of bytes. The
// input data does not include option code and length bytes.
func (op *optRelayPort) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
op.DownstreamSourcePort = buf.Read16()
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"github.com/u-root/uio/uio"
)
// OptRemoteID implemens the Remote ID option as defined by RFC 4649.
type OptRemoteID struct {
EnterpriseNumber uint32
RemoteID []byte
}
// Code implements Option.Code.
func (*OptRemoteID) Code() OptionCode {
return OptionRemoteID
}
// ToBytes serializes this option to a byte stream.
func (op *OptRemoteID) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write32(op.EnterpriseNumber)
buf.WriteBytes(op.RemoteID)
return buf.Data()
}
func (op *OptRemoteID) String() string {
return fmt.Sprintf("%s: {EnterpriseNumber=%d RemoteID=%#x}",
op.Code(), op.EnterpriseNumber, op.RemoteID,
)
}
// FromBytes builds an OptRemoteID structure from a sequence of bytes. The
// input data does not include option code and length bytes.
func (op *OptRemoteID) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
op.EnterpriseNumber = buf.Read32()
op.RemoteID = buf.ReadAll()
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"strings"
"github.com/u-root/uio/uio"
)
// OptionCodes are a collection of option codes.
type OptionCodes []OptionCode
// Add adds an option to the list, ignoring duplicates.
func (o *OptionCodes) Add(c OptionCode) {
if !o.Contains(c) {
*o = append(*o, c)
}
}
// Contains returns whether the option codes contain c.
func (o OptionCodes) Contains(c OptionCode) bool {
for _, oo := range o {
if oo == c {
return true
}
}
return false
}
// ToBytes implements Option.ToBytes.
func (o OptionCodes) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, ro := range o {
buf.Write16(uint16(ro))
}
return buf.Data()
}
func (o OptionCodes) String() string {
names := make([]string, 0, len(o))
for _, code := range o {
names = append(names, code.String())
}
return strings.Join(names, ", ")
}
// FromBytes populates o from binary-encoded data.
func (o *OptionCodes) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
for buf.Has(2) {
o.Add(OptionCode(buf.Read16()))
}
return buf.FinError()
}
// OptRequestedOption implements the requested options option as defined by RFC
// 3315 Section 22.7.
func OptRequestedOption(o ...OptionCode) Option {
return &optRequestedOption{
OptionCodes: o,
}
}
type optRequestedOption struct {
OptionCodes
}
// Code implements Option.Code.
func (*optRequestedOption) Code() OptionCode {
return OptionORO
}
func (op *optRequestedOption) String() string {
return fmt.Sprintf("%s: %s", op.Code(), op.OptionCodes)
}
package dhcpv6
import (
"fmt"
)
// OptServerID represents a Server Identifier option as defined by RFC 3315
// Section 22.1.
func OptServerID(d DUID) Option {
return &optServerID{d}
}
type optServerID struct {
DUID
}
func (*optServerID) Code() OptionCode {
return OptionServerID
}
func (op *optServerID) String() string {
return fmt.Sprintf("%s: %v", op.Code(), op.DUID)
}
// FromBytes builds an optServerID structure from a sequence of bytes. The
// input data does not include option code and length bytes.
func (op *optServerID) FromBytes(data []byte) error {
var err error
op.DUID, err = DUIDFromBytes(data)
return err
}
package dhcpv6
import (
"fmt"
"github.com/insomniacslk/dhcp/iana"
"github.com/u-root/uio/uio"
)
// OptStatusCode represents a DHCPv6 Status Code option
//
// This module defines the OptStatusCode structure.
// https://www.ietf.org/rfc/rfc3315.txt
type OptStatusCode struct {
StatusCode iana.StatusCode
StatusMessage string
}
// Code returns the option code.
func (op *OptStatusCode) Code() OptionCode {
return OptionStatusCode
}
// ToBytes serializes the option and returns it as a sequence of bytes.
func (op *OptStatusCode) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write16(uint16(op.StatusCode))
buf.WriteBytes([]byte(op.StatusMessage))
return buf.Data()
}
// String returns a human-readable option.
func (op *OptStatusCode) String() string {
return fmt.Sprintf("%s: {Code=%s (%d); Message=%s}",
op.Code(), op.StatusCode, op.StatusCode, op.StatusMessage)
}
// FromBytes builds an OptStatusCode structure from a sequence of bytes. The
// input data does not include option code and length bytes.
func (op *OptStatusCode) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
op.StatusCode = iana.StatusCode(buf.Read16())
op.StatusMessage = string(buf.ReadAll())
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"github.com/u-root/uio/uio"
)
// OptIATA implements the identity association for non-temporary addresses
// option.
//
// This module defines the OptIATA structure, as defined by RFC 8415 Section
// 21.5.
type OptIATA struct {
IaId [4]byte
Options IdentityOptions
}
// Code returns the option code for an IA_TA
func (op *OptIATA) Code() OptionCode {
return OptionIATA
}
// ToBytes serializes IATA to DHCPv6 bytes.
func (op *OptIATA) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.WriteBytes(op.IaId[:])
buf.WriteBytes(op.Options.ToBytes())
return buf.Data()
}
func (op *OptIATA) String() string {
return fmt.Sprintf("%s: {IAID=%#x, Options=%v}", op.Code(), op.IaId, op.Options)
}
// LongString returns a multi-line string representation of IATA data.
func (op *OptIATA) LongString(indentSpace int) string {
return fmt.Sprintf("%s: IAID=%#x Options=%v", op.Code(), op.IaId, op.Options.LongString(indentSpace))
}
// FromBytes builds an OptIATA structure from a sequence of bytes. The input
// data does not include option code and length bytes.
func (op *OptIATA) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
buf.ReadBytes(op.IaId[:])
if err := op.Options.FromBytes(buf.ReadAll()); err != nil {
return err
}
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"strings"
"github.com/u-root/uio/uio"
)
// OptUserClass represent a DHCPv6 User Class option
//
// This module defines the OptUserClass structure.
// https://www.ietf.org/rfc/rfc3315.txt
type OptUserClass struct {
UserClasses [][]byte
}
// Code returns the option code
func (op *OptUserClass) Code() OptionCode {
return OptionUserClass
}
// ToBytes serializes the option and returns it as a sequence of bytes
func (op *OptUserClass) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, uc := range op.UserClasses {
buf.Write16(uint16(len(uc)))
buf.WriteBytes(uc)
}
return buf.Data()
}
func (op *OptUserClass) String() string {
ucStrings := make([]string, 0, len(op.UserClasses))
for _, uc := range op.UserClasses {
ucStrings = append(ucStrings, string(uc))
}
return fmt.Sprintf("%s: [%s]", op.Code(), strings.Join(ucStrings, ", "))
}
// FromBytes builds an OptUserClass structure from a sequence of bytes. The
// input data does not include option code and length bytes.
func (op *OptUserClass) FromBytes(data []byte) error {
if len(data) == 0 {
return fmt.Errorf("%w: user class option must not be empty", uio.ErrBufferTooShort)
}
buf := uio.NewBigEndianBuffer(data)
for buf.Has(2) {
len := buf.Read16()
op.UserClasses = append(op.UserClasses, buf.CopyN(int(len)))
}
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"github.com/u-root/uio/uio"
)
// OptVendorOpts represents a DHCPv6 Status Code option
//
// This module defines the OptVendorOpts structure.
// https://tools.ietf.org/html/rfc3315#section-22.17
type OptVendorOpts struct {
EnterpriseNumber uint32
VendorOpts Options
}
// Code returns the option code
func (op *OptVendorOpts) Code() OptionCode {
return OptionVendorOpts
}
// ToBytes serializes the option and returns it as a sequence of bytes
func (op *OptVendorOpts) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write32(op.EnterpriseNumber)
buf.WriteData(op.VendorOpts.ToBytes())
return buf.Data()
}
// String returns a string representation of the VendorOpts data
func (op *OptVendorOpts) String() string {
return fmt.Sprintf("%s: {EnterpriseNumber=%v VendorOptions=%v}", op.Code(), op.EnterpriseNumber, op.VendorOpts)
}
// LongString returns a string representation of the VendorOpts data
func (op *OptVendorOpts) LongString(indent int) string {
return fmt.Sprintf("%s: EnterpriseNumber=%v VendorOptions=%s", op.Code(), op.EnterpriseNumber, op.VendorOpts.LongString(indent))
}
// FromBytes builds an OptVendorOpts structure from a sequence of bytes. The
// input data does not include option code and length bytes.
func (op *OptVendorOpts) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
op.EnterpriseNumber = buf.Read32()
if err := op.VendorOpts.FromBytesWithParser(buf.ReadAll(), vendParseOption); err != nil {
return err
}
return buf.FinError()
}
// vendParseOption builds a GenericOption from a slice of bytes
// We cannot use the existing ParseOption function in options.go because the
// sub-options include codes specific to each vendor. There are overlaps in these
// codes with RFC standard codes.
func vendParseOption(code OptionCode, data []byte) (Option, error) {
return &OptionGeneric{OptionCode: code, OptionData: data}, nil
}
package dhcpv6
import (
"fmt"
"strings"
"github.com/u-root/uio/uio"
)
// OptVendorClass represents a DHCPv6 Vendor Class option
type OptVendorClass struct {
EnterpriseNumber uint32
Data [][]byte
}
// Code returns the option code
func (op *OptVendorClass) Code() OptionCode {
return OptionVendorClass
}
// ToBytes serializes the option and returns it as a sequence of bytes
func (op *OptVendorClass) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write32(op.EnterpriseNumber)
for _, data := range op.Data {
buf.Write16(uint16(len(data)))
buf.WriteBytes(data)
}
return buf.Data()
}
// String returns a string representation of the VendorClass data
func (op *OptVendorClass) String() string {
vcStrings := make([]string, 0)
for _, data := range op.Data {
vcStrings = append(vcStrings, string(data))
}
return fmt.Sprintf("%s: {EnterpriseNumber=%d Data=[%s]}", op.Code(), op.EnterpriseNumber, strings.Join(vcStrings, ", "))
}
// FromBytes builds an OptVendorClass structure from a sequence of bytes. The
// input data does not include option code and length bytes.
func (op *OptVendorClass) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
*op = OptVendorClass{}
op.EnterpriseNumber = buf.Read32()
for buf.Has(2) {
len := buf.Read16()
op.Data = append(op.Data, buf.CopyN(int(len)))
}
if len(op.Data) == 0 {
return fmt.Errorf("%w: vendor class data should not be empty", uio.ErrBufferTooShort)
}
return buf.FinError()
}
package dhcpv6
import (
"fmt"
"strings"
"github.com/u-root/uio/uio"
)
// Option is an interface that all DHCPv6 options adhere to.
type Option interface {
Code() OptionCode
ToBytes() []byte
String() string
FromBytes([]byte) error
}
type OptionGeneric struct {
OptionCode OptionCode
OptionData []byte
}
func (og *OptionGeneric) Code() OptionCode {
return og.OptionCode
}
func (og *OptionGeneric) ToBytes() []byte {
return og.OptionData
}
func (og *OptionGeneric) String() string {
if len(og.OptionData) == 0 {
return og.OptionCode.String()
}
return fmt.Sprintf("%s: %v", og.OptionCode, og.OptionData)
}
// FromBytes resets OptionData to p.
func (og *OptionGeneric) FromBytes(p []byte) error {
og.OptionData = append([]byte(nil), p...)
return nil
}
// ParseOption parses data according to the given code.
//
// Parse a sequence of bytes as a single DHCPv6 option.
// Returns the option structure, or an error if any.
func ParseOption(code OptionCode, optData []byte) (Option, error) {
var opt Option
switch code {
case OptionClientID:
opt = &optClientID{}
case OptionServerID:
opt = &optServerID{}
case OptionIANA:
opt = &OptIANA{}
case OptionIATA:
opt = &OptIATA{}
case OptionIAAddr:
opt = &OptIAAddress{}
case OptionORO:
opt = &optRequestedOption{}
case OptionElapsedTime:
opt = &optElapsedTime{}
case OptionRelayMsg:
opt = &optRelayMsg{}
case OptionStatusCode:
opt = &OptStatusCode{}
case OptionUserClass:
opt = &OptUserClass{}
case OptionVendorClass:
opt = &OptVendorClass{}
case OptionVendorOpts:
opt = &OptVendorOpts{}
case OptionInterfaceID:
opt = &optInterfaceID{}
case OptionDNSRecursiveNameServer:
opt = &optDNS{}
case OptionDomainSearchList:
opt = &optDomainSearchList{}
case OptionIAPD:
opt = &OptIAPD{}
case OptionIAPrefix:
opt = &OptIAPrefix{}
case OptionInformationRefreshTime:
opt = &optInformationRefreshTime{}
case OptionRemoteID:
opt = &OptRemoteID{}
case OptionFQDN:
opt = &OptFQDN{}
case OptionNTPServer:
opt = &OptNTPServer{}
case OptionBootfileURL:
opt = &optBootFileURL{}
case OptionBootfileParam:
opt = &optBootFileParam{}
case OptionClientArchType:
opt = &optClientArchType{}
case OptionNII:
opt = &OptNetworkInterfaceID{}
case OptionClientLinkLayerAddr:
opt = &optClientLinkLayerAddress{}
case OptionDHCPv4Msg:
opt = &OptDHCPv4Msg{}
case OptionDHCP4oDHCP6Server:
opt = &OptDHCP4oDHCP6Server{}
case Option4RD:
opt = &Opt4RD{}
case Option4RDMapRule:
opt = &Opt4RDMapRule{}
case Option4RDNonMapRule:
opt = &Opt4RDNonMapRule{}
case OptionRelayPort:
opt = &optRelayPort{}
default:
opt = &OptionGeneric{OptionCode: code}
}
return opt, opt.FromBytes(optData)
}
type longStringer interface {
LongString(spaceIndent int) string
}
// Options is a collection of options.
type Options []Option
// LongString prints options with indentation of at least spaceIndent spaces.
func (o Options) LongString(spaceIndent int) string {
indent := strings.Repeat(" ", spaceIndent)
var s strings.Builder
if len(o) == 0 {
s.WriteString("[]")
} else {
s.WriteString("[\n")
for _, opt := range o {
s.WriteString(indent)
s.WriteString(" ")
if ls, ok := opt.(longStringer); ok {
s.WriteString(ls.LongString(spaceIndent + 2))
} else {
s.WriteString(opt.String())
}
s.WriteString("\n")
}
s.WriteString(indent)
s.WriteString("]")
}
return s.String()
}
// Get returns all options matching the option code.
func (o Options) Get(code OptionCode) []Option {
var ret []Option
for _, opt := range o {
if opt.Code() == code {
ret = append(ret, opt)
}
}
return ret
}
// GetOne returns the first option matching the option code.
func (o Options) GetOne(code OptionCode) Option {
for _, opt := range o {
if opt.Code() == code {
return opt
}
}
return nil
}
// Add appends one option.
func (o *Options) Add(option Option) {
*o = append(*o, option)
}
// Del deletes all options matching the option code.
func (o *Options) Del(code OptionCode) {
newOpts := make(Options, 0, len(*o))
for _, opt := range *o {
if opt.Code() != code {
newOpts = append(newOpts, opt)
}
}
*o = newOpts
}
// Update replaces the first option of the same type as the specified one.
func (o *Options) Update(option Option) {
for idx, opt := range *o {
if opt.Code() == option.Code() {
(*o)[idx] = option
// don't look further
return
}
}
// if not found, add it
o.Add(option)
}
// ToBytes marshals all options to bytes.
func (o Options) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, opt := range o {
buf.Write16(uint16(opt.Code()))
val := opt.ToBytes()
buf.Write16(uint16(len(val)))
buf.WriteBytes(val)
}
return buf.Data()
}
// FromBytes reads data into o and returns an error if the options are not a
// valid serialized representation of DHCPv6 options per RFC 3315.
func (o *Options) FromBytes(data []byte) error {
return o.FromBytesWithParser(data, ParseOption)
}
// OptionParser is a function signature for option parsing
type OptionParser func(code OptionCode, data []byte) (Option, error)
// FromBytesWithParser parses Options from byte sequences using the parsing
// function that is passed in as a paremeter
func (o *Options) FromBytesWithParser(data []byte, parser OptionParser) error {
if *o == nil {
*o = make(Options, 0, 10)
}
if len(data) == 0 {
// no options, no party
return nil
}
buf := uio.NewBigEndianBuffer(data)
for buf.Has(4) {
code := OptionCode(buf.Read16())
length := int(buf.Read16())
// Consume, but do not Copy. Each parser will make a copy of
// pertinent data.
optData := buf.Consume(length)
opt, err := parser(code, optData)
if err != nil {
return err
}
*o = append(*o, opt)
}
return buf.FinError()
}
package dhcpv6
import (
"fmt"
)
// TransactionID is a DHCPv6 Transaction ID defined by RFC 3315, Section 6.
type TransactionID [3]byte
// String prints the transaction ID as a hex value.
func (xid TransactionID) String() string {
return fmt.Sprintf("0x%x", xid[:])
}
// MessageType represents the kind of DHCPv6 message.
type MessageType uint8
// The DHCPv6 message types defined per RFC 3315, Section 5.3.
const (
// MessageTypeNone is used internally and is not part of the RFC.
MessageTypeNone MessageType = 0
MessageTypeSolicit MessageType = 1
MessageTypeAdvertise MessageType = 2
MessageTypeRequest MessageType = 3
MessageTypeConfirm MessageType = 4
MessageTypeRenew MessageType = 5
MessageTypeRebind MessageType = 6
MessageTypeReply MessageType = 7
MessageTypeRelease MessageType = 8
MessageTypeDecline MessageType = 9
MessageTypeReconfigure MessageType = 10
MessageTypeInformationRequest MessageType = 11
MessageTypeRelayForward MessageType = 12
MessageTypeRelayReply MessageType = 13
MessageTypeLeaseQuery MessageType = 14
MessageTypeLeaseQueryReply MessageType = 15
MessageTypeLeaseQueryDone MessageType = 16
MessageTypeLeaseQueryData MessageType = 17
_ MessageType = 18
_ MessageType = 19
MessageTypeDHCPv4Query MessageType = 20
MessageTypeDHCPv4Response MessageType = 21
)
// String prints the message type name.
func (m MessageType) String() string {
if s, ok := messageTypeToStringMap[m]; ok {
return s
}
return fmt.Sprintf("unknown (%d)", m)
}
// messageTypeToStringMap contains the mapping of MessageTypes to
// human-readable strings.
var messageTypeToStringMap = map[MessageType]string{
MessageTypeSolicit: "SOLICIT",
MessageTypeAdvertise: "ADVERTISE",
MessageTypeRequest: "REQUEST",
MessageTypeConfirm: "CONFIRM",
MessageTypeRenew: "RENEW",
MessageTypeRebind: "REBIND",
MessageTypeReply: "REPLY",
MessageTypeRelease: "RELEASE",
MessageTypeDecline: "DECLINE",
MessageTypeReconfigure: "RECONFIGURE",
MessageTypeInformationRequest: "INFORMATION-REQUEST",
MessageTypeRelayForward: "RELAY-FORW",
MessageTypeRelayReply: "RELAY-REPL",
MessageTypeLeaseQuery: "LEASEQUERY",
MessageTypeLeaseQueryReply: "LEASEQUERY-REPLY",
MessageTypeLeaseQueryDone: "LEASEQUERY-DONE",
MessageTypeLeaseQueryData: "LEASEQUERY-DATA",
MessageTypeDHCPv4Query: "DHCPv4-QUERY",
MessageTypeDHCPv4Response: "DHCPv4-RESPONSE",
}
// OptionCode is a single byte representing the code for a given Option.
type OptionCode uint16
// String returns the option code name.
func (o OptionCode) String() string {
if s, ok := optionCodeToString[o]; ok {
return s
}
return fmt.Sprintf("unknown (%d)", o)
}
// All DHCPv6 options.
const (
OptionClientID OptionCode = 1
OptionServerID OptionCode = 2
OptionIANA OptionCode = 3
OptionIATA OptionCode = 4
OptionIAAddr OptionCode = 5
OptionORO OptionCode = 6
OptionPreference OptionCode = 7
OptionElapsedTime OptionCode = 8
OptionRelayMsg OptionCode = 9
_ OptionCode = 10
OptionAuth OptionCode = 11
OptionUnicast OptionCode = 12
OptionStatusCode OptionCode = 13
OptionRapidCommit OptionCode = 14
OptionUserClass OptionCode = 15
OptionVendorClass OptionCode = 16
OptionVendorOpts OptionCode = 17
OptionInterfaceID OptionCode = 18
OptionReconfMessage OptionCode = 19
OptionReconfAccept OptionCode = 20
OptionSIPServersDomainNameList OptionCode = 21
OptionSIPServersIPv6AddressList OptionCode = 22
OptionDNSRecursiveNameServer OptionCode = 23
OptionDomainSearchList OptionCode = 24
OptionIAPD OptionCode = 25
OptionIAPrefix OptionCode = 26
OptionNISServers OptionCode = 27
OptionNISPServers OptionCode = 28
OptionNISDomainName OptionCode = 29
OptionNISPDomainName OptionCode = 30
OptionSNTPServerList OptionCode = 31
OptionInformationRefreshTime OptionCode = 32
OptionBCMCSControllerDomainNameList OptionCode = 33
OptionBCMCSControllerIPv6AddressList OptionCode = 34
_ OptionCode = 35
OptionGeoConfCivic OptionCode = 36
OptionRemoteID OptionCode = 37
OptionRelayAgentSubscriberID OptionCode = 38
OptionFQDN OptionCode = 39
OptionPANAAuthenticationAgent OptionCode = 40
OptionNewPOSIXTimezone OptionCode = 41
OptionNewTZDBTimezone OptionCode = 42
OptionEchoRequest OptionCode = 43
OptionLQQuery OptionCode = 44
OptionClientData OptionCode = 45
OptionCLTTime OptionCode = 46
OptionLQRelayData OptionCode = 47
OptionLQClientLink OptionCode = 48
OptionMIPv6HomeNetworkIDFQDN OptionCode = 49
OptionMIPv6VisitedHomeNetworkInformation OptionCode = 50
OptionLoSTServer OptionCode = 51
OptionCAPWAPAccessControllerAddresses OptionCode = 52
OptionRelayID OptionCode = 53
OptionIPv6AddressMOS OptionCode = 54
OptionIPv6FQDNMOS OptionCode = 55
OptionNTPServer OptionCode = 56
OptionV6AccessDomain OptionCode = 57
OptionSIPUACSList OptionCode = 58
OptionBootfileURL OptionCode = 59
OptionBootfileParam OptionCode = 60
OptionClientArchType OptionCode = 61
OptionNII OptionCode = 62
OptionGeolocation OptionCode = 63
OptionAFTRName OptionCode = 64
OptionERPLocalDomainName OptionCode = 65
OptionRSOO OptionCode = 66
OptionPDExclude OptionCode = 67
OptionVirtualSubnetSelection OptionCode = 68
OptionMIPv6IdentifiedHomeNetworkInformation OptionCode = 69
OptionMIPv6UnrestrictedHomeNetworkInformation OptionCode = 70
OptionMIPv6HomeNetworkPrefix OptionCode = 71
OptionMIPv6HomeAgentAddress OptionCode = 72
OptionMIPv6HomeAgentFQDN OptionCode = 73
OptionRDNSSSelection OptionCode = 74
OptionKRBPrincipalName OptionCode = 75
OptionKRBRealmName OptionCode = 76
OptionKRBDefaultRealmName OptionCode = 77
OptionKRBKDC OptionCode = 78
OptionClientLinkLayerAddr OptionCode = 79
OptionLinkAddress OptionCode = 80
OptionRadius OptionCode = 81
OptionSolMaxRT OptionCode = 82
OptionInfMaxRT OptionCode = 83
OptionAddrSel OptionCode = 84
OptionAddrSelTable OptionCode = 85
OptionV6PCPServer OptionCode = 86
OptionDHCPv4Msg OptionCode = 87
OptionDHCP4oDHCP6Server OptionCode = 88
OptionS46Rule OptionCode = 89
OptionS46BR OptionCode = 90
OptionS46DMR OptionCode = 91
OptionS46V4V6Bind OptionCode = 92
OptionS46PortParams OptionCode = 93
OptionS46ContMapE OptionCode = 94
OptionS46ContMapT OptionCode = 95
OptionS46ContLW OptionCode = 96
Option4RD OptionCode = 97
Option4RDMapRule OptionCode = 98
Option4RDNonMapRule OptionCode = 99
OptionLQBaseTime OptionCode = 100
OptionLQStartTime OptionCode = 101
OptionLQEndTime OptionCode = 102
OptionCaptivePortal OptionCode = 103
OptionMPLParameters OptionCode = 104
OptionANIAccessTechType OptionCode = 105
OptionANINetworkName OptionCode = 106
OptionANIAccessPointName OptionCode = 107
OptionANIAccessPointBSSID OptionCode = 108
OptionANIOperatorID OptionCode = 109
OptionANIOperatorRealm OptionCode = 110
OptionS46Priority OptionCode = 111
OptionMUDUrlV6 OptionCode = 112
OptionV6Prefix64 OptionCode = 113
OptionFailoverBindingStatus OptionCode = 114
OptionFailoverConnectFlags OptionCode = 115
OptionFailoverDNSRemovalInfo OptionCode = 116
OptionFailoverDNSHostName OptionCode = 117
OptionFailoverDNSZoneName OptionCode = 118
OptionFailoverDNSFlags OptionCode = 119
OptionFailoverExpirationTime OptionCode = 120
OptionFailoverMaxUnackedBNDUPD OptionCode = 121
OptionFailoverMCLT OptionCode = 122
OptionFailoverPartnerLifetime OptionCode = 123
OptionFailoverPartnerLifetimeSent OptionCode = 124
OptionFailoverPartnerDownTime OptionCode = 125
OptionFailoverPartnerRawCLTTime OptionCode = 126
OptionFailoverProtocolVersion OptionCode = 127
OptionFailoverKeepaliveTime OptionCode = 128
OptionFailoverReconfigureData OptionCode = 129
OptionFailoverRelationshipName OptionCode = 130
OptionFailoverServerFlags OptionCode = 131
OptionFailoverServerState OptionCode = 132
OptionFailoverStartTimeOfState OptionCode = 133
OptionFailoverStateExpirationTime OptionCode = 134
OptionRelayPort OptionCode = 135
OptionV6SZTPRedirect OptionCode = 136
OptionS46BindIPv6Prefix OptionCode = 137
_ OptionCode = 138
_ OptionCode = 139
_ OptionCode = 140
_ OptionCode = 141
_ OptionCode = 142
OptionIPv6AddressANDSF OptionCode = 143
)
// optionCodeToString maps DHCPv6 OptionCodes to human-readable strings.
var optionCodeToString = map[OptionCode]string{
OptionClientID: "Client ID",
OptionServerID: "Server ID",
OptionIANA: "IANA",
OptionIATA: "IATA",
OptionIAAddr: "IA IP Address",
OptionORO: "Requested Options",
OptionPreference: "Preference",
OptionElapsedTime: "Elapsed Time",
OptionRelayMsg: "Relay Message",
OptionAuth: "Auth",
OptionUnicast: "Unicast",
OptionStatusCode: "Status Code",
OptionRapidCommit: "Rapid Commit",
OptionUserClass: "User Class",
OptionVendorClass: "Vendor Class",
OptionVendorOpts: "Vendor Options",
OptionInterfaceID: "Interface ID",
OptionReconfMessage: "Reconfig Message",
OptionReconfAccept: "Reconfig Accept",
OptionSIPServersDomainNameList: "SIP Servers Domain Name List",
OptionSIPServersIPv6AddressList: "SIP Servers IPv6 Address List",
OptionDNSRecursiveNameServer: "DNS",
OptionDomainSearchList: "Domain Search List",
OptionIAPD: "IAPD",
OptionIAPrefix: "IA Prefix",
OptionNISServers: "NIS Servers",
OptionNISPServers: "NISP Servers",
OptionNISDomainName: "NIS Domain Name",
OptionNISPDomainName: "NISP Domain Name",
OptionSNTPServerList: "SNTP Server List",
OptionInformationRefreshTime: "Information Refresh Time",
OptionBCMCSControllerDomainNameList: "BCMCS Controller Domain Name List",
OptionBCMCSControllerIPv6AddressList: "BCMCS Controller IPv6 Address List",
OptionGeoConfCivic: "Geoconf",
OptionRemoteID: "Remote ID",
OptionRelayAgentSubscriberID: "Relay-Agent Subscriber ID",
OptionFQDN: "FQDN",
OptionPANAAuthenticationAgent: "PANA Authentication Agent",
OptionNewPOSIXTimezone: "New POSIX Timezone",
OptionNewTZDBTimezone: "New TZDB Timezone",
OptionEchoRequest: "Echo Request",
OptionLQQuery: "OPTION_LQ_QUERY",
OptionClientData: "OPTION_CLIENT_DATA",
OptionCLTTime: "OPTION_CLT_TIME",
OptionLQRelayData: "OPTION_LQ_RELAY_DATA",
OptionLQClientLink: "OPTION_LQ_CLIENT_LINK",
OptionMIPv6HomeNetworkIDFQDN: "MIPv6 Home Network ID FQDN",
OptionMIPv6VisitedHomeNetworkInformation: "MIPv6 Visited Home Network Information",
OptionLoSTServer: "LoST Server",
OptionCAPWAPAccessControllerAddresses: "CAPWAP Access Controller Addresses",
OptionRelayID: "Relay ID",
OptionIPv6AddressMOS: "OPTION-IPv6_Address-MoS",
OptionIPv6FQDNMOS: "OPTION-IPv6-FQDN-MoS",
OptionNTPServer: "NTP Server",
OptionV6AccessDomain: "OPTION_V6_ACCESS_DOMAIN",
OptionSIPUACSList: "OPTION_SIP_UA_CS_LIST",
OptionBootfileURL: "Boot File URL",
OptionBootfileParam: "Boot File Parameters",
OptionClientArchType: "Client Architecture",
OptionNII: "Network Interface ID",
OptionGeolocation: "OPTION_GEOLOCATION",
OptionAFTRName: "OPTION_AFTR_NAME",
OptionERPLocalDomainName: "OPTION_ERP_LOCAL_DOMAIN_NAME",
OptionRSOO: "OPTION_RSOO",
OptionPDExclude: "OPTION_PD_EXCLUDE",
OptionVirtualSubnetSelection: "Virtual Subnet Selection",
OptionMIPv6IdentifiedHomeNetworkInformation: "MIPv6 Identified Home Network Information",
OptionMIPv6UnrestrictedHomeNetworkInformation: "MIPv6 Unrestricted Home Network Information",
OptionMIPv6HomeNetworkPrefix: "MIPv6 Home Network Prefix",
OptionMIPv6HomeAgentAddress: "MIPv6 Home Agent Address",
OptionMIPv6HomeAgentFQDN: "MIPv6 Home Agent FQDN",
OptionRDNSSSelection: "RDNSS Selection",
OptionKRBPrincipalName: "Kerberos Principal Name",
OptionKRBRealmName: "Kerberos Realm Name",
OptionKRBDefaultRealmName: "Kerberos Default Realm Name",
OptionKRBKDC: "Kerberos KDC",
OptionClientLinkLayerAddr: "Client Link-Layer Address",
OptionLinkAddress: "Link Address",
OptionRadius: "OPTION_RADIUS",
OptionSolMaxRT: "Max Solicit Timeout Value",
OptionInfMaxRT: "Max Information-Request Timeout Value",
OptionAddrSel: "Address Selection",
OptionAddrSelTable: "Address Selection Policy Table",
OptionV6PCPServer: "Port Control Protocol Server",
OptionDHCPv4Msg: "Encapsulated DHCPv4 Message",
OptionDHCP4oDHCP6Server: "DHCPv4-over-DHCPv6 Server",
OptionS46Rule: "Softwire46 Rule",
OptionS46BR: "Softwire46 Border Relay",
OptionS46DMR: "Softwire46 Default Mapping Rule",
OptionS46V4V6Bind: "Softwire46 IPv4/IPv6 Address Binding",
OptionS46PortParams: "Softwire46 Port Parameters",
OptionS46ContMapE: "Softwire46 MAP-E Container",
OptionS46ContMapT: "Softwire46 MAP-T Container",
OptionS46ContLW: "Softwire46 Lightweight 4over6 Container",
Option4RD: "4RD",
Option4RDMapRule: "4RD Mapping Rule",
Option4RDNonMapRule: "4RD Non-Mapping Rule",
OptionLQBaseTime: "Leasequery Server Base time",
OptionLQStartTime: "Leasequery Server Query Start Time",
OptionLQEndTime: "Leasequery Server Query End Time",
OptionCaptivePortal: "Captive Portal URI",
OptionMPLParameters: "MPL Parameters",
OptionANIAccessTechType: "Access-Network-Information Access-Technology-Type",
OptionANINetworkName: "Access-Network-Information Network-Name",
OptionANIAccessPointName: "Access-Network-Information Access-Point-Name",
OptionANIAccessPointBSSID: "Access-Network-Information Access-Point-BSSID",
OptionANIOperatorID: "Access-Network-Information Operator-Identifier",
OptionANIOperatorRealm: "Access-Network-Information Operator-Realm",
OptionS46Priority: "Softwire46 Priority",
OptionMUDUrlV6: "Manufacturer Usage Description URL",
OptionV6Prefix64: "OPTION_V6_PREFIX64",
OptionFailoverBindingStatus: "Failover Binding Status",
OptionFailoverConnectFlags: "Failover Connection Flags",
OptionFailoverDNSRemovalInfo: "Failover DNS Removal Info",
OptionFailoverDNSHostName: "Failover DNS Removal Host Name",
OptionFailoverDNSZoneName: "Failover DNS Removal Zone Name",
OptionFailoverDNSFlags: "Failover DNS Removal Flags",
OptionFailoverExpirationTime: "Failover Maximum Expiration Time",
OptionFailoverMaxUnackedBNDUPD: "Failover Maximum Unacked BNDUPD Messages",
OptionFailoverMCLT: "Failover Maximum Client Lead Time",
OptionFailoverPartnerLifetime: "Failover Partner Lifetime",
OptionFailoverPartnerLifetimeSent: "Failover Received Partner Lifetime",
OptionFailoverPartnerDownTime: "Failover Last Partner Down Time",
OptionFailoverPartnerRawCLTTime: "Failover Last Client Time",
OptionFailoverProtocolVersion: "Failover Protocol Version",
OptionFailoverKeepaliveTime: "Failover Keepalive Time",
OptionFailoverReconfigureData: "Failover Reconfigure Data",
OptionFailoverRelationshipName: "Failover Relationship Name",
OptionFailoverServerFlags: "Failover Server Flags",
OptionFailoverServerState: "Failover Server State",
OptionFailoverStartTimeOfState: "Failover State Start Time",
OptionFailoverStateExpirationTime: "Failover State Expiration Time",
OptionRelayPort: "Relay Source Port",
OptionV6SZTPRedirect: "IPv6 Secure Zerotouch Provisioning Redirect",
OptionS46BindIPv6Prefix: "Softwire46 Source Binding Prefix Hint",
OptionIPv6AddressANDSF: "IPv6 Access Network Discovery and Selection Function Address",
}
package rfc1035label
import (
"errors"
"fmt"
"strings"
)
// Labels represents RFC1035 labels
//
// This implements RFC 1035 labels, including compression.
// https://tools.ietf.org/html/rfc1035#section-4.1.4
type Labels struct {
// original contains the original bytes if the object was parsed from a byte
// sequence, or nil otherwise. The `original` field is necessary to deal
// with compressed labels. If the labels are further modified, the original
// content is invalidated and no compression will be used.
original []byte
// Labels contains the parsed labels. A change here invalidates the
// `original` object.
Labels []string
}
// same compares two string arrays
func same(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}
// String prints labels.
func (l *Labels) String() string {
return fmt.Sprintf("%v", l.Labels)
}
// ToBytes returns a byte sequence representing the labels. If the original
// sequence is modified, the labels are parsed again, otherwise the original
// byte sequence is returned.
func (l *Labels) ToBytes() []byte {
// if the original byte sequence has been modified, invalidate it and
// serialize again.
// NOTE: this function is not thread-safe. If multiple threads modify
// the `Labels` field, the result may be wrong.
originalLabels, err := labelsFromBytes(l.original)
// if the original object has not been modified, or we cannot parse it,
// return the original bytes.
if err != nil || (l.original != nil && same(originalLabels, l.Labels)) {
return l.original
}
return labelsToBytes(l.Labels)
}
// Length returns the length in bytes of the serialized labels
func (l *Labels) Length() int {
return len(l.ToBytes())
}
// NewLabels returns an initialized Labels object.
func NewLabels() *Labels {
return &Labels{
Labels: make([]string, 0),
}
}
// FromBytes reads labels from a bytes stream according to RFC 1035.
func (l *Labels) FromBytes(data []byte) error {
labs, err := labelsFromBytes(data)
if err != nil {
return err
}
l.original = data
l.Labels = labs
return nil
}
// FromBytes returns a Labels object from the given byte sequence, or an error if
// any.
func FromBytes(data []byte) (*Labels, error) {
var l Labels
if err := l.FromBytes(data); err != nil {
return nil, err
}
return &l, nil
}
// ErrBufferTooShort is returned when the label cannot be parsed due to a wrong
// length or missing bytes.
var ErrBufferTooShort = errors.New("rfc1035label: buffer too short")
// fromBytes decodes a serialized stream and returns a list of labels
func labelsFromBytes(buf []byte) ([]string, error) {
var (
labels = make([]string, 0)
pos, oldPos int
label string
handlingPointer bool
)
for {
if pos >= len(buf) {
// interpret label without trailing zero-length byte as a partial
// domain name field as per RFC 4704 Section 4.2
if label != "" {
labels = append(labels, label)
}
break
}
length := int(buf[pos])
pos++
var chunk string
if length == 0 {
labels = append(labels, label)
label = ""
if handlingPointer {
pos = oldPos
handlingPointer = false
}
} else if length&0xc0 == 0xc0 {
// compression pointer
if handlingPointer {
return nil, errors.New("rfc1035label: cannot handle nested pointers")
}
handlingPointer = true
if pos+1 > len(buf) {
return nil, errors.New("rfc1035label: pointer buffer too short")
}
off := int(buf[pos-1]&^0xc0)<<8 + int(buf[pos])
oldPos = pos + 1
pos = off
} else {
if pos+length > len(buf) {
return nil, ErrBufferTooShort
}
chunk = string(buf[pos : pos+length])
if label != "" {
label += "."
}
label += chunk
pos += length
}
}
return labels, nil
}
// labelToBytes encodes a label and returns a serialized stream of bytes
func labelToBytes(label string) []byte {
var encodedLabel []byte
if len(label) == 0 {
return []byte{0}
}
for _, part := range strings.Split(label, ".") {
encodedLabel = append(encodedLabel, byte(len(part)))
encodedLabel = append(encodedLabel, []byte(part)...)
}
return append(encodedLabel, 0)
}
// labelsToBytes encodes a list of labels and returns a serialized stream of
// bytes
func labelsToBytes(labels []string) []byte {
var encodedLabels []byte
for _, label := range labels {
encodedLabels = append(encodedLabels, labelToBytes(label)...)
}
return encodedLabels
}