Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "flag" |
| 6 | "fmt" |
| 7 | "net" |
| 8 | "os" |
| 9 | "path" |
| 10 | "strings" |
| 11 | "time" |
| 12 | |
| 13 | "veyron/examples/tunnel" |
| 14 | "veyron/examples/tunnel/lib" |
| 15 | "veyron/lib/signals" |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 16 | "veyron2/context" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 17 | "veyron2/rt" |
| 18 | "veyron2/vlog" |
| 19 | ) |
| 20 | |
| 21 | var ( |
| 22 | disablePty = flag.Bool("T", false, "Disable pseudo-terminal allocation.") |
| 23 | forcePty = flag.Bool("t", false, "Force allocation of pseudo-terminal.") |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 24 | |
| 25 | portforward = flag.String("L", "", "localaddr,remoteaddr Forward local 'localaddr' to 'remoteaddr'") |
| 26 | lprotocol = flag.String("local_protocol", "tcp", "Local network protocol for port forwarding") |
| 27 | rprotocol = flag.String("remote_protocol", "tcp", "Remote network protocol for port forwarding") |
| 28 | |
| 29 | noshell = flag.Bool("N", false, "Do not execute a shell. Only do port forwarding.") |
| 30 | ) |
| 31 | |
| 32 | func init() { |
| 33 | flag.Usage = func() { |
| 34 | bname := path.Base(os.Args[0]) |
| 35 | fmt.Fprintf(os.Stderr, `%s: Veyron SHell. |
| 36 | |
| 37 | This tool is used to run shell commands or an interactive shell on a remote |
| 38 | tunneld service. |
| 39 | |
| 40 | To open an interactive shell, use: |
Asim Shankar | 2a46dba | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 41 | %s <object name> |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 42 | |
| 43 | To run a shell command, use: |
Asim Shankar | 2a46dba | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 44 | %s <object name> <command to run> |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 45 | |
| 46 | The -L flag will forward connections from a local port to a remote address |
| 47 | through the tunneld service. The flag value is localaddr,remoteaddr. E.g. |
| 48 | -L :14141,www.google.com:80 |
| 49 | |
Asim Shankar | 2a46dba | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 50 | %s can't be used directly with tools like rsync because veyron object names |
| 51 | don't look like traditional hostnames, which rsync doesn't understand. For |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 52 | compatibility with such tools, %s has a special feature that allows passing the |
Asim Shankar | 2a46dba | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 53 | veyron object name via the VSH_NAME environment variable. |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 54 | |
Asim Shankar | 2a46dba | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 55 | $ VSH_NAME=<object name> rsync -avh -e %s /foo/* veyron:/foo/ |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 56 | |
| 57 | In this example, the "veyron" host will be substituted with $VSH_NAME by %s |
| 58 | and rsync will work as expected. |
| 59 | |
| 60 | Full flags: |
| 61 | `, os.Args[0], bname, bname, bname, bname, os.Args[0], bname) |
| 62 | flag.PrintDefaults() |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | func main() { |
| 67 | // Work around the fact that os.Exit doesn't run deferred functions. |
| 68 | os.Exit(realMain()) |
| 69 | } |
| 70 | |
| 71 | func realMain() int { |
| 72 | r := rt.Init() |
Bogdan Caprita | 4258d88 | 2014-07-02 09:15:22 -0700 | [diff] [blame] | 73 | defer r.Cleanup() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 74 | |
Asim Shankar | 2a46dba | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 75 | oname, cmd, err := objectNameAndCommandLine() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 76 | if err != nil { |
| 77 | flag.Usage() |
| 78 | fmt.Fprintf(os.Stderr, "\n%v\n", err) |
| 79 | return 1 |
| 80 | } |
| 81 | |
Asim Shankar | 2a46dba | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 82 | t, err := tunnel.BindTunnel(oname) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 83 | if err != nil { |
Asim Shankar | 2a46dba | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 84 | vlog.Fatalf("BindTunnel(%q) failed: %v", oname, err) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 85 | } |
| 86 | |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 87 | ctx, _ := rt.R().NewContext().WithTimeout(24 * time.Hour) |
| 88 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 89 | if len(*portforward) > 0 { |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 90 | go runPortForwarding(ctx, t, oname) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 91 | } |
| 92 | |
| 93 | if *noshell { |
| 94 | <-signals.ShutdownOnSignals() |
| 95 | return 0 |
| 96 | } |
| 97 | |
| 98 | opts := shellOptions(cmd) |
| 99 | |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 100 | stream, err := t.Shell(ctx, cmd, opts) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 101 | if err != nil { |
| 102 | fmt.Fprintf(os.Stderr, "Error: %v\n", err) |
| 103 | return 1 |
| 104 | } |
Robin Thellend | ad0a965 | 2014-08-01 19:45:40 -0700 | [diff] [blame] | 105 | if opts.UsePty { |
| 106 | saved := lib.EnterRawTerminalMode() |
| 107 | defer lib.RestoreTerminalSettings(saved) |
| 108 | } |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 109 | runIOManager(os.Stdin, os.Stdout, os.Stderr, stream) |
| 110 | |
Asim Shankar | 2a46dba | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 111 | exitMsg := fmt.Sprintf("Connection to %s closed.", oname) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 112 | exitStatus, err := stream.Finish() |
| 113 | if err != nil { |
| 114 | exitMsg += fmt.Sprintf(" (%v)", err) |
| 115 | } |
| 116 | vlog.VI(1).Info(exitMsg) |
| 117 | // Only show the exit message on stdout for interactive shells. |
| 118 | // Otherwise, the exit message might get confused with the output |
| 119 | // of the command that was run. |
| 120 | if err != nil { |
| 121 | fmt.Fprintln(os.Stderr, exitMsg) |
| 122 | } else if len(cmd) == 0 { |
| 123 | fmt.Println(exitMsg) |
| 124 | } |
| 125 | return int(exitStatus) |
| 126 | } |
| 127 | |
| 128 | func shellOptions(cmd string) (opts tunnel.ShellOpts) { |
| 129 | opts.UsePty = (len(cmd) == 0 || *forcePty) && !*disablePty |
| 130 | opts.Environment = environment() |
| 131 | ws, err := lib.GetWindowSize() |
| 132 | if err != nil { |
| 133 | vlog.VI(1).Infof("GetWindowSize failed: %v", err) |
| 134 | } else { |
| 135 | opts.Rows = uint32(ws.Row) |
| 136 | opts.Cols = uint32(ws.Col) |
| 137 | } |
| 138 | return |
| 139 | } |
| 140 | |
| 141 | func environment() []string { |
| 142 | env := []string{} |
| 143 | for _, name := range []string{"TERM", "COLORTERM"} { |
| 144 | if value := os.Getenv(name); value != "" { |
| 145 | env = append(env, fmt.Sprintf("%s=%s", name, value)) |
| 146 | } |
| 147 | } |
| 148 | return env |
| 149 | } |
| 150 | |
Asim Shankar | 2a46dba | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 151 | // objectNameAndCommandLine extracts the object name and the remote command to |
| 152 | // send to the server. The object name is the first non-flag argument. |
| 153 | // The command line is the concatenation of all non-flag arguments excluding |
| 154 | // the object name. |
| 155 | func objectNameAndCommandLine() (string, string, error) { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 156 | args := flag.Args() |
Asim Shankar | 2a46dba | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 157 | if len(args) < 1 { |
Bogdan Caprita | d9281a3 | 2014-07-02 14:40:39 -0700 | [diff] [blame] | 158 | return "", "", errors.New("object name missing") |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 159 | } |
Asim Shankar | 2a46dba | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 160 | name := args[0] |
| 161 | args = args[1:] |
| 162 | // For compatibility with tools like rsync. Because object names |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 163 | // don't look like traditional hostnames, tools that work with rsh and |
| 164 | // ssh can't work directly with vsh. This trick makes the following |
| 165 | // possible: |
Asim Shankar | 2a46dba | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 166 | // $ VSH_NAME=<object name> rsync -avh -e vsh /foo/* veyron:/foo/ |
| 167 | // The "veyron" host will be substituted with <object name>. |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 168 | if envName := os.Getenv("VSH_NAME"); len(envName) > 0 && name == "veyron" { |
| 169 | name = envName |
| 170 | } |
| 171 | cmd := strings.Join(args, " ") |
| 172 | return name, cmd, nil |
| 173 | } |
| 174 | |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 175 | func runPortForwarding(ctx context.T, t tunnel.Tunnel, oname string) { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 176 | // *portforward is localaddr,remoteaddr |
| 177 | parts := strings.Split(*portforward, ",") |
| 178 | var laddr, raddr string |
| 179 | if len(parts) != 2 { |
| 180 | vlog.Fatalf("-L flag expects 2 values separated by a comma") |
| 181 | } |
| 182 | laddr = parts[0] |
| 183 | raddr = parts[1] |
| 184 | |
| 185 | ln, err := net.Listen(*lprotocol, laddr) |
| 186 | if err != nil { |
| 187 | vlog.Fatalf("net.Listen(%q, %q) failed: %v", *lprotocol, laddr, err) |
| 188 | } |
| 189 | defer ln.Close() |
| 190 | vlog.VI(1).Infof("Listening on %q", ln.Addr()) |
| 191 | for { |
| 192 | conn, err := ln.Accept() |
| 193 | if err != nil { |
| 194 | vlog.Infof("Accept failed: %v", err) |
| 195 | continue |
| 196 | } |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 197 | stream, err := t.Forward(ctx, *rprotocol, raddr) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 198 | if err != nil { |
| 199 | vlog.Infof("Tunnel(%q, %q) failed: %v", *rprotocol, raddr, err) |
| 200 | conn.Close() |
| 201 | continue |
| 202 | } |
Asim Shankar | 2a46dba | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 203 | name := fmt.Sprintf("%v-->%v-->(%v)-->%v", conn.RemoteAddr(), conn.LocalAddr(), oname, raddr) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 204 | go func() { |
| 205 | vlog.VI(1).Infof("TUNNEL START: %v", name) |
Shyam Jayaraman | 97b9dca | 2014-07-31 13:30:46 -0700 | [diff] [blame] | 206 | errf := lib.Forward(conn, stream.SendStream(), stream.RecvStream()) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 207 | err := stream.Finish() |
| 208 | vlog.VI(1).Infof("TUNNEL END : %v (%v, %v)", name, errf, err) |
| 209 | }() |
| 210 | } |
| 211 | } |