Add network device driver model, netdev

This PR adds a network device driver model called netdev. There will be a companion PR for TinyGo drivers to update the netdev drivers and network examples. This PR covers the core "net" package.

An RFC for the work is here: #tinygo-org/drivers#487. Some things have changed from the RFC, but nothing major.

The "net" package is a partial port of Go's "net" package, version 1.19.3. The src/net/README file has details on what is modified from Go's "net" package.

Most "net" features are working as they would in normal Go. TCP/UDP/TLS protocol support is there. As well as HTTP client and server support. Standard Go network packages such as golang.org/x/net/websockets and Paho MQTT client work as-is. Other packages are likely to work as-is.

Testing results are here (https://docs.google.com/spreadsheets/d/e/2PACX-1vT0cCjBvwXf9HJf6aJV2Sw198F2ief02gmbMV0sQocKT4y4RpfKv3dh6Jyew8lQW64FouZ8GwA2yjxI/pubhtml?gid=1013173032&single=true).
This commit is contained in:
Scott Feldman
2023-03-27 14:15:21 -07:00
committed by deadprogram
parent b46e2ec2ac
commit 693edae782
39 changed files with 11271 additions and 1092 deletions
+211
View File
@@ -1,8 +1,18 @@
// TINYGO: The following is copied and modified from Go 1.19.3 official implementation.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package net
import (
"fmt"
"internal/itoa"
"net/netip"
"strconv"
"syscall"
"time"
)
// UDPAddr represents the address of a UDP end point.
@@ -53,3 +63,204 @@ func (a *UDPAddr) opAddr() Addr {
}
return a
}
// ResolveUDPAddr returns an address of UDP end point.
//
// The network must be a UDP network name.
//
// If the host in the address parameter is not a literal IP address or
// the port is not a literal port number, ResolveUDPAddr resolves the
// address to an address of UDP end point.
// Otherwise, it parses the address as a pair of literal IP address
// and port number.
// The address parameter can use a host name, but this is not
// recommended, because it will return at most one of the host name's
// IP addresses.
//
// See func Dial for a description of the network and address
// parameters.
func ResolveUDPAddr(network, address string) (*UDPAddr, error) {
switch network {
case "udp", "udp4":
default:
return nil, fmt.Errorf("Network '%s' not supported", network)
}
// TINYGO: Use netdev resolver
host, sport, err := SplitHostPort(address)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(sport)
if err != nil {
return nil, fmt.Errorf("Error parsing port '%s' in address: %s",
sport, err)
}
if host == "" {
return &UDPAddr{Port: port}, nil
}
ip, err := netdev.GetHostByName(host)
if err != nil {
return nil, fmt.Errorf("Lookup of host name '%s' failed: %s", host, err)
}
return &UDPAddr{IP: ip, Port: port}, nil
}
// UDPConn is the implementation of the Conn and PacketConn interfaces
// for UDP network connections.
type UDPConn struct {
fd int
laddr *UDPAddr
raddr *UDPAddr
readDeadline time.Time
writeDeadline time.Time
}
// Use IANA RFC 6335 port range 4915265535 for ephemeral (dynamic) ports
var eport = int32(49151)
func ephemeralPort() int {
// TODO: this is racy, if concurrent DialUDPs; use atomic?
if eport == int32(65535) {
eport = int32(49151)
} else {
eport++
}
return int(eport)
}
// DialUDP acts like Dial for UDP networks.
//
// The network must be a UDP network name; see func Dial for details.
//
// If laddr is nil, a local address is automatically chosen.
// If the IP field of raddr is nil or an unspecified IP address, the
// local system is assumed.
func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error) {
switch network {
case "udp", "udp4":
default:
return nil, fmt.Errorf("Network '%s' not supported", network)
}
// TINYGO: Use netdev to create UDP socket and connect
if laddr == nil {
laddr = &UDPAddr{}
}
if raddr == nil {
raddr = &UDPAddr{}
}
if raddr.IP.IsUnspecified() {
return nil, fmt.Errorf("Sorry, localhost isn't available on Tinygo")
}
// If no port was given, grab an ephemeral port
if laddr.Port == 0 {
laddr.Port = ephemeralPort()
}
fd, err := netdev.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP)
if err != nil {
return nil, err
}
// Local bind
err = netdev.Bind(fd, laddr.IP, laddr.Port)
if err != nil {
netdev.Close(fd)
return nil, err
}
// Remote connect
if err = netdev.Connect(fd, "", raddr.IP, raddr.Port); err != nil {
netdev.Close(fd)
return nil, err
}
return &UDPConn{
fd: fd,
laddr: laddr,
raddr: raddr,
}, nil
}
// TINYGO: Use netdev for Conn methods: Read = Recv, Write = Send, etc.
func (c *UDPConn) Read(b []byte) (int, error) {
var timeout time.Duration
now := time.Now()
if !c.readDeadline.IsZero() {
if c.readDeadline.Before(now) {
return 0, fmt.Errorf("Read deadline expired")
} else {
timeout = c.readDeadline.Sub(now)
}
}
n, err := netdev.Recv(c.fd, b, 0, timeout)
// Turn the -1 socket error into 0 and let err speak for error
if n < 0 {
n = 0
}
return n, err
}
func (c *UDPConn) Write(b []byte) (int, error) {
var timeout time.Duration
now := time.Now()
if !c.writeDeadline.IsZero() {
if c.writeDeadline.Before(now) {
return 0, fmt.Errorf("Write deadline expired")
} else {
timeout = c.writeDeadline.Sub(now)
}
}
n, err := netdev.Send(c.fd, b, 0, timeout)
// Turn the -1 socket error into 0 and let err speak for error
if n < 0 {
n = 0
}
return n, err
}
func (c *UDPConn) Close() error {
return netdev.Close(c.fd)
}
func (c *UDPConn) LocalAddr() Addr {
return c.laddr
}
func (c *UDPConn) RemoteAddr() Addr {
return c.raddr
}
func (c *UDPConn) SetDeadline(t time.Time) error {
c.readDeadline = t
c.writeDeadline = t
return nil
}
func (c *UDPConn) SetReadDeadline(t time.Time) error {
c.readDeadline = t
return nil
}
func (c *UDPConn) SetWriteDeadline(t time.Time) error {
c.writeDeadline = t
return nil
}