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>
This commit is contained in:
Songmin Li
2024-09-14 22:00:20 +08:00
committed by Casey Callendrello
parent e4950728ce
commit d61e7e5e1f
310 changed files with 12785 additions and 11182 deletions

View File

@ -0,0 +1,10 @@
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)
}

View File

@ -0,0 +1,6 @@
package dhcpv4
const (
ServerPort = 67
ClientPort = 68
)

856
vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go generated vendored Normal file
View File

@ -0,0 +1,856 @@
// 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
}

176
vendor/github.com/insomniacslk/dhcp/dhcpv4/modifiers.go generated vendored Normal file
View File

@ -0,0 +1,176 @@
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))
}

View File

@ -0,0 +1,669 @@
// Copyright 2018 the u-root Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.12
// Package nclient4 is a small, minimum-functionality client for DHCPv4.
//
// It only supports the 4-way DHCPv4 Discover-Offer-Request-Ack handshake as
// well as the Request-Ack renewal process.
package nclient4
import (
"bytes"
"context"
"errors"
"fmt"
"log"
"net"
"os"
"sync"
"sync/atomic"
"time"
"github.com/insomniacslk/dhcp/dhcpv4"
)
const (
defaultBufferCap = 5
// DefaultTimeout is the default value for read-timeout if option WithTimeout is not set
DefaultTimeout = 5 * time.Second
// DefaultRetries is amount of retries will be done if no answer was received within read-timeout amount of time
DefaultRetries = 3
// MaxMessageSize is the value to be used for DHCP option "MaxMessageSize".
MaxMessageSize = 1500
// ClientPort is the port that DHCP clients listen on.
ClientPort = 68
// ServerPort is the port that DHCP servers and relay agents listen on.
ServerPort = 67
)
var (
// DefaultServers is the address of all link-local DHCP servers and
// relay agents.
DefaultServers = &net.UDPAddr{
IP: net.IPv4bcast,
Port: ServerPort,
}
)
var (
// ErrNoResponse is returned when no response packet is received.
ErrNoResponse = errors.New("no matching response packet received")
// ErrNoConn is returned when NewWithConn is called with nil-value as conn.
ErrNoConn = errors.New("conn is nil")
// ErrNoIfaceHWAddr is returned when NewWithConn is called with nil-value as ifaceHWAddr
ErrNoIfaceHWAddr = errors.New("ifaceHWAddr is nil")
)
// pendingCh is a channel associated with a pending TransactionID.
type pendingCh struct {
// SendAndRead closes done to indicate that it wishes for no more
// messages for this particular XID.
done <-chan struct{}
// ch is used by the receive loop to distribute DHCP messages.
ch chan<- *dhcpv4.DHCPv4
}
// Logger is a handler which will be used to output logging messages
type Logger interface {
// PrintMessage print _all_ DHCP messages
PrintMessage(prefix string, message *dhcpv4.DHCPv4)
// Printf is use to print the rest debugging information
Printf(format string, v ...interface{})
}
// EmptyLogger prints nothing
type EmptyLogger struct{}
// Printf is just a dummy function that does nothing
func (e EmptyLogger) Printf(format string, v ...interface{}) {}
// PrintMessage is just a dummy function that does nothing
func (e EmptyLogger) PrintMessage(prefix string, message *dhcpv4.DHCPv4) {}
// Printfer is used for actual output of the logger. For example *log.Logger is a Printfer.
type Printfer interface {
// Printf is the function for logging output. Arguments are handled in the manner of fmt.Printf.
Printf(format string, v ...interface{})
}
// ShortSummaryLogger is a wrapper for Printfer to implement interface Logger.
// DHCP messages are printed in the short format.
type ShortSummaryLogger struct {
// Printfer is used for actual output of the logger
Printfer
}
// Printf prints a log message as-is via predefined Printfer
func (s ShortSummaryLogger) Printf(format string, v ...interface{}) {
s.Printfer.Printf(format, v...)
}
// PrintMessage prints a DHCP message in the short format via predefined Printfer
func (s ShortSummaryLogger) PrintMessage(prefix string, message *dhcpv4.DHCPv4) {
s.Printf("%s: %s", prefix, message)
}
// DebugLogger is a wrapper for Printfer to implement interface Logger.
// DHCP messages are printed in the long format.
type DebugLogger struct {
// Printfer is used for actual output of the logger
Printfer
}
// Printf prints a log message as-is via predefined Printfer
func (d DebugLogger) Printf(format string, v ...interface{}) {
d.Printfer.Printf(format, v...)
}
// PrintMessage prints a DHCP message in the long format via predefined Printfer
func (d DebugLogger) PrintMessage(prefix string, message *dhcpv4.DHCPv4) {
d.Printf("%s: %s", prefix, message.Summary())
}
// Client is an IPv4 DHCP client.
type Client struct {
ifaceHWAddr net.HardwareAddr
conn net.PacketConn
timeout time.Duration
retry int
logger Logger
// bufferCap is the channel capacity for each TransactionID.
bufferCap int
// serverAddr is the UDP address to send all packets to.
//
// This may be an actual broadcast address, or a unicast address.
serverAddr *net.UDPAddr
// closed is an atomic bool set to 1 when done is closed.
closed uint32
// done is closed to unblock the receive loop.
done chan struct{}
// wg protects any spawned goroutines, namely the receiveLoop.
wg sync.WaitGroup
pendingMu sync.Mutex
// pending stores the distribution channels for each pending
// TransactionID. receiveLoop uses this map to determine which channel
// to send a new DHCP message to.
pending map[dhcpv4.TransactionID]*pendingCh
}
// New returns a client usable with an unconfigured interface.
func New(iface string, opts ...ClientOpt) (*Client, error) {
return new(iface, nil, nil, opts...)
}
// NewWithConn creates a new DHCP client that sends and receives packets on the
// given interface.
func NewWithConn(conn net.PacketConn, ifaceHWAddr net.HardwareAddr, opts ...ClientOpt) (*Client, error) {
return new(``, conn, ifaceHWAddr, opts...)
}
func new(iface string, conn net.PacketConn, ifaceHWAddr net.HardwareAddr, opts ...ClientOpt) (*Client, error) {
c := &Client{
ifaceHWAddr: ifaceHWAddr,
timeout: DefaultTimeout,
retry: DefaultRetries,
serverAddr: DefaultServers,
bufferCap: defaultBufferCap,
conn: conn,
logger: EmptyLogger{},
done: make(chan struct{}),
pending: make(map[dhcpv4.TransactionID]*pendingCh),
}
for _, opt := range opts {
err := opt(c)
if err != nil {
return nil, fmt.Errorf("unable to apply option: %w", err)
}
}
if c.ifaceHWAddr == nil {
if iface == `` {
return nil, ErrNoIfaceHWAddr
}
i, err := net.InterfaceByName(iface)
if err != nil {
return nil, fmt.Errorf("unable to get interface information: %w", err)
}
c.ifaceHWAddr = i.HardwareAddr
}
if c.conn == nil {
var err error
if iface == `` {
return nil, ErrNoConn
}
c.conn, err = NewRawUDPConn(iface, ClientPort) // broadcast
if err != nil {
return nil, fmt.Errorf("unable to open a broadcasting socket: %w", err)
}
}
c.wg.Add(1)
go c.receiveLoop()
return c, nil
}
// Close closes the underlying connection.
func (c *Client) Close() error {
// Make sure not to close done twice.
if !atomic.CompareAndSwapUint32(&c.closed, 0, 1) {
return nil
}
err := c.conn.Close()
// Closing c.done sets off a chain reaction:
//
// Any SendAndRead unblocks trying to receive more messages, which
// means rem() gets called.
//
// rem() should be unblocking receiveLoop if it is blocked.
//
// receiveLoop should then exit gracefully.
close(c.done)
// Wait for receiveLoop to stop.
c.wg.Wait()
return err
}
func (c *Client) isClosed() bool {
return atomic.LoadUint32(&c.closed) != 0
}
func (c *Client) receiveLoop() {
defer c.wg.Done()
for {
// TODO: Clients can send a "max packet size" option in their
// packets, IIRC. Choose a reasonable size and set it.
b := make([]byte, MaxMessageSize)
n, _, err := c.conn.ReadFrom(b)
if err != nil {
if !c.isClosed() {
c.logger.Printf("error reading from UDP connection: %v", err)
}
return
}
msg, err := dhcpv4.FromBytes(b[:n])
if err != nil {
// Not a valid DHCP packet; keep listening.
continue
}
if msg.OpCode != dhcpv4.OpcodeBootReply {
// Not a response message.
continue
}
// This is a somewhat non-standard check, by the looks
// of RFC 2131. It should work as long as the DHCP
// server is spec-compliant for the HWAddr field.
if c.ifaceHWAddr != nil && !bytes.Equal(c.ifaceHWAddr, msg.ClientHWAddr) {
// Not for us.
continue
}
c.pendingMu.Lock()
p, ok := c.pending[msg.TransactionID]
if ok {
select {
case <-p.done:
close(p.ch)
delete(c.pending, msg.TransactionID)
// This send may block.
case p.ch <- msg:
}
}
c.pendingMu.Unlock()
}
}
// ClientOpt is a function that configures the Client.
type ClientOpt func(c *Client) error
// WithTimeout configures the retransmission timeout.
//
// Default is 5 seconds.
func WithTimeout(d time.Duration) ClientOpt {
return func(c *Client) (err error) {
c.timeout = d
return
}
}
// WithSummaryLogger logs one-line DHCPv4 message summaries when sent & received.
func WithSummaryLogger() ClientOpt {
return func(c *Client) (err error) {
c.logger = ShortSummaryLogger{
Printfer: log.New(os.Stderr, "[dhcpv4] ", log.LstdFlags),
}
return
}
}
// WithDebugLogger logs multi-line full DHCPv4 messages when sent & received.
func WithDebugLogger() ClientOpt {
return func(c *Client) (err error) {
c.logger = DebugLogger{
Printfer: log.New(os.Stderr, "[dhcpv4] ", log.LstdFlags),
}
return
}
}
// WithLogger set the logger (see interface Logger).
func WithLogger(newLogger Logger) ClientOpt {
return func(c *Client) (err error) {
c.logger = newLogger
return
}
}
// WithUnicast forces client to send messages as unicast frames.
// By default client sends messages as broadcast frames even if server address is defined.
//
// srcAddr is both:
// * The source address of outgoing frames.
// * The address to be listened for incoming frames.
func WithUnicast(srcAddr *net.UDPAddr) ClientOpt {
return func(c *Client) (err error) {
if srcAddr == nil {
srcAddr = &net.UDPAddr{Port: ClientPort}
}
c.conn, err = net.ListenUDP("udp4", srcAddr)
if err != nil {
err = fmt.Errorf("unable to start listening UDP port: %w", err)
}
return
}
}
// WithHWAddr tells to the Client to receive messages destinated to selected
// hardware address
func WithHWAddr(hwAddr net.HardwareAddr) ClientOpt {
return func(c *Client) (err error) {
c.ifaceHWAddr = hwAddr
return
}
}
func withBufferCap(n int) ClientOpt {
return func(c *Client) (err error) {
c.bufferCap = n
return
}
}
// WithRetry configures the number of retransmissions to attempt.
//
// Default is 3.
func WithRetry(r int) ClientOpt {
return func(c *Client) (err error) {
c.retry = r
return
}
}
// WithServerAddr configures the address to send messages to.
func WithServerAddr(n *net.UDPAddr) ClientOpt {
return func(c *Client) (err error) {
c.serverAddr = n
return
}
}
// Matcher matches DHCP packets.
type Matcher func(*dhcpv4.DHCPv4) bool
// IsMessageType returns a matcher that checks for the message types.
func IsMessageType(t dhcpv4.MessageType, tt ...dhcpv4.MessageType) Matcher {
return func(p *dhcpv4.DHCPv4) bool {
if p.MessageType() == t {
return true
}
for _, mt := range tt {
if p.MessageType() == mt {
return true
}
}
return false
}
}
// IsCorrectServer returns a matcher that checks for the correct ServerAddress.
func IsCorrectServer(s net.IP) Matcher {
return func(p *dhcpv4.DHCPv4) bool {
return p.ServerIdentifier().Equal(s)
}
}
// IsAll returns a matcher that checks for all given matchers to be true.
func IsAll(ms ...Matcher) Matcher {
return func(p *dhcpv4.DHCPv4) bool {
for _, m := range ms {
if !m(p) {
return false
}
}
return true
}
}
// RemoteAddr is the default DHCP server address this client sends messages to.
func (c *Client) RemoteAddr() *net.UDPAddr {
// Make a copy so the caller cannot modify the address once the client
// is running.
cop := *c.serverAddr
return &cop
}
// InterfaceAddr returns the MAC address of the client's interface.
func (c *Client) InterfaceAddr() net.HardwareAddr {
b := make(net.HardwareAddr, len(c.ifaceHWAddr))
copy(b, c.ifaceHWAddr)
return b
}
// DiscoverOffer sends a DHCPDiscover message and returns the first valid offer
// received.
func (c *Client) DiscoverOffer(ctx context.Context, modifiers ...dhcpv4.Modifier) (offer *dhcpv4.DHCPv4, err error) {
// RFC 2131, Section 4.4.1, Table 5 details what a DISCOVER packet should
// contain.
discover, err := dhcpv4.NewDiscovery(c.ifaceHWAddr, dhcpv4.PrependModifiers(modifiers,
dhcpv4.WithOption(dhcpv4.OptMaxMessageSize(MaxMessageSize)))...)
if err != nil {
return nil, fmt.Errorf("unable to create a discovery request: %w", err)
}
offer, err = c.SendAndRead(ctx, c.serverAddr, discover, IsMessageType(dhcpv4.MessageTypeOffer))
if err != nil {
return nil, fmt.Errorf("got an error while the discovery request: %w", err)
}
return offer, nil
}
// Request completes the 4-way Discover-Offer-Request-Ack handshake.
//
// Note that modifiers will be applied *both* to Discover and Request packets.
func (c *Client) Request(ctx context.Context, modifiers ...dhcpv4.Modifier) (lease *Lease, err error) {
offer, err := c.DiscoverOffer(ctx, modifiers...)
if err != nil {
err = fmt.Errorf("unable to receive an offer: %w", err)
return
}
return c.RequestFromOffer(ctx, offer, modifiers...)
}
// Inform sends an INFORM request using the given local IP.
// Returns the ACK response from the server on success.
func (c *Client) Inform(ctx context.Context, localIP net.IP, modifiers ...dhcpv4.Modifier) (*dhcpv4.DHCPv4, error) {
request, err := dhcpv4.NewInform(c.ifaceHWAddr, localIP, modifiers...)
if err != nil {
return nil, err
}
// DHCP clients must not fill in the server identifier in an INFORM request as per RFC 2131 Section 4.4.1 Table 5,
// however, they may still unicast the request to the target server if the address is known (c.serverAddr), as per
// Section 4.4.3. The server must then respond with an ACK, as per Section 4.3.5.
response, err := c.SendAndRead(ctx, c.serverAddr, request, IsMessageType(dhcpv4.MessageTypeAck))
if err != nil {
return nil, fmt.Errorf("got an error while processing the request: %w", err)
}
return response, nil
}
// ErrNak is returned if a DHCP server rejected our Request.
type ErrNak struct {
Offer *dhcpv4.DHCPv4
Nak *dhcpv4.DHCPv4
}
// Error implements error.Error.
func (e *ErrNak) Error() string {
if msg := e.Nak.Message(); len(msg) > 0 {
return fmt.Sprintf("server rejected request with Nak (msg: %s)", msg)
}
return "server rejected request with Nak"
}
// RequestFromOffer sends a Request message and waits for an response.
// It assumes the SELECTING state by default, see Section 4.3.2 in RFC 2131 for more details.
func (c *Client) RequestFromOffer(ctx context.Context, offer *dhcpv4.DHCPv4, modifiers ...dhcpv4.Modifier) (*Lease, error) {
// TODO(chrisko): should this be unicast to the server?
request, err := dhcpv4.NewRequestFromOffer(offer, dhcpv4.PrependModifiers(modifiers,
dhcpv4.WithOption(dhcpv4.OptMaxMessageSize(MaxMessageSize)))...)
if err != nil {
return nil, fmt.Errorf("unable to create a request: %w", err)
}
// Servers are supposed to only respond to Requests containing their server identifier,
// but sometimes non-compliant servers respond anyway.
// Clients are not required to validate this field, but servers are required to
// include the server identifier in their Offer per RFC 2131 Section 4.3.1 Table 3.
response, err := c.SendAndRead(ctx, c.serverAddr, request, IsAll(
IsCorrectServer(offer.ServerIdentifier()),
IsMessageType(dhcpv4.MessageTypeAck, dhcpv4.MessageTypeNak)))
if err != nil {
return nil, fmt.Errorf("got an error while processing the request: %w", err)
}
if response.MessageType() == dhcpv4.MessageTypeNak {
return nil, &ErrNak{
Offer: offer,
Nak: response,
}
}
lease := &Lease{}
lease.ACK = response
lease.Offer = offer
lease.CreationTime = time.Now()
return lease, nil
}
// ErrTransactionIDInUse is returned if there were an attempt to send a message
// with the same TransactionID as we are already waiting an answer for.
type ErrTransactionIDInUse struct {
// TransactionID is the transaction ID of the message which the error is related to.
TransactionID dhcpv4.TransactionID
}
// Error is just the method to comply interface "error".
func (err *ErrTransactionIDInUse) Error() string {
return fmt.Sprintf("transaction ID %s already in use", err.TransactionID)
}
// send sends p to destination and returns a response channel.
//
// Responses will be matched by transaction ID and ClientHWAddr.
//
// The returned lambda function must be called after all desired responses have
// been received in order to return the Transaction ID to the usable pool.
func (c *Client) send(dest *net.UDPAddr, msg *dhcpv4.DHCPv4) (resp <-chan *dhcpv4.DHCPv4, cancel func(), err error) {
c.pendingMu.Lock()
if _, ok := c.pending[msg.TransactionID]; ok {
c.pendingMu.Unlock()
return nil, nil, &ErrTransactionIDInUse{msg.TransactionID}
}
ch := make(chan *dhcpv4.DHCPv4, c.bufferCap)
done := make(chan struct{})
c.pending[msg.TransactionID] = &pendingCh{done: done, ch: ch}
c.pendingMu.Unlock()
cancel = func() {
// Why can't we just close ch here?
//
// Because receiveLoop may potentially be blocked trying to
// send on ch. We gotta unblock it first, and then we can take
// the lock and remove the XID from the pending transaction
// map.
close(done)
c.pendingMu.Lock()
if p, ok := c.pending[msg.TransactionID]; ok {
close(p.ch)
delete(c.pending, msg.TransactionID)
}
c.pendingMu.Unlock()
}
if _, err := c.conn.WriteTo(msg.ToBytes(), dest); err != nil {
cancel()
return nil, nil, fmt.Errorf("error writing packet to connection: %w", err)
}
return ch, cancel, nil
}
// This error should never be visible to users.
// It is used only to increase the timeout in retryFn.
var errDeadlineExceeded = errors.New("INTERNAL ERROR: deadline exceeded")
// SendAndRead sends a packet p to a destination dest and waits for the first
// response matching `match` as well as its Transaction ID and ClientHWAddr.
//
// If match is nil, the first packet matching the Transaction ID and
// ClientHWAddr is returned.
func (c *Client) SendAndRead(ctx context.Context, dest *net.UDPAddr, p *dhcpv4.DHCPv4, match Matcher) (*dhcpv4.DHCPv4, error) {
var response *dhcpv4.DHCPv4
err := c.retryFn(func(timeout time.Duration) error {
ch, rem, err := c.send(dest, p)
if err != nil {
return err
}
c.logger.PrintMessage("sent message", p)
defer rem()
for {
select {
case <-c.done:
return ErrNoResponse
case <-time.After(timeout):
return errDeadlineExceeded
case <-ctx.Done():
return ctx.Err()
case packet := <-ch:
if match == nil || match(packet) {
c.logger.PrintMessage("received message", packet)
response = packet
return nil
}
}
}
})
if err == errDeadlineExceeded {
return nil, ErrNoResponse
}
if err != nil {
return nil, err
}
return response, nil
}
func (c *Client) retryFn(fn func(timeout time.Duration) error) error {
timeout := c.timeout
// Each retry takes the amount of timeout at worst.
for i := 0; i < c.retry || c.retry < 0; i++ { // TODO: why is this called "retry" if this is "tries" ("retries"+1)?
switch err := fn(timeout); err {
case nil:
// Got it!
return nil
case errDeadlineExceeded:
// Double timeout, then retry.
timeout *= 2
default:
return err
}
}
return errDeadlineExceeded
}

View File

@ -0,0 +1,154 @@
// Copyright 2018 the u-root Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.12 && (darwin || freebsd || linux || netbsd || openbsd || dragonfly)
// +build go1.12
// +build darwin freebsd linux netbsd openbsd dragonfly
package nclient4
import (
"errors"
"io"
"net"
"github.com/mdlayher/packet"
"github.com/u-root/uio/uio"
"golang.org/x/sys/unix"
)
var (
// BroadcastMac is the broadcast MAC address.
//
// Any UDP packet sent to this address is broadcast on the subnet.
BroadcastMac = net.HardwareAddr([]byte{255, 255, 255, 255, 255, 255})
)
var (
// ErrUDPAddrIsRequired is an error used when a passed argument is not of type "*net.UDPAddr".
ErrUDPAddrIsRequired = errors.New("must supply UDPAddr")
)
// NewRawUDPConn returns a UDP connection bound to the interface and port
// given based on a raw packet socket. All packets are broadcasted.
//
// The interface can be completely unconfigured.
func NewRawUDPConn(iface string, port int) (net.PacketConn, error) {
ifc, err := net.InterfaceByName(iface)
if err != nil {
return nil, err
}
rawConn, err := packet.Listen(ifc, packet.Datagram, unix.ETH_P_IP, nil)
if err != nil {
return nil, err
}
return NewBroadcastUDPConn(rawConn, &net.UDPAddr{Port: port}), nil
}
// BroadcastRawUDPConn uses a raw socket to send UDP packets to the broadcast
// MAC address.
type BroadcastRawUDPConn struct {
// PacketConn is a raw DGRAM socket.
net.PacketConn
// boundAddr is the address this RawUDPConn is "bound" to.
//
// Calls to ReadFrom will only return packets destined to this address.
boundAddr *net.UDPAddr
}
// NewBroadcastUDPConn returns a PacketConn that marshals and unmarshals UDP
// packets, sending them to the broadcast MAC at on rawPacketConn.
//
// Calls to ReadFrom will only return packets destined to boundAddr.
func NewBroadcastUDPConn(rawPacketConn net.PacketConn, boundAddr *net.UDPAddr) net.PacketConn {
return &BroadcastRawUDPConn{
PacketConn: rawPacketConn,
boundAddr: boundAddr,
}
}
func udpMatch(addr *net.UDPAddr, bound *net.UDPAddr) bool {
if bound == nil {
return true
}
if bound.IP != nil && !bound.IP.Equal(addr.IP) {
return false
}
return bound.Port == addr.Port
}
// ReadFrom implements net.PacketConn.ReadFrom.
//
// ReadFrom reads raw IP packets and will try to match them against
// upc.boundAddr. Any matching packets are returned via the given buffer.
func (upc *BroadcastRawUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
ipHdrMaxLen := ipv4MaximumHeaderSize
udpHdrLen := udpMinimumSize
for {
pkt := make([]byte, ipHdrMaxLen+udpHdrLen+len(b))
n, _, err := upc.PacketConn.ReadFrom(pkt)
if err != nil {
return 0, nil, err
}
if n == 0 {
return 0, nil, io.EOF
}
pkt = pkt[:n]
buf := uio.NewBigEndianBuffer(pkt)
ipHdr := ipv4(buf.Data())
if !ipHdr.isValid(n) {
continue
}
ipHdr = ipv4(buf.Consume(int(ipHdr.headerLength())))
if ipHdr.transportProtocol() != udpProtocolNumber {
continue
}
if !buf.Has(udpHdrLen) {
continue
}
udpHdr := udp(buf.Consume(udpHdrLen))
addr := &net.UDPAddr{
IP: ipHdr.destinationAddress(),
Port: int(udpHdr.destinationPort()),
}
if !udpMatch(addr, upc.boundAddr) {
continue
}
srcAddr := &net.UDPAddr{
IP: ipHdr.sourceAddress(),
Port: int(udpHdr.sourcePort()),
}
// Extra padding after end of IP packet should be ignored,
// if not dhcp option parsing will fail.
dhcpLen := int(ipHdr.payloadLength()) - udpHdrLen
return copy(b, buf.Consume(dhcpLen)), srcAddr, nil
}
}
// WriteTo implements net.PacketConn.WriteTo and broadcasts all packets at the
// raw socket level.
//
// WriteTo wraps the given packet in the appropriate UDP and IP header before
// sending it on the packet conn.
func (upc *BroadcastRawUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
udpAddr, ok := addr.(*net.UDPAddr)
if !ok {
return 0, ErrUDPAddrIsRequired
}
// Using the boundAddr is not quite right here, but it works.
pkt := udp4pkt(b, udpAddr, upc.boundAddr)
// Broadcasting is not always right, but hell, what the ARP do I know.
return upc.PacketConn.WriteTo(pkt, &packet.Addr{HardwareAddr: BroadcastMac})
}

View File

@ -0,0 +1,360 @@
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This file contains code taken from gVisor.
//go:build go1.12
// +build go1.12
package nclient4
import (
"encoding/binary"
"net"
"github.com/u-root/uio/uio"
)
const (
versIHL = 0
tos = 1
totalLen = 2
id = 4
flagsFO = 6
ttl = 8
protocol = 9
checksumOff = 10
srcAddr = 12
dstAddr = 16
)
// transportProtocolNumber is the number of a transport protocol.
type transportProtocolNumber uint32
// ipv4Fields contains the fields of an IPv4 packet. It is used to describe the
// fields of a packet that needs to be encoded.
type ipv4Fields struct {
// IHL is the "internet header length" field of an IPv4 packet.
IHL uint8
// TOS is the "type of service" field of an IPv4 packet.
TOS uint8
// TotalLength is the "total length" field of an IPv4 packet.
TotalLength uint16
// ID is the "identification" field of an IPv4 packet.
ID uint16
// Flags is the "flags" field of an IPv4 packet.
Flags uint8
// FragmentOffset is the "fragment offset" field of an IPv4 packet.
FragmentOffset uint16
// TTL is the "time to live" field of an IPv4 packet.
TTL uint8
// Protocol is the "protocol" field of an IPv4 packet.
Protocol uint8
// checksum is the "checksum" field of an IPv4 packet.
checksum uint16
// SrcAddr is the "source ip address" of an IPv4 packet.
SrcAddr net.IP
// DstAddr is the "destination ip address" of an IPv4 packet.
DstAddr net.IP
}
// ipv4 represents an ipv4 header stored in a byte array.
// Most of the methods of IPv4 access to the underlying slice without
// checking the boundaries and could panic because of 'index out of range'.
// Always call IsValid() to validate an instance of IPv4 before using other methods.
type ipv4 []byte
const (
// ipv4MinimumSize is the minimum size of a valid IPv4 packet.
ipv4MinimumSize = 20
// ipv4MaximumHeaderSize is the maximum size of an IPv4 header. Given
// that there are only 4 bits to represents the header length in 32-bit
// units, the header cannot exceed 15*4 = 60 bytes.
ipv4MaximumHeaderSize = 60
// ipv4AddressSize is the size, in bytes, of an IPv4 address.
ipv4AddressSize = 4
// IPv4Version is the version of the IPv4 protocol.
ipv4Version = 4
)
// ipVersion returns the version of IP used in the given packet. It returns -1
// if the packet is not large enough to contain the version field.
func ipVersion(b []byte) int {
// Length must be at least offset+length of version field.
if len(b) < versIHL+1 {
return -1
}
return int(b[versIHL] >> ipVersionShift)
}
const (
ipVersionShift = 4
)
// headerLength returns the value of the "header length" field of the ipv4
// header.
func (b ipv4) headerLength() uint8 {
return (b[versIHL] & 0xf) * 4
}
// protocol returns the value of the protocol field of the ipv4 header.
func (b ipv4) protocol() uint8 {
return b[protocol]
}
// sourceAddress returns the "source address" field of the ipv4 header.
func (b ipv4) sourceAddress() net.IP {
return net.IP(b[srcAddr : srcAddr+ipv4AddressSize])
}
// destinationAddress returns the "destination address" field of the ipv4
// header.
func (b ipv4) destinationAddress() net.IP {
return net.IP(b[dstAddr : dstAddr+ipv4AddressSize])
}
// transportProtocol implements Network.transportProtocol.
func (b ipv4) transportProtocol() transportProtocolNumber {
return transportProtocolNumber(b.protocol())
}
// payloadLength returns the length of the payload portion of the ipv4 packet.
func (b ipv4) payloadLength() uint16 {
return b.totalLength() - uint16(b.headerLength())
}
// totalLength returns the "total length" field of the ipv4 header.
func (b ipv4) totalLength() uint16 {
return binary.BigEndian.Uint16(b[totalLen:])
}
// setTotalLength sets the "total length" field of the ipv4 header.
func (b ipv4) setTotalLength(totalLength uint16) {
binary.BigEndian.PutUint16(b[totalLen:], totalLength)
}
// setChecksum sets the checksum field of the ipv4 header.
func (b ipv4) setChecksum(v uint16) {
binary.BigEndian.PutUint16(b[checksumOff:], v)
}
// setFlagsFragmentOffset sets the "flags" and "fragment offset" fields of the
// ipv4 header.
func (b ipv4) setFlagsFragmentOffset(flags uint8, offset uint16) {
v := (uint16(flags) << 13) | (offset >> 3)
binary.BigEndian.PutUint16(b[flagsFO:], v)
}
// calculateChecksum calculates the checksum of the ipv4 header.
func (b ipv4) calculateChecksum() uint16 {
return checksum(b[:b.headerLength()], 0)
}
// encode encodes all the fields of the ipv4 header.
func (b ipv4) encode(i *ipv4Fields) {
b[versIHL] = (4 << 4) | ((i.IHL / 4) & 0xf)
b[tos] = i.TOS
b.setTotalLength(i.TotalLength)
binary.BigEndian.PutUint16(b[id:], i.ID)
b.setFlagsFragmentOffset(i.Flags, i.FragmentOffset)
b[ttl] = i.TTL
b[protocol] = i.Protocol
b.setChecksum(i.checksum)
copy(b[srcAddr:srcAddr+ipv4AddressSize], i.SrcAddr)
copy(b[dstAddr:dstAddr+ipv4AddressSize], i.DstAddr)
}
// isValid performs basic validation on the packet.
func (b ipv4) isValid(pktSize int) bool {
if len(b) < ipv4MinimumSize {
return false
}
hlen := int(b.headerLength())
tlen := int(b.totalLength())
if hlen < ipv4MinimumSize || hlen > tlen || tlen > pktSize {
return false
}
if ipVersion(b) != ipv4Version {
return false
}
return true
}
const (
udpSrcPort = 0
udpDstPort = 2
udpLength = 4
udpchecksum = 6
)
// udpFields contains the fields of a udp packet. It is used to describe the
// fields of a packet that needs to be encoded.
type udpFields struct {
// SrcPort is the "source port" field of a udp packet.
SrcPort uint16
// DstPort is the "destination port" field of a UDP packet.
DstPort uint16
// Length is the "length" field of a UDP packet.
Length uint16
// checksum is the "checksum" field of a UDP packet.
checksum uint16
}
// udp represents a udp header stored in a byte array.
type udp []byte
const (
// udpMinimumSize is the minimum size of a valid udp packet.
udpMinimumSize = 8
// udpProtocolNumber is udp's transport protocol number.
udpProtocolNumber transportProtocolNumber = 17
)
// sourcePort returns the "source port" field of the udp header.
func (b udp) sourcePort() uint16 {
return binary.BigEndian.Uint16(b[udpSrcPort:])
}
// DestinationPort returns the "destination port" field of the udp header.
func (b udp) destinationPort() uint16 {
return binary.BigEndian.Uint16(b[udpDstPort:])
}
// Length returns the "length" field of the udp header.
func (b udp) length() uint16 {
return binary.BigEndian.Uint16(b[udpLength:])
}
// setChecksum sets the "checksum" field of the udp header.
func (b udp) setChecksum(checksum uint16) {
binary.BigEndian.PutUint16(b[udpchecksum:], checksum)
}
// calculateChecksum calculates the checksum of the udp packet, given the total
// length of the packet and the checksum of the network-layer pseudo-header
// (excluding the total length) and the checksum of the payload.
func (b udp) calculateChecksum(partialchecksum uint16, totalLen uint16) uint16 {
// Add the length portion of the checksum to the pseudo-checksum.
tmp := make([]byte, 2)
binary.BigEndian.PutUint16(tmp, totalLen)
xsum := checksum(tmp, partialchecksum)
// Calculate the rest of the checksum.
return checksum(b[:udpMinimumSize], xsum)
}
// encode encodes all the fields of the udp header.
func (b udp) encode(u *udpFields) {
binary.BigEndian.PutUint16(b[udpSrcPort:], u.SrcPort)
binary.BigEndian.PutUint16(b[udpDstPort:], u.DstPort)
binary.BigEndian.PutUint16(b[udpLength:], u.Length)
binary.BigEndian.PutUint16(b[udpchecksum:], u.checksum)
}
func calculateChecksum(buf []byte, initial uint32) uint16 {
v := initial
l := len(buf)
if l&1 != 0 {
l--
v += uint32(buf[l]) << 8
}
for i := 0; i < l; i += 2 {
v += (uint32(buf[i]) << 8) + uint32(buf[i+1])
}
return checksumCombine(uint16(v), uint16(v>>16))
}
// checksum calculates the checksum (as defined in RFC 1071) of the bytes in the
// given byte array.
//
// The initial checksum must have been computed on an even number of bytes.
func checksum(buf []byte, initial uint16) uint16 {
return calculateChecksum(buf, uint32(initial))
}
// checksumCombine combines the two uint16 to form their checksum. This is done
// by adding them and the carry.
//
// Note that checksum a must have been computed on an even number of bytes.
func checksumCombine(a, b uint16) uint16 {
v := uint32(a) + uint32(b)
return uint16(v + v>>16)
}
// pseudoHeaderchecksum calculates the pseudo-header checksum for the
// given destination protocol and network address, ignoring the length
// field. pseudo-headers are needed by transport layers when calculating
// their own checksum.
func pseudoHeaderchecksum(protocol transportProtocolNumber, srcAddr net.IP, dstAddr net.IP) uint16 {
xsum := checksum([]byte(srcAddr), 0)
xsum = checksum([]byte(dstAddr), xsum)
return checksum([]byte{0, uint8(protocol)}, xsum)
}
func udp4pkt(packet []byte, dest *net.UDPAddr, src *net.UDPAddr) []byte {
ipLen := ipv4MinimumSize
udpLen := udpMinimumSize
h := make([]byte, 0, ipLen+udpLen+len(packet))
hdr := uio.NewBigEndianBuffer(h)
ipv4fields := &ipv4Fields{
IHL: ipv4MinimumSize,
TotalLength: uint16(ipLen + udpLen + len(packet)),
TTL: 64, // Per RFC 1700's recommendation for IP time to live
Protocol: uint8(udpProtocolNumber),
SrcAddr: src.IP.To4(),
DstAddr: dest.IP.To4(),
}
ipv4hdr := ipv4(hdr.WriteN(ipLen))
ipv4hdr.encode(ipv4fields)
ipv4hdr.setChecksum(^ipv4hdr.calculateChecksum())
udphdr := udp(hdr.WriteN(udpLen))
udphdr.encode(&udpFields{
SrcPort: uint16(src.Port),
DstPort: uint16(dest.Port),
Length: uint16(udpLen + len(packet)),
})
xsum := checksum(packet, pseudoHeaderchecksum(
ipv4hdr.transportProtocol(), ipv4fields.SrcAddr, ipv4fields.DstAddr))
udphdr.setChecksum(^udphdr.calculateChecksum(xsum, udphdr.length()))
hdr.WriteBytes(packet)
return hdr.Data()
}

View File

@ -0,0 +1,79 @@
// This is lease support for nclient4
package nclient4
import (
"context"
"fmt"
"net"
"time"
"github.com/insomniacslk/dhcp/dhcpv4"
)
// Lease contains a DHCPv4 lease after DORA.
// note: Lease doesn't include binding interface name
type Lease struct {
Offer *dhcpv4.DHCPv4
ACK *dhcpv4.DHCPv4
CreationTime time.Time
}
// Release send DHCPv4 release messsage to server, based on specified lease.
// release is sent as unicast per RFC2131, section 4.4.4.
// Note: some DHCP server requries of using assigned IP address as source IP,
// use nclient4.WithUnicast to create client for such case.
func (c *Client) Release(lease *Lease, modifiers ...dhcpv4.Modifier) error {
if lease == nil {
return fmt.Errorf("lease is nil")
}
req, err := dhcpv4.NewReleaseFromACK(lease.ACK, modifiers...)
if err != nil {
return fmt.Errorf("fail to create release request,%w", err)
}
_, err = c.conn.WriteTo(req.ToBytes(), &net.UDPAddr{IP: lease.ACK.Options.Get(dhcpv4.OptionServerIdentifier), Port: ServerPort})
if err == nil {
c.logger.PrintMessage("sent message:", req)
}
return err
}
// Renew sends a DHCPv4 request to the server to renew the given lease. The renewal information is
// sourced from the initial offer in the lease, and the ACK of the lease is updated to the ACK of
// the latest renewal. This avoids issues with DHCP servers that omit information needed to build a
// completely new lease from their renewal ACK (such as the Windows DHCP Server).
func (c *Client) Renew(ctx context.Context, lease *Lease, modifiers ...dhcpv4.Modifier) (*Lease, error) {
if lease == nil {
return nil, fmt.Errorf("lease is nil")
}
request, err := dhcpv4.NewRenewFromAck(lease.ACK, dhcpv4.PrependModifiers(modifiers,
dhcpv4.WithOption(dhcpv4.OptMaxMessageSize(MaxMessageSize)))...)
if err != nil {
return nil, fmt.Errorf("unable to create a request: %w", err)
}
// Servers are supposed to only respond to Requests containing their server identifier,
// but sometimes non-compliant servers respond anyway.
// Clients are not required to validate this field, but servers are required to
// include the server identifier in their Offer per RFC 2131 Section 4.3.1 Table 3.
response, err := c.SendAndRead(ctx, c.serverAddr, request, IsAll(
IsCorrectServer(lease.Offer.ServerIdentifier()),
IsMessageType(dhcpv4.MessageTypeAck, dhcpv4.MessageTypeNak)))
if err != nil {
return nil, fmt.Errorf("got an error while processing the request: %w", err)
}
if response.MessageType() == dhcpv4.MessageTypeNak {
return nil, &ErrNak{
Offer: lease.Offer,
Nak: response,
}
}
// Return a new lease with the latest ACK and updated creation time
return &Lease{
Offer: lease.Offer,
ACK: response,
CreationTime: time.Now(),
}, nil
}

View File

@ -0,0 +1,61 @@
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}
}

View File

@ -0,0 +1,56 @@
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)}
}

View File

@ -0,0 +1,27 @@
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}}
}

View File

@ -0,0 +1,62 @@
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)}
}

View File

@ -0,0 +1,103 @@
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),
}
}
// 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...))
}

View File

@ -0,0 +1,50 @@
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)}
}

View File

@ -0,0 +1,6 @@
package dhcpv4
// OptMessageType returns a new DHCPv4 Message Type option.
func OptMessageType(m MessageType) Option {
return Option{Code: OptionDHCPMessageType, Value: m}
}

View File

@ -0,0 +1,23 @@
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)
}

View File

@ -0,0 +1,72 @@
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)}
}

View File

@ -0,0 +1,100 @@
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",
}

View File

@ -0,0 +1,104 @@
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),
}
}

View File

@ -0,0 +1,84 @@
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)}
}

View File

@ -0,0 +1,55 @@
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),
}
}

View File

@ -0,0 +1,40 @@
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),
}
}

View File

@ -0,0 +1,65 @@
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]
}

380
vendor/github.com/insomniacslk/dhcp/dhcpv4/options.go generated vendored Normal file
View File

@ -0,0 +1,380 @@
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}
}

467
vendor/github.com/insomniacslk/dhcp/dhcpv4/types.go generated vendored Normal file
View File

@ -0,0 +1,467 @@
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",
}