From 8fa186e42bd160a73053265578c0cb54d211ed6b Mon Sep 17 00:00:00 2001 From: Harry Harpham Date: Fri, 9 Jan 2026 17:28:23 -0700 Subject: [PATCH] tsnet: move example programs into godoc examples Signed-off-by: Harry Harpham --- .../tsnet-services-multiple-ports.go | 107 ------------------ .../tsnet-services-reverse-proxy.go | 99 ---------------- ...snet_listen_service_multiple_ports_test.go | 61 ++++++++++ tsnet/example_tsnet_test.go | 49 ++++++++ 4 files changed, 110 insertions(+), 206 deletions(-) delete mode 100644 tsnet/example/tsnet-services-multiple-ports/tsnet-services-multiple-ports.go delete mode 100644 tsnet/example/tsnet-services-reverse-proxy/tsnet-services-reverse-proxy.go create mode 100644 tsnet/example_tsnet_listen_service_multiple_ports_test.go diff --git a/tsnet/example/tsnet-services-multiple-ports/tsnet-services-multiple-ports.go b/tsnet/example/tsnet-services-multiple-ports/tsnet-services-multiple-ports.go deleted file mode 100644 index d44a8a85a..000000000 --- a/tsnet/example/tsnet-services-multiple-ports/tsnet-services-multiple-ports.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// The tsnet-services example demonstrates how to use tsnet with Services -// which listen on multiple ports. -// -// To run this example yourself: -// -// 1. Define an ACL tag, an auto-approval rule, and traffic permits by adding -// the following to your tailnet's ACL policy file: -// TODO: convince gofmt to chill -// "tagOwners": { -// "tag:tsnet-demo-host": ["autogroup:member"], -// }, -// "autoApprovers": { -// "services": { -// "svc:tsnet-demo": ["tag:tsnet-demo-host"], -// }, -// }, -// // Allow anybody in the tailnet to reach the demo Service. -// "grants": [ -// "src": ["*"], -// "dst": ["tag:tsnet-demo-host"], -// "ip": ["*"], -// ], -// -// 2. Generate an auth key using the Tailscale admin panel. When doing so, add -// the tsnet-demo-host tag to your key. -// https://tailscale.com/kb/1085/auth-keys#generate-an-auth-key -// -// 2. Define a Service. For the purposes of this demo, it must be defined to -// listen on TCP ports 443 and 6060. Note that you only need to follow Step -// 1 in the following document. -// https://tailscale.com/kb/1552/tailscale-services#step-1-define-a-tailscale-service -// -// 3. Run the demo on the command line: -// TS_AUTHKEY= go run tsnet-services.go -service -package main - -import ( - "flag" - "fmt" - "log" - "net/http" - _ "net/http/pprof" - "strings" - - "tailscale.com/tsnet" -) - -var ( - svcName = flag.String("service", "", "the name of your Service, e.g. svc:demo-service") -) - -func main() { - flag.Parse() - if *svcName == "" { - log.Fatal("a Service name must be provided") - } - - const serverPort uint16 = 443 - const pprofPort uint16 = 6060 - - s := &tsnet.Server{ - Hostname: "tsnet-services-demo", - } - defer s.Close() - - ln, err := s.ListenService(*svcName, serverPort, tsnet.ServiceHTTPOptions{HTTPS: true}) - if err != nil { - log.Fatal(err) - } - defer ln.Close() - - pprofLn, err := s.ListenService(*svcName, pprofPort, nil) - if err != nil { - log.Fatal(err) - } - defer pprofLn.Close() - - go func() { - log.Printf("Listening for pprof requests on http://%v:%d\n", pprofLn.FQDN, pprofPort) - - handler := func(w http.ResponseWriter, r *http.Request) { - // The pprof listener is separate from our main server, so we can - // allow users to leave off the /debug/pprof prefix. We'll just - // attach it here, then pass along to the pprof handlers, which have - // been added implicitly due to our import of net/http/pprof. - if !strings.HasPrefix("/debug/pprof", r.URL.Path) { - r.URL.Path = "/debug/pprof" + r.URL.Path - } - http.DefaultServeMux.ServeHTTP(w, r) - } - if err := http.Serve(pprofLn, http.HandlerFunc(handler)); err != nil { - log.Fatal("error serving pprof:", err) - } - }() - - log.Printf("Listening on https://%v\n", ln.FQDN) - - // Specifying a handler here means pprof endpoints will not be served by - // this server (since we are not using http.DefaultServeMux). - err = http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "

Hello, tailnet!

") - })) - log.Fatal(err) -} diff --git a/tsnet/example/tsnet-services-reverse-proxy/tsnet-services-reverse-proxy.go b/tsnet/example/tsnet-services-reverse-proxy/tsnet-services-reverse-proxy.go deleted file mode 100644 index 5ceb11429..000000000 --- a/tsnet/example/tsnet-services-reverse-proxy/tsnet-services-reverse-proxy.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// The tsnet-services example demonstrates how to use tsnet with Services and a -// reverse proxy. This is useful when the backing server is external to the -// tsnet application. -// -// To run this example yourself: -// -// 1. Define an ACL tag, an auto-approval rule, and traffic permits by adding -// the following to your tailnet's ACL policy file: -// TODO: convince gofmt to chill -// "tagOwners": { -// "tag:tsnet-demo-host": ["autogroup:member"], -// }, -// "autoApprovers": { -// "services": { -// "svc:tsnet-demo": ["tag:tsnet-demo-host"], -// }, -// }, -// // Allow anybody in the tailnet to reach the demo Service. -// "grants": [ -// "src": ["*"], -// "dst": ["tag:tsnet-demo-host"], -// "ip": ["*"], -// ], -// -// 2. Generate an auth key using the Tailscale admin panel. When doing so, add -// the tsnet-demo-host tag to your key. -// https://tailscale.com/kb/1085/auth-keys#generate-an-auth-key -// -// 2. Define a Service. For the purposes of this demo, it must be defined to -// listen on TCP port 443. Note that you only need to follow Step 1 in the -// following document. -// https://tailscale.com/kb/1552/tailscale-services#step-1-define-a-tailscale-service -// -// 3. Run the demo on the command line: -// TS_AUTHKEY= go run tsnet-services.go -service -package main - -import ( - "flag" - "fmt" - "log" - "net" - "net/http" - "net/http/httputil" - "net/url" - - "tailscale.com/tsnet" -) - -var ( - svcName = flag.String("service", "", "the name of your Service, e.g. svc:demo-service") -) - -func main() { - flag.Parse() - if *svcName == "" { - log.Fatal("a Service name must be provided") - } - - const port uint16 = 443 - - // We will start an HTTP server on a local socket. This server will simulate - // a server which may be running in another process or even another machine. - backingListener, err := net.Listen("tcp", "localhost:0") - if err != nil { - log.Fatal(err) - } - defer backingListener.Close() - go func() { - log.Fatal(http.Serve(backingListener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "

Hello, tailnet!

") - }))) - }() - - s := &tsnet.Server{ - Hostname: "tsnet-services-demo", - } - defer s.Close() - - ln, err := s.ListenService(*svcName, port, tsnet.ServiceHTTPOptions{HTTPS: true}) - if err != nil { - log.Fatal(err) - } - defer ln.Close() - - // Use a reverse proxy to direct traffic to the backing server - rp := httputil.NewSingleHostReverseProxy(&url.URL{ - Scheme: "http", - Host: backingListener.Addr().String(), - }) - - log.Printf("Listening on https://%v\n", ln.FQDN) - - err = http.Serve(ln, rp) - log.Fatal(err) -} diff --git a/tsnet/example_tsnet_listen_service_multiple_ports_test.go b/tsnet/example_tsnet_listen_service_multiple_ports_test.go new file mode 100644 index 000000000..a7d94c224 --- /dev/null +++ b/tsnet/example_tsnet_listen_service_multiple_ports_test.go @@ -0,0 +1,61 @@ +package tsnet_test + +import ( + "fmt" + "log" + "net/http" + _ "net/http/pprof" + "strings" + + "tailscale.com/tsnet" +) + +// This example function is in a separate file for the "net/http/pprof" import. + +// ExampleServer_ListenService_multiplePorts demonstrates how to advertise a +// Service on multiple ports. In this example, we run an HTTPS server on 443 and +// an HTTP server handling pprof requests to the same runtime on 6060. +func ExampleServer_ListenService_multiplePorts() { + s := &tsnet.Server{ + Hostname: "tsnet-services-demo", + } + defer s.Close() + + ln, err := s.ListenService("svc:my-service", 443, tsnet.ServiceHTTPOptions{HTTPS: true}) + if err != nil { + log.Fatal(err) + } + defer ln.Close() + + pprofLn, err := s.ListenService("svc:my-service", 6060, nil) + if err != nil { + log.Fatal(err) + } + defer pprofLn.Close() + + go func() { + log.Printf("Listening for pprof requests on http://%v:%d\n", pprofLn.FQDN, 6060) + + handler := func(w http.ResponseWriter, r *http.Request) { + // The pprof listener is separate from our main server, so we can + // allow users to leave off the /debug/pprof prefix. We'll just + // attach it here, then pass along to the pprof handlers, which have + // been added implicitly due to our import of net/http/pprof. + if !strings.HasPrefix("/debug/pprof", r.URL.Path) { + r.URL.Path = "/debug/pprof" + r.URL.Path + } + http.DefaultServeMux.ServeHTTP(w, r) + } + if err := http.Serve(pprofLn, http.HandlerFunc(handler)); err != nil { + log.Fatal("error serving pprof:", err) + } + }() + + log.Printf("Listening on https://%v\n", ln.FQDN) + + // Specifying a handler here means pprof endpoints will not be served by + // this server (since we are not using http.DefaultServeMux). + log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "

Hello, tailnet!

") + }))) +} diff --git a/tsnet/example_tsnet_test.go b/tsnet/example_tsnet_test.go index c5a20ab77..2dc44c3d9 100644 --- a/tsnet/example_tsnet_test.go +++ b/tsnet/example_tsnet_test.go @@ -8,6 +8,8 @@ import ( "fmt" "log" "net/http" + "net/http/httputil" + "net/url" "os" "path/filepath" @@ -200,3 +202,50 @@ func ExampleServer_ListenFunnel_funnelOnly() { fmt.Fprintln(w, "Hi there! Welcome to the tailnet!") }))) } + +// ExampleServer_ListenService demonstrates how to advertise an HTTPS Service. +func ExampleServer_ListenService() { + s := &tsnet.Server{ + Hostname: "tsnet-services-demo", + } + defer s.Close() + + ln, err := s.ListenService("svc:my-service", 443, tsnet.ServiceHTTPOptions{HTTPS: true}) + if err != nil { + log.Fatal(err) + } + defer ln.Close() + + log.Printf("Listening on https://%v\n", ln.FQDN) + log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "

Hello, tailnet!

") + }))) +} + +// ExampleServer_ListenService_reverseProxy demonstrates how to advertise a +// Service targeting a reverse proxy. This is useful when the backing server is +// external to the tsnet application. +func ExampleServer_ListenService_reverseProxy() { + // targetAddress represents the address of the backing server. + const targetAddress = "1.2.3.4:80" + + // We will use a reverse proxy to direct traffic to the backing server. + reverseProxy := httputil.NewSingleHostReverseProxy(&url.URL{ + Scheme: "http", + Host: targetAddress, + }) + + s := &tsnet.Server{ + Hostname: "tsnet-services-demo", + } + defer s.Close() + + ln, err := s.ListenService("svc:my-service", 443, tsnet.ServiceHTTPOptions{HTTPS: true}) + if err != nil { + log.Fatal(err) + } + defer ln.Close() + + log.Printf("Listening on https://%v\n", ln.FQDN) + log.Fatal(http.Serve(ln, reverseProxy)) +}