From 0008791bd8b51d079aae537f8f88c1ece5d18fb2 Mon Sep 17 00:00:00 2001 From: soypat Date: Sat, 31 May 2025 12:47:09 -0300 Subject: [PATCH] fix ethernet gateway addr not being set; add IPID field; hardware addr support of tap; better tap interface --- examples/stackbasic/main.go | 49 ++++++++++----------- examples/tap/main.go | 7 ++- internal/ltesto/httptap.go | 86 +++++++++++++++++++++++++++++++++++++ internal/tap.go | 67 ++++++++++++++++++++++++----- internet/tcpconn.go | 21 +++++---- 5 files changed, 187 insertions(+), 43 deletions(-) diff --git a/examples/stackbasic/main.go b/examples/stackbasic/main.go index 0986dbd..4eede40 100644 --- a/examples/stackbasic/main.go +++ b/examples/stackbasic/main.go @@ -23,8 +23,6 @@ import ( ) const ( - mtu = 2048 - iface = "192.168.10.1/24" stackIP = "192.168.10.2" stackPort = 80 iss = 100 @@ -34,8 +32,10 @@ var stackHWAddr = [6]byte{0xc0, 0xff, 0xee, 0x00, 0xde, 0xad} func main() { ip := netip.MustParseAddr(stackIP) - iface := netip.MustParsePrefix(iface) - if !iface.Contains(ip) { + tap := ltesto.NewHTTPTapClient("http://127.0.0.1:7070") + + ippfx := tap.IPPrefix() + if !ippfx.Contains(ip) { log.Fatal("interface does not contain stack address") } addrPort := netip.AddrPortFrom(ip, stackPort) @@ -44,26 +44,22 @@ func main() { })) slogger := logger{lg} - - lStack, handler, err := NewEthernetTCPStack(stackHWAddr, addrPort, slogger) + gatewayMAC := tap.HardwareAddr6() + mtu := tap.MTU() + lStack, handler, err := NewEthernetTCPStack(stackHWAddr, gatewayMAC, addrPort, uint16(mtu), slogger) if err != nil { log.Fatal(err) } - // h := &handler.H - // _ = h.AwaitingSynAck() - // _ = h.AwaitingSynResponse() - // _ = h.AwaitingSynSend() - // _ = h + err = handler.OpenListen(addrPort.Port(), iss) if err != nil { log.Fatal(err) } - tap := ltesto.NewHTTPTapClient("http://127.0.0.1:7070") defer tap.Close() tap.ReadDiscard() // Discard all unread content. - fmt.Println("hosting server at ", addrPort.String()) - var buf [mtu]byte + fmt.Println("hosting server at ", addrPort.String(), "over tap interface of mtu:", mtu, "prefix:", ippfx, "gateway:", net.HardwareAddr(gatewayMAC[:]).String()) + buf := make([]byte, mtu) var hdr httpraw.Header for { nread, err := tap.Read(buf[:]) @@ -114,12 +110,13 @@ func main() { } } -func NewEthernetTCPStack(mac [6]byte, ip netip.AddrPort, slogger logger) (*LinkStack, *internet.TCPConn, error) { +func NewEthernetTCPStack(ourMAC, gwMAC [6]byte, ip netip.AddrPort, mtu uint16, slogger logger) (*LinkStack, *internet.TCPConn, error) { var err error lStack := LinkStack{ logger: slogger, - mac: mac, + mac: ourMAC, mtu: mtu, + gwmac: gwMAC, } var ipStack internet.StackBasic @@ -161,7 +158,7 @@ func NewEthernetTCPStack(mac [6]byte, ip netip.AddrPort, slogger logger) (*LinkS proto = ethernet.TypeIPv6 } arphandler, err := arp.NewHandler(arp.HandlerConfig{ - HardwareAddr: mac[:], + HardwareAddr: ourMAC[:], ProtocolAddr: ip.Addr().AsSlice(), MaxQueries: 4, MaxPending: 4, @@ -196,8 +193,9 @@ type handler struct { type LinkStack struct { handlers []handler logger - mac [6]byte - mtu uint16 + mac [6]byte + gwmac [6]byte + mtu uint16 } func (ls *LinkStack) Register(h handler) error { @@ -239,23 +237,26 @@ DROP: } func (ls *LinkStack) HandleEth(dst []byte) (n int, err error) { - if len(dst) < int(ls.mtu) { + mtu := ls.mtu + if len(dst) < int(mtu) { return 0, io.ErrShortBuffer } + efrm, err := ethernet.NewFrame(dst) + if err != nil { + return 0, err + } + copy(efrm.DestinationHardwareAddr()[:], ls.gwmac[:]) // default set the gateway. for i := range ls.handlers { h := &ls.handlers[i] - n, err = h.handle(dst[:ls.mtu], 14) + n, err = h.handle(dst[:mtu], 14) if err != nil { ls.error("handling", slog.String("proto", ethernet.Type(h.proto).String()), slog.String("err", err.Error())) continue } if n > 0 { // Found packet - efrm, _ := ethernet.NewFrame(dst[:14]) - copy(efrm.DestinationHardwareAddr()[:], h.raddr) *efrm.SourceHardwareAddr() = ls.mac efrm.SetEtherType(ethernet.Type(h.proto)) - return n + 14, nil } } diff --git a/examples/tap/main.go b/examples/tap/main.go index 5097ac0..5aaadff 100644 --- a/examples/tap/main.go +++ b/examples/tap/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "log/slog" + "net" "net/http" "net/netip" "time" @@ -37,7 +38,11 @@ func run() error { return err } defer sv.Close() - fmt.Println("listening on http://127.0.0.1:7070/recv and http://127.0.0.1:7070/send") + hwaddr, err := sv.HardwareAddress6() + if err != nil { + return err + } + fmt.Println("listening on http://127.0.0.1:7070/recv and http://127.0.0.1:7070/send on hwaddr:", net.HardwareAddr(hwaddr[:]).String()) go http.ListenAndServe(":7070", sv) misses := 0 for { diff --git a/internal/ltesto/httptap.go b/internal/ltesto/httptap.go index 4a56f03..71e0207 100644 --- a/internal/ltesto/httptap.go +++ b/internal/ltesto/httptap.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "log/slog" + "net" "net/http" "net/netip" "net/url" @@ -13,11 +14,14 @@ import ( "github.com/soypat/lneto/internal" ) +const minMTU = 256 + // NewHTTPTapClient returns a HTTPTapClient ready for use. func NewHTTPTapClient(baseURL string) *HTTPTapClient { var h HTTPTapClient h.sendurl = baseURL + "/send" h.recvurl = baseURL + "/recv" + h.infoURL = baseURL + "/info" _, err := url.Parse(h.sendurl) if err != nil { panic(err) @@ -25,10 +29,59 @@ func NewHTTPTapClient(baseURL string) *HTTPTapClient { return &h } +func (h *HTTPTapClient) IPPrefix() netip.Prefix { + h.ensureMTU() + return h.ip +} + +func (h *HTTPTapClient) MTU() int { + h.ensureMTU() + return len(h.buf) +} + +func (h *HTTPTapClient) HardwareAddr6() [6]byte { + return h.hwaddr +} + +func (h *HTTPTapClient) ensureMTU() (err error) { + if len(h.buf) != 0 { + return nil // MTU processed correctly. + } + defer func() { + if err != nil { + err = fmt.Errorf("unable to get MTU from server: %w", err) + } + }() + resp, err := h.c.Get(h.infoURL) + if err != nil { + return err + } + var info TapInfo + err = json.NewDecoder(resp.Body).Decode(&info) + if err != nil { + return err + } else if info.MTU <= minMTU { + return errors.New("small MTU") + } + h.ip, err = netip.ParsePrefix(info.IPPrefix) + if err != nil { + return err + } + h.buf = make([]byte, info.MTU) + hw, err := net.ParseMAC(info.HardwareAddr) + if err == nil { + copy(h.hwaddr[:], hw) + } + return nil +} + type HTTPTapClient struct { c http.Client + infoURL string recvurl string sendurl string + ip netip.Prefix + hwaddr [6]byte buf []byte } @@ -41,6 +94,10 @@ func (h *HTTPTapClient) ReadDiscard() { } func (h *HTTPTapClient) Read(b []byte) (int, error) { + err := h.ensureMTU() + if err != nil { + return 0, err + } resp, err := h.c.Get(h.recvurl) if err != nil { return 0, err @@ -59,6 +116,10 @@ func (h *HTTPTapClient) Read(b []byte) (int, error) { } func (h *HTTPTapClient) Write(b []byte) (int, error) { + err := h.ensureMTU() + if err != nil { + return 0, err + } data, _ := json.Marshal(b) resp, err := h.c.Post(h.sendurl, "application/json", bytes.NewReader(data)) if err != nil { @@ -79,7 +140,16 @@ type HTTPTapServer struct { tapfailed bool } +type TapInfo struct { + MTU int + IPPrefix string + HardwareAddr string +} + func NewHTTPTapServer(iface string, ip netip.Prefix, mtu, queueOut, queueIn int) (*HTTPTapServer, error) { + if mtu < minMTU { + return nil, errors.New("too small MTU") + } tap, err := internal.NewTap(iface, ip) if err != nil { return nil, err @@ -113,6 +183,18 @@ func NewHTTPTapServer(iface string, ip netip.Prefix, mtu, queueOut, queueIn int) json.NewEncoder(w).Encode("") // send empty string. } }) + ipstr := ip.String() + sv.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) { + info := TapInfo{ + MTU: mtu, + IPPrefix: ipstr, + } + hw, err := tap.HardwareAddress6() + if err == nil { + info.HardwareAddr = net.HardwareAddr(hw[:]).String() + } + json.NewEncoder(w).Encode(info) + }) taps := HTTPTapServer{ router: sv, stack: s, @@ -122,6 +204,10 @@ func NewHTTPTapServer(iface string, ip netip.Prefix, mtu, queueOut, queueIn int) return &taps, nil } +func (sv *HTTPTapServer) HardwareAddress6() (hwaddr [6]byte, err error) { + return sv.tap.HardwareAddress6() +} + func (sv *HTTPTapServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { sv.router.ServeHTTP(w, r) } diff --git a/internal/tap.go b/internal/tap.go index f0e71c9..d82ddae 100644 --- a/internal/tap.go +++ b/internal/tap.go @@ -13,7 +13,7 @@ import ( ) type Tap struct { - fd int + fd int // points to /dev/net/tun device. name string } @@ -25,19 +25,19 @@ func NewTap(name string, ip netip.Prefix) (*Tap, error) { if err != nil { return nil, fmt.Errorf("failed to open tun device: %w", err) } - - var ifr [syscall.IFNAMSIZ + 64]byte // extra space for compatibility - - // Set the name; it will be zero-padded automatically. - copy(ifr[:syscall.IFNAMSIZ-1], name) + tap := Tap{ + name: name, + fd: fd, + } + ifr := tap.ifreq() // Set the flags (starting at offset IFNAMSIZ). flags := uint16(syscall.IFF_TAP | syscall.IFF_NO_PI) - *(*uint16)(unsafe.Pointer(&ifr[syscall.IFNAMSIZ])) = flags + ifr.setflags(flags) // Issue the ioctl to create the interface. - _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TUNSETIFF), uintptr(unsafe.Pointer(&ifr[0]))) - if errno != 0 { - return nil, fmt.Errorf("creating tap interface: %w", errno) + err = ioctl(fd, syscall.TUNSETIFF, ifr.ptr()) + if err != nil { + return nil, fmt.Errorf("creating tap interface: %w", err) } if ip.IsValid() { // Optionally, bring the interface up and assign an IP address. @@ -66,3 +66,50 @@ func (tap *Tap) Write(b []byte) (int, error) { func (tap *Tap) Close() error { return syscall.Close(tap.fd) } + +func ioctl(fd int, request uintptr, argp unsafe.Pointer) error { + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), request, uintptr(argp)) + if errno != 0 { + return os.NewSyscallError("ioctl", errno) + } + 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) + if err != nil { + return hw, fmt.Errorf("socket open: %w", err) + } + defer syscall.Close(sock) + ifr := tap.ifreq() + + err = ioctl(sock, syscall.SIOCGIFHWADDR, ifr.ptr()) + if err != nil { + return hw, err + } + sa_family := *(*uint16)(unsafe.Pointer(&ifr.Data[0])) + if sa_family != 1 { + 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 +} + +type ifreq struct { + Name [syscall.IFNAMSIZ]byte + Data [64]byte // union data (covers ifr_hwaddr, etc.) +} + +func (ifr *ifreq) setflags(flags uint16) { + *(*uint16)(unsafe.Pointer(&ifr.Data[0])) = flags +} + +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 +} diff --git a/internet/tcpconn.go b/internet/tcpconn.go index 6bb0c8a..7904d43 100644 --- a/internet/tcpconn.go +++ b/internet/tcpconn.go @@ -30,6 +30,7 @@ type TCPConn struct { lastTx time.Time lastRx time.Time + ipID uint16 abortErr error logger } @@ -102,7 +103,7 @@ func (conn *TCPConn) RecvIP(buf []byte, off int) (err error) { if off >= len(buf) { return errors.New("bad offset in TCPConn.Recv") } - raddr, err := getIPAddr(buf[:off]) + raddr, id, err := getIPAddr(buf[:off]) if err != nil { return err } @@ -115,6 +116,7 @@ func (conn *TCPConn) RecvIP(buf []byte, off int) (err error) { } if !conn.isRaddrSet() && conn.h.RemotePort() != 0 { conn.remoteAddr = append(conn.remoteAddr[:0], raddr...) + conn.ipID = ^(id - 1) } return nil } @@ -200,7 +202,7 @@ func (conn *TCPConn) HandleIP(buf []byte, off int) (n int, err error) { if len(conn.remoteAddr) == 0 { return 0, errors.New("unset IP address") } - raddr, err := getIPAddr(buf[:off]) + raddr, _, err := getIPAddr(buf[:off]) if err != nil { return 0, err } else if len(raddr) != len(conn.remoteAddr) { @@ -211,10 +213,11 @@ func (conn *TCPConn) HandleIP(buf []byte, off int) (n int, err error) { return 0, err } - err = setDstAddr(buf[:off], conn.remoteAddr) + err = setDstAddr(buf[:off], conn.ipID, conn.remoteAddr) if err != nil { return 0, err } + conn.ipID++ return n, nil } @@ -223,27 +226,28 @@ func (conn *TCPConn) Send(response []byte) (n int, err error) { return conn.h.Send(response) } -func getIPAddr(buf []byte) (addr []byte, err error) { +func getIPAddr(buf []byte) (addr []byte, id uint16, err error) { switch buf[0] >> 4 { case 4: ifrm4, err := ipv4.NewFrame(buf) if err != nil { - return addr, err + return addr, 0, err } addr = ifrm4.SourceAddr()[:] + id = ifrm4.ID() case 6: ifrm6, err := ipv6.NewFrame(buf) if err != nil { - return addr, err + return addr, 0, err } addr = ifrm6.SourceAddr()[:] default: err = errors.New("unsupported IP version") } - return addr, err + return addr, id, err } -func setDstAddr(buf []byte, addr []byte) (err error) { +func setDstAddr(buf []byte, id uint16, addr []byte) (err error) { var dstaddr []byte switch buf[0] >> 4 { case 4: @@ -252,6 +256,7 @@ func setDstAddr(buf []byte, addr []byte) (err error) { return err } dstaddr = ifrm4.DestinationAddr()[:] + ifrm4.SetID(id) case 6: ifrm6, err := ipv6.NewFrame(buf) if err != nil {