Songmin Li d61e7e5e1f fix(dhcp): can not renew an ip address
The dhcp server is systemd-networkd, and the dhcp
plugin can request an ip but can not renew it.
The systemd-networkd just ignore the renew request.

```
2024/09/14 21:46:00 no DHCP packet received within 10s
2024/09/14 21:46:00 retrying in 31.529038 seconds
2024/09/14 21:46:42 no DHCP packet received within 10s
2024/09/14 21:46:42 retrying in 63.150490 seconds
2024/09/14 21:47:45 98184616c91f15419f5cacd012697f85afaa2daeb5d3233e28b0ec21589fb45a/iot/eth1: no more tries
2024/09/14 21:47:45 98184616c91f15419f5cacd012697f85afaa2daeb5d3233e28b0ec21589fb45a/iot/eth1: renewal time expired, rebinding
2024/09/14 21:47:45 Link "eth1" down. Attempting to set up
2024/09/14 21:47:45 98184616c91f15419f5cacd012697f85afaa2daeb5d3233e28b0ec21589fb45a/iot/eth1: lease rebound, expiration is 2024-09-14 22:47:45.309270751 +0800 CST m=+11730.048516519
```

Follow the https://datatracker.ietf.org/doc/html/rfc2131#section-4.3.6,
following options must not be sent in renew

- Requested IP Address
- Server Identifier

Since the upstream code has been inactive for 6 years,
we should switch to another dhcpv4 library.
The new selected one is https://github.com/insomniacslk/dhcp.

Signed-off-by: Songmin Li <lisongmin@protonmail.com>
2024-10-14 17:42:30 +02:00

857 lines
24 KiB
Go

// 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) {
var xid TransactionID
ctx, cancel := context.WithTimeout(context.Background(), 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
}
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, nil
}
// 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)
}
// 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
}