Browse Source

tsnet: add examples (#8289)

Closes #8288

Follows the examples from the KB[1].

[1]: https://tailscale.com/kb/1244/tsnet/

Signed-off-by: Xe Iaso <[email protected]>
Xe Iaso 2 years ago
parent
commit
24f0e91169
2 changed files with 284 additions and 0 deletions
  1. 72 0
      tsnet/example_tshello_test.go
  2. 212 0
      tsnet/example_tsnet_test.go

+ 72 - 0
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
+//	<html><body><h1>Hello, world!</h1>
+//	<p>You are <b>Xe</b> from <b>pneuma</b> (100.78.40.86:49214)</p>
+//
+// 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, "<html><body><h1>Hello, tailnet!</h1>\n")
+		fmt.Fprintf(w, "<p>You are <b>%s</b> from <b>%s</b> (%s)</p>",
+			html.EscapeString(who.UserProfile.LoginName),
+			html.EscapeString(firstLabel(who.Node.ComputedName)),
+			r.RemoteAddr)
+	})))
+}

+ 212 - 0
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!")
+	})))
+}