diff --git a/netif/example_test.go b/netif/example_test.go new file mode 100644 index 000000000..63c06379c --- /dev/null +++ b/netif/example_test.go @@ -0,0 +1,97 @@ +package netif + +import ( + "net" + "time" +) + +func AutoProbe(timeout time.Duration) (Stack, error) { + // This function automatically gets the first available network device + // and returns a stack for it. + // It is intended to be used by the user as a convenience function. + // Will be guarded by build tags and specific for whether the device + // is a StackWifi, InterfaceEthPollWifi or InterfaceEthPoller. + return nil, nil +} + +func ProbeStackWifi() StackWifi { + return nil +} +func ProbeEthPollWifi() InterfaceEthPollWifi { + return nil +} +func ProbeEthPoller() InterfaceEthPoller { + return nil +} + +func StackForEthPoll(dev InterfaceEthPoller) Stack { + // Use seqs to generate a stack. + return nil +} + +// OSI layer 4 enabled WIFI chip. i.e.: ESP32 +func ExampleProbeStackWifi() { + dev := ProbeStackWifi() + wifiparams := WifiParams{ + SSID: "myssid", + ConnectMode: ConnectModeSTA, + Passphrase: "mypassphrase", + Auth: AuthTypeWPA2, + CountryCode: "US", + } + err := StartWifiAutoconnect(dev, WifiAutoconnectParams{ + WifiParams: wifiparams, + }) + if err != nil { + panic(err) + } + for dev.NetFlags()&net.FlagRunning == 0 { + time.Sleep(100 * time.Millisecond) + } + UseStack(dev) + net.Dial("tcp", "192.168.1.1:33") +} + +// Simplest case, OSI layer 2 enabled WIFI chip, i.e: CYW43439 +func ExampleProbeEthPollWifi() { + dev := ProbeEthPollWifi() + wifiparams := WifiParams{ + SSID: "myssid", + ConnectMode: ConnectModeSTA, + Passphrase: "mypassphrase", + Auth: AuthTypeWPA2, + CountryCode: "US", + } + err := StartWifiAutoconnect(dev, WifiAutoconnectParams{ + WifiParams: wifiparams, + }) + if err != nil { + panic(err) + } + for dev.NetFlags()&net.FlagRunning == 0 { + time.Sleep(100 * time.Millisecond) + } + stack := StackForEthPoll(dev) + UseStack(stack) + net.Dial("tcp", "192.168.1.1:33") +} + +// OSI level 2 chip with wired connection, i.e. ENC28J60. +func ExampleProbeEthPoller() { + dev := ProbeEthPoller() + if dev.NetFlags()&net.FlagRunning == 0 { + panic("ethernet not connected") + } + stack := StackForEthPoll(dev) + UseStack(stack) + net.Dial("tcp", "192.168.1.1:33") +} + +func ExampleAutoProbe() { + stack, err := AutoProbe(10 * time.Second) + if err != nil { + panic(err) + } + UseStack(stack) + net.Dial("tcp", "192.168.1.1:33") +} diff --git a/netif/netif.go b/netif/netif.go new file mode 100644 index 000000000..e931a63d6 --- /dev/null +++ b/netif/netif.go @@ -0,0 +1,211 @@ +package netif + +import ( + "context" + "errors" + "net" + "net/netip" + "time" + _ "unsafe" +) + +//go:linkname UseStack net.useNetdev +func UseStack(stack Stack) + +// Socket errors. +var ( + ErrProtocolNotSupported = errors.New("socket protocol/type not supported") +) + +// Wifi errors. +var ( + ErrAuthFailure = errors.New("wifi authentication failure") + ErrAuthUnsupported = errors.New("wifi authorization type not supported") +) + +const ( + _AF_INET = 0x2 + _SOCK_STREAM = 0x1 + _SOCK_DGRAM = 0x2 + _SOL_SOCKET = 0x1 + _SO_KEEPALIVE = 0x9 + _SOL_TCP = 0x6 + _TCP_KEEPINTVL = 0x5 + _IPPROTO_TCP = 0x6 + _IPPROTO_UDP = 0x11 + // Made up, not a real IP protocol number. This is used to create a + // TLS socket on the device, assuming the device supports mbed TLS. + _IPPROTO_TLS = 0xFE + _F_SETFL = 0x4 +) + +// Interface is the minimum interface that need be implemented by any network +// device driver and is based on [net.Interface]. +type Interface interface { + // HardwareAddr6 returns the device's 6-byte [MAC address]. + // + // [MAC address]: https://en.wikipedia.org/wiki/MAC_address + HardwareAddr6() ([6]byte, error) + // NetFlags returns the net.Flag values for the interface. It includes state of connection. + NetFlags() net.Flags + // MTU returns the maximum transmission unit size. + MTU() int + // Notify to register callback for network events. May not be supported for certain devices. + NetNotify(cb func(Event)) error +} + +// InterfaceEthPoller is implemented by devices that send/receive ethernet packets. +type InterfaceEthPoller interface { + Interface + // SendEth sends an Ethernet packet + SendEth(pkt []byte) error + // RecvEthHandle sets recieve Ethernet packet callback function + RecvEthHandle(func(pkt []byte) error) + // PollOne tries to receive one Ethernet packet and returns true if one was + PollOne() (bool, error) +} + +// InterfaceWifi is implemented by a interface device that has the capacity +// to connect to wifi networks. +type InterfaceWifi interface { + Interface + // Connect device to network + NetConnect(params WifiParams) error + // Disconnect device from network + NetDisconnect() +} + +// InterfaceEthPollWifi is implemented by devices that connect to wifi networks +// and send/receive ethernet packets (OSI level 2). +type InterfaceEthPollWifi interface { + InterfaceWifi + InterfaceEthPoller +} + +type Stack interface { + // GetHostByName returns the IP address of either a hostname or IPv4 + // address in standard dot notation + // GetHostByName(name string) (netip.Addr, error) + + // Addr returns IP address assigned to the interface, either by + // DHCP or statically + Addr() (netip.Addr, error) + + // Berkely Sockets-like interface, Go-ified. See man page for socket(2), etc. + Socket(domain int, stype int, protocol int) (int, error) + Bind(sockfd int, ip netip.AddrPort) error + Connect(sockfd int, host string, ip netip.AddrPort) error + Listen(sockfd int, backlog int) error + Accept(sockfd int) (int, netip.AddrPort, error) + Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) + Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) + Close(sockfd int) error + SetSockOpt(sockfd int, level int, opt int, value interface{}) error +} + +// Resolver is implemented by DNS resolvers, notably Go's [net.DefaultResolver]. +type Resolver interface { + // LookupNetIP looks up host using the local resolver. + // It returns a slice of that host's IP addresses of the type specified by + // network. + // The network must be one of "ip", "ip4" or "ip6". + LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error) +} + +// StackWifi is returned by `Probe` function for devices that communicate +// on the OSI level 4 (transport) layer. +type StackWifi interface { + InterfaceWifi + Stack +} + +type WifiParams struct { + // Connect mode + ConnectMode ConnectMode + + // SSID of Wifi AP + SSID string + + // Passphrase of Wifi AP + Passphrase string + + // Wifi authorization type + Auth AuthType + + // Wifi country code as two-char string. E.g. "XX" for world-wide, + // "US" for USA, etc. + CountryCode string +} + +type Event uint8 + +// Network events +const ( + // The device's network connection is now UP + EventNetUp Event = iota + // The device's network connection is now DOWN + EventNetDown +) + +type ConnectMode uint8 + +// Connect modes +const ( + ConnectModeSTA = iota // Connect as Wifi station (default) + ConnectModeAP // Connect as Wifi Access Point +) + +type AuthType uint8 + +// Wifi authorization types. Used when setting up an access point, or +// connecting to an access point +const ( + AuthTypeWPA2 = iota // WPA2 authorization (default) + AuthTypeOpen // No authorization required (open) + AuthTypeWPA // WPA authorization + AuthTypeWPA2Mixed // WPA2/WPA mixed authorization +) + +type WifiAutoconnectParams struct { + WifiParams + + // Retries is how many attempts to connect before returning with a + // "Connect failed" error. Zero means infinite retries. + // Retries int // Probably should be implemented as a function + + // Timeout duration for each connection attempt. The default zero + // value means 10sec. + ConnectTimeout time.Duration + + // Watchdog ticker duration. On tick, the watchdog will check for + // downed connection or hardware fault and try to recover the + // connection. Set to zero to disable watchodog. + WatchdogTimeout time.Duration +} + +func StartWifiAutoconnect(dev InterfaceWifi, cfg WifiAutoconnectParams) error { + if dev == nil { + return errors.New("nil device") + } + go func() { + // Wifi autoconnect algorithm in one place, + // no need to implement for every single netdever. + RECONNECT: + for i := 0; i < 4; i++ { + err := dev.NetConnect(cfg.WifiParams) + if err != nil { + time.Sleep(cfg.ConnectTimeout) + goto RECONNECT + } + // Once connected reset the retry counter. + i = 0 + for cfg.WatchdogTimeout != 0 { + time.Sleep(cfg.WatchdogTimeout) + if dev.NetFlags()&net.FlagRunning == 0 { + goto RECONNECT + } + } + } + }() + return nil +}