diff options
Diffstat (limited to 'tsnet')
| -rw-r--r-- | tsnet/example/ssh-game/ssh-game.go | 91 | ||||
| -rw-r--r-- | tsnet/tsnet.go | 27 |
2 files changed, 118 insertions, 0 deletions
diff --git a/tsnet/example/ssh-game/ssh-game.go b/tsnet/example/ssh-game/ssh-game.go new file mode 100644 index 000000000..a1b07e50c --- /dev/null +++ b/tsnet/example/ssh-game/ssh-game.go @@ -0,0 +1,91 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// The ssh-game server demonstrates how to use tsnet's ListenSSH to build +// a custom SSH application. It runs a simple "guess the number" game. +// +// Usage: +// +// go run ./tsnet/example/ssh-game +// +// Then from another Tailscale node: +// +// ssh -p 2222 <hostname> +package main + +import ( + "bufio" + "fmt" + "log" + "math/rand/v2" + "net" + "strings" + + _ "tailscale.com/feature/ssh" + "tailscale.com/ssh/tailssh" + "tailscale.com/tsnet" +) + +func main() { + s := &tsnet.Server{ + Hostname: "ssh-game", + } + defer s.Close() + + ln, err := s.ListenSSH(":2222") + if err != nil { + log.Fatal(err) + } + defer ln.Close() + log.Println("Listening on :2222") + + for { + conn, err := ln.Accept() + if err != nil { + log.Fatal(err) + } + go handleGame(conn) + } +} + +func handleGame(c net.Conn) { + sess, ok := c.(*tailssh.Session) + if !ok { + fmt.Fprintf(c, "unexpected connection type\n") + c.Close() + return + } + defer sess.Exit(0) + + peer := sess.PeerIdentity() + target := rand.IntN(100) + 1 + scanner := bufio.NewScanner(sess) + + fmt.Fprintf(sess, "Welcome, %s from %s!\r\n", + peer.UserProfile.LoginName, + peer.Node.ComputedName()) + fmt.Fprintf(sess, "I'm thinking of a number between 1 and 100.\r\n") + fmt.Fprintf(sess, "Can you guess it?\r\n\r\n") + + for attempts := 1; ; attempts++ { + fmt.Fprintf(sess, "Your guess: ") + if !scanner.Scan() { + return + } + line := strings.TrimSpace(scanner.Text()) + var guess int + if _, err := fmt.Sscanf(line, "%d", &guess); err != nil { + fmt.Fprintf(sess, "Please enter a number.\r\n") + continue + } + switch { + case guess < target: + fmt.Fprintf(sess, "Higher!\r\n") + case guess > target: + fmt.Fprintf(sess, "Lower!\r\n") + default: + fmt.Fprintf(sess, "Correct! You got it in %d attempts.\r\n", attempts) + return + } + } +} diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index 4a116cf34..57cd5a004 100644 --- a/tsnet/tsnet.go +++ b/tsnet/tsnet.go @@ -1074,6 +1074,33 @@ func (s *Server) Listen(network, addr string) (net.Listener, error) { return s.listen(network, addr, listenOnTailnet) } +// ListenSSH listens on the Tailscale network for SSH connections at the given +// addr (e.g. ":2222"). The returned listener's Accept method yields net.Conn +// values that are actually *tailssh.Session, providing access to the +// connecting peer's Tailscale identity, PTY information, signals, and more. +// +// Basic applications can use the returned connections as plain net.Conn +// (Read/Write/Close). Applications that need richer SSH semantics should +// type-assert to *tailssh.Session. +// +// SSH support must be linked into the binary by importing +// _ "tailscale.com/feature/ssh". Without that import, ListenSSH returns an +// error. +// +// If s has not been started yet, it will be started. +func (s *Server) ListenSSH(addr string) (net.Listener, error) { + rawLn, err := s.Listen("tcp", addr) + if err != nil { + return nil, err + } + sshLn, err := s.lb.ListenSSH(rawLn, s.logf) + if err != nil { + rawLn.Close() + return nil, err + } + return sshLn, nil +} + // ListenPacket announces on the Tailscale network. // // The network must be "udp", "udp4" or "udp6". The addr must be of the form |
