feat: implement refresher https fallback
rabbitprincess committed
Apr 12, 2026 at 19:03 UTC
4bf8b54f7aef8972847f5791d051cb09b0318163
3 files changed
+50
-92
portal/discovery/refresher.go
+45
-86
@@ -5,7 +5,6 @@ import (
5
"crypto/tls"
6
"crypto/x509"
7
"errors"
8
- "fmt"
8
"net/http"
9
"net/url"
10
"time"
@@ -28,11 +27,10 @@ type OverlayRuntime interface {
27
}
28
29
type Refresher struct {
31
- relaySet *RelaySet
32
- httpClient *http.Client
33
- overlay OverlayRuntime
34
- directRecoveryFailures int
35
- overlayRecoveryFailures int
30
+ relaySet *RelaySet
31
+ httpClient *http.Client
32
+ overlay OverlayRuntime
33
+ directRecoveryFailures int
34
}
35
36
func NewRefresher(relaySet *RelaySet, rootCAPEM []byte, overlay OverlayRuntime) (*Refresher, error) {
@@ -59,26 +57,22 @@ func NewRefresher(relaySet *RelaySet, rootCAPEM []byte, overlay OverlayRuntime)
57
},
58
Timeout: defaultRequestTimeout,
59
},
62
- overlay: overlay,
63
- directRecoveryFailures: defaultRecoveryFailures,
64
- overlayRecoveryFailures: defaultRecoveryFailures,
60
+ overlay: overlay,
61
+ directRecoveryFailures: defaultRecoveryFailures,
62
}, nil
63
}
64
65
func (r *Refresher) Refresh(ctx context.Context) error {
69
- if err := r.refreshHTTPS(ctx); err != nil {
70
- return err
71
- }
72
- if r.overlay == nil {
73
- return ctx.Err()
74
- }
75
- if err := r.overlay.Sync(r.relaySet.OverlayPeerStates()); err != nil {
76
- log.Warn().
77
- Err(err).
78
- Msg("sync wireguard peers")
79
- return ctx.Err()
66
+ if r.overlay != nil {
67
+ err := r.refreshOverlay(ctx)
68
+ if err == nil {
69
+ return nil
70
+ }
71
+ if ctx.Err() != nil {
72
+ return ctx.Err()
73
+ }
74
}
81
- return r.refreshOverlay(ctx)
75
+ return r.refreshHTTPS(ctx)
76
}
77
78
func (r *Refresher) refreshHTTPS(ctx context.Context) error {
@@ -94,9 +88,6 @@ func (r *Refresher) refreshHTTPS(ctx context.Context) error {
88
relay := state.Descriptor
89
baseURL, err := url.Parse(relay.APIHTTPSAddr)
90
if err != nil {
97
- if ctx.Err() != nil {
98
- return ctx.Err()
99
- }
91
continue
92
}
93
if utils.IsLocalRelayHost(baseURL.Hostname()) {
@@ -105,6 +96,7 @@ func (r *Refresher) refreshHTTPS(ctx context.Context) error {
96
Msg("skip loopback relay as discovery source")
97
continue
98
}
99
+
100
var resp types.DiscoveryResponse
101
if err := utils.HTTPDoAPIPath(ctx, r.httpClient, baseURL, http.MethodGet, types.PathDiscovery, nil, nil, &resp); err != nil {
102
if ctx.Err() != nil {
@@ -113,9 +105,7 @@ func (r *Refresher) refreshHTTPS(ctx context.Context) error {
105
continue
106
}
107
116
- now := time.Now().UTC()
117
- _, err = r.relaySet.ApplyRelayDiscoveryResponse(relay.Identity, relay.APIHTTPSAddr, resp, now)
118
- if err != nil {
108
+ if _, err := r.relaySet.ApplyRelayDiscoveryResponse(relay.Identity, relay.APIHTTPSAddr, resp, time.Now().UTC()); err != nil {
109
continue
110
}
111
}
@@ -132,16 +122,10 @@ func (r *Refresher) refreshHTTPS(ctx context.Context) error {
122
if !state.discoverable(now) || state.Bootstrap {
123
continue
124
}
135
- if r.overlay != nil && state.Descriptor.SupportsOverlayPeer {
136
- continue
137
- }
125
relay := state.Descriptor
126
baseURL, err := url.Parse(relay.APIHTTPSAddr)
127
if err != nil {
141
- if ctx.Err() != nil {
142
- return ctx.Err()
143
- }
144
- r.logDirectDiscoveryFailure(relay, fmt.Errorf("parse discovery base url: %w", err), r.directRecoveryFailures)
128
+ r.logDirectDiscoveryFailure(relay, err, r.directRecoveryFailures)
129
continue
130
}
131
if utils.IsLocalRelayHost(baseURL.Hostname()) {
@@ -150,6 +134,7 @@ func (r *Refresher) refreshHTTPS(ctx context.Context) error {
134
Msg("skip loopback relay as discovery source")
135
continue
136
}
137
+
138
var resp types.DiscoveryResponse
139
if err := utils.HTTPDoAPIPath(ctx, r.httpClient, baseURL, http.MethodGet, types.PathDiscovery, nil, nil, &resp); err != nil {
140
if ctx.Err() != nil {
@@ -159,74 +144,48 @@ func (r *Refresher) refreshHTTPS(ctx context.Context) error {
144
continue
145
}
146
162
- now := time.Now().UTC()
163
- _, err = r.relaySet.ApplyRelayDiscoveryResponse(relay.Identity, relay.APIHTTPSAddr, resp, now)
164
- if err != nil {
147
+ if _, err := r.relaySet.ApplyRelayDiscoveryResponse(relay.Identity, relay.APIHTTPSAddr, resp, time.Now().UTC()); err != nil {
148
r.logDirectDiscoveryFailure(relay, err, r.directRecoveryFailures)
149
continue
150
}
151
}
169
- return ctx.Err()
152
+ return nil
153
}
154
155
func (r *Refresher) refreshOverlay(ctx context.Context) error {
173
- r.relaySet.mu.RLock()
174
- states := r.relaySet.relayStatesLocked()
175
- r.relaySet.mu.RUnlock()
176
-
177
- now := time.Now().UTC()
156
+ states := r.relaySet.OverlayPeerStates()
157
+ if len(states) == 0 {
158
+ return errors.New("overlay discovery unavailable")
159
+ }
160
+ if err := r.overlay.Sync(states); err != nil {
161
+ log.Warn().
162
+ Err(err).
163
+ Msg("sync wireguard peers")
164
+ return err
165
+ }
166
for _, state := range states {
179
- if !state.discoverable(now) || state.Bootstrap || !state.Descriptor.SupportsOverlayPeer {
180
- continue
181
- }
167
relay := state.Descriptor
183
- var failureErr error
184
-
168
resp, err := r.overlay.DiscoverRelay(ctx, relay)
169
if err != nil {
187
- if ctx.Err() != nil {
188
- return ctx.Err()
189
- }
190
- failureErr = err
191
- } else {
192
- now := time.Now().UTC()
193
- relaySetChanged, err := r.relaySet.ApplyRelayDiscoveryResponse(relay.Identity, relay.APIHTTPSAddr, resp, now)
194
- if relaySetChanged {
195
- if syncErr := r.overlay.Sync(r.relaySet.OverlayPeerStates()); syncErr != nil {
196
- log.Warn().
197
- Err(syncErr).
198
- Str("relay", relay.APIHTTPSAddr).
199
- Msg("sync wireguard peers")
200
- }
201
- }
202
- if err != nil {
203
- failureErr = err
204
- } else {
205
- continue
206
- }
170
+ return err
171
}
172
209
- expired, expireReason, consecutiveFailures := r.relaySet.RecordDiscoveryFailure(relay.Identity, relay.APIHTTPSAddr, failureErr, r.overlayRecoveryFailures)
210
- if expired {
211
- if syncErr := r.overlay.Sync(r.relaySet.OverlayPeerStates()); syncErr != nil && failureErr == nil {
212
- failureErr = syncErr
213
- }
173
+ relaySetChanged, err := r.relaySet.ApplyRelayDiscoveryResponse(relay.Identity, relay.APIHTTPSAddr, resp, time.Now().UTC())
174
+ if err != nil {
175
+ return err
176
}
215
-
216
- event := log.Warn().
217
- Err(failureErr).
218
- Str("relay", relay.APIHTTPSAddr)
219
- if expired {
220
- event = event.
221
- Bool("expired", true).
222
- Str("reason", expireReason)
223
- if consecutiveFailures > 0 {
224
- event = event.Int("consecutive_failures", consecutiveFailures)
225
- }
177
+ if !relaySetChanged {
178
+ continue
179
+ }
180
+ if err := r.overlay.Sync(r.relaySet.OverlayPeerStates()); err != nil {
181
+ log.Warn().
182
+ Err(err).
183
+ Str("relay", relay.APIHTTPSAddr).
184
+ Msg("sync wireguard peers")
185
+ return err
186
}
227
- event.Msg("overlay relay discovery failed")
187
}
229
- return ctx.Err()
188
+ return nil
189
}
190
191
func (r *Refresher) logDirectDiscoveryFailure(relay types.RelayDescriptor, err error, recoveryFailures int) {
portal/discovery/relayset.go
+5
@@ -113,6 +113,11 @@ func (s *RelaySet) OverlayPeerStates() []RelayState {
113
if !state.discoverable(now) || !state.Descriptor.SupportsOverlayPeer {
114
continue
115
}
116
+ if state.Descriptor.WireGuardPublicKey == "" ||
117
+ state.Descriptor.WireGuardEndpoint == "" ||
118
+ state.Descriptor.OverlayIPv4 == "" {
119
+ continue
120
+ }
121
out = append(out, state)
122
}
123
if len(out) == 0 {
portal/discovery/relaystate.go
-6
@@ -71,12 +71,6 @@ func (state RelayState) discoverable(now time.Time) bool {
71
if !state.Descriptor.ExpiresAt.After(now) {
72
return false
73
}
74
- if state.Descriptor.SupportsOverlayPeer &&
75
- (state.Descriptor.WireGuardPublicKey == "" ||
76
- state.Descriptor.WireGuardEndpoint == "" ||
77
- state.Descriptor.OverlayIPv4 == "") {
78
- return false
79
- }
74
return true
75
}
76