summaryrefslogtreecommitdiff
path: root/tools/network/telemetryURIUpdateService.go
blob: 66dd87dd0ec2e18c224f039d110f7790730246c0 (plain)
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
// Copyright (C) 2019-2024 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand.  If not, see <https://www.gnu.org/licenses/>.

package network

import (
	"net/url"
	"strings"
	"time"

	"github.com/algorand/go-algorand/config"
	"github.com/algorand/go-algorand/logging"
	"github.com/algorand/go-algorand/protocol"
)

type telemetrySrvReader interface {
	readFromSRV(protocol string, bootstrapID string) (addrs []string, err error)
}

type telemetryURIUpdater struct {
	interval       time.Duration
	cfg            config.Local
	genesisNetwork protocol.NetworkID
	log            logging.Logger
	abort          chan struct{}
	srvReader      telemetrySrvReader
}

// StartTelemetryURIUpdateService starts a go routine which queries SRV records for a telemetry URI every <interval>
func StartTelemetryURIUpdateService(interval time.Duration, cfg config.Local, genesisNetwork protocol.NetworkID, log logging.Logger, abort chan struct{}) {
	updater := &telemetryURIUpdater{
		interval:       interval,
		cfg:            cfg,
		genesisNetwork: genesisNetwork,
		log:            log,
		abort:          abort,
	}
	updater.srvReader = updater
	updater.Start()

}
func (t *telemetryURIUpdater) Start() {
	go func() {
		ticker := time.NewTicker(t.interval)
		defer ticker.Stop()

		updateTelemetryURI := func() {
			endpointURL := t.lookupTelemetryURL()

			if endpointURL != nil && endpointURL.String() != t.log.GetTelemetryURI() && false == t.cfg.DisableNetworking {
				err := t.log.UpdateTelemetryURI(endpointURL.String())
				if err != nil {
					t.log.Warnf("Unable to update telemetry URI to '%s' : %v", endpointURL.String(), err)
				}
			}
		}

		// Update telemetry right away, followed by once every <interval>
		updateTelemetryURI()
		for {
			select {
			case <-ticker.C:
				updateTelemetryURI()
			case <-t.abort:
				return
			}
		}
	}()
}

// TODO: Support secondary telemetry SRV record lookup
func (t *telemetryURIUpdater) lookupTelemetryURL() (url *url.URL) {
	bootstrapArray := t.cfg.DNSBootstrapArray(t.genesisNetwork)
	bootstrapArray = append(bootstrapArray, &config.DNSBootstrap{PrimarySRVBootstrap: "default.algodev.network"})
	for _, dnsBootstrap := range bootstrapArray {
		addrs, err := t.srvReader.readFromSRV("tls", dnsBootstrap.PrimarySRVBootstrap)
		if err != nil {
			t.log.Infof("An issue occurred reading telemetry entry for '_telemetry._tls.%s': %v", dnsBootstrap.PrimarySRVBootstrap, err)
		} else if len(addrs) == 0 {
			t.log.Infof("No telemetry entry for: '_telemetry._tls.%s'", dnsBootstrap.PrimarySRVBootstrap)
		} else {
			for _, addr := range addrs {
				// the addr that we received from ReadFromSRV contains host:port, we need to prefix that with the schema. since it's the tls, we want to use https.
				url, err = url.Parse("https://" + addr)
				if err != nil {
					t.log.Infof("a telemetry endpoint '%s' was retrieved for '_telemerty._tls.%s'. This does not seems to be a valid endpoint and will be ignored(%v).", addr, dnsBootstrap.PrimarySRVBootstrap, err)
					continue
				}
				return url
			}
		}

		addrs, err = t.srvReader.readFromSRV("tcp", dnsBootstrap.PrimarySRVBootstrap)
		if err != nil {
			t.log.Infof("An issue occurred reading telemetry entry for '_telemetry._tcp.%s': %v", dnsBootstrap.PrimarySRVBootstrap, err)
		} else if len(addrs) == 0 {
			t.log.Infof("No telemetry entry for: '_telemetry._tcp.%s'", dnsBootstrap.PrimarySRVBootstrap)
		} else {
			for _, addr := range addrs {
				if strings.HasPrefix(addr, "https://") {
					// the addr that we received from ReadFromSRV should contain host:port. however, in some cases, it might contain a https prefix, where we want to take it as is.
					url, err = url.Parse(addr)
				} else {
					// the addr that we received from ReadFromSRV contains host:port, we need to prefix that with the schema. since it's the tcp, we want to use http.
					url, err = url.Parse("http://" + addr)
				}

				if err != nil {
					t.log.Infof("a telemetry endpoint '%s' was retrieved for '_telemerty._tcp.%s'. This does not seems to be a valid endpoint and will be ignored(%v).", addr, dnsBootstrap.PrimarySRVBootstrap, err)
					continue
				}
				return url
			}
		}
	}

	t.log.Warn("No telemetry endpoint was found.")
	return nil
}

func (t *telemetryURIUpdater) readFromSRV(protocol string, bootstrapID string) (addrs []string, err error) {
	return ReadFromSRV("telemetry", protocol, bootstrapID, t.cfg.FallbackDNSResolverAddress, t.cfg.DNSSecuritySRVEnforced())
}