tsnet: add opt-in SSH support (Server.ListenSSH)
This adds tsnet.Server.ListenSSH which, if the SSH feature is linked, returns a net.Listener whose Accept yields *tailssh.Session values (as net.Conn). This lets tsnet apps accept incoming SSH connections to implement custom TUI applications. Basic apps can use net.Conn directly (Read/Write/Close). Rich apps import ssh/tailssh and type-assert for peer identity, PTY, signals, etc. If feature/ssh isn't imported, ListenSSH returns an error. Includes a demo guess-the-number game in tsnet/example/ssh-game. Updates tailscale/corp#37839 Change-Id: I4e7c3c96afb030cdf4da8f2d8b2253820628129a Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
c9333854fb
commit
3e34e721e8
@@ -0,0 +1,13 @@
|
||||
<!-- README.md auto-generated by misc/genreadme; DO NOT EDIT. (or remove this line) -->
|
||||
|
||||
# ssh-game
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build (linux && !android) || (darwin && !ios) || freebsd || openbsd || plan9
|
||||
|
||||
// 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)
|
||||
|
||||
target := rand.IntN(100) + 1
|
||||
scanner := bufio.NewScanner(sess)
|
||||
|
||||
fmt.Fprintf(sess, "Welcome, %s from %s!\r\n",
|
||||
sess.UserProfile().LoginName,
|
||||
sess.Peer().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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user