diff options
-rw-r--r-- | pkg/dhcp/BUILD | 41 | ||||
-rw-r--r-- | pkg/dhcp/client.go | 348 | ||||
-rw-r--r-- | pkg/dhcp/dhcp.go | 280 | ||||
-rw-r--r-- | pkg/dhcp/dhcp_string.go | 115 | ||||
-rw-r--r-- | pkg/dhcp/dhcp_test.go | 322 | ||||
-rw-r--r-- | pkg/dhcp/server.go | 393 |
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 -} |