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:

committed by
Casey Callendrello

parent
e4950728ce
commit
d61e7e5e1f
669
vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go
generated
vendored
Normal file
669
vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/client.go
generated
vendored
Normal 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
|
||||
}
|
154
vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/conn_unix.go
generated
vendored
Normal file
154
vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/conn_unix.go
generated
vendored
Normal 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})
|
||||
}
|
360
vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/ipv4.go
generated
vendored
Normal file
360
vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/ipv4.go
generated
vendored
Normal 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()
|
||||
}
|
79
vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/lease.go
generated
vendored
Normal file
79
vendor/github.com/insomniacslk/dhcp/dhcpv4/nclient4/lease.go
generated
vendored
Normal 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
|
||||
}
|
Reference in New Issue
Block a user