mirror of
https://github.com/soypat/lneto.git
synced 2026-08-14 03:43:45 +00:00
add ntp.Client and begin adding dhcp.Client
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
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 }
|
||||
@@ -5,7 +5,30 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=OptNum,Op,MessageType -linecomment -output stringers.go
|
||||
//go:generate stringer -type=OptNum,Op,MessageType,ClientState -linecomment -output stringers.go
|
||||
|
||||
type ClientState uint8
|
||||
|
||||
// State transition table:
|
||||
//
|
||||
// StateInit -> | Send out Discover | -> StateSelecting
|
||||
// StateSelecting -> |Accept Offer+Request| -> StateRequesting
|
||||
// StateRequesting-> | Receive Ack | -> StateBound
|
||||
const (
|
||||
_ ClientState = iota
|
||||
// On clean slate boot, abort, NAK or decline enter the INIT state.
|
||||
StateInit // init
|
||||
// After sending out a Discover enter SELECTING.
|
||||
StateSelecting // selecting
|
||||
// After receiving a worthy offer and sending out request for offer enter REQUESTING.
|
||||
StateRequesting // requesting
|
||||
// On ACK to Request enter BOUND.
|
||||
StateBound // bound
|
||||
StateRenewing // renewing
|
||||
StateRebinding // rebinding
|
||||
StateInitReboot // init-reboot
|
||||
StateRebooting // rebooting
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
Num OptNum
|
||||
|
||||
+12
-2
@@ -20,8 +20,11 @@ const (
|
||||
DefaultServerPort = 67
|
||||
)
|
||||
|
||||
func NewFrameV4(buf []byte) FrameV4 {
|
||||
return FrameV4{buf: buf}
|
||||
func NewFrameV4(buf []byte) (FrameV4, error) {
|
||||
if len(buf) < sizeHeader {
|
||||
return FrameV4{}, errors.New("DHCP short frame")
|
||||
}
|
||||
return FrameV4{buf: buf}, nil
|
||||
}
|
||||
|
||||
// Frame encapsulates the raw data of a DHCP packet
|
||||
@@ -94,6 +97,13 @@ func (frm FrameV4) MagicCookie() uint32 {
|
||||
return binary.BigEndian.Uint32(frm.buf[magicCookieOffset:])
|
||||
}
|
||||
|
||||
// ClearHeader zeros out the header contents.
|
||||
func (frm FrameV4) ClearHeader() {
|
||||
for i := range frm.buf[:sizeHeader] {
|
||||
frm.buf[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (frm FrameV4) ForEachOption(fn func(opt Option) error) error {
|
||||
if fn == nil {
|
||||
return errors.New("nil function to parse DHCP")
|
||||
|
||||
+26
-1
@@ -1,4 +1,4 @@
|
||||
// Code generated by "stringer -type=OptNum,Op,MessageType -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
|
||||
|
||||
@@ -126,3 +126,28 @@ func (i MessageType) String() string {
|
||||
}
|
||||
return _MessageType_name[_MessageType_index[i]:_MessageType_index[i+1]]
|
||||
}
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[StateInit-1]
|
||||
_ = x[StateSelecting-2]
|
||||
_ = x[StateRequesting-3]
|
||||
_ = x[StateBound-4]
|
||||
_ = x[StateRenewing-5]
|
||||
_ = x[StateRebinding-6]
|
||||
_ = x[StateInitReboot-7]
|
||||
_ = x[StateRebooting-8]
|
||||
}
|
||||
|
||||
const _ClientState_name = "initselectingrequestingboundrenewingrebindinginit-rebootrebooting"
|
||||
|
||||
var _ClientState_index = [...]uint8{0, 4, 13, 23, 28, 36, 45, 56, 65}
|
||||
|
||||
func (i ClientState) String() string {
|
||||
i -= 1
|
||||
if i >= ClientState(len(_ClientState_index)-1) {
|
||||
return "ClientState(" + strconv.FormatInt(int64(i+1), 10) + ")"
|
||||
}
|
||||
return _ClientState_name[_ClientState_index[i]:_ClientState_index[i+1]]
|
||||
}
|
||||
|
||||
@@ -97,6 +97,13 @@ func (frm Frame) SetARCount(arCount uint16) {
|
||||
binary.BigEndian.PutUint16(frm.buf[10:12], arCount)
|
||||
}
|
||||
|
||||
// ClearHeader zeros out the fixed(non-variable) header contents.
|
||||
func (frm Frame) ClearHeader() {
|
||||
for i := range frm.buf[:SizeHeader] {
|
||||
frm.buf[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// HeaderFlags gathers the flags in bits 16..31 of the header.
|
||||
type HeaderFlags uint16
|
||||
|
||||
|
||||
@@ -125,6 +125,13 @@ func (efrm EthFrame) IsVLAN() bool {
|
||||
return efrm.EtherTypeOrSize() == EtherTypeVLAN
|
||||
}
|
||||
|
||||
// ClearHeader zeros out the fixed(non-variable) header contents.
|
||||
func (frm EthFrame) ClearHeader() {
|
||||
for i := range frm.buf[:sizeHeaderEthNoVLAN] {
|
||||
frm.buf[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// ARPFrame encapsulates the raw data of an ARP packet
|
||||
// and provides methods for manipulating, validating and
|
||||
// retrieving fields and payload data. See [RFC826].
|
||||
@@ -208,6 +215,13 @@ func (afrm ARPFrame) Target16() (hardwareAddr *[6]byte, proto *[16]byte) {
|
||||
return (*[6]byte)(afrm.buf[30:36]), (*[16]byte)(afrm.buf[36:52])
|
||||
}
|
||||
|
||||
// ClearHeader zeros out the fixed(non-variable) header contents.
|
||||
func (frm ARPFrame) ClearHeader() {
|
||||
for i := range frm.buf[:8] {
|
||||
frm.buf[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// IPv4Frame encapsulates the raw data of an IPv4 packet
|
||||
// and provides methods for manipulating, validating and
|
||||
// retreiving fields and payload data. See [RFC791].
|
||||
@@ -356,6 +370,13 @@ func (ifrm IPv4Frame) Payload() []byte {
|
||||
return ifrm.buf[off:l]
|
||||
}
|
||||
|
||||
// ClearHeader zeros out the fixed(non-variable) header contents.
|
||||
func (frm IPv4Frame) ClearHeader() {
|
||||
for i := range frm.buf[:sizeHeaderIPv4] {
|
||||
frm.buf[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// IPv6Frame encapsulates the raw data of an IPv6 packet
|
||||
// and provides methods for manipulating, validating and
|
||||
// retrieving fields and payload data. See [RFC8200].
|
||||
@@ -443,6 +464,13 @@ func (ifrm IPv6Frame) crcWritePseudo(crc *CRC791) {
|
||||
crc.AddUint32(uint32(ifrm.NextHeader()))
|
||||
}
|
||||
|
||||
// ClearHeader zeros out the header contents.
|
||||
func (frm IPv6Frame) ClearHeader() {
|
||||
for i := range frm.buf[:sizeHeaderIPv6] {
|
||||
frm.buf[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// TCPFrame encapsulates the raw data of a TCP segment
|
||||
// and provides methods for manipulating, validating and
|
||||
// retrieving fields and payload data. See [RFC9293].
|
||||
@@ -581,6 +609,13 @@ func (tfrm TCPFrame) Options() []byte {
|
||||
return tfrm.buf[sizeHeaderTCP:tfrm.HeaderLength()]
|
||||
}
|
||||
|
||||
// ClearHeader zeros out the fixed(non-variable) header contents.
|
||||
func (frm TCPFrame) ClearHeader() {
|
||||
for i := range frm.buf[:sizeHeaderTCP] {
|
||||
frm.buf[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// UDPFrame encapsulates the raw data of a UDP datagram
|
||||
// and provides methods for manipulating, validating and
|
||||
// retrieving fields and payload data. See [RFC768].
|
||||
@@ -662,3 +697,10 @@ func (ufrm UDPFrame) CalculateIPv6Checksum(ifrm IPv6Frame) uint16 {
|
||||
crc.Write(ufrm.Payload())
|
||||
return crc.Sum16()
|
||||
}
|
||||
|
||||
// ClearHeader zeros out the header contents.
|
||||
func (frm UDPFrame) ClearHeader() {
|
||||
for i := range frm.buf[:sizeHeaderUDP] {
|
||||
frm.buf[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
package ntp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type state uint8
|
||||
|
||||
const (
|
||||
stateClosed state = iota
|
||||
stateSend1
|
||||
stateAwait1
|
||||
stateSend2
|
||||
stateAwait2
|
||||
stateDone
|
||||
)
|
||||
|
||||
const sysprecRecalcNeeded int8 = 127
|
||||
|
||||
func NewClient(now func() time.Time) *Client {
|
||||
return &Client{
|
||||
_now: now,
|
||||
_sysprec: sysprecRecalcNeeded,
|
||||
}
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
start time.Time
|
||||
_now func() time.Time
|
||||
t [4]Timestamp
|
||||
// org Timestamp
|
||||
// rec Timestamp
|
||||
xmt Timestamp
|
||||
state state
|
||||
_sysprec int8
|
||||
}
|
||||
|
||||
func (c *Client) Write(payload []byte) (int, error) {
|
||||
if c.isDone() {
|
||||
return 0, io.EOF
|
||||
}
|
||||
frm, err := NewFrame(payload)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
switch c.state {
|
||||
case stateSend1:
|
||||
c.start = c.now()
|
||||
c.xmt = TimestampFromUint64(0)
|
||||
c.state = stateAwait1
|
||||
case stateSend2:
|
||||
c.xmt = c.unsyncTimestamp(c.now())
|
||||
c.state = stateDone
|
||||
default:
|
||||
return 0, nil // Nothing to handle.
|
||||
}
|
||||
|
||||
for i := range payload[:SizeHeader] {
|
||||
payload[i] = 0
|
||||
}
|
||||
sysprec := c.sysprec()
|
||||
frm.ClearHeader()
|
||||
frm.SetStratum(StratumUnsync)
|
||||
frm.SetPoll(6)
|
||||
frm.SetPrecision(sysprec)
|
||||
frm.SetOriginTime(c.xmt)
|
||||
frm.SetFlags(ModeClient, Version4, LeapNoWarning)
|
||||
return SizeHeader, nil
|
||||
}
|
||||
|
||||
func (c *Client) read(payload []byte) error {
|
||||
if c.isDone() {
|
||||
return io.EOF
|
||||
}
|
||||
frm, err := NewFrame(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t := &c.t
|
||||
switch c.state {
|
||||
case stateAwait1:
|
||||
tstx := frm.TransmitTime()
|
||||
tsorig := frm.OriginTime()
|
||||
if tstx == tsorig || tsorig == c.xmt {
|
||||
return errors.New("bogus NTP packet")
|
||||
}
|
||||
t[0] = tsorig
|
||||
t[1] = frm.ReceiveTime()
|
||||
t[2] = tstx
|
||||
t[3] = c.unsyncTimestamp(c.now())
|
||||
c.state = stateDone
|
||||
case stateAwait2:
|
||||
c.state = stateAwait2
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) isDone() bool {
|
||||
return c.state == stateDone
|
||||
}
|
||||
|
||||
func (c *Client) now() time.Time {
|
||||
if c._now == nil {
|
||||
return time.Now()
|
||||
}
|
||||
return c._now()
|
||||
}
|
||||
|
||||
func (c *Client) unsyncTimestamp(now time.Time) Timestamp {
|
||||
return TimestampFromUint64(0).Add(now.Sub(c.start))
|
||||
}
|
||||
|
||||
func (c *Client) sysprec() int8 {
|
||||
if c._sysprec == sysprecRecalcNeeded {
|
||||
c._sysprec = CalculateSystemPrecision(c._now)
|
||||
}
|
||||
return c._sysprec
|
||||
}
|
||||
|
||||
// Now returns the current time as corrected by NTP protocol.
|
||||
func (c *Client) Now() time.Time {
|
||||
return c.now().Add(c.Offset())
|
||||
}
|
||||
|
||||
// Offset returns the
|
||||
func (c *Client) Offset() time.Duration {
|
||||
if c.isDone() {
|
||||
t := &c.t
|
||||
return t[1].Sub(t[0])/2 + t[2].Sub(t[3])/2
|
||||
}
|
||||
return 0
|
||||
}
|
||||
+32
-5
@@ -24,10 +24,18 @@ const (
|
||||
MinDispDiv = 200 // Minimum dispersion divisor 1/(200) == 0.005
|
||||
)
|
||||
|
||||
func NewFrame(buf []byte) Frame {
|
||||
return Frame{buf: buf}
|
||||
func NewFrame(buf []byte) (Frame, error) {
|
||||
if len(buf) < SizeHeader {
|
||||
return Frame{buf: nil}, errors.New("NTP frame too short")
|
||||
}
|
||||
return Frame{buf: buf}, nil
|
||||
}
|
||||
|
||||
// Frame encapsulates the raw data of an NTP packet
|
||||
// and provides methods for manipulating, validating and
|
||||
// retrieving fields and payload data. See [RFC5905].
|
||||
//
|
||||
// [RFC5905]: https://tools.ietf.org/html/rfc5905
|
||||
type Frame struct {
|
||||
buf []byte
|
||||
}
|
||||
@@ -123,6 +131,13 @@ func (frm Frame) SetTransmitTime(rt Timestamp) {
|
||||
rt.Put(frm.buf[40:48])
|
||||
}
|
||||
|
||||
// ClearHeader zeros out the header contents.
|
||||
func (frm Frame) ClearHeader() {
|
||||
for i := range frm.buf[:SizeHeader] {
|
||||
frm.buf[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
type Short uint32
|
||||
|
||||
var baseTime = time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
@@ -262,11 +277,23 @@ func SystemPrecision() int8 {
|
||||
}
|
||||
|
||||
func recalculateSystemPrecision() {
|
||||
sysPrec = CalculateSystemPrecision(nil)
|
||||
}
|
||||
|
||||
// CalculateSystemPrecision calculates the NTP system precision for a time source.
|
||||
// If the time source is nil the default static call to [time.Now] is used.
|
||||
func CalculateSystemPrecision(now func() time.Time) int8 {
|
||||
const maxIter = 16
|
||||
var times [maxIter]time.Time
|
||||
for i := 0; i < maxIter; i++ {
|
||||
times[i] = time.Now()
|
||||
if now == nil {
|
||||
for i := 0; i < maxIter; i++ {
|
||||
times[i] = time.Now()
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < maxIter; i++ {
|
||||
times[i] = now()
|
||||
}
|
||||
}
|
||||
avg := times[maxIter-1].Sub(times[0]) / maxIter
|
||||
sysPrec = int8(math.Log2(avg.Seconds()))
|
||||
return int8(math.Log2(avg.Seconds()))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user