config: add peer, rpc, and rest listener options

This commit removes the `peerport` and `rpcport` config options and
adds `listen`, `rpclisten`, and `restlisten` options to allow setting
one or multiple interfaces to listen on for incoming connections.

It also adds a `nolisten` option to allow disabling the listener for
incoming peer connections.
This commit is contained in:
Matt Drollette 2017-12-17 11:28:38 -06:00 committed by Olaoluwa Osuntokun
parent 8cce2ad630
commit 86133e559b
4 changed files with 187 additions and 103 deletions

View File

@ -141,8 +141,11 @@ type config struct {
ReadMacPath string `long:"readonlymacaroonpath" description:"Path to write the read-only macaroon for lnd's RPC and REST services if it doesn't exist"`
LogDir string `long:"logdir" description:"Directory to log output."`
Listeners []string `long:"listen" description:"Add an interface/port to listen for connections (default all interfaces port: 9735)"`
ExternalIPs []string `long:"externalip" description:"Add an ip to the list of local addresses we claim to listen on to peers"`
RPCListeners []string `long:"rpclisten" description:"Add an interface/port to listen for RPC connections"`
RESTListeners []string `long:"restlisten" description:"Add an interface/port to listen for REST connections"`
Listeners []string `long:"listen" description:"Add an interface/port to listen for peer connections"`
DisableListen bool `long:"nolisten" description:"Disable listening for incoming peer connections"`
ExternalIPs []string `long:"externalip" description:"Add an ip to the list of local addresses we claim to listen on to peers"`
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
@ -150,9 +153,6 @@ type config struct {
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
PeerPort int `long:"peerport" description:"The port to listen on for incoming p2p connections"`
RPCPort int `long:"rpcport" description:"The port for the rpc server"`
RESTPort int `long:"restport" description:"The port for the REST server"`
DebugHTLC bool `long:"debughtlc" description:"Activate the debug htlc mode. With the debug HTLC mode, all payments sent use a pre-determined R-Hash. Additionally, all HTLCs sent to a node with the debug HTLC R-Hash are immediately settled in the next available state transition."`
HodlHTLC bool `long:"hodlhtlc" description:"Activate the hodl HTLC mode. With hodl HTLC mode, all incoming HTLCs will be accepted by the receiving node, but no attempt will be made to settle the payment with the sender."`
MaxPendingChannels int `long:"maxpendingchannels" description:"The maximum number of incoming pending channels permitted per peer."`
@ -192,9 +192,6 @@ func loadConfig() (*config, error) {
AdminMacPath: defaultAdminMacPath,
ReadMacPath: defaultReadMacPath,
LogDir: defaultLogDir,
PeerPort: defaultPeerPort,
RPCPort: defaultRPCPort,
RESTPort: defaultRESTPort,
Bitcoin: &chainConfig{
MinHTLC: defaultBitcoinMinHTLCMSat,
BaseFee: defaultBitcoinBaseFeeMSat,
@ -451,6 +448,44 @@ func loadConfig() (*config, error) {
return nil, err
}
// At least one RPCListener is required.
if len(cfg.RPCListeners) == 0 {
addr := fmt.Sprintf("localhost:%d", defaultRPCPort)
cfg.RPCListeners = append(cfg.RPCListeners, addr)
}
// Listen on the default interface/port if no REST listeners were specified.
if len(cfg.RESTListeners) == 0 {
addr := fmt.Sprintf("localhost:%d", defaultRESTPort)
cfg.RESTListeners = append(cfg.RESTListeners, addr)
}
// Listen on the default interface/port if no listeners were specified.
if len(cfg.Listeners) == 0 {
addr := fmt.Sprintf(":%d", defaultPeerPort)
cfg.Listeners = append(cfg.Listeners, addr)
}
// Remove all Listeners if listening is disabled.
if cfg.DisableListen {
cfg.Listeners = nil
}
// Add default port to all RPC listener addresses if needed and remove
// duplicate addresses.
cfg.RPCListeners = normalizeAddresses(cfg.RPCListeners,
strconv.Itoa(defaultRPCPort))
// Add default port to all REST listener addresses if needed and remove
// duplicate addresses.
cfg.RESTListeners = normalizeAddresses(cfg.RESTListeners,
strconv.Itoa(defaultRESTPort))
// Add default port to all listener addresses if needed and remove
// duplicate addresses.
cfg.Listeners = normalizeAddresses(cfg.Listeners,
strconv.Itoa(defaultPeerPort))
// Warn about missing config file only after all other configuration is
// done. This prevents the warning on help messages and invalid
// options. Note this should go directly before the return.
@ -788,3 +823,20 @@ func extractBitcoindRPCParams(bitcoindConfigPath string) (string, string, string
return string(userSubmatches[1]), string(passSubmatches[1]),
string(zmqPathSubmatches[1]), nil
}
// normalizeAddresses returns a new slice with all the passed addresses
// normalized with the given default port and all duplicates removed.
func normalizeAddresses(addrs []string, defaultPort string) []string {
result := make([]string, 0, len(addrs))
seen := map[string]struct{}{}
for _, addr := range addrs {
if _, _, err := net.SplitHostPort(addr); err != nil {
addr = net.JoinHostPort(addr, defaultPort)
}
if _, ok := seen[addr]; !ok {
result = append(result, addr)
seen[addr] = struct{}{}
}
}
return result
}

136
lnd.go
View File

@ -21,7 +21,7 @@ import (
"os"
"runtime"
"runtime/pprof"
"strconv"
"sync"
"time"
"gopkg.in/macaroon-bakery.v1/bakery"
@ -182,10 +182,7 @@ func lndMain() error {
}
sCreds := credentials.NewTLS(tlsConf)
serverOpts := []grpc.ServerOption{grpc.Creds(sCreds)}
grpcEndpoint := fmt.Sprintf("localhost:%d", loadedConfig.RPCPort)
restEndpoint := fmt.Sprintf(":%d", loadedConfig.RESTPort)
cCreds, err := credentials.NewClientTLSFromFile(cfg.TLSCertPath,
"")
cCreds, err := credentials.NewClientTLSFromFile(cfg.TLSCertPath, "")
if err != nil {
return err
}
@ -198,7 +195,7 @@ func lndMain() error {
publicWalletPw := []byte("public")
if !cfg.NoEncryptWallet {
privateWalletPw, publicWalletPw, err = waitForWalletPassword(
grpcEndpoint, restEndpoint, serverOpts, proxyOpts,
cfg.RPCListeners, cfg.RESTListeners, serverOpts, proxyOpts,
tlsConf, macaroonService,
)
if err != nil {
@ -233,10 +230,7 @@ func lndMain() error {
// Set up the core server which will listen for incoming peer
// connections.
defaultListenAddrs := []string{
net.JoinHostPort("", strconv.Itoa(cfg.PeerPort)),
}
server, err := newServer(defaultListenAddrs, chanDB, activeChainControl,
server, err := newServer(cfg.Listeners, chanDB, activeChainControl,
idPrivKey)
if err != nil {
srvrLog.Errorf("unable to create server: %v\n", err)
@ -384,37 +378,42 @@ func lndMain() error {
lnrpc.RegisterLightningServer(grpcServer, rpcServer)
// Next, Start the gRPC server listening for HTTP/2 connections.
lis, err := net.Listen("tcp", grpcEndpoint)
if err != nil {
fmt.Printf("failed to listen: %v", err)
return err
for _, listener := range cfg.RPCListeners {
lis, err := net.Listen("tcp", listener)
if err != nil {
ltndLog.Errorf("RPC server unable to listen on %s", listener)
return err
}
defer lis.Close()
go func() {
rpcsLog.Infof("RPC server listening on %s", lis.Addr())
grpcServer.Serve(lis)
}()
}
defer lis.Close()
go func() {
rpcsLog.Infof("RPC server listening on %s", lis.Addr())
grpcServer.Serve(lis)
}()
// Finally, start the REST proxy for our gRPC server above.
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := proxy.NewServeMux()
err = lnrpc.RegisterLightningHandlerFromEndpoint(ctx, mux, grpcEndpoint,
proxyOpts)
err = lnrpc.RegisterLightningHandlerFromEndpoint(ctx, mux,
cfg.RPCListeners[0], proxyOpts)
if err != nil {
return err
}
go func() {
for _, restEndpoint := range cfg.RESTListeners {
listener, err := tls.Listen("tcp", restEndpoint, tlsConf)
if err != nil {
ltndLog.Errorf("gRPC proxy unable to listen on "+
"localhost%s", restEndpoint)
return
ltndLog.Errorf("gRPC proxy unable to listen on %s", restEndpoint)
return err
}
rpcsLog.Infof("gRPC proxy started at localhost%s", restEndpoint)
http.Serve(listener, mux)
}()
defer listener.Close()
go func() {
rpcsLog.Infof("gRPC proxy started at %s", listener.Addr())
http.Serve(listener, mux)
}()
}
// If we're not in simnet mode, We'll wait until we're fully synced to
// continue the start up of the remainder of the daemon. This ensures
@ -683,7 +682,7 @@ func genMacaroons(svc *bakery.Service, admFile, roFile string) error {
// waitForWalletPassword will spin up gRPC and REST endpoints for the
// WalletUnlocker server, and block until a password is provided by
// the user to this RPC server.
func waitForWalletPassword(grpcEndpoint, restEndpoint string,
func waitForWalletPassword(grpcEndpoints, restEndpoints []string,
serverOpts []grpc.ServerOption, proxyOpts []grpc.DialOption,
tlsConf *tls.Config, macaroonService *bakery.Service) ([]byte, []byte, error) {
@ -699,27 +698,28 @@ func waitForWalletPassword(grpcEndpoint, restEndpoint string,
chainConfig.ChainDir, activeNetParams.Params)
lnrpc.RegisterWalletUnlockerServer(grpcServer, pwService)
// Start a gRPC server listening for HTTP/2 connections, solely
// used for getting the encryption password from the client.
lis, err := net.Listen("tcp", grpcEndpoint)
if err != nil {
fmt.Printf("failed to listen: %v", err)
return nil, nil, err
// Use a WaitGroup so we can be sure the instructions on how to input the
// password is the last thing to be printed to the console.
var wg sync.WaitGroup
for _, grpcEndpoint := range grpcEndpoints {
// Start a gRPC server listening for HTTP/2 connections, solely
// used for getting the encryption password from the client.
lis, err := net.Listen("tcp", grpcEndpoint)
if err != nil {
ltndLog.Errorf("password RPC server unable to listen on %s",
grpcEndpoint)
return nil, nil, err
}
defer lis.Close()
wg.Add(1)
go func() {
rpcsLog.Infof("password RPC server listening on %s", lis.Addr())
wg.Done()
grpcServer.Serve(lis)
}()
}
defer lis.Close()
// Use a two channels to synchronize on, so we can be sure the
// instructions on how to input the password is the last
// thing to be printed to the console.
grpcServing := make(chan struct{})
restServing := make(chan struct{})
go func(c chan struct{}) {
rpcsLog.Infof("password RPC server listening on %s",
lis.Addr())
close(c)
grpcServer.Serve(lis)
}(grpcServing)
// Start a REST proxy for our gRPC server above.
ctx := context.Background()
@ -727,37 +727,41 @@ func waitForWalletPassword(grpcEndpoint, restEndpoint string,
defer cancel()
mux := proxy.NewServeMux()
err = lnrpc.RegisterWalletUnlockerHandlerFromEndpoint(ctx, mux,
grpcEndpoint, proxyOpts)
err := lnrpc.RegisterWalletUnlockerHandlerFromEndpoint(ctx, mux,
grpcEndpoints[0], proxyOpts)
if err != nil {
return nil, nil, err
}
srv := &http.Server{Handler: mux}
defer func() {
// We must shut down this server, since we'll let
// the regular rpcServer listen on the same address.
if err := srv.Shutdown(ctx); err != nil {
ltndLog.Errorf("unable to shutdown gPRC proxy: %v", err)
rpcsLog.Errorf("unable to shutdown password gRPC proxy: %v", err)
}
}()
go func(c chan struct{}) {
listener, err := tls.Listen("tcp", restEndpoint,
tlsConf)
for _, restEndpoint := range restEndpoints {
lis, err := tls.Listen("tcp", restEndpoint, tlsConf)
if err != nil {
ltndLog.Errorf("gRPC proxy unable to listen "+
"on localhost%s", restEndpoint)
return
ltndLog.Errorf("password gRPC proxy unable to listen on %s",
restEndpoint)
return nil, nil, err
}
rpcsLog.Infof("password gRPC proxy started at "+
"localhost%s", restEndpoint)
close(c)
srv.Serve(listener)
}(restServing)
defer lis.Close()
// Wait for gRPC and REST server to be up running.
<-grpcServing
<-restServing
wg.Add(1)
go func() {
rpcsLog.Infof("password gRPC proxy started at %s", lis.Addr())
wg.Done()
srv.Serve(lis)
}()
}
// Wait for gRPC and REST servers to be up running.
wg.Wait()
// Wait for user to provide the password.
ltndLog.Infof("Waiting for wallet encryption password. " +

View File

@ -36,17 +36,24 @@ var (
// defaultNodePort is the initial p2p port which will be used by the
// first created lightning node to listen on for incoming p2p
// connections. Subsequent allocated ports for future lighting nodes
// instances will be monotonically increasing odd numbers calculated as
// such: defaultP2pPort + (2 * harness.nodeNum).
// instances will be monotonically increasing numbers calculated as
// such: defaultP2pPort + (3 * harness.nodeNum).
defaultNodePort = 19555
// defaultClientPort is the initial rpc port which will be used by the
// first created lightning node to listen on for incoming rpc
// connections. Subsequent allocated ports for future rpc harness
// instances will be monotonically increasing even numbers calculated
// as such: defaultP2pPort + (2 * harness.nodeNum).
// instances will be monotonically increasing numbers calculated
// as such: defaultP2pPort + (3 * harness.nodeNum).
defaultClientPort = 19556
// defaultRestPort is the initial rest port which will be used by the
// first created lightning node to listen on for incoming rest
// connections. Subsequent allocated ports for future rpc harness
// instances will be monotonically increasing numbers calculated
// as such: defaultP2pPort + (3 * harness.nodeNum).
defaultRestPort = 19557
// logOutput is a flag that can be set to append the output from the
// seed nodes to log files.
logOutput = flag.Bool("logoutput", false,
@ -57,22 +64,24 @@ var (
trickleDelay = 50
)
// generateListeningPorts returns two strings representing ports to listen on
// generateListeningPorts returns three ints representing ports to listen on
// designated for the current lightning network test. If there haven't been any
// test instances created, the default ports are used. Otherwise, in order to
// support multiple test nodes running at once, the p2p and rpc port are
// incremented after each initialization.
func generateListeningPorts() (int, int) {
var p2p, rpc int
// support multiple test nodes running at once, the p2p, rpc, and rest ports
// are incremented after each initialization.
func generateListeningPorts() (int, int, int) {
var p2p, rpc, rest int
if numActiveNodes == 0 {
p2p = defaultNodePort
rpc = defaultClientPort
rest = defaultRestPort
} else {
p2p = defaultNodePort + (2 * numActiveNodes)
rpc = defaultClientPort + (2 * numActiveNodes)
p2p = defaultNodePort + (3 * numActiveNodes)
rpc = defaultClientPort + (3 * numActiveNodes)
rest = defaultRestPort + (3 * numActiveNodes)
}
return p2p, rpc
return p2p, rpc, rest
}
type nodeConfig struct {
@ -89,6 +98,7 @@ type nodeConfig struct {
ReadMacPath string
P2PPort int
RPCPort int
RESTPort int
}
func (cfg nodeConfig) P2PAddr() string {
@ -99,6 +109,10 @@ func (cfg nodeConfig) RPCAddr() string {
return net.JoinHostPort("127.0.0.1", strconv.Itoa(cfg.RPCPort))
}
func (cfg nodeConfig) RESTAddr() string {
return net.JoinHostPort("127.0.0.1", strconv.Itoa(cfg.RESTPort))
}
func (cfg nodeConfig) DBPath() string {
return filepath.Join(cfg.DataDir, cfg.NetParams.Name, "bitcoin/channel.db")
}
@ -128,8 +142,9 @@ func (cfg nodeConfig) genArgs() []string {
args = append(args, fmt.Sprintf("--btcd.rpcuser=%v", cfg.RPCConfig.User))
args = append(args, fmt.Sprintf("--btcd.rpcpass=%v", cfg.RPCConfig.Pass))
args = append(args, fmt.Sprintf("--btcd.rawrpccert=%v", encodedCert))
args = append(args, fmt.Sprintf("--rpcport=%v", cfg.RPCPort))
args = append(args, fmt.Sprintf("--peerport=%v", cfg.P2PPort))
args = append(args, fmt.Sprintf("--rpclisten=%v", cfg.RPCAddr()))
args = append(args, fmt.Sprintf("--restlisten=%v", cfg.RESTAddr()))
args = append(args, fmt.Sprintf("--listen=%v", cfg.P2PAddr()))
args = append(args, fmt.Sprintf("--logdir=%v", cfg.LogDir))
args = append(args, fmt.Sprintf("--datadir=%v", cfg.DataDir))
args = append(args, fmt.Sprintf("--tlscertpath=%v", cfg.TLSCertPath))
@ -197,7 +212,7 @@ func newNode(cfg nodeConfig) (*HarnessNode, error) {
cfg.AdminMacPath = filepath.Join(cfg.DataDir, "admin.macaroon")
cfg.ReadMacPath = filepath.Join(cfg.DataDir, "readonly.macaroon")
cfg.P2PPort, cfg.RPCPort = generateListeningPorts()
cfg.P2PPort, cfg.RPCPort, cfg.RESTPort = generateListeningPorts()
nodeNum := numActiveNodes
numActiveNodes++

View File

@ -36,11 +36,33 @@
; readonlymacaroonpath=~/.lnd/readonly.macaroon
; Specify the interfaces to listen on. One listen address per line.
; All interfaces on default port (this is the default):
; listen=
; Only ipv4 localhost on port 999:
; listen=127.0.0.1:999
; Specify the interfaces to listen on for p2p connections. One listen
; address per line.
; All ipv4 on port 9735:
; listen=0.0.0.0:9735
; On all ipv4 interfaces on port 9735 and ipv6 localhost port 9736:
; listen=0.0.0.0:9735
; listen=[::1]:9736
; Disable listening for incoming p2p connections. This will override all
; listeners.
; nolisten=1
; Specify the interfaces to listen on for gRPC connections. One listen
; address per line.
; Only ipv4 localhost on port 10009:
; rpclisten=localhost:10009
; On ipv4 localhost port 10009 and ipv6 port 10010:
; rpclisten=localhost:10009
; rpclisten=[::1]:10010
; Specify the interfaces to listen on for REST connections. One listen
; address per line.
; All ipv4 interfaces on port 8080:
; restlisten=0.0.0.0:8080
; On ipv4 localhost port 80 and 443:
; restlisten=localhost:80
; restlisten=localhost:443
; Adding an external IP will advertise your node to the network. This signals
@ -63,15 +85,6 @@
; 65536. The profile can be access at: http://localhost:<PORT>/debug/pprof/.
;profile=
; The port to listen on for incoming p2p connections. The default port is 9735.
; peerport=9735
; The port that the gRPC server will listen on.
; rpcport=10009
; The port that the HTTP REST proxy to the gRPC server will listen on.
; restport=8080
; The maximum number of incoming pending channels permitted per peer.
; maxpendingchannels=1