tinygo icon indicating copy to clipboard operation
tinygo copied to clipboard

compiler: gigacrash when compiling gvisor netstack

Open soypat opened this issue 4 months ago • 4 comments

Full log of tinygo build . attached

$ tinygo build .
SIGSEGV: segmentation violation
PC=0x635f4d5 m=10 sigcode=1 addr=0x40
signal arrived during cgo execution

When compiling following program

module noisy

go 1.24.2

require (
	github.com/noisysockets/netstack v0.9.0
	gvisor.dev/gvisor v0.0.0-20250819051428-5ba23728415e
)

require (
	github.com/google/btree v1.1.2 // indirect
	golang.org/x/sys v0.26.0 // indirect
	golang.org/x/time v0.7.0 // indirect
)

//go:build linux

// tapstack/main.go
package main

import (
	"crypto/rand"
	"encoding/binary"
	"flag"
	"fmt"
	"log"
	"net"
	"time"
	"unsafe"

	"golang.org/x/sys/unix"

	// Netstack (noisysockets fork)
	"github.com/noisysockets/netstack/pkg/tcpip"
	"github.com/noisysockets/netstack/pkg/tcpip/adapters/gonet"
	"github.com/noisysockets/netstack/pkg/tcpip/link/fdbased"
	"github.com/noisysockets/netstack/pkg/tcpip/network/arp"
	"github.com/noisysockets/netstack/pkg/tcpip/network/ipv4"
	"github.com/noisysockets/netstack/pkg/tcpip/network/ipv6"
	"github.com/noisysockets/netstack/pkg/tcpip/stack"
	"github.com/noisysockets/netstack/pkg/tcpip/transport/tcp"
)

const (
	nicID = tcpip.NICID(1)

	// from <linux/if_tun.h>
	cIFFTAP   = 0x0002
	cIFFNO_PI = 0x1000
)

type ifreq struct {
	Name  [unix.IFNAMSIZ]byte
	Flags uint16
	_     [24 - 2]byte // sizeof(struct ifreq) varies; this is enough for TUNSETIFF
}

func openTAP(name string) (int, error) {
	fd, err := unix.Open("/dev/net/tun", unix.O_RDWR|unix.O_NONBLOCK, 0)
	if err != nil {
		return -1, err
	}
	var req ifreq
	copy(req.Name[:], name)
	req.Flags = cIFFTAP | cIFFNO_PI
	// TUNSETIFF = 0x400454ca on most arch; use unix.IoctlSetInt is tricky here,
	// so call Ioctl with the struct pointer directly.
	const TUNSETIFF = 0x400454ca
	if _, _, e := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(TUNSETIFF), uintptr(unsafe.Pointer(&req))); e != 0 {
		unix.Close(fd)
		return -1, e
	}
	return fd, nil
}

func parseCIDR4(cidr string) ([4]byte, int) {
	ip, ipnet, err := net.ParseCIDR(cidr)
	if err != nil {
		log.Fatalf("invalid -cidr %q: %v", cidr, err)
	}
	ip = ip.To4()
	if ip == nil {
		log.Fatalf("-cidr must be IPv4")
	}
	prefix, _ := ipnet.Mask.Size()
	var ip4 [4]byte
	copy(ip4[:], ip)
	return ip4, prefix
}

func defaultSubnet() tcpip.Subnet {
	return (tcpip.AddressWithPrefix{
		Address:   tcpip.AddrFrom4([4]byte{}),
		PrefixLen: 0,
	}).Subnet()
}

func randLaaMAC() tcpip.LinkAddress {
	var b [6]byte
	if _, err := rand.Read(b[:]); err != nil {
		// fall back to time-based
		now := time.Now().UnixNano()
		binary.LittleEndian.PutUint64(append(b[:0], make([]byte, 8)...), uint64(now))
		copy(b[:], append(b[:0], byte(now), byte(now>>8), byte(now>>16), byte(now>>24), byte(now>>32), byte(now>>40)))
	}
	// Set locally-administered, unicast: set bit1, clear bit0 of first byte.
	b[0] = (b[0] | 0x02) &^ 0x01
	return tcpip.LinkAddress(b[:])
}

func main() {
	dev := flag.String("dev", "tap0", "TAP device name (e.g. tap0)")
	cidr := flag.String("cidr", "10.0.0.2/24", "IPv4 address/prefix for the stack (e.g. 10.0.0.2/24)")
	gw := flag.String("gw", "", "default gateway IPv4 (optional, e.g. 10.0.0.1)")
	port := flag.Int("port", 8080, "TCP port to listen on (echo server)")
	mtu := flag.Int("mtu", 1500, "interface MTU")
	macStr := flag.String("mac", "", "MAC address to use on TAP (optional; random LAA if empty)")
	flag.Parse()

	// Open TAP (no kernel IP header; raw Ethernet frames).
	fd, err := openTAP(*dev)
	if err != nil {
		log.Fatalf("openTAP(%s): %v", *dev, err)
	}

	// Choose MAC address.
	var linkAddr tcpip.LinkAddress
	if *macStr != "" {
		la, err := tcpip.ParseMACAddress(*macStr)
		if err != nil {
			log.Fatalf("parse MAC: %v", err)
		}
		linkAddr = la
	} else {
		linkAddr = randLaaMAC()
	}

	// Wire TAP into netstack as an Ethernet link endpoint.
	linkEP, err := fdbased.New(&fdbased.Options{
		FDs:            []int{fd},
		MTU:            uint32(*mtu),
		EthernetHeader: true,
		Address:        linkAddr,
	})
	if err != nil {
		log.Fatalf("fdbased.New: %v", err)
	}

	// Build the stack (ARP, IPv4/IPv6, TCP).
	s := stack.New(stack.Options{
		NetworkProtocols: []stack.NetworkProtocolFactory{
			arp.NewProtocol,
			ipv4.NewProtocol,
			ipv6.NewProtocol,
		},
		TransportProtocols: []stack.TransportProtocolFactory{
			tcp.NewProtocol,
		},
	})

	// Create NIC on the link endpoint.
	if err := s.CreateNIC(nicID, linkEP); err != nil {
		log.Fatalf("CreateNIC: %v", err)
	}

	// Assign IPv4 address.
	ip4, prefix := parseCIDR4(*cidr)
	addrWithPrefix := tcpip.AddressWithPrefix{
		Address:   tcpip.AddrFrom4(ip4),
		PrefixLen: prefix,
	}
	terr := s.AddProtocolAddress(nicID, tcpip.ProtocolAddress{
		Protocol:          ipv4.ProtocolNumber,
		AddressWithPrefix: addrWithPrefix,
	}, stack.AddressProperties{})
	if terr != nil {
		log.Fatalf("AddProtocolAddress: %v", terr)
	}

	// Routes: on-link subnet + optional default route via gw.
	routes := []tcpip.Route{
		{Destination: addrWithPrefix.Subnet(), NIC: nicID},
	}
	if *gw != "" {
		ip := net.ParseIP(*gw).To4()
		if ip == nil {
			log.Fatalf("-gw must be IPv4")
		}
		routes = append(routes, tcpip.Route{
			Destination: defaultSubnet(),
			Gateway:     tcpip.AddrFrom4([4]byte{ip[0], ip[1], ip[2], ip[3]}),
			NIC:         nicID,
		})
	}
	s.SetRouteTable(routes)

	// Listen on TCP <stack-ip>:port using the gonet adapter.
	laddr := tcpip.FullAddress{
		NIC:  nicID,
		Addr: addrWithPrefix.Address,
		Port: uint16(*port),
	}
	ln, err := gonet.ListenTCP(s, laddr, ipv4.ProtocolNumber)
	if err != nil {
		log.Fatalf("ListenTCP: %v", err)
	}
	defer ln.Close()

	fmt.Printf("netstack up on %s (MAC %s, MTU %d)\n", *dev, linkAddr.String(), *mtu)
	fmt.Printf("IPv4 %s, listening on TCP %d\n", addrWithPrefix.String(), *port)
	if *gw != "" {
		fmt.Printf("Default route via %s\n", *gw)
	}
	fmt.Println("Echo server ready.")

	for {
		c, err := ln.Accept()
		if err != nil {
			log.Fatalf("Accept: %v", err)
		}
		go func(conn net.Conn) {
			defer conn.Close()
			_ = conn.SetDeadline(time.Now().Add(5 * time.Minute))
			buf := make([]byte, 32<<10)
			for {
				n, err := conn.Read(buf)
				if n > 0 {
					_, _ = conn.Write(buf[:n]) // echo
				}
				if err != nil {
					return
				}
			}
		}(c)
	}
}

log.txt

soypat avatar Aug 19 '25 15:08 soypat

Can confirm: minimal main to reproduce

package main

import (
	"github.com/noisysockets/netstack/pkg/tcpip/link/fdbased"
)

type PacketDispatchMode int

type Options struct {
	PacketDispatchMode fdbased.PacketDispatchMode
}

func main() {
	_ = Options{}
}
``

However, "fdbased" is still really big and contains very lowlevel go code.

Removing "fdbased" doesn't crash any more

oflebbe avatar Oct 18 '25 20:10 oflebbe

package main

import (
	_ "github.com/noisysockets/netstack/pkg/tcpip/stack"
)

func main() {

}

oflebbe avatar Oct 18 '25 21:10 oflebbe

Now I see it uses "reflect" , this is clearly not supported by tinygo.

oflebbe avatar Oct 18 '25 21:10 oflebbe

stripped it down further now I get a different Crash

package main

import (
	_ "github.com/noisysockets/netstack/pkg/sync/locking"
)

func main() {

}

panic: interp: load out of bounds

goroutine 210 [running]: github.com/tinygo-org/tinygo/interp.(*memoryView).load(0xc00135a030, {0x0?}, 0x10) /home/olaf/Developer/oss/tinygo/interp/memory.go:316 +0x1a5 github.com/tinygo-org/tinygo/interp.(*runner).run(0xc0040a4000, 0xc0028f9c20, {0xc0018a02d0, 0x3, 0x0?}, 0xc00130b590, {0xc005f82500, 0x8}) /home/olaf/Developer/oss/tinygo/interp/interpreter.go:618 +0x1bb1 github.com/tinygo-org/tinygo/interp.(*runner).run(0xc0040a4000, 0xc0028f9bd0, {0x0, 0x0, 0xc003b0c968?}, 0x0, {0xc6312a, 0x4}) /home/olaf/Developer/oss/tinygo/interp/interpreter.go:573 +0x8406 github.com/tinygo-org/tinygo/interp.Run({0x27de2db?}, 0x7fff837b3c40?, 0x0?) /home/olaf/Developer/oss/tinygo/interp/interp.go:121 +0x756 github.com/tinygo-org/tinygo/builder.optimizeProgram({0x7fff80521ca8?}, 0xc0001dee80) /home/olaf/Developer/oss/tinygo/builder/build.go:1177 +0x32 github.com/tinygo-org/tinygo/builder.Build.func5(0xc003939c20?) /home/olaf/Developer/oss/tinygo/builder/build.go:611 +0x6ab github.com/tinygo-org/tinygo/builder.runJob(0xc003939d40, 0xc0006f0b60) /home/olaf/Developer/oss/tinygo/builder/jobs.go:212 +0x4d created by github.com/tinygo-org/tinygo/builder.runJobs in goroutine 1 /home/olaf/Developer/oss/tinygo/builder/jobs.go:113 +0x5b7

oflebbe avatar Oct 18 '25 21:10 oflebbe