BUG Fix: return proper net.OpError's for Read/Write operations

Need to return the same error structure/content as regular Go for
net.Conn Read/Write operations.  Found/fixed when testing deadlines
on Read/Write operations.
This commit is contained in:
Scott Feldman
2023-04-16 09:48:09 -07:00
committed by deadprogram
parent ad745d61ae
commit 7898c5e946
5 changed files with 97 additions and 130 deletions
+9
View File
@@ -161,6 +161,15 @@ func (e *OpError) Error() string {
return s return s
} }
type timeout interface {
Timeout() bool
}
func (e *OpError) Timeout() bool {
t, ok := e.Err.(timeout)
return ok && t.Timeout()
}
// A ParseError is the error type of literal network address parsers. // A ParseError is the error type of literal network address parsers.
type ParseError struct { type ParseError struct {
// Type is the type of string that was expected, such as // Type is the type of string that was expected, such as
+2 -2
View File
@@ -45,8 +45,8 @@ type netdever interface {
Connect(sockfd int, host string, ip IP, port int) error Connect(sockfd int, host string, ip IP, port int) error
Listen(sockfd int, backlog int) error Listen(sockfd int, backlog int) error
Accept(sockfd int, ip IP, port int) (int, error) Accept(sockfd int, ip IP, port int) (int, error)
Send(sockfd int, buf []byte, flags int, timeout time.Duration) (int, error) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error)
Recv(sockfd int, buf []byte, flags int, timeout time.Duration) (int, error) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error)
Close(sockfd int) error Close(sockfd int) error
SetSockOpt(sockfd int, level int, opt int, value interface{}) error SetSockOpt(sockfd int, level int, opt int, value interface{}) error
} }
+12 -26
View File
@@ -9,6 +9,7 @@ package net
import ( import (
"fmt" "fmt"
"internal/itoa" "internal/itoa"
"io"
"net/netip" "net/netip"
"strconv" "strconv"
"syscall" "syscall"
@@ -116,6 +117,7 @@ func ResolveTCPAddr(network, address string) (*TCPAddr, error) {
// connections. // connections.
type TCPConn struct { type TCPConn struct {
fd int fd int
net string
laddr *TCPAddr laddr *TCPAddr
raddr *TCPAddr raddr *TCPAddr
readDeadline time.Time readDeadline time.Time
@@ -159,6 +161,7 @@ func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error) {
return &TCPConn{ return &TCPConn{
fd: fd, fd: fd,
net: network,
laddr: laddr, laddr: laddr,
raddr: raddr, raddr: raddr,
}, nil }, nil
@@ -167,44 +170,26 @@ func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error) {
// TINYGO: Use netdev for Conn methods: Read = Recv, Write = Send, etc. // TINYGO: Use netdev for Conn methods: Read = Recv, Write = Send, etc.
func (c *TCPConn) Read(b []byte) (int, error) { func (c *TCPConn) Read(b []byte) (int, error) {
var timeout time.Duration n, err := netdev.Recv(c.fd, b, 0, c.readDeadline)
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 // Turn the -1 socket error into 0 and let err speak for error
if n < 0 { if n < 0 {
n = 0 n = 0
} }
if err != nil && err != io.EOF {
err = &OpError{Op: "read", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err}
}
return n, err return n, err
} }
func (c *TCPConn) Write(b []byte) (int, error) { func (c *TCPConn) Write(b []byte) (int, error) {
var timeout time.Duration n, err := netdev.Send(c.fd, b, 0, c.writeDeadline)
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 // Turn the -1 socket error into 0 and let err speak for error
if n < 0 { if n < 0 {
n = 0 n = 0
} }
if err != nil {
err = &OpError{Op: "write", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err}
}
return n, err return n, err
} }
@@ -262,6 +247,7 @@ func (l *listener) Accept() (Conn, error) {
return &TCPConn{ return &TCPConn{
fd: fd, fd: fd,
net: "tcp",
laddr: l.laddr, laddr: l.laddr,
}, nil }, nil
} }
+63 -76
View File
@@ -9,12 +9,39 @@
package net package net
import ( import (
"fmt" "internal/itoa"
"io"
"strconv" "strconv"
"syscall" "syscall"
"time" "time"
) )
// TLSAddr represents the address of a TLS end point.
type TLSAddr struct {
Host string
Port int
}
func (a *TLSAddr) Network() string { return "tls" }
func (a *TLSAddr) String() string {
if a == nil {
return "<nil>"
}
return JoinHostPort(a.Host, itoa.Itoa(a.Port))
}
// A TLSConn represents a secured connection.
// It implements the net.Conn interface.
type TLSConn struct {
fd int
net string
laddr *TLSAddr
raddr *TLSAddr
readDeadline time.Time
writeDeadline time.Time
}
func DialTLS(addr string) (*TLSConn, error) { func DialTLS(addr string) (*TLSConn, error) {
host, sport, err := SplitHostPort(addr) host, sport, err := SplitHostPort(addr)
@@ -42,97 +69,33 @@ func DialTLS(addr string) (*TLSConn, error) {
} }
return &TLSConn{ return &TLSConn{
fd: fd, fd: fd,
net: "tls",
raddr: &TLSAddr{host, port},
}, nil }, nil
} }
// A TLSConn represents a secured connection.
// It implements the net.Conn interface.
type TLSConn struct {
fd int
readDeadline time.Time
writeDeadline time.Time
}
// Access to net.Conn methods.
// Cannot just embed net.Conn because that would
// export the struct field too.
// LocalAddr returns the local network address.
func (c *TLSConn) LocalAddr() Addr {
// TODO
return nil
}
// RemoteAddr returns the remote network address.
func (c *TLSConn) RemoteAddr() Addr {
// TODO
return nil
}
// SetDeadline sets the read and write deadlines associated with the connection.
// A zero value for t means Read and Write will not time out.
// After a Write has timed out, the TLS state is corrupt and all future writes will return the same error.
func (c *TLSConn) SetDeadline(t time.Time) error {
c.readDeadline = t
c.writeDeadline = t
return nil
}
// SetReadDeadline sets the read deadline on the underlying connection.
// A zero value for t means Read will not time out.
func (c *TLSConn) SetReadDeadline(t time.Time) error {
c.readDeadline = t
return nil
}
// SetWriteDeadline sets the write deadline on the underlying connection.
// A zero value for t means Write will not time out.
// After a Write has timed out, the TLS state is corrupt and all future writes will return the same error.
func (c *TLSConn) SetWriteDeadline(t time.Time) error {
c.writeDeadline = t
return nil
}
func (c *TLSConn) Read(b []byte) (int, error) { func (c *TLSConn) Read(b []byte) (int, error) {
var timeout time.Duration n, err := netdev.Recv(c.fd, b, 0, c.readDeadline)
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 // Turn the -1 socket error into 0 and let err speak for error
if n < 0 { if n < 0 {
n = 0 n = 0
} }
if err != nil && err != io.EOF {
err = &OpError{Op: "read", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err}
}
return n, err return n, err
} }
func (c *TLSConn) Write(b []byte) (int, error) { func (c *TLSConn) Write(b []byte) (int, error) {
var timeout time.Duration n, err := netdev.Send(c.fd, b, 0, c.writeDeadline)
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 // Turn the -1 socket error into 0 and let err speak for error
if n < 0 { if n < 0 {
n = 0 n = 0
} }
if err != nil {
err = &OpError{Op: "write", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err}
}
return n, err return n, err
} }
@@ -140,6 +103,30 @@ func (c *TLSConn) Close() error {
return netdev.Close(c.fd) return netdev.Close(c.fd)
} }
func (c *TLSConn) LocalAddr() Addr {
return c.laddr
}
func (c *TLSConn) RemoteAddr() Addr {
return c.raddr
}
func (c *TLSConn) SetDeadline(t time.Time) error {
c.readDeadline = t
c.writeDeadline = t
return nil
}
func (c *TLSConn) SetReadDeadline(t time.Time) error {
c.readDeadline = t
return nil
}
func (c *TLSConn) SetWriteDeadline(t time.Time) error {
c.writeDeadline = t
return nil
}
// Handshake runs the client or server handshake // Handshake runs the client or server handshake
// protocol if it has not yet been run. // protocol if it has not yet been run.
// //
+11 -26
View File
@@ -9,6 +9,7 @@ package net
import ( import (
"fmt" "fmt"
"internal/itoa" "internal/itoa"
"io"
"net/netip" "net/netip"
"strconv" "strconv"
"syscall" "syscall"
@@ -116,6 +117,7 @@ func ResolveUDPAddr(network, address string) (*UDPAddr, error) {
// for UDP network connections. // for UDP network connections.
type UDPConn struct { type UDPConn struct {
fd int fd int
net string
laddr *UDPAddr laddr *UDPAddr
raddr *UDPAddr raddr *UDPAddr
readDeadline time.Time readDeadline time.Time
@@ -187,6 +189,7 @@ func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error) {
return &UDPConn{ return &UDPConn{
fd: fd, fd: fd,
net: network,
laddr: laddr, laddr: laddr,
raddr: raddr, raddr: raddr,
}, nil }, nil
@@ -195,44 +198,26 @@ func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error) {
// TINYGO: Use netdev for Conn methods: Read = Recv, Write = Send, etc. // TINYGO: Use netdev for Conn methods: Read = Recv, Write = Send, etc.
func (c *UDPConn) Read(b []byte) (int, error) { func (c *UDPConn) Read(b []byte) (int, error) {
var timeout time.Duration n, err := netdev.Recv(c.fd, b, 0, c.readDeadline)
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 // Turn the -1 socket error into 0 and let err speak for error
if n < 0 { if n < 0 {
n = 0 n = 0
} }
if err != nil && err != io.EOF {
err = &OpError{Op: "read", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err}
}
return n, err return n, err
} }
func (c *UDPConn) Write(b []byte) (int, error) { func (c *UDPConn) Write(b []byte) (int, error) {
var timeout time.Duration n, err := netdev.Send(c.fd, b, 0, c.writeDeadline)
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 // Turn the -1 socket error into 0 and let err speak for error
if n < 0 { if n < 0 {
n = 0 n = 0
} }
if err != nil {
err = &OpError{Op: "write", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err}
}
return n, err return n, err
} }