cmd,service: in non-headless mode use an in-memory connection

Replace the socket connection with an in-memory connection (created by net.Pipe) for non-headless uses of delve.
This is faster and more secure.

Fixes #1332
This commit is contained in:
aarzilli 2018-09-03 10:00:30 +02:00 committed by Derek Parker
parent 910f90c3c8
commit ca0596724f
4 changed files with 109 additions and 27 deletions

@ -317,6 +317,13 @@ func traceCmd(cmd *cobra.Command, args []string) {
return 1
}
if Headless {
fmt.Fprintf(os.Stderr, "Warning: headless mode not supported with trace\n")
}
if AcceptMulti {
fmt.Fprintf(os.Stderr, "Warning: accept multiclient mode not supported with trace")
}
debugname, err := filepath.Abs(cmd.Flag("output").Value.String())
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
@ -343,12 +350,9 @@ func traceCmd(cmd *cobra.Command, args []string) {
processArgs = append([]string{debugname}, targetArgs...)
}
// Make a TCP listener
listener, err := net.Listen("tcp", Addr)
if err != nil {
fmt.Printf("couldn't start listener: %s\n", err)
return 1
}
// Make a local in-memory connection that client and server use to communicate
listener, clientConn := service.ListenerPipe()
defer listener.Close()
// Create and start a debug server
@ -364,7 +368,7 @@ func traceCmd(cmd *cobra.Command, args []string) {
fmt.Fprintln(os.Stderr, err)
return 1
}
client := rpc2.NewClient(listener.Addr().String())
client := rpc2.NewClientFromConn(clientConn)
funcs, err := client.ListFunctions(regexp)
if err != nil {
fmt.Fprintln(os.Stderr, err)
@ -430,7 +434,7 @@ func connectCmd(cmd *cobra.Command, args []string) {
fmt.Fprint(os.Stderr, "An empty address was provided. You must provide an address as the first argument.\n")
os.Exit(1)
}
os.Exit(connect(addr, conf, executingOther))
os.Exit(connect(addr, nil, conf, executingOther))
}
func splitArgs(cmd *cobra.Command, args []string) ([]string, []string) {
@ -440,9 +444,14 @@ func splitArgs(cmd *cobra.Command, args []string) ([]string, []string) {
return args, []string{}
}
func connect(addr string, conf *config.Config, kind executeKind) int {
func connect(addr string, clientConn net.Conn, conf *config.Config, kind executeKind) int {
// Create and start a terminal - attach to running instance
client := rpc2.NewClient(addr)
var client *rpc2.RPCClient
if clientConn != nil {
client = rpc2.NewClientFromConn(clientConn)
} else {
client = rpc2.NewClient(addr)
}
if client.Recorded() && (kind == executingGeneratedFile || kind == executingGeneratedTest) {
// When using the rr backend remove the trace directory if we built the
// executable
@ -474,14 +483,6 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
return 1
}
// Make a TCP listener
listener, err := net.Listen("tcp", Addr)
if err != nil {
fmt.Printf("couldn't start listener: %s\n", err)
return 1
}
defer listener.Close()
if Headless && (InitFile != "") {
fmt.Fprint(os.Stderr, "Warning: init file ignored\n")
}
@ -493,6 +494,22 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
AcceptMulti = false
}
var listener net.Listener
var clientConn net.Conn
var err error
// Make a TCP listener
if Headless {
listener, err = net.Listen("tcp", Addr)
} else {
listener, clientConn = service.ListenerPipe()
}
if err != nil {
fmt.Printf("couldn't start listener: %s\n", err)
return 1
}
defer listener.Close()
var server interface {
Run() error
Stop() error
@ -554,7 +571,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
return status
}
return connect(listener.Addr().String(), conf, kind)
return connect(listener.Addr().String(), clientConn, conf, kind)
}
func optflags(args []string) []string {

60
service/listenerpipe.go Normal file

@ -0,0 +1,60 @@
package service
import (
"errors"
"net"
"sync"
)
// ListenerPipe returns a full-duplex in-memory connection, like net.Pipe.
// Unlike net.Pipe one end of the connection is returned as an object
// satisfying the net.Listener interface.
// The first call to the Accept method of this object will return a net.Conn
// connected to the other net.Conn returned by ListenerPipe.
// Any subsequent calls to Accept will block until the listener is closed.
func ListenerPipe() (net.Listener, net.Conn) {
conn0, conn1 := net.Pipe()
return &preconnectedListener{conn: conn0, closech: make(chan struct{})}, conn1
}
// preconnectedListener satisfies the net.Listener interface by accepting a
// single pre-established connection.
// The first call to Accept will return the conn field, any subsequent call
// will block until the listener is closed.
type preconnectedListener struct {
accepted bool
conn net.Conn
closech chan struct{}
closeMu sync.Mutex
acceptMu sync.Mutex
}
// Accept returns the pre-established connection the first time it's called,
// it blocks until the listener is closed on every subsequent call.
func (l *preconnectedListener) Accept() (net.Conn, error) {
l.acceptMu.Lock()
defer l.acceptMu.Unlock()
if !l.accepted {
l.accepted = true
return l.conn, nil
}
<-l.closech
return nil, errors.New("accept failed: listener closed")
}
// Close closes the listener.
func (l *preconnectedListener) Close() error {
l.closeMu.Lock()
defer l.closeMu.Unlock()
if l.closech == nil {
return nil
}
close(l.closech)
l.closech = nil
return nil
}
// Addr returns the listener's network address.
func (l *preconnectedListener) Addr() net.Addr {
return l.conn.LocalAddr()
}

@ -3,6 +3,7 @@ package rpc2
import (
"fmt"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
"time"
@ -13,7 +14,6 @@ import (
// Client is a RPC service.Client.
type RPCClient struct {
addr string
client *rpc.Client
retValLoadCfg *api.LoadConfig
@ -28,11 +28,20 @@ func NewClient(addr string) *RPCClient {
if err != nil {
log.Fatal("dialing:", err)
}
c := &RPCClient{addr: addr, client: client}
return newFromRPCClient(client)
}
func newFromRPCClient(client *rpc.Client) *RPCClient {
c := &RPCClient{client: client}
c.call("SetApiVersion", api.SetAPIVersionIn{2}, &api.SetAPIVersionOut{})
return c
}
// NewClientFromConn creates a new RPCClient from the given connection.
func NewClientFromConn(conn net.Conn) *RPCClient {
return newFromRPCClient(jsonrpc.NewClient(conn))
}
func (c *RPCClient) ProcessPid() int {
out := new(ProcessPidOut)
c.call("ProcessPid", ProcessPidIn{}, out)

@ -41,11 +41,7 @@ func withTestClient2(name string, t *testing.T, fn func(c service.Client)) {
if testBackend == "rr" {
protest.MustHaveRecordingAllowed(t)
}
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("couldn't start listener: %s\n", err)
}
defer listener.Close()
listener, clientConn := service.ListenerPipe()
server := rpccommon.NewServer(&service.Config{
Listener: listener,
ProcessArgs: []string{protest.BuildFixture(name, 0).Path},
@ -54,7 +50,7 @@ func withTestClient2(name string, t *testing.T, fn func(c service.Client)) {
if err := server.Run(); err != nil {
t.Fatal(err)
}
client := rpc2.NewClient(listener.Addr().String())
client := rpc2.NewClientFromConn(clientConn)
defer func() {
dir, _ := client.TraceDirectory()
client.Detach(true)