diff --git a/tsnet/example_tshello_test.go b/tsnet/example_tshello_test.go new file mode 100644 index 000000000..d534bcfd1 --- /dev/null +++ b/tsnet/example_tshello_test.go @@ -0,0 +1,72 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +package tsnet_test + +import ( + "flag" + "fmt" + "html" + "log" + "net/http" + "strings" + + "tailscale.com/tsnet" +) + +func firstLabel(s string) string { + s, _, _ = strings.Cut(s, ".") + return s +} + +// Example_tshello is a full example on using tsnet. When you run this program it will print +// an authentication link. Open it in your favorite web browser and add it to your tailnet +// like any other machine. Open another terminal window and try to ping it: +// +// $ ping tshello -c 2 +// PING tshello (100.105.183.159) 56(84) bytes of data. +// 64 bytes from tshello.your-tailnet.ts.net (100.105.183.159): icmp_seq=1 ttl=64 time=25.0 ms +// 64 bytes from tshello.your-tailnet.ts.net (100.105.183.159): icmp_seq=2 ttl=64 time=1.12 ms +// +// Then connect to it using curl: +// +// $ curl http://tshello +//

Hello, world!

+//

You are Xe from pneuma (100.78.40.86:49214)

+// +// From here you can do anything you want with the Go standard library HTTP stack, or anything +// that is compatible with it (Gin/Gonic, Gorilla/mux, etc.). +func Example_tshello() { + var ( + addr = flag.String("addr", ":80", "address to listen on") + hostname = flag.String("hostname", "tshello", "hostname to use on the tailnet") + ) + + flag.Parse() + s := new(tsnet.Server) + s.Hostname = *hostname + defer s.Close() + ln, err := s.Listen("tcp", *addr) + if err != nil { + log.Fatal(err) + } + defer ln.Close() + + lc, err := s.LocalClient() + if err != nil { + log.Fatal(err) + } + + log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + who, err := lc.WhoIs(r.Context(), r.RemoteAddr) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + fmt.Fprintf(w, "

Hello, tailnet!

\n") + fmt.Fprintf(w, "

You are %s from %s (%s)

", + html.EscapeString(who.UserProfile.LoginName), + html.EscapeString(firstLabel(who.Node.ComputedName)), + r.RemoteAddr) + }))) +} diff --git a/tsnet/example_tsnet_test.go b/tsnet/example_tsnet_test.go new file mode 100644 index 000000000..dd39d8aa5 --- /dev/null +++ b/tsnet/example_tsnet_test.go @@ -0,0 +1,212 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +package tsnet_test + +import ( + "flag" + "fmt" + "log" + "net/http" + "os" + "path/filepath" + + "tailscale.com/tsnet" +) + +// ExampleServer shows you how to construct a ready-to-use tsnet instance. +func ExampleServer() { + srv := new(tsnet.Server) + if err := srv.Start(); err != nil { + log.Fatalf("can't start tsnet server: %v", err) + } + defer srv.Close() +} + +// ExampleServer_hostname shows you how to set a tsnet server's hostname. +// +// This setting lets you control the host name of your program on your +// tailnet. By default this will be the name of your program (such as foo +// for a program stored at /usr/local/bin/foo). You can also override this +// by setting the Hostname field. +func ExampleServer_hostname() { + srv := &tsnet.Server{ + Hostname: "kirito", + } + + // do something with srv + _ = srv +} + +// ExampleServer_dir shows you how to configure the persistent directory for +// a tsnet application. This is where the Tailscale node information is stored +// so that your application can reconnect to your tailnet when the application +// is restarted. +// +// By default, tsnet will store data in your user configuration directory based +// on the name of the binary. Note that this folder must already exist or tsnet +// calls will fail. +func ExampleServer_dir() { + dir := filepath.Join("/data", "tsnet") + + if err := os.MkdirAll(dir, 0700); err != nil { + log.Fatal(err) + } + + srv := &tsnet.Server{ + Dir: dir, + } + + // do something with srv + _ = srv +} + +// ExampleServer_multipleInstances shows you how to configure multiple instances +// of tsnet per program. This allows you to have multiple Tailscale nodes in the +// same process/container. +func ExampleServer_multipleInstances() { + baseDir := "/data" + var servers []*tsnet.Server + for _, hostname := range []string{"ichika", "nino", "miku", "yotsuba", "itsuki"} { + os.MkdirAll(filepath.Join(baseDir, hostname), 0700) + srv := &tsnet.Server{ + Hostname: hostname, + AuthKey: os.Getenv("TS_AUTHKEY"), + Ephemeral: true, + Dir: filepath.Join(baseDir, hostname), + } + if err := srv.Start(); err != nil { + log.Fatalf("can't start tsnet server: %v", err) + } + servers = append(servers, srv) + } + + // When you're done, close the instances + defer func() { + for _, srv := range servers { + srv.Close() + } + }() +} + +// ExampleServer_ignoreLogs shows you how to ignore all of the log messages written +// by a tsnet instance. +func ExampleServer_ignoreLogs() { + srv := &tsnet.Server{ + Logf: func(string, ...any) {}, + } + _ = srv +} + +// ExampleServer_ignoreLogsSometimes shows you how to ignore all of the log messages +// written by a tsnet instance, but allows you to opt-into them if a command-line +// flag is set. +func ExampleServer_ignoreLogsSometimes() { + tsnetVerbose := flag.Bool("tsnet-verbose", false, "if set, verbosely log tsnet information") + hostname := flag.String("tsnet-hostname", "hikari", "hostname to use on the tailnet") + + srv := &tsnet.Server{ + Hostname: *hostname, + Logf: func(string, ...any) {}, + } + + if *tsnetVerbose { + srv.Logf = log.New(os.Stderr, fmt.Sprintf("[tsnet:%s] ", *hostname), log.LstdFlags).Printf + } +} + +// ExampleServer_HTTPClient shows you how to make HTTP requests over your tailnet. +// +// If you want to make outgoing HTTP connections to resources on your tailnet, use +// the HTTP client that the tsnet.Server exposes. +func ExampleServer_HTTPClient() { + srv := &tsnet.Server{} + cli := srv.HTTPClient() + + resp, err := cli.Get("https://hello.ts.net") + if resp == nil { + log.Fatal(err) + } + // do something with resp + _ = resp +} + +// ExampleServer_Start demonstrates the Start method, which should be called if +// you need to explicitly start it. Note that the Start method is implicitly +// called if needed. +func ExampleServer_Start() { + srv := new(tsnet.Server) + + if err := srv.Start(); err != nil { + log.Fatal(err) + } + + // Be sure to close the server instance at some point. It will stay open until + // either the OS process ends or the server is explicitly closed. + defer srv.Close() +} + +// ExampleServer_Listen shows you how to create a TCP listener on your tailnet and +// then makes an HTTP server on top of that. +func ExampleServer_Listen() { + srv := &tsnet.Server{ + Hostname: "tadaima", + } + + ln, err := srv.Listen("tcp", ":80") + if err != nil { + log.Fatal(err) + } + + log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Hi there! Welcome to the tailnet!") + }))) +} + +// ExampleServer_ListenTLS shows you how to create a TCP listener on your tailnet and +// then makes an HTTPS server on top of that. +func ExampleServer_ListenTLS() { + srv := &tsnet.Server{ + Hostname: "aegis", + } + + ln, err := srv.ListenTLS("tcp", ":443") + if err != nil { + log.Fatal(err) + } + + log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Hi there! Welcome to the tailnet!") + }))) +} + +// ExampleServer_ListenFunnel shows you how to create an HTTPS service on both your tailnet +// and the public internet via Funnel. +func ExampleServer_ListenFunnel() { + srv := &tsnet.Server{ + Hostname: "ophion", + } + + ln, err := srv.ListenFunnel("tcp", ":443") + if err != nil { + log.Fatal(err) + } + + log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Hi there! Welcome to the tailnet!") + }))) +} + +// ExampleServer_ListenFunnel_funnelOnly shows you how to create a funnel-only HTTPS service. +func ExampleServer_ListenFunnel_funnelOnly() { + srv := new(tsnet.Server) + srv.Hostname = "ophion" + ln, err := srv.ListenFunnel("tcp", ":443", tsnet.FunnelOnly()) + if err != nil { + log.Fatal(err) + } + + log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Hi there! Welcome to the tailnet!") + }))) +}