-
Notifications
You must be signed in to change notification settings - Fork 6
/
byname.go
172 lines (146 loc) · 4.39 KB
/
byname.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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package dane
import (
"crypto/tls"
"fmt"
"net"
"sync"
"time"
)
//
// Response - response information
//
type Response struct {
config *Config
conn *tls.Conn
err error
}
// IPv6 connect headstart (delay IPv4 connections by this amount)
var IPv6Headstart = 25 * time.Millisecond
// Maximum number of parallel connections attempted
var MaxParallelConnections = 30
//
// ConnectByName takes a hostname and port, resolves the addresses for
// the hostname (IPv6 followed by IPv4), and then attempts to connect to
// them and establish TLS using DANE or PKIX authentication - DANE is
// attempted if there are secure TLSA records, otherwise it falls back to
// PKIX authentication. It returns a TLS connection and dane config for
// the first address that succeeds.
//
// Uses a default DANE configuration. For a custom DANE configuration,
// use the DialTLS or DialStartTLS functions instead.
//
func ConnectByName(hostname string, port int) (*tls.Conn, *Config, error) {
var conn *tls.Conn
resolver, err := GetResolver("")
if err != nil {
return nil, nil, fmt.Errorf("error obtaining resolver address: %s", err.Error())
}
tlsa, err := GetTLSA(resolver, hostname, port)
if err != nil {
return nil, nil, err
}
needSecure := (tlsa != nil)
iplist, err := GetAddresses(resolver, hostname, needSecure)
if err != nil {
return nil, nil, err
}
if len(iplist) == 0 {
return nil, nil, fmt.Errorf("%s: no addresses found", hostname)
}
for _, ip := range iplist {
config := NewConfig(hostname, ip, port)
config.SetTLSA(tlsa)
conn, err = DialTLS(config)
if err != nil {
fmt.Printf("Connection failed to %s: %s\n", config.Server.Address(),
err.Error())
continue
}
return conn, config, err
}
return conn, nil, fmt.Errorf("failed to connect to any server address for %s",
hostname)
}
//
// ConnectByNameAsyncBase. Should not be called directly. Instead call
// either ConnectByNameAsync or ConnectByNameAsync2
//
func ConnectByNameAsyncBase(hostname string, port int, pkixfallback bool) (*tls.Conn, *Config, error) {
var conn *tls.Conn
var ip net.IP
var wg sync.WaitGroup
var numParallel = MaxParallelConnections
var tokens = make(chan struct{}, numParallel)
var results = make(chan *Response)
var done = make(chan struct{})
defer close(done)
resolver, err := GetResolver("")
if err != nil {
return nil, nil, fmt.Errorf("error obtaining resolver address: %s", err.Error())
}
tlsa, err := GetTLSA(resolver, hostname, port)
if err != nil {
return nil, nil, err
}
if !pkixfallback && (tlsa == nil) {
return nil, nil, fmt.Errorf("no TLSA records found")
}
needSecure := (tlsa != nil)
iplist, err := GetAddresses(resolver, hostname, needSecure)
if err != nil {
return nil, nil, err
}
if len(iplist) == 0 {
return nil, nil, fmt.Errorf("%s: no addresses found", hostname)
}
go func() {
for _, ip = range iplist {
wg.Add(1)
tokens <- struct{}{}
go func(hostname string, ip net.IP, port int) {
defer wg.Done()
config := NewConfig(hostname, ip, port)
config.SetTLSA(tlsa)
if !pkixfallback {
config.NoPKIXfallback()
}
if ip4 := ip.To4(); ip4 != nil {
time.Sleep(IPv6Headstart)
}
conn, err = DialTLS(config)
select {
case <-done:
case results <- &Response{config: config, conn: conn, err: err}:
<-tokens
}
}(hostname, ip, port)
}
wg.Wait()
close(results)
}()
for r := range results {
if r.err == nil {
return r.conn, r.config, nil
}
}
return conn, nil, fmt.Errorf("failed to connect to any server address for %s",
hostname)
}
//
// ConnectByNameAsync is an async version of ConnectByName that tries
// to connect to all server addresses in parallel, and returns the first
// successful connection. IPv4 connections are intentionally delayed by
// an IPv6HeadStart amount of time. Performs DANE authentication with
// fallback to PKIX if no secure TLSA records are found.
//
func ConnectByNameAsync(hostname string, port int) (*tls.Conn, *Config, error) {
return ConnectByNameAsyncBase(hostname, port, true)
}
//
// ConnectByNameAsync2 is the same as ConnectByNameAsync, but supports
// an additional argument to specify whether PKIX fallback should be performed.
// By setting that argument to false, we can require DANE only authentication.
//
func ConnectByNameAsync2(hostname string, port int, pkixfallback bool) (*tls.Conn, *Config, error) {
return ConnectByNameAsyncBase(hostname, port, pkixfallback)
}