node: Make IPC endpoint more generic

This commit is contained in:
Bas van Kervel 2016-05-28 07:45:47 +02:00
parent c75d3b0ede
commit 73c6f4b9e6
9 changed files with 175 additions and 94 deletions

View file

@ -59,6 +59,7 @@ Welcome to the Geth JavaScript console!
instance: Geth/v{{gethver}}/{{goos}}/{{gover}} instance: Geth/v{{gethver}}/{{goos}}/{{gover}}
coinbase: {{.Etherbase}} coinbase: {{.Etherbase}}
network: mainnet
at block: 0 ({{niltime}}) at block: 0 ({{niltime}})
datadir: {{.Datadir}} datadir: {{.Datadir}}
modules:{{range apis}} {{.}}:1.0{{end}} modules:{{range apis}} {{.}}:1.0{{end}}
@ -153,6 +154,7 @@ Welcome to the Geth JavaScript console!
instance: Geth/v{{gethver}}/{{goos}}/{{gover}} instance: Geth/v{{gethver}}/{{goos}}/{{gover}}
coinbase: {{etherbase}} coinbase: {{etherbase}}
network: mainnet
at block: 0 ({{niltime}}){{if ipc}} at block: 0 ({{niltime}}){{if ipc}}
datadir: {{datadir}}{{end}} datadir: {{datadir}}{{end}}
modules:{{range apis}} {{.}}:1.0{{end}} modules:{{range apis}} {{.}}:1.0{{end}}

View file

@ -95,7 +95,7 @@ func main() {
func MakeSystemNode(keydir string, privkey string, test *tests.BlockTest) (*node.Node, error) { func MakeSystemNode(keydir string, privkey string, test *tests.BlockTest) (*node.Node, error) {
// Create a networkless protocol stack // Create a networkless protocol stack
stack, err := node.New(&node.Config{ stack, err := node.New(&node.Config{
IPCPath: node.DefaultIPCEndpoint(), IPCPath: []string{node.DefaultIPCEndpoint()},
HTTPHost: common.DefaultHTTPHost, HTTPHost: common.DefaultHTTPHost,
HTTPPort: common.DefaultHTTPPort, HTTPPort: common.DefaultHTTPPort,
HTTPModules: []string{"admin", "db", "eth", "debug", "miner", "net", "shh", "txpool", "personal", "web3"}, HTTPModules: []string{"admin", "db", "eth", "debug", "miner", "net", "shh", "txpool", "personal", "web3"},

View file

@ -18,6 +18,7 @@ package utils
import ( import (
"fmt" "fmt"
"runtime"
"strings" "strings"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
@ -40,6 +41,11 @@ func NewRemoteRPCClient(ctx *cli.Context) (rpc.Client, error) {
// endpoint. It must start with either `ipc:` or `rpc:` (HTTP). // endpoint. It must start with either `ipc:` or `rpc:` (HTTP).
func NewRemoteRPCClientFromString(endpoint string) (rpc.Client, error) { func NewRemoteRPCClientFromString(endpoint string) (rpc.Client, error) {
if strings.HasPrefix(endpoint, "ipc:") { if strings.HasPrefix(endpoint, "ipc:") {
if runtime.GOOS == "windows" && !strings.HasPrefix(endpoint, `ipc:\\.\pipe\`) {
// user supplied named pipe name, e.g: attach "namedpipename" instead
// of "attach ipc:\\.\pipe\namedpipename".
endpoint = `ipc:\\.\pipe\` + endpoint[4:]
}
return rpc.NewIPCClient(endpoint[4:]) return rpc.NewIPCClient(endpoint[4:])
} }
if strings.HasPrefix(endpoint, "rpc:") { if strings.HasPrefix(endpoint, "rpc:") {

View file

@ -271,8 +271,8 @@ var (
} }
IPCPathFlag = DirectoryFlag{ IPCPathFlag = DirectoryFlag{
Name: "ipcpath", Name: "ipcpath",
Usage: "Filename for IPC socket/pipe within the datadir (explicit paths escape it)", Usage: "IPC endpoint location (explicit paths escape it)",
Value: DirectoryString{common.DefaultIPCSocket}, Value: DirectoryString{common.DefaultIPCEndpoint()},
} }
WSEnabledFlag = cli.BoolFlag{ WSEnabledFlag = cli.BoolFlag{
Name: "ws", Name: "ws",
@ -418,13 +418,27 @@ func MakeKeyStoreDir(datadir string, ctx *cli.Context) string {
return filepath.Join(datadir, "keystore") return filepath.Join(datadir, "keystore")
} }
// MakeIPCPath creates an IPC path configuration from the set command line flags, // MakeIPCPaths creates an IPC path configuration from the set command line flags,
// returning an empty string if IPC was explicitly disabled, or the set path. // returning an empty slice if IPC was explicitly disabled, or the paths to open
func MakeIPCPath(ctx *cli.Context) string { // the IPC endpoint(s).
func MakeIPCPaths(ctx *cli.Context) []string {
if ctx.GlobalBool(IPCDisabledFlag.Name) { if ctx.GlobalBool(IPCDisabledFlag.Name) {
return "" return []string{}
} }
return ctx.GlobalString(IPCPathFlag.Name)
// user overruled default location
if ctx.GlobalIsSet(IPCPathFlag.Name) {
return []string{ctx.GlobalString(IPCPathFlag.Name)}
}
// return the old geth specific path and new more general default path.
// This is a temporary situation, the old path will be removed after a couple
// of weeks. This gives clients time to update and users the ability to use
// the latest geth version with default IPC setting as they are used to.
if runtime.GOOS == "windows" {
return []string{`\\.\pipe\geth.ipc`, ctx.GlobalString(IPCPathFlag.Name)}
}
return []string{"geth.ipc", ctx.GlobalString(IPCPathFlag.Name)}
} }
// MakeNodeKey creates a node key from set command line flags, either loading it // MakeNodeKey creates a node key from set command line flags, either loading it
@ -664,7 +678,7 @@ func MakeSystemNode(name, version string, relconf release.Config, extra []byte,
NAT: MakeNAT(ctx), NAT: MakeNAT(ctx),
MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name), MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name),
MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name), MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name),
IPCPath: MakeIPCPath(ctx), IPCPath: MakeIPCPaths(ctx),
HTTPHost: MakeHTTPRpcHost(ctx), HTTPHost: MakeHTTPRpcHost(ctx),
HTTPPort: ctx.GlobalInt(RPCPortFlag.Name), HTTPPort: ctx.GlobalInt(RPCPortFlag.Name),
HTTPCors: ctx.GlobalString(RPCCORSDomainFlag.Name), HTTPCors: ctx.GlobalString(RPCCORSDomainFlag.Name),

View file

@ -19,14 +19,16 @@ package common
import ( import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"os"
) )
const ( const (
DefaultIPCSocket = "geth.ipc" // Default (relative) name of the IPC RPC socket DefaultIPCSocket = "ipc.sock" // Default (relative) name of the IPC RPC unix socket
DefaultHTTPHost = "localhost" // Default host interface for the HTTP RPC server DefaultIPCNamedPipe = `\\.\pipe\ethereum` // Default name of the IPC RPC named pipe
DefaultHTTPPort = 8545 // Default TCP port for the HTTP RPC server DefaultHTTPHost = "localhost" // Default host interface for the HTTP RPC server
DefaultWSHost = "localhost" // Default host interface for the websocket RPC server DefaultHTTPPort = 8545 // Default TCP port for the HTTP RPC server
DefaultWSPort = 8546 // Default TCP port for the websocket RPC server DefaultWSHost = "localhost" // Default host interface for the websocket RPC server
DefaultWSPort = 8546 // Default TCP port for the websocket RPC server
) )
// DefaultDataDir is the default data directory to use for the databases and other // DefaultDataDir is the default data directory to use for the databases and other
@ -46,3 +48,27 @@ func DefaultDataDir() string {
// As we cannot guess a stable location, return empty and handle later // As we cannot guess a stable location, return empty and handle later
return "" return ""
} }
// DefaultIPCEndpoint returns the default location where to open the IPC endpoint.
//
// On Windows this is DefaultIPCNamedPipe.
// On OSX this is DefaultDataDir/DefaultIPCSocket.
// Else this is $ENV{XDG_RUNTIME_DIR}/ethereum/DefaultIPCSocket, if $ENV{XDG_RUNTIME_DIR} is not set,
// or the ethereum sub directory could not be created this is DefaultDataDir/DefaultIPCSocket.
func DefaultIPCEndpoint() string {
if runtime.GOOS == "windows" {
return DefaultIPCNamedPipe
}
if runtime.GOOS == "darwin" {
return filepath.Join(DefaultDataDir(), DefaultIPCSocket)
}
if dir := os.Getenv("XDG_RUNTIME_DIR"); dir != "" {
dir = filepath.Join(dir, "ethereum")
// create "ethereum" subdirectory if it doesn't exist
if err := os.MkdirAll(dir, 0700); err == nil {
return filepath.Join(dir, DefaultIPCSocket)
}
}
return filepath.Join(DefaultDataDir(), DefaultIPCSocket)
}

View file

@ -247,8 +247,17 @@ func (c *Console) Welcome() {
// Print some generic Geth metadata // Print some generic Geth metadata
fmt.Fprintf(c.printer, "Welcome to the Geth JavaScript console!\n\n") fmt.Fprintf(c.printer, "Welcome to the Geth JavaScript console!\n\n")
c.jsre.Run(` c.jsre.Run(`
var _network = 'private';
switch (eth.getBlock(0).hash.toLowerCase()) {
case '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3':
_network = 'mainnet'; break
case '0x0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303':
_network = 'testnet'; break
}
console.log("instance: " + web3.version.node); console.log("instance: " + web3.version.node);
console.log("coinbase: " + eth.coinbase); console.log("coinbase: " + eth.coinbase);
console.log(" network: " + _network);
console.log("at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")"); console.log("at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")");
console.log(" datadir: " + admin.datadir); console.log(" datadir: " + admin.datadir);
`) `)

View file

@ -53,11 +53,12 @@ type Config struct {
// in memory. // in memory.
DataDir string DataDir string
// IPCPath is the requested location to place the IPC endpoint. If the path is // IPCPath is the requested location to place the IPC endpoint(s). If the path
// a simple file name, it is placed inside the data directory (or on the root // is a simple file name, it is placed inside the data directory (or on the root
// pipe path on Windows), whereas if it's a resolvable path name (absolute or // pipe path on Windows), whereas if it's a resolvable path name (absolute or
// relative), then that specific path is enforced. An empty path disables IPC. // relative), then that specific path is enforced. A nil or empty slice
IPCPath string // disables IPC.
IPCPath []string
// This field should be a valid secp256k1 private key that will be used for both // This field should be a valid secp256k1 private key that will be used for both
// remote peer identification as well as network traffic encryption. If no key // remote peer identification as well as network traffic encryption. If no key
@ -138,35 +139,39 @@ type Config struct {
WSModules []string WSModules []string
} }
// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into // IPCEndpoints resolves the IPC endpoints based on a configured value, taking into
// account the set data folders as well as the designated platform we're currently // account the set data folders as well as the designated platform we're currently
// running on. // running on.
func (c *Config) IPCEndpoint() string { func (c *Config) IPCEndpoints() []string {
// Short circuit if IPC has not been enabled var endpoints []string
if c.IPCPath == "" { for _, endpoint := range c.IPCPath {
return "" if endpoint == "" {
} endpoints = append(endpoints, "")
// On windows we can only use plain top-level pipes } else if runtime.GOOS == "windows" {
if runtime.GOOS == "windows" { // On windows we can only use plain top-level pipes
if strings.HasPrefix(c.IPCPath, `\\.\pipe\`) { if strings.HasPrefix(endpoint, `\\.\pipe\`) {
return c.IPCPath endpoints = append(endpoints, endpoint)
} else {
endpoints = append(endpoints, `\\.\pipe\`+endpoint)
}
} else if filepath.Base(endpoint) == endpoint { // Resolve names into the data directory full paths otherwise
if c.DataDir == "" {
endpoints = append(endpoints, filepath.Join(os.TempDir(), endpoint))
} else {
endpoints = append(endpoints, filepath.Join(c.DataDir, endpoint))
}
} else {
endpoints = append(endpoints, endpoint)
} }
return `\\.\pipe\` + c.IPCPath
} }
// Resolve names into the data directory full paths otherwise
if filepath.Base(c.IPCPath) == c.IPCPath { return endpoints
if c.DataDir == "" {
return filepath.Join(os.TempDir(), c.IPCPath)
}
return filepath.Join(c.DataDir, c.IPCPath)
}
return c.IPCPath
} }
// DefaultIPCEndpoint returns the IPC path used by default. // DefaultIPCEndpoint returns the IPC path used by default.
func DefaultIPCEndpoint() string { func DefaultIPCEndpoint() string {
config := &Config{DataDir: common.DefaultDataDir(), IPCPath: common.DefaultIPCSocket} config := &Config{DataDir: common.DefaultDataDir(), IPCPath: []string{common.DefaultIPCEndpoint()}}
return config.IPCEndpoint() return config.IPCEndpoints()[0]
} }
// HTTPEndpoint resolves an HTTP endpoint based on the configured host interface // HTTPEndpoint resolves an HTTP endpoint based on the configured host interface

View file

@ -85,7 +85,7 @@ func TestIPCPathResolution(t *testing.T) {
for i, test := range tests { for i, test := range tests {
// Only run when platform/test match // Only run when platform/test match
if (runtime.GOOS == "windows") == test.Windows { if (runtime.GOOS == "windows") == test.Windows {
if endpoint := (&Config{DataDir: test.DataDir, IPCPath: test.IPCPath}).IPCEndpoint(); endpoint != test.Endpoint { if endpoint := (&Config{DataDir: test.DataDir, IPCPath: []string{test.IPCPath}}).IPCEndpoints()[0]; endpoint != test.Endpoint {
t.Errorf("test %d: IPC endpoint mismatch: have %s, want %s", i, endpoint, test.Endpoint) t.Errorf("test %d: IPC endpoint mismatch: have %s, want %s", i, endpoint, test.Endpoint)
} }
} }

View file

@ -43,6 +43,17 @@ var (
datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true} datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true}
) )
type ipcService struct {
endpoint string // endpoints to listen at (empty = IPC disabled)
listener net.Listener // RPC listener sockets to serve API requests
handler *rpc.Server // RPC request handlers to process the API requests
}
func (s *ipcService) stop() {
s.listener.Close()
s.handler.Stop()
}
// Node represents a P2P node into which arbitrary (uniquely typed) services might // Node represents a P2P node into which arbitrary (uniquely typed) services might
// be registered. // be registered.
type Node struct { type Node struct {
@ -58,9 +69,7 @@ type Node struct {
rpcAPIs []rpc.API // List of APIs currently provided by the node rpcAPIs []rpc.API // List of APIs currently provided by the node
inprocHandler *rpc.Server // In-process RPC request handler to process the API requests inprocHandler *rpc.Server // In-process RPC request handler to process the API requests
ipcEndpoint string // IPC endpoint to listen at (empty = IPC disabled) ipcServices []*ipcService // IPC services
ipcListener net.Listener // IPC RPC listener socket to serve API requests
ipcHandler *rpc.Server // IPC RPC request handler to process the API requests
httpHost string // HTTP hostname httpHost string // HTTP hostname
httpPort int // HTTP post httpPort int // HTTP post
@ -95,6 +104,12 @@ func New(conf *Config) (*Node, error) {
if conf.DataDir != "" { if conf.DataDir != "" {
nodeDbPath = filepath.Join(conf.DataDir, datadirNodeDatabase) nodeDbPath = filepath.Join(conf.DataDir, datadirNodeDatabase)
} }
ipcServices := make([]*ipcService, len(conf.IPCEndpoints()))
for i, endpoint := range conf.IPCEndpoints() {
ipcServices[i] = &ipcService{endpoint: endpoint}
}
return &Node{ return &Node{
datadir: conf.DataDir, datadir: conf.DataDir,
serverConfig: p2p.Config{ serverConfig: p2p.Config{
@ -113,7 +128,7 @@ func New(conf *Config) (*Node, error) {
MaxPendingPeers: conf.MaxPendingPeers, MaxPendingPeers: conf.MaxPendingPeers,
}, },
serviceFuncs: []ServiceConstructor{}, serviceFuncs: []ServiceConstructor{},
ipcEndpoint: conf.IPCEndpoint(), ipcServices: ipcServices,
httpHost: conf.HTTPHost, httpHost: conf.HTTPHost,
httpPort: conf.HTTPPort, httpPort: conf.HTTPPort,
httpEndpoint: conf.HTTPEndpoint(), httpEndpoint: conf.HTTPEndpoint(),
@ -272,64 +287,65 @@ func (n *Node) stopInProc() {
// startIPC initializes and starts the IPC RPC endpoint. // startIPC initializes and starts the IPC RPC endpoint.
func (n *Node) startIPC(apis []rpc.API) error { func (n *Node) startIPC(apis []rpc.API) error {
// Short circuit if the IPC endpoint isn't being exposed for i, _ := range n.ipcServices {
if n.ipcEndpoint == "" { service := n.ipcServices[i]
return nil
} // Register all the APIs exposed by the services
// Register all the APIs exposed by the services handler := rpc.NewServer()
handler := rpc.NewServer()
for _, api := range apis { for _, api := range apis {
if err := handler.RegisterName(api.Namespace, api.Service); err != nil { if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
return err
}
glog.V(logger.Debug).Infof("IPC registered %T under '%s' on '%s'", api.Service, api.Namespace, service.endpoint)
}
// All APIs registered, start the IPC listener
var (
listener net.Listener
err error
)
if listener, err = rpc.CreateIPCListener(service.endpoint); err != nil {
return err return err
} }
glog.V(logger.Debug).Infof("IPC registered %T under '%s'", api.Service, api.Namespace)
}
// All APIs registered, start the IPC listener
var (
listener net.Listener
err error
)
if listener, err = rpc.CreateIPCListener(n.ipcEndpoint); err != nil {
return err
}
go func() {
glog.V(logger.Info).Infof("IPC endpoint opened: %s", n.ipcEndpoint)
for { go func() {
conn, err := listener.Accept() glog.V(logger.Info).Infof("IPC endpoint opened: %s", service.endpoint)
if err != nil {
// Terminate if the listener was closed for {
n.lock.RLock() conn, err := listener.Accept()
closed := n.ipcListener == nil if err != nil {
n.lock.RUnlock() // Terminate if the listener was closed
if closed { n.lock.RLock()
return closed := service.listener == nil
n.lock.RUnlock()
if closed {
return
}
// Not closed, just some error; report and continue
glog.V(logger.Error).Infof("IPC accept failed: %v", err)
continue
} }
// Not closed, just some error; report and continue go handler.ServeCodec(rpc.NewJSONCodec(conn), rpc.OptionMethodInvocation|rpc.OptionSubscriptions)
glog.V(logger.Error).Infof("IPC accept failed: %v", err)
continue
} }
go handler.ServeCodec(rpc.NewJSONCodec(conn), rpc.OptionMethodInvocation|rpc.OptionSubscriptions) }()
}
}() service.listener = listener
// All listeners booted successfully service.handler = handler
n.ipcListener = listener }
n.ipcHandler = handler
return nil return nil
} }
// stopIPC terminates the IPC RPC endpoint. // stopIPC terminates the IPC RPC endpoint.
func (n *Node) stopIPC() { func (n *Node) stopIPC() {
if n.ipcListener != nil { for _, service := range n.ipcServices {
n.ipcListener.Close() service.listener.Close()
n.ipcListener = nil service.listener = nil
service.handler.Stop()
glog.V(logger.Info).Infof("IPC endpoint closed: %s", n.ipcEndpoint) service.handler = nil
} glog.V(logger.Info).Infof("IPC endpoint closed: %s", service.endpoint)
if n.ipcHandler != nil {
n.ipcHandler.Stop()
n.ipcHandler = nil
} }
} }
@ -550,9 +566,12 @@ func (n *Node) DataDir() string {
return n.datadir return n.datadir
} }
// IPCEndpoint retrieves the current IPC endpoint used by the protocol stack. // IPCEndpoint retrieves the current IPC endpoints used by the protocol stack.
func (n *Node) IPCEndpoint() string { func (n *Node) IPCEndpoint() (endpoints []string) {
return n.ipcEndpoint for _, service := range n.ipcServices {
endpoints = append(endpoints, service.endpoint)
}
return
} }
// HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack. // HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack.