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