283 lines
7.1 KiB
Go
283 lines
7.1 KiB
Go
// Copyright 2018 Google Inc.
|
|
//
|
|
// 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)) {
|
|
// TODO: s/%v/%s/ when `go vet` is smarter.
|
|
return fmt.Errorf("%v: 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 {
|
|
// TODO: s/%v/%s/ when `go vet` is smarter.
|
|
return 0, fmt.Errorf("%v: 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
|
|
)
|