summaryrefslogtreecommitdiffhomepage
path: root/pkg/dhcp
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/dhcp')
-rw-r--r--pkg/dhcp/BUILD41
-rw-r--r--pkg/dhcp/client.go348
-rw-r--r--pkg/dhcp/dhcp.go280
-rw-r--r--pkg/dhcp/dhcp_string.go115
-rw-r--r--pkg/dhcp/dhcp_test.go322
-rw-r--r--pkg/dhcp/server.go393
6 files changed, 0 insertions, 1499 deletions
diff --git a/pkg/dhcp/BUILD b/pkg/dhcp/BUILD
deleted file mode 100644
index ac39e04d7..000000000
--- a/pkg/dhcp/BUILD
+++ /dev/null
@@ -1,41 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "dhcp",
- srcs = [
- "client.go",
- "dhcp.go",
- "dhcp_string.go",
- "server.go",
- ],
- importpath = "gvisor.googlesource.com/gvisor/pkg/dhcp",
- deps = [
- "//pkg/rand",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/udp",
- "//pkg/waiter",
- ],
-)
-
-go_test(
- name = "dhcp_test",
- size = "small",
- srcs = ["dhcp_test.go"],
- embed = [":dhcp"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/link/channel",
- "//pkg/tcpip/link/sniffer",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/udp",
- "//pkg/waiter",
- ],
-)
diff --git a/pkg/dhcp/client.go b/pkg/dhcp/client.go
deleted file mode 100644
index b7cde3819..000000000
--- a/pkg/dhcp/client.go
+++ /dev/null
@@ -1,348 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// 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.
-
-package dhcp
-
-import (
- "bytes"
- "context"
- "fmt"
- "sync"
- "time"
-
- "gvisor.googlesource.com/gvisor/pkg/rand"
- "gvisor.googlesource.com/gvisor/pkg/tcpip"
- tcpipHeader "gvisor.googlesource.com/gvisor/pkg/tcpip/header"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/stack"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/transport/udp"
- "gvisor.googlesource.com/gvisor/pkg/waiter"
-)
-
-// Client is a DHCP client.
-type Client struct {
- stack *stack.Stack
- nicid tcpip.NICID
- linkAddr tcpip.LinkAddress
- acquiredFunc func(old, new tcpip.Address, cfg Config)
-
- mu sync.Mutex
- addr tcpip.Address
- cfg Config
- lease time.Duration
- cancelRenew func()
-}
-
-// NewClient creates a DHCP client.
-//
-// TODO: add s.LinkAddr(nicid) to *stack.Stack.
-func NewClient(s *stack.Stack, nicid tcpip.NICID, linkAddr tcpip.LinkAddress, acquiredFunc func(old, new tcpip.Address, cfg Config)) *Client {
- return &Client{
- stack: s,
- nicid: nicid,
- linkAddr: linkAddr,
- acquiredFunc: acquiredFunc,
- }
-}
-
-// Run starts the DHCP client.
-// It will periodically search for an IP address using the Request method.
-func (c *Client) Run(ctx context.Context) {
- go c.run(ctx)
-}
-
-func (c *Client) run(ctx context.Context) {
- defer func() {
- c.mu.Lock()
- defer c.mu.Unlock()
- if c.addr != "" {
- c.stack.RemoveAddress(c.nicid, c.addr)
- }
- }()
-
- var renewAddr tcpip.Address
- for {
- reqCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
- cfg, err := c.Request(reqCtx, renewAddr)
- cancel()
- if err != nil {
- select {
- case <-time.After(1 * time.Second):
- // loop and try again
- case <-ctx.Done():
- return
- }
- }
-
- c.mu.Lock()
- renewAddr = c.addr
- c.mu.Unlock()
-
- timer := time.NewTimer(cfg.LeaseLength)
- select {
- case <-ctx.Done():
- timer.Stop()
- return
- case <-timer.C:
- // loop and make a renewal request
- }
- }
-}
-
-// Address reports the IP address acquired by the DHCP client.
-func (c *Client) Address() tcpip.Address {
- c.mu.Lock()
- defer c.mu.Unlock()
- return c.addr
-}
-
-// Config reports the DHCP configuration acquired with the IP address lease.
-func (c *Client) Config() Config {
- c.mu.Lock()
- defer c.mu.Unlock()
- return c.cfg
-}
-
-// Request executes a DHCP request session.
-//
-// 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) (cfg Config, reterr error) {
- // TODO(b/127321246): remove calls to {Add,Remove}Address when they're no
- // longer required to send and receive broadcast.
- if err := c.stack.AddAddressWithOptions(c.nicid, ipv4.ProtocolNumber, tcpipHeader.IPv4Any, stack.NeverPrimaryEndpoint); err != nil && err != tcpip.ErrDuplicateAddress {
- return Config{}, fmt.Errorf("dhcp: AddAddressWithOptions(): %s", err)
- }
- defer c.stack.RemoveAddress(c.nicid, tcpipHeader.IPv4Any)
-
- var wq waiter.Queue
- ep, err := c.stack.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
- if err != nil {
- return Config{}, fmt.Errorf("dhcp: NewEndpoint(): %s", err)
- }
- defer ep.Close()
- if err := ep.SetSockOpt(tcpip.BroadcastOption(1)); err != nil {
- return Config{}, fmt.Errorf("dhcp: SetSockOpt(BroadcastOption): %s", err)
- }
- if err := ep.Bind(tcpip.FullAddress{
- Addr: tcpipHeader.IPv4Any,
- Port: ClientPort,
- NIC: c.nicid,
- }); err != nil {
- return Config{}, fmt.Errorf("dhcp: Bind(): %s", err)
- }
-
- var xid [4]byte
- if _, err := rand.Read(xid[:]); err != nil {
- return Config{}, fmt.Errorf("dhcp: rand.Read(): %s", err)
- }
-
- // DHCPDISCOVERY
- discOpts := options{
- {optDHCPMsgType, []byte{byte(dhcpDISCOVER)}},
- {optParamReq, []byte{
- 1, // request subnet mask
- 3, // request router
- 15, // domain name
- 6, // domain name server
- }},
- }
- if requestedAddr != "" {
- discOpts = append(discOpts, option{optReqIPAddr, []byte(requestedAddr)})
- }
- var clientID []byte
- if len(c.linkAddr) == 6 {
- clientID = append(
- []byte{1}, // RFC 1700: Hardware Type [Ethernet = 1]
- c.linkAddr...,
- )
- discOpts = append(discOpts, option{optClientID, clientID})
- }
- h := make(header, headerBaseSize+discOpts.len()+1)
- h.init()
- h.setOp(opRequest)
- copy(h.xidbytes(), xid[:])
- h.setBroadcast()
- copy(h.chaddr(), c.linkAddr)
- h.setOptions(discOpts)
-
- serverAddr := &tcpip.FullAddress{
- Addr: tcpipHeader.IPv4Broadcast,
- Port: ServerPort,
- NIC: c.nicid,
- }
- wopts := tcpip.WriteOptions{
- To: serverAddr,
- }
- var resCh <-chan struct{}
- if _, resCh, err = ep.Write(tcpip.SlicePayload(h), wopts); err != nil && resCh == nil {
- return Config{}, fmt.Errorf("dhcp discovery write: %v", err)
- }
-
- if resCh != nil {
- select {
- case <-resCh:
- case <-ctx.Done():
- return Config{}, fmt.Errorf("dhcp client address resolution: %v", tcpip.ErrAborted)
- }
-
- if _, _, err := ep.Write(tcpip.SlicePayload(h), wopts); err != nil {
- return Config{}, fmt.Errorf("dhcp discovery write: %v", err)
- }
- }
-
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.EventIn)
- defer wq.EventUnregister(&we)
-
- // DHCPOFFER
- var opts options
- for {
- v, _, err := ep.Read(nil)
- if err == tcpip.ErrWouldBlock {
- select {
- case <-ch:
- continue
- case <-ctx.Done():
- return Config{}, fmt.Errorf("reading dhcp offer: %v", tcpip.ErrAborted)
- }
- }
- h = header(v)
- var valid bool
- var e error
- opts, valid, e = loadDHCPReply(h, dhcpOFFER, xid[:])
- if !valid {
- if e != nil {
- // TODO: handle all the errors?
- // TODO: report malformed server responses
- }
- continue
- }
- break
- }
-
- var ack bool
- if err := cfg.decode(opts); err != nil {
- return Config{}, fmt.Errorf("dhcp offer: %v", err)
- }
-
- // DHCPREQUEST
- addr := tcpip.Address(h.yiaddr())
- if err := c.stack.AddAddressWithOptions(c.nicid, ipv4.ProtocolNumber, addr, stack.FirstPrimaryEndpoint); err != nil {
- if err != tcpip.ErrDuplicateAddress {
- return Config{}, fmt.Errorf("adding address: %v", err)
- }
- }
- defer func() {
- if !ack || reterr != nil {
- c.stack.RemoveAddress(c.nicid, addr)
- addr = ""
- cfg = Config{Error: reterr}
- }
-
- c.mu.Lock()
- oldAddr := c.addr
- c.addr = addr
- c.cfg = cfg
- c.mu.Unlock()
-
- // Clean up addresses before calling acquiredFunc
- // so nothing else uses them by mistake.
- //
- // (The deferred RemoveAddress call above silently errors.)
- c.stack.RemoveAddress(c.nicid, tcpipHeader.IPv4Any)
-
- if c.acquiredFunc != nil {
- c.acquiredFunc(oldAddr, addr, cfg)
- }
- if requestedAddr != "" && requestedAddr != addr {
- c.stack.RemoveAddress(c.nicid, requestedAddr)
- }
- }()
- h.init()
- h.setOp(opRequest)
- for i, b := 0, h.yiaddr(); i < len(b); i++ {
- b[i] = 0
- }
- for i, b := 0, h.siaddr(); i < len(b); i++ {
- b[i] = 0
- }
- for i, b := 0, h.giaddr(); i < len(b); i++ {
- b[i] = 0
- }
- reqOpts := []option{
- {optDHCPMsgType, []byte{byte(dhcpREQUEST)}},
- {optReqIPAddr, []byte(addr)},
- {optDHCPServer, []byte(cfg.ServerAddress)},
- }
- if len(clientID) != 0 {
- reqOpts = append(reqOpts, option{optClientID, clientID})
- }
- h.setOptions(reqOpts)
- if _, _, err := ep.Write(tcpip.SlicePayload(h), wopts); err != nil {
- return Config{}, fmt.Errorf("dhcp discovery write: %v", err)
- }
-
- // DHCPACK
- for {
- v, _, err := ep.Read(nil)
- if err == tcpip.ErrWouldBlock {
- select {
- case <-ch:
- continue
- case <-ctx.Done():
- return Config{}, fmt.Errorf("reading dhcp ack: %v", tcpip.ErrAborted)
- }
- }
- h = header(v)
- var valid bool
- var e error
- opts, valid, e = loadDHCPReply(h, dhcpACK, xid[:])
- if !valid {
- if e != nil {
- // TODO: handle all the errors?
- // TODO: report malformed server responses
- }
- if opts, valid, _ = loadDHCPReply(h, dhcpNAK, xid[:]); valid {
- if msg := opts.message(); msg != "" {
- return Config{}, fmt.Errorf("dhcp: NAK %q", msg)
- }
- return Config{}, fmt.Errorf("dhcp: NAK with no message")
- }
- continue
- }
- break
- }
- ack = true
- return cfg, nil
-}
-
-func loadDHCPReply(h header, typ dhcpMsgType, xid []byte) (opts options, valid bool, err error) {
- if !h.isValid() || h.op() != opReply || !bytes.Equal(h.xidbytes(), xid[:]) {
- return nil, false, nil
- }
- opts, err = h.options()
- if err != nil {
- return nil, false, err
- }
- msgtype, err := opts.dhcpMsgType()
- if err != nil {
- return nil, false, err
- }
- if msgtype != typ {
- return nil, false, nil
- }
- return opts, true, nil
-}
diff --git a/pkg/dhcp/dhcp.go b/pkg/dhcp/dhcp.go
deleted file mode 100644
index f96ffd891..000000000
--- a/pkg/dhcp/dhcp.go
+++ /dev/null
@@ -1,280 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// 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.
-
-// Package dhcp implements a DHCP client and server as described in RFC 2131.
-package dhcp
-
-import (
- "bytes"
- "encoding/binary"
- "fmt"
- "time"
-
- "gvisor.googlesource.com/gvisor/pkg/tcpip"
-)
-
-// 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
- DNS []tcpip.Address // client DNS server addresses
- LeaseLength time.Duration // length of the address lease
-}
-
-func (cfg *Config) decode(opts []option) error {
- *cfg = Config{}
- for _, opt := range opts {
- b := opt.body
- if !opt.code.lenValid(len(b)) {
- return fmt.Errorf("%s: bad length: %d", opt.code, len(b))
- }
- switch opt.code {
- case optLeaseTime:
- t := binary.BigEndian.Uint32(b)
- cfg.LeaseLength = time.Duration(t) * time.Second
- case optSubnetMask:
- cfg.SubnetMask = tcpip.AddressMask(b)
- case optDHCPServer:
- cfg.ServerAddress = tcpip.Address(b)
- case optDefaultGateway:
- cfg.Gateway = tcpip.Address(b)
- case optDomainNameServer:
- for ; len(b) > 0; b = b[4:] {
- if len(b) < 4 {
- return fmt.Errorf("DNS bad length: %d", len(b))
- }
- cfg.DNS = append(cfg.DNS, tcpip.Address(b[:4]))
- }
- }
- }
- return nil
-}
-
-func (cfg Config) encode() (opts []option) {
- if cfg.ServerAddress != "" {
- opts = append(opts, option{optDHCPServer, []byte(cfg.ServerAddress)})
- }
- if cfg.SubnetMask != "" {
- opts = append(opts, option{optSubnetMask, []byte(cfg.SubnetMask)})
- }
- if cfg.Gateway != "" {
- opts = append(opts, option{optDefaultGateway, []byte(cfg.Gateway)})
- }
- if len(cfg.DNS) > 0 {
- dns := make([]byte, 0, 4*len(cfg.DNS))
- for _, addr := range cfg.DNS {
- dns = append(dns, addr...)
- }
- opts = append(opts, option{optDomainNameServer, dns})
- }
- if l := cfg.LeaseLength / time.Second; l != 0 {
- v := make([]byte, 4)
- v[0] = byte(l >> 24)
- v[1] = byte(l >> 16)
- v[2] = byte(l >> 8)
- v[3] = byte(l >> 0)
- opts = append(opts, option{optLeaseTime, v})
- }
- return opts
-}
-
-const (
- // ServerPort is the well-known UDP port number for a DHCP server.
- ServerPort = 67
- // ClientPort is the well-known UDP port number for a DHCP client.
- ClientPort = 68
-)
-
-var magicCookie = []byte{99, 130, 83, 99} // RFC 1497
-
-type xid uint32
-
-type header []byte
-
-func (h header) init() {
- h[1] = 0x01 // htype
- h[2] = 0x06 // hlen
- h[3] = 0x00 // hops
- h[8], h[9] = 0, 0 // secs
- copy(h[236:240], magicCookie)
-}
-
-func (h header) isValid() bool {
- if len(h) < 241 {
- return false
- }
- if o := h.op(); o != opRequest && o != opReply {
- return false
- }
- if h[1] != 0x01 || h[2] != 0x06 {
- return false
- }
- return bytes.Equal(h[236:240], magicCookie)
-}
-
-func (h header) op() op { return op(h[0]) }
-func (h header) setOp(o op) { h[0] = byte(o) }
-func (h header) xidbytes() []byte { return h[4:8] }
-func (h header) xid() xid { return xid(h[4])<<24 | xid(h[5])<<16 | xid(h[6])<<8 | xid(h[7]) }
-func (h header) setBroadcast() { h[10], h[11] = 0x80, 0x00 } // flags top bit
-func (h header) ciaddr() []byte { return h[12:16] }
-func (h header) yiaddr() []byte { return h[16:20] }
-func (h header) siaddr() []byte { return h[20:24] }
-func (h header) giaddr() []byte { return h[24:28] }
-func (h header) chaddr() []byte { return h[28:44] }
-func (h header) sname() []byte { return h[44:108] }
-func (h header) file() []byte { return h[108:236] }
-
-func (h header) options() (opts options, err error) {
- i := headerBaseSize
- for i < len(h) {
- if h[i] == 0 {
- i++
- continue
- }
- if h[i] == 255 {
- break
- }
- if len(h) <= i+1 {
- return nil, fmt.Errorf("option missing length")
- }
- optlen := int(h[i+1])
- if len(h) < i+2+optlen {
- return nil, fmt.Errorf("option %v too long i=%d, optlen=%d", optionCode(h[i]), i, optlen)
- }
- opts = append(opts, option{
- code: optionCode(h[i]),
- body: h[i+2 : i+2+optlen],
- })
- i += 2 + optlen
- }
- return opts, nil
-}
-
-func (h header) setOptions(opts []option) {
- i := headerBaseSize
- for _, opt := range opts {
- h[i] = byte(opt.code)
- h[i+1] = byte(len(opt.body))
- copy(h[i+2:i+2+len(opt.body)], opt.body)
- i += 2 + len(opt.body)
- }
- h[i] = 255 // End option
- i++
- for ; i < len(h); i++ {
- h[i] = 0
- }
-}
-
-// headerBaseSize is the size of a DHCP packet, including the magic cookie.
-//
-// Note that a DHCP packet is required to have an 'end' option that takes
-// up an extra byte, so the minimum DHCP packet size is headerBaseSize + 1.
-const headerBaseSize = 240
-
-type option struct {
- code optionCode
- body []byte
-}
-
-type optionCode byte
-
-const (
- optSubnetMask optionCode = 1
- optDefaultGateway optionCode = 3
- optDomainNameServer optionCode = 6
- optDomainName optionCode = 15
- optReqIPAddr optionCode = 50
- optLeaseTime optionCode = 51
- optDHCPMsgType optionCode = 53 // dhcpMsgType
- optDHCPServer optionCode = 54
- optParamReq optionCode = 55
- optMessage optionCode = 56
- optClientID optionCode = 61
-)
-
-func (code optionCode) lenValid(l int) bool {
- switch code {
- case optSubnetMask, optDefaultGateway,
- optReqIPAddr, optLeaseTime, optDHCPServer:
- return l == 4
- case optDHCPMsgType:
- return l == 1
- case optDomainNameServer:
- return l%4 == 0
- case optMessage, optDomainName, optClientID:
- return l >= 1
- case optParamReq:
- return true // no fixed length
- default:
- return true // unknown option, assume ok
- }
-}
-
-type options []option
-
-func (opts options) dhcpMsgType() (dhcpMsgType, error) {
- for _, opt := range opts {
- if opt.code == optDHCPMsgType {
- if len(opt.body) != 1 {
- return 0, fmt.Errorf("%s: bad length: %d", opt.code, len(opt.body))
- }
- v := opt.body[0]
- if v <= 0 || v >= 8 {
- return 0, fmt.Errorf("DHCP bad length: %d", len(opt.body))
- }
- return dhcpMsgType(v), nil
- }
- }
- return 0, nil
-}
-
-func (opts options) message() string {
- for _, opt := range opts {
- if opt.code == optMessage {
- return string(opt.body)
- }
- }
- return ""
-}
-
-func (opts options) len() int {
- l := 0
- for _, opt := range opts {
- l += 1 + 1 + len(opt.body) // code + len + body
- }
- return l + 1 // extra byte for 'pad' option
-}
-
-type op byte
-
-const (
- opRequest op = 0x01
- opReply op = 0x02
-)
-
-// dhcpMsgType is the DHCP Message Type from RFC 1533, section 9.4.
-type dhcpMsgType byte
-
-const (
- dhcpDISCOVER dhcpMsgType = 1
- dhcpOFFER dhcpMsgType = 2
- dhcpREQUEST dhcpMsgType = 3
- dhcpDECLINE dhcpMsgType = 4
- dhcpACK dhcpMsgType = 5
- dhcpNAK dhcpMsgType = 6
- dhcpRELEASE dhcpMsgType = 7
-)
diff --git a/pkg/dhcp/dhcp_string.go b/pkg/dhcp/dhcp_string.go
deleted file mode 100644
index 29ce98593..000000000
--- a/pkg/dhcp/dhcp_string.go
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// 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.
-
-package dhcp
-
-import (
- "bytes"
- "fmt"
-
- "gvisor.googlesource.com/gvisor/pkg/tcpip"
-)
-
-func (h header) String() string {
- opts, err := h.options()
- var msgtype dhcpMsgType
- if err == nil {
- msgtype, err = opts.dhcpMsgType()
- }
- if !h.isValid() || err != nil {
- return fmt.Sprintf("DHCP invalid, %v %v h[1:4]=%x cookie=%x len=%d (%v)", h.op(), h.xid(), []byte(h[1:4]), []byte(h[236:240]), len(h), err)
- }
- buf := new(bytes.Buffer)
- fmt.Fprintf(buf, "%v %v len=%d\n", msgtype, h.xid(), len(h))
- fmt.Fprintf(buf, "\tciaddr:%v yiaddr:%v siaddr:%v giaddr:%v\n",
- tcpip.Address(h.ciaddr()),
- tcpip.Address(h.yiaddr()),
- tcpip.Address(h.siaddr()),
- tcpip.Address(h.giaddr()))
- fmt.Fprintf(buf, "\tchaddr:%x", h.chaddr())
- for _, opt := range opts {
- fmt.Fprintf(buf, "\n\t%v", opt)
- }
- return buf.String()
-}
-
-func (opt option) String() string {
- buf := new(bytes.Buffer)
- fmt.Fprintf(buf, "%v: ", opt.code)
- fmt.Fprintf(buf, "%x", opt.body)
- return buf.String()
-}
-
-func (code optionCode) String() string {
- switch code {
- case optSubnetMask:
- return "option(subnet-mask)"
- case optDefaultGateway:
- return "option(default-gateway)"
- case optDomainNameServer:
- return "option(dns)"
- case optDomainName:
- return "option(domain-name)"
- case optReqIPAddr:
- return "option(request-ip-address)"
- case optLeaseTime:
- return "option(lease-time)"
- case optDHCPMsgType:
- return "option(message-type)"
- case optDHCPServer:
- return "option(server)"
- case optParamReq:
- return "option(parameter-request)"
- case optMessage:
- return "option(message)"
- case optClientID:
- return "option(client-id)"
- default:
- return fmt.Sprintf("option(%d)", code)
- }
-}
-
-func (o op) String() string {
- switch o {
- case opRequest:
- return "op(request)"
- case opReply:
- return "op(reply)"
- }
- return fmt.Sprintf("op(UNKNOWN:%d)", int(o))
-}
-
-func (t dhcpMsgType) String() string {
- switch t {
- case dhcpDISCOVER:
- return "DHCPDISCOVER"
- case dhcpOFFER:
- return "DHCPOFFER"
- case dhcpREQUEST:
- return "DHCPREQUEST"
- case dhcpDECLINE:
- return "DHCPDECLINE"
- case dhcpACK:
- return "DHCPACK"
- case dhcpNAK:
- return "DHCPNAK"
- case dhcpRELEASE:
- return "DHCPRELEASE"
- }
- return fmt.Sprintf("DHCP(%d)", int(t))
-}
-
-func (v xid) String() string {
- return fmt.Sprintf("xid:%x", uint32(v))
-}
diff --git a/pkg/dhcp/dhcp_test.go b/pkg/dhcp/dhcp_test.go
deleted file mode 100644
index 751626bb0..000000000
--- a/pkg/dhcp/dhcp_test.go
+++ /dev/null
@@ -1,322 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// 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.
-
-package dhcp
-
-import (
- "context"
- "strings"
- "testing"
- "time"
-
- "gvisor.googlesource.com/gvisor/pkg/tcpip"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/buffer"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/link/channel"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/link/sniffer"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/stack"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/transport/udp"
- "gvisor.googlesource.com/gvisor/pkg/waiter"
-)
-
-const nicid = tcpip.NICID(1)
-const serverAddr = tcpip.Address("\xc0\xa8\x03\x01")
-
-func createStack(t *testing.T) *stack.Stack {
- const defaultMTU = 65536
- id, linkEP := channel.New(256, defaultMTU, "")
- if testing.Verbose() {
- id = sniffer.New(id)
- }
-
- go func() {
- for pkt := range linkEP.C {
- linkEP.Inject(pkt.Proto, buffer.NewVectorisedView(len(pkt.Header)+len(pkt.Payload), []buffer.View{pkt.Header, pkt.Payload}))
- }
- }()
-
- s := stack.New([]string{ipv4.ProtocolName}, []string{udp.ProtocolName}, stack.Options{})
-
- if err := s.CreateNIC(nicid, id); err != nil {
- t.Fatal(err)
- }
- if err := s.AddAddress(nicid, ipv4.ProtocolNumber, serverAddr); err != nil {
- t.Fatal(err)
- }
-
- s.SetRouteTable([]tcpip.Route{{
- Destination: tcpip.Address(strings.Repeat("\x00", 4)),
- Mask: tcpip.AddressMask(strings.Repeat("\x00", 4)),
- Gateway: "",
- NIC: nicid,
- }})
-
- return s
-}
-
-func TestDHCP(t *testing.T) {
- s := createStack(t)
- clientAddrs := []tcpip.Address{"\xc0\xa8\x03\x02", "\xc0\xa8\x03\x03"}
-
- serverCfg := Config{
- ServerAddress: serverAddr,
- SubnetMask: "\xff\xff\xff\x00",
- Gateway: "\xc0\xa8\x03\xF0",
- DNS: []tcpip.Address{
- "\x08\x08\x08\x08", "\x08\x08\x04\x04",
- },
- LeaseLength: 24 * time.Hour,
- }
- serverCtx, cancel := context.WithCancel(context.Background())
- defer cancel()
- _, err := newEPConnServer(serverCtx, s, clientAddrs, serverCfg)
- if err != nil {
- t.Fatal(err)
- }
-
- const clientLinkAddr0 = tcpip.LinkAddress("\x52\x11\x22\x33\x44\x52")
- c0 := NewClient(s, nicid, clientLinkAddr0, nil)
- if _, err := c0.Request(context.Background(), ""); err != nil {
- t.Fatal(err)
- }
- if got, want := c0.Address(), clientAddrs[0]; got != want {
- t.Errorf("c.Addr()=%s, want=%s", got, want)
- }
- if _, err := c0.Request(context.Background(), ""); err != nil {
- t.Fatal(err)
- }
- if got, want := c0.Address(), clientAddrs[0]; got != want {
- t.Errorf("c.Addr()=%s, want=%s", got, want)
- }
-
- const clientLinkAddr1 = tcpip.LinkAddress("\x52\x11\x22\x33\x44\x53")
- c1 := NewClient(s, nicid, clientLinkAddr1, nil)
- if _, err := c1.Request(context.Background(), ""); err != nil {
- t.Fatal(err)
- }
- if got, want := c1.Address(), clientAddrs[1]; got != want {
- t.Errorf("c.Addr()=%s, want=%s", got, want)
- }
-
- if _, err := c0.Request(context.Background(), ""); err != nil {
- t.Fatal(err)
- }
- if got, want := c0.Address(), clientAddrs[0]; got != want {
- t.Errorf("c.Addr()=%s, want=%s", got, want)
- }
-
- if got, want := c0.Config(), serverCfg; !equalConfig(got, want) {
- t.Errorf("client config:\n\t%#+v\nwant:\n\t%#+v", got, want)
- }
-}
-
-func equalConfig(c0, c1 Config) bool {
- if c0.Error != c1.Error || c0.ServerAddress != c1.ServerAddress || c0.SubnetMask != c1.SubnetMask || c0.Gateway != c1.Gateway || c0.LeaseLength != c1.LeaseLength {
- return false
- }
- if len(c0.DNS) != len(c1.DNS) {
- return false
- }
- for i := 0; i < len(c0.DNS); i++ {
- if c0.DNS[i] != c1.DNS[i] {
- return false
- }
- }
- return true
-}
-
-func TestRenew(t *testing.T) {
- s := createStack(t)
- clientAddrs := []tcpip.Address{"\xc0\xa8\x03\x02"}
-
- serverCfg := Config{
- ServerAddress: serverAddr,
- SubnetMask: "\xff\xff\xff\x00",
- Gateway: "\xc0\xa8\x03\xF0",
- DNS: []tcpip.Address{"\x08\x08\x08\x08"},
- LeaseLength: 1 * time.Second,
- }
- serverCtx, cancel := context.WithCancel(context.Background())
- defer cancel()
- _, err := newEPConnServer(serverCtx, s, clientAddrs, serverCfg)
- if err != nil {
- t.Fatal(err)
- }
-
- count := 0
- 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", count, curAddr, oldAddr)
- }
- if cfg.LeaseLength != time.Second {
- t.Fatalf("aquisition %d: lease length: %v, want %v", count, cfg.LeaseLength, time.Second)
- }
- count++
- curAddr = newAddr
- addrCh <- newAddr
- }
-
- clientCtx, cancel := context.WithCancel(context.Background())
- const clientLinkAddr0 = tcpip.LinkAddress("\x52\x11\x22\x33\x44\x52")
- c := NewClient(s, nicid, clientLinkAddr0, acquiredFunc)
- c.Run(clientCtx)
-
- var addr tcpip.Address
- select {
- case addr = <-addrCh:
- t.Logf("got first address: %v", addr)
- case <-time.After(5 * time.Second):
- t.Fatal("timeout acquiring initial address")
- }
-
- select {
- case newAddr := <-addrCh:
- t.Logf("got renewal: %v", newAddr)
- if newAddr != addr {
- t.Fatalf("renewal address is %v, want %v", newAddr, addr)
- }
- case <-time.After(5 * time.Second):
- t.Fatal("timeout waiting for address renewal")
- }
-
- cancel()
-}
-
-// Regression test for https://fuchsia.atlassian.net/browse/NET-17
-func TestNoNullTerminator(t *testing.T) {
- v := "\x02\x01\x06\x00" +
- "\xc8\x37\xbe\x73\x00\x00\x80\x00\x00\x00\x00\x00\xc0\xa8\x2b\x92" +
- "\xc0\xa8\x2b\x01\x00\x00\x00\x00\x00\x0f\x60\x0a\x23\x93\x00\x00" +
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
- "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
- "\x00\x00\x00\x00\x00\x00\x00\x00\x63\x82\x53\x63\x35\x01\x02\x36" +
- "\x04\xc0\xa8\x2b\x01\x33\x04\x00\x00\x0e\x10\x3a\x04\x00\x00\x07" +
- "\x08\x3b\x04\x00\x00\x0c\x4e\x01\x04\xff\xff\xff\x00\x1c\x04\xc0" +
- "\xa8\x2b\xff\x03\x04\xc0\xa8\x2b\x01\x06\x04\xc0\xa8\x2b\x01\x2b" +
- "\x0f\x41\x4e\x44\x52\x4f\x49\x44\x5f\x4d\x45\x54\x45\x52\x45\x44" +
- "\xff"
- h := header(v)
- if !h.isValid() {
- t.Error("failed to decode header")
- }
-
- if got, want := h.op(), opReply; got != want {
- t.Errorf("h.op()=%v, want=%v", got, want)
- }
-
- if _, err := h.options(); err != nil {
- t.Errorf("bad options: %v", err)
- }
-}
-
-func teeConn(c conn) (conn, conn) {
- dup1 := &dupConn{
- c: c,
- dup: make(chan connMsg, 8),
- }
- dup2 := &chConn{
- c: c,
- ch: dup1.dup,
- }
- return dup1, dup2
-}
-
-type connMsg struct {
- buf buffer.View
- addr tcpip.FullAddress
- err error
-}
-
-type dupConn struct {
- c conn
- dup chan connMsg
-}
-
-func (c *dupConn) Read() (buffer.View, tcpip.FullAddress, error) {
- v, addr, err := c.c.Read()
- c.dup <- connMsg{v, addr, err}
- return v, addr, err
-}
-func (c *dupConn) Write(b []byte, addr *tcpip.FullAddress) error { return c.c.Write(b, addr) }
-
-type chConn struct {
- ch chan connMsg
- c conn
-}
-
-func (c *chConn) Read() (buffer.View, tcpip.FullAddress, error) {
- msg := <-c.ch
- return msg.buf, msg.addr, msg.err
-}
-func (c *chConn) Write(b []byte, addr *tcpip.FullAddress) error { return c.c.Write(b, addr) }
-
-func TestTwoServers(t *testing.T) {
- s := createStack(t)
-
- wq := new(waiter.Queue)
- ep, err := s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, wq)
- if err != nil {
- t.Fatalf("dhcp: server endpoint: %v", err)
- }
- if err = ep.Bind(tcpip.FullAddress{Port: ServerPort}); err != nil {
- t.Fatalf("dhcp: server bind: %v", err)
- }
- if err = ep.SetSockOpt(tcpip.BroadcastOption(1)); err != nil {
- t.Fatalf("dhcp: setsockopt: %v", err)
- }
-
- serverCtx, cancel := context.WithCancel(context.Background())
- defer cancel()
- c1, c2 := teeConn(newEPConn(serverCtx, wq, ep))
-
- if _, err := NewServer(serverCtx, c1, []tcpip.Address{"\xc0\xa8\x03\x02"}, Config{
- ServerAddress: "\xc0\xa8\x03\x01",
- SubnetMask: "\xff\xff\xff\x00",
- Gateway: "\xc0\xa8\x03\xF0",
- DNS: []tcpip.Address{"\x08\x08\x08\x08"},
- LeaseLength: 30 * time.Minute,
- }); err != nil {
- t.Fatal(err)
- }
- if _, err := NewServer(serverCtx, c2, []tcpip.Address{"\xc0\xa8\x04\x02"}, Config{
- ServerAddress: "\xc0\xa8\x04\x01",
- SubnetMask: "\xff\xff\xff\x00",
- Gateway: "\xc0\xa8\x03\xF0",
- DNS: []tcpip.Address{"\x08\x08\x08\x08"},
- LeaseLength: 30 * time.Minute,
- }); err != nil {
- t.Fatal(err)
- }
-
- const clientLinkAddr0 = tcpip.LinkAddress("\x52\x11\x22\x33\x44\x52")
- c := NewClient(s, nicid, clientLinkAddr0, nil)
- if _, err := c.Request(context.Background(), ""); err != nil {
- t.Fatal(err)
- }
-}
diff --git a/pkg/dhcp/server.go b/pkg/dhcp/server.go
deleted file mode 100644
index 6a1972860..000000000
--- a/pkg/dhcp/server.go
+++ /dev/null
@@ -1,393 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// 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.
-
-package dhcp
-
-import (
- "context"
- "fmt"
- "io"
- "log"
- "sync"
- "time"
-
- "gvisor.googlesource.com/gvisor/pkg/tcpip"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/buffer"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/stack"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/transport/udp"
- "gvisor.googlesource.com/gvisor/pkg/waiter"
-)
-
-// Server is a DHCP server.
-type Server struct {
- conn conn
- broadcast tcpip.FullAddress
- addrs []tcpip.Address // TODO: use a tcpip.AddressMask or range structure
- cfg Config
- cfgopts []option // cfg to send to client
-
- handlers []chan header
-
- mu sync.Mutex
- leases map[tcpip.LinkAddress]serverLease
-}
-
-// conn is a blocking read/write network endpoint.
-type conn interface {
- Read() (buffer.View, tcpip.FullAddress, error)
- Write([]byte, *tcpip.FullAddress) error
-}
-
-type epConn struct {
- ctx context.Context
- wq *waiter.Queue
- ep tcpip.Endpoint
- we waiter.Entry
- inCh chan struct{}
-}
-
-func newEPConn(ctx context.Context, wq *waiter.Queue, ep tcpip.Endpoint) *epConn {
- c := &epConn{
- ctx: ctx,
- wq: wq,
- ep: ep,
- }
- c.we, c.inCh = waiter.NewChannelEntry(nil)
- wq.EventRegister(&c.we, waiter.EventIn)
-
- go func() {
- <-ctx.Done()
- wq.EventUnregister(&c.we)
- }()
-
- return c
-}
-
-func (c *epConn) Read() (buffer.View, tcpip.FullAddress, error) {
- for {
- var addr tcpip.FullAddress
- v, _, err := c.ep.Read(&addr)
- if err == tcpip.ErrWouldBlock {
- select {
- case <-c.inCh:
- continue
- case <-c.ctx.Done():
- return nil, tcpip.FullAddress{}, io.EOF
- }
- }
- if err != nil {
- return v, addr, fmt.Errorf("read: %v", err)
- }
- return v, addr, nil
- }
-}
-
-func (c *epConn) Write(b []byte, addr *tcpip.FullAddress) error {
- _, resCh, err := c.ep.Write(tcpip.SlicePayload(b), tcpip.WriteOptions{To: addr})
- if err != nil && resCh == nil {
- return fmt.Errorf("write: %v", err)
- }
-
- if resCh != nil {
- select {
- case <-resCh:
- case <-c.ctx.Done():
- return fmt.Errorf("dhcp server address resolution: %v", tcpip.ErrAborted)
- }
-
- if _, _, err := c.ep.Write(tcpip.SlicePayload(b), tcpip.WriteOptions{To: addr}); err != nil {
- return fmt.Errorf("write: %v", err)
- }
- }
- return nil
-}
-
-func newEPConnServer(ctx context.Context, stack *stack.Stack, addrs []tcpip.Address, cfg Config) (*Server, error) {
- wq := new(waiter.Queue)
- ep, err := stack.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, wq)
- if err != nil {
- return nil, fmt.Errorf("dhcp: server endpoint: %v", err)
- }
- if err := ep.Bind(tcpip.FullAddress{Port: ServerPort}); err != nil {
- return nil, fmt.Errorf("dhcp: server bind: %v", err)
- }
- if err := ep.SetSockOpt(tcpip.BroadcastOption(1)); err != nil {
- return nil, fmt.Errorf("dhcp: server setsockopt: %v", err)
- }
- c := newEPConn(ctx, wq, ep)
- return NewServer(ctx, c, addrs, cfg)
-}
-
-// NewServer creates a new DHCP server and begins serving.
-// The server continues serving until ctx is done.
-func NewServer(ctx context.Context, c conn, addrs []tcpip.Address, cfg Config) (*Server, error) {
- if cfg.ServerAddress == "" {
- return nil, fmt.Errorf("dhcp: server requires explicit server address")
- }
- s := &Server{
- conn: c,
- addrs: addrs,
- cfg: cfg,
- cfgopts: cfg.encode(),
- broadcast: tcpip.FullAddress{
- Addr: "\xff\xff\xff\xff",
- Port: ClientPort,
- },
-
- handlers: make([]chan header, 8),
- leases: make(map[tcpip.LinkAddress]serverLease),
- }
-
- for i := 0; i < len(s.handlers); i++ {
- ch := make(chan header, 8)
- s.handlers[i] = ch
- go s.handler(ctx, ch)
- }
-
- go s.expirer(ctx)
- go s.reader(ctx)
- return s, nil
-}
-
-func (s *Server) expirer(ctx context.Context) {
- t := time.NewTicker(1 * time.Minute)
- defer t.Stop()
- for {
- select {
- case <-t.C:
- s.mu.Lock()
- for linkAddr, lease := range s.leases {
- if time.Since(lease.start) > s.cfg.LeaseLength {
- lease.state = leaseExpired
- s.leases[linkAddr] = lease
- }
- }
- s.mu.Unlock()
- case <-ctx.Done():
- return
- }
- }
-}
-
-// reader listens for all incoming DHCP packets and fans them out to
-// handling goroutines based on XID as session identifiers.
-func (s *Server) reader(ctx context.Context) {
- for {
- v, _, err := s.conn.Read()
- if err != nil {
- return
- }
-
- h := header(v)
- if !h.isValid() || h.op() != opRequest {
- continue
- }
- xid := h.xid()
-
- // Fan out the packet to a handler goroutine.
- //
- // Use a consistent handler for a given xid, so that
- // packets from a particular client are processed
- // in order.
- ch := s.handlers[int(xid)%len(s.handlers)]
- select {
- case <-ctx.Done():
- return
- case ch <- h:
- default:
- // drop the packet
- }
- }
-}
-
-func (s *Server) handler(ctx context.Context, ch chan header) {
- for {
- select {
- case h := <-ch:
- if h == nil {
- return
- }
- opts, err := h.options()
- if err != nil {
- continue
- }
- // TODO: Handle DHCPRELEASE and DHCPDECLINE.
- msgtype, err := opts.dhcpMsgType()
- if err != nil {
- continue
- }
- switch msgtype {
- case dhcpDISCOVER:
- s.handleDiscover(h, opts)
- case dhcpREQUEST:
- s.handleRequest(h, opts)
- }
- case <-ctx.Done():
- return
- }
- }
-}
-
-func (s *Server) handleDiscover(hreq header, opts options) {
- linkAddr := tcpip.LinkAddress(hreq.chaddr()[:6])
- xid := hreq.xid()
-
- s.mu.Lock()
- lease := s.leases[linkAddr]
- switch lease.state {
- case leaseNew:
- if len(s.leases) < len(s.addrs) {
- // Find an unused address.
- // TODO: avoid building this state on each request.
- alloced := make(map[tcpip.Address]bool)
- for _, lease := range s.leases {
- alloced[lease.addr] = true
- }
- for _, addr := range s.addrs {
- if !alloced[addr] {
- lease = serverLease{
- start: time.Now(),
- addr: addr,
- xid: xid,
- state: leaseOffer,
- }
- s.leases[linkAddr] = lease
- break
- }
- }
- } else {
- // No more addresses, take an expired address.
- for k, oldLease := range s.leases {
- if oldLease.state == leaseExpired {
- delete(s.leases, k)
- lease = serverLease{
- start: time.Now(),
- addr: lease.addr,
- xid: xid,
- state: leaseOffer,
- }
- s.leases[linkAddr] = lease
- break
- }
- }
- log.Printf("server has no more addresses")
- s.mu.Unlock()
- return
- }
- case leaseOffer, leaseAck, leaseExpired:
- lease = serverLease{
- start: time.Now(),
- addr: s.leases[linkAddr].addr,
- xid: xid,
- state: leaseOffer,
- }
- s.leases[linkAddr] = lease
- }
- s.mu.Unlock()
-
- // DHCPOFFER
- opts = options{{optDHCPMsgType, []byte{byte(dhcpOFFER)}}}
- opts = append(opts, s.cfgopts...)
- h := make(header, headerBaseSize+opts.len()+1)
- h.init()
- h.setOp(opReply)
- copy(h.xidbytes(), hreq.xidbytes())
- copy(h.yiaddr(), lease.addr)
- copy(h.chaddr(), hreq.chaddr())
- h.setOptions(opts)
- s.conn.Write([]byte(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()+1)
- h.init()
- h.setOp(opReply)
- copy(h.xidbytes(), hreq.xidbytes())
- copy(h.chaddr(), hreq.chaddr())
- h.setOptions(opts)
- s.conn.Write([]byte(h), &s.broadcast)
-}
-
-func (s *Server) handleRequest(hreq header, opts options) {
- 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 {
- // This request is for a different DHCP server. Ignore it.
- return
- }
-
- s.mu.Lock()
- lease := s.leases[linkAddr]
- switch lease.state {
- case leaseOffer, leaseAck, leaseExpired:
- lease = serverLease{
- start: time.Now(),
- addr: s.leases[linkAddr].addr,
- xid: xid,
- state: leaseAck,
- }
- s.leases[linkAddr] = lease
- }
- s.mu.Unlock()
-
- if lease.state == leaseNew {
- // TODO: NACK or accept request
- return
- }
-
- // DHCPACK
- opts = []option{{optDHCPMsgType, []byte{byte(dhcpACK)}}}
- opts = append(opts, s.cfgopts...)
- h := make(header, headerBaseSize+opts.len()+1)
- h.init()
- h.setOp(opReply)
- copy(h.xidbytes(), hreq.xidbytes())
- copy(h.yiaddr(), lease.addr)
- copy(h.chaddr(), hreq.chaddr())
- h.setOptions(opts)
- s.conn.Write([]byte(h), &s.broadcast)
-}
-
-type leaseState int
-
-const (
- leaseNew leaseState = iota
- leaseOffer
- leaseAck
- leaseExpired
-)
-
-type serverLease struct {
- start time.Time
- addr tcpip.Address
- xid xid
- state leaseState
-}