From c2e81dd9d9893f97771cb9b34a69aecbb056e537 Mon Sep 17 00:00:00 2001 From: Harry Harpham Date: Fri, 9 Jan 2026 13:56:04 -0700 Subject: [PATCH] tsnet: resolve issues listening on multiple ports, refactor input Issues were resolved mainly via cherry-picked commits prior to this. Signed-off-by: Harry Harpham --- tsnet/tsnet.go | 42 +++++++++++++++++++++--------------------- tsnet/tsnet_test.go | 25 +++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index e280eee06..6bb863feb 100644 --- a/tsnet/tsnet.go +++ b/tsnet/tsnet.go @@ -7,8 +7,10 @@ package tsnet import ( "context" crand "crypto/rand" + "crypto/sha256" "crypto/tls" "encoding/hex" + "encoding/json" "errors" "fmt" "io" @@ -1263,7 +1265,7 @@ type ServiceTransportOptions interface { // TODO: doc type ServiceTCPOptions struct { TerminateTLS bool - PROXYProtocol int + PROXYProtocol int // TODO: does this have meaning for HTTP Services? } func (ServiceTCPOptions) serviceTransportOptions() {} @@ -1326,26 +1328,18 @@ func (s *Server) ListenService(name string, port uint16, opts ServiceTransportOp return nil, err } - lc := s.localClient - - st, err := lc.StatusWithoutPeers(ctx) - if err != nil { - return nil, fmt.Errorf("fetching ACL tags: %w", err) - } + st := s.lb.StatusWithoutPeers() if st.Self.Tags == nil || st.Self.Tags.Len() == 0 { return nil, ErrUntaggedServiceHost } - prefs, err := lc.GetPrefs(ctx) - if err != nil { - return nil, fmt.Errorf("fetching node preferences: %w", err) - } - if !slices.Contains(prefs.AdvertiseServices, svcName) { + advertisedServices := s.lb.Prefs().AdvertiseServices().AsSlice() + if !slices.Contains(advertisedServices, svcName) { // TODO: do we need to undo this edit on error? - _, err = lc.EditPrefs(ctx, &ipn.MaskedPrefs{ + _, err = s.lb.EditPrefs(&ipn.MaskedPrefs{ AdvertiseServicesSet: true, Prefs: ipn.Prefs{ - AdvertiseServices: append(prefs.AdvertiseServices, svcName), + AdvertiseServices: append(advertisedServices, svcName), }, }) if err != nil { @@ -1353,12 +1347,18 @@ func (s *Server) ListenService(name string, port uint16, opts ServiceTransportOp } } - srvConfig, err := lc.GetServeConfig(ctx) - if err != nil { - return nil, fmt.Errorf("fetching node serve config: %w", err) - } - if srvConfig == nil { - srvConfig = new(ipn.ServeConfig) + srvConfig := new(ipn.ServeConfig) + etag := "empty" + if sc := s.lb.ServeConfig(); sc.Valid() { + srvConfig = sc.AsStruct() + // TODO: it's weird that we have to calculate the ETag ourselves. + // Shouldn't the backend do this for us? + b, err := json.Marshal(srvConfig) + if err != nil { + return nil, fmt.Errorf("marshaling serve config: %w", err) + } + sum := sha256.Sum256(b) + etag = hex.EncodeToString(sum[:]) } // Start listening on a TCP socket. @@ -1405,7 +1405,7 @@ func (s *Server) ListenService(name string, port uint16, opts ServiceTransportOp return nil, fmt.Errorf("unknown ServiceTransportOptions type %T", opts) } - if err := lc.SetServeConfig(ctx, srvConfig); err != nil { + if err := s.lb.SetServeConfig(srvConfig, etag); err != nil { ln.Close() return nil, err } diff --git a/tsnet/tsnet_test.go b/tsnet/tsnet_test.go index 67969a8b8..965df879d 100644 --- a/tsnet/tsnet_test.go +++ b/tsnet/tsnet_test.go @@ -1091,10 +1091,31 @@ func TestListenService(t *testing.T) { assertEchoHTTP(t, listeners[0].FQDN, "/foo/bar", peer.Dial) }, }, + { + name: "multiple_ports", + inputs: []input{ + { + port: 99, + }, + { + opts: ServiceHTTPOptions{}, + port: 80, + }, + }, + run: func(t *testing.T, listeners []*ServiceListener, peer *Server) { + go acceptAndEcho(t, listeners[0]) + + target := fmt.Sprintf("%s:%d", listeners[0].FQDN, 99) + conn := must.Get(peer.Dial(t.Context(), "tcp", target)) + defer conn.Close() + assertEcho(t, conn) + + go checkAndEcho(t, listeners[1], nil) + assertEchoHTTP(t, listeners[1].FQDN, "", peer.Dial) + }, + }, // TODO: // Success cases: - // - TLS-terminated-TCP - // - Service with multiple ports // - TUN Service // Error cases: // - Untagged node