diff --git a/docs/rpc/README.md b/docs/rpc/README.md index 4e9adbacf..b10681e8f 100644 --- a/docs/rpc/README.md +++ b/docs/rpc/README.md @@ -33,6 +33,24 @@ The main entrypoint to RPC routing is `handleConn` in [agent/consul/rpc.go]. [agent/consul/rpc.go]: https://github.com/hashicorp/consul/blob/main/agent/consul/rpc.go +### Development + +Multiplexing several protocols over a single server port helps to reduce our +network requirements, but also makes interacting with Consul using local +development tools such as [grpcurl] difficult. + +[grpcurl]: https://github.com/fullstorydev/grpcurl + +You can get a "plain" TCP connection to the gRPC server using this proxy script: + +``` +$ go run tools/private-grpc-proxy/main.go localhost:8300 +Proxying connections to Consul's private gRPC server +Use this address: 127.0.0.1:64077 +``` + +Pass the returned proxy address to your tool of choice. + ## RPC Endpoints This section is a work in progress, it will eventually cover topics like: @@ -51,4 +69,3 @@ Routing RPC request to Consul servers and for connection pooling. - [agent/router](https://github.com/hashicorp/consul/tree/main/agent/router) - [agent/pool](https://github.com/hashicorp/consul/tree/main/agent/pool) - diff --git a/tools/private-grpc-proxy/main.go b/tools/private-grpc-proxy/main.go new file mode 100644 index 000000000..32a7633f6 --- /dev/null +++ b/tools/private-grpc-proxy/main.go @@ -0,0 +1,75 @@ +package main + +import ( + "fmt" + "io" + "net" + "os" +) + +// gRPC byte prefix (see RPCGRPC in agent/pool/conn.go). +const bytePrefix byte = 8 + +func main() { + if len(os.Args) != 2 { + log("usage: %s host:port", os.Args[0]) + os.Exit(1) + } + serverAddr := os.Args[1] + + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + log("failed to start listener: %v", err) + os.Exit(1) + } + defer lis.Close() + + fmt.Println("Proxying connections to Consul's private gRPC server") + fmt.Printf("Use this address: %s\n", lis.Addr()) + + for { + conn, err := lis.Accept() + if err != nil { + log("failed to accept connection: %v", err) + continue + } + + go func(conn net.Conn) { + if err := handleClient(serverAddr, conn); err != nil { + log(err.Error()) + } + }(conn) + } +} + +func handleClient(serverAddr string, clientConn net.Conn) error { + defer clientConn.Close() + + serverConn, err := net.Dial("tcp", serverAddr) + if err != nil { + return fmt.Errorf("failed to dial server connection: %w", err) + } + defer serverConn.Close() + + if _, err := serverConn.Write([]byte{bytePrefix}); err != nil { + return fmt.Errorf("failed to write byte prefix: %v", err) + } + + errCh := make(chan error, 1) + + go func() { + _, err := io.Copy(serverConn, clientConn) + errCh <- err + }() + + go func() { + _, err := io.Copy(clientConn, serverConn) + errCh <- err + }() + + return <-errCh +} + +func log(message string, args ...interface{}) { + fmt.Fprintf(os.Stderr, message+"\n", args...) +}