mirror of
https://github.com/soypat/lneto.git
synced 2026-09-03 05:19:04 +00:00
dhcp->dhcpv4; finish NTP and DHCPv4 clients
This commit is contained in:
@@ -1,56 +0,0 @@
|
|||||||
package dhcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ClientV4 struct {
|
|
||||||
reqHostname []byte
|
|
||||||
hostname []byte
|
|
||||||
offer [4]byte
|
|
||||||
svip [4]byte
|
|
||||||
reqIP [4]byte
|
|
||||||
dns [4]byte
|
|
||||||
router [4]byte
|
|
||||||
subnet [4]byte
|
|
||||||
broadcast [4]byte
|
|
||||||
gateway [4]byte
|
|
||||||
optbuf [10]Option
|
|
||||||
currentXID uint32
|
|
||||||
tRenew uint32
|
|
||||||
tRebind uint32
|
|
||||||
tIPLease uint32
|
|
||||||
state ClientState
|
|
||||||
}
|
|
||||||
|
|
||||||
type RequestConfig struct {
|
|
||||||
RequestedAddr [4]byte
|
|
||||||
// Optional hostname to request.
|
|
||||||
Hostname string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientV4) BeginRequest(xid uint32, cfg RequestConfig) error {
|
|
||||||
c.currentXID = xid
|
|
||||||
c.reqHostname = append(c.reqHostname[:0], cfg.Hostname...)
|
|
||||||
c.reqIP = cfg.RequestedAddr
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientV4) Write(dst []byte) (int, error) {
|
|
||||||
if c.isClosed() {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
frm, err := NewFrameV4(dst)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
// var options []Option
|
|
||||||
// var nextState ClientState
|
|
||||||
switch c.state {
|
|
||||||
case StateInit:
|
|
||||||
frm.MagicCookie()
|
|
||||||
}
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientV4) isClosed() bool { return c.state == 0 }
|
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
package dhcpv4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
reqHostname string
|
||||||
|
hostname []byte
|
||||||
|
dns [][4]byte
|
||||||
|
|
||||||
|
tRenew uint32
|
||||||
|
tRebind uint32
|
||||||
|
tIPLease uint32
|
||||||
|
currentXID uint32
|
||||||
|
state ClientState
|
||||||
|
offer [4]byte
|
||||||
|
svip [4]byte
|
||||||
|
reqIP [4]byte
|
||||||
|
router [4]byte
|
||||||
|
subnet [4]byte
|
||||||
|
broadcast [4]byte
|
||||||
|
gateway [4]byte
|
||||||
|
clientMAC [6]byte
|
||||||
|
|
||||||
|
auxbuf [64]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestConfig struct {
|
||||||
|
RequestedAddr [4]byte
|
||||||
|
ClientHardwareAddr [6]byte
|
||||||
|
// Optional hostname to request.
|
||||||
|
Hostname string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) BeginRequest(xid uint32, cfg RequestConfig) error {
|
||||||
|
if len(cfg.Hostname) > 36 {
|
||||||
|
return errors.New("requested hostname too long")
|
||||||
|
}
|
||||||
|
c.reset(xid)
|
||||||
|
c.currentXID = xid
|
||||||
|
c.reqHostname = cfg.Hostname
|
||||||
|
c.reqIP = cfg.RequestedAddr
|
||||||
|
c.clientMAC = cfg.ClientHardwareAddr
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Send(dst []byte) (int, error) {
|
||||||
|
if c.isClosed() {
|
||||||
|
return 0, io.EOF
|
||||||
|
} else if c.state == StateSelecting && c.offer == [4]byte{} {
|
||||||
|
return 0, nil // No offer received yet.
|
||||||
|
} else if c.state == StateBound {
|
||||||
|
return 0, nil // Done!
|
||||||
|
}
|
||||||
|
frm, err := NewFrame(dst)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// var options []Option
|
||||||
|
// var nextState ClientState
|
||||||
|
optBuf := c.auxbuf[:0]
|
||||||
|
var nextState ClientState
|
||||||
|
switch c.state {
|
||||||
|
case StateInit:
|
||||||
|
// Send out discover.
|
||||||
|
optBuf = AppendOption(optBuf, OptMessageType, byte(MsgDiscover))
|
||||||
|
optBuf = AppendOption(optBuf, OptParameterRequestList, defaultParamReqList...)
|
||||||
|
optBuf = AppendOption(optBuf, OptClientIdentifier, c.clientMAC[:]...)
|
||||||
|
maxlen := len(dst)
|
||||||
|
if maxlen > math.MaxUint16 {
|
||||||
|
maxlen = math.MaxUint16
|
||||||
|
}
|
||||||
|
optBuf = AppendOption(optBuf, OptMaximumMessageSize, byte(maxlen>>8), byte(maxlen))
|
||||||
|
if c.reqIP != [4]byte{} {
|
||||||
|
optBuf = AppendOption(optBuf, OptRequestedIPaddress, c.reqIP[:]...)
|
||||||
|
}
|
||||||
|
nextState = StateSelecting
|
||||||
|
|
||||||
|
case StateSelecting:
|
||||||
|
// Send out request, we know we've received an offer by now.
|
||||||
|
optBuf = AppendOption(optBuf, OptMessageType, byte(MsgRequest))
|
||||||
|
optBuf = AppendOption(optBuf, OptRequestedIPaddress, c.offer[:]...)
|
||||||
|
optBuf = AppendOption(optBuf, OptServerIdentification, c.svip[:]...)
|
||||||
|
nextState = StateRequesting
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0, errors.New("unhandled state")
|
||||||
|
}
|
||||||
|
if len(c.reqHostname) > 0 {
|
||||||
|
optBuf = append(optBuf, byte(OptHostName), byte(len(c.hostname)))
|
||||||
|
optBuf = append(optBuf, c.hostname...)
|
||||||
|
}
|
||||||
|
optBuf = append(optBuf, 0xff) // End mark.
|
||||||
|
options := frm.OptionsPayload()
|
||||||
|
if len(optBuf) > len(options) {
|
||||||
|
return 0, errors.New("DHCPv4 short buffer for options")
|
||||||
|
}
|
||||||
|
c.setHeader(frm)
|
||||||
|
n := copy(options, optBuf)
|
||||||
|
c.state = nextState
|
||||||
|
return optionsOffset + n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Recv(pkt []byte) error {
|
||||||
|
if c.isClosed() {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
frm, err := NewFrame(pkt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if frm.XID() != c.currentXID {
|
||||||
|
return errors.New("dhcpv4 unexpected transaction ID")
|
||||||
|
} else if frm.MagicCookie() != MagicCookie {
|
||||||
|
return errors.New("dhcpv4 bad magic cookie")
|
||||||
|
}
|
||||||
|
msgType := c.getMessageType(frm)
|
||||||
|
if msgType == MsgNack {
|
||||||
|
return errors.New("dhcp nack received")
|
||||||
|
}
|
||||||
|
|
||||||
|
msgOK := msgType == MsgOffer || msgType == MsgAck
|
||||||
|
if !msgOK {
|
||||||
|
return fmt.Errorf("invalid DHCP message received or none got=%d", msgType)
|
||||||
|
}
|
||||||
|
err = c.setOptions(frm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c.state {
|
||||||
|
case StateSelecting:
|
||||||
|
if msgType == MsgOffer && c.offer == [4]byte{} {
|
||||||
|
// Lock in on this offer.
|
||||||
|
c.gateway = *frm.GIAddr()
|
||||||
|
c.offer = *frm.YIAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
case StateRequesting:
|
||||||
|
if msgType == MsgAck {
|
||||||
|
c.state = StateBound
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("dcpv4 unexpected state in recv %s", c.state.String())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) getMessageType(frm Frame) MessageType {
|
||||||
|
c.auxbuf[0] = 255
|
||||||
|
ptrMsgType := &c.auxbuf[0]
|
||||||
|
frm.ForEachOption(func(opt OptNum, data []byte) error {
|
||||||
|
if len(data) == 1 {
|
||||||
|
*ptrMsgType = data[0]
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return MessageType(*ptrMsgType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) setOptions(frm Frame) error {
|
||||||
|
return frm.ForEachOption(func(opt OptNum, data []byte) error {
|
||||||
|
switch opt {
|
||||||
|
case OptRenewTimeValue:
|
||||||
|
c.tRenew = maybeU32(data)
|
||||||
|
case OptIPAddressLeaseTime:
|
||||||
|
c.tIPLease = maybeU32(data)
|
||||||
|
case OptRebindingTimeValue:
|
||||||
|
c.tRebind = maybeU32(data)
|
||||||
|
|
||||||
|
case OptServerIdentification:
|
||||||
|
c.svip = maybe4byte(data)
|
||||||
|
case OptRouter:
|
||||||
|
c.router = maybe4byte(data)
|
||||||
|
case OptBroadcastAddress:
|
||||||
|
c.broadcast = maybe4byte(data)
|
||||||
|
case OptSubnetMask:
|
||||||
|
c.subnet = maybe4byte(data)
|
||||||
|
|
||||||
|
case OptHostName:
|
||||||
|
if len(data) < maxHostSize {
|
||||||
|
c.hostname = append(c.hostname[:0], data...)
|
||||||
|
}
|
||||||
|
case OptDNSServers:
|
||||||
|
if len(c.dns) > 0 || len(data)%4 != 0 {
|
||||||
|
return nil // No DNS parsing if already got in previous exchange.
|
||||||
|
}
|
||||||
|
for i := 0; i < len(data); i += 4 {
|
||||||
|
c.dns = append(c.dns, [4]byte(data[i:i+4]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) isClosed() bool { return c.state == 0 || c.currentXID == 0 }
|
||||||
|
|
||||||
|
func (c *Client) setHeader(frm Frame) {
|
||||||
|
frm.ClearHeader()
|
||||||
|
frm.SetOp(OpRequest)
|
||||||
|
frm.SetXID(c.currentXID)
|
||||||
|
frm.SetHardware(1, 6, 0)
|
||||||
|
frm.SetSecs(1)
|
||||||
|
// copy(frm.CIAddr()[:], c.offer[:])
|
||||||
|
copy(frm.SIAddr()[:], c.svip[:])
|
||||||
|
copy(frm.YIAddr()[:], c.offer[:])
|
||||||
|
copy(frm.CHAddrAs6()[:], c.clientMAC[:])
|
||||||
|
frm.SetMagicCookie(MagicCookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) reset(xid uint32) {
|
||||||
|
*c = Client{
|
||||||
|
reqHostname: c.reqHostname,
|
||||||
|
currentXID: xid,
|
||||||
|
reqIP: c.reqIP,
|
||||||
|
clientMAC: c.clientMAC,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Client) State() ClientState { return d.state }
|
||||||
|
|
||||||
|
func (d *Client) CIDRBits() uint8 {
|
||||||
|
if d.subnet == [4]byte{} {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
v := binary.BigEndian.Uint32(d.subnet[:])
|
||||||
|
return 32 - uint8(bits.TrailingZeros32(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultParamReqList = []byte{
|
||||||
|
byte(OptSubnetMask),
|
||||||
|
byte(OptTimeOffset),
|
||||||
|
byte(OptRouter),
|
||||||
|
byte(OptInterfaceMTUSize),
|
||||||
|
byte(OptBroadcastAddress),
|
||||||
|
byte(OptDNSServers),
|
||||||
|
byte(OptDomainName),
|
||||||
|
byte(OptNTPServersAddresses),
|
||||||
|
}
|
||||||
|
|
||||||
|
func maybeU32(b []byte) uint32 {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return binary.BigEndian.Uint32(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func maybe4byte(b []byte) [4]byte {
|
||||||
|
if len(b) != 4 {
|
||||||
|
return [4]byte{}
|
||||||
|
}
|
||||||
|
return [4]byte(b)
|
||||||
|
}
|
||||||
@@ -1,19 +1,18 @@
|
|||||||
package dhcp
|
package dhcpv4
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate stringer -type=OptNum,Op,MessageType,ClientState -linecomment -output stringers.go
|
//go:generate stringer -type=OptNum,Op,MessageType,ClientState -linecomment -output stringers.go
|
||||||
|
|
||||||
type ClientState uint8
|
// ClientState transition table during request:
|
||||||
|
|
||||||
// State transition table:
|
|
||||||
//
|
//
|
||||||
// StateInit -> | Send out Discover | -> StateSelecting
|
// StateInit -> | Send out Discover | -> StateSelecting
|
||||||
// StateSelecting -> |Accept Offer+Request| -> StateRequesting
|
// StateSelecting -> |Accept Offer+Request| -> StateRequesting
|
||||||
// StateRequesting-> | Receive Ack | -> StateBound
|
// StateRequesting-> | Receive Ack | -> StateBound
|
||||||
|
type ClientState uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_ ClientState = iota
|
_ ClientState = iota
|
||||||
// On clean slate boot, abort, NAK or decline enter the INIT state.
|
// On clean slate boot, abort, NAK or decline enter the INIT state.
|
||||||
@@ -30,26 +29,26 @@ const (
|
|||||||
StateRebooting // rebooting
|
StateRebooting // rebooting
|
||||||
)
|
)
|
||||||
|
|
||||||
type Option struct {
|
func AppendOption(dst []byte, opt OptNum, data ...byte) []byte {
|
||||||
Num OptNum
|
if len(data) > 255 {
|
||||||
Data []byte
|
panic("option data too long")
|
||||||
|
}
|
||||||
|
dst = append(dst, byte(opt), byte(len(data)))
|
||||||
|
dst = append(dst, data...)
|
||||||
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *Option) String() string {
|
func EncodeOption(dst []byte, opt OptNum, data ...byte) (int, error) {
|
||||||
return opt.Num.String() + ":" + fmt.Sprint(opt.Data)
|
if len(data) > 255 {
|
||||||
}
|
return 0, errors.New("DHCPv4 option data too long (>255)")
|
||||||
|
} else if len(dst) < 2+len(data) {
|
||||||
func (opt *Option) Encode(dst []byte) (int, error) {
|
|
||||||
if len(opt.Data) > 255 {
|
|
||||||
return 0, errors.New("DHCP option data too long")
|
|
||||||
} else if len(dst) < 2+len(opt.Data) {
|
|
||||||
return 0, errors.New("DHCP option buffer too short")
|
return 0, errors.New("DHCP option buffer too short")
|
||||||
}
|
}
|
||||||
_ = dst[2+len(opt.Data)]
|
_ = dst[2+len(data)]
|
||||||
dst[0] = byte(opt.Num)
|
dst[0] = byte(opt)
|
||||||
dst[1] = byte(len(opt.Data))
|
dst[1] = byte(len(data))
|
||||||
copy(dst[2:], opt.Data)
|
copy(dst[2:], data)
|
||||||
return 2 + len(opt.Data), nil
|
return 2 + len(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type OptNum uint8
|
type OptNum uint8
|
||||||
@@ -137,7 +136,9 @@ const (
|
|||||||
MsgRequest // request
|
MsgRequest // request
|
||||||
MsgDecline // decline
|
MsgDecline // decline
|
||||||
MsgAck // ack
|
MsgAck // ack
|
||||||
MsgNak // nak
|
MsgNack // nak
|
||||||
MsgRelease // release
|
MsgRelease // release
|
||||||
MsgInform // inform
|
MsgInform // inform
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Flags uint16
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package dhcp
|
package dhcpv4
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
maxHostSize = 16 // max size for hostname.
|
||||||
sizeSName = 64 // Server name, part of BOOTP too.
|
sizeSName = 64 // Server name, part of BOOTP too.
|
||||||
sizeBootFile = 128 // Boot file name, Legacy.
|
sizeBootFile = 128 // Boot file name, Legacy.
|
||||||
sizeHeader = 44
|
sizeHeader = 44
|
||||||
@@ -20,11 +21,13 @@ const (
|
|||||||
DefaultServerPort = 67
|
DefaultServerPort = 67
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewFrameV4(buf []byte) (FrameV4, error) {
|
// NewFrame returns a new DHCPv4 Frame with data set to buf.
|
||||||
if len(buf) < sizeHeader {
|
// An error is returned if the buffer size is smaller than 240.
|
||||||
return FrameV4{}, errors.New("DHCP short frame")
|
func NewFrame(buf []byte) (Frame, error) {
|
||||||
|
if len(buf) < optionsOffset {
|
||||||
|
return Frame{}, errors.New("DHCPv4 short frame")
|
||||||
}
|
}
|
||||||
return FrameV4{buf: buf}, nil
|
return Frame{buf: buf}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frame encapsulates the raw data of a DHCP packet
|
// Frame encapsulates the raw data of a DHCP packet
|
||||||
@@ -32,79 +35,81 @@ func NewFrameV4(buf []byte) (FrameV4, error) {
|
|||||||
// retrieving fields and payload data. See [RFC2131].
|
// retrieving fields and payload data. See [RFC2131].
|
||||||
//
|
//
|
||||||
// [RFC2131]: https://tools.ietf.org/html/rfc2131
|
// [RFC2131]: https://tools.ietf.org/html/rfc2131
|
||||||
type FrameV4 struct {
|
type Frame struct {
|
||||||
buf []byte
|
buf []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (frm FrameV4) Op() Op {
|
// OptionsPayload returns the options portion of the DHCP frame. May be zero lengthed.
|
||||||
return Op(frm.buf[0])
|
func (frm Frame) OptionsPayload() []byte {
|
||||||
|
return frm.buf[:optionsOffset]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (frm FrameV4) Hardware() (Type, Len, Ops uint8) {
|
func (frm Frame) Op() Op { return Op(frm.buf[0]) }
|
||||||
|
func (frm Frame) SetOp(op Op) { frm.buf[0] = byte(op) }
|
||||||
|
|
||||||
|
func (frm Frame) Hardware() (Type, Len, Ops uint8) {
|
||||||
return frm.buf[1], frm.buf[2], frm.buf[3]
|
return frm.buf[1], frm.buf[2], frm.buf[3]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (frm FrameV4) SetHardware(Type, Len, Ops uint8) {
|
func (frm Frame) SetHardware(Type, Len, Ops uint8) {
|
||||||
frm.buf[1], frm.buf[2], frm.buf[3] = Type, Len, Ops
|
frm.buf[1], frm.buf[2], frm.buf[3] = Type, Len, Ops
|
||||||
}
|
}
|
||||||
|
|
||||||
func (frm FrameV4) XID() uint32 {
|
func (frm Frame) XID() uint32 { return binary.BigEndian.Uint32(frm.buf[4:8]) }
|
||||||
return binary.BigEndian.Uint32(frm.buf[4:8])
|
func (frm Frame) SetXID(xid uint32) { binary.BigEndian.PutUint32(frm.buf[4:8], xid) }
|
||||||
}
|
|
||||||
|
|
||||||
func (frm FrameV4) Secs() uint16 {
|
func (frm Frame) Secs() uint16 { return binary.BigEndian.Uint16(frm.buf[8:10]) }
|
||||||
return binary.BigEndian.Uint16(frm.buf[8:10])
|
func (frm Frame) SetSecs(secs uint16) { binary.BigEndian.PutUint16(frm.buf[8:10], secs) }
|
||||||
}
|
|
||||||
|
|
||||||
func (frm FrameV4) Flags() uint16 {
|
func (frm Frame) Flags() Flags { return Flags(binary.BigEndian.Uint16(frm.buf[10:12])) }
|
||||||
return binary.BigEndian.Uint16(frm.buf[10:12])
|
func (frm Frame) SetFlags(flags Flags) { binary.BigEndian.PutUint16(frm.buf[10:12], uint16(flags)) }
|
||||||
}
|
|
||||||
|
|
||||||
// CIAddr is the client IP address. If the client has not obtained an IP
|
// CIAddr is the client IP address. If the client has not obtained an IP
|
||||||
// address yet, this field is set to 0.
|
// address yet, this field is set to 0.
|
||||||
func (frm FrameV4) CIAddr() *[4]byte {
|
func (frm Frame) CIAddr() *[4]byte {
|
||||||
return (*[4]byte)(frm.buf[12:16])
|
return (*[4]byte)(frm.buf[12:16])
|
||||||
}
|
}
|
||||||
|
|
||||||
// YIAddr is the IP address offered by the server to the client.
|
// YIAddr is the IP address offered by the server to the client.
|
||||||
func (frm FrameV4) YIAddr() *[4]byte {
|
func (frm Frame) YIAddr() *[4]byte {
|
||||||
return (*[4]byte)(frm.buf[16:20])
|
return (*[4]byte)(frm.buf[16:20])
|
||||||
}
|
}
|
||||||
|
|
||||||
// SIAddr is the IP address of the next server to use in bootstrap. This
|
// SIAddr is the IP address of the next server to use in bootstrap. This
|
||||||
// field is used in DHCPOFFER and DHCPACK messages.
|
// field is used in DHCPOFFER and DHCPACK messages.
|
||||||
func (frm FrameV4) SIAddr() *[4]byte {
|
func (frm Frame) SIAddr() *[4]byte {
|
||||||
return (*[4]byte)(frm.buf[20:24])
|
return (*[4]byte)(frm.buf[20:24])
|
||||||
}
|
}
|
||||||
|
|
||||||
// GIAddr is the gateway IP address.
|
// GIAddr is the gateway IP address.
|
||||||
func (frm FrameV4) GIAddr() *[4]byte {
|
func (frm Frame) GIAddr() *[4]byte {
|
||||||
return (*[4]byte)(frm.buf[24:28])
|
return (*[4]byte)(frm.buf[24:28])
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHAddrAs6 returns [FrameV4.CHAddr] but limited to first 6 bytes.
|
// CHAddrAs6 returns [Frame.CHAddr] but limited to first 6 bytes.
|
||||||
func (frm FrameV4) CHAddrAs6() *[6]byte {
|
func (frm Frame) CHAddrAs6() *[6]byte {
|
||||||
return (*[6]byte)(frm.buf[28 : 28+6])
|
return (*[6]byte)(frm.buf[28 : 28+6])
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHAddr is the client hardware address. Can be up to 16 bytes in length but
|
// CHAddr is the client hardware address. Can be up to 16 bytes in length but
|
||||||
// is usually 6 bytes for Ethernet.
|
// is usually 6 bytes for Ethernet.
|
||||||
func (frm FrameV4) CHAddr() *[16]byte {
|
func (frm Frame) CHAddr() *[16]byte {
|
||||||
return (*[16]byte)(frm.buf[28:44])
|
return (*[16]byte)(frm.buf[28:44])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (frm FrameV4) MagicCookie() uint32 {
|
func (frm Frame) MagicCookie() uint32 { return binary.BigEndian.Uint32(frm.buf[magicCookieOffset:]) }
|
||||||
return binary.BigEndian.Uint32(frm.buf[magicCookieOffset:])
|
func (frm Frame) SetMagicCookie(cookie uint32) {
|
||||||
|
binary.BigEndian.PutUint32(frm.buf[magicCookieOffset:], cookie)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearHeader zeros out the header contents.
|
// ClearHeader zeros out the header contents.
|
||||||
func (frm FrameV4) ClearHeader() {
|
func (frm Frame) ClearHeader() {
|
||||||
for i := range frm.buf[:sizeHeader] {
|
for i := range frm.buf[:optionsOffset] {
|
||||||
frm.buf[i] = 0
|
frm.buf[i] = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (frm FrameV4) ForEachOption(fn func(opt Option) error) error {
|
func (frm Frame) ForEachOption(fn func(op OptNum, data []byte) error) error {
|
||||||
if fn == nil {
|
if fn == nil {
|
||||||
return errors.New("nil function to parse DHCP")
|
return errors.New("nil function to parse DHCP")
|
||||||
}
|
}
|
||||||
@@ -126,7 +131,7 @@ func (frm FrameV4) ForEachOption(fn func(opt Option) error) error {
|
|||||||
}
|
}
|
||||||
optlen := frm.buf[ptr+1]
|
optlen := frm.buf[ptr+1]
|
||||||
optionData := frm.buf[ptr+2 : ptr+2+int(optlen)]
|
optionData := frm.buf[ptr+2 : ptr+2+int(optlen)]
|
||||||
if err := fn(Option{optnum, optionData}); err != nil {
|
if err := fn(optnum, optionData); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ptr += int(optlen) + 2
|
ptr += int(optlen) + 2
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by "stringer -type=OptNum,Op,MessageType,ClientState -linecomment -output stringers.go"; DO NOT EDIT.
|
// Code generated by "stringer -type=OptNum,Op,MessageType,ClientState -linecomment -output stringers.go"; DO NOT EDIT.
|
||||||
|
|
||||||
package dhcp
|
package dhcpv4
|
||||||
|
|
||||||
import "strconv"
|
import "strconv"
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ func _() {
|
|||||||
_ = x[MsgRequest-3]
|
_ = x[MsgRequest-3]
|
||||||
_ = x[MsgDecline-4]
|
_ = x[MsgDecline-4]
|
||||||
_ = x[MsgAck-5]
|
_ = x[MsgAck-5]
|
||||||
_ = x[MsgNak-6]
|
_ = x[MsgNack-6]
|
||||||
_ = x[MsgRelease-7]
|
_ = x[MsgRelease-7]
|
||||||
_ = x[MsgInform-8]
|
_ = x[MsgInform-8]
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewEthFrame returns a EthFrame with data set to buf.
|
// NewEthFrame returns a EthFrame with data set to buf.
|
||||||
|
// An error is returned if the buffer size is smaller than 14.
|
||||||
// Users should still call [EthFrame.ValidateSize] before working
|
// Users should still call [EthFrame.ValidateSize] before working
|
||||||
// with payload/options of frames to avoid panics.
|
// with payload/options of frames to avoid panics.
|
||||||
func NewEthFrame(buf []byte) (EthFrame, error) {
|
func NewEthFrame(buf []byte) (EthFrame, error) {
|
||||||
@@ -18,6 +19,7 @@ func NewEthFrame(buf []byte) (EthFrame, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewARPFrame returns a ARPFrame with data set to buf.
|
// NewARPFrame returns a ARPFrame with data set to buf.
|
||||||
|
// An error is returned if the buffer size is smaller than 28 (IPv4 min size).
|
||||||
// Users should still call [ARPFrame.ValidateSize] before working
|
// Users should still call [ARPFrame.ValidateSize] before working
|
||||||
// with payload/options of frames to avoid panics.
|
// with payload/options of frames to avoid panics.
|
||||||
func NewARPFrame(buf []byte) (ARPFrame, error) {
|
func NewARPFrame(buf []byte) (ARPFrame, error) {
|
||||||
@@ -28,6 +30,7 @@ func NewARPFrame(buf []byte) (ARPFrame, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewIPv4Frame returns a new IPv4Frame with data set to buf.
|
// NewIPv4Frame returns a new IPv4Frame with data set to buf.
|
||||||
|
// An error is returned if the buffer size is smaller than 20.
|
||||||
// Users should still call [IPv4Frame.ValidateSize] before working
|
// Users should still call [IPv4Frame.ValidateSize] before working
|
||||||
// with payload/options of frames to avoid panics.
|
// with payload/options of frames to avoid panics.
|
||||||
func NewIPv4Frame(buf []byte) (IPv4Frame, error) {
|
func NewIPv4Frame(buf []byte) (IPv4Frame, error) {
|
||||||
@@ -38,6 +41,7 @@ func NewIPv4Frame(buf []byte) (IPv4Frame, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewIPv6Frame returns a new IPv6Frame with data set to buf.
|
// NewIPv6Frame returns a new IPv6Frame with data set to buf.
|
||||||
|
// An error is returned if the buffer size is smaller than 40.
|
||||||
// Users should still call [IPv6Frame.ValidateSize] before working
|
// Users should still call [IPv6Frame.ValidateSize] before working
|
||||||
// with payload/options of frames to avoid panics.
|
// with payload/options of frames to avoid panics.
|
||||||
func NewIPv6Frame(buf []byte) (IPv6Frame, error) {
|
func NewIPv6Frame(buf []byte) (IPv6Frame, error) {
|
||||||
@@ -48,6 +52,7 @@ func NewIPv6Frame(buf []byte) (IPv6Frame, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewTCPFrame returns a new TCPFrame with data set to buf.
|
// NewTCPFrame returns a new TCPFrame with data set to buf.
|
||||||
|
// An error is returned if the buffer size is smaller than 20.
|
||||||
// Users should still call [TCPFrame.ValidateSize] before working
|
// Users should still call [TCPFrame.ValidateSize] before working
|
||||||
// with payload/options of frames to avoid panics.
|
// with payload/options of frames to avoid panics.
|
||||||
func NewTCPFrame(buf []byte) (TCPFrame, error) {
|
func NewTCPFrame(buf []byte) (TCPFrame, error) {
|
||||||
@@ -58,6 +63,7 @@ func NewTCPFrame(buf []byte) (TCPFrame, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewUDPFrame returns a new UDPFrame with data set to buf.
|
// NewUDPFrame returns a new UDPFrame with data set to buf.
|
||||||
|
// An error is returned if the buffer size is smaller than 8.
|
||||||
// Users should still call [UDPFrame.ValidateSize] before working
|
// Users should still call [UDPFrame.ValidateSize] before working
|
||||||
// with payload/options of frames to avoid panics.
|
// with payload/options of frames to avoid panics.
|
||||||
func NewUDPFrame(buf []byte) (UDPFrame, error) {
|
func NewUDPFrame(buf []byte) (UDPFrame, error) {
|
||||||
|
|||||||
+10
-5
@@ -32,12 +32,13 @@ type Client struct {
|
|||||||
t [4]Timestamp
|
t [4]Timestamp
|
||||||
// org Timestamp
|
// org Timestamp
|
||||||
// rec Timestamp
|
// rec Timestamp
|
||||||
xmt Timestamp
|
xmt Timestamp
|
||||||
state state
|
state state
|
||||||
_sysprec int8
|
serverStratum Stratum
|
||||||
|
_sysprec int8
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Write(payload []byte) (int, error) {
|
func (c *Client) Send(payload []byte) (int, error) {
|
||||||
if c.isDone() {
|
if c.isDone() {
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
@@ -71,7 +72,7 @@ func (c *Client) Write(payload []byte) (int, error) {
|
|||||||
return SizeHeader, nil
|
return SizeHeader, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) read(payload []byte) error {
|
func (c *Client) Read(payload []byte) error {
|
||||||
if c.isDone() {
|
if c.isDone() {
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
@@ -91,6 +92,7 @@ func (c *Client) read(payload []byte) error {
|
|||||||
t[1] = frm.ReceiveTime()
|
t[1] = frm.ReceiveTime()
|
||||||
t[2] = tstx
|
t[2] = tstx
|
||||||
t[3] = c.unsyncTimestamp(c.now())
|
t[3] = c.unsyncTimestamp(c.now())
|
||||||
|
c.serverStratum = frm.Stratum()
|
||||||
c.state = stateDone
|
c.state = stateDone
|
||||||
case stateAwait2:
|
case stateAwait2:
|
||||||
c.state = stateAwait2
|
c.state = stateAwait2
|
||||||
@@ -125,6 +127,9 @@ func (c *Client) Now() time.Time {
|
|||||||
return c.now().Add(c.Offset())
|
return c.now().Add(c.Offset())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServerStratum returns the stratum of the server client synchronized with.
|
||||||
|
func (c *Client) ServerStratum() Stratum { return c.serverStratum }
|
||||||
|
|
||||||
// Offset returns the
|
// Offset returns the
|
||||||
func (c *Client) Offset() time.Duration {
|
func (c *Client) Offset() time.Duration {
|
||||||
if c.isDone() {
|
if c.isDone() {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package ntp
|
package ntp
|
||||||
|
|
||||||
|
// LeapIndicator represents the leap second indicator.
|
||||||
|
// It indicates whether there is no warning, an extra second (61 seconds in the last minute),
|
||||||
|
// or a missing second (59 seconds in the last minute).
|
||||||
type LeapIndicator uint8
|
type LeapIndicator uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -8,20 +11,42 @@ const (
|
|||||||
LeapLastMinute59 // last minute 59
|
LeapLastMinute59 // last minute 59
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Stratum represents the stratum level of the NTP server.
|
||||||
|
type Stratum uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// If the Stratum field is 0, which implies unspecified or invalid, the
|
// If the Stratum field is 0, which implies unspecified or invalid, the
|
||||||
// Reference Identifier field can be used to convey messages useful for
|
// Reference Identifier field can be used to convey messages useful for
|
||||||
// status reporting and access control. These are called Kiss-o'-Death
|
// status reporting and access control. These are called Kiss-o'-Death
|
||||||
// (KoD) packets and the ASCII messages they convey are called kiss codes.
|
// (KoD) packets and the ASCII messages they convey are called kiss codes.
|
||||||
StratumUnspecified = 0
|
StratumUnspecified Stratum = 0 // unspecified
|
||||||
StratumPrimary = 1
|
StratumPrimary Stratum = 1 // primary
|
||||||
StratumUnsync = 16
|
StratumUnsync Stratum = 16 // unsynchronized
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsStratumSecondary(stratum uint8) bool {
|
// String returns a human readable representation of the Stratum.
|
||||||
return stratum > 1 && stratum < 16
|
func (s Stratum) String() string {
|
||||||
|
switch s {
|
||||||
|
case 0:
|
||||||
|
return "unspecified"
|
||||||
|
case 1:
|
||||||
|
return "primary"
|
||||||
|
case 16:
|
||||||
|
return "unsynchronized"
|
||||||
|
}
|
||||||
|
if s < 16 {
|
||||||
|
return "secondary"
|
||||||
|
}
|
||||||
|
return "invalid"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Stratum) IsSecondary() bool {
|
||||||
|
return s > 1 && s < 16
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode represents the mode of the NTP message.
|
||||||
|
// It can be undefined, symmetric active, symmetric passive, client, server, broadcast,
|
||||||
|
// NTP control message, or private use.
|
||||||
type Mode uint8
|
type Mode uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
+2
-2
@@ -53,8 +53,8 @@ func (frm Frame) SetFlags(mode Mode, version uint8, lp LeapIndicator) {
|
|||||||
frm.buf[0] = b
|
frm.buf[0] = b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (frm Frame) Stratum() uint8 { return frm.buf[1] }
|
func (frm Frame) Stratum() Stratum { return Stratum(frm.buf[1]) }
|
||||||
func (frm Frame) SetStratum(stratum uint8) { frm.buf[1] = stratum }
|
func (frm Frame) SetStratum(stratum Stratum) { frm.buf[1] = byte(stratum) }
|
||||||
|
|
||||||
// Poll is 8-bit signed integer representing the maximum interval between
|
// Poll is 8-bit signed integer representing the maximum interval between
|
||||||
// successive messages, in log2 seconds. Suggested default limits for
|
// successive messages, in log2 seconds. Suggested default limits for
|
||||||
|
|||||||
Reference in New Issue
Block a user