Skip to content

Commit d5da3dd

Browse files
authored
net: add UDP listen, resolver, DNSError, and TCP listener APIs for wasm (#63)
* net: add UDP listen, resolver, DNSError, and TCP listener APIs for wasm TinyGo's net package was missing several symbols that upstream Go's js/wasm net declares, blocking builds that pull pion/transport, pion/dtls, and similar (via netbird's WASM client). Add them, backed by the existing netdev abstraction so they compile for all targets and no-op cleanly under nopNetdev (matching Go's js runtime behavior): - DNSError type (dnserror.go), copied from Go 1.26.2. - Resolver + DefaultResolver with LookupHost/LookupIP/LookupIPAddr/ LookupNetIP/LookupPort; LookupHost/LookupIP package funcs; Dialer.Resolver field for API compatibility. - UDPConn: ListenUDP, ReadFromUDP(AddrPort), WriteToUDP(AddrPort), SetReadBuffer, SetWriteBuffer. - TCPConn: CloseRead, SetNoDelay, SetReadBuffer, SetWriteBuffer, ReadFrom(io.Reader). - TCPListener: ListenTCP, AcceptTCP, SetDeadline; ListenPacket package func. - Interface: Addrs, MulticastAddrs stubs. * more complete PR for adding more net support (#64) * format unixsock
1 parent 1026408 commit d5da3dd

12 files changed

Lines changed: 770 additions & 1 deletion

dial.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ type Dialer struct {
8787
// If KeepAliveConfig.Enable is false and KeepAlive is negative,
8888
// keep-alive probes are disabled.
8989
KeepAliveConfig KeepAliveConfig
90+
91+
// Resolver optionally specifies an alternate resolver to use.
92+
//
93+
// TINYGO: present for API compatibility; DialContext resolves via the
94+
// netdev-backed ResolveTCPAddr/ResolveUDPAddr and does not consult this
95+
// field.
96+
Resolver *Resolver
9097
}
9198

9299
// Dial connects to the address on the named network.
@@ -281,3 +288,22 @@ func Listen(network, address string) (Listener, error) {
281288

282289
return listenTCP(laddr)
283290
}
291+
292+
// ListenPacket announces on the local network address.
293+
//
294+
// TINYGO: only UDP networks are supported, backed by ListenUDP; the
295+
// returned PacketConn is a *UDPConn.
296+
func ListenPacket(network, address string) (PacketConn, error) {
297+
switch network {
298+
case "udp", "udp4":
299+
default:
300+
return nil, fmt.Errorf("Network %s not supported", network)
301+
}
302+
303+
laddr, err := ResolveUDPAddr(network, address)
304+
if err != nil {
305+
return nil, err
306+
}
307+
308+
return ListenUDP(network, laddr)
309+
}

dnserror.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// TINYGO: The following is copied and modified from Go 1.26.2 official implementation.
2+
3+
// Copyright 2018 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package net
8+
9+
// DNSError represents a DNS lookup error.
10+
type DNSError struct {
11+
UnwrapErr error // error returned by the [DNSError.Unwrap] method, might be nil
12+
Err string // description of the error
13+
Name string // name looked for
14+
Server string // server used
15+
IsTimeout bool // if true, timed out; not all timeouts set this
16+
IsTemporary bool // if true, error is temporary; not all errors set this
17+
18+
// IsNotFound is set to true when the requested name does not
19+
// contain any records of the requested type (data not found),
20+
// or the name itself was not found (NXDOMAIN).
21+
IsNotFound bool
22+
}
23+
24+
// Unwrap returns e.UnwrapErr.
25+
func (e *DNSError) Unwrap() error { return e.UnwrapErr }
26+
27+
func (e *DNSError) Error() string {
28+
if e == nil {
29+
return "<nil>"
30+
}
31+
s := "lookup " + e.Name
32+
if e.Server != "" {
33+
s += " on " + e.Server
34+
}
35+
s += ": " + e.Err
36+
return s
37+
}
38+
39+
// Timeout reports whether the DNS lookup is known to have timed out.
40+
// This is not always known; a DNS lookup may fail due to a timeout
41+
// and return a [DNSError] for which Timeout returns false.
42+
func (e *DNSError) Timeout() bool { return e.IsTimeout }
43+
44+
// Temporary reports whether the DNS error is known to be temporary.
45+
// This is not always known; a DNS lookup may fail due to a temporary
46+
// error and return a [DNSError] for which Temporary returns false.
47+
func (e *DNSError) Temporary() bool { return e.IsTimeout || e.IsTemporary }

http/response.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ package http
1414
import (
1515
"bufio"
1616
"bytes"
17+
"crypto/tls"
1718
"errors"
1819
"fmt"
1920
"io"
@@ -117,6 +118,11 @@ type Response struct {
117118
// Request's Body is nil (having already been consumed).
118119
// This is only populated for Client requests.
119120
Request *Request
121+
122+
// TLS contains information about the TLS connection on which the response
123+
// was received. It is nil for unencrypted responses. TINYGO: populated only
124+
// if a caller sets it; the wasm fetch path leaves it nil.
125+
TLS *tls.ConnectionState
120126
}
121127

122128
// Cookies parses and returns the cookies set in the Set-Cookie headers.

http/responsecontroller.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// TINYGO: Copied verbatim from the Go 1.26.2 official implementation to provide
2+
// the ResponseController API used by net/http/httputil (reverse proxy). The
3+
// underlying methods degrade to ErrNotSupported when the ResponseWriter does
4+
// not implement them, which is the standard behavior.
5+
6+
// Copyright 2022 The Go Authors. All rights reserved.
7+
// Use of this source code is governed by a BSD-style
8+
// license that can be found in the LICENSE file.
9+
10+
package http
11+
12+
import (
13+
"bufio"
14+
"fmt"
15+
"net"
16+
"time"
17+
)
18+
19+
// A ResponseController is used by an HTTP handler to control the response.
20+
//
21+
// A ResponseController may not be used after the [Handler.ServeHTTP] method has returned.
22+
type ResponseController struct {
23+
rw ResponseWriter
24+
}
25+
26+
// NewResponseController creates a [ResponseController] for a request.
27+
//
28+
// The ResponseWriter should be the original value passed to the [Handler.ServeHTTP] method,
29+
// or have an Unwrap method returning the original ResponseWriter.
30+
//
31+
// If the ResponseWriter implements any of the following methods, the ResponseController
32+
// will call them as appropriate:
33+
//
34+
// Flush()
35+
// FlushError() error // alternative Flush returning an error
36+
// Hijack() (net.Conn, *bufio.ReadWriter, error)
37+
// SetReadDeadline(deadline time.Time) error
38+
// SetWriteDeadline(deadline time.Time) error
39+
// EnableFullDuplex() error
40+
//
41+
// If the ResponseWriter does not support a method, ResponseController returns
42+
// an error matching [ErrNotSupported].
43+
func NewResponseController(rw ResponseWriter) *ResponseController {
44+
return &ResponseController{rw}
45+
}
46+
47+
type rwUnwrapper interface {
48+
Unwrap() ResponseWriter
49+
}
50+
51+
// Flush flushes buffered data to the client.
52+
func (c *ResponseController) Flush() error {
53+
rw := c.rw
54+
for {
55+
switch t := rw.(type) {
56+
case interface{ FlushError() error }:
57+
return t.FlushError()
58+
case Flusher:
59+
t.Flush()
60+
return nil
61+
case rwUnwrapper:
62+
rw = t.Unwrap()
63+
default:
64+
return errNotSupported()
65+
}
66+
}
67+
}
68+
69+
// Hijack lets the caller take over the connection.
70+
// See the [Hijacker] interface for details.
71+
func (c *ResponseController) Hijack() (net.Conn, *bufio.ReadWriter, error) {
72+
rw := c.rw
73+
for {
74+
switch t := rw.(type) {
75+
case Hijacker:
76+
return t.Hijack()
77+
case rwUnwrapper:
78+
rw = t.Unwrap()
79+
default:
80+
return nil, nil, errNotSupported()
81+
}
82+
}
83+
}
84+
85+
// SetReadDeadline sets the deadline for reading the entire request, including the body.
86+
// Reads from the request body after the deadline has been exceeded will return an error.
87+
// A zero value means no deadline.
88+
//
89+
// Setting the read deadline after it has been exceeded will not extend it.
90+
func (c *ResponseController) SetReadDeadline(deadline time.Time) error {
91+
rw := c.rw
92+
for {
93+
switch t := rw.(type) {
94+
case interface{ SetReadDeadline(time.Time) error }:
95+
return t.SetReadDeadline(deadline)
96+
case rwUnwrapper:
97+
rw = t.Unwrap()
98+
default:
99+
return errNotSupported()
100+
}
101+
}
102+
}
103+
104+
// SetWriteDeadline sets the deadline for writing the response.
105+
// Writes to the response body after the deadline has been exceeded will not block,
106+
// but may succeed if the data has been buffered.
107+
// A zero value means no deadline.
108+
//
109+
// Setting the write deadline after it has been exceeded will not extend it.
110+
func (c *ResponseController) SetWriteDeadline(deadline time.Time) error {
111+
rw := c.rw
112+
for {
113+
switch t := rw.(type) {
114+
case interface{ SetWriteDeadline(time.Time) error }:
115+
return t.SetWriteDeadline(deadline)
116+
case rwUnwrapper:
117+
rw = t.Unwrap()
118+
default:
119+
return errNotSupported()
120+
}
121+
}
122+
}
123+
124+
// EnableFullDuplex indicates that the request handler will interleave reads from [Request.Body]
125+
// with writes to the [ResponseWriter].
126+
//
127+
// For HTTP/1 requests, the Go HTTP server by default consumes any unread portion of
128+
// the request body before beginning to write the response, preventing handlers from
129+
// concurrently reading from the request and writing the response.
130+
// Calling EnableFullDuplex disables this behavior and permits handlers to continue to read
131+
// from the request while concurrently writing the response.
132+
//
133+
// For HTTP/2 requests, the Go HTTP server always permits concurrent reads and responses.
134+
func (c *ResponseController) EnableFullDuplex() error {
135+
rw := c.rw
136+
for {
137+
switch t := rw.(type) {
138+
case interface{ EnableFullDuplex() error }:
139+
return t.EnableFullDuplex()
140+
case rwUnwrapper:
141+
rw = t.Unwrap()
142+
default:
143+
return errNotSupported()
144+
}
145+
}
146+
}
147+
148+
// errNotSupported returns an error that Is ErrNotSupported,
149+
// but is not == to it.
150+
func errNotSupported() error {
151+
return fmt.Errorf("%w", ErrNotSupported)
152+
}

http/server.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2779,6 +2779,23 @@ type Server struct {
27792779
// value.
27802780
ConnContext func(ctx context.Context, c net.Conn) context.Context
27812781

2782+
// TLSNextProto optionally specifies a function to take over
2783+
// ownership of the provided TLS connection when an ALPN
2784+
// protocol upgrade has occurred. The map key is the protocol
2785+
// name negotiated. The Handler argument should be used to
2786+
// handle HTTP requests and will initialize the Request's TLS
2787+
// and RemoteAddr if not already set. The connection is
2788+
// automatically closed when the function returns.
2789+
// If TLSNextProto is not nil, HTTP/2 support is not enabled
2790+
// automatically.
2791+
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
2792+
2793+
// HTTP2 configures HTTP/2 connections.
2794+
//
2795+
// This field does not yet have any effect.
2796+
// See https://go.dev/issue/67813.
2797+
HTTP2 *HTTP2Config
2798+
27822799
inShutdown atomicBool // true when server is in shutdown
27832800

27842801
disableKeepAlives int32 // accessed atomically.

0 commit comments

Comments
 (0)