Skip to content

Commit

Permalink
continue trying to add DHCP
Browse files Browse the repository at this point in the history
  • Loading branch information
soypat committed Sep 14, 2023
1 parent 86deee4 commit f298641
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 70 deletions.
143 changes: 100 additions & 43 deletions cmd/cyrw/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"encoding/binary"
"encoding/hex"
"errors"
"time"
Expand Down Expand Up @@ -49,15 +50,8 @@ func main() {
MAC: nil,
MaxUDPConns: 2,
})
// Prepare DHCP handler
err = stack.OpenUDP(68, func(u *tcpctl.UDPPacket, b []byte) (int, error) {
println("UDP payload:", hex.Dump(u.Payload()))
return 0, nil // TODO
})
if err != nil {
panic(err.Error())
}
dev.RecvEthHandle(stack.RecvEth)
err = DoDHCP(stack, dev)

println("finished init OK")

Expand All @@ -76,45 +70,108 @@ func main() {
}

var (
stack *tcpctl.Stack
errNotTCP = errors.New("packet not TCP")
errNotIPv4 = errors.New("packet not IPv4")
errPacketSmol = errors.New("packet too small")
stack *tcpctl.Stack
txbuf [1500]byte
)

func rcv(pkt []byte) error {
// Note: rcv is called from a locked Device context.
// No calls to device I/O should be performed here.
lastRx = time.Now()
if len(pkt) < 14 {
return errPacketSmol
}
ethHdr := eth.DecodeEthernetHeader(pkt)
if ethHdr.AssertType() != eth.EtherTypeIPv4 {
return errNotIPv4
func DoDHCP(s *tcpctl.Stack, dev *cyrw.Device) error {
// States
const (
none = iota
discover
offer
request
ack
)
state := none
err := s.OpenUDP(68, func(u *tcpctl.UDPPacket, b []byte) (int, error) {
println("UDP payload:", hex.Dump(u.Payload()))
return 0, nil
})
if err != nil {
return err
}
ipHdr := eth.DecodeIPv4Header(pkt[eth.SizeEthernetHeaderNoVLAN:])
println("ETH:", ethHdr.String())
println("IPv4:", ipHdr.String())
println("Rx:", len(pkt))
println(hex.Dump(pkt))
if ipHdr.Protocol == 17 {
// We got an UDP packet and we validate it.
udpHdr := eth.DecodeUDPHeader(pkt[eth.SizeEthernetHeaderNoVLAN+eth.SizeIPv4Header:])
gotChecksum := udpHdr.CalculateChecksumIPv4(&ipHdr, pkt[eth.SizeEthernetHeaderNoVLAN+eth.SizeIPv4Header+eth.SizeUDPHeader:])
println("UDP:", udpHdr.String())
if gotChecksum == 0 || gotChecksum == udpHdr.Checksum {
println("checksum match!")
} else {
println("checksum mismatch! Received ", udpHdr.Checksum, " but calculated ", gotChecksum)
}
return nil
var ehdr eth.EthernetHeader
var ihdr eth.IPv4Header
var uhdr eth.UDPHeader

copy(ehdr.Destination[:], eth.BroadcastHW())
copy(ehdr.Source[:], dev.MAC())
ehdr.SizeOrEtherType = uint16(eth.EtherTypeIPv4)

copy(ihdr.Destination[:], eth.BroadcastHW())
ihdr.Protocol = 17
ihdr.TTL = 2
ihdr.TotalLength = eth.SizeUDPHeader + 11*4 + 192 + 21
ihdr.ID = 12345
ihdr.VersionAndIHL = 5 // Sets IHL: No IP options. Version set automatically.

uhdr.DestinationPort = 67
uhdr.SourcePort = 68
uhdr.Length = ihdr.TotalLength - eth.SizeIPv4Header
ehdr.Put(txbuf[:])
ihdr.Put(txbuf[eth.SizeEthernetHeader:])
uhdr.Put(txbuf[eth.SizeEthernetHeader+4*ihdr.IHL():])
dhcppayload := txbuf[eth.SizeEthernetHeader+4*ihdr.IHL()+eth.SizeUDPHeader:]

dev.SendEth(txbuf[:])
for retry := 0; retry < 20 && state == none; retry++ {
time.Sleep(50 * time.Millisecond)
// We should see received packets received on callback passed into OpenUDP.
}
if ipHdr.Protocol != 6 {
return errNotTCP
if state == 0 {
return errors.New("DoDHCP failed")
}
tcpHdr := eth.DecodeTCPHeader(pkt[eth.SizeEthernetHeaderNoVLAN+eth.SizeIPv4Header:])
println("TCP:", tcpHdr.String())

return nil
}

// DHCPHeader specifies the first 44 bytes of a DHCP packet payload
// not including BOOTP, magic cookie and options.
type DHCPHeader struct {
OP byte // 0:1
HType byte // 1:2
HLen byte // 2:3
HOps byte // 3:4
Xid uint32 // 4:8
Secs uint16 // 8:10
Flags uint16 // 10:12
CIAddr [4]byte // 12:16
YIAddr [4]byte // 16:20
SIAddr [4]byte // 20:24
GIAddr [4]byte // 24:28
CHAddr [4 * 4]byte // 28:44
// BOOTP, Magic Cookie, and DHCP Options not included.
}

func (d *DHCPHeader) Put(dst []byte) {
_ = dst[43]
dst[0] = d.OP
dst[1] = d.HType
dst[2] = d.HLen
dst[3] = d.HOps
binary.BigEndian.PutUint32(dst[4:8], d.Xid)
binary.BigEndian.PutUint16(dst[8:10], d.Secs)
binary.BigEndian.PutUint16(dst[10:12], d.Flags)
copy(dst[12:16], d.CIAddr[:])
copy(dst[16:20], d.YIAddr[:])
copy(dst[20:24], d.SIAddr[:])
copy(dst[24:28], d.GIAddr[:])
copy(dst[28:44], d.CHAddr[:])
}

func DecodeDHCPHeader(src []byte) (d DHCPHeader) {
_ = src[43]
d.OP = src[0]
d.HType = src[1]
d.HLen = src[2]
d.HOps = src[3]
d.Xid = binary.BigEndian.Uint32(src[4:8])
d.Secs = binary.BigEndian.Uint16(src[8:10])
d.Flags = binary.BigEndian.Uint16(src[10:12])
copy(d.CIAddr[:], src[12:16])
copy(d.YIAddr[:], src[16:20])
copy(d.SIAddr[:], src[20:24])
copy(d.GIAddr[:], src[24:28])
copy(d.CHAddr[:], src[28:44])
return d
}
8 changes: 4 additions & 4 deletions cmd/cyweth/cyweth.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ func rx(pkt []byte) error {
if ethHdr.AssertType() != eth.EtherTypeIPv4 {
return errNotIPv4
}
ipHdr := eth.DecodeIPv4Header(pkt[eth.SizeEthernetHeaderNoVLAN:])
ipHdr := eth.DecodeIPv4Header(pkt[eth.SizeEthernetHeader:])
println("ETH:", ethHdr.String())
println("IPv4:", ipHdr.String())
println("Rx:", len(pkt))
println(hex.Dump(pkt))
if ipHdr.Protocol == 17 {
// We got an UDP packet and we validate it.
udpHdr := eth.DecodeUDPHeader(pkt[eth.SizeEthernetHeaderNoVLAN+eth.SizeIPv4Header:])
gotChecksum := udpHdr.CalculateChecksumIPv4(&ipHdr, pkt[eth.SizeEthernetHeaderNoVLAN+eth.SizeIPv4Header+eth.SizeUDPHeader:])
udpHdr := eth.DecodeUDPHeader(pkt[eth.SizeEthernetHeader+eth.SizeIPv4Header:])
gotChecksum := udpHdr.CalculateChecksumIPv4(&ipHdr, pkt[eth.SizeEthernetHeader+eth.SizeIPv4Header+eth.SizeUDPHeader:])
println("UDP:", udpHdr.String())
if gotChecksum == 0 || gotChecksum == udpHdr.Checksum {
println("checksum match!")
Expand All @@ -41,7 +41,7 @@ func rx(pkt []byte) error {
if ipHdr.Protocol != 6 {
return errNotTCP
}
tcpHdr := eth.DecodeTCPHeader(pkt[eth.SizeEthernetHeaderNoVLAN+eth.SizeIPv4Header:])
tcpHdr := eth.DecodeTCPHeader(pkt[eth.SizeEthernetHeader+eth.SizeIPv4Header:])
println("TCP:", tcpHdr.String())

return nil
Expand Down
37 changes: 22 additions & 15 deletions internal/tcpctl/eth/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ type ARPv4Header struct {
type IPv4Header struct {
// VersionAndIHL contains union of both IP Version and IHL data.
//
// Version must be 4 for IPv4.
// Version must be 4 for IPv4. It is force-set to its valid value in a call to Put.
//
// Internet Header Length (IHL) The IPv4 header is variable in size due to the
// optional 14th field (options). The IHL field contains the size of the IPv4 header;
Expand Down Expand Up @@ -123,9 +123,10 @@ type IPv4Header struct {
Flags IPFlags // 6:8

// An eight-bit time to live field limits a datagram's lifetime to prevent
// network failure in the event of a routing loop. When the datagram arrives
// at a router, the router decrements the TTL field by one. It is specified
// in seconds, but time intervals less than 1 second are rounded up to 1.
// network failure in the event of a routing loop. In practice, the field
// is used as a hop count—when the datagram arrives at a router,
// the router decrements the TTL field by one. When the TTL field hits zero,
// the router discards the packet and typically sends an ICMP time exceeded message to the sender.
TTL uint8 // 8:9

// This field defines the protocol used in the data portion of the IP datagram. TCP is 6, UDP is 17.
Expand All @@ -151,8 +152,11 @@ type TCPHeader struct {
type UDPHeader struct {
SourcePort uint16 // 0:2
DestinationPort uint16 // 2:4
Length uint16 // 4:6
Checksum uint16 // 6:8
// Length specifies length in bytes of UDP header and UDP payload. The minimum length
// is 8 bytes (UDP header length). This field should match the result of the IP header
// TotalLength field minus the IP header size: udp.Length == ip.TotalLength - 4*ip.IHL
Length uint16 // 4:6
Checksum uint16 // 6:8
}

// There are 9 flags, bits 100 thru 103 are reserved
Expand All @@ -174,16 +178,19 @@ const (
FlagTCP_NS
)

// These are minimum sizes that do not take into consideration the presence of
// options or special tags (i.e: VLAN, IP/TCP Options).
const (
SizeEthernetHeaderNoVLAN = 14
SizeIPv4Header = 20
SizeUDPHeader = 8
SizeARPv4Header = 28
SizeTCPHeaderNoOptions = 20
ipflagDontFrag = 0x4000
ipFlagMoreFrag = 0x8000
ipVersion4 = 0x45
ipProtocolTCP = 6
SizeEthernetHeader = 14
SizeIPv4Header = 20
SizeUDPHeader = 8
SizeARPv4Header = 28
SizeTCPHeaderNoOptions = 20
ipflagDontFrag = 0x4000
ipFlagMoreFrag = 0x8000
ipVersion4 = 0x45
ipProtocolTCP = 6
ipProtocolUDP = 17
)

func IsBroadcastHW(hwaddr net.HardwareAddr) bool {
Expand Down
10 changes: 5 additions & 5 deletions internal/tcpctl/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ type UDPPacket struct {
Eth eth.EthernetHeader
IP eth.IPv4Header
UDP eth.UDPHeader
payload [_MTU - eth.SizeEthernetHeaderNoVLAN - eth.SizeIPv4Header - eth.SizeUDPHeader]byte
payload [_MTU - eth.SizeEthernetHeader - eth.SizeIPv4Header - eth.SizeUDPHeader]byte
}

// Payload returns the UDP payload. If UDP or IPv4 header data is incorrect/bad it returns nil.
Expand Down Expand Up @@ -173,7 +173,7 @@ func (s *Stack) RecvEth(payload []byte) (err error) {
s.error("Stack.RecvEth", slog.String("err", err.Error()), slog.Any("IP", ihdr))
}
}()
if len(payload) < eth.SizeEthernetHeaderNoVLAN+eth.SizeIPv4Header {
if len(payload) < eth.SizeEthernetHeader+eth.SizeIPv4Header {
return errPacketSmol
}
s.info("Stack.RecvEth:start", slog.Int("plen", len(payload)))
Expand All @@ -189,7 +189,7 @@ func (s *Stack) RecvEth(payload []byte) (err error) {
}

// IP parsing block.
ihdr = eth.DecodeIPv4Header(payload[eth.SizeEthernetHeaderNoVLAN:])
ihdr = eth.DecodeIPv4Header(payload[eth.SizeEthernetHeader:])
if ihdr.ToS != 0 {
return errors.New("ToS not supported")
} else if ihdr.Version() != 4 {
Expand All @@ -201,8 +201,8 @@ func (s *Stack) RecvEth(payload []byte) (err error) {
}

// Handle UDP/TCP packets.
offset := eth.SizeEthernetHeaderNoVLAN + 4*ihdr.IHL() // Can be at most 14+60=74, so no overflow risk.
end := eth.SizeEthernetHeaderNoVLAN + ihdr.TotalLength
offset := eth.SizeEthernetHeader + 4*ihdr.IHL() // Can be at most 14+60=74, so no overflow risk.
end := eth.SizeEthernetHeader + ihdr.TotalLength
if len(payload) < int(end) || end < uint16(offset) {
return errors.New("short payload buffer or bad IP TotalLength")
} else if end > _MTU {
Expand Down
6 changes: 3 additions & 3 deletions internal/tcpctl/tcpctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (s *TCPSocket) RecvEthernet(buf []byte) (payloadStart, payloadEnd uint16, e
switch {
case len(buf) > math.MaxUint16:
err = errors.New("buffer too long")
case buflen < eth.SizeEthernetHeaderNoVLAN+eth.SizeIPv4Header+eth.SizeTCPHeaderNoOptions:
case buflen < eth.SizeEthernetHeader+eth.SizeIPv4Header+eth.SizeTCPHeaderNoOptions:
err = errors.New("buffer too short to contain TCP")

}
Expand All @@ -81,11 +81,11 @@ func (s *TCPSocket) RecvEthernet(buf []byte) (payloadStart, payloadEnd uint16, e
if ethhdr.SizeOrEtherType != uint16(eth.EtherTypeIPv4) {
return 0, 0, errors.New("support only IPv4")
}
payloadStart, payloadEnd, err = s.RecvTCP(buf[eth.SizeEthernetHeaderNoVLAN:])
payloadStart, payloadEnd, err = s.RecvTCP(buf[eth.SizeEthernetHeader:])
if err != nil {
return 0, 0, err
}
return payloadStart + eth.SizeEthernetHeaderNoVLAN, payloadEnd + eth.SizeEthernetHeaderNoVLAN, nil
return payloadStart + eth.SizeEthernetHeader, payloadEnd + eth.SizeEthernetHeader, nil
}

func (s *TCPSocket) RecvTCP(buf []byte) (payloadStart, payloadEnd uint16, err error) {
Expand Down

0 comments on commit f298641

Please sign in to comment.