diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go index c9a05f14..57ce93aa 100644 --- a/dhcpv4/dhcpv4.go +++ b/dhcpv4/dhcpv4.go @@ -1,82 +1,28 @@ +//go:build !tinygo + // 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})) +// 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() +// // Retrieve the DHCP Message Type option. +// m := p.MessageType() // -// bytesOnTheWire := p.ToBytes() -// longSummary := p.Summary() +// 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) { @@ -90,98 +36,6 @@ func IPv4AddrsForInterface(iface *net.Interface) ([]net.IP, error) { 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. @@ -193,21 +47,6 @@ func NewDiscoveryForInterface(ifname string, modifiers ...Modifier) (*DHCPv4, er 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. @@ -235,651 +74,3 @@ func NewInformForInterface(ifname string, needsBroadcast bool) (*DHCPv4, error) } 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 -} diff --git a/dhcpv4/dhcpv4packet.go b/dhcpv4/dhcpv4packet.go new file mode 100644 index 00000000..4ecbd591 --- /dev/null +++ b/dhcpv4/dhcpv4packet.go @@ -0,0 +1,833 @@ +// 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) + +// 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 +} + +// 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), + )...) +} + +// 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 +}