-
Notifications
You must be signed in to change notification settings - Fork 0
/
socket.go
96 lines (83 loc) · 2.4 KB
/
socket.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package rdv
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/netip"
urlpkg "net/url"
"github.com/libp2p/go-reuseport"
)
// An SO_REUSEPORT TCP socket suitable for NAT traversal/hole punching, over both ipv4 and ipv6.
// Usually, higher level abstractions should be used.
type Socket struct {
// A dual-stack (ipv4/6) TCP listener.
//
// TODO: Should this be refactored into two single-stack listeners, in order to support
// non dual-stack systems? And if so, can the ports be different? See also NAT64.
net.Listener
/// Dialers for ipv4 and ipv6.
D4, D6 *net.Dialer
/// Port number for the socket, both stacks.
Port uint16
// TLS config for https.
//
// TODO: Higher level protocols should be one layer above sockets?
TlsConfig *tls.Config
}
func dialer(localIp net.IP, port uint16) *net.Dialer {
return &net.Dialer{
Control: reuseport.Control,
LocalAddr: &net.TCPAddr{IP: localIp, Port: int(port)},
}
}
func NewSocket(ctx context.Context, port uint16, tlsConf *tls.Config) (*Socket, error) {
lc := net.ListenConfig{
Control: reuseport.Control,
}
ln, err := lc.Listen(ctx, "tcp", fmt.Sprintf(":%v", port))
if err != nil {
return nil, err
}
port = netip.MustParseAddrPort(ln.Addr().String()).Port()
return &Socket{
Listener: ln,
D4: dialer(net.IPv4zero, port),
D6: dialer(net.IPv6zero, port),
Port: port,
TlsConfig: tlsConf,
}, nil
}
func (s *Socket) networkToDialer(network string) *net.Dialer {
if network == "tcp6" {
return s.D6
}
return s.D4
}
func (s *Socket) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
d := s.networkToDialer(network)
return d.DialContext(ctx, network, address)
}
func (s *Socket) DialIPContext(ctx context.Context, addr netip.AddrPort) (net.Conn, error) {
// TODO: Ipv4 mapped 6 addresses?
network := "tcp4"
if addr.Addr().Is6() {
network = "tcp6"
}
return s.DialContext(ctx, network, addr.String())
}
func (s *Socket) DialURLContext(ctx context.Context, network string, url *urlpkg.URL) (net.Conn, error) {
hostPort := net.JoinHostPort(url.Hostname(), urlPort(url))
netd := s.networkToDialer(network)
dialFn := netd.DialContext
if url.Scheme == "https" {
tlsd := &tls.Dialer{
NetDialer: netd,
Config: s.TlsConfig,
}
dialFn = tlsd.DialContext
} else if url.Scheme != "http" {
return nil, fmt.Errorf("unexpected scheme [%s]", url.Scheme)
}
return dialFn(ctx, network, hostPort)
}