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
This commit is contained in:
Tamir Duberstein
2019-05-09 15:23:03 -07:00
committed by Shentubot
parent c3b6d4587e
commit 0f4be95a33
6 changed files with 0 additions and 1499 deletions
-41
View File
@@ -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",
],
)
-348
View File
@@ -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
}
-280
View File
@@ -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
)
-115
View File
@@ -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))
}
-322
View File
@@ -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)
}
}
-393
View File
@@ -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
}