TBR
v: renaming the v directory to go
Change-Id: I4fd9f6ee2895d8034c23b65927eb118980b3c17a
diff --git a/lib/forward.go b/lib/forward.go
new file mode 100644
index 0000000..5a7c21f
--- /dev/null
+++ b/lib/forward.go
@@ -0,0 +1,71 @@
+package lib
+
+import (
+ "fmt"
+ "io"
+ "net"
+)
+
+type sender interface {
+ Send([]uint8) error
+}
+type receiver interface {
+ Recv() ([]uint8, error)
+}
+
+// stream is the interface common to TunnelForwardStream and TunnelServiceForwardStream.
+type stream interface {
+ sender
+ receiver
+}
+
+// Forward forwards data read from net.Conn to a TunnelForwardStream or a TunnelServiceForwardStream.
+func Forward(conn net.Conn, stream stream) error {
+ defer conn.Close()
+ // Both conn2stream and stream2conn will write to the channel exactly
+ // once.
+ // Forward reads from the channel exactly once.
+ // A buffered channel is used to prevent the other write to the channel
+ // from blocking.
+ done := make(chan error, 1)
+ go conn2stream(conn, stream, done)
+ go stream2conn(stream, conn, done)
+ return <-done
+}
+
+func conn2stream(r io.Reader, s sender, done chan error) {
+ var buf [2048]byte
+ for {
+ n, err := r.Read(buf[:])
+ if err == io.EOF {
+ done <- nil
+ return
+ }
+ if err != nil {
+ done <- err
+ return
+ }
+ if err := s.Send(buf[:n]); err != nil {
+ done <- err
+ return
+ }
+ }
+}
+
+func stream2conn(r receiver, w io.Writer, done chan error) {
+ for {
+ buf, err := r.Recv()
+ if err == io.EOF {
+ done <- nil
+ return
+ }
+ if err != nil {
+ done <- err
+ return
+ }
+ if n, err := w.Write(buf); n != len(buf) || err != nil {
+ done <- fmt.Errorf("conn.Write returned (%d, %v) want (%d, nil)", n, err, len(buf))
+ return
+ }
+ }
+}
diff --git a/lib/terminal.go b/lib/terminal.go
new file mode 100644
index 0000000..1e33bb1
--- /dev/null
+++ b/lib/terminal.go
@@ -0,0 +1,93 @@
+package lib
+
+import (
+ "errors"
+ "os/exec"
+ "strings"
+ "syscall"
+ "unsafe"
+
+ "veyron2/vlog"
+)
+
+// Used with ioctl TIOCGWINSZ and TIOCSWINSZ.
+type Winsize struct {
+ Row uint16
+ Col uint16
+ Xpixel uint16
+ Ypixel uint16
+}
+
+// SetWindowSize sets the terminal's window size.
+func SetWindowSize(fd uintptr, ws Winsize) error {
+ vlog.Infof("Setting window size: %v", ws)
+ ret, _, _ := syscall.Syscall(
+ syscall.SYS_IOCTL,
+ fd,
+ uintptr(syscall.TIOCSWINSZ),
+ uintptr(unsafe.Pointer(&ws)))
+ if int(ret) == -1 {
+ return errors.New("ioctl(TIOCSWINSZ) failed")
+ }
+ return nil
+}
+
+// GetWindowSize gets the terminal's window size.
+func GetWindowSize() (*Winsize, error) {
+ ws := &Winsize{}
+ ret, _, _ := syscall.Syscall(
+ syscall.SYS_IOCTL,
+ uintptr(syscall.Stdin),
+ uintptr(syscall.TIOCGWINSZ),
+ uintptr(unsafe.Pointer(ws)))
+ if int(ret) == -1 {
+ return nil, errors.New("ioctl(TIOCGWINSZ) failed")
+ }
+ return ws, nil
+}
+
+func EnterRawTerminalMode() string {
+ var savedBytes []byte
+ var err error
+ if savedBytes, err = exec.Command("stty", "-F", "/dev/tty", "-g").Output(); err != nil {
+ vlog.Infof("Failed to save terminal settings: %q (%v)", savedBytes, err)
+ }
+ saved := strings.TrimSpace(string(savedBytes))
+
+ args := []string{
+ "-F", "/dev/tty",
+ // Don't buffer stdin. Read characters as they are typed.
+ "-icanon", "min", "1", "time", "0",
+ // Turn off local echo of input characters.
+ "-echo", "-echoe", "-echok", "-echonl",
+ // Disable interrupt, quit, and suspend special characters.
+ "-isig",
+ // Ignore characters with parity errors.
+ "ignpar",
+ // Disable translate newline to carriage return.
+ "-inlcr",
+ // Disable ignore carriage return.
+ "-igncr",
+ // Disable translate carriage return to newline.
+ "-icrnl",
+ // Disable flow control.
+ "-ixon", "-ixany", "-ixoff",
+ // Disable non-POSIX special characters.
+ "-iexten",
+ }
+ if out, err := exec.Command("stty", args...).CombinedOutput(); err != nil {
+ vlog.Infof("stty failed (%v) (%q)", err, out)
+ }
+
+ return string(saved)
+}
+
+func RestoreTerminalSettings(saved string) {
+ args := []string{
+ "-F", "/dev/tty",
+ saved,
+ }
+ if out, err := exec.Command("stty", args...).CombinedOutput(); err != nil {
+ vlog.Infof("stty failed (%v) (%q)", err, out)
+ }
+}
diff --git a/tunnel.idl b/tunnel.idl
new file mode 100644
index 0000000..88eb2bf
--- /dev/null
+++ b/tunnel.idl
@@ -0,0 +1,40 @@
+package tunnel
+
+import "veyron2/security"
+
+// Tunnel creates a network tunnel from the client to the server.
+
+type Tunnel interface {
+ // The Forward method is used for network forwarding. All the data sent over
+ // the byte stream is forwarded to the requested network address and all the
+ // data received from that network connection is sent back in the reply
+ // stream.
+ Forward(network, address string) stream<[]byte, []byte> error {security.AdminLabel}
+
+ // The Shell method is used to either run shell commands remotely, or to open
+ // an interactive shell. The data received over the byte stream is sent to the
+ // shell's stdin, and the data received from the shell's stdout and stderr is
+ // sent back in the reply stream. It returns the exit status of the shell
+ // command.
+ Shell(command string, shellOpts ShellOpts) stream<ClientShellPacket, ServerShellPacket> (int32, error) {security.AdminLabel}
+}
+
+type ShellOpts struct {
+ UsePty bool // Whether to open a pseudo-terminal
+ Environment []string // Environment variables to pass to the remote shell.
+ Rows, Cols uint32 // Window size.
+}
+
+type ClientShellPacket struct {
+ // Bytes going to the shell's stdin.
+ Stdin []byte
+ // A dynamic update of the window size. The default value of 0 means no-change.
+ Rows, Cols uint32
+}
+
+type ServerShellPacket struct {
+ // Bytes coming from the shell's stdout.
+ Stdout []byte
+ // Bytes coming from the shell's stderr.
+ Stderr []byte
+}
diff --git a/tunnel.idl.go b/tunnel.idl.go
new file mode 100644
index 0000000..14c039c
--- /dev/null
+++ b/tunnel.idl.go
@@ -0,0 +1,423 @@
+// This file was auto-generated by the veyron idl tool.
+// Source: tunnel.idl
+
+package tunnel
+
+import (
+ "veyron2/security"
+
+ // The non-user imports are prefixed with "_gen_" to prevent collisions.
+ _gen_idl "veyron2/idl"
+ _gen_ipc "veyron2/ipc"
+ _gen_naming "veyron2/naming"
+ _gen_rt "veyron2/rt/r"
+ _gen_wiretype "veyron2/wiretype"
+)
+
+type ShellOpts struct {
+ UsePty bool // Whether to open a pseudo-terminal
+ Environment []string // Environment variables to pass to the remote shell.
+ Rows uint32 // Window size.
+ Cols uint32
+}
+type ClientShellPacket struct {
+ // Bytes going to the shell's stdin.
+ Stdin []byte
+ // A dynamic update of the window size. The default value of 0 means no-change.
+ Rows uint32
+ Cols uint32
+}
+type ServerShellPacket struct {
+ // Bytes coming from the shell's stdout.
+ Stdout []byte
+ // Bytes coming from the shell's stderr.
+ Stderr []byte
+}
+
+// Tunnel is the interface the client binds and uses.
+// Tunnel_InternalNoTagGetter is the interface without the TagGetter
+// and UnresolveStep methods (both framework-added, rathern than user-defined),
+// to enable embedding without method collisions. Not to be used directly by
+// clients.
+type Tunnel_InternalNoTagGetter interface {
+
+ // The Forward method is used for network forwarding. All the data sent over
+ // the byte stream is forwarded to the requested network address and all the
+ // data received from that network connection is sent back in the reply
+ // stream.
+ Forward(network string, address string, opts ..._gen_ipc.ClientCallOpt) (reply TunnelForwardStream, err error)
+
+ // The Shell method is used to either run shell commands remotely, or to open
+ // an interactive shell. The data received over the byte stream is sent to the
+ // shell's stdin, and the data received from the shell's stdout and stderr is
+ // sent back in the reply stream. It returns the exit status of the shell
+ // command.
+ Shell(command string, shellOpts ShellOpts, opts ..._gen_ipc.ClientCallOpt) (reply TunnelShellStream, err error)
+}
+type Tunnel interface {
+ _gen_idl.TagGetter
+ // UnresolveStep returns the names for the remote service, rooted at the
+ // service's immediate namespace ancestor.
+ UnresolveStep(opts ..._gen_ipc.ClientCallOpt) ([]string, error)
+ Tunnel_InternalNoTagGetter
+}
+
+// TunnelService is the interface the server implements.
+type TunnelService interface {
+
+ // The Forward method is used for network forwarding. All the data sent over
+ // the byte stream is forwarded to the requested network address and all the
+ // data received from that network connection is sent back in the reply
+ // stream.
+ Forward(context _gen_ipc.Context, network string, address string, stream TunnelServiceForwardStream) (err error)
+
+ // The Shell method is used to either run shell commands remotely, or to open
+ // an interactive shell. The data received over the byte stream is sent to the
+ // shell's stdin, and the data received from the shell's stdout and stderr is
+ // sent back in the reply stream. It returns the exit status of the shell
+ // command.
+ Shell(context _gen_ipc.Context, command string, shellOpts ShellOpts, stream TunnelServiceShellStream) (reply int32, err error)
+}
+
+// TunnelForwardStream is the interface for streaming responses of the method
+// Forward in the service interface Tunnel.
+type TunnelForwardStream interface {
+
+ // Send places the item onto the output stream, blocking if there is no buffer
+ // space available.
+ Send(item []byte) error
+
+ // CloseSend indicates to the server that no more items will be sent; server
+ // Recv calls will receive io.EOF after all sent items. Subsequent calls to
+ // Send on the client will fail. This is an optional call - it's used by
+ // streaming clients that need the server to receive the io.EOF terminator.
+ CloseSend() error
+
+ // Recv returns the next item in the input stream, blocking until
+ // an item is available. Returns io.EOF to indicate graceful end of input.
+ Recv() (item []byte, err error)
+
+ // Finish closes the stream and returns the positional return values for
+ // call.
+ Finish() (err error)
+
+ // Cancel cancels the RPC, notifying the server to stop processing.
+ Cancel()
+}
+
+// Implementation of the TunnelForwardStream interface that is not exported.
+type implTunnelForwardStream struct {
+ clientCall _gen_ipc.ClientCall
+}
+
+func (c *implTunnelForwardStream) Send(item []byte) error {
+ return c.clientCall.Send(item)
+}
+
+func (c *implTunnelForwardStream) CloseSend() error {
+ return c.clientCall.CloseSend()
+}
+
+func (c *implTunnelForwardStream) Recv() (item []byte, err error) {
+ err = c.clientCall.Recv(&item)
+ return
+}
+
+func (c *implTunnelForwardStream) Finish() (err error) {
+ if ierr := c.clientCall.Finish(&err); ierr != nil {
+ err = ierr
+ }
+ return
+}
+
+func (c *implTunnelForwardStream) Cancel() {
+ c.clientCall.Cancel()
+}
+
+// TunnelServiceForwardStream is the interface for streaming responses of the method
+// Forward in the service interface Tunnel.
+type TunnelServiceForwardStream interface {
+ // Send places the item onto the output stream, blocking if there is no buffer
+ // space available.
+ Send(item []byte) error
+
+ // Recv fills itemptr with the next item in the input stream, blocking until
+ // an item is available. Returns io.EOF to indicate graceful end of input.
+ Recv() (item []byte, err error)
+}
+
+// Implementation of the TunnelServiceForwardStream interface that is not exported.
+type implTunnelServiceForwardStream struct {
+ serverCall _gen_ipc.ServerCall
+}
+
+func (s *implTunnelServiceForwardStream) Send(item []byte) error {
+ return s.serverCall.Send(item)
+}
+
+func (s *implTunnelServiceForwardStream) Recv() (item []byte, err error) {
+ err = s.serverCall.Recv(&item)
+ return
+}
+
+// TunnelShellStream is the interface for streaming responses of the method
+// Shell in the service interface Tunnel.
+type TunnelShellStream interface {
+
+ // Send places the item onto the output stream, blocking if there is no buffer
+ // space available.
+ Send(item ClientShellPacket) error
+
+ // CloseSend indicates to the server that no more items will be sent; server
+ // Recv calls will receive io.EOF after all sent items. Subsequent calls to
+ // Send on the client will fail. This is an optional call - it's used by
+ // streaming clients that need the server to receive the io.EOF terminator.
+ CloseSend() error
+
+ // Recv returns the next item in the input stream, blocking until
+ // an item is available. Returns io.EOF to indicate graceful end of input.
+ Recv() (item ServerShellPacket, err error)
+
+ // Finish closes the stream and returns the positional return values for
+ // call.
+ Finish() (reply int32, err error)
+
+ // Cancel cancels the RPC, notifying the server to stop processing.
+ Cancel()
+}
+
+// Implementation of the TunnelShellStream interface that is not exported.
+type implTunnelShellStream struct {
+ clientCall _gen_ipc.ClientCall
+}
+
+func (c *implTunnelShellStream) Send(item ClientShellPacket) error {
+ return c.clientCall.Send(item)
+}
+
+func (c *implTunnelShellStream) CloseSend() error {
+ return c.clientCall.CloseSend()
+}
+
+func (c *implTunnelShellStream) Recv() (item ServerShellPacket, err error) {
+ err = c.clientCall.Recv(&item)
+ return
+}
+
+func (c *implTunnelShellStream) Finish() (reply int32, err error) {
+ if ierr := c.clientCall.Finish(&reply, &err); ierr != nil {
+ err = ierr
+ }
+ return
+}
+
+func (c *implTunnelShellStream) Cancel() {
+ c.clientCall.Cancel()
+}
+
+// TunnelServiceShellStream is the interface for streaming responses of the method
+// Shell in the service interface Tunnel.
+type TunnelServiceShellStream interface {
+ // Send places the item onto the output stream, blocking if there is no buffer
+ // space available.
+ Send(item ServerShellPacket) error
+
+ // Recv fills itemptr with the next item in the input stream, blocking until
+ // an item is available. Returns io.EOF to indicate graceful end of input.
+ Recv() (item ClientShellPacket, err error)
+}
+
+// Implementation of the TunnelServiceShellStream interface that is not exported.
+type implTunnelServiceShellStream struct {
+ serverCall _gen_ipc.ServerCall
+}
+
+func (s *implTunnelServiceShellStream) Send(item ServerShellPacket) error {
+ return s.serverCall.Send(item)
+}
+
+func (s *implTunnelServiceShellStream) Recv() (item ClientShellPacket, err error) {
+ err = s.serverCall.Recv(&item)
+ return
+}
+
+// BindTunnel returns the client stub implementing the Tunnel
+// interface.
+//
+// If no _gen_ipc.Client is specified, the default _gen_ipc.Client in the
+// global Runtime is used.
+func BindTunnel(name string, opts ..._gen_ipc.BindOpt) (Tunnel, error) {
+ var client _gen_ipc.Client
+ switch len(opts) {
+ case 0:
+ client = _gen_rt.R().Client()
+ case 1:
+ switch o := opts[0].(type) {
+ case _gen_ipc.Runtime:
+ client = o.Client()
+ case _gen_ipc.Client:
+ client = o
+ default:
+ return nil, _gen_idl.ErrUnrecognizedOption
+ }
+ default:
+ return nil, _gen_idl.ErrTooManyOptionsToBind
+ }
+ stub := &clientStubTunnel{client: client, name: name}
+
+ return stub, nil
+}
+
+// NewServerTunnel creates a new server stub.
+//
+// It takes a regular server implementing the TunnelService
+// interface, and returns a new server stub.
+func NewServerTunnel(server TunnelService) interface{} {
+ return &ServerStubTunnel{
+ service: server,
+ }
+}
+
+// clientStubTunnel implements Tunnel.
+type clientStubTunnel struct {
+ client _gen_ipc.Client
+ name string
+}
+
+func (c *clientStubTunnel) GetMethodTags(method string) []interface{} {
+ return GetTunnelMethodTags(method)
+}
+
+func (__gen_c *clientStubTunnel) Forward(network string, address string, opts ..._gen_ipc.ClientCallOpt) (reply TunnelForwardStream, err error) {
+ var call _gen_ipc.ClientCall
+ if call, err = __gen_c.client.StartCall(__gen_c.name, "Forward", []interface{}{network, address}, opts...); err != nil {
+ return
+ }
+ reply = &implTunnelForwardStream{clientCall: call}
+ return
+}
+
+func (__gen_c *clientStubTunnel) Shell(command string, shellOpts ShellOpts, opts ..._gen_ipc.ClientCallOpt) (reply TunnelShellStream, err error) {
+ var call _gen_ipc.ClientCall
+ if call, err = __gen_c.client.StartCall(__gen_c.name, "Shell", []interface{}{command, shellOpts}, opts...); err != nil {
+ return
+ }
+ reply = &implTunnelShellStream{clientCall: call}
+ return
+}
+
+func (c *clientStubTunnel) UnresolveStep(opts ..._gen_ipc.ClientCallOpt) (reply []string, err error) {
+ var call _gen_ipc.ClientCall
+ if call, err = c.client.StartCall(c.name, "UnresolveStep", nil, opts...); err != nil {
+ return
+ }
+ if ierr := call.Finish(&reply, &err); ierr != nil {
+ err = ierr
+ }
+ return
+}
+
+// ServerStubTunnel wraps a server that implements
+// TunnelService and provides an object that satisfies
+// the requirements of veyron2/ipc.ReflectInvoker.
+type ServerStubTunnel struct {
+ service TunnelService
+}
+
+func (s *ServerStubTunnel) GetMethodTags(method string) []interface{} {
+ return GetTunnelMethodTags(method)
+}
+
+func (s *ServerStubTunnel) Signature(call _gen_ipc.ServerCall) (_gen_ipc.ServiceSignature, error) {
+ result := _gen_ipc.ServiceSignature{Methods: make(map[string]_gen_ipc.MethodSignature)}
+ result.Methods["Forward"] = _gen_ipc.MethodSignature{
+ InArgs: []_gen_ipc.MethodArgument{
+ {Name: "network", Type: 3},
+ {Name: "address", Type: 3},
+ },
+ OutArgs: []_gen_ipc.MethodArgument{
+ {Name: "", Type: 65},
+ },
+ InStream: 67,
+ OutStream: 67,
+ }
+ result.Methods["Shell"] = _gen_ipc.MethodSignature{
+ InArgs: []_gen_ipc.MethodArgument{
+ {Name: "command", Type: 3},
+ {Name: "shellOpts", Type: 68},
+ },
+ OutArgs: []_gen_ipc.MethodArgument{
+ {Name: "", Type: 36},
+ {Name: "", Type: 65},
+ },
+ InStream: 69,
+ OutStream: 70,
+ }
+
+ result.TypeDefs = []_gen_idl.AnyData{
+ _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "byte", Tags: []string(nil)}, _gen_wiretype.SliceType{Elem: 0x42, Name: "", Tags: []string(nil)}, _gen_wiretype.StructType{
+ []_gen_wiretype.FieldType{
+ _gen_wiretype.FieldType{Type: 0x2, Name: "UsePty"},
+ _gen_wiretype.FieldType{Type: 0x3d, Name: "Environment"},
+ _gen_wiretype.FieldType{Type: 0x34, Name: "Rows"},
+ _gen_wiretype.FieldType{Type: 0x34, Name: "Cols"},
+ },
+ "ShellOpts", []string(nil)},
+ _gen_wiretype.StructType{
+ []_gen_wiretype.FieldType{
+ _gen_wiretype.FieldType{Type: 0x43, Name: "Stdin"},
+ _gen_wiretype.FieldType{Type: 0x34, Name: "Rows"},
+ _gen_wiretype.FieldType{Type: 0x34, Name: "Cols"},
+ },
+ "ClientShellPacket", []string(nil)},
+ _gen_wiretype.StructType{
+ []_gen_wiretype.FieldType{
+ _gen_wiretype.FieldType{Type: 0x43, Name: "Stdout"},
+ _gen_wiretype.FieldType{Type: 0x43, Name: "Stderr"},
+ },
+ "ServerShellPacket", []string(nil)},
+ }
+
+ return result, nil
+}
+
+func (s *ServerStubTunnel) UnresolveStep(call _gen_ipc.ServerCall) (reply []string, err error) {
+ if unresolver, ok := s.service.(_gen_ipc.Unresolver); ok {
+ return unresolver.UnresolveStep(call)
+ }
+ if call.Server() == nil {
+ return
+ }
+ var published []string
+ if published, err = call.Server().Published(); err != nil || published == nil {
+ return
+ }
+ reply = make([]string, len(published))
+ for i, p := range published {
+ reply[i] = _gen_naming.Join(p, call.Name())
+ }
+ return
+}
+
+func (__gen_s *ServerStubTunnel) Forward(call _gen_ipc.ServerCall, network string, address string) (err error) {
+ stream := &implTunnelServiceForwardStream{serverCall: call}
+ err = __gen_s.service.Forward(call, network, address, stream)
+ return
+}
+
+func (__gen_s *ServerStubTunnel) Shell(call _gen_ipc.ServerCall, command string, shellOpts ShellOpts) (reply int32, err error) {
+ stream := &implTunnelServiceShellStream{serverCall: call}
+ reply, err = __gen_s.service.Shell(call, command, shellOpts, stream)
+ return
+}
+
+func GetTunnelMethodTags(method string) []interface{} {
+ switch method {
+ case "Forward":
+ return []interface{}{security.Label(4)}
+ case "Shell":
+ return []interface{}{security.Label(4)}
+ default:
+ return nil
+ }
+}
diff --git a/tunneld/impl/impl.go b/tunneld/impl/impl.go
new file mode 100644
index 0000000..cd210ed
--- /dev/null
+++ b/tunneld/impl/impl.go
@@ -0,0 +1,162 @@
+package impl
+
+import (
+ "github.com/kr/pty"
+
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net"
+ "os"
+ "os/exec"
+ "syscall"
+
+ "veyron/examples/tunnel"
+ "veyron/examples/tunnel/lib"
+ "veyron2/ipc"
+ "veyron2/vlog"
+)
+
+// T implements tunnel.TunnelService
+type T struct {
+}
+
+func (t *T) Forward(ctx ipc.Context, network, address string, stream tunnel.TunnelServiceForwardStream) error {
+ conn, err := net.Dial(network, address)
+ if err != nil {
+ return err
+ }
+ name := fmt.Sprintf("RemoteID:%v LocalAddr:%v RemoteAddr:%v", ctx.RemoteID(), conn.LocalAddr(), conn.RemoteAddr())
+ vlog.Infof("TUNNEL START: %v", name)
+ err = lib.Forward(conn, stream)
+ vlog.Infof("TUNNEL END : %v (%v)", name, err)
+ return err
+}
+
+func (t *T) Shell(ctx ipc.Context, command string, shellOpts tunnel.ShellOpts, stream tunnel.TunnelServiceShellStream) (int32, error) {
+ vlog.Infof("SHELL START for %v: %q", ctx.RemoteID(), command)
+
+ const nonShellErrorCode = 255
+
+ shell, err := findShell()
+ if err != nil {
+ return nonShellErrorCode, err
+ }
+
+ var c *exec.Cmd
+ // An empty command means that we need an interactive shell.
+ if len(command) == 0 {
+ c = exec.Command(shell, "-i")
+ sendMotd(stream)
+ } else {
+ c = exec.Command(shell, "-c", command)
+ }
+
+ c.Env = []string{
+ fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
+ fmt.Sprintf("VEYRON_LOCAL_IDENTITY=%s", ctx.LocalID()),
+ fmt.Sprintf("VEYRON_REMOTE_IDENTITY=%s", ctx.RemoteID()),
+ }
+ c.Env = append(c.Env, shellOpts.Environment...)
+ vlog.Infof("Shell environment: %v", c.Env)
+
+ c.Dir = os.Getenv("HOME")
+ vlog.Infof("Shell CWD: %v", c.Dir)
+
+ var (
+ stdin io.WriteCloser // We write to stdin.
+ stdout, stderr io.ReadCloser // We read from stdout and stderr.
+ ptyFd uintptr // File descriptor for pty.
+ )
+
+ if shellOpts.UsePty {
+ f, err := pty.Start(c)
+ if err != nil {
+ return nonShellErrorCode, err
+ }
+ stdin = f
+ stdout = f
+ stderr = nil
+ ptyFd = f.Fd()
+
+ defer f.Close()
+
+ setWindowSize(ptyFd, shellOpts.Rows, shellOpts.Cols)
+ } else {
+ var err error
+ if stdin, err = c.StdinPipe(); err != nil {
+ return nonShellErrorCode, err
+ }
+ defer stdin.Close()
+
+ if stdout, err = c.StdoutPipe(); err != nil {
+ return nonShellErrorCode, err
+ }
+ defer stdout.Close()
+
+ if stderr, err = c.StderrPipe(); err != nil {
+ return nonShellErrorCode, err
+ }
+ defer stderr.Close()
+
+ if err = c.Start(); err != nil {
+ vlog.Infof("Cmd.Start failed: %v", err)
+ return nonShellErrorCode, err
+ }
+ }
+
+ defer c.Process.Kill()
+
+ ferr := runIOManager(stdin, stdout, stderr, ptyFd, stream)
+ vlog.Infof("SHELL END for %v: %q (%v)", ctx.RemoteID(), command, ferr)
+
+ // Check the exit status.
+ var status syscall.WaitStatus
+ if _, err := syscall.Wait4(c.Process.Pid, &status, syscall.WNOHANG, nil); err != nil {
+ return nonShellErrorCode, err
+ }
+ if status.Signaled() {
+ return int32(status), fmt.Errorf("process killed by signal %d (%v)", int(status.Signal()), status.Signal())
+ }
+ if status.Exited() {
+ if status.ExitStatus() == 0 {
+ return 0, nil
+ }
+ return int32(status.ExitStatus()), fmt.Errorf("process exited with exit status %d", status.ExitStatus())
+ }
+
+ // The process has not exited. Use the error from ForwardStdIO.
+ return nonShellErrorCode, ferr
+}
+
+// findShell returns the path to the first usable shell binary.
+func findShell() (string, error) {
+ shells := []string{"/bin/bash", "/bin/sh"}
+ for _, s := range shells {
+ if _, err := os.Stat(s); err == nil {
+ return s, nil
+ }
+ }
+ return "", errors.New("could not find any shell binary")
+}
+
+// sendMotd sends the content of the MOTD file to the stream, if it exists.
+func sendMotd(s tunnel.TunnelServiceShellStream) {
+ data, err := ioutil.ReadFile("/etc/motd")
+ if err != nil {
+ // No MOTD. That's OK.
+ return
+ }
+ packet := tunnel.ServerShellPacket{Stdout: []byte(data)}
+ if err = s.Send(packet); err != nil {
+ vlog.Infof("Send failed: %v", err)
+ }
+}
+
+func setWindowSize(fd uintptr, row, col uint32) {
+ ws := lib.Winsize{Row: uint16(row), Col: uint16(col)}
+ if err := lib.SetWindowSize(fd, ws); err != nil {
+ vlog.Infof("Failed to set window size: %v", err)
+ }
+}
diff --git a/tunneld/impl/iomanager.go b/tunneld/impl/iomanager.go
new file mode 100644
index 0000000..fe422db
--- /dev/null
+++ b/tunneld/impl/iomanager.go
@@ -0,0 +1,113 @@
+package impl
+
+import (
+ "fmt"
+ "io"
+
+ "veyron/examples/tunnel"
+ "veyron2/vlog"
+)
+
+func runIOManager(stdin io.Writer, stdout, stderr io.Reader, ptyFd uintptr, stream tunnel.TunnelServiceShellStream) error {
+ m := ioManager{stdin: stdin, stdout: stdout, stderr: stderr, ptyFd: ptyFd, stream: stream}
+ return m.run()
+}
+
+// ioManager manages the forwarding of all the data between the shell and the
+// stream.
+type ioManager struct {
+ stdin io.Writer
+ stdout, stderr io.Reader
+ ptyFd uintptr
+ stream tunnel.TunnelServiceShellStream
+
+ // done receives any error from chan2stream, user2stream, or
+ // stream2user.
+ done chan error
+ // outchan is used to serialize the output to the stream. This is
+ // needed because stream.Send is not thread-safe.
+ outchan chan tunnel.ServerShellPacket
+}
+
+func (m *ioManager) run() error {
+ // done receives any error from chan2stream, stdout2stream, or
+ // stream2stdin.
+ m.done = make(chan error, 3)
+
+ // outchan is used to serialize the output to the stream.
+ // chan2stream() receives data sent by stdout2outchan() and
+ // stderr2outchan() and sends it to the stream.
+ m.outchan = make(chan tunnel.ServerShellPacket)
+ defer close(m.outchan)
+ go m.chan2stream()
+
+ // Forward data between the shell's stdio and the stream.
+ go m.stdout2outchan()
+ if m.stderr != nil {
+ go m.stderr2outchan()
+ }
+ go m.stream2stdin()
+
+ // Block until something reports an error.
+ return <-m.done
+}
+
+// chan2stream receives ServerShellPacket from outchan and sends it to stream.
+func (m *ioManager) chan2stream() {
+ for packet := range m.outchan {
+ if err := m.stream.Send(packet); err != nil {
+ m.done <- err
+ return
+ }
+ }
+ m.done <- io.EOF
+}
+
+// stdout2stream reads data from the shell's stdout and sends it to the outchan.
+func (m *ioManager) stdout2outchan() {
+ for {
+ buf := make([]byte, 2048)
+ n, err := m.stdout.Read(buf[:])
+ if err != nil {
+ vlog.VI(2).Infof("stdout2outchan: %v", err)
+ m.done <- err
+ return
+ }
+ m.outchan <- tunnel.ServerShellPacket{Stdout: buf[:n]}
+ }
+}
+
+// stderr2stream reads data from the shell's stderr and sends it to the outchan.
+func (m *ioManager) stderr2outchan() {
+ for {
+ buf := make([]byte, 2048)
+ n, err := m.stderr.Read(buf[:])
+ if err != nil {
+ vlog.VI(2).Infof("stderr2outchan: %v", err)
+ m.done <- err
+ return
+ }
+ m.outchan <- tunnel.ServerShellPacket{Stderr: buf[:n]}
+ }
+}
+
+// stream2stdin reads data from the stream and sends it to the shell's stdin.
+func (m *ioManager) stream2stdin() {
+ for {
+ packet, err := m.stream.Recv()
+ if err != nil {
+ vlog.VI(2).Infof("stream2stdin: %v", err)
+ m.done <- err
+ return
+ }
+ if len(packet.Stdin) > 0 {
+ if n, err := m.stdin.Write(packet.Stdin); n != len(packet.Stdin) || err != nil {
+ m.done <- fmt.Errorf("stdin.Write returned (%d, %v) want (%d, nil)", n, err, len(packet.Stdin))
+ return
+ }
+ }
+ if packet.Rows > 0 && packet.Cols > 0 && m.ptyFd != 0 {
+ setWindowSize(m.ptyFd, packet.Rows, packet.Cols)
+ }
+ }
+}
diff --git a/tunneld/main.go b/tunneld/main.go
new file mode 100644
index 0000000..44aaf0e
--- /dev/null
+++ b/tunneld/main.go
@@ -0,0 +1,99 @@
+package main
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "net"
+ "os"
+ "strings"
+
+ "veyron/examples/tunnel"
+ "veyron/examples/tunnel/tunneld/impl"
+ "veyron/lib/signals"
+ isecurity "veyron/runtimes/google/security"
+ "veyron2/ipc"
+ "veyron2/rt"
+ "veyron2/security"
+ "veyron2/vlog"
+)
+
+var (
+ protocol = flag.String("protocol", "tcp", "network to listen on. For example, set to 'veyron' and set --address to the endpoint/name of a proxy to have this tunnel service proxied.")
+ address = flag.String("address", ":0", "address to listen on")
+
+ users = flag.String("users", "", "A comma-separated list of principal patterns allowed to use this service.")
+)
+
+// firstHardwareAddrInUse returns the hwaddr of the first network interface
+// that is up, excluding loopback.
+func firstHardwareAddrInUse() (string, error) {
+ interfaces, err := net.Interfaces()
+ if err != nil {
+ return "", err
+ }
+ for _, i := range interfaces {
+ if i.Name != "lo" && i.Flags&net.FlagUp != 0 {
+ name := i.HardwareAddr.String()
+ vlog.Infof("Using %q (from %v)", name, i.Name)
+ return name, nil
+ }
+ }
+ return "", errors.New("No usable network interfaces")
+}
+
+func authorizer() security.Authorizer {
+ ACL := make(security.ACL)
+ principals := strings.Split(*users, ",")
+ for _, p := range principals {
+ ACL[security.PrincipalPattern(p)] = security.LabelSet(security.AdminLabel)
+ }
+ return isecurity.NewACLAuthorizer(ACL)
+}
+
+func main() {
+ r := rt.Init()
+ defer r.Shutdown()
+ server, err := r.NewServer()
+ if err != nil {
+ vlog.Fatalf("NewServer failed: %v", err)
+ }
+ defer server.Stop()
+
+ if err := server.Register("", ipc.SoloDispatcher(tunnel.NewServerTunnel(&impl.T{}), authorizer())); err != nil {
+ vlog.Fatalf("Register failed: %v", err)
+ }
+ ep, err := server.Listen(*protocol, *address)
+ if err != nil {
+ vlog.Fatalf("Listen(%q, %q) failed: %v", "tcp", *address, err)
+ }
+ hwaddr, err := firstHardwareAddrInUse()
+ if err != nil {
+ vlog.Fatalf("Couldn't find a good hw address: %v", err)
+ }
+ hostname, err := os.Hostname()
+ if err != nil {
+ vlog.Fatalf("os.Hostname failed: %v", err)
+ }
+ // TODO(rthellend): This is not secure. We should use
+ // rt.R().Product().ID() and the associated verification, when it is
+ // ready.
+ names := []string{
+ fmt.Sprintf("tunnel/hostname/%s", hostname),
+ fmt.Sprintf("tunnel/hwaddr/%s", hwaddr),
+ fmt.Sprintf("tunnel/id/%s", rt.R().Identity().PublicID()),
+ }
+ published := false
+ for _, n := range names {
+ if err := server.Publish(n); err != nil {
+ vlog.Infof("Publish(%v) failed: %v", n, err)
+ continue
+ }
+ published = true
+ }
+ if !published {
+ vlog.Fatalf("Failed to publish with any of %v", names)
+ }
+ vlog.Infof("Listening on endpoint /%s (published as %v)", ep, names)
+ <-signals.ShutdownOnSignals()
+}
diff --git a/vsh/iomanager.go b/vsh/iomanager.go
new file mode 100644
index 0000000..00cd29d
--- /dev/null
+++ b/vsh/iomanager.go
@@ -0,0 +1,114 @@
+package main
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "os/signal"
+ "syscall"
+
+ "veyron/examples/tunnel"
+ "veyron/examples/tunnel/lib"
+ "veyron2/vlog"
+)
+
+func runIOManager(stdin io.Reader, stdout, stderr io.Writer, stream tunnel.TunnelShellStream) error {
+ m := ioManager{stdin: stdin, stdout: stdout, stderr: stderr, stream: stream}
+ return m.run()
+}
+
+// ioManager manages the forwarding of all the data between the shell and the
+// stream.
+type ioManager struct {
+ stdin io.Reader
+ stdout, stderr io.Writer
+ stream tunnel.TunnelShellStream
+
+ // done receives any error from chan2stream, user2outchan, or
+ // stream2user.
+ done chan error
+ // outchan is used to serialize the output to the stream. This is
+ // needed because stream.Send is not thread-safe.
+ outchan chan tunnel.ClientShellPacket
+}
+
+func (m *ioManager) run() error {
+ m.done = make(chan error, 3)
+ // outchan is used to serialize the output to the stream.
+ // chan2stream() receives data sent by handleWindowResize() and
+ // user2outchan() and sends it to the stream.
+ m.outchan = make(chan tunnel.ClientShellPacket)
+ defer close(m.outchan)
+ go m.chan2stream()
+ // When the terminal window is resized, we receive a SIGWINCH. Then we
+ // send the new window size to the server.
+ winch := make(chan os.Signal, 1)
+ signal.Notify(winch, syscall.SIGWINCH)
+ defer signal.Stop(winch)
+ go m.handleWindowResize(winch)
+ // Forward data between the user and the remote shell.
+ go m.user2outchan()
+ go m.stream2user()
+ // Block until something reports an error.
+ return <-m.done
+}
+
+// chan2stream receives ClientShellPacket from outchan and sends it to stream.
+func (m *ioManager) chan2stream() {
+ for packet := range m.outchan {
+ if err := m.stream.Send(packet); err != nil {
+ m.done <- err
+ return
+ }
+ }
+ m.done <- io.EOF
+}
+
+func (m *ioManager) handleWindowResize(winch chan os.Signal) {
+ for _ = range winch {
+ ws, err := lib.GetWindowSize()
+ if err != nil {
+ vlog.Infof("GetWindowSize failed: %v", err)
+ continue
+ }
+ m.outchan <- tunnel.ClientShellPacket{Rows: uint32(ws.Row), Cols: uint32(ws.Col)}
+ }
+}
+
+// user2stream reads input from stdin and sends it to the outchan.
+func (m *ioManager) user2outchan() {
+ for {
+ buf := make([]byte, 2048)
+ n, err := m.stdin.Read(buf[:])
+ if err != nil {
+ vlog.VI(2).Infof("user2stream: %v", err)
+ m.done <- err
+ return
+ }
+ m.outchan <- tunnel.ClientShellPacket{Stdin: buf[:n]}
+ }
+}
+
+// stream2user reads data from the stream and sends it to either stdout or stderr.
+func (m *ioManager) stream2user() {
+ for {
+ packet, err := m.stream.Recv()
+ if err != nil {
+ vlog.VI(2).Infof("stream2user: %v", err)
+ m.done <- err
+ return
+ }
+ if len(packet.Stdout) > 0 {
+ if n, err := m.stdout.Write(packet.Stdout); n != len(packet.Stdout) || err != nil {
+ m.done <- fmt.Errorf("stdout.Write returned (%d, %v) want (%d, nil)", n, err, len(packet.Stdout))
+ return
+ }
+ }
+ if len(packet.Stderr) > 0 {
+ if n, err := m.stderr.Write(packet.Stderr); n != len(packet.Stderr) || err != nil {
+ m.done <- fmt.Errorf("stderr.Write returned (%d, %v) want (%d, nil)", n, err, len(packet.Stderr))
+ return
+ }
+ }
+ }
+}
diff --git a/vsh/main.go b/vsh/main.go
new file mode 100644
index 0000000..943835e
--- /dev/null
+++ b/vsh/main.go
@@ -0,0 +1,213 @@
+package main
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "net"
+ "os"
+ "path"
+ "strings"
+ "time"
+
+ "veyron/examples/tunnel"
+ "veyron/examples/tunnel/lib"
+ "veyron/lib/signals"
+ "veyron2"
+ "veyron2/rt"
+ "veyron2/vlog"
+)
+
+var (
+ disablePty = flag.Bool("T", false, "Disable pseudo-terminal allocation.")
+ forcePty = flag.Bool("t", false, "Force allocation of pseudo-terminal.")
+ vname = flag.String("vname", "", "Veyron name (or endpoint) for tunneling service.")
+
+ portforward = flag.String("L", "", "localaddr,remoteaddr Forward local 'localaddr' to 'remoteaddr'")
+ lprotocol = flag.String("local_protocol", "tcp", "Local network protocol for port forwarding")
+ rprotocol = flag.String("remote_protocol", "tcp", "Remote network protocol for port forwarding")
+
+ noshell = flag.Bool("N", false, "Do not execute a shell. Only do port forwarding.")
+)
+
+func init() {
+ flag.Usage = func() {
+ bname := path.Base(os.Args[0])
+ fmt.Fprintf(os.Stderr, `%s: Veyron SHell.
+
+This tool is used to run shell commands or an interactive shell on a remote
+tunneld service.
+
+To open an interactive shell, use:
+ %s --host=<veyron name or endpoint>
+
+To run a shell command, use:
+ %s --host=<veyron name or endpoint> <command to run>
+
+The -L flag will forward connections from a local port to a remote address
+through the tunneld service. The flag value is localaddr,remoteaddr. E.g.
+ -L :14141,www.google.com:80
+
+%s can't be used directly with tools like rsync because veyron addresses don't
+look like traditional hostnames, which rsync doesn't understand. For
+compatibility with such tools, %s has a special feature that allows passing the
+veyron address via the VSH_NAME environment variable.
+
+ $ VSH_NAME=<veyron address> rsync -avh -e %s /foo/* veyron:/foo/
+
+In this example, the "veyron" host will be substituted with $VSH_NAME by %s
+and rsync will work as expected.
+
+Full flags:
+`, os.Args[0], bname, bname, bname, bname, os.Args[0], bname)
+ flag.PrintDefaults()
+ }
+}
+
+func main() {
+ // Work around the fact that os.Exit doesn't run deferred functions.
+ os.Exit(realMain())
+}
+
+func realMain() int {
+ r := rt.Init()
+ defer r.Shutdown()
+
+ host, cmd, err := veyronNameAndCommandLine()
+ if err != nil {
+ flag.Usage()
+ fmt.Fprintf(os.Stderr, "\n%v\n", err)
+ return 1
+ }
+
+ t, err := tunnel.BindTunnel(host)
+ if err != nil {
+ vlog.Fatalf("BindTunnel(%q) failed: %v", host, err)
+ }
+
+ if len(*portforward) > 0 {
+ go runPortForwarding(t, host)
+ }
+
+ if *noshell {
+ <-signals.ShutdownOnSignals()
+ return 0
+ }
+
+ opts := shellOptions(cmd)
+
+ stream, err := t.Shell(cmd, opts, veyron2.CallTimeout(24*time.Hour))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return 1
+ }
+ saved := lib.EnterRawTerminalMode()
+ defer lib.RestoreTerminalSettings(saved)
+ runIOManager(os.Stdin, os.Stdout, os.Stderr, stream)
+
+ exitMsg := fmt.Sprintf("Connection to %s closed.", host)
+ exitStatus, err := stream.Finish()
+ if err != nil {
+ exitMsg += fmt.Sprintf(" (%v)", err)
+ }
+ vlog.VI(1).Info(exitMsg)
+ // Only show the exit message on stdout for interactive shells.
+ // Otherwise, the exit message might get confused with the output
+ // of the command that was run.
+ if err != nil {
+ fmt.Fprintln(os.Stderr, exitMsg)
+ } else if len(cmd) == 0 {
+ fmt.Println(exitMsg)
+ }
+ return int(exitStatus)
+}
+
+func shellOptions(cmd string) (opts tunnel.ShellOpts) {
+ opts.UsePty = (len(cmd) == 0 || *forcePty) && !*disablePty
+ opts.Environment = environment()
+ ws, err := lib.GetWindowSize()
+ if err != nil {
+ vlog.VI(1).Infof("GetWindowSize failed: %v", err)
+ } else {
+ opts.Rows = uint32(ws.Row)
+ opts.Cols = uint32(ws.Col)
+ }
+ return
+}
+
+func environment() []string {
+ env := []string{}
+ for _, name := range []string{"TERM", "COLORTERM"} {
+ if value := os.Getenv(name); value != "" {
+ env = append(env, fmt.Sprintf("%s=%s", name, value))
+ }
+ }
+ return env
+}
+
+// veyronNameAndCommandLine extracts the veyron name and the remote command to
+// send to the server. The name can be specified with the --vname flag or as the
+// first non-flag argument. The command line is the concatenation of all the
+// non-flag arguments, minus the veyron name.
+func veyronNameAndCommandLine() (string, string, error) {
+ name := *vname
+ args := flag.Args()
+ if len(name) == 0 {
+ if len(args) > 0 {
+ name = args[0]
+ args = args[1:]
+ }
+ }
+ if len(name) == 0 {
+ return "", "", errors.New("veyron name missing")
+ }
+ // For compatibility with tools like rsync. Because veyron addresses
+ // don't look like traditional hostnames, tools that work with rsh and
+ // ssh can't work directly with vsh. This trick makes the following
+ // possible:
+ // $ VSH_NAME=<veyron address> rsync -avh -e vsh /foo/* veyron:/foo/
+ // The "veyron" host will be substituted with <veyron address>.
+ if envName := os.Getenv("VSH_NAME"); len(envName) > 0 && name == "veyron" {
+ name = envName
+ }
+ cmd := strings.Join(args, " ")
+ return name, cmd, nil
+}
+
+func runPortForwarding(t tunnel.Tunnel, host string) {
+ // *portforward is localaddr,remoteaddr
+ parts := strings.Split(*portforward, ",")
+ var laddr, raddr string
+ if len(parts) != 2 {
+ vlog.Fatalf("-L flag expects 2 values separated by a comma")
+ }
+ laddr = parts[0]
+ raddr = parts[1]
+
+ ln, err := net.Listen(*lprotocol, laddr)
+ if err != nil {
+ vlog.Fatalf("net.Listen(%q, %q) failed: %v", *lprotocol, laddr, err)
+ }
+ defer ln.Close()
+ vlog.VI(1).Infof("Listening on %q", ln.Addr())
+ for {
+ conn, err := ln.Accept()
+ if err != nil {
+ vlog.Infof("Accept failed: %v", err)
+ continue
+ }
+ stream, err := t.Forward(*rprotocol, raddr, veyron2.CallTimeout(24*time.Hour))
+ if err != nil {
+ vlog.Infof("Tunnel(%q, %q) failed: %v", *rprotocol, raddr, err)
+ conn.Close()
+ continue
+ }
+ name := fmt.Sprintf("%v-->%v-->(%v)-->%v", conn.RemoteAddr(), conn.LocalAddr(), host, raddr)
+ go func() {
+ vlog.VI(1).Infof("TUNNEL START: %v", name)
+ errf := lib.Forward(conn, stream)
+ err := stream.Finish()
+ vlog.VI(1).Infof("TUNNEL END : %v (%v, %v)", name, errf, err)
+ }()
+ }
+}