summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--dhcpv4/dhcpv4.go7
-rw-r--r--dhcpv4/nclient4/client.go63
-rw-r--r--dhcpv4/option_string.go5
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)}
+}