From 731362a0cfeba850696ef7a0b8974fe722b48357 Mon Sep 17 00:00:00 2001 From: soypat Date: Mon, 18 Dec 2023 15:32:49 -0800 Subject: [PATCH 01/14] nets: new package with netdev redesign ideas --- nets/nets.go | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 nets/nets.go diff --git a/nets/nets.go b/nets/nets.go new file mode 100644 index 000000000..05944258c --- /dev/null +++ b/nets/nets.go @@ -0,0 +1,200 @@ +package nets + +import ( + "errors" + "net/netip" + "time" +) + +//go:linkname UseNetdev net.useNetdev +func UseNetdev(dev Stack) + +// GethostByName() errors +var ( + ErrHostUnknown = errors.New("host unknown") + ErrMalAddr = errors.New("malformed address") +) + +// Socket errors +var ( + ErrFamilyNotSupported = errors.New("address family not supported") + ErrProtocolNotSupported = errors.New("socket protocol/type not supported") + ErrStartingDHCPClient = errors.New("error starting DHPC client") + ErrNoMoreSockets = errors.New("no more sockets") + ErrClosingSocket = errors.New("error closing socket") +) + +var ( + ErrConnected = errors.New("already connected") + ErrConnectFailed = errors.New("connect failed") + ErrConnectTimeout = errors.New("connect timed out") + ErrMissingSSID = errors.New("missing WiFi SSID") + ErrAuthFailure = errors.New("wifi authentication failure") + ErrAuthTypeNoGood = errors.New("wifi authorization type not supported") + ErrConnectModeNoGood = errors.New("connect mode not supported") + ErrNotSupported = errors.New("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 +) + +// Link is the minimum interface that need be implemented by any network +// device driver. +type Link interface { + // HardwareAddr6 returns the device's 6-byte [MAC address], a.k.a EUI-48. + // + // [MAC address]: https://en.wikipedia.org/wiki/MAC_address + HardwareAddr6() ([6]byte, error) + LinkStatus() LinkStatus +} + +type LinkWifi interface { + Link + // Connect device to network + NetConnect(params WifiParams) error + // Disconnect device from network + NetDisconnect() + // Notify to register callback for network events + NetNotify(cb func(Event)) +} + +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, ip netip.AddrPort) (int, 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 +} + +// Netdev is returned by `Probe` function. +type Netdev interface { + LinkWifi + 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 int + +// 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 int + +// Connect modes +const ( + ConnectModeSTA = iota // Connect as Wifi station (default) + ConnectModeAP // Connect as Wifi Access Point +) + +type AuthType int + +// 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 LinkStatus uint8 + +const ( + LinkDown LinkStatus = iota + LinkConnecting + LinkUp +) + +type WifiAutoconnectParams struct { + WifiParams + // NOTE ON FIELDS BELOW: These fields seem like a leaky abstraction- they can + // be implemented as an external "AutoConnect" function that is called in + // a goroutine that performs auto connect and whatnot. + + // 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 Netdev, cfg WifiAutoconnectParams) error { + if dev == nil { + return ErrConnectModeNoGood + } + go func() { + // Wifi autoconnect algorithm in one place, + // no need to implement for every single netdever. + RECONNECT: + for i := 0; i < cfg.Retries; i++ { + err := dev.NetConnect(cfg.WifiParams) + if err != nil { + time.Sleep(cfg.ConnectTimeout) + goto RECONNECT + } + for cfg.WatchdogTimeout != 0 { + time.Sleep(cfg.WatchdogTimeout) + if dev.LinkStatus() == LinkDown { + i = 0 + goto RECONNECT + } + } + } + }() + return nil +} From 60cdb7fd50fea930383edc46dcfcfc1f55b3f220 Mon Sep 17 00:00:00 2001 From: soypat Date: Mon, 18 Dec 2023 15:56:35 -0800 Subject: [PATCH 02/14] add EthLinkPoller; --- nets/nets.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/nets/nets.go b/nets/nets.go index 05944258c..32cfaaf60 100644 --- a/nets/nets.go +++ b/nets/nets.go @@ -6,8 +6,8 @@ import ( "time" ) -//go:linkname UseNetdev net.useNetdev -func UseNetdev(dev Stack) +//go:linkname UseStack net.useNetdev +func UseStack(stack Stack) // GethostByName() errors var ( @@ -61,6 +61,16 @@ type Link interface { LinkStatus() LinkStatus } +type EthLinkPoller interface { + Link + // 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) +} + type LinkWifi interface { Link // Connect device to network From 7e45551ff52847adbfa0852f87bb7f53ff127e3f Mon Sep 17 00:00:00 2001 From: soypat Date: Mon, 18 Dec 2023 16:01:17 -0800 Subject: [PATCH 03/14] add more methods to Link interface --- nets/nets.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nets/nets.go b/nets/nets.go index 32cfaaf60..140c2f1a2 100644 --- a/nets/nets.go +++ b/nets/nets.go @@ -58,7 +58,10 @@ type Link interface { // // [MAC address]: https://en.wikipedia.org/wiki/MAC_address HardwareAddr6() ([6]byte, error) + // LinkStatus returns the state of the connection. LinkStatus() LinkStatus + // MTU returns the maximum transmission unit size. + MTU() int } type EthLinkPoller interface { From 570e3d2b71555cc53f8a4fe4b32d97e5708ce8ac Mon Sep 17 00:00:00 2001 From: soypat Date: Mon, 18 Dec 2023 16:01:59 -0800 Subject: [PATCH 04/14] change EthLinkPoller name --- nets/nets.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nets/nets.go b/nets/nets.go index 140c2f1a2..a3149173e 100644 --- a/nets/nets.go +++ b/nets/nets.go @@ -64,7 +64,7 @@ type Link interface { MTU() int } -type EthLinkPoller interface { +type EthPollerLink interface { Link // SendEth sends an Ethernet packet SendEth(pkt []byte) error From b4f43b4d21107b4c66bb75c82e286244504d9187 Mon Sep 17 00:00:00 2001 From: soypat Date: Mon, 18 Dec 2023 16:02:55 -0800 Subject: [PATCH 05/14] remove old comment --- nets/nets.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/nets/nets.go b/nets/nets.go index a3149173e..ab8bd7d48 100644 --- a/nets/nets.go +++ b/nets/nets.go @@ -168,9 +168,6 @@ const ( type WifiAutoconnectParams struct { WifiParams - // NOTE ON FIELDS BELOW: These fields seem like a leaky abstraction- they can - // be implemented as an external "AutoConnect" function that is called in - // a goroutine that performs auto connect and whatnot. // Retries is how many attempts to connect before returning with a // "Connect failed" error. Zero means infinite retries. From 728bdbba5b6bfebe27789efdee43be4041161900 Mon Sep 17 00:00:00 2001 From: soypat Date: Tue, 19 Dec 2023 23:07:20 -0300 Subject: [PATCH 06/14] apply some of @scottfeldman's suggestions --- nets/nets.go | 79 ++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/nets/nets.go b/nets/nets.go index ab8bd7d48..19cddafb0 100644 --- a/nets/nets.go +++ b/nets/nets.go @@ -2,12 +2,13 @@ package nets import ( "errors" + "net" "net/netip" "time" ) -//go:linkname UseStack net.useNetdev -func UseStack(stack Stack) +//go:linkname UseSocketStack net.useNetdev +func UseSocketStack(stack SocketStack) // GethostByName() errors var ( @@ -36,36 +37,36 @@ var ( ) 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 + _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 + _IPPROTO_TLS = 0xFE + _F_SETFL = 0x4 ) -// Link is the minimum interface that need be implemented by any network -// device driver. -type Link interface { - // HardwareAddr6 returns the device's 6-byte [MAC address], a.k.a EUI-48. +// 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) - // LinkStatus returns the state of the connection. - LinkStatus() LinkStatus + // Flags returns the net.Flag values for the interface. It includes state of connection. + Flags() net.Flags // MTU returns the maximum transmission unit size. MTU() int } -type EthPollerLink interface { - Link +type EthPoller interface { + Interface // SendEth sends an Ethernet packet SendEth(pkt []byte) error // RecvEthHandle sets recieve Ethernet packet callback function @@ -74,8 +75,8 @@ type EthPollerLink interface { PollOne() (bool, error) } -type LinkWifi interface { - Link +type InterfaceWifi interface { + Interface // Connect device to network NetConnect(params WifiParams) error // Disconnect device from network @@ -84,10 +85,10 @@ type LinkWifi interface { NetNotify(cb func(Event)) } -type Stack interface { +type SocketStack interface { // GetHostByName returns the IP address of either a hostname or IPv4 // address in standard dot notation - GetHostByName(name string) (netip.Addr, error) + // GetHostByName(name string) (netip.Addr, error) // Addr returns IP address assigned to the interface, either by // DHCP or statically @@ -105,10 +106,18 @@ type Stack interface { SetSockOpt(sockfd int, level int, opt int, value interface{}) error } -// Netdev is returned by `Probe` function. -type Netdev interface { - LinkWifi - Stack +// Should have UseResolver package level function that replaces the Go Resolver? +type Resolver interface { + // GetHostByName returns the IP address of either a hostname or IPv4 + // address in standard dot notation + GetHostByName(name string) (netip.Addr, error) +} + +// L4WifiInterface is returned by `Probe` function for devices that communicate +// on the OSI level 4 (transport) layer. +type L4WifiInterface interface { + InterfaceWifi + SocketStack } type WifiParams struct { @@ -158,14 +167,6 @@ const ( AuthTypeWPA2Mixed // WPA2/WPA mixed authorization ) -type LinkStatus uint8 - -const ( - LinkDown LinkStatus = iota - LinkConnecting - LinkUp -) - type WifiAutoconnectParams struct { WifiParams @@ -183,7 +184,7 @@ type WifiAutoconnectParams struct { WatchdogTimeout time.Duration } -func StartWifiAutoconnect(dev Netdev, cfg WifiAutoconnectParams) error { +func StartWifiAutoconnect(dev L4WifiInterface, cfg WifiAutoconnectParams) error { if dev == nil { return ErrConnectModeNoGood } @@ -199,7 +200,7 @@ func StartWifiAutoconnect(dev Netdev, cfg WifiAutoconnectParams) error { } for cfg.WatchdogTimeout != 0 { time.Sleep(cfg.WatchdogTimeout) - if dev.LinkStatus() == LinkDown { + if dev.Flags()&net.FlagRunning == 0 { i = 0 goto RECONNECT } From d4705e501d0d9b8fec7ec1da56add9b652e26b54 Mon Sep 17 00:00:00 2001 From: soypat Date: Tue, 19 Dec 2023 23:12:46 -0300 Subject: [PATCH 07/14] rethink netdev name again --- nets/nets.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nets/nets.go b/nets/nets.go index 19cddafb0..268575bf5 100644 --- a/nets/nets.go +++ b/nets/nets.go @@ -113,9 +113,9 @@ type Resolver interface { GetHostByName(name string) (netip.Addr, error) } -// L4WifiInterface is returned by `Probe` function for devices that communicate +// WifiStack is returned by `Probe` function for devices that communicate // on the OSI level 4 (transport) layer. -type L4WifiInterface interface { +type WifiStack interface { InterfaceWifi SocketStack } @@ -184,7 +184,7 @@ type WifiAutoconnectParams struct { WatchdogTimeout time.Duration } -func StartWifiAutoconnect(dev L4WifiInterface, cfg WifiAutoconnectParams) error { +func StartWifiAutoconnect(dev WifiStack, cfg WifiAutoconnectParams) error { if dev == nil { return ErrConnectModeNoGood } From 6913eaf2b9a11b67d2d9fadf40f211b3782f3e25 Mon Sep 17 00:00:00 2001 From: soypat Date: Wed, 20 Dec 2023 10:26:06 -0300 Subject: [PATCH 08/14] add examples --- nets/example_test.go | 97 ++++++++++++++++++++++++++++++++++++++++++++ nets/nets.go | 38 ++++++++++------- 2 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 nets/example_test.go diff --git a/nets/example_test.go b/nets/example_test.go new file mode 100644 index 000000000..0e6fefba0 --- /dev/null +++ b/nets/example_test.go @@ -0,0 +1,97 @@ +package nets + +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/nets/nets.go b/nets/nets.go index 268575bf5..6545803bf 100644 --- a/nets/nets.go +++ b/nets/nets.go @@ -7,8 +7,8 @@ import ( "time" ) -//go:linkname UseSocketStack net.useNetdev -func UseSocketStack(stack SocketStack) +//go:linkname UseStack net.useNetdev +func UseStack(stack Stack) // GethostByName() errors var ( @@ -59,13 +59,16 @@ type Interface interface { // // [MAC address]: https://en.wikipedia.org/wiki/MAC_address HardwareAddr6() ([6]byte, error) - // Flags returns the net.Flag values for the interface. It includes state of connection. - Flags() net.Flags + // 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 } -type EthPoller interface { +// InterfaceEthPoller is implemented by devices that send/receive ethernet packets. +type InterfaceEthPoller interface { Interface // SendEth sends an Ethernet packet SendEth(pkt []byte) error @@ -75,17 +78,24 @@ type EthPoller interface { 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() - // Notify to register callback for network events - NetNotify(cb func(Event)) } -type SocketStack interface { +// 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) @@ -99,7 +109,7 @@ type SocketStack interface { 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, ip netip.AddrPort) (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 @@ -113,11 +123,11 @@ type Resolver interface { GetHostByName(name string) (netip.Addr, error) } -// WifiStack is returned by `Probe` function for devices that communicate +// StackWifi is returned by `Probe` function for devices that communicate // on the OSI level 4 (transport) layer. -type WifiStack interface { +type StackWifi interface { InterfaceWifi - SocketStack + Stack } type WifiParams struct { @@ -184,7 +194,7 @@ type WifiAutoconnectParams struct { WatchdogTimeout time.Duration } -func StartWifiAutoconnect(dev WifiStack, cfg WifiAutoconnectParams) error { +func StartWifiAutoconnect(dev InterfaceWifi, cfg WifiAutoconnectParams) error { if dev == nil { return ErrConnectModeNoGood } @@ -200,7 +210,7 @@ func StartWifiAutoconnect(dev WifiStack, cfg WifiAutoconnectParams) error { } for cfg.WatchdogTimeout != 0 { time.Sleep(cfg.WatchdogTimeout) - if dev.Flags()&net.FlagRunning == 0 { + if dev.NetFlags()&net.FlagRunning == 0 { i = 0 goto RECONNECT } From f657269a665da9f560ac685a190089753568ec60 Mon Sep 17 00:00:00 2001 From: soypat Date: Sun, 7 Jan 2024 15:26:55 -0300 Subject: [PATCH 09/14] rename package: nets -> netif --- netif/example_test.go | 97 ++++++++++++++++++ netif/nets.go | 222 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 319 insertions(+) create mode 100644 netif/example_test.go create mode 100644 netif/nets.go 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/nets.go b/netif/nets.go new file mode 100644 index 000000000..8b4f0b1b3 --- /dev/null +++ b/netif/nets.go @@ -0,0 +1,222 @@ +package netif + +import ( + "errors" + "net" + "net/netip" + "time" + _ "unsafe" +) + +//go:linkname UseStack net.useNetdev +func UseStack(stack Stack) + +// GethostByName() errors +var ( + ErrHostUnknown = errors.New("host unknown") + ErrMalAddr = errors.New("malformed address") +) + +// Socket errors +var ( + ErrFamilyNotSupported = errors.New("address family not supported") + ErrProtocolNotSupported = errors.New("socket protocol/type not supported") + ErrStartingDHCPClient = errors.New("error starting DHPC client") + ErrNoMoreSockets = errors.New("no more sockets") + ErrClosingSocket = errors.New("error closing socket") +) + +var ( + ErrConnected = errors.New("already connected") + ErrConnectFailed = errors.New("connect failed") + ErrConnectTimeout = errors.New("connect timed out") + ErrMissingSSID = errors.New("missing WiFi SSID") + ErrAuthFailure = errors.New("wifi authentication failure") + ErrAuthTypeNoGood = errors.New("wifi authorization type not supported") + ErrConnectModeNoGood = errors.New("connect mode not supported") + ErrNotSupported = errors.New("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 +} + +// Should have UseResolver package level function that replaces the Go Resolver? +type Resolver interface { + // GetHostByName returns the IP address of either a hostname or IPv4 + // address in standard dot notation + GetHostByName(name 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 int + +// 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 int + +// Connect modes +const ( + ConnectModeSTA = iota // Connect as Wifi station (default) + ConnectModeAP // Connect as Wifi Access Point +) + +type AuthType int + +// 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 ErrConnectModeNoGood + } + go func() { + // Wifi autoconnect algorithm in one place, + // no need to implement for every single netdever. + RECONNECT: + for i := 0; i < cfg.Retries; i++ { + err := dev.NetConnect(cfg.WifiParams) + if err != nil { + time.Sleep(cfg.ConnectTimeout) + goto RECONNECT + } + for cfg.WatchdogTimeout != 0 { + time.Sleep(cfg.WatchdogTimeout) + if dev.NetFlags()&net.FlagRunning == 0 { + i = 0 + goto RECONNECT + } + } + } + }() + return nil +} From 68ccd70c567c3e5f4b3a77b6b97f106f4f31bdc6 Mon Sep 17 00:00:00 2001 From: soypat Date: Sun, 7 Jan 2024 15:28:48 -0300 Subject: [PATCH 10/14] rename file --- netif/{nets.go => netif.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename netif/{nets.go => netif.go} (100%) diff --git a/netif/nets.go b/netif/netif.go similarity index 100% rename from netif/nets.go rename to netif/netif.go From cdc76f3ea7fd92945e1c777cecb0f846076d16a7 Mon Sep 17 00:00:00 2001 From: soypat Date: Sun, 7 Jan 2024 15:42:43 -0300 Subject: [PATCH 11/14] modify netif.Resolver to use net.DefaultResolver method set --- netif/netif.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/netif/netif.go b/netif/netif.go index 8b4f0b1b3..bde8ee925 100644 --- a/netif/netif.go +++ b/netif/netif.go @@ -1,6 +1,7 @@ package netif import ( + "context" "errors" "net" "net/netip" @@ -117,11 +118,13 @@ type Stack interface { SetSockOpt(sockfd int, level int, opt int, value interface{}) error } -// Should have UseResolver package level function that replaces the Go Resolver? +// Resolver is implemented by DNS resolvers, notably Go's [net.DefaultResolver]. type Resolver interface { - // GetHostByName returns the IP address of either a hostname or IPv4 - // address in standard dot notation - GetHostByName(name string) (netip.Addr, error) + // 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 From 1690c2c2d817f515a4e0c901c8a3ff0da93ffdf2 Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Wed, 10 Jan 2024 09:11:09 -0300 Subject: [PATCH 12/14] Delete nets/nets.go nets/nets.go was replaced by netif/netif.go --- nets/nets.go | 221 --------------------------------------------------- 1 file changed, 221 deletions(-) delete mode 100644 nets/nets.go diff --git a/nets/nets.go b/nets/nets.go deleted file mode 100644 index 6545803bf..000000000 --- a/nets/nets.go +++ /dev/null @@ -1,221 +0,0 @@ -package nets - -import ( - "errors" - "net" - "net/netip" - "time" -) - -//go:linkname UseStack net.useNetdev -func UseStack(stack Stack) - -// GethostByName() errors -var ( - ErrHostUnknown = errors.New("host unknown") - ErrMalAddr = errors.New("malformed address") -) - -// Socket errors -var ( - ErrFamilyNotSupported = errors.New("address family not supported") - ErrProtocolNotSupported = errors.New("socket protocol/type not supported") - ErrStartingDHCPClient = errors.New("error starting DHPC client") - ErrNoMoreSockets = errors.New("no more sockets") - ErrClosingSocket = errors.New("error closing socket") -) - -var ( - ErrConnected = errors.New("already connected") - ErrConnectFailed = errors.New("connect failed") - ErrConnectTimeout = errors.New("connect timed out") - ErrMissingSSID = errors.New("missing WiFi SSID") - ErrAuthFailure = errors.New("wifi authentication failure") - ErrAuthTypeNoGood = errors.New("wifi authorization type not supported") - ErrConnectModeNoGood = errors.New("connect mode not supported") - ErrNotSupported = errors.New("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 -} - -// Should have UseResolver package level function that replaces the Go Resolver? -type Resolver interface { - // GetHostByName returns the IP address of either a hostname or IPv4 - // address in standard dot notation - GetHostByName(name 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 int - -// 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 int - -// Connect modes -const ( - ConnectModeSTA = iota // Connect as Wifi station (default) - ConnectModeAP // Connect as Wifi Access Point -) - -type AuthType int - -// 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 ErrConnectModeNoGood - } - go func() { - // Wifi autoconnect algorithm in one place, - // no need to implement for every single netdever. - RECONNECT: - for i := 0; i < cfg.Retries; i++ { - err := dev.NetConnect(cfg.WifiParams) - if err != nil { - time.Sleep(cfg.ConnectTimeout) - goto RECONNECT - } - for cfg.WatchdogTimeout != 0 { - time.Sleep(cfg.WatchdogTimeout) - if dev.NetFlags()&net.FlagRunning == 0 { - i = 0 - goto RECONNECT - } - } - } - }() - return nil -} From 9b74ecbc364af41742161d22d6d8cba11485fe5e Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Wed, 10 Jan 2024 09:11:23 -0300 Subject: [PATCH 13/14] Delete nets/example_test.go --- nets/example_test.go | 97 -------------------------------------------- 1 file changed, 97 deletions(-) delete mode 100644 nets/example_test.go diff --git a/nets/example_test.go b/nets/example_test.go deleted file mode 100644 index 0e6fefba0..000000000 --- a/nets/example_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package nets - -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") -} From 85a28fb9376ec523b91c6037e07b60dbda4c92a7 Mon Sep 17 00:00:00 2001 From: soypat Date: Sat, 24 Feb 2024 23:43:12 -0300 Subject: [PATCH 14/14] delay addition of extra error types until verifiably needed --- netif/netif.go | 38 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/netif/netif.go b/netif/netif.go index bde8ee925..e931a63d6 100644 --- a/netif/netif.go +++ b/netif/netif.go @@ -12,30 +12,15 @@ import ( //go:linkname UseStack net.useNetdev func UseStack(stack Stack) -// GethostByName() errors +// Socket errors. var ( - ErrHostUnknown = errors.New("host unknown") - ErrMalAddr = errors.New("malformed address") -) - -// Socket errors -var ( - ErrFamilyNotSupported = errors.New("address family not supported") ErrProtocolNotSupported = errors.New("socket protocol/type not supported") - ErrStartingDHCPClient = errors.New("error starting DHPC client") - ErrNoMoreSockets = errors.New("no more sockets") - ErrClosingSocket = errors.New("error closing socket") ) +// Wifi errors. var ( - ErrConnected = errors.New("already connected") - ErrConnectFailed = errors.New("connect failed") - ErrConnectTimeout = errors.New("connect timed out") - ErrMissingSSID = errors.New("missing WiFi SSID") - ErrAuthFailure = errors.New("wifi authentication failure") - ErrAuthTypeNoGood = errors.New("wifi authorization type not supported") - ErrConnectModeNoGood = errors.New("connect mode not supported") - ErrNotSupported = errors.New("not supported") + ErrAuthFailure = errors.New("wifi authentication failure") + ErrAuthUnsupported = errors.New("wifi authorization type not supported") ) const ( @@ -152,7 +137,7 @@ type WifiParams struct { CountryCode string } -type Event int +type Event uint8 // Network events const ( @@ -162,7 +147,7 @@ const ( EventNetDown ) -type ConnectMode int +type ConnectMode uint8 // Connect modes const ( @@ -170,7 +155,7 @@ const ( ConnectModeAP // Connect as Wifi Access Point ) -type AuthType int +type AuthType uint8 // Wifi authorization types. Used when setting up an access point, or // connecting to an access point @@ -186,7 +171,7 @@ type WifiAutoconnectParams struct { // 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 + // Retries int // Probably should be implemented as a function // Timeout duration for each connection attempt. The default zero // value means 10sec. @@ -200,22 +185,23 @@ type WifiAutoconnectParams struct { func StartWifiAutoconnect(dev InterfaceWifi, cfg WifiAutoconnectParams) error { if dev == nil { - return ErrConnectModeNoGood + 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 < cfg.Retries; i++ { + 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 { - i = 0 goto RECONNECT } }