Skip to content

Instantly share code, notes, and snippets.

@brianm
Last active April 26, 2024 07:34
Show Gist options
  • Save brianm/4741956 to your computer and use it in GitHub Desktop.
Save brianm/4741956 to your computer and use it in GitHub Desktop.
Go net/rpc over ssh+netcat and unix domain sockets It would be nice if ssh.Session implemented io.ReaderWriter
local_client
server
client
ext
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)
}
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)
}
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
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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment