summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.travis.yml7
-rw-r--r--CONTRIBUTORS.md7
-rw-r--r--README.md1
-rw-r--r--dhcpv4/bsdp/client.go12
-rw-r--r--dhcpv4/dhcpv4.go13
-rw-r--r--dhcpv4/dhcpv4_test.go10
-rw-r--r--dhcpv4/option_archtype.go42
-rw-r--r--dhcpv4/option_archtype_test.go9
-rw-r--r--dhcpv4/options.go3
-rw-r--r--dhcpv4/options_test.go5
-rw-r--r--dhcpv6/async/client.go (renamed from dhcpv6/async.go)101
-rw-r--r--dhcpv6/async/client_test.go153
-rw-r--r--dhcpv6/async_test.go54
-rw-r--r--dhcpv6/client.go4
-rw-r--r--dhcpv6/dhcpv6.go58
-rw-r--r--dhcpv6/dhcpv6_test.go47
-rw-r--r--dhcpv6/dhcpv6message.go27
-rw-r--r--dhcpv6/dhcpv6message_test.go34
-rw-r--r--dhcpv6/dhcpv6relay.go25
-rw-r--r--dhcpv6/dhcpv6relay_test.go22
-rw-r--r--dhcpv6/future.go111
-rw-r--r--dhcpv6/future_test.go170
-rw-r--r--dhcpv6/modifiers.go6
-rw-r--r--dhcpv6/option_archtype.go62
-rw-r--r--dhcpv6/option_archtype_test.go5
-rw-r--r--dhcpv6/utils.go77
-rw-r--r--dhcpv6/utils_test.go69
-rw-r--r--iana/archtype.go41
-rw-r--r--netboot/netboot.go4
29 files changed, 524 insertions, 655 deletions
diff --git a/.travis.yml b/.travis.yml
index 142bea8..3876f7c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
language: go
-sudo: false
+sudo: required
go:
- "1.8"
@@ -11,6 +11,11 @@ go:
before_install:
- go get -t -v ./...
+before_script:
+ - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
+ sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6';
+ fi
+
script:
- ./.travis/tests.sh
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 8b7e9e1..7ac418f 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -1,4 +1,7 @@
## Contributors
-* Andrea Barberio, main author
-* Sean Karlage, author of the BSDP package, and of tons of improvements to the DHCPv4 package
+* Andrea Barberio (main author)
+* Pablo Mazzini (tons of fixes and new options)
+* Sean Karlage (BSDP package, and of tons of improvements to the DHCPv4 package)
+* Owen Mooney (several option fixes and modifiers)
+* Mikolaj Walczak (asynchronous DHCPv6 client)
diff --git a/README.md b/README.md
index 4055964..5d655f7 100644
--- a/README.md
+++ b/README.md
@@ -7,3 +7,4 @@ See examples at https://github.com/insomniacslk/exdhcp
## Public projects that use it
* Facebook's DHCP load balancer, `dhcplb`, https://github.com/facebookincubator/dhcplb
+* Router7, a pure-Go router implementation for fiber7 connections, https://github.com/rtr7/router7
diff --git a/dhcpv4/bsdp/client.go b/dhcpv4/bsdp/client.go
index 2683e2a..38de094 100644
--- a/dhcpv4/bsdp/client.go
+++ b/dhcpv4/bsdp/client.go
@@ -38,8 +38,8 @@ func castVendorOpt(ack *dhcpv4.DHCPv4) {
// Exchange runs a full BSDP exchange (Inform[list], Ack, Inform[select],
// Ack). Returns a list of DHCPv4 structures representing the exchange.
-func (c *Client) Exchange(ifname string, informList *dhcpv4.DHCPv4) ([]dhcpv4.DHCPv4, error) {
- conversation := make([]dhcpv4.DHCPv4, 1)
+func (c *Client) Exchange(ifname string, informList *dhcpv4.DHCPv4) ([]*dhcpv4.DHCPv4, error) {
+ conversation := make([]*dhcpv4.DHCPv4, 1)
var err error
// Get our file descriptor for the broadcast socket.
@@ -73,7 +73,7 @@ func (c *Client) Exchange(ifname string, informList *dhcpv4.DHCPv4) ([]dhcpv4.DH
return conversation, err
}
}
- conversation[0] = *informList
+ conversation[0] = informList
// ACK[LIST]
ackForList, err := dhcpv4.BroadcastSendReceive(sendFd, recvFd, informList, c.ReadTimeout, c.WriteTimeout, dhcpv4.MessageTypeAck)
@@ -83,7 +83,7 @@ func (c *Client) Exchange(ifname string, informList *dhcpv4.DHCPv4) ([]dhcpv4.DH
// Rewrite vendor-specific option for pretty printing.
castVendorOpt(ackForList)
- conversation = append(conversation, *ackForList)
+ conversation = append(conversation, ackForList)
// Parse boot images sent back by server
bootImages, err := ParseBootImageListFromAck(*ackForList)
@@ -99,7 +99,7 @@ func (c *Client) Exchange(ifname string, informList *dhcpv4.DHCPv4) ([]dhcpv4.DH
if err != nil {
return conversation, err
}
- conversation = append(conversation, *informSelect)
+ conversation = append(conversation, informSelect)
// ACK[SELECT]
ackForSelect, err := dhcpv4.BroadcastSendReceive(sendFd, recvFd, informSelect, c.ReadTimeout, c.WriteTimeout, dhcpv4.MessageTypeAck)
@@ -107,5 +107,5 @@ func (c *Client) Exchange(ifname string, informList *dhcpv4.DHCPv4) ([]dhcpv4.DH
if err != nil {
return conversation, err
}
- return append(conversation, *ackForSelect), nil
+ return append(conversation, ackForSelect), nil
}
diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go
index d452f6e..972d103 100644
--- a/dhcpv4/dhcpv4.go
+++ b/dhcpv4/dhcpv4.go
@@ -656,6 +656,19 @@ func (d *DHCPv4) ValidateOptions() {
}
}
+// IsOptionRequested returns true if that option is within the requested
+// options of the DHCPv4 message.
+func (d *DHCPv4) IsOptionRequested(requested OptionCode) bool {
+ for _, optprl := range d.GetOption(OptionParameterRequestList) {
+ for _, o := range optprl.(*OptParameterRequestList).RequestedOpts {
+ if o == requested {
+ return true
+ }
+ }
+ }
+ return false
+}
+
// ToBytes encodes a DHCPv4 structure into a sequence of bytes in its wire
// format.
func (d *DHCPv4) ToBytes() []byte {
diff --git a/dhcpv4/dhcpv4_test.go b/dhcpv4/dhcpv4_test.go
index fe9fe0d..28f38d4 100644
--- a/dhcpv4/dhcpv4_test.go
+++ b/dhcpv4/dhcpv4_test.go
@@ -493,5 +493,15 @@ func TestNewInform(t *testing.T) {
require.True(t, m.ClientIPAddr().Equal(localIP))
}
+func TestIsOptionRequested(t *testing.T) {
+ pkt, err := New()
+ require.NoError(t, err)
+ require.False(t, pkt.IsOptionRequested(OptionDomainNameServer))
+
+ optprl := OptParameterRequestList{RequestedOpts: []OptionCode{OptionDomainNameServer}}
+ pkt.AddOption(&optprl)
+ require.True(t, pkt.IsOptionRequested(OptionDomainNameServer))
+}
+
// TODO
// test Summary() and String()
diff --git a/dhcpv4/option_archtype.go b/dhcpv4/option_archtype.go
index 16ca98d..8eafc55 100644
--- a/dhcpv4/option_archtype.go
+++ b/dhcpv4/option_archtype.go
@@ -6,43 +6,14 @@ package dhcpv4
import (
"encoding/binary"
"fmt"
-)
-
-//ArchType encodes an architecture type in an uint16
-type ArchType uint16
-// see rfc4578
-const (
- INTEL_X86PC ArchType = 0
- NEC_PC98 ArchType = 1
- EFI_ITANIUM ArchType = 2
- DEC_ALPHA ArchType = 3
- ARC_X86 ArchType = 4
- INTEL_LEAN_CLIENT ArchType = 5
- EFI_IA32 ArchType = 6
- EFI_BC ArchType = 7
- EFI_XSCALE ArchType = 8
- EFI_X86_64 ArchType = 9
+ "github.com/insomniacslk/dhcp/iana"
)
-// ArchTypeToStringMap maps an ArchType to a mnemonic name
-var ArchTypeToStringMap = map[ArchType]string{
- INTEL_X86PC: "Intel x86PC",
- NEC_PC98: "NEC/PC98",
- EFI_ITANIUM: "EFI Itanium",
- DEC_ALPHA: "DEC Alpha",
- ARC_X86: "Arc x86",
- INTEL_LEAN_CLIENT: "Intel Lean Client",
- EFI_IA32: "EFI IA32",
- EFI_BC: "EFI BC",
- EFI_XSCALE: "EFI Xscale",
- EFI_X86_64: "EFI x86-64",
-}
-
// OptClientArchType represents an option encapsulating the Client System
// Architecture Type option Definition.
type OptClientArchType struct {
- ArchTypes []ArchType
+ ArchTypes []iana.ArchType
}
// Code returns the option code.
@@ -71,10 +42,7 @@ func (o *OptClientArchType) Length() int {
func (o *OptClientArchType) String() string {
var archTypes string
for idx, at := range o.ArchTypes {
- name, ok := ArchTypeToStringMap[at]
- if !ok {
- name = "Unknown"
- }
+ name := iana.ArchTypeToString(at)
archTypes += name
if idx < len(o.ArchTypes)-1 {
archTypes += ", "
@@ -100,10 +68,10 @@ func ParseOptClientArchType(data []byte) (*OptClientArchType, error) {
if len(data) < 2+length {
return nil, ErrShortByteStream
}
- archTypes := make([]ArchType, 0, length%2)
+ archTypes := make([]iana.ArchType, 0, length%2)
for idx := 0; idx < length; idx += 2 {
b := data[2+idx : 2+idx+2]
- archTypes = append(archTypes, ArchType(binary.BigEndian.Uint16(b)))
+ archTypes = append(archTypes, iana.ArchType(binary.BigEndian.Uint16(b)))
}
return &OptClientArchType{ArchTypes: archTypes}, nil
}
diff --git a/dhcpv4/option_archtype_test.go b/dhcpv4/option_archtype_test.go
index cb9bc3f..d803328 100644
--- a/dhcpv4/option_archtype_test.go
+++ b/dhcpv4/option_archtype_test.go
@@ -3,6 +3,7 @@ package dhcpv4
import (
"testing"
+ "github.com/insomniacslk/dhcp/iana"
"github.com/stretchr/testify/require"
)
@@ -14,7 +15,7 @@ func TestParseOptClientArchType(t *testing.T) {
}
opt, err := ParseOptClientArchType(data)
require.NoError(t, err)
- require.Equal(t, opt.ArchTypes[0], EFI_IA32)
+ require.Equal(t, opt.ArchTypes[0], iana.EFI_IA32)
}
func TestParseOptClientArchTypeMultiple(t *testing.T) {
@@ -26,8 +27,8 @@ func TestParseOptClientArchTypeMultiple(t *testing.T) {
}
opt, err := ParseOptClientArchType(data)
require.NoError(t, err)
- require.Equal(t, opt.ArchTypes[0], EFI_IA32)
- require.Equal(t, opt.ArchTypes[1], EFI_ITANIUM)
+ require.Equal(t, opt.ArchTypes[0], iana.EFI_IA32)
+ require.Equal(t, opt.ArchTypes[1], iana.EFI_ITANIUM)
}
func TestParseOptClientArchTypeInvalid(t *testing.T) {
@@ -61,7 +62,7 @@ func TestOptClientArchTypeParseAndToBytesMultiple(t *testing.T) {
func TestOptClientArchType(t *testing.T) {
opt := OptClientArchType{
- ArchTypes: []ArchType{EFI_ITANIUM},
+ ArchTypes: []iana.ArchType{iana.EFI_ITANIUM},
}
require.Equal(t, opt.Length(), 2)
require.Equal(t, opt.Code(), OptionClientSystemArchitectureType)
diff --git a/dhcpv4/options.go b/dhcpv4/options.go
index d869b7d..02fa6e4 100644
--- a/dhcpv4/options.go
+++ b/dhcpv4/options.go
@@ -126,6 +126,9 @@ func OptionsFromBytesWithoutMagicCookie(data []byte) ([]Option, error) {
return nil, err
}
options = append(options, opt)
+ if opt.Code() == OptionEnd {
+ break
+ }
// Options with zero length have no length byte, so here we handle the
// ones with nonzero length
diff --git a/dhcpv4/options_test.go b/dhcpv4/options_test.go
index 0268483..899fb2c 100644
--- a/dhcpv4/options_test.go
+++ b/dhcpv4/options_test.go
@@ -167,12 +167,9 @@ func TestOptionsFromBytes(t *testing.T) {
}
opts, err := OptionsFromBytes(options)
require.NoError(t, err)
- require.Equal(t, 5, len(opts))
+ require.Equal(t, 2, len(opts))
require.Equal(t, opts[0].(*OptionGeneric), &OptionGeneric{OptionCode: OptionNameServer, Data: []byte{192, 168, 1, 1}})
require.Equal(t, opts[1].(*OptionGeneric), &OptionGeneric{OptionCode: OptionEnd})
- require.Equal(t, opts[2].(*OptionGeneric), &OptionGeneric{OptionCode: OptionPad})
- require.Equal(t, opts[3].(*OptionGeneric), &OptionGeneric{OptionCode: OptionPad})
- require.Equal(t, opts[4].(*OptionGeneric), &OptionGeneric{OptionCode: OptionPad})
}
func TestOptionsFromBytesZeroLength(t *testing.T) {
diff --git a/dhcpv6/async.go b/dhcpv6/async/client.go
index e9930bf..08c2cfb 100644
--- a/dhcpv6/async.go
+++ b/dhcpv6/async/client.go
@@ -1,4 +1,4 @@
-package dhcpv6
+package async
import (
"context"
@@ -6,10 +6,13 @@ import (
"net"
"sync"
"time"
+
+ "github.com/fanliao/go-promise"
+ "github.com/insomniacslk/dhcp/dhcpv6"
)
-// AsyncClient implements an asynchronous DHCPv6 client
-type AsyncClient struct {
+// Client implements an asynchronous DHCPv6 client
+type Client struct {
ReadTimeout time.Duration
WriteTimeout time.Duration
LocalAddr net.Addr
@@ -19,35 +22,35 @@ type AsyncClient struct {
connection *net.UDPConn
cancel context.CancelFunc
stopping *sync.WaitGroup
- receiveQueue chan DHCPv6
- sendQueue chan DHCPv6
+ receiveQueue chan dhcpv6.DHCPv6
+ sendQueue chan dhcpv6.DHCPv6
packetsLock sync.Mutex
- packets map[uint32](chan Response)
+ packets map[uint32]*promise.Promise
errors chan error
}
-// NewAsyncClient creates an asynchronous client
-func NewAsyncClient() *AsyncClient {
- return &AsyncClient{
- ReadTimeout: DefaultReadTimeout,
- WriteTimeout: DefaultWriteTimeout,
+// NewClient creates an asynchronous client
+func NewClient() *Client {
+ return &Client{
+ ReadTimeout: dhcpv6.DefaultReadTimeout,
+ WriteTimeout: dhcpv6.DefaultWriteTimeout,
}
}
// OpenForInterface starts the client on the specified interface, replacing
// client LocalAddr with a link-local address of the given interface and
// standard DHCP port (546).
-func (c *AsyncClient) OpenForInterface(ifname string, bufferSize int) error {
- addr, err := GetLinkLocalAddr(ifname)
+func (c *Client) OpenForInterface(ifname string, bufferSize int) error {
+ addr, err := dhcpv6.GetLinkLocalAddr(ifname)
if err != nil {
return err
}
- c.LocalAddr = &net.UDPAddr{IP: *addr, Port: DefaultClientPort, Zone: ifname}
+ c.LocalAddr = &net.UDPAddr{IP: *addr, Port: dhcpv6.DefaultClientPort, Zone: ifname}
return c.Open(bufferSize)
}
// Open starts the client
-func (c *AsyncClient) Open(bufferSize int) error {
+func (c *Client) Open(bufferSize int) error {
var (
addr *net.UDPAddr
ok bool
@@ -64,9 +67,9 @@ func (c *AsyncClient) Open(bufferSize int) error {
return err
}
c.stopping = new(sync.WaitGroup)
- c.sendQueue = make(chan DHCPv6, bufferSize)
- c.receiveQueue = make(chan DHCPv6, bufferSize)
- c.packets = make(map[uint32](chan Response))
+ c.sendQueue = make(chan dhcpv6.DHCPv6, bufferSize)
+ c.receiveQueue = make(chan dhcpv6.DHCPv6, bufferSize)
+ c.packets = make(map[uint32]*promise.Promise)
c.packetsLock = sync.Mutex{}
c.errors = make(chan error)
@@ -79,7 +82,7 @@ func (c *AsyncClient) Open(bufferSize int) error {
}
// Close stops the client
-func (c *AsyncClient) Close() {
+func (c *Client) Close() {
// Wait for sender and receiver loops
c.stopping.Add(2)
c.cancel()
@@ -93,17 +96,17 @@ func (c *AsyncClient) Close() {
}
// Errors returns a channel where runtime errors are posted
-func (c *AsyncClient) Errors() <-chan error {
+func (c *Client) Errors() <-chan error {
return c.errors
}
-func (c *AsyncClient) addError(err error) {
+func (c *Client) addError(err error) {
if !c.IgnoreErrors {
c.errors <- err
}
}
-func (c *AsyncClient) receiverLoop(ctx context.Context) {
+func (c *Client) receiverLoop(ctx context.Context) {
defer func() { c.stopping.Done() }()
for {
select {
@@ -115,7 +118,7 @@ func (c *AsyncClient) receiverLoop(ctx context.Context) {
}
}
-func (c *AsyncClient) senderLoop(ctx context.Context) {
+func (c *Client) senderLoop(ctx context.Context) {
defer func() { c.stopping.Done() }()
for {
select {
@@ -127,41 +130,41 @@ func (c *AsyncClient) senderLoop(ctx context.Context) {
}
}
-func (c *AsyncClient) send(packet DHCPv6) {
- transactionID, err := GetTransactionID(packet)
+func (c *Client) send(packet dhcpv6.DHCPv6) {
+ transactionID, err := dhcpv6.GetTransactionID(packet)
if err != nil {
c.addError(fmt.Errorf("Warning: This should never happen, there is no transaction ID on %s", packet))
return
}
c.packetsLock.Lock()
- f := c.packets[transactionID]
+ p := c.packets[transactionID]
c.packetsLock.Unlock()
raddr, err := c.remoteAddr()
if err != nil {
- f <- NewResponse(nil, err)
+ p.Reject(err)
return
}
c.connection.SetWriteDeadline(time.Now().Add(c.WriteTimeout))
_, err = c.connection.WriteTo(packet.ToBytes(), raddr)
if err != nil {
- f <- NewResponse(nil, err)
+ p.Reject(err)
return
}
c.receiveQueue <- packet
}
-func (c *AsyncClient) receive(_ DHCPv6) {
+func (c *Client) receive(_ dhcpv6.DHCPv6) {
var (
oobdata = []byte{}
- received DHCPv6
+ received dhcpv6.DHCPv6
)
c.connection.SetReadDeadline(time.Now().Add(c.ReadTimeout))
for {
- buffer := make([]byte, maxUDPReceivedPacketSize)
+ buffer := make([]byte, dhcpv6.MaxUDPReceivedPacketSize)
n, _, _, _, err := c.connection.ReadMsgUDP(buffer, oobdata)
if err != nil {
if err, ok := err.(net.Error); !ok || !err.Timeout() {
@@ -169,7 +172,7 @@ func (c *AsyncClient) receive(_ DHCPv6) {
}
return
}
- received, err = FromBytes(buffer[:n])
+ received, err = dhcpv6.FromBytes(buffer[:n])
if err != nil {
// skip non-DHCP packets
continue
@@ -177,23 +180,23 @@ func (c *AsyncClient) receive(_ DHCPv6) {
break
}
- transactionID, err := GetTransactionID(received)
+ transactionID, err := dhcpv6.GetTransactionID(received)
if err != nil {
c.addError(fmt.Errorf("Unable to get a transactionID for %s: %s", received, err))
return
}
c.packetsLock.Lock()
- if f, ok := c.packets[transactionID]; ok {
+ if p, ok := c.packets[transactionID]; ok {
delete(c.packets, transactionID)
- f <- NewResponse(received, nil)
+ p.Resolve(received)
}
c.packetsLock.Unlock()
}
-func (c *AsyncClient) remoteAddr() (*net.UDPAddr, error) {
+func (c *Client) remoteAddr() (*net.UDPAddr, error) {
if c.RemoteAddr == nil {
- return &net.UDPAddr{IP: AllDHCPRelayAgentsAndServers, Port: DefaultServerPort}, nil
+ return &net.UDPAddr{IP: dhcpv6.AllDHCPRelayAgentsAndServers, Port: dhcpv6.DefaultServerPort}, nil
}
if addr, ok := c.RemoteAddr.(*net.UDPAddr); ok {
@@ -204,32 +207,20 @@ func (c *AsyncClient) remoteAddr() (*net.UDPAddr, error) {
// Send inserts a message to the queue to be sent asynchronously.
// Returns a future which resolves to response and error.
-func (c *AsyncClient) Send(message DHCPv6, modifiers ...Modifier) Future {
+func (c *Client) Send(message dhcpv6.DHCPv6, modifiers ...dhcpv6.Modifier) *promise.Future {
for _, mod := range modifiers {
message = mod(message)
}
- transactionID, err := GetTransactionID(message)
+ transactionID, err := dhcpv6.GetTransactionID(message)
if err != nil {
- return NewFailureFuture(err)
+ return promise.Wrap(err)
}
- f := NewFuture()
+ p := promise.NewPromise()
c.packetsLock.Lock()
- c.packets[transactionID] = f
+ c.packets[transactionID] = p
c.packetsLock.Unlock()
c.sendQueue <- message
- return f
-}
-
-// Exchange executes asynchronously a 4-way DHCPv6 request (SOLICIT,
-// ADVERTISE, REQUEST, REPLY).
-func (c *AsyncClient) Exchange(solicit DHCPv6, modifiers ...Modifier) Future {
- return c.Send(solicit).OnSuccess(func(advertise DHCPv6) Future {
- request, err := NewRequestFromAdvertise(advertise)
- if err != nil {
- return NewFailureFuture(err)
- }
- return c.Send(request, modifiers...)
- })
+ return p.Future
}
diff --git a/dhcpv6/async/client_test.go b/dhcpv6/async/client_test.go
new file mode 100644
index 0000000..0bc3a87
--- /dev/null
+++ b/dhcpv6/async/client_test.go
@@ -0,0 +1,153 @@
+package async
+
+import (
+ "context"
+ "net"
+ "testing"
+ "time"
+
+ "github.com/insomniacslk/dhcp/dhcpv6"
+ "github.com/insomniacslk/dhcp/iana"
+ "github.com/stretchr/testify/require"
+)
+
+const retries = 5
+
+// solicit creates new solicit based on the mac address
+func solicit(input string) (dhcpv6.DHCPv6, error) {
+ mac, err := net.ParseMAC(input)
+ if err != nil {
+ return nil, err
+ }
+ duid := dhcpv6.Duid{
+ Type: dhcpv6.DUID_LLT,
+ HwType: iana.HwTypeEthernet,
+ Time: dhcpv6.GetTime(),
+ LinkLayerAddr: mac,
+ }
+ return dhcpv6.NewSolicitWithCID(duid)
+}
+
+// server creates a server which responds with a predefined response
+func serve(ctx context.Context, addr *net.UDPAddr, response dhcpv6.DHCPv6) error {
+ conn, err := net.ListenUDP("udp6", addr)
+ if err != nil {
+ return err
+ }
+ go func() {
+ defer conn.Close()
+ oobdata := []byte{}
+ buffer := make([]byte, dhcpv6.MaxUDPReceivedPacketSize)
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ default:
+ conn.SetReadDeadline(time.Now().Add(1 * time.Second))
+ n, _, _, src, err := conn.ReadMsgUDP(buffer, oobdata)
+ if err != nil {
+ continue
+ }
+ _, err = dhcpv6.FromBytes(buffer[:n])
+ if err != nil {
+ continue
+ }
+ conn.SetWriteDeadline(time.Now().Add(1 * time.Second))
+ _, err = conn.WriteTo(response.ToBytes(), src)
+ if err != nil {
+ continue
+ }
+ }
+ }
+ }()
+ return nil
+}
+
+func TestNewClient(t *testing.T) {
+ c := NewClient()
+ require.NotNil(t, c)
+ require.Equal(t, c.ReadTimeout, dhcpv6.DefaultReadTimeout)
+ require.Equal(t, c.ReadTimeout, dhcpv6.DefaultWriteTimeout)
+}
+
+func TestOpenInvalidAddrFailes(t *testing.T) {
+ c := NewClient()
+ err := c.Open(512)
+ require.Error(t, err)
+}
+
+// This test uses port 15438 so please make sure its not used before running
+func TestOpenClose(t *testing.T) {
+ c := NewClient()
+ addr, err := net.ResolveUDPAddr("udp6", ":15438")
+ require.NoError(t, err)
+ c.LocalAddr = addr
+ err = c.Open(512)
+ require.NoError(t, err)
+ defer c.Close()
+}
+
+// This test uses ports 15438 and 15439 so please make sure they are not used
+// before running
+func TestSendTimeout(t *testing.T) {
+ c := NewClient()
+ addr, err := net.ResolveUDPAddr("udp6", ":15438")
+ require.NoError(t, err)
+ remote, err := net.ResolveUDPAddr("udp6", ":15439")
+ require.NoError(t, err)
+ c.ReadTimeout = 50 * time.Millisecond
+ c.WriteTimeout = 50 * time.Millisecond
+ c.LocalAddr = addr
+ c.RemoteAddr = remote
+ err = c.Open(512)
+ require.NoError(t, err)
+ defer c.Close()
+ m, err := dhcpv6.NewMessage()
+ require.NoError(t, err)
+ _, err, timeout := c.Send(m).GetOrTimeout(200)
+ require.NoError(t, err)
+ require.True(t, timeout)
+}
+
+// This test uses ports 15438 and 15439 so please make sure they are not used
+// before running
+func TestSend(t *testing.T) {
+ s, err := solicit("c8:6c:2c:47:96:fd")
+ require.NoError(t, err)
+ require.NotNil(t, s)
+
+ a, err := dhcpv6.NewAdvertiseFromSolicit(s)
+ require.NoError(t, err)
+ require.NotNil(t, a)
+
+ c := NewClient()
+ addr, err := net.ResolveUDPAddr("udp6", ":15438")
+ require.NoError(t, err)
+ remote, err := net.ResolveUDPAddr("udp6", ":15439")
+ require.NoError(t, err)
+ c.LocalAddr = addr
+ c.RemoteAddr = remote
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ err = serve(ctx, remote, a)
+ require.NoError(t, err)
+
+ err = c.Open(16)
+ require.NoError(t, err)
+ defer c.Close()
+
+ f := c.Send(s)
+
+ var passed bool
+ for i := 0; i < retries; i++ {
+ response, err, timeout := f.GetOrTimeout(1000)
+ if timeout {
+ continue
+ }
+ passed = true
+ require.NoError(t, err)
+ require.Equal(t, a, response)
+ }
+ require.True(t, passed, "All attempts to TestSend timed out")
+}
diff --git a/dhcpv6/async_test.go b/dhcpv6/async_test.go
deleted file mode 100644
index 4f5a750..0000000
--- a/dhcpv6/async_test.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package dhcpv6
-
-import (
- "net"
- "testing"
- "time"
-
- "github.com/stretchr/testify/require"
-)
-
-func TestNewAsyncClient(t *testing.T) {
- c := NewAsyncClient()
- require.NotNil(t, c)
- require.Equal(t, c.ReadTimeout, DefaultReadTimeout)
- require.Equal(t, c.ReadTimeout, DefaultWriteTimeout)
-}
-
-func TestOpenInvalidAddrFailes(t *testing.T) {
- c := NewAsyncClient()
- err := c.Open(512)
- require.Error(t, err)
-}
-
-// This test uses port 15438 so please make sure its not used before running
-func TestOpenClose(t *testing.T) {
- c := NewAsyncClient()
- addr, err := net.ResolveUDPAddr("udp6", ":15438")
- require.NoError(t, err)
- c.LocalAddr = addr
- err = c.Open(512)
- require.NoError(t, err)
- defer c.Close()
-}
-
-// This test uses ports 15438 and 15439 so please make sure they are not used
-// before running
-func TestSendTimeout(t *testing.T) {
- c := NewAsyncClient()
- addr, err := net.ResolveUDPAddr("udp6", ":15438")
- require.NoError(t, err)
- remote, err := net.ResolveUDPAddr("udp6", ":15439")
- require.NoError(t, err)
- c.ReadTimeout = 50 * time.Millisecond
- c.WriteTimeout = 50 * time.Millisecond
- c.LocalAddr = addr
- c.RemoteAddr = remote
- err = c.Open(512)
- require.NoError(t, err)
- defer c.Close()
- m, err := NewMessage()
- require.NoError(t, err)
- _, err = c.Send(m).WaitTimeout(200 * time.Millisecond)
- require.Error(t, err)
-}
diff --git a/dhcpv6/client.go b/dhcpv6/client.go
index 3492a47..3ed7861 100644
--- a/dhcpv6/client.go
+++ b/dhcpv6/client.go
@@ -11,7 +11,7 @@ const (
DefaultWriteTimeout = 3 * time.Second // time to wait for write calls
DefaultReadTimeout = 3 * time.Second // time to wait for read calls
DefaultInterfaceUpTimeout = 3 * time.Second // time to wait before a network interface goes up
- maxUDPReceivedPacketSize = 8192 // arbitrary size. Theoretically could be up to 65kb
+ MaxUDPReceivedPacketSize = 8192 // arbitrary size. Theoretically could be up to 65kb
)
// Broadcast destination IP addresses as defined by RFC 3315
@@ -148,7 +148,7 @@ func (c *Client) sendReceive(ifname string, packet DHCPv6, expectedType MessageT
isMessage = true
}
for {
- buf := make([]byte, maxUDPReceivedPacketSize)
+ buf := make([]byte, MaxUDPReceivedPacketSize)
n, _, _, _, err := conn.ReadMsgUDP(buf, oobdata)
if err != nil {
return nil, err
diff --git a/dhcpv6/dhcpv6.go b/dhcpv6/dhcpv6.go
index 0dabca5..9382334 100644
--- a/dhcpv6/dhcpv6.go
+++ b/dhcpv6/dhcpv6.go
@@ -1,8 +1,12 @@
package dhcpv6
import (
+ "errors"
"fmt"
"net"
+ "strings"
+
+ "github.com/insomniacslk/dhcp/iana"
)
type DHCPv6 interface {
@@ -199,3 +203,57 @@ func EncapsulateRelay(d DHCPv6, mType MessageType, linkAddr, peerAddr net.IP) (D
outer.AddOption(&orm)
return &outer, nil
}
+
+// IsUsingUEFI function takes a DHCPv6 message and returns true if
+// the machine trying to netboot is using UEFI of false if it is not.
+func IsUsingUEFI(msg DHCPv6) bool {
+ // RFC 4578 says:
+ // As of the writing of this document, the following pre-boot
+ // architecture types have been requested.
+ // Type Architecture Name
+ // ---- -----------------
+ // 0 Intel x86PC
+ // 1 NEC/PC98
+ // 2 EFI Itanium
+ // 3 DEC Alpha
+ // 4 Arc x86
+ // 5 Intel Lean Client
+ // 6 EFI IA32
+ // 7 EFI BC
+ // 8 EFI Xscale
+ // 9 EFI x86-64
+ if opt := msg.GetOneOption(OptionClientArchType); opt != nil {
+ optat := opt.(*OptClientArchType)
+ for _, at := range optat.ArchTypes {
+ // TODO investigate if other types are appropriate
+ if at == iana.EFI_BC || at == iana.EFI_X86_64 {
+ return true
+ }
+ }
+ }
+ if opt := msg.GetOneOption(OptionUserClass); opt != nil {
+ optuc := opt.(*OptUserClass)
+ for _, uc := range optuc.UserClasses {
+ if strings.Contains(string(uc), "EFI") {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// GetTransactionID returns a transactionID of a message or its inner message
+// in case of relay
+func GetTransactionID(packet DHCPv6) (uint32, error) {
+ if message, ok := packet.(*DHCPv6Message); ok {
+ return message.TransactionID(), nil
+ }
+ if relay, ok := packet.(*DHCPv6Relay); ok {
+ message, err := relay.GetInnerMessage()
+ if err != nil {
+ return 0, err
+ }
+ return GetTransactionID(message)
+ }
+ return 0, errors.New("Invalid DHCPv6 packet")
+}
diff --git a/dhcpv6/dhcpv6_test.go b/dhcpv6/dhcpv6_test.go
index 4a44e0e..6840252 100644
--- a/dhcpv6/dhcpv6_test.go
+++ b/dhcpv6/dhcpv6_test.go
@@ -215,5 +215,52 @@ func TestNewMessageTypeSolicitWithCID(t *testing.T) {
require.Equal(t, len(opts), 2)
}
+
+func TestIsUsingUEFIArchTypeTrue(t *testing.T) {
+ msg := DHCPv6Message{}
+ opt := OptClientArchType{ArchTypes: []iana.ArchType{iana.EFI_BC}}
+ msg.AddOption(&opt)
+ require.True(t, IsUsingUEFI(&msg))
+}
+
+func TestIsUsingUEFIArchTypeFalse(t *testing.T) {
+ msg := DHCPv6Message{}
+ opt := OptClientArchType{ArchTypes: []iana.ArchType{iana.INTEL_X86PC}}
+ msg.AddOption(&opt)
+ require.False(t, IsUsingUEFI(&msg))
+}
+
+func TestIsUsingUEFIUserClassTrue(t *testing.T) {
+ msg := DHCPv6Message{}
+ opt := OptUserClass{UserClasses: [][]byte{[]byte("ipxeUEFI")}}
+ msg.AddOption(&opt)
+ require.True(t, IsUsingUEFI(&msg))
+}
+
+func TestIsUsingUEFIUserClassFalse(t *testing.T) {
+ msg := DHCPv6Message{}
+ opt := OptUserClass{UserClasses: [][]byte{[]byte("ipxeLegacy")}}
+ msg.AddOption(&opt)
+ require.False(t, IsUsingUEFI(&msg))
+}
+
+func TestGetTransactionIDMessage(t *testing.T) {
+ message, err := NewMessage()
+ require.NoError(t, err)
+ transactionID, err := GetTransactionID(message)
+ require.NoError(t, err)
+ require.Equal(t, transactionID, message.(*DHCPv6Message).TransactionID())
+}
+
+func TestGetTransactionIDRelay(t *testing.T) {
+ message, err := NewMessage()
+ require.NoError(t, err)
+ relay, err := EncapsulateRelay(message, MessageTypeRelayForward, nil, nil)
+ require.NoError(t, err)
+ transactionID, err := GetTransactionID(relay)
+ require.NoError(t, err)
+ require.Equal(t, transactionID, message.(*DHCPv6Message).TransactionID())
+}
+
// TODO test NewMessageTypeSolicit
// test String and Summary
diff --git a/dhcpv6/dhcpv6message.go b/dhcpv6/dhcpv6message.go
index e601932..7ee00ad 100644
--- a/dhcpv6/dhcpv6message.go
+++ b/dhcpv6/dhcpv6message.go
@@ -286,6 +286,33 @@ func (d *DHCPv6Message) UpdateOption(option Option) {
d.AddOption(option)
}
+// IsNetboot returns true if the machine is trying to netboot. It checks if
+// "boot file" is one of the requested options, which is useful for
+// SOLICIT/REQUEST packet types, it also checks if the "boot file" option is
+// included in the packet, which is useful for ADVERTISE/REPLY packet.
+func (d *DHCPv6Message) IsNetboot() bool {
+ if d.IsOptionRequested(OptionBootfileURL) {
+ return true
+ }
+ if optbf := d.GetOneOption(OptionBootfileURL); optbf != nil {
+ return true
+ }
+ return false
+}
+
+// IsOptionRequested takes an OptionCode and returns true if that option is
+// within the requested options of the DHCPv6 message.
+func (d *DHCPv6Message) IsOptionRequested(requested OptionCode) bool {
+ for _, optoro := range d.GetOption(OptionORO) {
+ for _, o := range optoro.(*OptRequestedOption).RequestedOptions() {
+ if o == requested {
+ return true
+ }
+ }
+ }
+ return false
+}
+
func (d *DHCPv6Message) String() string {
return fmt.Sprintf("DHCPv6Message(messageType=%v transactionID=0x%06x, %d options)",
d.MessageTypeToString(), d.TransactionID(), len(d.options),
diff --git a/dhcpv6/dhcpv6message_test.go b/dhcpv6/dhcpv6message_test.go
new file mode 100644
index 0000000..5c92a7b
--- /dev/null
+++ b/dhcpv6/dhcpv6message_test.go
@@ -0,0 +1,34 @@
+package dhcpv6
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestIsNetboot(t *testing.T) {
+ msg1 := DHCPv6Message{}
+ require.False(t, msg1.IsNetboot())
+
+ msg2 := DHCPv6Message{}
+ optro := OptRequestedOption{}
+ optro.AddRequestedOption(OptionBootfileURL)
+ msg2.AddOption(&optro)
+ require.True(t, msg2.IsNetboot())
+
+ msg3 := DHCPv6Message{}
+ optbf := OptBootFileURL{}
+ msg3.AddOption(&optbf)
+ require.True(t, msg3.IsNetboot())
+}
+
+func TestIsOptionRequested(t *testing.T) {
+ msg1 := DHCPv6Message{}
+ require.False(t, msg1.IsOptionRequested(OptionDNSRecursiveNameServer))
+
+ msg2 := DHCPv6Message{}
+ optro := OptRequestedOption{}
+ optro.AddRequestedOption(OptionDNSRecursiveNameServer)
+ msg2.AddOption(&optro)
+ require.True(t, msg2.IsOptionRequested(OptionDNSRecursiveNameServer))
+}
diff --git a/dhcpv6/dhcpv6relay.go b/dhcpv6/dhcpv6relay.go
index d9555cb..5c30bad 100644
--- a/dhcpv6/dhcpv6relay.go
+++ b/dhcpv6/dhcpv6relay.go
@@ -158,23 +158,26 @@ func (d *DHCPv6Relay) GetInnerMessage() (DHCPv6, error) {
}
}
-// NewRelayReplFromRelayForw creates a RELAY_REPL packet based on a RELAY_FORW
-// packet and replaces the inner message with the passed DHCPv6 message.
+// NewRelayReplFromRelayForw creates a MessageTypeRelayReply based on a
+// MessageTypeRelayForward and replaces the inner message with the passed
+// DHCPv6 message. It copies the OptionInterfaceID and OptionRemoteID if the
+// options are present in the Relay packet.
func NewRelayReplFromRelayForw(relayForw, msg DHCPv6) (DHCPv6, error) {
var (
err error
linkAddr, peerAddr []net.IP
- optiids []Option
+ optiid []Option
+ optrid []Option
)
if relayForw == nil {
- return nil, errors.New("RELAY_FORW cannot be nil")
+ return nil, errors.New("Relay message cannot be nil")
}
relay, ok := relayForw.(*DHCPv6Relay)
if !ok {
return nil, errors.New("Not a DHCPv6Relay")
}
if relay.Type() != MessageTypeRelayForward {
- return nil, errors.New("The passed packet is not of type RELAY_FORW")
+ return nil, errors.New("The passed packet is not of type MessageTypeRelayForward")
}
if msg == nil {
return nil, errors.New("The passed message cannot be nil")
@@ -185,7 +188,8 @@ func NewRelayReplFromRelayForw(relayForw, msg DHCPv6) (DHCPv6, error) {
for {
linkAddr = append(linkAddr, relay.LinkAddr())
peerAddr = append(peerAddr, relay.PeerAddr())
- optiids = append(optiids, relay.GetOneOption(OptionInterfaceID))
+ optiid = append(optiid, relay.GetOneOption(OptionInterfaceID))
+ optrid = append(optrid, relay.GetOneOption(OptionRemoteID))
decap, err := DecapsulateRelay(relay)
if err != nil {
return nil, err
@@ -198,12 +202,15 @@ func NewRelayReplFromRelayForw(relayForw, msg DHCPv6) (DHCPv6, error) {
}
for i := len(linkAddr) - 1; i >= 0; i-- {
msg, err = EncapsulateRelay(msg, MessageTypeRelayReply, linkAddr[i], peerAddr[i])
- if opt := optiids[i]; opt != nil {
- msg.AddOption(opt)
- }
if err != nil {
return nil, err
}
+ if opt := optiid[i]; opt != nil {
+ msg.AddOption(opt)
+ }
+ if opt := optrid[i]; opt != nil {
+ msg.AddOption(opt)
+ }
}
return msg, nil
}
diff --git a/dhcpv6/dhcpv6relay_test.go b/dhcpv6/dhcpv6relay_test.go
index afb4086..fe1b840 100644
--- a/dhcpv6/dhcpv6relay_test.go
+++ b/dhcpv6/dhcpv6relay_test.go
@@ -108,19 +108,23 @@ func TestDHCPv6RelayToBytes(t *testing.T) {
}
func TestNewRelayRepFromRelayForw(t *testing.T) {
+ // create a new relay forward
rf := DHCPv6Relay{}
rf.SetMessageType(MessageTypeRelayForward)
rf.SetPeerAddr(net.IPv6linklocalallrouters)
rf.SetLinkAddr(net.IPv6interfacelocalallnodes)
- oro := OptRelayMsg{}
- s := DHCPv6Message{}
- s.SetMessage(MessageTypeSolicit)
- cid := OptClientId{}
- s.AddOption(&cid)
- oro.SetRelayMessage(&s)
- rf.AddOption(&oro)
+ rf.AddOption(&OptInterfaceId{})
+ rf.AddOption(&OptRemoteId{})
- a, err := NewAdvertiseFromSolicit(&s)
+ // create the inner message
+ s, err := NewMessage()
+ require.NoError(t, err)
+ s.AddOption(&OptClientId{})
+ orm := OptRelayMsg{}
+ orm.SetRelayMessage(s)
+ rf.AddOption(&orm)
+
+ a, err := NewAdvertiseFromSolicit(s)
require.NoError(t, err)
rr, err := NewRelayReplFromRelayForw(&rf, a)
require.NoError(t, err)
@@ -129,6 +133,8 @@ func TestNewRelayRepFromRelayForw(t *testing.T) {
require.Equal(t, relay.HopCount(), rf.HopCount())
require.Equal(t, relay.PeerAddr(), rf.PeerAddr())
require.Equal(t, relay.LinkAddr(), rf.LinkAddr())
+ require.NotNil(t, rr.GetOneOption(OptionInterfaceID))
+ require.NotNil(t, rr.GetOneOption(OptionRemoteID))
m, err := relay.GetInnerMessage()
require.NoError(t, err)
require.Equal(t, m, a)
diff --git a/dhcpv6/future.go b/dhcpv6/future.go
deleted file mode 100644
index d0ae6cd..0000000
--- a/dhcpv6/future.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package dhcpv6
-
-import (
- "errors"
- "time"
-)
-
-// Response represents a value which Future resolves to
-type Response interface {
- Value() DHCPv6
- Error() error
-}
-
-// Future is a result of an asynchronous DHCPv6 call
-type Future (<-chan Response)
-
-// SuccessFun can be used as a success callback
-type SuccessFun func(val DHCPv6) Future
-
-// FailureFun can be used as a failure callback
-type FailureFun func(err error) Future
-
-type response struct {
- val DHCPv6
- err error
-}
-
-func (r *response) Value() DHCPv6 {
- return r.val
-}
-
-func (r *response) Error() error {
- return r.err
-}
-
-// NewFuture creates a new future, which can be written to
-func NewFuture() chan Response {
- return make(chan Response, 1)
-}
-
-// NewResponse creates a new future response
-func NewResponse(val DHCPv6, err error) Response {
- return &response{val: val, err: err}
-}
-
-// NewSuccessFuture creates a future that resolves to a value
-func NewSuccessFuture(val DHCPv6) Future {
- f := NewFuture()
- go func() {
- f <- NewResponse(val, nil)
- }()
- return f
-}
-
-// NewFailureFuture creates a future that resolves to an error
-func NewFailureFuture(err error) Future {
- f := NewFuture()
- go func() {
- f <- NewResponse(nil, err)
- }()
- return f
-}
-
-// Then allows to chain the futures executing appropriate function depending
-// on the previous future value
-func (f Future) Then(success SuccessFun, failure FailureFun) Future {
- g := NewFuture()
- go func() {
- r := <-f
- if r.Error() != nil {
- r = <-failure(r.Error())
- g <- r
- } else {
- r = <-success(r.Value())
- g <- r
- }
- }()
- return g
-}
-
-// OnSuccess allows to chain the futures executing next one only if the first
-// one succeeds
-func (f Future) OnSuccess(success SuccessFun) Future {
- return f.Then(success, func(err error) Future {
- return NewFailureFuture(err)
- })
-}
-
-// OnFailure allows to chain the futures executing next one only if the first
-// one fails
-func (f Future) OnFailure(failure FailureFun) Future {
- return f.Then(func(val DHCPv6) Future {
- return NewSuccessFuture(val)
- }, failure)
-}
-
-// Wait blocks the execution until a future resolves
-func (f Future) Wait() (DHCPv6, error) {
- r := <-f
- return r.Value(), r.Error()
-}
-
-// WaitTimeout blocks the execution until a future resolves or times out
-func (f Future) WaitTimeout(timeout time.Duration) (DHCPv6, error) {
- select {
- case r := <-f:
- return r.Value(), r.Error()
- case <-time.After(timeout):
- return nil, errors.New("Timed out")
- }
-}
diff --git a/dhcpv6/future_test.go b/dhcpv6/future_test.go
deleted file mode 100644
index bee87e3..0000000
--- a/dhcpv6/future_test.go
+++ /dev/null
@@ -1,170 +0,0 @@
-package dhcpv6
-
-import (
- "errors"
- "testing"
- "time"
-
- "github.com/stretchr/testify/require"
-)
-
-func TestResponseValue(t *testing.T) {
- m, err := NewMessage()
- require.NoError(t, err)
- r := NewResponse(m, nil)
- require.Equal(t, r.Value(), m)
- require.Equal(t, r.Error(), nil)
-}
-
-func TestResponseError(t *testing.T) {
- e := errors.New("Test error")
- r := NewResponse(nil, e)
- require.Equal(t, r.Value(), nil)
- require.Equal(t, r.Error(), e)
-}
-
-func TestSuccessFuture(t *testing.T) {
- m, err := NewMessage()
- require.NoError(t, err)
- f := NewSuccessFuture(m)
-
- val, err := f.Wait()
- require.NoError(t, err)
- require.Equal(t, val, m)
-}
-
-func TestFailureFuture(t *testing.T) {
- e := errors.New("Test error")
- f := NewFailureFuture(e)
-
- val, err := f.Wait()
- require.Equal(t, err, e)
- require.Equal(t, val, nil)
-}
-
-func TestThenSuccess(t *testing.T) {
- m, err := NewMessage()
- require.NoError(t, err)
- s, err := NewMessage()
- require.NoError(t, err)
- e := errors.New("Test error")
-
- f := NewSuccessFuture(m).
- Then(func(_ DHCPv6) Future {
- return NewSuccessFuture(s)
- }, func(_ error) Future {
- return NewFailureFuture(e)
- })
-
- val, err := f.Wait()
- require.NoError(t, err)
- require.NotEqual(t, val, m)
- require.Equal(t, val, s)
-}
-
-func TestThenFailure(t *testing.T) {
- m, err := NewMessage()
- require.NoError(t, err)
- s, err := NewMessage()
- require.NoError(t, err)
- e := errors.New("Test error")
- e2 := errors.New("Test error 2")
-
- f := NewFailureFuture(e).
- Then(func(_ DHCPv6) Future {
- return NewSuccessFuture(s)
- }, func(_ error) Future {
- return NewFailureFuture(e2)
- })
-
- val, err := f.Wait()
- require.Error(t, err)
- require.NotEqual(t, val, m)
- require.NotEqual(t, val, s)
- require.NotEqual(t, err, e)
- require.Equal(t, err, e2)
-}
-
-func TestOnSuccess(t *testing.T) {
- m, err := NewMessage()
- require.NoError(t, err)
- s, err := NewMessage()
- require.NoError(t, err)
-
- f := NewSuccessFuture(m).
- OnSuccess(func(_ DHCPv6) Future {
- return NewSuccessFuture(s)
- })
-
- val, err := f.Wait()
- require.NoError(t, err)
- require.NotEqual(t, val, m)
- require.Equal(t, val, s)
-}
-
-func TestOnSuccessForFailureFuture(t *testing.T) {
- m, err := NewMessage()
- require.NoError(t, err)
- e := errors.New("Test error")
-
- f := NewFailureFuture(e).
- OnSuccess(func(_ DHCPv6) Future {
- return NewSuccessFuture(m)
- })
-
- val, err := f.Wait()
- require.Error(t, err)
- require.Equal(t, err, e)
- require.NotEqual(t, val, m)
-}
-
-func TestOnFailure(t *testing.T) {
- m, err := NewMessage()
- require.NoError(t, err)
- s, err := NewMessage()
- require.NoError(t, err)
- e := errors.New("Test error")
-
- f := NewFailureFuture(e).
- OnFailure(func(_ error) Future {
- return NewSuccessFuture(s)
- })
-
- val, err := f.Wait()
- require.NoError(t, err)
- require.NotEqual(t, val, m)
- require.Equal(t, val, s)
-}
-
-func TestOnFailureForSuccessFuture(t *testing.T) {
- m, err := NewMessage()
- require.NoError(t, err)
- s, err := NewMessage()
- require.NoError(t, err)
-
- f := NewSuccessFuture(m).
- OnFailure(func(_ error) Future {
- return NewSuccessFuture(s)
- })
-
- val, err := f.Wait()
- require.NoError(t, err)
- require.NotEqual(t, val, s)
- require.Equal(t, val, m)
-}
-
-func TestWaitTimeout(t *testing.T) {
- m, err := NewMessage()
- require.NoError(t, err)
- s, err := NewMessage()
- require.NoError(t, err)
- f := NewSuccessFuture(m).OnSuccess(func(_ DHCPv6) Future {
- time.Sleep(1 * time.Second)
- return NewSuccessFuture(s)
- })
- val, err := f.WaitTimeout(50 * time.Millisecond)
- require.Error(t, err)
- require.Equal(t, err.Error(), "Timed out")
- require.NotEqual(t, val, m)
- require.NotEqual(t, val, s)
-}
diff --git a/dhcpv6/modifiers.go b/dhcpv6/modifiers.go
index 32d11ac..6cd66db 100644
--- a/dhcpv6/modifiers.go
+++ b/dhcpv6/modifiers.go
@@ -2,6 +2,8 @@ package dhcpv6
import (
"log"
+
+ "github.com/insomniacslk/dhcp/iana"
)
// WithClientID adds a client ID option to a DHCPv6 packet
@@ -53,9 +55,9 @@ func WithUserClass(uc []byte) Modifier {
}
// WithArchType adds an arch type option to the packet
-func WithArchType(at ArchType) Modifier {
+func WithArchType(at iana.ArchType) Modifier {
return func(d DHCPv6) DHCPv6 {
- ao := OptClientArchType{ArchType: at}
+ ao := OptClientArchType{ArchTypes: []iana.ArchType{at}}
d.AddOption(&ao)
return d
}
diff --git a/dhcpv6/option_archtype.go b/dhcpv6/option_archtype.go
index a1b4a9b..231eddd 100644
--- a/dhcpv6/option_archtype.go
+++ b/dhcpv6/option_archtype.go
@@ -6,42 +6,14 @@ package dhcpv6
import (
"encoding/binary"
"fmt"
-)
-
-//ArchType encodes an architecture type in an uint16
-type ArchType uint16
+ "strings"
-// see rfc4578
-const (
- INTEL_X86PC ArchType = 0
- NEC_PC98 ArchType = 1
- EFI_ITANIUM ArchType = 2
- DEC_ALPHA ArchType = 3
- ARC_X86 ArchType = 4
- INTEL_LEAN_CLIENT ArchType = 5
- EFI_IA32 ArchType = 6
- EFI_BC ArchType = 7
- EFI_XSCALE ArchType = 8
- EFI_X86_64 ArchType = 9
+ "github.com/insomniacslk/dhcp/iana"
)
-// ArchTypeToStringMap maps an ArchType to a mnemonic name
-var ArchTypeToStringMap = map[ArchType]string{
- INTEL_X86PC: "Intel x86PC",
- NEC_PC98: "NEC/PC98",
- EFI_ITANIUM: "EFI Itanium",
- DEC_ALPHA: "DEC Alpha",
- ARC_X86: "Arc x86",
- INTEL_LEAN_CLIENT: "Intel Lean Client",
- EFI_IA32: "EFI IA32",
- EFI_BC: "EFI BC",
- EFI_XSCALE: "EFI Xscale",
- EFI_X86_64: "EFI x86-64",
-}
-
// OptClientArchType represents an option CLIENT_ARCH_TYPE
type OptClientArchType struct {
- ArchType ArchType
+ ArchTypes []iana.ArchType
}
func (op *OptClientArchType) Code() OptionCode {
@@ -49,23 +21,28 @@ func (op *OptClientArchType) Code() OptionCode {
}
func (op *OptClientArchType) ToBytes() []byte {
- buf := make([]byte, 6)
+ buf := make([]byte, 4)
binary.BigEndian.PutUint16(buf[0:2], uint16(OptionClientArchType))
binary.BigEndian.PutUint16(buf[2:4], uint16(op.Length()))
- binary.BigEndian.PutUint16(buf[4:6], uint16(op.ArchType))
+ u16 := make([]byte, 2)
+ for _, at := range op.ArchTypes {
+ binary.BigEndian.PutUint16(u16, uint16(at))
+ buf = append(buf, u16...)
+ }
return buf
}
func (op *OptClientArchType) Length() int {
- return 2
+ return 2*len(op.ArchTypes)
}
func (op *OptClientArchType) String() string {
- name, ok := ArchTypeToStringMap[op.ArchType]
- if !ok {
- name = "Unknown"
+ atStrings := make([]string, 0)
+ for _, at := range op.ArchTypes {
+ name := iana.ArchTypeToString(at)
+ atStrings = append(atStrings, name)
}
- return fmt.Sprintf("OptClientArchType{archtype=%v}", name)
+ return fmt.Sprintf("OptClientArchType{archtype=%v}", strings.Join(atStrings, ", "))
}
// ParseOptClientArchType builds an OptClientArchType structure from
@@ -73,9 +50,12 @@ func (op *OptClientArchType) String() string {
// length bytes.
func ParseOptClientArchType(data []byte) (*OptClientArchType, error) {
opt := OptClientArchType{}
- if len(data) != 2 {
- return nil, fmt.Errorf("Invalid arch type data length. Expected 2 bytes, got %v", len(data))
+ if len(data) == 0 || len(data)%2 != 0 {
+ return nil, fmt.Errorf("Invalid arch type data length. Expected multiple of 2 larger than 2, got %v", len(data))
+ }
+ for idx := 0; idx < len(data); idx += 2 {
+ b := data[idx : idx+2]
+ opt.ArchTypes = append(opt.ArchTypes, iana.ArchType(binary.BigEndian.Uint16(b)))
}
- opt.ArchType = ArchType(binary.BigEndian.Uint16(data))
return &opt, nil
}
diff --git a/dhcpv6/option_archtype_test.go b/dhcpv6/option_archtype_test.go
index 748c8c5..1848e55 100644
--- a/dhcpv6/option_archtype_test.go
+++ b/dhcpv6/option_archtype_test.go
@@ -3,6 +3,7 @@ package dhcpv6
import (
"testing"
+ "github.com/insomniacslk/dhcp/iana"
"github.com/stretchr/testify/require"
)
@@ -12,7 +13,7 @@ func TestParseOptClientArchType(t *testing.T) {
}
opt, err := ParseOptClientArchType(data)
require.NoError(t, err)
- require.Equal(t, opt.ArchType, EFI_IA32)
+ require.Equal(t, opt.ArchTypes[0], iana.EFI_IA32)
}
func TestParseOptClientArchTypeInvalid(t *testing.T) {
@@ -37,7 +38,7 @@ func TestOptClientArchTypeParseAndToBytes(t *testing.T) {
func TestOptClientArchType(t *testing.T) {
opt := OptClientArchType{
- ArchType: EFI_ITANIUM,
+ ArchTypes: []iana.ArchType{iana.EFI_ITANIUM},
}
require.Equal(t, opt.Length(), 2)
require.Equal(t, opt.Code(), OptionClientArchType)
diff --git a/dhcpv6/utils.go b/dhcpv6/utils.go
deleted file mode 100644
index 1681661..0000000
--- a/dhcpv6/utils.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package dhcpv6
-
-import (
- "errors"
- "strings"
-)
-
-// IsNetboot function takes a DHCPv6 message and returns true if the machine
-// is trying to netboot. It checks if "boot file" is one of the requested
-// options, which is useful for SOLICIT/REQUEST packet types, it also checks
-// if the "boot file" option is included in the packet, which is useful for
-// ADVERTISE/REPLY packet.
-func IsNetboot(msg DHCPv6) bool {
- for _, optoro := range msg.GetOption(OptionORO) {
- for _, o := range optoro.(*OptRequestedOption).RequestedOptions() {
- if o == OptionBootfileURL {
- return true
- }
- }
- }
- if optbf := msg.GetOneOption(OptionBootfileURL); optbf != nil {
- return true
- }
- return false
-}
-
-// IsUsingUEFI function takes a DHCPv6 message and returns true if
-// the machine trying to netboot is using UEFI of false if it is not.
-func IsUsingUEFI(msg DHCPv6) bool {
- // RFC 4578 says:
- // As of the writing of this document, the following pre-boot
- // architecture types have been requested.
- // Type Architecture Name
- // ---- -----------------
- // 0 Intel x86PC
- // 1 NEC/PC98
- // 2 EFI Itanium
- // 3 DEC Alpha
- // 4 Arc x86
- // 5 Intel Lean Client
- // 6 EFI IA32
- // 7 EFI BC
- // 8 EFI Xscale
- // 9 EFI x86-64
- if opt := msg.GetOneOption(OptionClientArchType); opt != nil {
- optat := opt.(*OptClientArchType)
- // TODO investigate if other types are appropriate
- if optat.ArchType == EFI_BC || optat.ArchType == EFI_X86_64 {
- return true
- }
- }
- if opt := msg.GetOneOption(OptionUserClass); opt != nil {
- optuc := opt.(*OptUserClass)
- for _, uc := range optuc.UserClasses {
- if strings.Contains(string(uc), "EFI") {
- return true
- }
- }
- }
- return false
-}
-
-// GetTransactionID returns a transactionID of a message or its inner message
-// in case of relay
-func GetTransactionID(packet DHCPv6) (uint32, error) {
- if message, ok := packet.(*DHCPv6Message); ok {
- return message.TransactionID(), nil
- }
- if relay, ok := packet.(*DHCPv6Relay); ok {
- message, err := relay.GetInnerMessage()
- if err != nil {
- return 0, err
- }
- return GetTransactionID(message)
- }
- return 0, errors.New("Invalid DHCPv6 packet")
-}
diff --git a/dhcpv6/utils_test.go b/dhcpv6/utils_test.go
deleted file mode 100644
index f3b53f0..0000000
--- a/dhcpv6/utils_test.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package dhcpv6
-
-import (
- "testing"
-
- "github.com/stretchr/testify/require"
-)
-
-func TestIsNetboot(t *testing.T) {
- msg1 := DHCPv6Message{}
- require.False(t, IsNetboot(&msg1))
-
- msg2 := DHCPv6Message{}
- optro := OptRequestedOption{}
- optro.AddRequestedOption(OptionBootfileURL)
- msg2.AddOption(&optro)
- require.True(t, IsNetboot(&msg2))
-
- msg3 := DHCPv6Message{}
- optbf := OptBootFileURL{}
- msg3.AddOption(&optbf)
- require.True(t, IsNetboot(&msg3))
-}
-
-func TestIsUsingUEFIArchTypeTrue(t *testing.T) {
- msg := DHCPv6Message{}
- opt := OptClientArchType{ArchType: EFI_BC}
- msg.AddOption(&opt)
- require.True(t, IsUsingUEFI(&msg))
-}
-
-func TestIsUsingUEFIArchTypeFalse(t *testing.T) {
- msg := DHCPv6Message{}
- opt := OptClientArchType{ArchType: INTEL_X86PC}
- msg.AddOption(&opt)
- require.False(t, IsUsingUEFI(&msg))
-}
-
-func TestIsUsingUEFIUserClassTrue(t *testing.T) {
- msg := DHCPv6Message{}
- opt := OptUserClass{UserClasses: [][]byte{[]byte("ipxeUEFI")}}
- msg.AddOption(&opt)
- require.True(t, IsUsingUEFI(&msg))
-}
-
-func TestIsUsingUEFIUserClassFalse(t *testing.T) {
- msg := DHCPv6Message{}
- opt := OptUserClass{UserClasses: [][]byte{[]byte("ipxeLegacy")}}
- msg.AddOption(&opt)
- require.False(t, IsUsingUEFI(&msg))
-}
-
-func TestGetTransactionIDMessage(t *testing.T) {
- message, err := NewMessage()
- require.NoError(t, err)
- transactionID, err := GetTransactionID(message)
- require.NoError(t, err)
- require.Equal(t, transactionID, message.(*DHCPv6Message).TransactionID())
-}
-
-func TestGetTransactionIDRelay(t *testing.T) {
- message, err := NewMessage()
- require.NoError(t, err)
- relay, err := EncapsulateRelay(message, MessageTypeRelayForward, nil, nil)
- require.NoError(t, err)
- transactionID, err := GetTransactionID(relay)
- require.NoError(t, err)
- require.Equal(t, transactionID, message.(*DHCPv6Message).TransactionID())
-}
diff --git a/iana/archtype.go b/iana/archtype.go
new file mode 100644
index 0000000..6b2cc9e
--- /dev/null
+++ b/iana/archtype.go
@@ -0,0 +1,41 @@
+package iana
+
+//ArchType encodes an architecture type in an uint16
+type ArchType uint16
+
+// see rfc4578
+const (
+ INTEL_X86PC ArchType = 0
+ NEC_PC98 ArchType = 1
+ EFI_ITANIUM ArchType = 2
+ DEC_ALPHA ArchType = 3
+ ARC_X86 ArchType = 4
+ INTEL_LEAN_CLIENT ArchType = 5
+ EFI_IA32 ArchType = 6
+ EFI_BC ArchType = 7
+ EFI_XSCALE ArchType = 8
+ EFI_X86_64 ArchType = 9
+)
+
+// ArchTypeToStringMap maps an ArchType to a mnemonic name
+var ArchTypeToStringMap = map[ArchType]string{
+ INTEL_X86PC: "Intel x86PC",
+ NEC_PC98: "NEC/PC98",
+ EFI_ITANIUM: "EFI Itanium",
+ DEC_ALPHA: "DEC Alpha",
+ ARC_X86: "Arc x86",
+ INTEL_LEAN_CLIENT: "Intel Lean Client",
+ EFI_IA32: "EFI IA32",
+ EFI_BC: "EFI BC",
+ EFI_XSCALE: "EFI Xscale",
+ EFI_X86_64: "EFI x86-64",
+}
+
+
+// ArchTypeToString returns a mnemonic name for a given architecture type
+func ArchTypeToString(a ArchType) string {
+ if at := ArchTypeToStringMap[a]; at != "" {
+ return at
+ }
+ return "Unknown"
+}
diff --git a/netboot/netboot.go b/netboot/netboot.go
index 81ba144..2f4cc8f 100644
--- a/netboot/netboot.go
+++ b/netboot/netboot.go
@@ -15,7 +15,6 @@ func RequestNetbootv6(ifname string, timeout time.Duration, retries int, modifie
var (
conversation []dhcpv6.DHCPv6
)
- modifiers = append(modifiers, dhcpv6.WithNetboot)
delay := 2 * time.Second
for i := 0; i <= retries; i++ {
log.Printf("sending request, attempt #%d", i+1)
@@ -26,6 +25,9 @@ func RequestNetbootv6(ifname string, timeout time.Duration, retries int, modifie
client := dhcpv6.NewClient()
client.ReadTimeout = timeout
+ // WithNetboot is added only later, to avoid applying it twice (one
+ // here and one in the above call to NewSolicitForInterface)
+ modifiers = append(modifiers, dhcpv6.WithNetboot)
conversation, err = client.Exchange(ifname, solicit, modifiers...)
if err != nil {
log.Printf("Client.Exchange failed: %v", err)