feat: Refactor lease registration and hostname handling for improved clarity and functionality
Kim committed
May 6, 2026 at 17:25 UTC
400e7eda5ff279b89ca879fdcbbafa4936bc8b56
6 files changed
+58
-69
portal/lease.go
+7
-4
@@ -335,14 +335,13 @@ func (r *leaseRegistry) Register(req types.RegisterChallengeRequest, clientIP, r
335
336
resp := types.RegisterResponse{
337
Identity: record.Identity,
338
- Hostname: record.Hostname,
338
ExpiresAt: record.ExpiresAt,
339
AccessToken: accessToken,
340
+ SNIPort: r.sniPort,
341
UDPEnabled: record.datagram != nil,
342
TCPEnabled: record.tcpPort != nil,
343
}
344
if record.datagram != nil {
345
- resp.SNIPort = r.sniPort
345
resp.UDPAddr = fmt.Sprintf("%s:%d", r.rootHostname, record.datagram.UDPPort())
346
}
347
if record.tcpPort != nil {
@@ -469,7 +468,7 @@ func (r *leaseRegistry) RegisterHopRoute(route *types.HopRoute, now time.Time) (
468
case !expiresAt.After(now):
469
return nil, errors.New("route expiry must be in the future")
470
case matchToken != "" && hasPublicMatcher:
472
- return nil, errors.New("hostname and token matchers are mutually exclusive")
471
+ return nil, errors.New("route and token matchers are mutually exclusive")
472
case matchToken == "" && routeHostname == "":
473
return nil, errors.New("route hostname or token matcher is required")
474
case overlayErr != nil:
@@ -779,7 +778,11 @@ func (r *leaseRegistry) deleteRecord(i int) {
778
func (r *leaseRegistry) publicLease(record *leaseRecord) types.Lease {
779
name := record.Name
780
hostname := record.Hostname
782
- if record.HostnameHash != "" && record.Hostname != "" {
781
+ if record.stream != nil && record.HostnameHash != "" {
782
+ if publicHostname, err := utils.LeaseHostname(record.Name, r.rootHostname); err == nil && utils.HostnameHash(publicHostname) == record.HostnameHash {
783
+ hostname = publicHostname
784
+ }
785
+ } else if record.HostnameHash != "" && record.Hostname != "" {
786
label, _, _ := strings.Cut(record.Hostname, ".")
787
name = label
788
}
portal/lease_test.go
+12
-13
@@ -96,7 +96,7 @@ func TestLeaseRegistryAutomaticECHRouteFallsBackToPlainSNI(t *testing.T) {
96
registry := newTestRegistry(t)
97
routeHostname := "ech-auto-ech.example.com"
98
publicHostname := "auto-ech.example.com"
99
- record, registered, err := registry.Register(types.RegisterChallengeRequest{
99
+ record, _, err := registry.Register(types.RegisterChallengeRequest{
100
Identity: newTestLeaseIdentity(t, "auto-ech"),
101
RouteHostname: routeHostname,
102
HostnameHash: utils.HostnameHash(publicHostname),
@@ -104,34 +104,33 @@ func TestLeaseRegistryAutomaticECHRouteFallsBackToPlainSNI(t *testing.T) {
104
if err != nil {
105
t.Fatalf("Register() error = %v", err)
106
}
107
- if registered.Hostname != routeHostname {
108
- t.Fatalf("Register() hostname = %q, want route hostname %q", registered.Hostname, routeHostname)
107
+ if record.Hostname != routeHostname {
108
+ t.Fatalf("Register() route hostname = %q, want %q", record.Hostname, routeHostname)
109
}
110
- if registered.Hostname == publicHostname {
111
- t.Fatalf("Register() hostname = public hostname = %q", registered.Hostname)
110
+ if record.Hostname == publicHostname {
111
+ t.Fatalf("Register() route hostname = public hostname = %q", record.Hostname)
112
}
113
if lookedUp, ok := registry.Lookup(publicHostname); !ok || lookedUp != record {
114
t.Fatalf("Lookup(public hostname) = %v, %v, want fallback lease", lookedUp, ok)
115
}
116
- lookedUp, ok := registry.Lookup(registered.Hostname)
116
+ lookedUp, ok := registry.Lookup(record.Hostname)
117
if !ok || lookedUp != record {
118
t.Fatalf("Lookup(route hostname) = %v, %v, want registered lease", lookedUp, ok)
119
}
120
leases := registry.PublicLeases(time.Now())
121
- publicHostnameFromLease := ""
122
- if len(leases) == 1 {
123
- publicHostnameFromLease = leases[0].Hostname
121
+ if len(leases) != 1 {
122
+ t.Fatalf("PublicLeases() length = %d, want 1", len(leases))
123
}
125
- if len(leases) != 1 || publicHostnameFromLease != registered.Hostname {
126
- t.Fatalf("PublicLeases() hostname = len %d, host %q, want len 1, host %q", len(leases), publicHostnameFromLease, registered.Hostname)
124
+ if leases[0].Hostname != publicHostname {
125
+ t.Fatalf("PublicLeases()[0].Hostname = %q, want %q", leases[0].Hostname, publicHostname)
126
}
127
128
adminLeases := registry.AdminLeases(time.Now())
129
if len(adminLeases) != 1 {
130
t.Fatalf("AdminLeases() length = %d, want 1", len(adminLeases))
131
}
133
- if adminLeases[0].Hostname != registered.Hostname {
134
- t.Fatalf("AdminLeases()[0] hostname = %q, want %q", adminLeases[0].Hostname, registered.Hostname)
132
+ if adminLeases[0].Hostname != publicHostname {
133
+ t.Fatalf("AdminLeases()[0] hostname = %q, want %q", adminLeases[0].Hostname, publicHostname)
134
}
135
136
if _, _, err := registry.Register(types.RegisterChallengeRequest{
portal/server_test.go
+6
-6
@@ -263,7 +263,7 @@ func TestServerStartDomainReportsCompatibilityInfo(t *testing.T) {
263
}
264
}
265
266
-func TestRegisterLeaseOmitsSNIPortWithoutUDP(t *testing.T) {
266
+func TestRegisterLeaseIncludesSNIPortForPublicIngress(t *testing.T) {
267
t.Parallel()
268
269
server, err := NewServer(ServerConfig{
@@ -292,8 +292,8 @@ func TestRegisterLeaseOmitsSNIPortWithoutUDP(t *testing.T) {
292
record.Close()
293
})
294
295
- if resp.SNIPort != 0 {
296
- t.Fatalf("RegisterResponse.SNIPort = %d, want 0 without udp", resp.SNIPort)
295
+ if resp.SNIPort != server.cfg.SNIPort {
296
+ t.Fatalf("RegisterResponse.SNIPort = %d, want %d", resp.SNIPort, server.cfg.SNIPort)
297
}
298
}
299
@@ -374,7 +374,7 @@ func TestRegisterLeaseDerivesFixedHostnameFromName(t *testing.T) {
374
t.Fatalf("NewServer() error = %v", err)
375
}
376
377
- record, resp, err := server.registry.Register(types.RegisterChallengeRequest{
377
+ record, _, err := server.registry.Register(types.RegisterChallengeRequest{
378
Identity: types.Identity{
379
Name: "Demo-App",
380
Address: server.identity.Address,
@@ -385,8 +385,8 @@ func TestRegisterLeaseDerivesFixedHostnameFromName(t *testing.T) {
385
}
386
387
wantHostname := "demo-app.portal.example.com"
388
- if resp.Hostname != wantHostname {
389
- t.Fatalf("registry.Register() hostname = %q, want %q", resp.Hostname, wantHostname)
388
+ if record.Hostname != wantHostname {
389
+ t.Fatalf("registry.Register() route hostname = %q, want %q", record.Hostname, wantHostname)
390
}
391
392
lease := server.registry.publicLease(record)
sdk/api_client.go
+21
-36
@@ -87,7 +87,7 @@ func (l *listener) initHTTPTransport(ctx context.Context) error {
87
return nil
88
}
89
90
-func (l *listener) registerLease(ctx context.Context, ttl time.Duration, udpEnabled, tcpEnabled bool) (types.RegisterResponse, []types.HopRoute, string, error) {
90
+func (l *listener) registerLease(ctx context.Context, ttl time.Duration, udpEnabled, tcpEnabled bool) (types.RegisterResponse, []types.HopRoute, string, string, error) {
91
var exitHopToken string
92
var publicHostname string
93
var routeHostname string
@@ -96,27 +96,15 @@ func (l *listener) registerLease(ctx context.Context, ttl time.Duration, udpEnab
96
var hopPath []types.RelayDescriptor
97
streamLease := !udpEnabled && !tcpEnabled
98
registerIdentity := l.identity
99
- if streamLease {
100
- token, err := l.identity.DeriveToken("opaque-lease-name", l.identity.Name)
101
- if err != nil {
102
- return types.RegisterResponse{}, nil, "", fmt.Errorf("derive opaque lease name: %w", err)
103
- }
104
- sum := sha256.Sum256([]byte(token))
105
- label := "lid-" + strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(sum[:20]))
106
- registerIdentity.Name, err = utils.NormalizeDNSLabel(label)
107
- if err != nil {
108
- return types.RegisterResponse{}, nil, "", fmt.Errorf("derive opaque lease name: %w", err)
109
- }
110
- }
99
if len(l.multiHop) > 0 {
100
if !streamLease {
113
- return types.RegisterResponse{}, nil, "", errors.New("multi-hop requires stream lease")
101
+ return types.RegisterResponse{}, nil, "", "", errors.New("multi-hop requires stream lease")
102
}
103
if len(l.multiHop) < 2 {
116
- return types.RegisterResponse{}, nil, "", errors.New("multi-hop requires at least entry and exit relay urls")
104
+ return types.RegisterResponse{}, nil, "", "", errors.New("multi-hop requires at least entry and exit relay urls")
105
}
106
if l.relaySet == nil {
119
- return types.RegisterResponse{}, nil, "", errors.New("multi-hop relay set is unavailable")
107
+ return types.RegisterResponse{}, nil, "", "", errors.New("multi-hop relay set is unavailable")
108
}
109
110
now := time.Now().UTC()
@@ -124,31 +112,31 @@ func (l *listener) registerLease(ctx context.Context, ttl time.Duration, udpEnab
112
for i, relayURL := range l.multiHop {
113
desc, ok := l.relaySet.OverlayRelayDescriptor(relayURL, now)
114
if !ok {
127
- return types.RegisterResponse{}, nil, "", fmt.Errorf("multi-hop relay %d descriptor is unavailable", i)
115
+ return types.RegisterResponse{}, nil, "", "", fmt.Errorf("multi-hop relay %d descriptor is unavailable", i)
116
}
117
hopPath = append(hopPath, desc)
118
}
119
120
rootHostname = utils.PortalRootHost(hopPath[0].APIHTTPSAddr)
133
- } else if streamLease {
121
+ } else {
122
rootHostname = utils.PortalRootHost(l.relayURL.String())
123
}
124
125
+ var err error
126
+ publicHostname, err = utils.LeaseHostname(l.identity.Name, rootHostname)
127
+ if err != nil {
128
+ return types.RegisterResponse{}, nil, "", "", err
129
+ }
130
if streamLease {
138
- var err error
139
- publicHostname, err = utils.LeaseHostname(l.identity.Name, rootHostname)
140
- if err != nil {
141
- return types.RegisterResponse{}, nil, "", err
142
- }
131
routeToken, err := l.identity.DeriveToken("ech-route", publicHostname, rootHostname)
132
if err != nil {
145
- return types.RegisterResponse{}, nil, "", err
133
+ return types.RegisterResponse{}, nil, "", "", err
134
}
135
routeSum := sha256.Sum256([]byte(routeToken))
136
routeLabel := "ech-" + strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(routeSum[:20]))
137
routeHostname, err = utils.LeaseHostname(routeLabel, rootHostname)
138
if err != nil {
151
- return types.RegisterResponse{}, nil, "", err
139
+ return types.RegisterResponse{}, nil, "", "", err
140
}
141
}
142
@@ -164,7 +152,7 @@ func (l *listener) registerLease(ctx context.Context, ttl time.Duration, udpEnab
152
hopPath[i+1].APIHTTPSAddr,
153
)
154
if err != nil {
167
- return types.RegisterResponse{}, nil, "", err
155
+ return types.RegisterResponse{}, nil, "", "", err
156
}
157
forwardToken := "hpt_" + token
158
route := types.HopRoute{
@@ -201,12 +189,12 @@ func (l *listener) registerLease(ctx context.Context, ttl time.Duration, udpEnab
189
190
var challenge types.RegisterChallengeResponse
191
if err := utils.HTTPDoAPIPath(ctx, l.httpClient, l.relayURL, http.MethodPost, types.PathSDKRegisterChallenge, registerReq, nil, &challenge); err != nil {
204
- return types.RegisterResponse{}, nil, "", err
192
+ return types.RegisterResponse{}, nil, "", "", err
193
}
194
195
signature, err := utils.SignEthereumPersonalMessage(challenge.SIWEMessage, l.identity.PrivateKey)
196
if err != nil {
209
- return types.RegisterResponse{}, nil, "", err
197
+ return types.RegisterResponse{}, nil, "", "", err
198
}
199
200
var resp types.RegisterResponse
@@ -216,27 +204,24 @@ func (l *listener) registerLease(ctx context.Context, ttl time.Duration, udpEnab
204
SIWESignature: signature,
205
ReportedIP: utils.ResolvePublicIP(ctx),
206
}, nil, &resp); err != nil {
219
- return types.RegisterResponse{}, nil, "", err
207
+ return types.RegisterResponse{}, nil, "", "", err
208
}
209
registeredIdentity, err := utils.NormalizeIdentity(resp.Identity)
210
if err != nil {
211
_ = l.unregisterLease(context.Background(), resp.AccessToken, hopRoutes)
224
- return types.RegisterResponse{}, nil, "", err
212
+ return types.RegisterResponse{}, nil, "", "", err
213
}
214
if registeredIdentity.Key() != registerIdentity.Key() {
215
_ = l.unregisterLease(context.Background(), resp.AccessToken, hopRoutes)
228
- return types.RegisterResponse{}, nil, "", errors.New("relay returned mismatched lease identity")
216
+ return types.RegisterResponse{}, nil, "", "", errors.New("relay returned mismatched lease identity")
217
}
218
if len(hopRoutes) > 0 {
219
if err := l.syncHopRoutes(ctx, http.MethodPost, resp.ExpiresAt, hopRoutes); err != nil {
220
_ = l.unregisterLease(context.Background(), resp.AccessToken, hopRoutes)
233
- return types.RegisterResponse{}, nil, "", err
221
+ return types.RegisterResponse{}, nil, "", "", err
222
}
223
}
236
- if publicHostname != "" {
237
- resp.Hostname = publicHostname
238
- }
239
- return resp, hopRoutes, routeHostname, nil
224
+ return resp, hopRoutes, publicHostname, routeHostname, nil
225
}
226
227
func (l *listener) renewRegisteredLease(ctx context.Context, ttl time.Duration, accessToken string, hopRoutes []types.HopRoute) (types.RenewResponse, error) {
sdk/listener.go
+12
-9
@@ -379,8 +379,13 @@ func (l *listener) publicURLForLease(lease listenerLease) string {
379
}
380
381
host := lease.hostname
382
- if port := baseURL.Port(); port != "" {
383
- host = net.JoinHostPort(lease.hostname, port)
382
+ sniPort := lease.sniPort
383
+ scheme := strings.ToLower(strings.TrimSpace(baseURL.Scheme))
384
+ if (scheme == "https" && sniPort == 443) || (scheme == "http" && sniPort == 80) {
385
+ sniPort = 0
386
+ }
387
+ if sniPort > 0 {
388
+ host = net.JoinHostPort(lease.hostname, fmt.Sprintf("%d", sniPort))
389
}
390
391
return (&url.URL{
@@ -697,7 +702,7 @@ func (l *listener) registerAndConfigure(ctx context.Context) error {
702
return err
703
}
704
700
- resp, hopRoutes, routeHostname, err := l.registerLease(ctx, l.leaseTTL, l.udpEnabled, l.tcpEnabled)
705
+ resp, hopRoutes, publicHostname, routeHostname, err := l.registerLease(ctx, l.leaseTTL, l.udpEnabled, l.tcpEnabled)
706
if err != nil {
707
return err
708
}
@@ -729,7 +734,7 @@ func (l *listener) registerAndConfigure(ctx context.Context) error {
734
var echKeys []tls.EncryptedClientHelloKey
735
var echConfigList []byte
736
if routeHostname != "" {
732
- echSeed, err := l.identity.DeriveToken("tenant-ech", resp.Hostname, routeHostname)
737
+ echSeed, err := l.identity.DeriveToken("tenant-ech", publicHostname, routeHostname)
738
if err != nil {
739
_ = l.unregisterLease(context.Background(), resp.AccessToken, hopRoutes)
740
return fmt.Errorf("derive tenant ech seed: %w", err)
@@ -742,7 +747,7 @@ func (l *listener) registerAndConfigure(ctx context.Context) error {
747
echConfigList = keyless.EncryptedClientHelloConfigList(echKeys)
748
}
749
745
- tlsConf, tenantTLSCloser, err := keyless.BuildClientTLSConfig(keylessURL, resp.Hostname, echKeys)
750
+ tlsConf, tenantTLSCloser, err := keyless.BuildClientTLSConfig(keylessURL, publicHostname, echKeys)
751
if err != nil {
752
_ = l.unregisterLease(context.Background(), resp.AccessToken, hopRoutes)
753
if tenantTLSCloser != nil {
@@ -759,20 +764,18 @@ func (l *listener) registerAndConfigure(ctx context.Context) error {
764
return ctx.Err()
765
}
766
next := &listenerLease{
762
- hostname: resp.Hostname,
767
+ hostname: publicHostname,
768
echConfigList: echConfigList,
769
udpAddr: resp.UDPAddr,
770
tcpAddr: resp.TCPAddr,
771
accessToken: resp.AccessToken,
772
expiresAt: resp.ExpiresAt,
773
+ sniPort: resp.SNIPort,
774
publicURLBase: publicURLBase,
775
tlsConfig: tlsConf,
776
tlsCloser: tenantTLSCloser,
777
hopRoutes: hopRoutes,
778
}
773
- if l.udpEnabled {
774
- next.sniPort = resp.SNIPort
775
- }
779
l.leaseMu.Lock()
780
oldLease := l.lease
781
l.lease = next
types/api.go
-1
@@ -83,7 +83,6 @@ type RegisterChallengeResponse struct {
83
type RegisterResponse struct {
84
Identity Identity `json:"identity"`
85
ExpiresAt time.Time `json:"expires_at"`
86
- Hostname string `json:"hostname"`
86
AccessToken string `json:"access_token"`
87
SNIPort int `json:"sni_port,omitempty"`
88
UDPAddr string `json:"udp_addr,omitempty"`