Skip to content

Instantly share code, notes, and snippets.

@rcrowley
Created February 19, 2014 17:07
Show Gist options
  • Save rcrowley/9096494 to your computer and use it in GitHub Desktop.
Save rcrowley/9096494 to your computer and use it in GitHub Desktop.
sendmsg(2) SCM_RIGHTS demo
package main
import (
"fmt"
"log"
"net"
"os"
"os/exec"
"reflect"
"syscall"
)
func init() {
log.SetFlags(log.Lmicroseconds | log.Lshortfile)
log.SetPrefix(fmt.Sprintf("pid:%d ", os.Getpid()))
}
func lsof() {
log.Printf("lsof -p %d:", os.Getpid())
cmd := exec.Command("lsof", "-p", fmt.Sprint(os.Getpid()))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); nil != err {
log.Fatalln(err)
}
}
func main() {
// Open a UNIX domain socket. The parent process will use it to receive
// sentinel requests from and send file descriptors to the child process.
// The child process will use it to send sentinel requests to and receive
// file descriptors from the parent process.
laddr, err := net.ResolveUnixAddr(
"unixgram",
fmt.Sprintf("%d.sock", os.Getpid()),
)
if nil != err {
log.Fatalln(err)
}
c, err := net.DialUnix("unixgram", laddr, nil)
if nil != err {
log.Fatalln(err)
}
defer c.Close()
defer os.Remove(laddr.String())
// Parent process.
if "" == os.Getenv("SENDMSG") {
// Fork and exec another copy of the same process image. This one
// has SENDMSG=1 in its environment so it goes down the other side
// of the if-statement above.
argv0, err := exec.LookPath(os.Args[0])
if nil != err {
log.Fatalln(err)
}
p, err := os.StartProcess(argv0, os.Args, &os.ProcAttr{
Env: append(os.Environ(), "SENDMSG=1"),
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
})
if nil != err {
log.Fatalln(err)
}
log.Println("spawned child", p.Pid)
// Listen. There's nothing special about this listener. The only
// notable thing is that it's opened after the child process has
// forked so it is not inherited.
l, err := net.Listen("tcp", "127.0.0.1:48879")
if nil != err {
log.Fatalln(err)
}
log.Println("listening on", l.Addr())
// Receive a sentinel request from the UNIX domain socket that
// instructs us to send the listening file descriptor.
b := make([]byte, 128)
oob := make([]byte, 128)
n, oobn, _, addr, err := c.ReadMsgUnix(b, oob)
if nil != err {
log.Fatalln(err)
}
log.Println("b:", b[:n])
log.Println("oob:", oob[:oobn])
// Send the listening file descrptor to another process.
//
// Go doesn't make it super easy to get a file descriptor that
// hasn't been dup(2)ed.
if _, _, err := c.WriteMsgUnix(
nil,
syscall.UnixRights(
int(reflect.ValueOf(l).Elem().FieldByName("fd").Elem().FieldByName("sysfd").Int()),
),
addr,
); nil != err {
log.Fatalln(err)
}
// Wait for the child process to exit. The fun's over.
ps, err := p.Wait()
if nil != err {
log.Fatalln(err)
}
log.Println(ps)
// Child process.
} else {
// Prove this process is not listening on 127.0.0.1:48879.
lsof()
// Open a UNIX domain socket to use for receiving file descriptors
// from our parent process.
raddr, err := net.ResolveUnixAddr(
"unixgram",
fmt.Sprintf("%d.sock", os.Getppid()),
)
if nil != err {
log.Fatalln(err)
}
// Request the parent process send us the listening file descriptor.
if _, _, err := c.WriteMsgUnix(nil, nil, raddr); nil != err {
log.Fatalln(err)
}
// Receive the listening file descriptor.
b := make([]byte, 128)
oob := make([]byte, 128)
n, oobn, _, _, err := c.ReadMsgUnix(b, oob)
if nil != err {
log.Fatalln(err)
}
log.Println("b:", b[:n])
log.Println("oob:", oob[:oobn])
// Prove that this process is now listening on 127.0.0.1:48879.
lsof()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment