Files
lneto/lneto/dhcp/frame.go
T
2024-12-25 23:45:30 -03:00

136 lines
3.4 KiB
Go

package dhcp
import (
"encoding/binary"
"errors"
)
const (
sizeSName = 64 // Server name, part of BOOTP too.
sizeBootFile = 128 // Boot file name, Legacy.
sizeHeader = 44
// Magic Cookie offset measured from the start of the UDP payload.
magicCookieOffset = sizeHeader + sizeSName + sizeBootFile
// Expected Magic Cookie value.
MagicCookie uint32 = 0x63825363
// DHCP Options offset measured from the start of the UDP payload.
optionsOffset = magicCookieOffset + 4
DefaultClientPort = 68
DefaultServerPort = 67
)
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
// and provides methods for manipulating, validating and
// retrieving fields and payload data. See [RFC2131].
//
// [RFC2131]: https://tools.ietf.org/html/rfc2131
type FrameV4 struct {
buf []byte
}
func (frm FrameV4) Op() Op {
return Op(frm.buf[0])
}
func (frm FrameV4) Hardware() (Type, Len, Ops uint8) {
return frm.buf[1], frm.buf[2], frm.buf[3]
}
func (frm FrameV4) SetHardware(Type, Len, Ops uint8) {
frm.buf[1], frm.buf[2], frm.buf[3] = Type, Len, Ops
}
func (frm FrameV4) XID() uint32 {
return binary.BigEndian.Uint32(frm.buf[4:8])
}
func (frm FrameV4) Secs() uint16 {
return binary.BigEndian.Uint16(frm.buf[8:10])
}
func (frm FrameV4) Flags() uint16 {
return binary.BigEndian.Uint16(frm.buf[10:12])
}
// CIAddr is the client IP address. If the client has not obtained an IP
// address yet, this field is set to 0.
func (frm FrameV4) CIAddr() *[4]byte {
return (*[4]byte)(frm.buf[12:16])
}
// YIAddr is the IP address offered by the server to the client.
func (frm FrameV4) YIAddr() *[4]byte {
return (*[4]byte)(frm.buf[16:20])
}
// SIAddr is the IP address of the next server to use in bootstrap. This
// field is used in DHCPOFFER and DHCPACK messages.
func (frm FrameV4) SIAddr() *[4]byte {
return (*[4]byte)(frm.buf[20:24])
}
// GIAddr is the gateway IP address.
func (frm FrameV4) GIAddr() *[4]byte {
return (*[4]byte)(frm.buf[24:28])
}
// CHAddrAs6 returns [FrameV4.CHAddr] but limited to first 6 bytes.
func (frm FrameV4) CHAddrAs6() *[6]byte {
return (*[6]byte)(frm.buf[28 : 28+6])
}
// CHAddr is the client hardware address. Can be up to 16 bytes in length but
// is usually 6 bytes for Ethernet.
func (frm FrameV4) CHAddr() *[16]byte {
return (*[16]byte)(frm.buf[28:44])
}
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")
}
// Parse DHCP options.
ptr := optionsOffset
if ptr >= len(frm.buf) {
return errors.New("short payload to parse DHCP options")
}
for ptr+1 < len(frm.buf) {
if int(frm.buf[ptr+1]) >= len(frm.buf) {
return errors.New("DHCP option length exceeds payload")
}
optnum := OptNum(frm.buf[ptr])
if optnum == 0xff {
break
} else if optnum == OptWordAligned {
ptr++
continue
}
optlen := frm.buf[ptr+1]
optionData := frm.buf[ptr+2 : ptr+2+int(optlen)]
if err := fn(Option{optnum, optionData}); err != nil {
return err
}
ptr += int(optlen) + 2
}
return nil
}