diff options
Diffstat (limited to 'pkg/dhcp/dhcp.go')
-rw-r--r-- | pkg/dhcp/dhcp.go | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/pkg/dhcp/dhcp.go b/pkg/dhcp/dhcp.go new file mode 100644 index 000000000..762086853 --- /dev/null +++ b/pkg/dhcp/dhcp.go @@ -0,0 +1,263 @@ +// Copyright 2016 The Netstack Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// 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 { + ServerAddress tcpip.Address // address of the server + SubnetMask tcpip.AddressMask // client address subnet mask + Gateway tcpip.Address // client default gateway + DomainNameServer tcpip.Address // client domain name server + 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 l := opt.code.len(); l != -1 && l != 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: + cfg.DomainNameServer = tcpip.Address(b) + } + } + 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 cfg.DomainNameServer != "" { + opts = append(opts, option{optDomainNameServer, []byte(cfg.DomainNameServer)}) + } + 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 = 67 + 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 || h[3] != 0x00 { + return false + } + return bytes.Equal(h[236:240], magicCookie) && h[len(h)-1] == 0 +} + +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 too long") + } + 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) + } + 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 + optReqIPAddr optionCode = 50 + optLeaseTime optionCode = 51 + optDHCPMsgType optionCode = 53 // dhcpMsgType + optDHCPServer optionCode = 54 + optParamReq optionCode = 55 +) + +func (code optionCode) len() int { + switch code { + case optSubnetMask, optDefaultGateway, optDomainNameServer, + optReqIPAddr, optLeaseTime, optDHCPServer: + return 4 + case optDHCPMsgType: + return 1 + case optParamReq: + return -1 // no fixed length + default: + return -1 + } +} + +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 optReqIPAddr: + return "option(request-ip-address)" + case optLeaseTime: + return "option(least-time)" + case optDHCPMsgType: + return "option(message-type)" + case optDHCPServer: + return "option(server)" + case optParamReq: + return "option(parameter-request)" + default: + return fmt.Sprintf("option(%d)", code) + } +} + +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", optDHCPMsgType, len(opt.body)) + } + v := opt.body[0] + if v <= 0 || v >= 8 { + return 0, fmt.Errorf("%s: unknown value: %d", optDHCPMsgType, v) + } + return dhcpMsgType(v), nil + } + } + return 0, nil +} + +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 +) |