Michael Cambria 0af31fc4d0 Change dhcp plugin to send ClientID allowing container to have multiple CNI
interfaces using dhcp ipam.

Vendor latest dhcp4server, dhcp4client, dhcp4

Added additional tests for new functionality in dhcp2_test.go

Wrap d2g dhcp4client calls with our own which add clientID to packet.
2018-11-15 11:31:56 -05:00

417 lines
11 KiB
Go

package dhcp4client
import (
"bytes"
"fmt"
"hash/fnv"
"math/rand"
"net"
"sync"
"syscall"
"time"
"github.com/d2g/dhcp4"
)
const (
MaxDHCPLen = 576
)
type Client struct {
hardwareAddr net.HardwareAddr //The HardwareAddr to send in the request.
ignoreServers []net.IP //List of Servers to Ignore requests from.
timeout time.Duration //Time before we timeout.
broadcast bool //Set the Bcast flag in BOOTP Flags
connection ConnectionInt //The Connection Method to use
generateXID func([]byte) //Function Used to Generate a XID
}
//Abstracts the type of underlying socket used
type ConnectionInt interface {
Close() error
Write(packet []byte) error
ReadFrom() ([]byte, net.IP, error)
SetReadTimeout(t time.Duration) error
}
func New(options ...func(*Client) error) (*Client, error) {
c := Client{
timeout: time.Second * 10,
broadcast: true,
}
err := c.SetOption(options...)
if err != nil {
return nil, err
}
if c.generateXID == nil {
// https://tools.ietf.org/html/rfc2131#section-4.1 explains:
//
// A DHCP client MUST choose 'xid's in such a way as to minimize the chance
// of using an 'xid' identical to one used by another client.
//
// Hence, seed a random number generator with the current time and hardware
// address.
h := fnv.New64()
h.Write(c.hardwareAddr)
seed := int64(h.Sum64()) + time.Now().Unix()
rnd := rand.New(rand.NewSource(seed))
var rndMu sync.Mutex
c.generateXID = func(b []byte) {
rndMu.Lock()
defer rndMu.Unlock()
rnd.Read(b)
}
}
//if connection hasn't been set as an option create the default.
if c.connection == nil {
conn, err := NewInetSock()
if err != nil {
return nil, err
}
c.connection = conn
}
return &c, nil
}
func (c *Client) SetOption(options ...func(*Client) error) error {
for _, opt := range options {
if err := opt(c); err != nil {
return err
}
}
return nil
}
func Timeout(t time.Duration) func(*Client) error {
return func(c *Client) error {
c.timeout = t
return nil
}
}
func IgnoreServers(s []net.IP) func(*Client) error {
return func(c *Client) error {
c.ignoreServers = s
return nil
}
}
func HardwareAddr(h net.HardwareAddr) func(*Client) error {
return func(c *Client) error {
c.hardwareAddr = h
return nil
}
}
func Broadcast(b bool) func(*Client) error {
return func(c *Client) error {
c.broadcast = b
return nil
}
}
func Connection(conn ConnectionInt) func(*Client) error {
return func(c *Client) error {
c.connection = conn
return nil
}
}
func GenerateXID(g func([]byte)) func(*Client) error {
return func(c *Client) error {
c.generateXID = g
return nil
}
}
//Close Connections
func (c *Client) Close() error {
if c.connection != nil {
return c.connection.Close()
}
return nil
}
//Send the Discovery Packet to the Broadcast Channel
func (c *Client) SendDiscoverPacket() (dhcp4.Packet, error) {
discoveryPacket := c.DiscoverPacket()
discoveryPacket.PadToMinSize()
return discoveryPacket, c.SendPacket(discoveryPacket)
}
// TimeoutError records a timeout when waiting for a DHCP packet.
type TimeoutError struct {
Timeout time.Duration
}
func (te *TimeoutError) Error() string {
return fmt.Sprintf("no DHCP packet received within %v", te.Timeout)
}
//Retreive Offer...
//Wait for the offer for a specific Discovery Packet.
func (c *Client) GetOffer(discoverPacket *dhcp4.Packet) (dhcp4.Packet, error) {
start := time.Now()
for {
timeout := c.timeout - time.Since(start)
if timeout < 0 {
return dhcp4.Packet{}, &TimeoutError{Timeout: c.timeout}
}
c.connection.SetReadTimeout(timeout)
readBuffer, source, err := c.connection.ReadFrom()
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EAGAIN {
return dhcp4.Packet{}, &TimeoutError{Timeout: c.timeout}
}
return dhcp4.Packet{}, err
}
offerPacket := dhcp4.Packet(readBuffer)
offerPacketOptions := offerPacket.ParseOptions()
// Ignore Servers in my Ignore list
for _, ignoreServer := range c.ignoreServers {
if source.Equal(ignoreServer) {
continue
}
if offerPacket.SIAddr().Equal(ignoreServer) {
continue
}
}
if len(offerPacketOptions[dhcp4.OptionDHCPMessageType]) < 1 || dhcp4.MessageType(offerPacketOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.Offer || !bytes.Equal(discoverPacket.XId(), offerPacket.XId()) {
continue
}
return offerPacket, nil
}
}
//Send Request Based On the offer Received.
func (c *Client) SendRequest(offerPacket *dhcp4.Packet) (dhcp4.Packet, error) {
requestPacket := c.RequestPacket(offerPacket)
requestPacket.PadToMinSize()
return requestPacket, c.SendPacket(requestPacket)
}
//Retreive Acknowledgement
//Wait for the offer for a specific Request Packet.
func (c *Client) GetAcknowledgement(requestPacket *dhcp4.Packet) (dhcp4.Packet, error) {
start := time.Now()
for {
timeout := c.timeout - time.Since(start)
if timeout < 0 {
return dhcp4.Packet{}, &TimeoutError{Timeout: c.timeout}
}
c.connection.SetReadTimeout(timeout)
readBuffer, source, err := c.connection.ReadFrom()
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EAGAIN {
return dhcp4.Packet{}, &TimeoutError{Timeout: c.timeout}
}
return dhcp4.Packet{}, err
}
acknowledgementPacket := dhcp4.Packet(readBuffer)
acknowledgementPacketOptions := acknowledgementPacket.ParseOptions()
// Ignore Servers in my Ignore list
for _, ignoreServer := range c.ignoreServers {
if source.Equal(ignoreServer) {
continue
}
if acknowledgementPacket.SIAddr().Equal(ignoreServer) {
continue
}
}
if !bytes.Equal(requestPacket.XId(), acknowledgementPacket.XId()) || len(acknowledgementPacketOptions[dhcp4.OptionDHCPMessageType]) < 1 || (dhcp4.MessageType(acknowledgementPacketOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK && dhcp4.MessageType(acknowledgementPacketOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.NAK) {
continue
}
return acknowledgementPacket, nil
}
}
//Send Decline to the received acknowledgement.
func (c *Client) SendDecline(acknowledgementPacket *dhcp4.Packet) (dhcp4.Packet, error) {
declinePacket := c.DeclinePacket(acknowledgementPacket)
declinePacket.PadToMinSize()
return declinePacket, c.SendPacket(declinePacket)
}
//Send a DHCP Packet.
func (c *Client) SendPacket(packet dhcp4.Packet) error {
return c.connection.Write(packet)
}
//Create Discover Packet
func (c *Client) DiscoverPacket() dhcp4.Packet {
messageid := make([]byte, 4)
c.generateXID(messageid)
packet := dhcp4.NewPacket(dhcp4.BootRequest)
packet.SetCHAddr(c.hardwareAddr)
packet.SetXId(messageid)
packet.SetBroadcast(c.broadcast)
packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Discover)})
//packet.PadToMinSize()
return packet
}
//Create Request Packet
func (c *Client) RequestPacket(offerPacket *dhcp4.Packet) dhcp4.Packet {
offerOptions := offerPacket.ParseOptions()
packet := dhcp4.NewPacket(dhcp4.BootRequest)
packet.SetCHAddr(c.hardwareAddr)
packet.SetXId(offerPacket.XId())
packet.SetCIAddr(offerPacket.CIAddr())
packet.SetSIAddr(offerPacket.SIAddr())
packet.SetBroadcast(c.broadcast)
packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Request)})
packet.AddOption(dhcp4.OptionRequestedIPAddress, (offerPacket.YIAddr()).To4())
packet.AddOption(dhcp4.OptionServerIdentifier, offerOptions[dhcp4.OptionServerIdentifier])
return packet
}
//Create Request Packet For a Renew
func (c *Client) RenewalRequestPacket(acknowledgement *dhcp4.Packet) dhcp4.Packet {
messageid := make([]byte, 4)
c.generateXID(messageid)
acknowledgementOptions := acknowledgement.ParseOptions()
packet := dhcp4.NewPacket(dhcp4.BootRequest)
packet.SetCHAddr(acknowledgement.CHAddr())
packet.SetXId(messageid)
packet.SetCIAddr(acknowledgement.YIAddr())
packet.SetSIAddr(acknowledgement.SIAddr())
packet.SetBroadcast(c.broadcast)
packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Request)})
packet.AddOption(dhcp4.OptionRequestedIPAddress, (acknowledgement.YIAddr()).To4())
packet.AddOption(dhcp4.OptionServerIdentifier, acknowledgementOptions[dhcp4.OptionServerIdentifier])
return packet
}
//Create Release Packet For a Release
func (c *Client) ReleasePacket(acknowledgement *dhcp4.Packet) dhcp4.Packet {
messageid := make([]byte, 4)
c.generateXID(messageid)
acknowledgementOptions := acknowledgement.ParseOptions()
packet := dhcp4.NewPacket(dhcp4.BootRequest)
packet.SetCHAddr(acknowledgement.CHAddr())
packet.SetXId(messageid)
packet.SetCIAddr(acknowledgement.YIAddr())
packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Release)})
packet.AddOption(dhcp4.OptionServerIdentifier, acknowledgementOptions[dhcp4.OptionServerIdentifier])
return packet
}
//Create Decline Packet
func (c *Client) DeclinePacket(acknowledgement *dhcp4.Packet) dhcp4.Packet {
messageid := make([]byte, 4)
c.generateXID(messageid)
acknowledgementOptions := acknowledgement.ParseOptions()
packet := dhcp4.NewPacket(dhcp4.BootRequest)
packet.SetCHAddr(acknowledgement.CHAddr())
packet.SetXId(messageid)
packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Decline)})
packet.AddOption(dhcp4.OptionRequestedIPAddress, (acknowledgement.YIAddr()).To4())
packet.AddOption(dhcp4.OptionServerIdentifier, acknowledgementOptions[dhcp4.OptionServerIdentifier])
return packet
}
//Lets do a Full DHCP Request.
func (c *Client) Request() (bool, dhcp4.Packet, error) {
discoveryPacket, err := c.SendDiscoverPacket()
if err != nil {
return false, discoveryPacket, err
}
offerPacket, err := c.GetOffer(&discoveryPacket)
if err != nil {
return false, offerPacket, err
}
requestPacket, err := c.SendRequest(&offerPacket)
if err != nil {
return false, requestPacket, err
}
acknowledgement, err := c.GetAcknowledgement(&requestPacket)
if err != nil {
return false, acknowledgement, err
}
acknowledgementOptions := acknowledgement.ParseOptions()
if dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK {
return false, acknowledgement, nil
}
return true, acknowledgement, nil
}
//Renew a lease backed on the Acknowledgement Packet.
//Returns Sucessfull, The AcknoledgementPacket, Any Errors
func (c *Client) Renew(acknowledgement dhcp4.Packet) (bool, dhcp4.Packet, error) {
renewRequest := c.RenewalRequestPacket(&acknowledgement)
renewRequest.PadToMinSize()
err := c.SendPacket(renewRequest)
if err != nil {
return false, renewRequest, err
}
newAcknowledgement, err := c.GetAcknowledgement(&renewRequest)
if err != nil {
return false, newAcknowledgement, err
}
newAcknowledgementOptions := newAcknowledgement.ParseOptions()
if dhcp4.MessageType(newAcknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK {
return false, newAcknowledgement, nil
}
return true, newAcknowledgement, nil
}
//Release a lease backed on the Acknowledgement Packet.
//Returns Any Errors
func (c *Client) Release(acknowledgement dhcp4.Packet) error {
release := c.ReleasePacket(&acknowledgement)
release.PadToMinSize()
return c.SendPacket(release)
}