diff options
-rw-r--r-- | dhcpv4/dhcpv4.go | 7 | ||||
-rw-r--r-- | dhcpv4/nclient4/client.go | 63 | ||||
-rw-r--r-- | dhcpv4/option_string.go | 5 |
3 files changed, 56 insertions, 19 deletions
diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go index 4316c5e..c09e76a 100644 --- a/dhcpv4/dhcpv4.go +++ b/dhcpv4/dhcpv4.go @@ -735,6 +735,13 @@ func (d *DHCPv4) MessageType() MessageType { 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. diff --git a/dhcpv4/nclient4/client.go b/dhcpv4/nclient4/client.go index 83ed065..41c7a73 100644 --- a/dhcpv4/nclient4/client.go +++ b/dhcpv4/nclient4/client.go @@ -399,12 +399,18 @@ func WithServerAddr(n *net.UDPAddr) ClientOpt { // Matcher matches DHCP packets. type Matcher func(*dhcpv4.DHCPv4) bool -// IsMessageType returns a matcher that checks for the message type. -// -// If t is MessageTypeNone, all packets are matched. -func IsMessageType(t dhcpv4.MessageType) Matcher { +// 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 { - return p.MessageType() == t || t == dhcpv4.MessageTypeNone + if p.MessageType() == t { + return true + } + for _, mt := range tt { + if p.MessageType() == mt { + return true + } + } + return false } } @@ -416,17 +422,14 @@ func (c *Client) DiscoverOffer(ctx context.Context, modifiers ...dhcpv4.Modifier discover, err := dhcpv4.NewDiscovery(c.ifaceHWAddr, dhcpv4.PrependModifiers(modifiers, dhcpv4.WithOption(dhcpv4.OptMaxMessageSize(MaxMessageSize)))...) if err != nil { - err = fmt.Errorf("unable to create a discovery request: %w", err) - return + 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 { - err = fmt.Errorf("got an error while the discovery request: %w", err) - return + return nil, fmt.Errorf("got an error while the discovery request: %w", err) } - - return + return offer, nil } // Request completes the 4-way Discover-Offer-Request-Ack handshake. @@ -438,25 +441,47 @@ func (c *Client) Request(ctx context.Context, modifiers ...dhcpv4.Modifier) (lea err = fmt.Errorf("unable to receive an offer: %w", err) return } + return c.RequestFromOffer(ctx, offer, modifiers...) +} +// 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. +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 { - err = fmt.Errorf("unable to create a request: %w", err) - return + return nil, fmt.Errorf("unable to create a request: %w", err) } - ack, err := c.SendAndRead(ctx, c.serverAddr, request, nil) + response, err := c.SendAndRead(ctx, c.serverAddr, request, IsMessageType(dhcpv4.MessageTypeAck, dhcpv4.MessageTypeNak)) if err != nil { - err = fmt.Errorf("got an error while processing the request: %w", err) - return + 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 = ack + lease := &Lease{} + lease.ACK = response lease.Offer = offer lease.CreationTime = time.Now() - return + return lease, nil } // ErrTransactionIDInUse is returned if there were an attempt to send a message diff --git a/dhcpv4/option_string.go b/dhcpv4/option_string.go index 289319b..eb0cc2b 100644 --- a/dhcpv4/option_string.go +++ b/dhcpv4/option_string.go @@ -77,3 +77,8 @@ func OptClassIdentifier(name string) 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)} +} |