public
Last active

Go net/rpc over ssh+netcat and unix domain sockets It would be nice if ssh.Session implemented io.ReaderWriter

  • Download Gist
.gitignore
1 2 3 4
local_client
server
client
ext
Makefile
Makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
build: setup
GOPATH=$(PWD):$(PWD)/ext go build server.go
GOPATH=$(PWD):$(PWD)/ext go build client.go
GOPATH=$(PWD):$(PWD)/ext go build local_client.go
 
fmt:
GOPATH=$(PWD) go fmt *.go
 
clean:
rm -rf server client local_client ext/pkg
 
setup:
GOPATH=$(PWD)/ext go get code.google.com/p/go.crypto/ssh
 
godoc:
GOPATH=$(PWD):$(PWD)/ext godoc -http=:6060
client.go
Go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
package main
 
import (
"code.google.com/p/go.crypto/ssh"
"fmt"
"io"
"log"
"net"
"net/rpc"
"os"
"strings"
)
 
// RPC response container
type Response struct {
Greeting string
}
 
// RPC request container
type Request struct {
Name string
}
 
// It would be nice if ssh.Session was an io.ReaderWriter
// proposal submitted :-)
type NetCatSession struct {
*ssh.Session // define Close()
writer io.Writer
reader io.Reader
}
 
// io.Reader
func (s NetCatSession) Read(p []byte) (n int, err error) {
return s.reader.Read(p)
}
 
// io.Writer
func (s NetCatSession) Write(p []byte) (n int, err error) {
return s.writer.Write(p)
}
 
// given the established ssh connection, start a session against netcat and
// return a io.ReaderWriterCloser appropriate for rpc.NewClient(...)
func StartNetCat(client *ssh.ClientConn, path string) (rwc *NetCatSession, err error) {
session, err := client.NewSession()
if err != nil {
return
}
 
cmd := fmt.Sprintf("/usr/bin/nc -U %s", path)
in, err := session.StdinPipe()
if err != nil {
return nil, fmt.Errorf("unable to get stdin: %s", err)
}
 
out, err := session.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("unable to get stdout: %s", err)
}
 
err = session.Start(cmd)
if err != nil {
return nil, fmt.Errorf("unable to start '%s': %s", cmd, err)
}
 
return &NetCatSession{session, in, out}, nil
}
 
// ./client localhost:/tmp/foo Brian
func main() {
parts := strings.Split(os.Args[1], ":")
host := parts[0]
path := parts[1]
name := os.Args[2]
 
// SSH setup, we assume current username and use the ssh agent
// for auth
agent_sock, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
if err != nil {
log.Fatalf("sorry, this example requires the ssh agent: %s", err)
}
defer agent_sock.Close()
 
config := &ssh.ClientConfig{
User: os.Getenv("USER"),
Auth: []ssh.ClientAuth{
ssh.ClientAuthAgent(ssh.NewAgentClient(agent_sock)),
},
}
ssh_client, err := ssh.Dial("tcp", fmt.Sprintf("%s:22", host), config)
if err != nil {
panic("Failed to dial: " + err.Error())
}
defer ssh_client.Close()
 
// Establish sesstion to netcat talking to the domain socket
s, err := StartNetCat(ssh_client, path)
if err != nil {
log.Fatalf("unable to start netcat session: %s", err)
}
 
// now comes the RPC!
client := rpc.NewClient(s)
defer client.Close()
 
req := &Request{name}
var res Response
 
err = client.Call("Greeter.Greet", req, &res)
if err != nil {
log.Fatalf("error in rpc: %s", err)
}
fmt.Println(res.Greeting)
}
local_client.go
Go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
package main
 
import (
"fmt"
"log"
"net/rpc"
"os"
)
 
type Response struct {
Greeting string
}
 
type Request struct {
Name string
}
 
// ./local_client /tmp/foo Brian
func main() {
client, err := rpc.Dial("unix", os.Args[1])
if err != nil {
log.Fatalf("failed: %s", err)
}
 
req := &Request{os.Args[2]}
var res Response
 
err = client.Call("Greeter.Greet", req, &res)
if err != nil {
log.Fatalf("error in rpc: %s", err)
}
 
fmt.Println(res.Greeting)
}
server.go
Go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
package main
 
import (
"fmt"
"log"
"net"
"net/rpc"
"os"
"os/signal"
"syscall"
)
 
// rpc response
type Response struct {
Greeting string
}
 
// rpc request
type Request struct {
Name string
}
 
// rpc host struct thing
type Greeter struct{}
 
// our remotely invocable function
func (g *Greeter) Greet(req Request, res *Response) (err error) {
res.Greeting = fmt.Sprintf("Hello %s", req.Name)
return
}
 
// start up rpc listener at path
func ServeAt(path string) (err error) {
rpc.Register(&Greeter{})
 
listener, err := net.Listen("unix", path)
if err != nil {
return fmt.Errorf("unable to listen at %s: %s", path, err)
}
 
go rpc.Accept(listener)
return
}
 
// ./server /tmp/foo
func main() {
path := os.Args[1]
 
err := ServeAt(path)
if err != nil {
log.Fatalf("failed: %s", err)
}
defer os.Remove(path)
 
// block until we are signalled to quit
wait()
}
 
func wait() {
signals := make(chan os.Signal)
signal.Notify(signals, syscall.SIGINT, syscall.SIGKILL, syscall.SIGHUP)
<-signals
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.