serversInfo.go 10.6 KB
Newer Older
1 2 3
package main

import (
4
	"crypto/sha256"
5
	"encoding/hex"
6 7
	"errors"
	"fmt"
8
	"io"
9
	"io/ioutil"
10
	"math/rand"
11
	"net"
12
	"net/url"
13
	"os"
14 15
	"strings"
	"sync"
16
	"time"
17

18
	"github.com/VividCortex/ewma"
Frank Denis's avatar
Frank Denis committed
19
	"github.com/jedisct1/dlog"
20 21 22
	"golang.org/x/crypto/ed25519"
)

23 24
const (
	RTTEwmaDecay = 10.0
25
	DefaultPort  = 443
26 27
)

28 29 30
type ServerInformalProperties uint64

const (
31 32 33
	ServerInformalPropertyDNSSEC   = ServerInformalProperties(1) << 0
	ServerInformalPropertyNoLog    = ServerInformalProperties(1) << 1
	ServerInformalPropertyNoFilter = ServerInformalProperties(1) << 2
34 35
)

36
type RegisteredServer struct {
37 38 39
	name        string
	stamp       ServerStamp
	description string
40 41
}

42
type ServerInfo struct {
43
	sync.RWMutex
44
	Proto              StampProtoType
45 46 47 48 49 50
	MagicQuery         [8]byte
	ServerPk           [32]byte
	SharedKey          [32]byte
	CryptoConstruction CryptoConstruction
	Name               string
	Timeout            time.Duration
51 52
	URL                *url.URL
	HostName           string
53 54
	UDPAddr            *net.UDPAddr
	TCPAddr            *net.TCPAddr
55 56
	lastActionTS       time.Time
	rtt                ewma.MovingAverage
57
	initialRtt         int
58
	useGet             bool
59 60
}

61 62 63 64 65 66 67 68 69 70 71 72
type LBStrategy int

const (
	LBStrategyNone = LBStrategy(iota)
	LBStrategyP2
	LBStrategyPH
	LBStrategyFastest
	LBStrategyRandom
)

const DefaultLBStrategy = LBStrategyP2

73 74
type ServersInfo struct {
	sync.RWMutex
75
	inner             []*ServerInfo
76
	registeredServers []RegisteredServer
77
	lbStrategy        LBStrategy
78 79
}

Frank Denis's avatar
Frank Denis committed
80
func (serversInfo *ServersInfo) registerServer(proxy *Proxy, name string, stamp ServerStamp) error {
81 82 83 84 85 86 87 88 89 90 91 92 93 94
	newRegisteredServer := RegisteredServer{name: name, stamp: stamp}
	serversInfo.Lock()
	defer serversInfo.Unlock()
	for i, oldRegisteredServer := range serversInfo.registeredServers {
		if oldRegisteredServer.name == name {
			serversInfo.registeredServers[i] = newRegisteredServer
			return nil
		}
	}
	serversInfo.registeredServers = append(serversInfo.registeredServers, newRegisteredServer)
	return nil
}

func (serversInfo *ServersInfo) refreshServer(proxy *Proxy, name string, stamp ServerStamp) error {
Frank Denis's avatar
Frank Denis committed
95 96
	serversInfo.Lock()
	defer serversInfo.Unlock()
97 98 99 100 101 102 103 104
	previousIndex := -1
	for i, oldServer := range serversInfo.inner {
		if oldServer.Name == name {
			previousIndex = i
			break
		}
	}
	newServer, err := serversInfo.fetchServerInfo(proxy, name, stamp, previousIndex < 0)
105 106 107
	if err != nil {
		return err
	}
108 109 110
	if name != newServer.Name {
		dlog.Fatalf("[%s] != [%s]", name, newServer.Name)
	}
111
	newServer.rtt = ewma.NewMovingAverage(RTTEwmaDecay)
112
	if previousIndex >= 0 {
113
		serversInfo.inner[previousIndex] = &newServer
114
		return nil
115
	}
116
	serversInfo.inner = append(serversInfo.inner, &newServer)
117
	serversInfo.registeredServers = append(serversInfo.registeredServers, RegisteredServer{name: name, stamp: stamp})
118 119 120
	return nil
}

121
func (serversInfo *ServersInfo) refresh(proxy *Proxy) (int, error) {
122
	dlog.Debug("Refreshing certificates")
Frank Denis's avatar
Frank Denis committed
123
	serversInfo.RLock()
124
	registeredServers := serversInfo.registeredServers
Frank Denis's avatar
Frank Denis committed
125
	serversInfo.RUnlock()
126 127
	liveServers := 0
	var err error
128
	for _, registeredServer := range registeredServers {
129
		if err = serversInfo.refreshServer(proxy, registeredServer.name, registeredServer.stamp); err == nil {
130 131
			liveServers++
		}
Frank Denis's avatar
Frank Denis committed
132
	}
133 134 135 136 137 138 139 140 141 142 143
	serversInfo.Lock()
	inner := serversInfo.inner
	innerLen := len(inner)
	for i := 0; i < innerLen; i++ {
		for j := i + 1; j < innerLen; j++ {
			if inner[j].initialRtt < inner[i].initialRtt {
				inner[j], inner[i] = inner[i], inner[j]
			}
		}
	}
	serversInfo.inner = inner
144
	if innerLen > 0 {
145
		dlog.Noticef("Server with the lowest initial latency: %s (rtt: %dms)", inner[0].Name, inner[0].initialRtt)
146
		proxy.certIgnoreTimestamp = false
147 148
	}
	serversInfo.Unlock()
149 150 151 152 153
	return liveServers, err
}

func (serversInfo *ServersInfo) liveServers() int {
	serversInfo.RLock()
154
	liveServers := len(serversInfo.inner)
155 156
	serversInfo.RUnlock()
	return liveServers
Frank Denis's avatar
Frank Denis committed
157 158
}

159
func (serversInfo *ServersInfo) getOne() *ServerInfo {
160 161
	serversInfo.Lock()
	defer serversInfo.Unlock()
Frank Denis's avatar
Frank Denis committed
162 163 164 165
	serversCount := len(serversInfo.inner)
	if serversCount <= 0 {
		return nil
	}
166 167
	candidate := rand.Intn(serversCount)
	if candidate == 0 {
168
		return serversInfo.inner[candidate]
169
	}
170 171 172 173 174 175 176
	candidateRtt, currentBestRtt := serversInfo.inner[candidate].rtt.Value(), serversInfo.inner[0].rtt.Value()
	if currentBestRtt < 0 {
		currentBestRtt = candidateRtt
		serversInfo.inner[0].rtt.Set(currentBestRtt)
	}
	partialSort := false
	if candidateRtt < currentBestRtt {
177
		serversInfo.inner[candidate], serversInfo.inner[0] = serversInfo.inner[0], serversInfo.inner[candidate]
178
		partialSort = true
a1346054's avatar
a1346054 committed
179
		dlog.Debugf("New preferred candidate: %v (rtt: %v vs previous: %v)", serversInfo.inner[0].Name, candidateRtt, currentBestRtt)
180 181 182 183 184 185 186 187 188 189 190 191
	} else if candidateRtt >= currentBestRtt*4.0 {
		if time.Since(serversInfo.inner[candidate].lastActionTS) > time.Duration(1*time.Minute) {
			serversInfo.inner[candidate].rtt.Add(MinF(MaxF(candidateRtt/2.0, currentBestRtt*2.0), candidateRtt))
			partialSort = true
		}
	}
	if partialSort {
		for i := 1; i < serversCount; i++ {
			if serversInfo.inner[i-1].rtt.Value() > serversInfo.inner[i].rtt.Value() {
				serversInfo.inner[i-1], serversInfo.inner[i] = serversInfo.inner[i], serversInfo.inner[i-1]
			}
		}
192
	}
193 194 195 196
	switch serversInfo.lbStrategy {
	case LBStrategyFastest:
		candidate = 0
	case LBStrategyPH:
Frank Denis's avatar
Frank Denis committed
197
		candidate = rand.Intn(Min(Min(serversCount, 2), serversCount/2))
198
	case LBStrategyRandom:
Frank Denis's avatar
Frank Denis committed
199
		candidate = rand.Intn(serversCount)
200
	default:
Frank Denis's avatar
Frank Denis committed
201
		candidate = rand.Intn(Min(serversCount, 2))
202
	}
203 204
	serverInfo := serversInfo.inner[candidate]
	dlog.Debugf("Using candidate %v: [%v]", candidate, (*serverInfo).Name)
205

206 207 208
	return serverInfo
}

209
func (serversInfo *ServersInfo) fetchServerInfo(proxy *Proxy, name string, stamp ServerStamp, isNew bool) (ServerInfo, error) {
210
	if stamp.proto == StampProtoTypeDNSCrypt {
211
		return serversInfo.fetchDNSCryptServerInfo(proxy, name, stamp, isNew)
212
	} else if stamp.proto == StampProtoTypeDoH {
213
		return serversInfo.fetchDoHServerInfo(proxy, name, stamp, isNew)
214 215 216 217
	}
	return ServerInfo{}, errors.New("Unsupported protocol")
}

218
func (serversInfo *ServersInfo) fetchDNSCryptServerInfo(proxy *Proxy, name string, stamp ServerStamp, isNew bool) (ServerInfo, error) {
219 220 221 222 223 224 225
	if len(stamp.serverPk) != ed25519.PublicKeySize {
		serverPk, err := hex.DecodeString(strings.Replace(string(stamp.serverPk), ":", "", -1))
		if err != nil || len(serverPk) != ed25519.PublicKeySize {
			dlog.Fatalf("Unsupported public key for [%s]: [%s]", name, stamp.serverPk)
		}
		dlog.Warnf("Public key [%s] shouldn't be hex-encoded any more", string(stamp.serverPk))
		stamp.serverPk = serverPk
226
	}
227
	certInfo, rtt, err := FetchCurrentDNSCryptCert(proxy, &name, proxy.mainProto, stamp.serverPk, stamp.serverAddrStr, stamp.providerName, isNew)
228
	if err != nil {
229
		return ServerInfo{}, err
230
	}
Frank Denis's avatar
Frank Denis committed
231
	remoteUDPAddr, err := net.ResolveUDPAddr("udp", stamp.serverAddrStr)
232 233 234
	if err != nil {
		return ServerInfo{}, err
	}
Frank Denis's avatar
Frank Denis committed
235
	remoteTCPAddr, err := net.ResolveTCPAddr("tcp", stamp.serverAddrStr)
236 237 238 239
	if err != nil {
		return ServerInfo{}, err
	}
	serverInfo := ServerInfo{
240
		Proto:              StampProtoTypeDNSCrypt,
241 242 243 244
		MagicQuery:         certInfo.MagicQuery,
		ServerPk:           certInfo.ServerPk,
		SharedKey:          certInfo.SharedKey,
		CryptoConstruction: certInfo.CryptoConstruction,
245
		Name:               name,
246
		Timeout:            proxy.timeout,
247 248
		UDPAddr:            remoteUDPAddr,
		TCPAddr:            remoteTCPAddr,
249
		initialRtt:         rtt,
250 251 252
	}
	return serverInfo, nil
}
Frank Denis's avatar
Frank Denis committed
253

254
func (serversInfo *ServersInfo) fetchDoHServerInfo(proxy *Proxy, name string, stamp ServerStamp, isNew bool) (ServerInfo, error) {
255 256 257
	if len(stamp.serverAddrStr) > 0 {
		addrStr := stamp.serverAddrStr
		ipOnly := addrStr[:strings.LastIndex(addrStr, ":")]
258 259 260
		proxy.xTransport.cachedIPs.Lock()
		proxy.xTransport.cachedIPs.cache[stamp.providerName] = ipOnly
		proxy.xTransport.cachedIPs.Unlock()
261
	}
262 263 264 265 266
	url := &url.URL{
		Scheme: "https",
		Host:   stamp.providerName,
		Path:   stamp.path,
	}
267 268
	body := []byte{
		0xca, 0xfe, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
269
	}
270 271 272 273 274 275 276
	useGet := false
	if _, _, err := proxy.xTransport.DoHQuery(useGet, url, body, proxy.timeout); err != nil {
		useGet = true
		if _, _, err := proxy.xTransport.DoHQuery(useGet, url, body, proxy.timeout); err != nil {
			return ServerInfo{}, err
		}
		dlog.Debugf("Server [%s] doesn't appear to support POST; falling back to GET requests", name)
277
	}
278
	resp, rtt, err := proxy.xTransport.DoHQuery(useGet, url, body, proxy.timeout)
279
	if err != nil {
280 281
		return ServerInfo{}, err
	}
282 283 284 285
	tls := resp.TLS
	if tls == nil || !tls.HandshakeComplete {
		return ServerInfo{}, errors.New("TLS handshake failed")
	}
286
	showCerts := len(os.Getenv("SHOW_CERTS")) > 0
287 288 289 290
	found := false
	var wantedHash [32]byte
	for _, cert := range tls.PeerCertificates {
		h := sha256.Sum256(cert.RawTBSCertificate)
291 292 293 294 295
		if showCerts {
			dlog.Infof("Advertised cert: [%s] [%x]", cert.Subject, h)
		} else {
			dlog.Debugf("Advertised cert: [%s] [%x]", cert.Subject, h)
		}
296 297 298 299 300 301 302 303 304 305
		for _, hash := range stamp.hashes {
			if len(hash) == len(wantedHash) {
				copy(wantedHash[:], hash)
				if h == wantedHash {
					found = true
					break
				}
			}
		}
		if found {
306 307 308
			break
		}
	}
309
	if !found && len(stamp.hashes) > 0 {
310 311
		return ServerInfo{}, fmt.Errorf("Certificate hash [%x] not found for [%s]", wantedHash, name)
	}
312
	respBody, err := ioutil.ReadAll(io.LimitReader(resp.Body, MaxHTTPBodyLength))
313 314 315
	if err != nil {
		return ServerInfo{}, err
	}
316 317
	if len(respBody) < MinDNSPacketSize || len(respBody) > MaxDNSPacketSize ||
		respBody[0] != 0xca || respBody[1] != 0xfe || respBody[4] != 0x00 || respBody[5] != 0x01 {
318 319
		return ServerInfo{}, errors.New("Webserver returned an unexpected response")
	}
320 321 322 323 324
	if isNew {
		dlog.Noticef("[%s] OK (DoH) - rtt: %dms", name, rtt.Nanoseconds()/1000000)
	} else {
		dlog.Infof("[%s] OK (DoH) - rtt: %dms", name, rtt.Nanoseconds()/1000000)
	}
325

326 327 328 329 330 331
	serverInfo := ServerInfo{
		Proto:      StampProtoTypeDoH,
		Name:       name,
		Timeout:    proxy.timeout,
		URL:        url,
		HostName:   stamp.providerName,
332
		initialRtt: int(rtt.Nanoseconds() / 1000000),
333
		useGet:     useGet,
334 335 336 337
	}
	return serverInfo, nil
}

338 339
func (serverInfo *ServerInfo) noticeFailure(proxy *Proxy) {
	serverInfo.Lock()
340
	serverInfo.rtt.Add(float64(proxy.timeout.Nanoseconds() / 1000000))
341 342 343 344 345 346 347 348 349 350 351 352
	serverInfo.Unlock()
}

func (serverInfo *ServerInfo) noticeBegin(proxy *Proxy) {
	serverInfo.Lock()
	serverInfo.lastActionTS = time.Now()
	serverInfo.Unlock()
}

func (serverInfo *ServerInfo) noticeSuccess(proxy *Proxy) {
	now := time.Now()
	serverInfo.Lock()
353 354 355 356
	elapsed := now.Sub(serverInfo.lastActionTS)
	elapsedMs := elapsed.Nanoseconds() / 1000000
	if elapsedMs > 0 && elapsed < proxy.timeout {
		serverInfo.rtt.Add(float64(elapsedMs))
357 358
	}
	serverInfo.Unlock()
Frank Denis's avatar
Frank Denis committed
359
}