diff options
author | Tamir Duberstein <tamird@google.com> | 2019-05-09 15:21:50 -0700 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2019-05-09 15:23:03 -0700 |
commit | 0f4be95a336bd5dbf214bc40eb5d1bbb5fce36a4 (patch) | |
tree | 07f34cd1ef2ee67bc85da09fa75ce195d6c73c77 /pkg | |
parent | c3b6d4587edc1c04c86c2504b68a41b8c77fd202 (diff) |
Remove dhcp client
This was upstreamed from Fuchsia, but it is pretty buggy and doesn't
rely on any private APIs. Thus it can be checked into the Fuchsia source
tree without forking netstack, where we can more easily iterate on (and
eventually remove) it.
PiperOrigin-RevId: 247506582
Change-Id: Ifb1b60c6c4941c374a59c5570a6a9cacf2468981
Diffstat (limited to 'pkg')
-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 -} |