dhcp working!

This commit is contained in:
soypat
2025-07-09 16:54:48 -03:00
parent 9c0e250cf6
commit 489e9951fa
10 changed files with 228 additions and 69 deletions
+25 -22
View File
@@ -19,6 +19,7 @@ type Client struct {
hostname []byte
dns [][4]byte
svIPtos ipv4.ToS
tRenew uint32
tRebind uint32
tIPLease uint32
@@ -73,26 +74,16 @@ func (c *Client) setIP(b []byte, frameOffset int) {
ifrm, _ := ipv4.NewFrame(b)
ifrm.SetID((uint16(c.currentXID) ^ uint16(c.currentXID>>16)) + uint16(c.state))
if c.state > StateInit {
// TODO(soypat): Document why disabling ToS used by DHCP server may cause Request to fail.
// Apparently server sets ToS=192. Uncommenting this line causes DHCP to fail on my setup.
// 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)
// Match server ToS since some routers drop DHCP requests if no ToS set apparently?
ifrm.SetToS(c.svIPtos)
}
src := ifrm.SourceAddr()
for i := range src {
src[i] = 0
}
dst := ifrm.DestinationAddr()[:]
if c.svip == ([4]byte{}) {
for i := range dst {
dst[i] = 255
}
} else {
copy(dst, c.svip[:])
for i := range dst {
dst[i] = 255
}
}
@@ -103,6 +94,8 @@ func (c *Client) Encapsulate(carrierFrame []byte, frameOffset int) (int, error)
return 0, nil // No offer received yet.
} else if c.state == StateBound {
return 0, nil // Done!
} else if c.state == StateRequesting {
return 0, nil // Currently awaiting ACK.
}
dst := carrierFrame[frameOffset:]
frm, err := NewFrame(dst)
@@ -149,7 +142,7 @@ func (c *Client) Encapsulate(carrierFrame []byte, frameOffset int) (int, error)
nextState = StateRequesting
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
@@ -166,10 +159,11 @@ func (c *Client) Encapsulate(carrierFrame []byte, frameOffset int) (int, error)
c.setHeader(frm)
c.setIP(carrierFrame, frameOffset)
c.state = nextState
return optionsOffset + numOpts, nil
return OptionsOffset + numOpts, nil
}
func (c *Client) Demux(carrierData []byte, frameOffset int) error {
fmt.Println("DEMUX DHCP")
if c.isClosed() {
return net.ErrClosed
}
@@ -212,13 +206,17 @@ func (c *Client) Demux(carrierData []byte, frameOffset int) error {
default:
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
}
func (c *Client) getMessageType(frm Frame) MessageType {
c.auxbuf[0] = 255
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 {
*ptrMsgType = data[0]
return io.EOF
@@ -229,7 +227,7 @@ func (c *Client) getMessageType(frm Frame) MessageType {
}
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 {
case OptRenewTimeValue:
c.tRenew = maybeU32(data)
@@ -237,7 +235,6 @@ func (c *Client) setOptions(frm Frame) error {
c.tIPLease = maybeU32(data)
case OptRebindingTimeValue:
c.tRebind = maybeU32(data)
case OptServerIdentification:
c.svip = maybe4byte(data)
case OptRouter:
@@ -261,6 +258,7 @@ func (c *Client) setOptions(frm Frame) error {
}
return nil
})
return err
}
func (c *Client) isClosed() bool { return c.state == 0 || c.currentXID == 0 }
@@ -272,16 +270,21 @@ func (c *Client) setHeader(frm Frame) {
frm.SetHardware(1, 6, 0)
frm.SetSecs(1)
if c.state.HasIP() {
copy(frm.CIAddr()[:], c.offer[:])
*frm.CIAddr() = c.offer
}
if c.state == StateInit {
siaddr := frm.SIAddr()[:]
for i := range siaddr {
siaddr[i] = 255
}
} else {
if c.siip == [4]byte{} {
*frm.SIAddr() = c.svip
} else {
*frm.SIAddr() = c.siip
}
}
copy(frm.YIAddr()[:], c.offer[:])
*frm.YIAddr() = c.offer
copy(frm.CHAddrAs6()[:], c.clientMAC[:])
frm.SetMagicCookie(MagicCookie)
}
+27 -13
View File
@@ -98,13 +98,18 @@ func TestExample(t *testing.T) {
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)
@@ -116,27 +121,36 @@ func TestExample(t *testing.T) {
copy(dfrm.CHAddr()[:], clientHwaddr[:])
dfrm.SetMagicCookie(MagicCookie)
ntot := 0
nopt, _ := EncodeOption(buf[optionsOffset+ntot:], OptMessageType, byte(MsgOffer))
nopt, _ := EncodeOption(buf[OptionsOffset+ntot:], OptMessageType, byte(MsgOffer))
ntot += nopt
nopt, _ = EncodeOption(buf[optionsOffset+ntot:], OptServerIdentification, serverIP[:]...)
nopt, _ = EncodeOption(buf[OptionsOffset+ntot:], OptServerIdentification, serverIP[:]...)
ntot += nopt
nopt, _ = EncodeOption32(buf[optionsOffset+ntot:], OptServerIdentification, offerLease)
nopt, _ = EncodeOption32(buf[OptionsOffset+ntot:], OptServerIdentification, offerLease)
ntot += nopt
nopt, _ = EncodeOption(buf[optionsOffset+ntot:], OptSubnetMask, subnetMask[:]...)
nopt, _ = EncodeOption(buf[OptionsOffset+ntot:], OptSubnetMask, subnetMask[:]...)
ntot += nopt
nopt, _ = EncodeOption(buf[optionsOffset+ntot:], OptRouter, routerAddr[:]...)
nopt, _ = EncodeOption(buf[OptionsOffset+ntot:], OptRouter, routerAddr[:]...)
ntot += nopt
nopt, _ = EncodeOption(buf[optionsOffset+ntot:], OptDNSServers, dnsAddr[:]...)
nopt, _ = EncodeOption(buf[OptionsOffset+ntot:], OptDNSServers, dnsAddr[:]...)
ntot += nopt
nopt, _ = EncodeOption(buf[optionsOffset+ntot:], OptEnd, dnsAddr[:]...)
nopt, _ = EncodeOption(buf[OptionsOffset+ntot:], OptEnd, dnsAddr[:]...)
ntot += nopt
err = cl.Demux(buf[:optionsOffset+ntot], 0)
err = cl.Demux(buf[:OptionsOffset+ntot], 0)
if err != nil {
t.Fatal(err)
}
// frame := buf[:optionsOffset]
// frame = AppendOption(frame, OptMessageType, byte(MsgOffer))
// frame = AppendOption(frame, OptServerIdentification, serverIP[:]...)
// frame = AppendOption(frame, OptIPAddressLeaseTime, serverIP[:]...)
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.
MagicCookie uint32 = 0x63825363
// DHCP Options offset measured from the start of the UDP payload.
optionsOffset = magicCookieOffset + 4
OptionsOffset = magicCookieOffset + 4
DefaultClientPort = 68
DefaultServerPort = 67
@@ -26,12 +26,16 @@ const (
// NewFrame returns a new DHCPv4 Frame with data set to buf.
// An error is returned if the buffer size is smaller than 240.
func NewFrame(buf []byte) (Frame, error) {
if len(buf) < optionsOffset {
if len(buf) < OptionsOffset {
return Frame{}, errSmallFrame
}
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
// and provides methods for manipulating, validating and
// 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.
func (frm Frame) OptionsPayload() []byte {
return frm.buf[optionsOffset:]
return frm.buf[OptionsOffset:]
}
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.
func (frm Frame) ClearHeader() {
for i := range frm.buf[:optionsOffset] {
for i := range frm.buf[:OptionsOffset] {
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.
// 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.
ptr := optionsOffset
ptr := OptionsOffset
if ptr > len(frm.buf) {
return errSmallFrame
} 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]
if callback {
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
}
}
+2 -2
View File
@@ -77,7 +77,7 @@ func (sv *Server) Demux(carrierData []byte, frameOffset int) error {
var reqlist []byte
var reqAddr []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 {
case OptMessageType:
if len(data) == 1 {
@@ -231,7 +231,7 @@ func (sv *Server) Encapsulate(carrierData []byte, frameOffset int) (int, error)
// Set server state.
sv.hosts[clientID] = client
sv.pending--
return optionsOffset + nopt, nil
return OptionsOffset + nopt, nil
}
func (sv *Server) getClient(clientID [36]byte) (serverEntry, bool) {
+27 -17
View File
@@ -6,6 +6,7 @@ import (
"errors"
"flag"
"fmt"
"log/slog"
"net"
"net/netip"
"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())
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 {
return err
}
@@ -97,9 +98,7 @@ func run() (err error) {
if err != nil {
return err
}
var shark pcap.PacketBreakdown
buf := make([]byte, mtu)
var iframes []pcap.Frame
lastAction := time.Now()
dnsOngoing := false
for {
@@ -126,12 +125,6 @@ func run() (err error) {
if err != nil {
fmt.Println("ERR:ENCAPSULATE", err)
} 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])
if err != nil {
return err
@@ -145,12 +138,6 @@ func run() (err error) {
if err != nil {
return err
} 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)
if err != nil {
fmt.Println("ERR:DEMUX", err)
@@ -174,14 +161,36 @@ type Stack struct {
dhcp dhcpv4.Client
dns dns.Client
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)
}
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 {
@@ -227,6 +236,7 @@ func (s *Stack) Reset(mac [6]byte, addr netip.Addr, mtu uint16) error {
if err != nil {
return err
}
s.ip.SetLogger(slog.Default())
return nil
}
+126 -2
View File
@@ -9,6 +9,7 @@ import (
"github.com/soypat/lneto"
"github.com/soypat/lneto/arp"
"github.com/soypat/lneto/dhcpv4"
"github.com/soypat/lneto/ethernet"
"github.com/soypat/lneto/http/httpraw"
"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...)
}
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[:]...)
dst = append(dst, finfo)
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
}
@@ -547,6 +589,7 @@ const (
FieldClassOptions // options
FieldClassPayload // payload
FieldClassText // text
FieldClassAddress // address
)
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 {
return Frame{
Protocol: proto,
+1 -1
View File
@@ -100,7 +100,7 @@ func (ls *StackEthernet) Demux(carrierData []byte, frameOffset int) (err error)
}
DROP:
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) {
+7 -3
View File
@@ -64,6 +64,10 @@ func (sb *StackIP) Addr() netip.Addr {
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 {
sb.info("StackIP.Demux:start")
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
}
dst := ifrm.DestinationAddr()
if *dst != sb.ip {
return nil // Not meant for us.
if sb.ip != ([4]byte{}) && *dst != sb.ip {
return errors.New("not meant for us") // Not meant for us.
}
sb.validator.ResetErr()
@@ -155,7 +159,7 @@ func (sb *StackIP) Encapsulate(carrierData []byte, frameOffset int) (int, error)
println("NODE REMOVED", proto.String(), h.port)
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
} else if n == 0 {
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)
if n == 0 {
if err != nil {
slog.Error("stackudp:demux", slog.String("err", err.Error()))
slog.Error("stackudp:encapsulate", slog.String("err", err.Error()))
}
return 0, err
}
+1 -1
View File
@@ -46,7 +46,7 @@ func expectExchange(t *testing.T, from, to *StackIP, buf []byte) {
t.Helper()
n, err := from.Encapsulate(buf, 0)
if err != nil {
t.Error("expectExchange:Handle:", err)
t.Error("expectExchange:encapsulate:", err)
} else if n == 0 {
t.Error("expected data exchange")
return