Adding DUT Server impl + Tests

Splitting up exec and fetch as two portions to have a working tester
earlier. Note that tests require that we create a wrapper for most ssh
items as they are structs and not interfaces and as such cannot be
mocked.

BUG=None
TEST=None

Change-Id: Ieb779551acf36ded1a88b049e0d0699f1595880e
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/2946488
Reviewed-by: C Shapiro <shapiroc@chromium.org>
Reviewed-by: Andrew Lamb <andrewlamb@chromium.org>
Reviewed-by: Jaques Clapauch <jaquesc@google.com>
Commit-Queue: Jaques Clapauch <jaquesc@google.com>
Tested-by: Jaques Clapauch <jaquesc@google.com>
Auto-Submit: Jaques Clapauch <jaquesc@google.com>
diff --git a/src/chromiumos/test/dut/cmd/dutserver/dutserver.go b/src/chromiumos/test/dut/cmd/dutserver/dutserver.go
index a38576d..3d65cc3 100644
--- a/src/chromiumos/test/dut/cmd/dutserver/dutserver.go
+++ b/src/chromiumos/test/dut/cmd/dutserver/dutserver.go
@@ -6,41 +6,124 @@
 package main
 
 import (
-	"chromiumos/lro"
+	"bytes"
+	"context"
+	"errors"
 	"log"
 	"net"
 
 	"go.chromium.org/chromiumos/config/go/test/api"
+	"golang.org/x/crypto/ssh"
 	"google.golang.org/grpc"
+
+	"chromiumos/test/dut/cmd/dutserver/dutssh"
 )
 
 // DutServiceServer implementation of dut_service.proto
 type DutServiceServer struct {
-	Manager *lro.Manager
-	logger  *log.Logger
+	logger     *log.Logger
+	connection dutssh.ClientInterface
 }
 
-// Create a new provision service server to listen to rpc requests.
-func newDutServiceServer(l net.Listener, logger *log.Logger) (*grpc.Server, error) {
+// newDutServiceServer creates a new dut service server to listen to rpc requests.
+func newDutServiceServer(l net.Listener, logger *log.Logger, conn dutssh.ClientInterface) *grpc.Server {
 	s := &DutServiceServer{
-		Manager: lro.New(),
-		logger:  logger,
+		logger:     logger,
+		connection: conn,
 	}
-	defer s.Manager.Close()
+
 	server := grpc.NewServer()
 	api.RegisterDutServiceServer(server, s)
 	logger.Println("dutservice listen to request at ", l.Addr().String())
-	return server, nil
+	return server
 }
 
-// Remotely execute a command on the DUT.
+// ExecCommand remotely executes a command on the DUT.
 func (s *DutServiceServer) ExecCommand(req *api.ExecCommandRequest, stream api.DutService_ExecCommandServer) error {
 	s.logger.Println("Received api.ExecCommandRequest: ", *req)
-	return stream.Send(&api.ExecCommandResponse{})
+	resp := s.runCmd(req.Command)
+	return stream.Send(resp)
 }
 
-// Remotely fetch crashes from the DUT.
+// FetchCrashes remotely fetches crashes from the DUT.
 func (s *DutServiceServer) FetchCrashes(req *api.FetchCrashesRequest, stream api.DutService_FetchCrashesServer) error {
 	s.logger.Println("Received api.FetchCrashesRequest: ", *req)
 	return stream.Send(&api.FetchCrashesResponse{})
 }
+
+// GetConnection resolves the dut address name to ip address and ssh into it
+func GetConnection(ctx context.Context, dutAddress string, wiringAddress string) (*ssh.Client, error) {
+	addr, err := dutssh.GetSSHAddr(ctx, dutAddress, wiringAddress)
+	if err != nil {
+		return nil, err
+	}
+
+	return ssh.Dial("tcp", addr, dutssh.GetSSHConfig())
+}
+
+// runCmd run remote command returning return value, stdout, stderr, and error if any
+func (s *DutServiceServer) runCmd(cmd string) *api.ExecCommandResponse {
+	session, err := s.connection.NewSession()
+	if err != nil {
+		return &api.ExecCommandResponse{
+			ExitInfo: createFailedToStartExitInfo(err),
+		}
+	}
+	defer session.Close()
+
+	var stdOut bytes.Buffer
+	var stdErr bytes.Buffer
+	session.SetStdout(&stdOut)
+	session.SetStderr(&stdErr)
+	err = session.Run(cmd)
+
+	return &api.ExecCommandResponse{
+		Stdout:   stdOut.Bytes(),
+		Stderr:   stdErr.Bytes(),
+		ExitInfo: getExitInfo(err),
+	}
+}
+
+// getExitInfo extracts exit info from Session Run's error
+func getExitInfo(runError error) *api.ExecCommandResponse_ExitInfo {
+	// If no error, command succeeded
+	if runError == nil {
+		return createCommandSucceededExitInfo()
+	}
+
+	// If ExitError, command ran but did not succeed
+	var ee *ssh.ExitError
+	if errors.As(runError, &ee) {
+		return createCommandFailedExitInfo(ee)
+	}
+
+	// Otherwise we assume command failed to start
+	return createFailedToStartExitInfo(runError)
+}
+
+func createFailedToStartExitInfo(err error) *api.ExecCommandResponse_ExitInfo {
+	return &api.ExecCommandResponse_ExitInfo{
+		Status:       42, // Contract dictates arbitrary response, thus 42 is as good as any number
+		Signaled:     false,
+		Started:      false,
+		ErrorMessage: err.Error(),
+	}
+}
+
+func createCommandSucceededExitInfo() *api.ExecCommandResponse_ExitInfo {
+	return &api.ExecCommandResponse_ExitInfo{
+		Status:       0,
+		Signaled:     false,
+		Started:      true,
+		ErrorMessage: "",
+	}
+}
+
+func createCommandFailedExitInfo(err *ssh.ExitError) *api.ExecCommandResponse_ExitInfo {
+	return &api.ExecCommandResponse_ExitInfo{
+		Status:       int32(err.ExitStatus()),
+		Signaled:     true,
+		Started:      true,
+		ErrorMessage: "",
+	}
+}
diff --git a/src/chromiumos/test/dut/cmd/dutserver/dutserver_test.go b/src/chromiumos/test/dut/cmd/dutserver/dutserver_test.go
index 7baeb4a..e5e52fc 100644
--- a/src/chromiumos/test/dut/cmd/dutserver/dutserver_test.go
+++ b/src/chromiumos/test/dut/cmd/dutserver/dutserver_test.go
@@ -6,15 +6,50 @@
 
 import (
 	"bytes"
+	"chromiumos/test/dut/cmd/dutserver/dutssh"
 	"context"
+	"errors"
+	"io"
 	"log"
 	"net"
 	"testing"
 
 	"go.chromium.org/chromiumos/config/go/test/api"
+	"golang.org/x/crypto/ssh"
 	"google.golang.org/grpc"
 )
 
+type MockSession_Success struct {
+	Stdout io.Writer
+	Stderr io.Writer
+}
+
+func (s *MockSession_Success) Close() error {
+	return nil
+}
+func (s *MockSession_Success) SetStdout(writer io.Writer) {
+	s.Stdout = writer
+}
+func (s *MockSession_Success) SetStderr(writer io.Writer) {
+	s.Stderr = writer
+}
+
+func (s *MockSession_Success) Run(cmd string) error {
+	s.Stdout.Write([]byte("success!"))
+	s.Stderr.Write([]byte("not failed!"))
+	return nil
+}
+
+type MockConnection_Success struct{}
+
+func (c *MockConnection_Success) Close() error {
+	return nil
+}
+
+func (c *MockConnection_Success) NewSession() (dutssh.SessionInterface, error) {
+	return &MockSession_Success{}, nil
+}
+
 // Tests if DutServiceServer can handle empty request without problem.
 func TestDutServiceServer_Empty(t *testing.T) {
 	var logBuf bytes.Buffer
@@ -24,7 +59,8 @@
 	}
 
 	ctx := context.Background()
-	srv, err := newDutServiceServer(l, log.New(&logBuf, "", log.LstdFlags|log.LUTC))
+	dutConn := &MockConnection_Success{}
+	srv := newDutServiceServer(l, log.New(&logBuf, "", log.LstdFlags|log.LUTC), dutConn)
 	if err != nil {
 		t.Fatalf("Failed to start DutServiceServer: %v", err)
 	}
@@ -42,3 +78,294 @@
 		t.Fatalf("Failed at api.ExecCommand: %v", err)
 	}
 }
+
+// Tests that a command executes successfully
+func TestDutServiceServer_CommandWorks(t *testing.T) {
+	var logBuf bytes.Buffer
+	l, err := net.Listen("tcp", ":0")
+	if err != nil {
+		t.Fatal("Failed to create a net listener: ", err)
+	}
+
+	ctx := context.Background()
+	dutConn := &MockConnection_Success{}
+	srv := newDutServiceServer(l, log.New(&logBuf, "", log.LstdFlags|log.LUTC), dutConn)
+	if err != nil {
+		t.Fatalf("Failed to start DutServiceServer: %v", err)
+	}
+	go srv.Serve(l)
+	defer srv.Stop()
+
+	conn, err := grpc.Dial(l.Addr().String(), grpc.WithInsecure())
+	if err != nil {
+		t.Fatalf("Failed to dial: %v", err)
+	}
+	defer conn.Close()
+
+	cl := api.NewDutServiceClient(conn)
+	stream, err := cl.ExecCommand(ctx, &api.ExecCommandRequest{})
+	if err != nil {
+		t.Fatalf("Failed at api.ExecCommand: %v", err)
+	}
+
+	resp := &api.ExecCommandResponse{}
+	err = stream.RecvMsg(resp)
+	if err != nil {
+		t.Fatalf("Failed at api.ExecCommand: %v", err)
+	}
+
+	if resp.ExitInfo.Status != 0 {
+		t.Fatalf("Expecting return type to be 0, instead got: %v", resp.ExitInfo.Status)
+	}
+
+	if string(resp.Stderr) != "not failed!" {
+		t.Fatalf("Expecting stderr to be \"not failed\", instead got %v", string(resp.Stderr))
+	}
+
+	if string(resp.Stdout) != "success!" {
+		t.Fatalf("Expecting stderr to be \"success\", instead got %v", string(resp.Stderr))
+	}
+
+	if resp.ExitInfo.Signaled {
+		t.Fatalf("Signalled should not be set!")
+	}
+
+	if !resp.ExitInfo.Started {
+		t.Fatalf("Started should be set!")
+	}
+}
+
+type MockSession_CommandFailed struct {
+	Stdout io.Writer
+	Stderr io.Writer
+}
+
+func (s *MockSession_CommandFailed) Close() error {
+	return nil
+}
+func (s *MockSession_CommandFailed) SetStdout(writer io.Writer) {
+	s.Stdout = writer
+}
+func (s *MockSession_CommandFailed) SetStderr(writer io.Writer) {
+	s.Stderr = writer
+}
+
+func (s *MockSession_CommandFailed) Run(cmd string) error {
+	s.Stdout.Write([]byte("not success!"))
+	s.Stderr.Write([]byte("failure!"))
+	wm := ssh.Waitmsg{}
+	return &ssh.ExitError{
+		Waitmsg: wm,
+	}
+}
+
+type MockConnection_CommandFailed struct{}
+
+func (c *MockConnection_CommandFailed) Close() error {
+	return nil
+}
+
+func (c *MockConnection_CommandFailed) NewSession() (dutssh.SessionInterface, error) {
+	return &MockSession_CommandFailed{}, nil
+}
+
+// Tests that a command does not execute successfully
+func TestDutServiceServer_CommandFails(t *testing.T) {
+	var logBuf bytes.Buffer
+	l, err := net.Listen("tcp", ":0")
+	if err != nil {
+		t.Fatal("Failed to create a net listener: ", err)
+	}
+
+	ctx := context.Background()
+	dutConn := &MockConnection_CommandFailed{}
+	srv := newDutServiceServer(l, log.New(&logBuf, "", log.LstdFlags|log.LUTC), dutConn)
+	if err != nil {
+		t.Fatalf("Failed to start DutServiceServer: %v", err)
+	}
+	go srv.Serve(l)
+	defer srv.Stop()
+
+	conn, err := grpc.Dial(l.Addr().String(), grpc.WithInsecure())
+	if err != nil {
+		t.Fatalf("Failed to dial: %v", err)
+	}
+	defer conn.Close()
+
+	cl := api.NewDutServiceClient(conn)
+	stream, err := cl.ExecCommand(ctx, &api.ExecCommandRequest{})
+	if err != nil {
+		t.Fatalf("Failed at api.ExecCommand: %v", err)
+	}
+
+	resp := &api.ExecCommandResponse{}
+	err = stream.RecvMsg(resp)
+	if err != nil {
+		t.Fatalf("Failed at api.ExecCommand: %v", err)
+	}
+
+	if string(resp.Stderr) != "failure!" {
+		t.Fatalf("Expecting stderr to be \"not success\", instead got %v", string(resp.Stderr))
+	}
+
+	if string(resp.Stdout) != "not success!" {
+		t.Fatalf("Expecting stderr to be \"failure\", instead got %v", string(resp.Stderr))
+	}
+
+	if !resp.ExitInfo.Signaled {
+		t.Fatalf("Signalled should be set!")
+	}
+
+	if !resp.ExitInfo.Started {
+		t.Fatalf("Started should be set!")
+	}
+}
+
+type MockSession_PreCommandFailure struct {
+	Stdout io.Writer
+	Stderr io.Writer
+}
+
+func (s *MockSession_PreCommandFailure) Close() error {
+	return nil
+}
+func (s *MockSession_PreCommandFailure) SetStdout(writer io.Writer) {
+	s.Stdout = writer
+}
+func (s *MockSession_PreCommandFailure) SetStderr(writer io.Writer) {
+	s.Stderr = writer
+}
+
+func (s *MockSession_PreCommandFailure) Run(cmd string) error {
+	s.Stdout.Write([]byte(""))
+	s.Stderr.Write([]byte(""))
+	return &ssh.ExitMissingError{}
+}
+
+type MockConnection_PreCommandFailure struct{}
+
+func (c *MockConnection_PreCommandFailure) Close() error {
+	return nil
+}
+
+func (c *MockConnection_PreCommandFailure) NewSession() (dutssh.SessionInterface, error) {
+	return &MockSession_PreCommandFailure{}, nil
+}
+
+// Tests that a command does not execute
+func TestDutServiceServer_PreCommandFails(t *testing.T) {
+	var logBuf bytes.Buffer
+	l, err := net.Listen("tcp", ":0")
+	if err != nil {
+		t.Fatal("Failed to create a net listener: ", err)
+	}
+
+	ctx := context.Background()
+	dutConn := &MockConnection_PreCommandFailure{}
+	srv := newDutServiceServer(l, log.New(&logBuf, "", log.LstdFlags|log.LUTC), dutConn)
+	if err != nil {
+		t.Fatalf("Failed to start DutServiceServer: %v", err)
+	}
+	go srv.Serve(l)
+	defer srv.Stop()
+
+	conn, err := grpc.Dial(l.Addr().String(), grpc.WithInsecure())
+	if err != nil {
+		t.Fatalf("Failed to dial: %v", err)
+	}
+	defer conn.Close()
+
+	cl := api.NewDutServiceClient(conn)
+	stream, err := cl.ExecCommand(ctx, &api.ExecCommandRequest{})
+	if err != nil {
+		t.Fatalf("Failed at api.ExecCommand: %v", err)
+	}
+
+	resp := &api.ExecCommandResponse{}
+	err = stream.RecvMsg(resp)
+	if err != nil {
+		t.Fatalf("Failed at api.ExecCommand: %v", err)
+	}
+
+	if string(resp.Stderr) != "" {
+		t.Fatalf("Expecting stderr to be empty, instead got %v", string(resp.Stderr))
+	}
+
+	if string(resp.Stdout) != "" {
+		t.Fatalf("Expecting stderr to be empty, instead got %v", string(resp.Stderr))
+	}
+
+	if resp.ExitInfo.Signaled {
+		t.Fatalf("Signalled should not be set!")
+	}
+
+	if resp.ExitInfo.Started {
+		t.Fatalf("Started should not be set!")
+	}
+}
+
+type MockConnection_NewSessionFailure struct{}
+
+func (c *MockConnection_NewSessionFailure) Close() error {
+	return nil
+}
+
+func (c *MockConnection_NewSessionFailure) NewSession() (dutssh.SessionInterface, error) {
+	return nil, errors.New("Session failed.")
+}
+
+// Tests that a session fails
+func TestDutServiceServer_NewSessionFails(t *testing.T) {
+	var logBuf bytes.Buffer
+	l, err := net.Listen("tcp", ":0")
+	if err != nil {
+		t.Fatal("Failed to create a net listener: ", err)
+	}
+
+	ctx := context.Background()
+	dutConn := &MockConnection_NewSessionFailure{}
+	srv := newDutServiceServer(l, log.New(&logBuf, "", log.LstdFlags|log.LUTC), dutConn)
+	if err != nil {
+		t.Fatalf("Failed to start DutServiceServer: %v", err)
+	}
+	go srv.Serve(l)
+	defer srv.Stop()
+
+	conn, err := grpc.Dial(l.Addr().String(), grpc.WithInsecure())
+	if err != nil {
+		t.Fatalf("Failed to dial: %v", err)
+	}
+	defer conn.Close()
+
+	cl := api.NewDutServiceClient(conn)
+	stream, err := cl.ExecCommand(ctx, &api.ExecCommandRequest{})
+	if err != nil {
+		t.Fatalf("Failed at api.ExecCommand: %v", err)
+	}
+
+	resp := &api.ExecCommandResponse{}
+	err = stream.RecvMsg(resp)
+	if err != nil {
+		t.Fatalf("Failed at api.ExecCommand: %v", err)
+	}
+
+	if string(resp.Stderr) != "" {
+		t.Fatalf("Expecting stderr to be empty, instead got %v", string(resp.Stderr))
+	}
+
+	if string(resp.Stdout) != "" {
+		t.Fatalf("Expecting stderr to be empty, instead got %v", string(resp.Stderr))
+	}
+
+	if resp.ExitInfo.Signaled {
+		t.Fatalf("Signalled should not be set!")
+	}
+
+	if resp.ExitInfo.Started {
+		t.Fatalf("Started should not be set!")
+	}
+
+	if resp.ExitInfo.ErrorMessage != "Session failed." {
+		t.Fatalf("Error message should be session failed, instead got %v", resp.ExitInfo.ErrorMessage)
+	}
+}
diff --git a/src/chromiumos/test/dut/cmd/dutserver/dutssh/connection.go b/src/chromiumos/test/dut/cmd/dutserver/dutssh/connection.go
new file mode 100644
index 0000000..3e50619
--- /dev/null
+++ b/src/chromiumos/test/dut/cmd/dutserver/dutssh/connection.go
@@ -0,0 +1,60 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package dutssh
+
+import (
+	"io"
+
+	"golang.org/x/crypto/ssh"
+)
+
+// This file only exists because go cannot mock structs and the ssh client
+// library does not provide interfaces for testing.
+type ClientInterface interface {
+	Close() error
+	NewSession() (SessionInterface, error)
+}
+
+type SSHClient struct {
+	*ssh.Client
+}
+
+func (c *SSHClient) Close() error {
+	return c.Client.Close()
+}
+
+func (c *SSHClient) NewSession() (SessionInterface, error) {
+	session, err := c.Client.NewSession()
+	if err != nil {
+		return nil, err
+	}
+	return &SSHSession{Session: session}, nil
+}
+
+type SessionInterface interface {
+	Close() error
+	SetStdout(writer io.Writer)
+	SetStderr(writer io.Writer)
+	Run(cmd string) error
+}
+
+type SSHSession struct {
+	*ssh.Session
+}
+
+func (s *SSHSession) Close() error {
+	return s.Session.Close()
+}
+func (s *SSHSession) SetStdout(writer io.Writer) {
+	s.Session.Stdout = writer
+}
+
+func (s *SSHSession) SetStderr(writer io.Writer) {
+	s.Session.Stderr = writer
+}
+
+func (s *SSHSession) Run(cmd string) error {
+	return s.Session.Run(cmd)
+}
diff --git a/src/chromiumos/test/dut/cmd/dutserver/dutssh/network.go b/src/chromiumos/test/dut/cmd/dutserver/dutssh/network.go
new file mode 100644
index 0000000..d90ebb8
--- /dev/null
+++ b/src/chromiumos/test/dut/cmd/dutserver/dutssh/network.go
@@ -0,0 +1,55 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package dutssh
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"go.chromium.org/chromiumos/config/go/api/test/tls"
+	"golang.org/x/crypto/ssh"
+	"google.golang.org/grpc"
+)
+
+// GetSSHAddr returns the SSH address to use for the DUT, through the wiring service.
+func GetSSHAddr(ctx context.Context, name string, wiringAddress string) (string, error) {
+	c, err := createWiringClient(wiringAddress)
+	if err != nil {
+		return "", err
+	}
+	resp, err := c.OpenDutPort(ctx, &tls.OpenDutPortRequest{
+		Name: name,
+		Port: 22,
+	})
+	if err != nil {
+		return "", err
+	}
+	return fmt.Sprintf("%s:%d", resp.GetAddress(), resp.GetPort()), nil
+}
+
+// GetSSHConfig construct a static ssh config
+func GetSSHConfig() *ssh.ClientConfig {
+	return &ssh.ClientConfig{
+		User: "root",
+		// We don't care about the host key for DUTs.
+		// Attackers intercepting our connections to DUTs is not part
+		// of our attack profile.
+		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+		Timeout:         5 * time.Second,
+		// Use the well known testing RSA key as the default SSH auth
+		// method.
+		Auth: []ssh.AuthMethod{ssh.PublicKeys(testingSSHSigner)},
+	}
+}
+
+// createWiringClient creates a client to wiring service
+func createWiringClient(wiringAddress string) (tls.WiringClient, error) {
+	conn, err := grpc.Dial(wiringAddress, grpc.WithInsecure())
+	if err != nil {
+		return nil, err
+	}
+	return tls.WiringClient(tls.NewWiringClient(conn)), nil
+}
diff --git a/src/chromiumos/test/dut/cmd/dutserver/dutssh/ssh_keys.go b/src/chromiumos/test/dut/cmd/dutserver/dutssh/ssh_keys.go
new file mode 100644
index 0000000..bf7e8b1
--- /dev/null
+++ b/src/chromiumos/test/dut/cmd/dutserver/dutssh/ssh_keys.go
@@ -0,0 +1,54 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package dutssh
+
+import (
+	"fmt"
+
+	"golang.org/x/crypto/ssh"
+)
+
+// The provided key is copied from
+// https://chromium.googlesource.com/chromiumos/chromite/+/master/ssh_keys/testing_rsa
+// It's a well known "private" key used widely in Chrome OS testing.
+const sshKeyContent = `
+-----BEGIN RSA PRIVATE KEY-----
+MIIEoAIBAAKCAQEAvsNpFdK5lb0GfKx+FgsrsM/2+aZVFYXHMPdvGtTz63ciRhq0
+Jnw7nln1SOcHraSz3/imECBg8NHIKV6rA+B9zbf7pZXEv20x5Ul0vrcPqYWC44PT
+tgsgvi8s0KZUZN93YlcjZ+Q7BjQ/tuwGSaLWLqJ7hnHALMJ3dbEM9fKBHQBCrG5H
+OaWD2gtXj7jp04M/WUnDDdemq/KMg6E9jcrJOiQ39IuTpas4hLQzVkKAKSrpl6MY
+2etHyoNarlWhcOwitArEDwf3WgnctwKstI/MTKB5BTpO2WXUNUv4kXzA+g8/l1al
+jIG13vtd9A/IV3KFVx/sLkkjuZ7z2rQXyNKuJwIBIwKCAQA79EWZJPh/hI0CnJyn
+16AEXp4T8nKDG2p9GpCiCGnq6u2Dvz/u1pZk97N9T+x4Zva0GvJc1vnlST7objW/
+Y8/ET8QeGSCT7x5PYDqiVspoemr3DCyYTKPkADKn+cLAngDzBXGHDTcfNP4U6xfr
+Qc5JK8BsFR8kApqSs/zCU4eqBtp2FVvPbgUOv3uUrFnjEuGs9rb1QZ0K6o08L4Cq
+N+e2nTysjp78blakZfqlurqTY6iJb0ImU2W3T8sV6w5GP1NT7eicXLO3WdIRB15a
+evogPeqtMo8GcO62wU/D4UCvq4GNEjvYOvFmPzXHvhTxsiWv5KEACtleBIEYmWHA
+POwrAoGBAOKgNRgxHL7r4bOmpLQcYK7xgA49OpikmrebXCQnZ/kZ3QsLVv1QdNMH
+Rx/ex7721g8R0oWslM14otZSMITCDCMWTYVBNM1bqYnUeEu5HagFwxjQ2tLuSs8E
+SBzEr96JLfhwuBhDH10sQqn+OQG1yj5acs4Pt3L4wlYwMx0vs1BxAoGBANd9Owro
+5ONiJXfKNaNY/cJYuLR+bzGeyp8oxToxgmM4UuA4hhDU7peg4sdoKJ4XjB9cKMCz
+ZGU5KHKKxNf95/Z7aywiIJEUE/xPRGNP6tngRunevp2QyvZf4pgvACvk1tl9B3HH
+7J5tY/GRkT4sQuZYpx3YnbdP5Y6Kx33BF7QXAoGAVCzghVQR/cVT1QNhvz29gs66
+iPIrtQnwUtNOHA6i9h+MnbPBOYRIpidGTaqEtKTTKisw79JjJ78X6TR4a9ML0oSg
+c1K71z9NmZgPbJU25qMN80ZCph3+h2f9hwc6AjLz0U5wQ4alP909VRVIX7iM8paf
+q59wBiHhyD3J16QAxhsCgYBu0rCmhmcV2rQu+kd4lCq7uJmBZZhFZ5tny9MlPgiK
+zIJkr1rkFbyIfqCDzyrU9irOTKc+iCUA25Ek9ujkHC4m/aTU3lnkNjYp/OFXpXF3
+XWZMY+0Ak5uUpldG85mwLIvATu3ivpbyZCTFYM5afSm4StmaUiU5tA+oZKEcGily
+jwKBgBdFLg+kTm877lcybQ04G1kIRMf5vAXcConzBt8ry9J+2iX1ddlu2K2vMroD
+1cP/U/EmvoCXSOGuetaI4UNQwE/rGCtkpvNj5y4twVLh5QufSOl49V0Ut0mwjPXw
+HfN/2MoO07vQrjgsFylvrw9A79xItABaqKndlmqlwMZWc9Ne
+-----END RSA PRIVATE KEY-----
+`
+
+var testingSSHSigner ssh.Signer
+
+func init() {
+	var err error
+	testingSSHSigner, err = ssh.ParsePrivateKey([]byte(sshKeyContent))
+	if err != nil {
+		panic(fmt.Sprintf("The well known RSA key: %v", err.Error()))
+	}
+}
diff --git a/src/chromiumos/test/dut/cmd/dutserver/main.go b/src/chromiumos/test/dut/cmd/dutserver/main.go
index 0747181..51cb7a2 100644
--- a/src/chromiumos/test/dut/cmd/dutserver/main.go
+++ b/src/chromiumos/test/dut/cmd/dutserver/main.go
@@ -7,6 +7,8 @@
 package main
 
 import (
+	"chromiumos/test/dut/cmd/dutserver/dutssh"
+	"context"
 	"flag"
 	"fmt"
 	"io"
@@ -46,14 +48,25 @@
 
 func main() {
 	os.Exit(func() int {
-		version := flag.Bool("version", false, "print version and exit")
+		flag.NewFlagSet("version", flag.ExitOnError)
+
+		dutAddress := flag.String("dut_address", "", "Dut to connect to")
+		wiringAddress := flag.String("wiring_address", "", "Address to TLW")
 		flag.Parse()
 
-		if *version {
+		if os.Args[1] == "version" {
 			fmt.Println("dutservice version ", Version)
 			return 0
 		}
 
+		if *dutAddress != "" {
+			fmt.Println("A DUT address must be valid")
+		}
+
+		if *wiringAddress != "" {
+			fmt.Println("A Wiring address must be valid")
+		}
+
 		logFile, err := createLogFile()
 		if err != nil {
 			log.Fatalln("Failed to create log file: ", err)
@@ -67,10 +80,16 @@
 			logger.Fatalln("Failed to create a net listener: ", err)
 			return 2
 		}
-		server, err := newDutServiceServer(l, logger)
+
+		ctx := context.Background()
+		conn, err := GetConnection(ctx, *dutAddress, *wiringAddress)
 		if err != nil {
-			logger.Fatalln("Failed to start dutserver: ", err)
+			logger.Fatalln("Failed to connect to dut: ", err)
+			return 2
 		}
+		defer conn.Close()
+
+		server := newDutServiceServer(l, logger, &dutssh.SSHClient{Client: conn})
 
 		err = server.Serve(l)
 		if err != nil {