dhcp: send 'server identifier' on request
The 'server identifier' option on a DHCPREQUEST must be the value of
the option sent by the server on the DHCPOFFER. Before this CL, we
were mistakenly using the siaddr field of the packet as the
identifier.
To cover this case in our unit tests, we fix our server to always
provide an siaddr of 0, and then require the client to send the
correct 'server identifier' option. (Just the server changes from
this CL cause the tests to fail.)
Also allow the DNS option to be any multiple of four, as DHCP
servers can send more than DNS server.
Change-Id: I40ff359f739c1129558e316edcee50de13e56446
diff --git a/dhcp/client.go b/dhcp/client.go
index b95b68f..4ba3fff 100644
--- a/dhcp/client.go
+++ b/dhcp/client.go
@@ -94,7 +94,7 @@
// On success, it adds a new address to this client's TCPIP stack.
// If the server sets a lease limit a timer is set to automatically
// renew it.
-func (c *Client) Request(ctx context.Context, requestedAddr tcpip.Address) error {
+func (c *Client) Request(ctx context.Context, requestedAddr tcpip.Address) (reterr error) {
if err := c.stack.AddAddress(c.nicid, ipv4.ProtocolNumber, "\xff\xff\xff\xff"); err != nil && err != tcpip.ErrDuplicateAddress {
return fmt.Errorf("dhcp: %v", err)
}
@@ -187,12 +187,16 @@
break
}
}
- if _, err := h.options(); err != nil {
+ opts, err := h.options()
+ if err != nil {
return fmt.Errorf("dhcp offer: %v", err)
}
var ack bool
var cfg Config
+ if err := cfg.decode(opts); err != nil {
+ return fmt.Errorf("dhcp offer: %v", err)
+ }
// DHCPREQUEST
addr := tcpip.Address(h.yiaddr())
@@ -202,9 +206,10 @@
}
}
defer func() {
- if !ack {
+ if !ack || reterr != nil {
c.stack.RemoveAddress(c.nicid, addr)
addr = ""
+ cfg = Config{Error: reterr}
}
c.mu.Lock()
@@ -227,7 +232,7 @@
h.setOptions([]option{
{optDHCPMsgType, []byte{byte(dhcpREQUEST)}},
{optReqIPAddr, []byte(addr)},
- {optDHCPServer, h.siaddr()},
+ {optDHCPServer, []byte(cfg.ServerAddress)},
})
if _, err := ep.Write([]byte(h), serverAddr); err != nil {
return fmt.Errorf("dhcp discovery write: %v", err)
@@ -250,7 +255,7 @@
break
}
}
- opts, err := h.options()
+ opts, err = h.options()
if err != nil {
return fmt.Errorf("dhcp ack: %v", err)
}
diff --git a/dhcp/dhcp.go b/dhcp/dhcp.go
index 09937b8..63d0184 100644
--- a/dhcp/dhcp.go
+++ b/dhcp/dhcp.go
@@ -16,6 +16,7 @@
// Config is standard DHCP configuration.
type Config struct {
+ Error error
ServerAddress tcpip.Address // address of the server
SubnetMask tcpip.AddressMask // client address subnet mask
Gateway tcpip.Address // client default gateway
@@ -27,7 +28,7 @@
*cfg = Config{}
for _, opt := range opts {
b := opt.body
- if l := opt.code.len(); l != -1 && l != len(b) {
+ if !opt.code.lenValid(len(b)) {
return fmt.Errorf("%s bad length: %d", opt.code, len(b))
}
switch opt.code {
@@ -41,7 +42,8 @@
case optDefaultGateway:
cfg.Gateway = tcpip.Address(b)
case optDomainNameServer:
- cfg.DomainNameServer = tcpip.Address(b)
+ // TODO(crawshaw): handle multiple DNS servers
+ cfg.DomainNameServer = tcpip.Address(b[:4])
}
}
return nil
@@ -179,17 +181,19 @@
optParamReq optionCode = 55
)
-func (code optionCode) len() int {
+func (code optionCode) lenValid(l int) bool {
switch code {
- case optSubnetMask, optDefaultGateway, optDomainNameServer,
+ case optSubnetMask, optDefaultGateway,
optReqIPAddr, optLeaseTime, optDHCPServer:
- return 4
+ return l == 4
case optDHCPMsgType:
- return 1
+ return l == 1
+ case optDomainNameServer:
+ return l%4 == 0
case optParamReq:
- return -1 // no fixed length
+ return true // no fixed length
default:
- return -1
+ return true // unknown option, assume ok
}
}
@@ -204,7 +208,7 @@
case optReqIPAddr:
return "option(request-ip-address)"
case optLeaseTime:
- return "option(least-time)"
+ return "option(lease-time)"
case optDHCPMsgType:
return "option(message-type)"
case optDHCPServer:
@@ -222,7 +226,7 @@
for _, opt := range opts {
if opt.code == optDHCPMsgType {
if len(opt.body) != 1 {
- return 0, fmt.Errorf("%s: bad length: %d", optDHCPMsgType, len(opt.body))
+ return 0, fmt.Errorf("%s: wrong length: %d", optDHCPMsgType, len(opt.body))
}
v := opt.body[0]
if v <= 0 || v >= 8 {
diff --git a/dhcp/dhcp_test.go b/dhcp/dhcp_test.go
index 1b9438b..9a23f78 100644
--- a/dhcp/dhcp_test.go
+++ b/dhcp/dhcp_test.go
@@ -134,11 +134,14 @@
var curAddr tcpip.Address
addrCh := make(chan tcpip.Address)
acquiredFunc := func(oldAddr, newAddr tcpip.Address, cfg Config) {
+ if err := cfg.Error; err != nil {
+ t.Fatalf("acquisition %d failed: %v", count, err)
+ }
if oldAddr != curAddr {
- t.Fatalf("aquisition %d: curAddr=%v, oldAddr=%v", curAddr, oldAddr)
+ t.Fatalf("aquisition %d: curAddr=%v, oldAddr=%v", count, curAddr, oldAddr)
}
if cfg.LeaseLength != time.Second {
- t.Fatalf("aquisition %d: lease length: %v, want %v", cfg.LeaseLength, time.Second)
+ t.Fatalf("aquisition %d: lease length: %v, want %v", count, cfg.LeaseLength, time.Second)
}
count++
curAddr = newAddr
diff --git a/dhcp/server.go b/dhcp/server.go
index 4f525e6..90ec04b 100644
--- a/dhcp/server.go
+++ b/dhcp/server.go
@@ -38,6 +38,9 @@
// NewServer creates a new DHCP server and begins serving.
// The server continues serving until ctx is done.
func NewServer(ctx context.Context, stack *stack.Stack, addrs []tcpip.Address, cfg Config) (*Server, error) {
+ if cfg.ServerAddress == "" {
+ return nil, fmt.Errorf("dhcp: server requires explicit server address")
+ }
s := &Server{
stack: stack,
addrs: addrs,
@@ -230,7 +233,21 @@
h.setOp(opReply)
copy(h.xidbytes(), hreq.xidbytes())
copy(h.yiaddr(), lease.addr)
- copy(h.siaddr(), s.cfg.ServerAddress)
+ copy(h.chaddr(), hreq.chaddr())
+ h.setOptions(opts)
+ s.ep.Write(buffer.View(h), &s.broadcast)
+}
+
+func (s *Server) nack(hreq header) {
+ // DHCPNACK
+ opts := options([]option{
+ {optDHCPMsgType, []byte{byte(dhcpNAK)}},
+ {optDHCPServer, []byte(s.cfg.ServerAddress)},
+ })
+ h := make(header, headerBaseSize+opts.len())
+ h.init()
+ h.setOp(opReply)
+ copy(h.xidbytes(), hreq.xidbytes())
copy(h.chaddr(), hreq.chaddr())
h.setOptions(opts)
s.ep.Write(buffer.View(h), &s.broadcast)
@@ -240,6 +257,21 @@
linkAddr := tcpip.LinkAddress(hreq.chaddr()[:6])
xid := hreq.xid()
+ reqopts, err := hreq.options()
+ if err != nil {
+ s.nack(hreq)
+ return
+ }
+ var reqcfg Config
+ if err := reqcfg.decode(reqopts); err != nil {
+ s.nack(hreq)
+ return
+ }
+ if reqcfg.ServerAddress != s.cfg.ServerAddress {
+ s.nack(hreq)
+ return
+ }
+
s.mu.Lock()
lease := s.leases[linkAddr]
switch lease.state {
@@ -267,7 +299,6 @@
h.setOp(opReply)
copy(h.xidbytes(), hreq.xidbytes())
copy(h.yiaddr(), lease.addr)
- copy(h.siaddr(), s.cfg.ServerAddress)
copy(h.chaddr(), hreq.chaddr())
h.setOptions(opts)
s.ep.Write(buffer.View(h), &s.broadcast)