Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dev: UDPConn implementation by @nickaxgit #31

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion eth/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ func (ehdr EthernetHeader) AssertType() EtherType { return EtherType(ehdr.SizeOr
// Put marshals the ethernet frame onto buf. buf needs to be 14 bytes in length or Put panics.
func (ehdr *EthernetHeader) Put(buf []byte) {
_ = buf[13]

copy(buf[0:], ehdr.Destination[0:])
copy(buf[6:], ehdr.Source[0:])
binary.BigEndian.PutUint16(buf[12:14], ehdr.SizeOrEtherType)
Expand Down Expand Up @@ -256,7 +257,7 @@ func (ehdr *EthernetHeader) String() string {

// IHL returns the internet header length in 32bit words and is guaranteed to be within 0..15.
// Valid values for IHL are 5..15. When multiplied by 4 this yields number of bytes of the header, 20..60.
func (iphdr *IPv4Header) IHL() uint8 { return iphdr.VersionAndIHL & 0xf }
func (iphdr *IPv4Header) IHL() uint8 { return iphdr.VersionAndIHL & 0xf } //low four bits
func (iphdr *IPv4Header) Version() uint8 { return iphdr.VersionAndIHL >> 4 }
func (iphdr *IPv4Header) DSCP() uint8 { return iphdr.ToS >> 2 }
func (iphdr *IPv4Header) ECN() uint8 { return iphdr.ToS & 0b11 }
Expand Down
6 changes: 4 additions & 2 deletions stacks/arp.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ func (c *arpClient) pendingOutReqARPv4() bool {
return c.result.Operation == 1 // User asked for a ARP request.
}

func (c *arpClient) handle(dst []byte) (n int) {
// putOutboundEth implements [iudphandler] interface.
func (c *arpClient) putOutboundEth(dst []byte) (n int) {
pendingOutReq := c.pendingOutReqARPv4()
switch {
case pendingOutReq:
Expand Down Expand Up @@ -131,7 +132,8 @@ func (c *arpClient) handle(dst []byte) (n int) {
return n
}

func (c *arpClient) recv(ahdr *eth.ARPv4Header) error {
// recvEth implements [iudphandler] interface.
func (c *arpClient) recvEth(ahdr *eth.ARPv4Header) error {
if ahdr.HardwareLength != 6 || ahdr.ProtoLength != 4 || ahdr.HardwareType != 1 || ahdr.AssertEtherType() != eth.EtherTypeIPv4 {
return errARPUnsupported // Ignore ARP unsupported requests.
}
Expand Down
6 changes: 4 additions & 2 deletions stacks/dhcp_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@ func getDefaultParams() []byte {
return unsafe.Slice((*byte)(unsafe.Pointer(ptr)), len(dhcpDefaultParamReqList))
}

func (d *DHCPClient) send(dst []byte) (n int, err error) {
// putOutboundEth implements [iudphandler] interface.
func (d *DHCPClient) putOutboundEth(dst []byte) (n int, err error) {
if d.isAborted() {
return 0, io.EOF
} else if !d.isPendingHandling() {
Expand Down Expand Up @@ -340,7 +341,8 @@ func (d *DHCPClient) send(dst []byte) (n int, err error) {
return ptr, nil
}

func (d *DHCPClient) recv(pkt *UDPPacket) (err error) {
// recvEth implements [iudphandler] interface.
func (d *DHCPClient) recvEth(pkt *UDPPacket) (err error) {
if d.isAborted() {
return io.EOF
}
Expand Down
6 changes: 4 additions & 2 deletions stacks/dhcp_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ func (d *DHCPServer) Start() error {
return d.stack.OpenUDP(d.port, d)
}

func (d *DHCPServer) recv(pkt *UDPPacket) (err error) {
// recvEth implements [iudphandler] interface.
func (d *DHCPServer) recvEth(pkt *UDPPacket) (err error) {
if d.isAborted() {
return io.EOF // Signal to close socket.
}
Expand All @@ -57,7 +58,8 @@ func (d *DHCPServer) recv(pkt *UDPPacket) (err error) {
return nil
}

func (d *DHCPServer) send(dst []byte) (int, error) {
// putOutboundEth implements [iudphandler] interface.
func (d *DHCPServer) putOutboundEth(dst []byte) (int, error) {
if d.isAborted() {
return 0, io.EOF // Signal to close socket.
}
Expand Down
6 changes: 4 additions & 2 deletions stacks/dns_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@
return nil
}

func (dnsc *DNSClient) send(dst []byte) (n int, err error) {
// putOutboundEth implements [iudphandler] interface.
func (dnsc *DNSClient) putOutboundEth(dst []byte) (n int, err error) {
if dnsc.state == dnsAborted {
return 0, io.EOF
} else if dnsc.state != dnsSendQuery {
Expand Down Expand Up @@ -101,7 +102,8 @@
return payloadOffset + int(msgLen), nil
}

func (dnsc *DNSClient) recv(pkt *UDPPacket) error {
// recvEth implements the [iudphandler] interface.
func (dnsc *DNSClient) recvEth(pkt *UDPPacket) error {

Check warning on line 106 in stacks/dns_client.go

View check run for this annotation

Codecov / codecov/patch

stacks/dns_client.go#L106

Added line #L106 was not covered by tests
if dnsc.state == dnsAborted {
return io.EOF
} else if dnsc.state != dnsAwaitResponse {
Expand Down
6 changes: 4 additions & 2 deletions stacks/ntp_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@
return nil
}

func (nc *NTPClient) send(dst []byte) (n int, err error) {
// putOutboundEth implements [iudphandler] interface.
func (nc *NTPClient) putOutboundEth(dst []byte) (n int, err error) {

Check warning on line 68 in stacks/ntp_client.go

View check run for this annotation

Codecov / codecov/patch

stacks/ntp_client.go#L68

Added line #L68 was not covered by tests
const (
payloadoffset = eth.SizeEthernetHeader + eth.SizeIPv4Header + eth.SizeUDPHeader
ToS = 192
Expand Down Expand Up @@ -101,7 +102,8 @@
return payloadoffset + ntp.SizeHeader, nil
}

func (nc *NTPClient) recv(pkt *UDPPacket) (err error) {
// recvEth implements the [iudphandler] interface.
func (nc *NTPClient) recvEth(pkt *UDPPacket) (err error) {

Check warning on line 106 in stacks/ntp_client.go

View check run for this annotation

Codecov / codecov/patch

stacks/ntp_client.go#L106

Added line #L106 was not covered by tests
if nc.isAborted() || nc.IsDone() {
return io.EOF
}
Expand Down
27 changes: 16 additions & 11 deletions stacks/port_tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,20 @@ import (
"github.com/soypat/seqs/eth"
)

// tcphandler represents a user provided function for handling incoming TCP packets on a port.
// Incoming data is sent inside the `pkt` TCPPacket argument when pkt.HasPacket returns true.
// Outgoing data is stored into the `response` byte slice. The function must return the number of
// itcphandler represents a user provided function for handling incoming TCP packets on a port.
// Incoming data is passed in a 'pkt' to the recv function which is invoked whenever data arrives (by [PortStack.RecvEth])
// Outgoing data is written into the `dst` byte slice (from the tx ring buffer). The function must return the number of
// bytes written to `response` and an error.
//
// TCPConn provides an implemntation of this interface - note .send is ONLY called by [PortStack.PutOutboundEth]
// See [PortStack] for information on how to use this function and other port handlers.
// Note [TCPConn] is our implementation of this interface
type itcphandler interface {
send(dst []byte) (n int, err error)
recv(pkt *TCPPacket) error
// putOutboundEth is called by the underlying stack [PortStack.PutOutboundEth] method and populates
// response from the TX ring buffer, with data to be sent as a packet and returns n bytes written.
// See [PortStack] for more information.
putOutboundEth(response []byte) (n int, err error)
// recvEth called by the [PortStack.RecvEth] method when a packet is received on the network interface, pkt is (a pointer to) the arrived packet.
recvEth(pkt *TCPPacket) error
// needsHandling() bool
isPendingHandling() bool
abort()
Expand All @@ -36,22 +41,22 @@ func (port *tcpPort) IsPendingHandling() bool {
return port.port != 0 && port.handler.isPendingHandling()
}

// HandleEth writes the socket's response into dst to be sent over an ethernet interface.
// HandleEth can return 0 bytes written and a nil error to indicate no action must be taken.
func (port *tcpPort) HandleEth(dst []byte) (n int, err error) {
// PutOutboundEth writes the socket's response into dst to be sent over an ethernet interface.
// PutOutboundEth can return 0 bytes written and a nil error to indicate no action must be taken.
func (port *tcpPort) PutOutboundEth(dst []byte) (n int, err error) {
if port.handler == nil {
panic("nil tcp handler on port " + strconv.Itoa(int(port.port)))
}

n, err = port.handler.send(dst)
n, err = port.handler.putOutboundEth(dst)
port.p = false
if err == ErrFlagPending {
port.p = true
}
return n, err
}

// Open sets the UDP handler and opens the port.
// Open sets the TCP handler and opens the port.
func (port *tcpPort) Open(portNum uint16, handler itcphandler) {
if portNum == 0 || handler == nil {
panic("invalid port or nil handler" + strconv.Itoa(int(port.port)))
Expand Down
55 changes: 42 additions & 13 deletions stacks/port_udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,49 +8,55 @@
)

type iudphandler interface {
send(dst []byte) (n int, err error)
recv(pkt *UDPPacket) error
// putOutboundEth is called by the underlying stack [PortStack.PutOutboundEth] method and populates
// response from the TX ring buffer, with data to be sent as a packet and returns n bytes written.
// See [PortStack] for more information.
putOutboundEth(response []byte) (n int, err error)
recvEth(pkt *UDPPacket) error
// needsHandling() bool
isPendingHandling() bool
abort()
}

type udpPort struct {
ihandler iudphandler
port uint16
handler iudphandler
port uint16
}

func (port udpPort) Port() uint16 { return port.port }

// IsPendingHandling returns true if there are packet(s) pending handling.
func (port *udpPort) IsPendingHandling() bool {
// return port.port != 0 && port.ihandler.isPendingHandling()
return port.port != 0 && port.ihandler.isPendingHandling()
return port.port != 0 && port.handler.isPendingHandling()
}

// HandleEth writes the socket's response into dst to be sent over an ethernet interface.
// HandleEth can return 0 bytes written and a nil error to indicate no action must be taken.
func (port *udpPort) HandleEth(dst []byte) (int, error) {
if port.ihandler == nil {
// PutOutboundEth writes the socket's response into dst to be sent over an ethernet interface.
// PutOutboundEth can return 0 bytes written and a nil error to indicate no action must be taken.
func (port *udpPort) PutOutboundEth(dst []byte) (int, error) {

if port.handler == nil {
panic("nil udp handler on port " + strconv.Itoa(int(port.port)))
}
return port.ihandler.send(dst)

return port.handler.putOutboundEth(dst)
}

// Open sets the UDP handler and opens the port.
// This is effectively a constructor for the port NewUDPPort() - would be an alternative name
func (port *udpPort) Open(portNum uint16, h iudphandler) {
if portNum == 0 || h == nil {
panic("invalid port or nil handler" + strconv.Itoa(int(port.port)))
} else if port.port != 0 {
panic("port already open")
}
port.ihandler = h
port.handler = h
port.port = portNum
}

func (port *udpPort) Close() {
port.port = 0 // Port 0 flags the port is inactive.
port.ihandler = nil
port.handler = nil

Check warning on line 59 in stacks/port_udp.go

View check run for this annotation

Codecov / codecov/patch

stacks/port_udp.go#L59

Added line #L59 was not covered by tests
}

// UDP socket can be forced to respond even if no packet has been received
Expand All @@ -70,7 +76,7 @@
panic("short UDPPacket buffer")
}
if pkt.IP.IHL() != 5 {
panic("UDPPacket.PutHeaders expects no IP options")
panic("UDPPacket.PutHeaders expects no IP options " + strconv.Itoa(int(pkt.IP.IHL())))

Check warning on line 79 in stacks/port_udp.go

View check run for this annotation

Codecov / codecov/patch

stacks/port_udp.go#L79

Added line #L79 was not covered by tests
}
pkt.Eth.Put(b)
pkt.IP.Put(b[eth.SizeEthernetHeader:])
Expand All @@ -87,3 +93,26 @@
}
return pkt.payload[:uLen]
}

func (pkt *UDPPacket) CalculateHeaders(payload []byte) {
const ipLenInWords = 5
pkt.Eth.SizeOrEtherType = uint16(eth.EtherTypeIPv4)

// IPv4 frame.
pkt.IP.Protocol = 17 // UDP
pkt.IP.TTL = 64
pkt.IP.ID = prand16(pkt.IP.ID)
pkt.IP.VersionAndIHL = ipLenInWords // Sets IHL: No IP options. Version set automatically.
pkt.IP.TotalLength = 4*ipLenInWords + eth.SizeUDPHeader + uint16(len(payload))
// TODO(soypat): Document how to handle ToS. For now just use ToS used by other side.
pkt.IP.Flags = 0 // packet.IP.ToS = 0
pkt.IP.Checksum = pkt.IP.CalculateChecksum()

pkt.UDP = eth.UDPHeader{
SourcePort: pkt.UDP.SourcePort,
DestinationPort: pkt.UDP.DestinationPort,
Checksum: 0,
Length: uint16(len(payload) + eth.SizeUDPHeader),
}
pkt.UDP.Checksum = pkt.UDP.CalculateChecksumIPv4(&pkt.IP, payload)
}
Loading
Loading