Merge pull request #3 from soypat/dhcp-encoding

switch to encoding DHCP options instead of appending
This commit is contained in:
Patricio Whittingslow
2025-07-09 16:55:47 -03:00
committed by GitHub
11 changed files with 345 additions and 90 deletions
+55 -40
View File
@@ -19,6 +19,7 @@ type Client struct {
hostname []byte hostname []byte
dns [][4]byte dns [][4]byte
svIPtos ipv4.ToS
tRenew uint32 tRenew uint32
tRebind uint32 tRebind uint32
tIPLease uint32 tIPLease uint32
@@ -42,6 +43,7 @@ type RequestConfig struct {
ClientHardwareAddr [6]byte ClientHardwareAddr [6]byte
// Optional hostname to request. // Optional hostname to request.
Hostname string Hostname string
ClientID string
} }
func (c *Client) BeginRequest(xid uint32, cfg RequestConfig) error { func (c *Client) BeginRequest(xid uint32, cfg RequestConfig) error {
@@ -72,26 +74,16 @@ func (c *Client) setIP(b []byte, frameOffset int) {
ifrm, _ := ipv4.NewFrame(b) ifrm, _ := ipv4.NewFrame(b)
ifrm.SetID((uint16(c.currentXID) ^ uint16(c.currentXID>>16)) + uint16(c.state)) ifrm.SetID((uint16(c.currentXID) ^ uint16(c.currentXID>>16)) + uint16(c.state))
if c.state > StateInit { if c.state > StateInit {
// TODO(soypat): Document why disabling ToS used by DHCP server may cause Request to fail. // Match server ToS since some routers drop DHCP requests if no ToS set apparently?
// Apparently server sets ToS=192. Uncommenting this line causes DHCP to fail on my setup. ifrm.SetToS(c.svIPtos)
// If left fixed at 192, DHCP does not work.
// If left fixed at 0, DHCP does not work.
// Apparently ToS is a function of which state of DHCP one is in. Not sure why code below works.
// Note: Not exactly needed for all servers.
const ecnmask = 0b1100_0000
ifrm.SetToS(ecnmask)
} }
src := ifrm.SourceAddr() src := ifrm.SourceAddr()
for i := range src { for i := range src {
src[i] = 0 src[i] = 0
} }
dst := ifrm.DestinationAddr()[:] dst := ifrm.DestinationAddr()[:]
if c.svip == ([4]byte{}) { for i := range dst {
for i := range dst { dst[i] = 255
dst[i] = 255
}
} else {
copy(dst, c.svip[:])
} }
} }
@@ -102,30 +94,37 @@ func (c *Client) Encapsulate(carrierFrame []byte, frameOffset int) (int, error)
return 0, nil // No offer received yet. return 0, nil // No offer received yet.
} else if c.state == StateBound { } else if c.state == StateBound {
return 0, nil // Done! return 0, nil // Done!
} else if c.state == StateRequesting {
return 0, nil // Currently awaiting ACK.
} }
dst := carrierFrame[frameOffset:] dst := carrierFrame[frameOffset:]
frm, err := NewFrame(dst) frm, err := NewFrame(dst)
if err != nil { if err != nil {
return 0, err return 0, err
} }
opts := frm.OptionsPayload()
if len(opts) < 255 {
return 0, errors.New("too short packet for options")
}
// var options []Option
// var nextState ClientState
optBuf := c.auxbuf[:0]
var nextState ClientState var nextState ClientState
var numOpts int
switch c.state { switch c.state {
case StateInit: case StateInit:
// Send out discover. // Send out discover.
optBuf = AppendOption(optBuf, OptMessageType, byte(MsgDiscover)) n, _ := EncodeOption(opts[numOpts:], OptMessageType, byte(MsgDiscover))
optBuf = AppendOption(optBuf, OptParameterRequestList, defaultParamReqList...) numOpts += n
optBuf = AppendOption(optBuf, OptClientIdentifier, c.clientMAC[:]...) n, _ = EncodeOption(opts[numOpts:], OptParameterRequestList, defaultParamReqList...)
numOpts += n
maxlen := len(dst) maxlen := len(dst)
if maxlen > math.MaxUint16 { if maxlen > math.MaxUint16 {
maxlen = math.MaxUint16 maxlen = math.MaxUint16
} }
optBuf = AppendOption(optBuf, OptMaximumMessageSize, byte(maxlen>>8), byte(maxlen)) n, _ = EncodeOption16(opts[numOpts:], OptMaximumMessageSize, uint16(maxlen))
numOpts += n
if c.reqIP != [4]byte{} { if c.reqIP != [4]byte{} {
optBuf = AppendOption(optBuf, OptRequestedIPaddress, c.reqIP[:]...) n, _ = EncodeOption(opts[numOpts:], OptRequestedIPaddress, c.reqIP[:]...)
numOpts += n
} }
nextState = StateSelecting nextState = StateSelecting
@@ -134,30 +133,37 @@ func (c *Client) Encapsulate(carrierFrame []byte, frameOffset int) (int, error)
return 0, nil // Offer not yet received. return 0, nil // Offer not yet received.
} }
// Send out request, we know we've received an offer by now. // Send out request, we know we've received an offer by now.
optBuf = AppendOption(optBuf, OptMessageType, byte(MsgRequest)) n, _ := EncodeOption(opts[numOpts:], OptMessageType, byte(MsgRequest))
optBuf = AppendOption(optBuf, OptRequestedIPaddress, c.offer[:]...) numOpts += n
optBuf = AppendOption(optBuf, OptServerIdentification, c.svip[:]...) n, _ = EncodeOption(opts[numOpts:], OptRequestedIPaddress, c.offer[:]...)
numOpts += n
n, _ = EncodeOption(opts[numOpts:], OptServerIdentification, c.svip[:]...)
numOpts += n
nextState = StateRequesting nextState = StateRequesting
default: default:
return 0, errors.New("unhandled state") return 0, errors.New("unhandled state" + c.state.String())
} }
n, _ := EncodeOption(opts[numOpts:], OptClientIdentifier, c.clientMAC[:]...)
numOpts += n
if len(c.reqHostname) > 0 { if len(c.reqHostname) > 0 {
optBuf = AppendOptionString(optBuf, OptHostName, c.reqHostname) n, err := EncodeOptionString(opts[numOpts:], OptHostName, c.reqHostname)
} numOpts += n
optBuf = append(optBuf, 0xff) // End mark. if err != nil {
options := frm.OptionsPayload() return 0, err
if len(optBuf) > len(options) { }
return 0, errors.New("DHCPv4 short buffer for options")
} }
opts[numOpts] = byte(OptEnd)
numOpts++
c.setHeader(frm) c.setHeader(frm)
n := copy(options, optBuf)
c.setIP(carrierFrame, frameOffset) c.setIP(carrierFrame, frameOffset)
c.state = nextState c.state = nextState
return optionsOffset + n, nil return OptionsOffset + numOpts, nil
} }
func (c *Client) Demux(carrierData []byte, frameOffset int) error { func (c *Client) Demux(carrierData []byte, frameOffset int) error {
fmt.Println("DEMUX DHCP")
if c.isClosed() { if c.isClosed() {
return net.ErrClosed return net.ErrClosed
} }
@@ -200,13 +206,17 @@ func (c *Client) Demux(carrierData []byte, frameOffset int) error {
default: default:
return fmt.Errorf("dcpv4 unexpected state in recv %s", c.state.String()) return fmt.Errorf("dcpv4 unexpected state in recv %s", c.state.String())
} }
if frameOffset > 28 && c.svIPtos == 0 {
ifrm, _ := ipv4.NewFrame(carrierData)
c.svIPtos = ifrm.ToS()
}
return nil return nil
} }
func (c *Client) getMessageType(frm Frame) MessageType { func (c *Client) getMessageType(frm Frame) MessageType {
c.auxbuf[0] = 255 c.auxbuf[0] = 255
ptrMsgType := &c.auxbuf[0] ptrMsgType := &c.auxbuf[0]
frm.ForEachOption(func(opt OptNum, data []byte) error { frm.ForEachOption(func(_ int, opt OptNum, data []byte) error {
if len(data) == 1 { if len(data) == 1 {
*ptrMsgType = data[0] *ptrMsgType = data[0]
return io.EOF return io.EOF
@@ -217,7 +227,7 @@ func (c *Client) getMessageType(frm Frame) MessageType {
} }
func (c *Client) setOptions(frm Frame) error { func (c *Client) setOptions(frm Frame) error {
return frm.ForEachOption(func(opt OptNum, data []byte) error { err := frm.ForEachOption(func(_ int, opt OptNum, data []byte) error {
switch opt { switch opt {
case OptRenewTimeValue: case OptRenewTimeValue:
c.tRenew = maybeU32(data) c.tRenew = maybeU32(data)
@@ -225,7 +235,6 @@ func (c *Client) setOptions(frm Frame) error {
c.tIPLease = maybeU32(data) c.tIPLease = maybeU32(data)
case OptRebindingTimeValue: case OptRebindingTimeValue:
c.tRebind = maybeU32(data) c.tRebind = maybeU32(data)
case OptServerIdentification: case OptServerIdentification:
c.svip = maybe4byte(data) c.svip = maybe4byte(data)
case OptRouter: case OptRouter:
@@ -249,6 +258,7 @@ func (c *Client) setOptions(frm Frame) error {
} }
return nil return nil
}) })
return err
} }
func (c *Client) isClosed() bool { return c.state == 0 || c.currentXID == 0 } func (c *Client) isClosed() bool { return c.state == 0 || c.currentXID == 0 }
@@ -260,16 +270,21 @@ func (c *Client) setHeader(frm Frame) {
frm.SetHardware(1, 6, 0) frm.SetHardware(1, 6, 0)
frm.SetSecs(1) frm.SetSecs(1)
if c.state.HasIP() { if c.state.HasIP() {
copy(frm.CIAddr()[:], c.offer[:]) *frm.CIAddr() = c.offer
} }
if c.state == StateInit { if c.state == StateInit {
siaddr := frm.SIAddr()[:] siaddr := frm.SIAddr()[:]
for i := range siaddr { for i := range siaddr {
siaddr[i] = 255 siaddr[i] = 255
} }
} else {
if c.siip == [4]byte{} {
*frm.SIAddr() = c.svip
} else {
*frm.SIAddr() = c.siip
}
} }
*frm.YIAddr() = c.offer
copy(frm.YIAddr()[:], c.offer[:])
copy(frm.CHAddrAs6()[:], c.clientMAC[:]) copy(frm.CHAddrAs6()[:], c.clientMAC[:])
frm.SetMagicCookie(MagicCookie) frm.SetMagicCookie(MagicCookie)
} }
+14 -10
View File
@@ -35,18 +35,19 @@ func (state ClientState) HasIP() bool {
return state == StateBound || state == StateRenewing || state == StateRebinding return state == StateBound || state == StateRenewing || state == StateRebinding
} }
func AppendOption(dst []byte, opt OptNum, data ...byte) []byte { func EncodeOptionString(dst []byte, opt OptNum, data string) (int, error) {
if len(data) > 255 { bdata := unsafe.Slice(unsafe.StringData(data), len(data))
panic("option data too long") return EncodeOption(dst, opt, bdata...)
}
dst = append(dst, byte(opt), byte(len(data)))
dst = append(dst, data...)
return dst
} }
func AppendOptionString(dst []byte, opt OptNum, data string) []byte { func EncodeOption16(dst []byte, opt OptNum, v uint16) (int, error) {
bdata := unsafe.Slice(unsafe.StringData(data), len(data)) // See binary.BigEndian.PutUint16()
return AppendOption(dst, opt, bdata...) return EncodeOption(dst, opt, byte(v>>8), byte(v))
}
func EncodeOption32(dst []byte, opt OptNum, v uint32) (int, error) {
// See binary.BigEndian.PutUint32()
return EncodeOption(dst, opt, byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
} }
func EncodeOption(dst []byte, opt OptNum, data ...byte) (int, error) { func EncodeOption(dst []byte, opt OptNum, data ...byte) (int, error) {
@@ -66,6 +67,8 @@ type OptNum uint8
// DHCP options. Taken from https://help.sonicwall.com/help/sw/eng/6800/26/2/3/content/Network_DHCP_Server.042.12.htm. // DHCP options. Taken from https://help.sonicwall.com/help/sw/eng/6800/26/2/3/content/Network_DHCP_Server.042.12.htm.
const ( const (
OptEnd OptNum = 255 // end options
OptWordAligned OptNum = 0 // word-aligned OptWordAligned OptNum = 0 // word-aligned
OptSubnetMask OptNum = 1 // subnet mask OptSubnetMask OptNum = 1 // subnet mask
OptTimeOffset OptNum = 2 // Time offset in seconds from UTC OptTimeOffset OptNum = 2 // Time offset in seconds from UTC
@@ -128,6 +131,7 @@ const (
OptRebindingTimeValue OptNum = 59 // DHCP rebinding (T2) time OptRebindingTimeValue OptNum = 59 // DHCP rebinding (T2) time
OptClientIdentifier OptNum = 60 // Client identifier OptClientIdentifier OptNum = 60 // Client identifier
OptClientIdentifier1 OptNum = 61 // Client identifier(1) OptClientIdentifier1 OptNum = 61 // Client identifier(1)
) )
type Op byte type Op byte
+76
View File
@@ -78,3 +78,79 @@ func TestClientServer(t *testing.T) {
} }
assertClState(StateBound) assertClState(StateBound)
} }
func TestExample(t *testing.T) {
const (
xid = 1
offerLease = 9001
)
var cl Client
clientHwaddr := [6]byte{0, 0, 0, 0, 0, 1}
clientReqAddr := [4]byte{192, 168, 1, 2}
clientHostname := "client"
serverIP := [4]byte{192, 168, 1, 1}
subnetMask := [4]byte{255, 255, 255, 0}
routerAddr := [4]byte{192, 168, 1, 0}
dnsAddr := [4]byte{192, 168, 1, 255}
cl.BeginRequest(xid, RequestConfig{
RequestedAddr: clientReqAddr,
ClientHardwareAddr: clientHwaddr,
Hostname: clientHostname,
})
buf := make([]byte, 2048)
buf2 := make([]byte, len(buf))
n, err := cl.Encapsulate(buf, 0)
if err != nil {
t.Fatal(err)
} else if n <= 0 {
t.Fatal("no data sent out by client after starting request")
}
n, err = cl.Encapsulate(buf2, 0)
if err != nil {
t.Error("client encaps double tap after discover:", err)
}
// Fabricate server OFFER response.
dfrm, _ := NewFrame(buf)
dfrm.ClearHeader()
dfrm.SetOp(OpReply)
dfrm.SetHardware(1, 6, 0)
dfrm.SetFlags(0)
dfrm.SetXID(xid)
dfrm.SetSecs(1)
*dfrm.YIAddr() = clientReqAddr
copy(dfrm.CHAddr()[:], clientHwaddr[:])
dfrm.SetMagicCookie(MagicCookie)
ntot := 0
nopt, _ := EncodeOption(buf[OptionsOffset+ntot:], OptMessageType, byte(MsgOffer))
ntot += nopt
nopt, _ = EncodeOption(buf[OptionsOffset+ntot:], OptServerIdentification, serverIP[:]...)
ntot += nopt
nopt, _ = EncodeOption32(buf[OptionsOffset+ntot:], OptServerIdentification, offerLease)
ntot += nopt
nopt, _ = EncodeOption(buf[OptionsOffset+ntot:], OptSubnetMask, subnetMask[:]...)
ntot += nopt
nopt, _ = EncodeOption(buf[OptionsOffset+ntot:], OptRouter, routerAddr[:]...)
ntot += nopt
nopt, _ = EncodeOption(buf[OptionsOffset+ntot:], OptDNSServers, dnsAddr[:]...)
ntot += nopt
nopt, _ = EncodeOption(buf[OptionsOffset+ntot:], OptEnd, dnsAddr[:]...)
ntot += nopt
err = cl.Demux(buf[:OptionsOffset+ntot], 0)
if err != nil {
t.Fatal(err)
}
n, err = cl.Encapsulate(buf[:], 0)
if err != nil {
t.Fatal(err)
} else if n <= 0 {
t.Fatal("no data written from client in response to offer")
}
n, err = cl.Encapsulate(buf[:], 0)
if err != nil {
t.Error("encapsulate double tap after request:", err)
} else if n > 0 {
t.Error("encapsulate double tap got data!", n)
}
}
+11 -7
View File
@@ -17,7 +17,7 @@ const (
// Expected Magic Cookie value. // Expected Magic Cookie value.
MagicCookie uint32 = 0x63825363 MagicCookie uint32 = 0x63825363
// DHCP Options offset measured from the start of the UDP payload. // DHCP Options offset measured from the start of the UDP payload.
optionsOffset = magicCookieOffset + 4 OptionsOffset = magicCookieOffset + 4
DefaultClientPort = 68 DefaultClientPort = 68
DefaultServerPort = 67 DefaultServerPort = 67
@@ -26,12 +26,16 @@ const (
// NewFrame returns a new DHCPv4 Frame with data set to buf. // NewFrame returns a new DHCPv4 Frame with data set to buf.
// An error is returned if the buffer size is smaller than 240. // An error is returned if the buffer size is smaller than 240.
func NewFrame(buf []byte) (Frame, error) { func NewFrame(buf []byte) (Frame, error) {
if len(buf) < optionsOffset { if len(buf) < OptionsOffset {
return Frame{}, errSmallFrame return Frame{}, errSmallFrame
} }
return Frame{buf: buf}, nil return Frame{buf: buf}, nil
} }
func PayloadIsDHCPv4(payload []byte) bool {
return len(payload) >= OptionsOffset && binary.BigEndian.Uint32(payload[magicCookieOffset:]) == MagicCookie
}
// Frame encapsulates the raw data of a DHCP packet // Frame encapsulates the raw data of a DHCP packet
// and provides methods for manipulating, validating and // and provides methods for manipulating, validating and
// retrieving fields and payload data. See [RFC2131]. // retrieving fields and payload data. See [RFC2131].
@@ -43,7 +47,7 @@ type Frame struct {
// OptionsPayload returns the options portion of the DHCP frame. May be zero lengthed. // OptionsPayload returns the options portion of the DHCP frame. May be zero lengthed.
func (frm Frame) OptionsPayload() []byte { func (frm Frame) OptionsPayload() []byte {
return frm.buf[optionsOffset:] return frm.buf[OptionsOffset:]
} }
func (frm Frame) Op() Op { return Op(frm.buf[0]) } func (frm Frame) Op() Op { return Op(frm.buf[0]) }
@@ -111,16 +115,16 @@ func (frm Frame) SetMagicCookie(cookie uint32) {
// ClearHeader zeros out the header contents. // ClearHeader zeros out the header contents.
func (frm Frame) ClearHeader() { func (frm Frame) ClearHeader() {
for i := range frm.buf[:optionsOffset] { for i := range frm.buf[:OptionsOffset] {
frm.buf[i] = 0 frm.buf[i] = 0
} }
} }
// ForEachOption iterates over all DHCPv4 options returning an error on a malformed option or when user provided callback returns an error. // ForEachOption iterates over all DHCPv4 options returning an error on a malformed option or when user provided callback returns an error.
// If the user provided callback is nil then only option buffer validation is performed. // If the user provided callback is nil then only option buffer validation is performed.
func (frm Frame) ForEachOption(fn func(op OptNum, data []byte) error) error { func (frm Frame) ForEachOption(fn func(off int, op OptNum, data []byte) error) error {
// Parse DHCP options. // Parse DHCP options.
ptr := optionsOffset ptr := OptionsOffset
if ptr > len(frm.buf) { if ptr > len(frm.buf) {
return errSmallFrame return errSmallFrame
} else if len(frm.buf[ptr:]) == 0 { } else if len(frm.buf[ptr:]) == 0 {
@@ -141,7 +145,7 @@ func (frm Frame) ForEachOption(fn func(op OptNum, data []byte) error) error {
optlen := frm.buf[ptr+1] optlen := frm.buf[ptr+1]
if callback { if callback {
optionData := frm.buf[ptr+2 : ptr+2+int(optlen)] optionData := frm.buf[ptr+2 : ptr+2+int(optlen)]
if err := fn(optnum, optionData); err != nil { if err := fn(ptr, optnum, optionData); err != nil {
return err return err
} }
} }
+26 -8
View File
@@ -77,7 +77,7 @@ func (sv *Server) Demux(carrierData []byte, frameOffset int) error {
var reqlist []byte var reqlist []byte
var reqAddr []byte var reqAddr []byte
var hostname []byte var hostname []byte
err = dfrm.ForEachOption(func(op OptNum, data []byte) error { err = dfrm.ForEachOption(func(off int, op OptNum, data []byte) error {
switch op { switch op {
case OptMessageType: case OptMessageType:
if len(data) == 1 { if len(data) == 1 {
@@ -136,8 +136,14 @@ func (sv *Server) Demux(carrierData []byte, frameOffset int) error {
sv.pending++ sv.pending++
case MsgRequest: case MsgRequest:
if client.state != StateSelecting && client.state != StateRequesting { if !clientExists {
err = errors.New("request for non existing client?")
} else if dfrm.XID() != client.xid {
err = errors.New("unexpected XID for client")
} else if client.state != StateSelecting && client.state != StateRequesting {
err = errors.New("DHCP request unexpected state") err = errors.New("DHCP request unexpected state")
}
if err != nil {
break break
} }
client.state = StateRequesting client.state = StateRequesting
@@ -151,16 +157,15 @@ func (sv *Server) Demux(carrierData []byte, frameOffset int) error {
} }
sv.hosts[clientIDRaw] = client sv.hosts[clientIDRaw] = client
return nil return nil
// n := copy(dfrm.OptionsPayload(), optBuf)
} }
func (sv *Server) Encapsulate(carrierData []byte, frameOffset int) (int, error) { func (sv *Server) Encapsulate(carrierData []byte, frameOffset int) (int, error) {
carrierIsIP := frameOffset >= 28 carrierIsIP := frameOffset >= 28
dfrm, err := NewFrame(carrierData[frameOffset:]) dfrm, err := NewFrame(carrierData[frameOffset:])
optBuf := dfrm.OptionsPayload()[:0] optBuf := dfrm.OptionsPayload()[:]
if err != nil { if err != nil {
return 0, err return 0, err
} else if cap(optBuf) < 255 { } else if len(optBuf) < 255 {
return 0, errOptionNotFit return 0, errOptionNotFit
} }
if sv.pending == 0 { if sv.pending == 0 {
@@ -181,15 +186,27 @@ func (sv *Server) Encapsulate(carrierData []byte, frameOffset int) (int, error)
return 0, nil // Nothing to do. return 0, nil // Nothing to do.
} }
futureState := ClientState(0) futureState := ClientState(0)
var nopt int
switch client.state { switch client.state {
case StateInit: case StateInit:
futureState = StateSelecting futureState = StateSelecting
optBuf = AppendOption(optBuf, OptMessageType, byte(MsgOffer)) nopt, err = EncodeOption(optBuf[nopt:], OptMessageType, byte(MsgOffer))
case StateRequesting: case StateRequesting:
futureState = StateBound futureState = StateBound
optBuf = AppendOption(optBuf, OptMessageType, byte(MsgAck)) nopt, err = EncodeOption(optBuf[nopt:], OptMessageType, byte(MsgAck))
*dfrm.CIAddr() = client.addr *dfrm.CIAddr() = client.addr
} }
if err != nil {
return 0, err
}
n, _ := EncodeOption(optBuf[nopt:], OptServerIdentification, sv.siaddr[:]...)
nopt += n
if sv.gwaddr != [4]byte{} {
n, _ = EncodeOption(optBuf[nopt:], OptRouter, sv.gwaddr[:]...)
nopt += n
}
optBuf[nopt] = byte(OptEnd)
nopt++
dfrm.ClearHeader() dfrm.ClearHeader()
dfrm.SetOp(OpReply) dfrm.SetOp(OpReply)
@@ -208,12 +225,13 @@ func (sv *Server) Encapsulate(carrierData []byte, frameOffset int) (int, error)
return 0, err return 0, err
} }
} }
client.state = futureState client.state = futureState
// Set server state. // Set server state.
sv.hosts[clientID] = client sv.hosts[clientID] = client
sv.pending-- sv.pending--
return optionsOffset + len(optBuf), nil return OptionsOffset + nopt, nil
} }
func (sv *Server) getClient(clientID [36]byte) (serverEntry, bool) { func (sv *Server) getClient(clientID [36]byte) (serverEntry, bool) {
+27 -17
View File
@@ -6,6 +6,7 @@ import (
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"log/slog"
"net" "net"
"net/netip" "net/netip"
"os" "os"
@@ -89,7 +90,7 @@ func run() (err error) {
} }
fmt.Println("NIC hardware address:", net.HardwareAddr(nicHW[:]).String(), "bridgeHW:", net.HardwareAddr(brHW[:]).String(), "mtu:", mtu, "addr:", nicAddr.String()) fmt.Println("NIC hardware address:", net.HardwareAddr(nicHW[:]).String(), "bridgeHW:", net.HardwareAddr(brHW[:]).String(), "mtu:", mtu, "addr:", nicAddr.String())
var stack Stack var stack Stack
err = stack.Reset(brHW, nicAddr.Addr().Next(), uint16(mtu)) err = stack.Reset(brHW, netip.AddrFrom4([4]byte{}), uint16(mtu))
if err != nil { if err != nil {
return err return err
} }
@@ -97,9 +98,7 @@ func run() (err error) {
if err != nil { if err != nil {
return err return err
} }
var shark pcap.PacketBreakdown
buf := make([]byte, mtu) buf := make([]byte, mtu)
var iframes []pcap.Frame
lastAction := time.Now() lastAction := time.Now()
dnsOngoing := false dnsOngoing := false
for { for {
@@ -126,12 +125,6 @@ func run() (err error) {
if err != nil { if err != nil {
fmt.Println("ERR:ENCAPSULATE", err) fmt.Println("ERR:ENCAPSULATE", err)
} else if nwrite > 0 { } else if nwrite > 0 {
iframes, err = shark.CaptureEthernet(iframes[:0], buf[:nwrite], 0)
if err != nil {
fmt.Println("OU", iframes, err.Error())
} else {
fmt.Println("OU", iframes)
}
n, err := iface.Write(buf[:nwrite]) n, err := iface.Write(buf[:nwrite])
if err != nil { if err != nil {
return err return err
@@ -145,12 +138,6 @@ func run() (err error) {
if err != nil { if err != nil {
return err return err
} else if nread > 0 { } else if nread > 0 {
iframes, err = shark.CaptureEthernet(iframes[:0], buf[:nread], 0)
if err != nil {
fmt.Println("IN", iframes, err.Error())
} else {
fmt.Println("IN", iframes)
}
err = stack.Demux(buf[:nread], 0) err = stack.Demux(buf[:nread], 0)
if err != nil { if err != nil {
fmt.Println("ERR:DEMUX", err) fmt.Println("ERR:DEMUX", err)
@@ -174,14 +161,36 @@ type Stack struct {
dhcp dhcpv4.Client dhcp dhcpv4.Client
dns dns.Client dns dns.Client
lookup dns.Message lookup dns.Message
// Packet capture and top level filtering.
shark pcap.PacketBreakdown
aux []pcap.Frame
} }
func (s *Stack) Demux(b []byte, _ int) error { func (s *Stack) Demux(b []byte, _ int) (err error) {
s.aux, err = s.shark.CaptureEthernet(s.aux[:0], b, 0)
if s.aux[len(s.aux)-1].Protocol != "DHCPv4" {
return nil
}
if err != nil {
fmt.Println("IN", s.aux, err.Error())
} else {
fmt.Println("IN", s.aux)
}
return s.link.Demux(b, 0) return s.link.Demux(b, 0)
} }
func (s *Stack) Encapsulate(b []byte, _ int) (int, error) { func (s *Stack) Encapsulate(b []byte, _ int) (int, error) {
return s.link.Encapsulate(b, 0) n, err := s.link.Encapsulate(b, 0)
if n > 0 {
iframes, errpcap := s.shark.CaptureEthernet(s.aux[:0], b[:n], 0)
if errpcap != nil {
fmt.Println("OU", iframes, errpcap.Error())
} else {
fmt.Println("OU", iframes)
}
}
return n, err
} }
func (s *Stack) Reset(mac [6]byte, addr netip.Addr, mtu uint16) error { func (s *Stack) Reset(mac [6]byte, addr netip.Addr, mtu uint16) error {
@@ -227,6 +236,7 @@ func (s *Stack) Reset(mac [6]byte, addr netip.Addr, mtu uint16) error {
if err != nil { if err != nil {
return err return err
} }
s.ip.SetLogger(slog.Default())
return nil return nil
} }
+126 -2
View File
@@ -9,6 +9,7 @@ import (
"github.com/soypat/lneto" "github.com/soypat/lneto"
"github.com/soypat/lneto/arp" "github.com/soypat/lneto/arp"
"github.com/soypat/lneto/dhcpv4"
"github.com/soypat/lneto/ethernet" "github.com/soypat/lneto/ethernet"
"github.com/soypat/lneto/http/httpraw" "github.com/soypat/lneto/http/httpraw"
"github.com/soypat/lneto/ipv4" "github.com/soypat/lneto/ipv4"
@@ -238,7 +239,6 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
} }
} }
return pc.captureIPProto(proto, dst, pkt, end, protoErrs...) return pc.captureIPProto(proto, dst, pkt, end, protoErrs...)
} }
func (pc *PacketBreakdown) captureIPProto(proto lneto.IPProto, dst []Frame, pkt []byte, bitOffset int, ipProtoErrs ...error) (_ []Frame, err error) { func (pc *PacketBreakdown) captureIPProto(proto lneto.IPProto, dst []Frame, pkt []byte, bitOffset int, ipProtoErrs ...error) (_ []Frame, err error) {
@@ -321,7 +321,49 @@ func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([
finfo.Fields = append(finfo.Fields, baseUDPFields[:]...) finfo.Fields = append(finfo.Fields, baseUDPFields[:]...)
dst = append(dst, finfo) dst = append(dst, finfo)
end := bitOffset + 8*octet end := bitOffset + 8*octet
dst = append(dst, remainingFrameInfo(unknownPayloadProto, FieldClassPayload, end, octet*len(pkt))) payload := ufrm.Payload()
if dhcpv4.PayloadIsDHCPv4(payload) {
return pc.CaptureDHCPv4(dst, pkt, end)
} else {
dst = append(dst, remainingFrameInfo(unknownPayloadProto, FieldClassPayload, end, octet*len(pkt)))
}
return dst, nil
}
func (pc *PacketBreakdown) CaptureDHCPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
if bitOffset%8 != 0 {
return nil, errors.New("DHCP must be parsed at byte boundary")
}
dhcpData := pkt[bitOffset/8:]
dfrm, err := dhcpv4.NewFrame(dhcpData)
if err != nil {
return nil, err
}
finfo := Frame{
Protocol: "DHCPv4",
PacketBitOffset: bitOffset,
}
magic := dfrm.MagicCookie()
if magic != dhcpv4.MagicCookie {
finfo.Errors = append(finfo.Errors, errors.New("incorrect DHCPv4 magic cookie"))
}
finfo.Fields = append(finfo.Fields, baseDHCPv4Fields[:]...)
options := dfrm.OptionsPayload()
if len(options) > 0 {
err = dfrm.ForEachOption(func(optoff int, op dhcpv4.OptNum, data []byte) error {
finfo.Fields = append(finfo.Fields, FrameField{
Name: op.String(),
Class: FieldClassOptions,
FrameBitOffset: optoff * octet,
BitLength: (2 + len(data)) * octet,
})
return nil
})
if err != nil {
finfo.Errors = append(finfo.Errors, err)
}
}
dst = append(dst, finfo)
return dst, nil return dst, nil
} }
@@ -547,6 +589,7 @@ const (
FieldClassOptions // options FieldClassOptions // options
FieldClassPayload // payload FieldClassPayload // payload
FieldClassText // text FieldClassText // text
FieldClassAddress // address
) )
const octet = 8 const octet = 8
@@ -799,6 +842,87 @@ var baseUDPFields = [...]FrameField{
}, },
} }
var baseDHCPv4Fields = [...]FrameField{
{
Name: "Opcode",
Class: FieldClassType,
FrameBitOffset: 0,
BitLength: 1 * octet,
},
{
Name: "Hardware Address Type",
Class: FieldClassProto,
FrameBitOffset: 1 * octet,
BitLength: 1 * octet,
},
{
Name: "Hardware Address Length",
Class: FieldClassSize,
FrameBitOffset: 2 * octet,
BitLength: 1 * octet,
},
{
Name: "Hops",
Class: fieldClassUndefined,
FrameBitOffset: 3 * octet,
BitLength: 1 * octet,
},
{
Name: "Transaction ID",
Class: FieldClassID,
FrameBitOffset: 4 * octet,
BitLength: 4 * octet,
},
{
Name: "Start Time",
Class: fieldClassUndefined,
FrameBitOffset: 8 * octet,
BitLength: 2 * octet,
},
{
Name: "Flags",
Class: FieldClassFlags,
FrameBitOffset: 10 * octet,
BitLength: 2 * octet,
},
{
Name: "Client Address",
Class: FieldClassAddress,
FrameBitOffset: 12 * octet,
BitLength: 4 * octet,
},
{
Name: "Offered Address",
Class: FieldClassAddress,
FrameBitOffset: 16 * octet,
BitLength: 4 * octet,
},
{
Name: "Server Next Address",
Class: FieldClassAddress,
FrameBitOffset: 20 * octet,
BitLength: 4 * octet,
},
{
Name: "Relay Agent Address",
Class: FieldClassAddress,
FrameBitOffset: 24 * octet,
BitLength: 4 * octet,
},
{
Name: "Client Hardware Address",
Class: FieldClassAddress,
FrameBitOffset: 28 * octet,
BitLength: 16 * octet,
},
{
Name: "BOOTP",
Class: FieldClassAddress,
FrameBitOffset: (28 + 16) * octet,
BitLength: (dhcpv4.OptionsOffset - (28 + 16)) * octet,
},
}
func remainingFrameInfo(proto any, class FieldClass, pktBitOffset, pktBitLen int) Frame { func remainingFrameInfo(proto any, class FieldClass, pktBitOffset, pktBitLen int) Frame {
return Frame{ return Frame{
Protocol: proto, Protocol: proto,
+1 -1
View File
@@ -100,7 +100,7 @@ func (ls *StackEthernet) Demux(carrierData []byte, frameOffset int) (err error)
} }
DROP: DROP:
ls.info("LinkStack:drop-packet", slog.String("dsthw", net.HardwareAddr(dstaddr[:]).String()), slog.String("ethertype", efrm.EtherTypeOrSize().String())) ls.info("LinkStack:drop-packet", slog.String("dsthw", net.HardwareAddr(dstaddr[:]).String()), slog.String("ethertype", efrm.EtherTypeOrSize().String()))
return nil return errors.New("drop packet")
} }
func (ls *StackEthernet) Encapsulate(carrierData []byte, frameOffset int) (n int, err error) { func (ls *StackEthernet) Encapsulate(carrierData []byte, frameOffset int) (n int, err error) {
+7 -3
View File
@@ -64,6 +64,10 @@ func (sb *StackIP) Addr() netip.Addr {
return netip.AddrFrom4(sb.ip) return netip.AddrFrom4(sb.ip)
} }
func (sb *StackIP) SetLogger(logger *slog.Logger) {
sb.logger.log = logger
}
func (sb *StackIP) Demux(carrierData []byte, offset int) error { func (sb *StackIP) Demux(carrierData []byte, offset int) error {
sb.info("StackIP.Demux:start") sb.info("StackIP.Demux:start")
frame := carrierData[offset:] // we don't care about carrier data in IP. frame := carrierData[offset:] // we don't care about carrier data in IP.
@@ -72,8 +76,8 @@ func (sb *StackIP) Demux(carrierData []byte, offset int) error {
return err return err
} }
dst := ifrm.DestinationAddr() dst := ifrm.DestinationAddr()
if *dst != sb.ip { if sb.ip != ([4]byte{}) && *dst != sb.ip {
return nil // Not meant for us. return errors.New("not meant for us") // Not meant for us.
} }
sb.validator.ResetErr() sb.validator.ResetErr()
@@ -155,7 +159,7 @@ func (sb *StackIP) Encapsulate(carrierData []byte, frameOffset int) (int, error)
println("NODE REMOVED", proto.String(), h.port) println("NODE REMOVED", proto.String(), h.port)
h.destroy() h.destroy()
} }
sb.error("StackIP:handle", slog.String("proto", proto.String()), slog.String("err", err.Error())) sb.error("StackIP:encapsulate", slog.String("proto", proto.String()), slog.String("err", err.Error()))
continue continue
} else if n == 0 { } else if n == 0 {
continue continue
+1 -1
View File
@@ -74,7 +74,7 @@ func (sudp *StackUDPPort) Encapsulate(carrierData []byte, frameOffset int) (int,
n, err := sudp.h.encapsulate(carrierData, frameOffset+8) n, err := sudp.h.encapsulate(carrierData, frameOffset+8)
if n == 0 { if n == 0 {
if err != nil { if err != nil {
slog.Error("stackudp:demux", slog.String("err", err.Error())) slog.Error("stackudp:encapsulate", slog.String("err", err.Error()))
} }
return 0, err return 0, err
} }
+1 -1
View File
@@ -46,7 +46,7 @@ func expectExchange(t *testing.T, from, to *StackIP, buf []byte) {
t.Helper() t.Helper()
n, err := from.Encapsulate(buf, 0) n, err := from.Encapsulate(buf, 0)
if err != nil { if err != nil {
t.Error("expectExchange:Handle:", err) t.Error("expectExchange:encapsulate:", err)
} else if n == 0 { } else if n == 0 {
t.Error("expected data exchange") t.Error("expected data exchange")
return return