huge tap/bridge overhaul; udp node; dhcp node; DHCP example

This commit is contained in:
soypat
2025-06-15 18:01:04 -03:00
parent 29ac9a3a1a
commit dcc7a95e62
16 changed files with 706 additions and 157 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ vendor/
**__debug_bin*
# `__debug_bin` Debug binary generated in VSCode when using the built-in debugger.
*bin
/bridge
# IDE
.vscode/
+20
View File
@@ -21,6 +21,26 @@ Userspace networking primitives.
- [`lneto/ntp`](./ntp): NTP implementation and low level logic. Includes NTP time primitives manipulation and conversion to Go native types.
- [`lneto/internal`](./internal): Lightweight and flexible ring buffer implementation and debugging primitives.
### Abstractions
The following interface is implemented by networking stack nodes and the stack themselves.
```go
type StackNode interface {
// Encapsulate receives a buffer the receiver must fill with data.
// The receiver's start byte is at carrierData[frameOffset].
Encapsulate(carrierData []byte, frameOffset int) (int, error)
// Demux receives a buffer the receiver must decode and pass on to corresponding child StackNode(s).
// The receiver's start byte is at carrierData[frameOffset].
Demux(carrierData []byte, frameOffset int) error
// LocalPort returns the port of the node if applicable or zero. Used for UDP/TCP nodes.
LocalPort() uint16
// Protocol returns the protocol of this node if applicable or zero. Usually either a ethernet.Type (EtherType) or lneto.IPProto (IP Protocol number).
Protocol() uint64
// ConnectionID returns a pointer to the connection ID of the StackNode.
// A change in the ID means the node is no longer valid and should be discarded.
// A change in the ID could mean the connection was closed by the user or that the node will not send nor receive any more data over said connection ID.
ConnectionID() *uint64
```
## Install
How to install package with newer versions of Go (+1.16):
+20 -4
View File
@@ -7,9 +7,13 @@ import (
"io"
"math"
"math/bits"
"net"
"github.com/soypat/lneto"
)
type Client struct {
connID uint64
reqHostname string
hostname []byte
dns [][4]byte
@@ -41,8 +45,13 @@ type RequestConfig struct {
func (c *Client) BeginRequest(xid uint32, cfg RequestConfig) error {
if len(cfg.Hostname) > 36 {
return errors.New("requested hostname too long")
} else if c.state != StateInit && c.state != 0 {
return errors.New("dhcp client must be closed/done before new request")
} else if xid == 0 {
return errors.New("zero xid")
}
c.reset(xid)
c.state = StateInit
c.currentXID = xid
c.reqHostname = cfg.Hostname
c.reqIP = cfg.RequestedAddr
@@ -50,14 +59,19 @@ func (c *Client) BeginRequest(xid uint32, cfg RequestConfig) error {
return nil
}
func (c *Client) Send(dst []byte) (int, error) {
func (c *Client) Protocol() uint64 { return uint64(lneto.IPProtoUDP) }
func (c *Client) LocalPort() uint16 { return DefaultClientPort }
func (c *Client) ConnectionID() *uint64 { return &c.connID }
func (c *Client) Encapsulate(carrierFrame []byte, frameOffset int) (int, error) {
if c.isClosed() {
return 0, io.EOF
return 0, net.ErrClosed
} else if c.state == StateSelecting && c.offer == [4]byte{} {
return 0, nil // No offer received yet.
} else if c.state == StateBound {
return 0, nil // Done!
}
dst := carrierFrame[frameOffset:]
frm, err := NewFrame(dst)
if err != nil {
return 0, err
@@ -108,10 +122,11 @@ func (c *Client) Send(dst []byte) (int, error) {
return optionsOffset + n, nil
}
func (c *Client) Recv(pkt []byte) error {
func (c *Client) Demux(carrierData []byte, frameOffset int) error {
if c.isClosed() {
return io.EOF
return net.ErrClosed
}
pkt := carrierData[frameOffset:]
frm, err := NewFrame(pkt)
if err != nil {
return err
@@ -217,6 +232,7 @@ func (c *Client) setHeader(frm Frame) {
func (c *Client) reset(xid uint32) {
*c = Client{
connID: c.connID + 1,
reqHostname: c.reqHostname,
currentXID: xid,
reqIP: c.reqIP,
+4
View File
@@ -4,6 +4,10 @@ const (
sizeHeaderNoVLAN = 14
)
func BroadcastAddr() [6]byte {
return [6]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
}
//go:generate stringer -type=Type -linecomment -output stringers.go .
type Type uint16
+189
View File
@@ -0,0 +1,189 @@
package main
import (
"crypto/rand"
"encoding/binary"
"fmt"
"net"
"net/netip"
"os"
"time"
"github.com/soypat/lneto"
"github.com/soypat/lneto/arp"
"github.com/soypat/lneto/dhcpv4"
"github.com/soypat/lneto/ethernet"
"github.com/soypat/lneto/internal/ltesto"
"github.com/soypat/lneto/internet"
"github.com/soypat/lneto/internet/pcap"
)
func main() {
err := run()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println("success")
}
func run() (err error) {
br := ltesto.NewHTTPTapClient("http://127.0.0.1:7070")
defer br.Close()
nicHW := br.HardwareAddr6()
brHW := nicHW
brHW[5]++ // We'll be using a similar HW address but with NIC specific identifier modified.
mtu := br.MTU()
nicAddr := br.IPPrefix()
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))
if err != nil {
return err
}
err = stack.BeginDHCPRequest()
if err != nil {
return err
}
var shark pcap.PacketBreakdown
buf := make([]byte, mtu)
var iframes []pcap.Frame
lastAction := time.Now()
for {
clear(buf)
nwrite, err := stack.Encapsulate(buf[:], 0)
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 := br.Write(buf[:nwrite])
if err != nil {
return err
} else if n != nwrite {
return fmt.Errorf("mismatch written bytes %d!=%d", nwrite, n)
}
}
clear(buf)
nread, err := br.Read(buf)
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)
}
}
if nread == 0 && nwrite == 0 && time.Since(lastAction) > 4*time.Second {
time.Sleep(5 * time.Millisecond)
} else {
lastAction = time.Now()
}
}
return nil
}
type Stack struct {
link internet.StackEthernet
ip internet.StackIP
arp internet.NodeARP
udps internet.StackPorts
dhcp dhcpv4.Client
}
func (s *Stack) Demux(b []byte, _ int) error {
return s.link.Demux(b, 0)
}
func (s *Stack) Encapsulate(b []byte, _ int) (int, error) {
return s.link.Encapsulate(b, 0)
}
func (s *Stack) Reset(mac [6]byte, addr netip.Addr, mtu uint16) error {
err := s.link.Reset6(mac, ethernet.BroadcastAddr(), int(mtu))
if err != nil {
return err
}
err = s.ip.Reset(addr)
if err != nil {
return err
}
ipaddr := addr.AsSlice()
proto := ethernet.TypeIPv4
if addr.Is6() {
proto = ethernet.TypeIPv6
}
err = s.arp.Reset(arp.HandlerConfig{
HardwareAddr: mac[:],
ProtocolAddr: ipaddr,
MaxQueries: 3,
MaxPending: 3,
HardwareType: 1,
ProtocolType: proto,
})
if err != nil {
return err
}
err = s.udps.Reset(uint64(lneto.IPProtoUDP), 2)
if err != nil {
return err
}
// Now setup stacks.
err = s.link.Register(&s.arp) // ARP.
if err != nil {
return err
}
err = s.link.Register(&s.ip) // IPv4 | IPv6
if err != nil {
return err
}
err = s.ip.Register(&s.udps)
if err != nil {
return err
}
return nil
}
func (s *Stack) BeginDHCPRequest() error {
addr4 := s.ip.Addr().As4()
var buf [4]byte
rand.Read(buf[:])
xid := binary.LittleEndian.Uint32(buf[:])
err := s.dhcp.BeginRequest(xid, dhcpv4.RequestConfig{
RequestedAddr: addr4,
ClientHardwareAddr: s.link.HardwareAddr6(),
Hostname: "lneto",
})
if err != nil {
return err
}
var u internet.StackUDPPort
u.SetStackNode(&s.dhcp, dhcpv4.DefaultServerPort)
err = s.udps.Register(&u)
if err != nil {
return err
}
return err
}
func clear(buf []byte) {
for i := range buf {
buf[i] = 0
}
}
+41 -12
View File
@@ -2,6 +2,7 @@ package main
import (
"errors"
"flag"
"fmt"
"log"
"log/slog"
@@ -9,9 +10,11 @@ import (
"net/http"
"net/netip"
"runtime"
"strings"
"time"
"github.com/soypat/lneto"
"github.com/soypat/lneto/internal"
"github.com/soypat/lneto/internal/ltesto"
"github.com/soypat/lneto/internet/pcap"
"github.com/soypat/lneto/tcp"
@@ -27,17 +30,35 @@ func main() {
}
func run() error {
var (
flagInterface = "tap0"
)
flag.StringVar(&flagInterface, "i", flagInterface, "Interface to select. tap* creates a tap interface. Any other name will create a bridge to the name of the interface i.e: 'enp7s0', 'wlp8s0', 'lo'")
var (
flagNet = "192.168.10.1/24"
flagiface = "tap0"
flagMTU = 1500
flagPacketQueueSize = 2048
)
ip, err := netip.ParsePrefix(flagNet)
if err != nil {
return err
var iface ltesto.Interface
if strings.HasPrefix(flagInterface, "tap") {
pfx, err := netip.ParsePrefix(flagNet)
if err != nil {
return err
}
tap, err := internal.NewTap(flagiface, pfx)
if err != nil {
return err
}
iface = tap
} else {
br, err := internal.NewBridge(flagInterface)
if err != nil {
return err
}
iface = br
}
sv, err := ltesto.NewHTTPTapServer(flagiface, ip, flagMTU, flagPacketQueueSize, flagPacketQueueSize)
sv, err := ltesto.NewHTTPTapServer(iface, flagPacketQueueSize, flagPacketQueueSize)
if err != nil {
return err
}
@@ -48,8 +69,12 @@ func run() error {
frames, err := cap.CaptureEthernet(nil, pkt, 0)
if err == nil {
flags, src, dst := getTCPData(frames, pkt)
if flags != 0 {
fmt.Println(channel, captime.Format("15:04:05.000"), frames, flags.String(), src, "->", dst)
if src != 0 {
if flags != 0 {
fmt.Println(channel, captime.Format("15:04:05.000"), frames, flags.String(), src, "->", dst)
} else {
fmt.Println(channel, captime.Format("15:04:05.000"), frames, src, "->", dst)
}
} else {
fmt.Println(channel, captime.Format("15:04:05.000"), frames)
}
@@ -86,12 +111,16 @@ func run() error {
func getTCPData(frames []pcap.Frame, pkt []byte) (flags tcp.Flags, src, dst uint16) {
for i := range frames {
if frames[i].Protocol != lneto.IPProtoTCP {
continue
proto := frames[i].Protocol
if proto == lneto.IPProtoTCP {
return tcp.Flags(getFrameClassUint(frames[i], pkt, pcap.FieldClassFlags)),
uint16(getFrameClassUint(frames[i], pkt, pcap.FieldClassSrc)),
uint16(getFrameClassUint(frames[i], pkt, pcap.FieldClassDst))
} else if proto == lneto.IPProtoUDP {
return 0,
uint16(getFrameClassUint(frames[i], pkt, pcap.FieldClassSrc)),
uint16(getFrameClassUint(frames[i], pkt, pcap.FieldClassDst))
}
return tcp.Flags(getFrameClassUint(frames[i], pkt, pcap.FieldClassFlags)),
uint16(getFrameClassUint(frames[i], pkt, pcap.FieldClassSrc)),
uint16(getFrameClassUint(frames[i], pkt, pcap.FieldClassDst))
}
return 0, 0, 0
}
+23 -9
View File
@@ -9,12 +9,19 @@ import (
"net/http"
"net/netip"
"net/url"
"github.com/soypat/lneto/internal"
)
const minMTU = 256
type Interface interface {
Read(b []byte) (int, error)
Write(b []byte) (int, error)
Close() error
HardwareAddress6() ([6]byte, error)
MTU() (int, error)
IPMask() (netip.Prefix, error)
}
// NewHTTPTapClient returns a HTTPTapClient ready for use.
func NewHTTPTapClient(baseURL string) *HTTPTapClient {
var h HTTPTapClient
@@ -150,7 +157,7 @@ func (h *HTTPTapClient) Close() error { return nil }
type HTTPTapServer struct {
router *http.ServeMux
stack stack
tap *internal.Tap
tap Interface
buf []byte
onTx func(channel int, pkt []byte)
tapfailed bool
@@ -166,11 +173,17 @@ func (sv *HTTPTapServer) OnTransfer(cb func(channel int, pkt []byte)) {
sv.onTx = cb
}
func NewHTTPTapServer(iface string, ip netip.Prefix, mtu, queueOut, queueIn int) (*HTTPTapServer, error) {
if mtu < minMTU {
func NewHTTPTapServer(iface Interface, queueOut, queueIn int) (*HTTPTapServer, error) {
if iface == nil {
return nil, errors.New("nil interface argument to HTTP interface server")
}
mtu, err := iface.MTU()
if err != nil {
return nil, err
} else if mtu < minMTU {
return nil, errors.New("too small MTU")
}
tap, err := internal.NewTap(iface, ip)
netmask, err := iface.IPMask()
if err != nil {
return nil, err
}
@@ -183,7 +196,7 @@ func NewHTTPTapServer(iface string, ip netip.Prefix, mtu, queueOut, queueIn int)
taps := &HTTPTapServer{
router: sv,
stack: s,
tap: tap,
tap: iface,
buf: make([]byte, mtu),
}
sv.HandleFunc("/send", func(w http.ResponseWriter, r *http.Request) {
@@ -210,13 +223,14 @@ func NewHTTPTapServer(iface string, ip netip.Prefix, mtu, queueOut, queueIn int)
json.NewEncoder(w).Encode("") // send empty string.
}
})
ipstr := ip.String()
ipstr := netmask.String()
sv.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
info := tapInfo{
MTU: mtu,
IPPrefix: ipstr,
}
hw, err := tap.HardwareAddress6()
hw, err := iface.HardwareAddress6()
if err == nil {
info.HardwareAddr = net.HardwareAddr(hw[:]).String()
}
+168 -19
View File
@@ -3,8 +3,11 @@
package internal
import (
"encoding/binary"
"errors"
"fmt"
"math/bits"
"net"
"net/netip"
"os"
"os/exec"
@@ -12,6 +15,8 @@ import (
"unsafe"
)
const safamily_hw6 = 1
type Tap struct {
fd int // points to /dev/net/tun device.
name string
@@ -25,12 +30,7 @@ func NewTap(name string, ip netip.Prefix) (*Tap, error) {
if err != nil {
return nil, fmt.Errorf("failed to open tun device: %w", err)
}
tap := Tap{
name: name,
fd: fd,
}
ifr := tap.ifreq()
ifr := makeifreq(name)
// Set the flags (starting at offset IFNAMSIZ).
flags := uint16(syscall.IFF_TAP | syscall.IFF_NO_PI)
ifr.setflags(flags)
@@ -55,6 +55,14 @@ func NewTap(name string, ip netip.Prefix) (*Tap, error) {
return &Tap{fd: fd, name: name}, nil
}
func (tap *Tap) IPMask() (netip.Prefix, error) {
sockfd, err := tap.getSock()
if err != nil {
return netip.Prefix{}, err
}
return getSocketMask(sockfd, tap.name)
}
func (tap *Tap) Read(b []byte) (int, error) {
return syscall.Read(tap.fd, b)
}
@@ -75,27 +83,108 @@ func ioctl(fd int, request uintptr, argp unsafe.Pointer) error {
return nil
}
func (tap *Tap) HardwareAddress6() (hw [6]byte, err error) {
// We cannot use tap.sock to query the hardware address, this is something known by the network stack, so get a sock to network stack.
sock, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
func (tap *Tap) MTU() (int, error) {
sock, err := tap.getSock()
if err != nil {
return hw, fmt.Errorf("socket open: %w", err)
return 0, err
}
defer syscall.Close(sock)
ifr := tap.ifreq()
return getSocketMTU(sock, tap.name)
}
err = ioctl(sock, syscall.SIOCGIFHWADDR, ifr.ptr())
func (tap *Tap) HardwareAddress6() (hw [6]byte, err error) {
// We cannot use tap.sock to query the hardware address, this is something known by the network stack, so get a sock to network stack.
sock, err := tap.getSock()
if err != nil {
return hw, err
}
sa_family := *(*uint16)(unsafe.Pointer(&ifr.Data[0]))
if sa_family != 1 {
defer syscall.Close(sock)
return getSocketHW(sock, tap.name)
}
func (tap *Tap) getSock() (int, error) {
sock, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
if err != nil {
return 0, fmt.Errorf("tap socket open: %w", err)
}
return sock, err
}
func getSocketMTU(sockfd int, ifaceName string) (int, error) {
ifr := makeifreq(ifaceName)
err := ioctl(sockfd, syscall.SIOCGIFMTU, ifr.ptr())
if err != nil {
return 0, err
}
mtu := *(*int32)(unsafe.Pointer(&ifr.Data[0]))
return int(mtu), nil
}
func getSocketHW(sockfd int, ifaceName string) (hw [6]byte, err error) {
ifr := makeifreq(ifaceName)
err = ioctl(sockfd, syscall.SIOCGIFHWADDR, ifr.ptr())
if err != nil {
return hw, err
}
sa_family := *(*uint16)(unsafe.Pointer(&ifr.Data[0])) // Host order.
if sa_family != safamily_hw6 {
return hw, fmt.Errorf("expecting sa_family=1 got %d", sa_family)
}
copy(hw[:], ifr.Data[2:]) // first two bytes are sa_family
return hw, nil
}
func getSocketMask(sockfd int, ifaceName string) (netip.Prefix, error) {
addrp, err := getSocketIP(sockfd, ifaceName)
if err != nil {
return netip.Prefix{}, err
}
ifr := makeifreq(ifaceName)
err = ioctl(sockfd, syscall.SIOCGIFNETMASK, ifr.ptr())
if err != nil {
return netip.Prefix{}, err
}
addr32 := binary.BigEndian.Uint32(ifr.Data[4:8])
cidr := bits.OnesCount32(addr32)
return netip.PrefixFrom(addrp.Addr(), cidr), nil
}
func setSocketHW(sockfd int, ifaceName string, hw [6]byte) error {
ifr := makeifreq(ifaceName)
*(*uint16)(unsafe.Pointer(&ifr.Data[0])) = safamily_hw6
copy(ifr.Data[2:], hw[:])
err := ioctl(sockfd, syscall.SIOCSIFHWADDR, ifr.ptr())
if err != nil {
return fmt.Errorf("setting hw addr: %w", err)
}
return nil
}
func getSocketIP(sockfd int, ifaceName string) (addrp netip.AddrPort, err error) {
ifr := makeifreq(ifaceName)
err = ioctl(sockfd, syscall.SIOCGIFADDR, ifr.ptr())
if err != nil {
return netip.AddrPort{}, err
}
safamily := *(*uint16)(unsafe.Pointer(&ifr.Data[0]))
port := *(*uint16)(unsafe.Pointer(&ifr.Data[2]))
switch safamily {
case 2:
addr, _ := netip.AddrFromSlice(ifr.Data[4:8])
addrp = netip.AddrPortFrom(addr, port)
default:
return addrp, fmt.Errorf("unsupported IP addr sa_family=%d", safamily)
}
return addrp, nil
}
func makeifreq(name string) ifreq {
// Set the name; it will be zero-padded automatically.
var ifr ifreq
copy(ifr.Name[:], name)
return ifr
}
type ifreq struct {
Name [syscall.IFNAMSIZ]byte
Data [64]byte // union data (covers ifr_hwaddr, etc.)
@@ -107,9 +196,69 @@ func (ifr *ifreq) setflags(flags uint16) {
func (ifr *ifreq) ptr() unsafe.Pointer { return unsafe.Pointer(ifr) }
func (tap *Tap) ifreq() ifreq {
// Set the name; it will be zero-padded automatically.
var ifr ifreq
copy(ifr.Name[:], tap.name)
return ifr
// Bridge serves as a virtual socket that "bridges" to an existing interface (could be TAP or an actual NIC that connects to internet).
// Bridge is used in this project to
type Bridge struct {
fd int
name string
index int
}
func NewBridge(name string) (*Bridge, error) {
iface, err := net.InterfaceByName(name)
if err != nil {
return nil, err
}
proto := htons(syscall.ETH_P_IP)
fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, int(proto))
if err != nil {
return nil, err
}
ll := syscall.SockaddrLinklayer{
Protocol: proto,
Ifindex: iface.Index,
}
if err := syscall.Bind(fd, &ll); err != nil {
return nil, err
}
return &Bridge{fd: fd, name: iface.Name, index: iface.Index}, nil
}
func (br *Bridge) Write(frame []byte) (int, error) {
return syscall.Write(br.fd, frame)
}
func (br *Bridge) Read(frame []byte) (int, error) {
return syscall.Read(br.fd, frame)
}
func (br *Bridge) Close() error {
return syscall.Close(br.fd)
}
func (br *Bridge) HardwareAddress6() (hw [6]byte, err error) {
return getSocketHW(br.fd, br.name)
}
func (br *Bridge) SetHardwareAddress6(hw [6]byte) error {
return setSocketHW(br.fd, br.name, hw)
}
func (br *Bridge) IPMask() (netip.Prefix, error) {
return getSocketMask(br.fd, br.name)
}
func (br *Bridge) Addr() (netip.Addr, error) {
addrp, err := getSocketIP(br.fd, br.name)
if err != nil {
return netip.Addr{}, err
}
return addrp.Addr(), nil
}
func (br *Bridge) MTU() (int, error) {
return getSocketMTU(br.fd, br.name)
}
// htons converts a uint16 from host to network byte order.
func htons(i uint16) uint16 { return (i<<8)&0xff00 | i>>8 }
+43 -13
View File
@@ -24,7 +24,6 @@ type StackNode interface {
Encapsulate(carrierData []byte, frameOffset int) (int, error)
// Demux reads from the argument buffer where frameOffset is the offset of this StackNode's frame first byte.
// The stack node then dispatches(demuxes) the encapsulated frames to its corresponding sub-stack-node(s).
//
Demux(carrierData []byte, frameOffset int) error
LocalPort() uint16
Protocol() uint64
@@ -39,7 +38,6 @@ type node struct {
connID *uint64
demux func([]byte, int) error
encapsulate func([]byte, int) (int, error)
lastErrs [2]error
proto uint16
port uint16
}
@@ -57,34 +55,66 @@ func handleNodeError(nodesPtr *[]node, nodeIdx int, err error) (discarded bool)
panic("unreachable")
}
nodes := *nodesPtr
badConnID := nodes[nodeIdx].connID != nil && *nodes[nodeIdx].connID != nodes[nodeIdx].currConnID
if err == net.ErrClosed || badConnID {
if checkNodeErr(&nodes[nodeIdx], err) {
*nodesPtr = slices.Delete(nodes, nodeIdx, nodeIdx+1)
discarded = true
} else {
// Advance Queue of errors
nodes[nodeIdx].lastErrs[1] = nodes[nodeIdx].lastErrs[0]
nodes[nodeIdx].lastErrs[0] = err
}
}
return discarded
}
func checkNode(node *node) (discard bool) {
return node.demux == nil || node.connID != nil && node.currConnID != *node.connID
}
func checkNodeErr(node *node, err error) (discard bool) {
return checkNode(node) || (err != nil && err == net.ErrClosed)
}
func addNode(nodes *[]node, h StackNode, port uint16, protocol uint64) {
*nodes = append(*nodes, nodeFromStackNode(h, port, protocol))
}
func nodeFromStackNode(s StackNode, port uint16, protocol uint64) node {
if protocol > math.MaxUint16 {
panic(">16bit protocol number unsupported")
}
var currConnID uint64
connIDPtr := h.ConnectionID()
connIDPtr := s.ConnectionID()
if connIDPtr != nil {
currConnID = *connIDPtr
}
*nodes = append(*nodes, node{
return node{
currConnID: currConnID,
connID: connIDPtr,
demux: h.Demux,
encapsulate: h.Encapsulate,
demux: s.Demux,
encapsulate: s.Encapsulate,
proto: uint16(protocol),
port: port,
})
}
}
func getNode(nodes []node, port uint16, protocol uint16) (node *node) {
for i := range nodes {
node := &nodes[i]
if node.port == port && node.proto == protocol {
return node
}
}
return nil
}
// destroy removes all references to underlying StackNode. Allows garbage collection of node if possible.
func (n *node) destroy() {
*n = node{}
}
func getNodeByProto(nodes []node, protocol uint16) int {
for i := range nodes {
node := &nodes[i]
if node.proto == protocol {
return i
}
}
return -1
}
-31
View File
@@ -1,31 +0,0 @@
package internet
import (
"github.com/soypat/lneto/ntp"
)
var _ StackNode = (*NodeNTPClient)(nil)
type NodeNTPClient struct {
c ntp.Client
}
func (n *NodeNTPClient) Protocol() uint64 {
return 0
}
func (n *NodeNTPClient) LocalPort() uint16 {
return ntp.ClientPort
}
func (n *NodeNTPClient) ConnectionID() *uint64 {
return n.c.ConnectionID()
}
func (n *NodeNTPClient) Demux(carrierData []byte, ntpOffset int) error {
return nil
}
func (n *NodeNTPClient) Encapsulate(carrierData []byte, ntpOffset int) (int, error) {
return 0, nil
}
+14 -1
View File
@@ -208,6 +208,11 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
ifrm4.CRCWriteTCPPseudo(&crc)
tfrm, err := tcp.NewFrame(ifrm4.Payload())
if err == nil {
tfrm.ValidateSize(pc.validator())
if pc.vld.HasError() {
println("BAD TCP")
return dst, pc.vld.ErrPop()
}
tfrm.CRCWrite(&crc)
wantSum := crc.Sum16()
gotSum := tfrm.CRC()
@@ -215,10 +220,15 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
protoErrs = append(protoErrs, &crcError16{protocol: "ipv4+tcp", want: wantSum, got: gotSum})
}
}
} else if proto == lneto.IPProtoUDP || proto == lneto.IPProtoUDPLite {
} else if proto == lneto.IPProtoUDP {
ifrm4.CRCWriteUDPPseudo(&crc)
ufrm, err := udp.NewFrame(ifrm4.Payload())
if err == nil {
ufrm.ValidateSize(pc.validator())
if pc.vld.HasError() {
println("BAD UDP")
return dst, pc.vld.ErrPop()
}
ufrm.CRCWriteIPv4(&crc)
wantSum := crc.Sum16()
gotSum := ufrm.CRC()
@@ -289,6 +299,9 @@ func (pc *PacketBreakdown) CaptureTCP(dst []Frame, pkt []byte, bitOffset int) ([
return dst, nil
}
// func (pc *PacketBreakdown) CaptureDHCPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
// }
func (pc *PacketBreakdown) CaptureUDP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
if bitOffset%8 != 0 {
return dst, errors.New("UDP must be parsed at byte boundary")
+12
View File
@@ -20,6 +20,18 @@ type StackEthernet struct {
mtu uint16
}
func (ls *StackEthernet) SetGateway6(gw [6]byte) {
ls.gwmac = gw
}
func (ls *StackEthernet) SetHardwareAddr6(mac [6]byte) {
ls.mac = mac
}
func (ls *StackEthernet) HardwareAddr6() [6]byte {
return ls.mac
}
func (ls *StackEthernet) Reset6(mac, gateway [6]byte, mtu int) error {
if mtu > math.MaxUint16 || mtu < 256 {
return errors.New("invalid MTU")
+77 -52
View File
@@ -4,15 +4,14 @@ import (
"errors"
"io"
"log/slog"
"net"
"net/netip"
"slices"
"github.com/soypat/lneto"
"github.com/soypat/lneto/ethernet"
"github.com/soypat/lneto/internal"
"github.com/soypat/lneto/ipv4"
"github.com/soypat/lneto/tcp"
"github.com/soypat/lneto/udp"
)
var _ StackNode = (*StackIP)(nil)
@@ -73,40 +72,60 @@ func (sb *StackIP) Demux(carrierData []byte, offset int) error {
}
dst := ifrm.DestinationAddr()
if *dst != sb.ip {
goto DROP
}
{
sb.validator.ResetErr()
ifrm.ValidateExceptCRC(&sb.validator)
if err = sb.validator.ErrPop(); err != nil {
return err
}
gotCRC := ifrm.CRC()
wantCRC := ifrm.CalculateHeaderCRC()
if gotCRC != wantCRC {
sb.error("StackIP:Demux:crc-mismatch", slog.Uint64("want", uint64(wantCRC)), slog.Uint64("got", uint64(gotCRC)))
return errors.New("IPv4 CRC mismatch")
}
off := ifrm.HeaderLength()
totalLen := ifrm.TotalLength()
for i := range sb.handlers {
h := &sb.handlers[i]
proto := ifrm.Protocol()
if h.proto == uint16(proto) {
sb.info("ipDemux", slog.String("ipproto", proto.String()), slog.Int("plen", int(totalLen)))
err = h.demux(frame[:totalLen], off)
if err == net.ErrClosed {
sb.info("ipclose", slog.String("proto", proto.String()))
sb.handlers = slices.Delete(sb.handlers, i, i+1)
}
return err
}
}
return nil // Not meant for us.
}
DROP:
sb.info("iprecv:drop", slog.String("dstaddr", netip.AddrFrom4(*ifrm.DestinationAddr()).String()), slog.String("proto", ifrm.Protocol().String()))
return nil
sb.validator.ResetErr()
ifrm.ValidateExceptCRC(&sb.validator)
if err = sb.validator.ErrPop(); err != nil {
return err
}
gotCRC := ifrm.CRC()
wantCRC := ifrm.CalculateHeaderCRC()
if gotCRC != wantCRC {
sb.error("StackIP:Demux:crc-mismatch", slog.Uint64("want", uint64(wantCRC)), slog.Uint64("got", uint64(gotCRC)))
return errors.New("IPv4 CRC mismatch")
}
off := ifrm.HeaderLength()
totalLen := ifrm.TotalLength()
proto := ifrm.Protocol()
nodeIdx := getNodeByProto(sb.handlers, uint16(proto))
if nodeIdx < 0 {
// Drop packet.
sb.info("iprecv:drop", slog.String("dstaddr", netip.AddrFrom4(*ifrm.DestinationAddr()).String()), slog.String("proto", ifrm.Protocol().String()))
return nil
}
// Incoming CRC Validation of common IP Protocols.
var crc lneto.CRC791
switch proto {
case lneto.IPProtoTCP:
ifrm.CRCWriteTCPPseudo(&crc)
tfrm, err := tcp.NewFrame(ifrm.Payload())
if err != nil {
return err
}
tfrm.CRCWrite(&crc)
if crc.Sum16() != tfrm.CRC() {
return errors.New("TCP CRC mismatch")
}
case lneto.IPProtoUDP:
ifrm.CRCWriteUDPPseudo(&crc)
ufrm, err := udp.NewFrame(ifrm.Payload())
if err != nil {
return err
}
ufrm.CRCWriteIPv4(&crc)
if crc.Sum16() != ufrm.CRC() {
return errors.New("UDP CRC mismatch")
}
}
sb.info("ipDemux", slog.String("ipproto", proto.String()), slog.Int("plen", int(totalLen)))
err = sb.handlers[nodeIdx].demux(frame[:totalLen], off)
if handleNodeError(&sb.handlers, nodeIdx, err) {
sb.info("ipclose", slog.String("proto", proto.String()))
err = nil
}
return err
}
func (sb *StackIP) Encapsulate(carrierData []byte, frameOffset int) (int, error) {
@@ -117,7 +136,7 @@ func (sb *StackIP) Encapsulate(carrierData []byte, frameOffset int) (int, error)
ifrm, _ := ipv4.NewFrame(frame)
const ihl = 5
const headerlen = ihl * 4
ifrm.SetVersionAndIHL(4, 5)
ifrm.SetVersionAndIHL(4, ihl)
ifrm.SetToS(0)
ifrm.SetID(0)
*ifrm.SourceAddr() = sb.ip
@@ -128,25 +147,31 @@ func (sb *StackIP) Encapsulate(carrierData []byte, frameOffset int) (int, error)
if err != nil {
sb.error("StackIP:handle", slog.String("proto", proto.String()), slog.String("err", err.Error()))
continue
} else if n == 0 {
continue
}
if n > 0 {
const dontFrag = 0x4000
totalLen := n + headerlen
ifrm.SetTotalLength(uint16(totalLen))
ifrm.SetFlags(dontFrag)
ifrm.SetTTL(64)
ifrm.SetProtocol(proto)
ifrm.SetCRC(ifrm.CalculateHeaderCRC())
if ifrm.Protocol() == lneto.IPProtoTCP {
var crc lneto.CRC791
ifrm.CRCWriteTCPPseudo(&crc)
tfrm, _ := tcp.NewFrame(ifrm.Payload())
tfrm.CRCWrite(&crc)
tfrm.SetCRC(crc.Sum16())
sb.info("StackIP:send", slog.String("ip", ifrm.String()), slog.String("tcp", tfrm.String()))
}
return totalLen, nil
const dontFrag = 0x4000
totalLen := n + headerlen
ifrm.SetTotalLength(uint16(totalLen))
ifrm.SetFlags(dontFrag)
ifrm.SetTTL(64)
ifrm.SetProtocol(proto)
ifrm.SetCRC(ifrm.CalculateHeaderCRC())
// Calculate CRC for our newly generated packet.
var crc lneto.CRC791
switch proto {
case lneto.IPProtoTCP:
ifrm.CRCWriteTCPPseudo(&crc)
tfrm, _ := tcp.NewFrame(ifrm.Payload())
tfrm.CRCWrite(&crc)
tfrm.SetCRC(crc.Sum16())
case lneto.IPProtoUDP:
ifrm.CRCWriteUDPPseudo(&crc)
ufrm, _ := udp.NewFrame(ifrm.Payload())
ufrm.CRCWriteIPv4(&crc)
ufrm.SetCRC(crc.Sum16())
}
return totalLen, nil
}
return 0, nil
}
+12 -6
View File
@@ -3,26 +3,32 @@ package internet
import (
"encoding/binary"
"io"
"math"
)
type StackPorts struct {
connID uint64
protocol uint64
handlers []node
dstPortOff int
protocol uint16
}
func (ps *StackPorts) Reset(protocol uint64, dstPortOffset int) {
func (ps *StackPorts) Reset(protocol uint64, dstPortOffset int) error {
if protocol > math.MaxUint16 {
return errInvalidProto
}
*ps = StackPorts{
connID: ps.connID + 1,
handlers: ps.handlers[:0],
dstPortOff: dstPortOffset,
protocol: protocol,
protocol: uint16(protocol),
}
return nil
}
func (ps *StackPorts) LocalPort() uint16 { return 0 }
func (ps *StackPorts) Protocol() uint64 { return ps.protocol }
func (ps *StackPorts) Protocol() uint64 { return uint64(ps.protocol) }
func (ps *StackPorts) ConnectionID() *uint64 { return &ps.connID }
@@ -65,13 +71,13 @@ func (ps *StackPorts) Register(h StackNode) error {
proto := h.Protocol()
if port <= 0 {
return errZeroPort
} else if proto != ps.protocol {
} else if proto != uint64(ps.protocol) {
return errInvalidProto
}
ps.handlers = append(ps.handlers, node{
demux: h.Demux,
encapsulate: h.Encapsulate,
port: uint16(port),
port: port,
})
return nil
}
+78
View File
@@ -0,0 +1,78 @@
package internet
import (
"log/slog"
"net"
"github.com/soypat/lneto"
"github.com/soypat/lneto/udp"
)
type StackUDPPort struct {
h node
vld lneto.Validator
rmport uint16
}
func (sudp *StackUDPPort) SetStackNode(node StackNode, rmport uint16) {
sudp.h = nodeFromStackNode(node, node.LocalPort(), node.Protocol())
sudp.rmport = rmport
}
func (sudp *StackUDPPort) Protocol() uint64 { return uint64(lneto.IPProtoUDP) }
func (sudp *StackUDPPort) LocalPort() uint16 { return sudp.h.port }
func (sudp *StackUDPPort) ConnectionID() *uint64 { return sudp.h.connID }
func (sudp *StackUDPPort) Demux(carrierData []byte, frameOffset int) error {
if checkNode(&sudp.h) {
sudp.h.destroy()
return net.ErrClosed
}
ufrm, err := udp.NewFrame(carrierData[frameOffset:])
if err != nil {
return err
}
ufrm.ValidateSize(&sudp.vld)
if sudp.vld.HasError() {
return sudp.vld.ErrPop()
}
dst := ufrm.DestinationPort()
if dst != sudp.h.port {
return nil // Not meant for us.
}
src := ufrm.SourcePort()
if sudp.rmport != 0 && src != sudp.rmport {
return nil // Not from our target remote port.
}
err = sudp.h.demux(ufrm.Payload(), 8)
if err != nil {
if checkNodeErr(&sudp.h, err) {
sudp.h.destroy()
}
slog.Error("stackudp:demux", slog.String("err", err.Error()))
}
return err
}
func (sudp *StackUDPPort) Encapsulate(carrierData []byte, frameOffset int) (int, error) {
if checkNode(&sudp.h) {
sudp.h.destroy()
return 0, net.ErrClosed
}
ufrm, err := udp.NewFrame(carrierData[frameOffset:])
if err != nil {
return 0, err
}
ufrm.SetSourcePort(sudp.h.port)
ufrm.SetDestinationPort(sudp.rmport)
n, err := sudp.h.encapsulate(carrierData[frameOffset:], 8)
if err != nil {
slog.Error("stackudp:demux", slog.String("err", err.Error()))
}
ufrm.SetLength(8 + uint16(n))
// UDP CRC left to IP layer.
return n, err
}
+4 -9
View File
@@ -19,13 +19,6 @@ const (
const sysprecRecalcNeeded int8 = 127
func NewClient(now func() time.Time) *Client {
return &Client{
_now: now,
_sysprec: sysprecRecalcNeeded,
}
}
type Client struct {
connID uint64
start time.Time
@@ -54,10 +47,11 @@ func (c *Client) ConnectionID() *uint64 {
return &c.connID
}
func (c *Client) Send(payload []byte) (int, error) {
func (c *Client) Encapsulate(carrierData []byte, frameOffset int) (int, error) {
if c.isDone() {
return 0, io.EOF
}
payload := carrierData[frameOffset:]
frm, err := NewFrame(payload)
if err != nil {
return 0, err
@@ -88,10 +82,11 @@ func (c *Client) Send(payload []byte) (int, error) {
return SizeHeader, nil
}
func (c *Client) Read(payload []byte) error {
func (c *Client) Demux(carrierData []byte, frameOffset int) error {
if c.isDone() {
return io.EOF
}
payload := carrierData[frameOffset:]
frm, err := NewFrame(payload)
if err != nil {
return err