refact: remove type alias

Kim committed Mar 5, 2026 at 10:10 UTC 917d33d040a6a1c73036e737015bc497419d1217
16 files changed +119 -179
cmd/portal-tunnel/main.go
+6 -6
@@ -17,9 +17,9 @@ import (
17 "github.com/rs/zerolog"
18 "github.com/rs/zerolog/log"
19
20 - "gosuda.org/portal/portal/contracts"
20 "gosuda.org/portal/portal/netutil"
21 "gosuda.org/portal/sdk"
22 + "gosuda.org/portal/types"
23 )
24
25 var (
@@ -86,11 +86,11 @@ func runTunnel() error {
86
87 listener, err := sdkClient.Listen(
88 flagName,
89 - contracts.WithDescription(flagDesc),
90 - contracts.WithTags(netutil.ParseURLs(flagTags)),
91 - contracts.WithOwner(flagOwner),
92 - contracts.WithThumbnail(flagThumbnail),
93 - contracts.WithHide(flagHide),
89 + types.WithDescription(flagDesc),
90 + types.WithTags(netutil.ParseURLs(flagTags)),
91 + types.WithOwner(flagOwner),
92 + types.WithThumbnail(flagThumbnail),
93 + types.WithHide(flagHide),
94 )
95 if err != nil {
96 return fmt.Errorf("service %s: failed to register service: %w", flagName, err)
cmd/relay-server/registry.go
+3 -3
@@ -254,7 +254,7 @@ func (r *SDKRegistry) decodeRequestBody(w http.ResponseWriter, req *http.Request
254 return true
255 }
256
257 -func writeRegistryError(w http.ResponseWriter, apiErr *controlplaneregistry.APIError) bool {
257 +func writeRegistryError(w http.ResponseWriter, apiErr *types.APIError) bool {
258 if apiErr == nil {
259 return true
260 }
@@ -277,7 +277,7 @@ func (b *relayRegistryBackend) BaseHost() string {
277 return b.serv.BaseHost
278 }
279
280 -func (b *relayRegistryBackend) UpdateLease(lease *portal.Lease) bool {
280 +func (b *relayRegistryBackend) UpdateLease(lease *types.Lease) bool {
281 if b.serv == nil || b.serv.GetLeaseManager() == nil {
282 return false
283 }
@@ -291,7 +291,7 @@ func (b *relayRegistryBackend) DeleteLease(leaseID string) bool {
291 return b.serv.GetLeaseManager().DeleteLease(leaseID)
292 }
293
294 -func (b *relayRegistryBackend) GetLeaseByID(leaseID string) (*portal.LeaseEntry, bool) {
294 +func (b *relayRegistryBackend) GetLeaseByID(leaseID string) (*types.LeaseEntry, bool) {
295 if b.serv == nil || b.serv.GetLeaseManager() == nil {
296 return nil, false
297 }
cmd/relay-server/serve.go
+9 -9
@@ -16,10 +16,10 @@ import (
16 "github.com/rs/zerolog/log"
17
18 "gosuda.org/portal/portal"
19 - "gosuda.org/portal/portal/contracts"
19 "gosuda.org/portal/portal/keyless"
20 "gosuda.org/portal/portal/netutil"
21 "gosuda.org/portal/portal/policy"
22 + "gosuda.org/portal/types"
23 )
24
25 const defaultHTTPSPort = "443"
@@ -42,21 +42,21 @@ func serveAPI(addr string, serv *portal.RelayServer, admin *Admin, frontend *Fro
42 frontend.ServeAsset(appMux, "/favicon.svg", "favicon.svg", "image/svg+xml")
43
44 // Portal app assets (JS, CSS, etc.) - served from /app/
45 - appMux.HandleFunc(contracts.PathAppPrefix, func(w http.ResponseWriter, r *http.Request) {
45 + appMux.HandleFunc(types.PathAppPrefix, func(w http.ResponseWriter, r *http.Request) {
46 setCORSHeaders(w)
47 if r.Method == http.MethodOptions {
48 w.WriteHeader(http.StatusOK)
49 return
50 }
51 - p := strings.TrimPrefix(r.URL.Path, contracts.PathAppPrefix)
51 + p := strings.TrimPrefix(r.URL.Path, types.PathAppPrefix)
52 frontend.ServeAppStatic(w, r, p, serv)
53 })
54
55 // Tunnel installer script and binaries
56 - appMux.HandleFunc(contracts.PathTunnelScript, func(w http.ResponseWriter, r *http.Request) {
56 + appMux.HandleFunc(types.PathTunnelScript, func(w http.ResponseWriter, r *http.Request) {
57 serveTunnelScript(w, r, cfg.PortalURL)
58 })
59 - appMux.HandleFunc(contracts.PathTunnelBinary, func(w http.ResponseWriter, r *http.Request) {
59 + appMux.HandleFunc(types.PathTunnelBinary, func(w http.ResponseWriter, r *http.Request) {
60 serveTunnelBinary(w, r)
61 })
62
@@ -70,12 +70,12 @@ func serveAPI(addr string, serv *portal.RelayServer, admin *Admin, frontend *Fro
70 portalURL: cfg.PortalURL,
71 trustProxyHeaders: cfg.TrustProxyHeaders,
72 }
73 - appMux.HandleFunc(contracts.PathSDKPrefix, func(w http.ResponseWriter, r *http.Request) {
73 + appMux.HandleFunc(types.PathSDKPrefix, func(w http.ResponseWriter, r *http.Request) {
74 registry.HandleSDKRequest(w, r, serv)
75 })
76
77 // Keyless signer endpoint.
78 - appMux.HandleFunc(contracts.PathKeylessSign, func(w http.ResponseWriter, r *http.Request) {
78 + appMux.HandleFunc(types.PathKeylessSign, func(w http.ResponseWriter, r *http.Request) {
79 handleKeylessSign(w, r, serv.GetKeylessSigner())
80 })
81
@@ -86,7 +86,7 @@ func serveAPI(addr string, serv *portal.RelayServer, admin *Admin, frontend *Fro
86 frontend.ServeAppStatic(w, r, p, serv)
87 })
88
89 - appMux.HandleFunc(contracts.PathHealthz, func(w http.ResponseWriter, _ *http.Request) {
89 + appMux.HandleFunc(types.PathHealthz, func(w http.ResponseWriter, _ *http.Request) {
90 w.WriteHeader(http.StatusOK)
91 if _, err := w.Write([]byte("{\"status\":\"ok\"}")); err != nil {
92 log.Debug().Err(err).Msg("[healthz] failed to write response")
@@ -94,7 +94,7 @@ func serveAPI(addr string, serv *portal.RelayServer, admin *Admin, frontend *Fro
94 })
95
96 // Admin API
97 - appMux.HandleFunc(contracts.PathAdminPrefix+"/", func(w http.ResponseWriter, r *http.Request) {
97 + appMux.HandleFunc(types.PathAdminPrefix+"/", func(w http.ResponseWriter, r *http.Request) {
98 admin.HandleAdminRequest(w, r, serv)
99 })
100
cmd/relay-server/utils.go
+6 -6
@@ -11,10 +11,10 @@ import (
11 "github.com/rs/zerolog/log"
12
13 "gosuda.org/portal/portal"
14 - "gosuda.org/portal/portal/contracts"
14 "gosuda.org/portal/portal/keyless"
15 "gosuda.org/portal/portal/netutil"
16 "gosuda.org/portal/portal/policy"
17 + "gosuda.org/portal/types"
18 )
19
20 const (
@@ -149,7 +149,7 @@ func isLeaseConnected(since time.Duration) bool {
149 }
150
151 // fromLeaseEntry populates the leaseRow from a LeaseEntry with common fields.
152 -func (r *leaseRow) fromLeaseEntry(entry *portal.LeaseEntry, admin *Admin, portalURL string) {
152 +func (r *leaseRow) fromLeaseEntry(entry *types.LeaseEntry, admin *Admin, portalURL string) {
153 lease := entry.Lease
154 identityID := lease.ID
155 since := max(time.Since(entry.LastSeen), 0)
@@ -281,7 +281,7 @@ func convertLeaseEntriesToRows(serv *portal.RelayServer, admin *Admin, forAdmin
281 func writeAPIData(w http.ResponseWriter, status int, data any) {
282 w.Header().Set("Content-Type", "application/json")
283 w.WriteHeader(status)
284 - if err := json.NewEncoder(w).Encode(contracts.APIEnvelope{
284 + if err := json.NewEncoder(w).Encode(types.APIEnvelope{
285 OK: true,
286 Data: data,
287 }); err != nil {
@@ -292,7 +292,7 @@ func writeAPIData(w http.ResponseWriter, status int, data any) {
292 func writeAPIOK(w http.ResponseWriter, status int) {
293 w.Header().Set("Content-Type", "application/json")
294 w.WriteHeader(status)
295 - if err := json.NewEncoder(w).Encode(contracts.APIEnvelope{OK: true}); err != nil {
295 + if err := json.NewEncoder(w).Encode(types.APIEnvelope{OK: true}); err != nil {
296 log.Error().Err(err).Msg("[HTTP] Failed to encode API success response")
297 }
298 }
@@ -304,10 +304,10 @@ func writeAPIError(w http.ResponseWriter, status int, code, message string) {
304 func writeAPIErrorWithData(w http.ResponseWriter, status int, code, message string, data any) {
305 w.Header().Set("Content-Type", "application/json")
306 w.WriteHeader(status)
307 - if err := json.NewEncoder(w).Encode(contracts.APIEnvelope{
307 + if err := json.NewEncoder(w).Encode(types.APIEnvelope{
308 OK: false,
309 Data: data,
310 - Error: &contracts.APIError{
310 + Error: &types.APIError{
311 Code: code,
312 Message: message,
313 },
portal/contracts/contracts.go deleted
-58
@@ -1,58 +0,0 @@
1 -package contracts
2 -
3 -import "gosuda.org/portal/types"
4 -
5 -// API path constants for Portal relay server.
6 -const (
7 - PathSDKPrefix = types.PathSDKPrefix
8 - PathSDKRegister = types.PathSDKRegister
9 - PathSDKUnregister = types.PathSDKUnregister
10 - PathSDKRenew = types.PathSDKRenew
11 - PathSDKDomain = types.PathSDKDomain
12 - PathSDKConnect = types.PathSDKConnect
13 -
14 - PathAdminPrefix = types.PathAdminPrefix
15 - PathAdminLogin = types.PathAdminLogin
16 - PathAdminLogout = types.PathAdminLogout
17 - PathAdminAuthStatus = types.PathAdminAuthStatus
18 - PathAdminLeases = types.PathAdminLeases
19 - PathAdminLeasesBanned = types.PathAdminLeasesBanned
20 - PathAdminStats = types.PathAdminStats
21 - PathAdminSettings = types.PathAdminSettings
22 - PathAdminApprovalMode = types.PathAdminApprovalMode
23 -
24 - PathKeylessSign = types.PathKeylessSign
25 - PathHealthz = types.PathHealthz
26 -
27 - PathTunnelScript = types.PathTunnelScript
28 - PathTunnelBinary = types.PathTunnelBinary
29 -
30 - PathAppPrefix = types.PathAppPrefix
31 -)
32 -
33 -type (
34 - APIError = types.APIError
35 - APIEnvelope = types.APIEnvelope
36 - Metadata = types.Metadata
37 - MetadataOption = types.MetadataOption
38 -)
39 -
40 -func WithDescription(description string) MetadataOption {
41 - return types.WithDescription(description)
42 -}
43 -
44 -func WithTags(tags []string) MetadataOption {
45 - return types.WithTags(tags)
46 -}
47 -
48 -func WithThumbnail(thumbnail string) MetadataOption {
49 - return types.WithThumbnail(thumbnail)
50 -}
51 -
52 -func WithOwner(owner string) MetadataOption {
53 - return types.WithOwner(owner)
54 -}
55 -
56 -func WithHide(hide bool) MetadataOption {
57 - return types.WithHide(hide)
58 -}
portal/controlplane/registry/service.go
+11 -15
@@ -10,7 +10,6 @@ import (
10
11 "github.com/rs/zerolog/log"
12
13 - "gosuda.org/portal/portal"
13 "gosuda.org/portal/portal/controlplane"
14 "gosuda.org/portal/types"
15 )
@@ -21,9 +20,9 @@ const DefaultLeaseTTL = 30 * time.Second
20 // Backend provides the relay operations needed by the control-plane registry.
21 type Backend interface {
22 BaseHost() string
24 - UpdateLease(lease *portal.Lease) bool
23 + UpdateLease(lease *types.Lease) bool
24 DeleteLease(leaseID string) bool
26 - GetLeaseByID(leaseID string) (*portal.LeaseEntry, bool)
25 + GetLeaseByID(leaseID string) (*types.LeaseEntry, bool)
26 ClearDropped(leaseID string)
27 DropLease(leaseID string)
28 RegisterRoute(sniName, leaseID, name string) error
@@ -37,9 +36,6 @@ type Options struct {
36 LeaseTTL time.Duration
37 }
38
40 -// APIError is an alias for types.APIError to avoid breaking existing consumers.
41 -type APIError = types.APIError
42 -
39 // AdmissionInput describes runtime context for control-plane admission checks.
40 type AdmissionInput struct {
41 ConnectionTLSState *tls.ConnectionState
@@ -52,7 +48,7 @@ type AdmissionInput struct {
48
49 // AdmissionResult returns normalized, validated admission context.
50 type AdmissionResult struct {
55 - Entry *portal.LeaseEntry
51 + Entry *types.LeaseEntry
52 LeaseID string
53 ReverseToken string
54 ClientIP string
@@ -96,7 +92,7 @@ func NewService(backend Backend, opts Options) (*Service, error) {
92 }
93
94 // Admit validates and normalizes control-plane credentials before SDK operations.
99 -func (s *Service) Admit(input AdmissionInput) (AdmissionResult, *APIError) {
95 +func (s *Service) Admit(input AdmissionInput) (AdmissionResult, *types.APIError) {
96 leaseID, reverseToken := normalizeLeaseCredentials(input.RawLeaseID, input.RawReverseToken)
97 if err := validateLeaseCredentials(leaseID, reverseToken); err != nil {
98 return AdmissionResult{}, err
@@ -130,7 +126,7 @@ func (s *Service) Admit(input AdmissionInput) (AdmissionResult, *APIError) {
126 }
127
128 // Register creates a new lease and associated SNI route.
133 -func (s *Service) Register(input RegisterInput) (types.RegisterResponse, *APIError) {
129 +func (s *Service) Register(input RegisterInput) (types.RegisterResponse, *types.APIError) {
130 name := strings.TrimSpace(input.Name)
131 if !types.IsValidLeaseName(name) {
132 return types.RegisterResponse{}, apiError(httpStatusBadRequest, "invalid_name", "name must be a DNS label (letters, digits, hyphen; no dots or underscores)")
@@ -144,7 +140,7 @@ func (s *Service) Register(input RegisterInput) (types.RegisterResponse, *APIErr
140 metadata = *input.Metadata
141 }
142
147 - lease := &portal.Lease{
143 + lease := &types.Lease{
144 ID: input.LeaseID,
145 Name: name,
146 Metadata: metadata,
@@ -198,7 +194,7 @@ func (s *Service) Unregister(leaseID string) {
194 }
195
196 // Renew extends lease expiry and opportunistically refreshes SNI routing.
201 -func (s *Service) Renew(entry *portal.LeaseEntry) *APIError {
197 +func (s *Service) Renew(entry *types.LeaseEntry) *types.APIError {
198 if entry == nil || entry.Lease == nil {
199 return apiError(httpStatusNotFound, "lease_not_found", "lease not found")
200 }
@@ -228,7 +224,7 @@ func (s *Service) Renew(entry *portal.LeaseEntry) *APIError {
224 }
225
226 // Domain returns the configured relay base domain.
231 -func (s *Service) Domain() (types.DomainResponse, *APIError) {
227 +func (s *Service) Domain() (types.DomainResponse, *types.APIError) {
228 baseHost := strings.TrimSpace(s.backend.BaseHost())
229 if baseHost == "" {
230 return types.DomainResponse{}, apiError(httpStatusServiceUnavailable, "base_domain_missing", "base domain not configured")
@@ -248,7 +244,7 @@ func normalizeLeaseCredentials(rawLeaseID, rawReverseToken string) (leaseID, rev
244 return strings.TrimSpace(rawLeaseID), strings.TrimSpace(rawReverseToken)
245 }
246
251 -func validateLeaseCredentials(leaseID, reverseToken string) *APIError {
247 +func validateLeaseCredentials(leaseID, reverseToken string) *types.APIError {
248 if leaseID == "" {
249 return apiError(httpStatusBadRequest, "missing_lease_id", "lease_id is required")
250 }
@@ -258,8 +254,8 @@ func validateLeaseCredentials(leaseID, reverseToken string) *APIError {
254 return nil
255 }
256
261 -func apiError(statusCode int, code, message string) *APIError {
262 - return &APIError{
257 +func apiError(statusCode int, code, message string) *types.APIError {
258 + return &types.APIError{
259 StatusCode: statusCode,
260 Code: code,
261 Message: message,
portal/controlplane/registry/service_test.go
+24 -25
@@ -8,14 +8,13 @@ import (
8 "testing"
9 "time"
10
11 - "gosuda.org/portal/portal"
11 "gosuda.org/portal/portal/controlplane"
12 "gosuda.org/portal/types"
13 )
14
15 type fakeBackend struct {
16 registerRouteErr error
18 - leases map[string]*portal.LeaseEntry
17 + leases map[string]*types.LeaseEntry
18 baseHost string
19 connectLeaseID string
20 connectToken string
@@ -29,7 +28,7 @@ func newFakeBackend(baseHost string) *fakeBackend {
28 return &fakeBackend{
29 baseHost: baseHost,
30 updateLeaseAllowed: true,
32 - leases: make(map[string]*portal.LeaseEntry),
31 + leases: make(map[string]*types.LeaseEntry),
32 }
33 }
34
@@ -37,11 +36,11 @@ func (f *fakeBackend) BaseHost() string {
36 return f.baseHost
37 }
38
40 -func (f *fakeBackend) UpdateLease(lease *portal.Lease) bool {
39 +func (f *fakeBackend) UpdateLease(lease *types.Lease) bool {
40 if !f.updateLeaseAllowed {
41 return false
42 }
44 - f.leases[lease.ID] = &portal.LeaseEntry{
43 + f.leases[lease.ID] = &types.LeaseEntry{
44 Lease: lease,
45 Expires: lease.Expires,
46 }
@@ -56,7 +55,7 @@ func (f *fakeBackend) DeleteLease(leaseID string) bool {
55 return true
56 }
57
59 -func (f *fakeBackend) GetLeaseByID(leaseID string) (*portal.LeaseEntry, bool) {
58 +func (f *fakeBackend) GetLeaseByID(leaseID string) (*types.LeaseEntry, bool) {
59 entry, ok := f.leases[leaseID]
60 return entry, ok
61 }
@@ -175,8 +174,8 @@ func TestAdmitSuccessWithMatchingToken(t *testing.T) {
174 t.Parallel()
175
176 backend := newFakeBackend("example.com")
178 - backend.leases["lease-1"] = &portal.LeaseEntry{
179 - Lease: &portal.Lease{
177 + backend.leases["lease-1"] = &types.LeaseEntry{
178 + Lease: &types.Lease{
179 ID: "lease-1",
180 ReverseToken: "token-1",
181 },
@@ -212,8 +211,8 @@ func TestAdmitRejectsInvalidTokenWithValidCertificate(t *testing.T) {
211 t.Parallel()
212
213 backend := newFakeBackend("example.com")
215 - backend.leases["lease-1"] = &portal.LeaseEntry{
216 - Lease: &portal.Lease{
214 + backend.leases["lease-1"] = &types.LeaseEntry{
215 + Lease: &types.Lease{
216 ID: "lease-1",
217 ReverseToken: "token-1",
218 },
@@ -242,8 +241,8 @@ func TestAdmit_ValidCert_Passes(t *testing.T) {
241 t.Parallel()
242
243 backend := newFakeBackend("example.com")
245 - backend.leases["lease-1"] = &portal.LeaseEntry{
246 - Lease: &portal.Lease{
244 + backend.leases["lease-1"] = &types.LeaseEntry{
245 + Lease: &types.Lease{
246 ID: "lease-1",
247 ReverseToken: "token-1",
248 },
@@ -272,8 +271,8 @@ func TestAdmit_InvalidCert_Rejected(t *testing.T) {
271 t.Parallel()
272
273 backend := newFakeBackend("example.com")
275 - backend.leases["lease-1"] = &portal.LeaseEntry{
276 - Lease: &portal.Lease{
274 + backend.leases["lease-1"] = &types.LeaseEntry{
275 + Lease: &types.Lease{
276 ID: "lease-1",
277 ReverseToken: "token-1",
278 },
@@ -303,8 +302,8 @@ func TestAdmit_NoCert_TokenValid_Passes(t *testing.T) {
302 t.Parallel()
303
304 backend := newFakeBackend("example.com")
306 - backend.leases["lease-1"] = &portal.LeaseEntry{
307 - Lease: &portal.Lease{
305 + backend.leases["lease-1"] = &types.LeaseEntry{
306 + Lease: &types.Lease{
307 ID: "lease-1",
308 ReverseToken: "token-1",
309 },
@@ -315,7 +314,7 @@ func TestAdmit_NoCert_TokenValid_Passes(t *testing.T) {
314 t.Fatalf("NewService returned error: %v", err)
315 }
316
318 - // Nil TLS state — CertBind skipped, token validation still applies.
317 + // Nil TLS state ??CertBind skipped, token validation still applies.
318 result, apiErr := svc.Admit(AdmissionInput{
319 RawLeaseID: "lease-1",
320 RawReverseToken: "token-1",
@@ -334,8 +333,8 @@ func TestAdmit_NoCert_TokenInvalid_Rejected(t *testing.T) {
333 t.Parallel()
334
335 backend := newFakeBackend("example.com")
337 - backend.leases["lease-1"] = &portal.LeaseEntry{
338 - Lease: &portal.Lease{
336 + backend.leases["lease-1"] = &types.LeaseEntry{
337 + Lease: &types.Lease{
338 ID: "lease-1",
339 ReverseToken: "token-1",
340 },
@@ -346,7 +345,7 @@ func TestAdmit_NoCert_TokenInvalid_Rejected(t *testing.T) {
345 t.Fatalf("NewService returned error: %v", err)
346 }
347
349 - // Nil TLS state — CertBind skipped, but token does not match.
348 + // Nil TLS state ??CertBind skipped, but token does not match.
349 _, apiErr := svc.Admit(AdmissionInput{
350 RawLeaseID: "lease-1",
351 RawReverseToken: "wrong-token",
@@ -433,8 +432,8 @@ func TestRenewExtendsLease(t *testing.T) {
432
433 now := time.Date(2026, time.March, 4, 1, 0, 0, 0, time.UTC)
434 backend := newFakeBackend("example.com")
436 - entry := &portal.LeaseEntry{
437 - Lease: &portal.Lease{
435 + entry := &types.LeaseEntry{
436 + Lease: &types.Lease{
437 ID: "lease-1",
438 Name: "demo",
439 Expires: now,
@@ -464,8 +463,8 @@ func TestRenewResetsFutureExpiryFromNow(t *testing.T) {
463 now := time.Date(2026, time.March, 4, 1, 0, 0, 0, time.UTC)
464 originalExpiry := now.Add(5 * time.Minute)
465 backend := newFakeBackend("example.com")
467 - entry := &portal.LeaseEntry{
468 - Lease: &portal.Lease{
466 + entry := &types.LeaseEntry{
467 + Lease: &types.Lease{
468 ID: "lease-1",
469 Name: "demo",
470 Expires: originalExpiry,
@@ -498,7 +497,7 @@ func TestUnregisterDropsLeaseAndRoutes(t *testing.T) {
497 t.Parallel()
498
499 backend := newFakeBackend("example.com")
501 - backend.leases["lease-1"] = &portal.LeaseEntry{Lease: &portal.Lease{ID: "lease-1"}}
500 + backend.leases["lease-1"] = &types.LeaseEntry{Lease: &types.Lease{ID: "lease-1"}}
501 svc, err := NewService(backend, Options{})
502 if err != nil {
503 t.Fatalf("NewService returned error: %v", err)
portal/lease.go
+9 -15
@@ -9,12 +9,6 @@ import (
9 "gosuda.org/portal/types"
10 )
11
12 -// Lease is an alias for types.Lease for backward compatibility within the portal package.
13 -type Lease = types.Lease
14 -
15 -// LeaseEntry is an alias for types.LeaseEntry for backward compatibility within the portal package.
16 -type LeaseEntry = types.LeaseEntry
17 -
12 type LeaseManager struct {
13 leases map[string]*types.LeaseEntry
14 stopCh chan struct{}
@@ -31,7 +25,7 @@ type LeaseManager struct {
25
26 func NewLeaseManager(ttlInterval time.Duration) *LeaseManager {
27 return &LeaseManager{
34 - leases: make(map[string]*LeaseEntry),
28 + leases: make(map[string]*types.LeaseEntry),
29 stopCh: make(chan struct{}),
30 ttlInterval: ttlInterval,
31 bannedLeases: make(map[string]struct{}),
@@ -86,7 +80,7 @@ func (lm *LeaseManager) cleanupExpiredLeases() {
80 }
81 }
82
89 -func (lm *LeaseManager) UpdateLease(lease *Lease) bool {
83 +func (lm *LeaseManager) UpdateLease(lease *types.Lease) bool {
84 lm.leasesLock.Lock()
85 defer lm.leasesLock.Unlock()
86
@@ -137,7 +131,7 @@ func (lm *LeaseManager) UpdateLease(lease *Lease) bool {
131 firstSeen = time.Now()
132 }
133
140 - lm.leases[identityID] = &LeaseEntry{
134 + lm.leases[identityID] = &types.LeaseEntry{
135 Lease: lease,
136 Expires: lease.Expires,
137 LastSeen: time.Now(),
@@ -168,7 +162,7 @@ func (lm *LeaseManager) SetOnLeaseDeleted(callback func(string)) {
162 lm.onLeaseDeleted = callback
163 }
164
171 -func (lm *LeaseManager) GetLeaseByID(leaseID string) (*LeaseEntry, bool) {
165 +func (lm *LeaseManager) GetLeaseByID(leaseID string) (*types.LeaseEntry, bool) {
166 lm.leasesLock.RLock()
167 defer lm.leasesLock.RUnlock()
168
@@ -190,7 +184,7 @@ func (lm *LeaseManager) GetLeaseByID(leaseID string) (*LeaseEntry, bool) {
184 return lease, true
185 }
186
193 -func (lm *LeaseManager) GetLeaseByName(name string) (*LeaseEntry, bool) {
187 +func (lm *LeaseManager) GetLeaseByName(name string) (*types.LeaseEntry, bool) {
188 lm.leasesLock.RLock()
189 defer lm.leasesLock.RUnlock()
190
@@ -213,12 +207,12 @@ func (lm *LeaseManager) GetLeaseByName(name string) (*LeaseEntry, bool) {
207 return nil, false
208 }
209
216 -func (lm *LeaseManager) GetAllLeases() []*Lease {
210 +func (lm *LeaseManager) GetAllLeases() []*types.Lease {
211 lm.leasesLock.RLock()
212 defer lm.leasesLock.RUnlock()
213
214 now := time.Now()
221 - var validLeases []*Lease
215 + var validLeases []*types.Lease
216
217 for _, entry := range lm.leases {
218 if now.Before(entry.Expires) {
@@ -230,12 +224,12 @@ func (lm *LeaseManager) GetAllLeases() []*Lease {
224 }
225
226 // GetAllLeaseEntries returns all lease entries from the lease manager.
233 -func (lm *LeaseManager) GetAllLeaseEntries() []*LeaseEntry {
227 +func (lm *LeaseManager) GetAllLeaseEntries() []*types.LeaseEntry {
228 lm.leasesLock.RLock()
229 defer lm.leasesLock.RUnlock()
230
231 now := time.Now()
238 - var entries []*LeaseEntry
232 + var entries []*types.LeaseEntry
233
234 for _, entry := range lm.leases {
235 if now.Before(entry.Expires) {
portal/lease_test.go
+7 -5
@@ -4,6 +4,8 @@ import (
4 "slices"
5 "testing"
6 "time"
7 +
8 + "gosuda.org/portal/types"
9 )
10
11 func TestLeaseManagerDeleteLeaseInvokesCallback(t *testing.T) {
@@ -14,7 +16,7 @@ func TestLeaseManagerDeleteLeaseInvokesCallback(t *testing.T) {
16 deleted = append(deleted, id)
17 })
18
17 - lease := &Lease{
19 + lease := &types.Lease{
20 ID: "lease-1",
21 Name: "app-1",
22 Expires: time.Now().Add(30 * time.Second),
@@ -39,16 +41,16 @@ func TestLeaseManagerCleanupExpiredLeasesInvokesCallback(t *testing.T) {
41 deleted = append(deleted, id)
42 })
43
42 - lm.leases["expired-1"] = &LeaseEntry{
43 - Lease: &Lease{
44 + lm.leases["expired-1"] = &types.LeaseEntry{
45 + Lease: &types.Lease{
46 ID: "expired-1",
47 Name: "expired",
48 Expires: time.Now().Add(-1 * time.Second),
49 },
50 Expires: time.Now().Add(-1 * time.Second),
51 }
50 - lm.leases["active-1"] = &LeaseEntry{
51 - Lease: &Lease{
52 + lm.leases["active-1"] = &types.LeaseEntry{
53 + Lease: &types.Lease{
54 ID: "active-1",
55 Name: "active",
56 Expires: time.Now().Add(30 * time.Second),
portal/relay_test.go
+3 -1
@@ -6,6 +6,8 @@ import (
6 "strings"
7 "testing"
8 "time"
9 +
10 + "gosuda.org/portal/types"
11 )
12
13 func TestRelayServerReverseHubAuthorizerTrimsToken(t *testing.T) {
@@ -14,7 +16,7 @@ func TestRelayServerReverseHubAuthorizerTrimsToken(t *testing.T) {
16 t.Fatalf("create relay server: %v", err)
17 }
18
17 - lease := &Lease{
19 + lease := &types.Lease{
20 ID: "lease-authorizer-trim",
21 Name: "tenant",
22 TLS: true,
portal/reverse_hub.go
+3 -5
@@ -9,6 +9,8 @@ import (
9 "time"
10
11 "github.com/rs/zerolog/log"
12 +
13 + "gosuda.org/portal/portal/sni"
14 )
15
16 const (
@@ -16,10 +18,6 @@ const (
18 // before they are activated for a real client request.
19 ReverseKeepaliveMarker = byte(0x00)
20
19 - // TLSStartMarker is sent by the relay to activate a reverse connection
20 - // for TLS reverse-connect mode.
21 - TLSStartMarker = byte(0x02)
22 -
21 // QueueSize is the maximum number of pending reverse connections per lease.
22 QueueSize = 64
23
@@ -267,7 +265,7 @@ func (h *ReverseHub) AcquireForTLS(leaseID string, timeout time.Duration) (*Reve
265 }
266 // Stop idle keepalive and signal tunnel worker to release this connection.
267 conn.Activate()
270 - err := conn.WriteControlByte(TLSStartMarker, controlWriteTimeout)
268 + err := conn.WriteControlByte(sni.TLSStartMarker, controlWriteTimeout)
269 if err == nil {
270 return conn, nil
271 }
portal/reverse_hub_test.go
+6 -4
@@ -6,6 +6,8 @@ import (
6 "strings"
7 "testing"
8 "time"
9 +
10 + "gosuda.org/portal/portal/sni"
11 )
12
13 func TestReverseHubAuthorization(t *testing.T) {
@@ -113,7 +115,7 @@ func TestHandleConnectTrimsLeaseIDAndToken(t *testing.T) {
115 case err := <-readErr:
116 t.Fatalf("failed to read marker: %v", err)
117 case marker := <-markerRead:
116 - if marker != TLSStartMarker {
118 + if marker != sni.TLSStartMarker {
119 t.Fatalf("unexpected marker: %d", marker)
120 }
121 case <-time.After(500 * time.Millisecond):
@@ -168,7 +170,7 @@ func TestAcquireForTLSSendsStartMarker(t *testing.T) {
170 case err := <-readErr:
171 t.Fatalf("failed to read marker: %v", err)
172 case b := <-markerRead:
171 - if b != TLSStartMarker {
173 + if b != sni.TLSStartMarker {
174 t.Fatalf("unexpected marker: %d", b)
175 }
176 case <-time.After(500 * time.Millisecond):
@@ -226,7 +228,7 @@ func TestAcquireForTLSPollLoopSendsStartMarker(t *testing.T) {
228 case err := <-readErr:
229 t.Fatalf("failed to read marker: %v", err)
230 case b := <-markerRead:
229 - if b != TLSStartMarker {
231 + if b != sni.TLSStartMarker {
232 t.Fatalf("unexpected marker: %d", b)
233 }
234 case <-time.After(500 * time.Millisecond):
@@ -293,7 +295,7 @@ func TestHandleConnectOffersAuthorizedConn(t *testing.T) {
295 case err := <-readErr:
296 t.Fatalf("failed to read start marker: %v", err)
297 case b := <-markerRead:
296 - if b != TLSStartMarker {
298 + if b != sni.TLSStartMarker {
299 t.Fatalf("unexpected marker: %d", b)
300 }
301 case <-time.After(500 * time.Millisecond):
portal/sni/parser.go
+7
@@ -10,6 +10,13 @@ import (
10 "io"
11 )
12
13 +const (
14 + // TLSStartMarker activates a reverse TCP connection for TLS passthrough.
15 + TLSStartMarker = byte(0x02)
16 + // NonTLSStartMarker is reserved for non-TLS protocol marker tests.
17 + NonTLSStartMarker = byte(0x01)
18 +)
19 +
20 var (
21 // ErrInvalidTLSRecord is returned when the TLS record is malformed.
22 ErrInvalidTLSRecord = errors.New("invalid TLS record")
sdk/client.go
+2 -3
@@ -22,7 +22,6 @@ import (
22
23 "github.com/gosuda/keyless_tls/keyless/lifecycle"
24
25 - "gosuda.org/portal/portal"
25 "gosuda.org/portal/portal/keyless"
26 "gosuda.org/portal/portal/netutil"
27 "gosuda.org/portal/types"
@@ -166,7 +165,7 @@ func (c *Client) Listen(name string, options ...types.MetadataOption) (net.Liste
165 return listener, nil
166 }
167
169 -func (c *Client) newLease(name string, options ...types.MetadataOption) (*portal.Lease, error) {
168 +func (c *Client) newLease(name string, options ...types.MetadataOption) (*types.Lease, error) {
169 var metadata types.Metadata
170 for _, option := range options {
171 option(&metadata)
@@ -182,7 +181,7 @@ func (c *Client) newLease(name string, options ...types.MetadataOption) (*portal
181 return nil, fmt.Errorf("generate reverse token: %w", err)
182 }
183
185 - lease := &portal.Lease{
184 + lease := &types.Lease{
185 ID: hex.EncodeToString(idBytes),
186 Name: name,
187 TLS: true,
sdk/listener.go
+8 -8
@@ -19,8 +19,8 @@ import (
19 "github.com/rs/zerolog/log"
20
21 "gosuda.org/portal/portal"
22 - "gosuda.org/portal/portal/contracts"
22 "gosuda.org/portal/portal/netutil"
23 + "gosuda.org/portal/portal/sni"
24 "gosuda.org/portal/types"
25 )
26
@@ -88,7 +88,7 @@ func (e *reverseConnectRejectionError) IsFatal() bool {
88 type Listener struct {
89 tlsConfig *tls.Config
90 controlPlaneCert tls.Certificate
91 - lease *portal.Lease
91 + lease *types.Lease
92 httpClient *http.Client
93 stopCh chan struct{}
94 acceptCh chan net.Conn
@@ -106,7 +106,7 @@ var _ net.Listener = (*Listener)(nil)
106
107 // NewListener creates a relay-backed listener.
108 // If tlsConfig is provided, reverse workers complete TLS handshakes before enqueueing connections.
109 -func NewListener(relayAddr string, lease *portal.Lease, tlsConfig *tls.Config, controlPlaneCert tls.Certificate, reverseWorkers int, reverseDialTimeout time.Duration, closeFns ...func()) (*Listener, error) {
109 +func NewListener(relayAddr string, lease *types.Lease, tlsConfig *tls.Config, controlPlaneCert tls.Certificate, reverseWorkers int, reverseDialTimeout time.Duration, closeFns ...func()) (*Listener, error) {
110 if lease == nil {
111 return nil, errors.New("lease is required")
112 }
@@ -317,7 +317,7 @@ func (l *Listener) reverseAcceptWorker(workerID int) {
317 continue
318 }
319
320 - err = l.waitForReverseStart(conn, portal.TLSStartMarker)
320 + err = l.waitForReverseStart(conn, sni.TLSStartMarker)
321 if err != nil {
322 if closeErr := conn.Close(); closeErr != nil {
323 log.Debug().Err(closeErr).Msg("[SDK] failed to close reverse connection")
@@ -664,7 +664,7 @@ func (l *Listener) registerWithRelay() error {
664 ReverseToken: l.lease.ReverseToken,
665 }
666
667 - return l.postJSON(contracts.PathSDKRegister, reqBody)
667 + return l.postJSON(types.PathSDKRegister, reqBody)
668 }
669
670 func (l *Listener) unregisterFromRelay() error {
@@ -672,7 +672,7 @@ func (l *Listener) unregisterFromRelay() error {
672 LeaseID: l.lease.ID,
673 ReverseToken: l.lease.ReverseToken,
674 }
675 - return l.postJSON(contracts.PathSDKUnregister, reqBody)
675 + return l.postJSON(types.PathSDKUnregister, reqBody)
676 }
677
678 func (l *Listener) sendKeepalive() error {
@@ -680,7 +680,7 @@ func (l *Listener) sendKeepalive() error {
680 LeaseID: l.lease.ID,
681 ReverseToken: l.lease.ReverseToken,
682 }
683 - return l.postJSON(contracts.PathSDKRenew, reqBody)
683 + return l.postJSON(types.PathSDKRenew, reqBody)
684 }
685
686 func (l *Listener) postJSON(path string, body any) error {
@@ -759,7 +759,7 @@ func relayConnectURL(relayAddr, leaseID, token string) (string, error) {
759 if u.Scheme != "https" {
760 return "", fmt.Errorf("unsupported relay URL scheme: %q (use https)", u.Scheme)
761 }
762 - u.Path = contracts.PathSDKConnect
762 + u.Path = types.PathSDKConnect
763 q := u.Query()
764 q.Set("lease_id", leaseID)
765 u.RawQuery = q.Encode()
sdk/listener_test.go
+15 -16
@@ -13,11 +13,10 @@ import (
13 "time"
14
15 "gosuda.org/portal/portal"
16 + "gosuda.org/portal/portal/sni"
17 "gosuda.org/portal/types"
18 )
19
19 -const testNonTLSStartMarker = byte(0x01)
20 -
20 func TestNormalizeRelayAPIURL(t *testing.T) {
21 t.Parallel()
22
@@ -172,7 +171,7 @@ func TestOpenReverseConnection_RejectsNonHTTPSRelay(t *testing.T) {
171
172 l := &Listener{
173 relayAddr: "http://localhost:4017",
175 - lease: &portal.Lease{ID: "lease-1", ReverseToken: "token-1"},
174 + lease: &types.Lease{ID: "lease-1", ReverseToken: "token-1"},
175 reverseDialTimeout: 2 * time.Second,
176 stopCh: make(chan struct{}),
177 }
@@ -211,7 +210,7 @@ func TestOpenReverseConnection_StopUnblocksTLSHandshake(t *testing.T) {
210
211 l := &Listener{
212 relayAddr: "https://" + ln.Addr().String(),
214 - lease: &portal.Lease{ID: "lease-1", ReverseToken: "token-1"},
213 + lease: &types.Lease{ID: "lease-1", ReverseToken: "token-1"},
214 reverseDialTimeout: 5 * time.Second,
215 stopCh: make(chan struct{}),
216 }
@@ -256,7 +255,7 @@ func TestWriteReverseConnectRequest_RespectsWriteDeadline(t *testing.T) {
255 }
256
257 l := &Listener{
259 - lease: &portal.Lease{ReverseToken: "token-1"},
258 + lease: &types.Lease{ReverseToken: "token-1"},
259 reverseDialTimeout: 25 * time.Millisecond,
260 stopCh: make(chan struct{}),
261 }
@@ -483,10 +482,10 @@ func TestWaitForReverseStart_HTTPMode(t *testing.T) {
482
483 done := make(chan error, 1)
484 go func() {
486 - done <- l.waitForReverseStart(local, portal.TLSStartMarker)
485 + done <- l.waitForReverseStart(local, sni.TLSStartMarker)
486 }()
487
489 - _, err := peer.Write([]byte{portal.TLSStartMarker})
488 + _, err := peer.Write([]byte{sni.TLSStartMarker})
489 if err != nil {
490 t.Fatalf("write marker: %v", err)
491 }
@@ -514,10 +513,10 @@ func TestWaitForReverseStart_TLSMode(t *testing.T) {
513
514 done := make(chan error, 1)
515 go func() {
517 - done <- l.waitForReverseStart(local, portal.TLSStartMarker)
516 + done <- l.waitForReverseStart(local, sni.TLSStartMarker)
517 }()
518
520 - _, err := peer.Write([]byte{portal.TLSStartMarker})
519 + _, err := peer.Write([]byte{sni.TLSStartMarker})
520 if err != nil {
521 t.Fatalf("write marker: %v", err)
522 }
@@ -542,14 +541,14 @@ func TestWaitForReverseStart_IgnoresKeepaliveMarker(t *testing.T) {
541
542 done := make(chan error, 1)
543 go func() {
545 - done <- l.waitForReverseStart(local, portal.TLSStartMarker)
544 + done <- l.waitForReverseStart(local, sni.TLSStartMarker)
545 }()
546
547 _, err := peer.Write([]byte{portal.ReverseKeepaliveMarker})
548 if err != nil {
549 t.Fatalf("write keepalive marker: %v", err)
550 }
552 - _, err = peer.Write([]byte{portal.TLSStartMarker})
551 + _, err = peer.Write([]byte{sni.TLSStartMarker})
552 if err != nil {
553 t.Fatalf("write start marker: %v", err)
554 }
@@ -577,10 +576,10 @@ func TestWaitForReverseStart_TLSRejectsHTTPMarker(t *testing.T) {
576
577 done := make(chan error, 1)
578 go func() {
580 - done <- l.waitForReverseStart(local, portal.TLSStartMarker)
579 + done <- l.waitForReverseStart(local, sni.TLSStartMarker)
580 }()
581
583 - _, err := peer.Write([]byte{testNonTLSStartMarker})
582 + _, err := peer.Write([]byte{sni.NonTLSStartMarker})
583 if err != nil {
584 t.Fatalf("write marker: %v", err)
585 }
@@ -605,10 +604,10 @@ func TestWaitForReverseStart_HTTPRejectsTLSMarker(t *testing.T) {
604
605 done := make(chan error, 1)
606 go func() {
608 - done <- l.waitForReverseStart(local, testNonTLSStartMarker)
607 + done <- l.waitForReverseStart(local, sni.NonTLSStartMarker)
608 }()
609
611 - _, err := peer.Write([]byte{portal.TLSStartMarker})
610 + _, err := peer.Write([]byte{sni.TLSStartMarker})
611 if err != nil {
612 t.Fatalf("write marker: %v", err)
613 }
@@ -632,7 +631,7 @@ func TestWaitForReverseStart_StopCancelsWait(t *testing.T) {
631
632 done := make(chan error, 1)
633 go func() {
635 - done <- l.waitForReverseStart(local, portal.TLSStartMarker)
634 + done <- l.waitForReverseStart(local, sni.TLSStartMarker)
635 }()
636
637 close(l.stopCh)