netdev

package
v1.1.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

README

netdev — host Netdever for TinyGo

TinyGo’s net / net/http packages do not talk to the OS directly. They call a Netdever registered with UseNetdev. On microcontrollers that is usually Wi-Fi firmware (WiFiNINA, etc.). On a desktop host there is no default driver, which produces:

Netdev not set

This package implements a Netdever on Linux / macOS / Windows using the host TCP/IP stack, compatible with the interface in tinygo-org/drivers/netdev.

Usage

Blank-import the package before any net / net/http use (init registers the driver under TinyGo):

package main

import (
	"net/http"

	_ "github.com/shibukawa/tinygodriver/netdev"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("ok"))
	})
	http.ListenAndServe(":8080", nil)
}

Or register explicitly:

import "github.com/shibukawa/tinygodriver/netdev"

func main() {
	netdev.Use(netdev.New())
	// ...
}

Build

With TinyGo:

tinygo build -o server ./examples/httpserver
./server

Standard go build / go run also work: the driver is a no-op registrar because the stock net package does not use netdev.

go run ./examples/httpserver

Notes

  • IPv4 only (matches TinyGo’s net port).

  • Unix domain sockets (unix, unixgram, unixpacket) are not supported and will not be. TinyGo’s net rejects those networks before a driver is consulted, and the Netdever interface addresses sockets as netip.AddrPort, which cannot carry a filesystem path. Use tcp4 on 127.0.0.1 for local IPC.

  • Listening on port 0 works, but net.Listener.Addr() still reports port 0. The Netdever Bind signature returns only an error, so the port the OS picked cannot be handed back to TinyGo’s net. Read it from the driver instead:

    d := netdev.New()
    netdev.Use(d)
    fd, _ := d.Socket(netdev.AF_INET, netdev.SOCK_STREAM, netdev.IPPROTO_TCP)
    d.Bind(fd, netip.MustParseAddrPort("127.0.0.1:0"))
    laddr, _ := d.LocalAddr(fd) // 127.0.0.1:54321
    

    A full fix needs an upstream change in TinyGo’s listenTCP.

  • Socket errors are reported as the shared classes in netdev.Err* (ErrConnRefused, ErrAddrNotAvailable, …), so errors.Is works the same on Linux, macOS, and Windows.

  • IPPROTO_TLS uses Secure Transport on macOS, so a binary needs no Homebrew and no OpenSSL. Peer certificates and hostnames are verified against the system keychain; set SSL_CERT_FILE to add a private CA, which is read in Go and passed in as an extra anchor.

    Secure Transport is the only OS-provided option that fits this seam: it hands over an already connected descriptor, and an nw_connection owns DNS, TCP and TLS as one unit and cannot adopt one. The cost is that Secure Transport stops at TLS 1.2; the previous OpenSSL implementation negotiated 1.3.

  • Windows has two socket backends. Host Go uses a pure-Go one built on syscall plus a few ws2_32 entry points, so go build needs no C compiler; that matters because the blank import is a no-op under host Go and used to break builds that never touched this package. TinyGo has no windows syscall package, so it keeps the cgo backend and still needs mingw-w64.

  • IPPROTO_TLS uses Schannel on Windows, reached through SSPI. It ships with the OS, so there is nothing to install here either, and it reaches TLS 1.3 where the OS supports it. Peer certificates and hostnames are verified against the Windows certificate store; SSL_CERT_FILE adds a private CA the same way it does on macOS.

    TLS is the one part that does need cgo. A cgo-free host-Go build keeps full socket support and returns ErrProtocolNotSupported for IPPROTO_TLS.

  • IPPROTO_TLS uses crypto/tls on host-Go Linux. Trust anchors come from x509.SystemCertPool, which honors SSL_CERT_FILE and SSL_CERT_DIR. TinyGo Linux cannot safely call the distribution's shared OpenSSL, because it bypasses glibc process initialization, so it returns ErrProtocolNotSupported for IPPROTO_TLS.

    Linux is the only platform where the host-Go TLS backend is not also the one a TinyGo binary runs. That is exactly why the standard library is the right answer here and the wrong one on macOS and Windows: an OpenSSL adapter made libssl a dependency of every Linux build in order to serve a path that never ships.

  • No package manager is needed anywhere. No platform requires OpenSSL, at build time or at run time.

  • DNS uses /etc/hosts (or the Windows hosts file) plus a simple UDP A-record resolver. Nameservers come from /etc/resolv.conf, falling back to 8.8.8.8 when none is found.

  • Name resolution asks the OS first, so the DNS suffix search list and any domain-scoped resolvers a VPN installs both apply. The lookup order is: localhost, the hosts file, NETDEV_DNS, the system resolver, then the built-in UDP query.

    Build System resolver
    host Go, any OS net.LookupHost
    Windows, cgo (incl. TinyGo) getaddrinfo via ws2_32
    TinyGo on macOS and Linux none, falls through to the UDP query

    TinyGo cannot reach a system resolver on macOS or Linux, and this is a linker limitation rather than a choice: getaddrinfo is absent from TinyGo's macos-minimal-sdk libSystem stubs, and TinyGo's Linux linker does not expose the libc socket stubs either — which is why sys_linux.go issues raw syscalls instead of calling libc. A link failure cannot be recovered from at run time, so attempting it would break builds that currently work. On those two builds an unqualified short name will not resolve; use NETDEV_DNS or the hosts file.

  • NETDEV_DNS overrides which resolvers are used, on every platform:

    NETDEV_DNS=10.0.0.53
    NETDEV_DNS=10.0.0.53,10.0.0.54:5353   # comma or space separated, :port optional
    

    It replaces the discovered list outright rather than being appended to it, so a resolver that cannot answer internal names is not tried first. Unparseable and non-IPv4 entries are skipped rather than failing the lookup.

    This exists because what the package can discover is not always what the machine actually uses:

    • Windows has no /etc/resolv.conf at all, so without an override every lookup goes to 8.8.8.8 and no internal or split-horizon name can ever resolve.
    • macOS /etc/resolv.conf is a legacy file the OS documents as not consulted — it says so in its own header, and directs you to scutil --dns. It happens to carry the right servers on a simple network, but it does not represent the domain-scoped resolvers a VPN installs.
    • Linux reads the real thing and is generally correct.

    Two further limits apply everywhere: search and domain lines are ignored, so an unqualified short name will not resolve, and a resolver list with only IPv6 entries is treated as empty because this package is IPv4-only.

    The hosts file is read before any resolver, so an entry in /etc/hosts or %SystemRoot%\System32\drivers\etc\hosts also works.

  • Multiple goroutines may call socket methods concurrently; the driver serializes bookkeeping and relies on OS sockets for I/O.

Documentation

Overview

Package netdev implements a host OS Netdever for TinyGo.

TinyGo's net package routes all networking through a Netdever registered via UseNetdev. Embedded Wi-Fi drivers fill that role on microcontrollers; this package fills it on Linux, macOS, and Windows so the same net/http code can run under TinyGo on a desktop host.

Compatible with the Netdever interface described by https://github.com/tinygo-org/drivers/tree/dev/netdev

Usage:

import _ "github.com/shibukawa/tinygodriver/netdev"

The blank import registers the host driver during init.

Index

Constants

View Source
const (
	AF_INET       = 0x2
	SOCK_STREAM   = 0x1
	SOCK_DGRAM    = 0x2
	SOL_SOCKET    = 0x1
	SO_KEEPALIVE  = 0x9
	SO_LINGER     = 0xd
	SOL_TCP       = 0x6
	TCP_KEEPINTVL = 0x5
	IPPROTO_TCP   = 0x6
	IPPROTO_UDP   = 0x11
	// Made up; TLS is implemented by the host netdev on desktop targets.
	IPPROTO_TLS = 0xFE
	F_SETFL     = 0x4
)

BSD-socket style constants mirrored from tinygo-org/drivers/netdev. Values match the abstract constants TinyGo's net package passes to drivers (not always the host OS numeric values).

View Source
const NetdevDNSEnv = "NETDEV_DNS"

NetdevDNSEnv names the environment variable that overrides which resolvers are used. The value is a comma or space separated list of IPv4 addresses, each with an optional :port that defaults to 53:

NETDEV_DNS=10.0.0.53
NETDEV_DNS=10.0.0.53,10.0.0.54:5353

It exists because the configuration this package can discover on its own is not always the configuration the machine actually uses. On windows there is no /etc/resolv.conf at all, so without this every lookup went to 8.8.8.8 and no internal name could ever resolve. On macOS /etc/resolv.conf is a legacy file that the OS itself documents as not consulted, so it misses the domain-scoped resolvers a VPN installs.

Variables

View Source
var (
	ErrHostUnknown          = errors.New("Host unknown")
	ErrMalAddr              = errors.New("Malformed address")
	ErrFamilyNotSupported   = errors.New("Address family not supported")
	ErrProtocolNotSupported = errors.New("Socket protocol/type not supported")
	ErrNoMoreSockets        = errors.New("No more sockets")
	ErrClosingSocket        = errors.New("Error closing socket")
	ErrNotSupported         = errors.New("Not supported")
	ErrInvalidSocketFd      = errors.New("Invalid socket fd")
	ErrTimeout              = &timeoutError{}
)

Errors aligned with tinygo-org/drivers/netdev.

View Source
var (
	ErrAddrNotAvailable = errors.New("can't assign requested address")
	ErrAddrInUse        = errors.New("address already in use")
	ErrConnRefused      = errors.New("connection refused")
	ErrConnReset        = errors.New("connection reset by peer")
	ErrNotConnected     = errors.New("socket is not connected")
	ErrConnTimedOut     = errors.New("connection timed out")
	ErrWouldBlock       = errors.New("resource temporarily unavailable")
	ErrSyscall          = errors.New("syscall error")
)

Socket error classes shared by the three backends. Each platform maps its native code onto one of these, so application and test code can branch with errors.Is regardless of the OS. Messages match the standard Go wording.

Functions

func Use

func Use(d *Device)

Use registers d as the process-wide TinyGo netdev.

Types

type Device

type Device struct {
	// contains filtered or unexported fields
}

Device is a host OS implementation of Netdever.

func New

func New() *Device

New returns a host netdev driver. Prefer Use or the blank import.

func (*Device) Accept

func (d *Device) Accept(sockfd int) (int, netip.AddrPort, error)

func (*Device) Addr

func (d *Device) Addr() (netip.Addr, error)

func (*Device) Bind

func (d *Device) Bind(sockfd int, ip netip.AddrPort) error

func (*Device) Close

func (d *Device) Close(sockfd int) error

func (*Device) Connect

func (d *Device) Connect(sockfd int, host string, ip netip.AddrPort) error

func (*Device) GetHostByName

func (d *Device) GetHostByName(name string) (netip.Addr, error)

func (*Device) Listen

func (d *Device) Listen(sockfd int, backlog int) error

func (*Device) LocalAddr added in v1.0.5

func (d *Device) LocalAddr(sockfd int) (netip.AddrPort, error)

LocalAddr returns the address the OS assigned to sockfd, including the port chosen for a bind on port 0.

This is an extension beyond the Netdever interface. TinyGo's net.Listener keeps its own copy of the requested address and has no way to receive this value, so net.Listener.Addr() still reports port 0; see requirement:netdev-bound-port.

func (*Device) Recv

func (d *Device) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error)

func (*Device) Send

func (d *Device) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error)

func (*Device) SetSockOpt

func (d *Device) SetSockOpt(sockfd int, level int, opt int, value interface{}) error

func (*Device) Socket

func (d *Device) Socket(domain int, stype int, protocol int) (int, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL