diff --git a/aperture.go b/aperture.go index f87c3985..1146959c 100644 --- a/aperture.go +++ b/aperture.go @@ -971,8 +971,12 @@ func createHashMailServer(cfg *Config) ([]proxy.LocalService, func(), error) { } // Wrap the default grpc-gateway handler with the WebSocket handler. + // We enable WebSocket-level pings to keep the underlying connection + // alive through intermediary proxies and load balancers that may + // silently drop idle WebSocket connections. restHandler := lnrpc.NewWebSocketProxy( - mux, log, 0, 0, clientStreamingURIs, + mux, log, cfg.WsPingInterval, cfg.WsPongWait, + clientStreamingURIs, ) // Create our proxy chain now. A request will pass diff --git a/config.go b/config.go index a24437dc..0f34dc0f 100644 --- a/config.go +++ b/config.go @@ -35,6 +35,17 @@ const ( defaultIdleTimeout = time.Minute * 2 defaultReadTimeout = time.Second * 15 defaultWriteTimeout = time.Second * 30 + + // defaultWsPingInterval is the default interval at which we send + // WebSocket-level pings to connected clients. This keeps the + // connection alive through intermediary proxies and load balancers + // that may drop idle connections. + defaultWsPingInterval = 30 * time.Second + + // defaultWsPongWait is the default duration we wait for a pong + // response after sending a WebSocket ping before considering the + // connection dead. + defaultWsPongWait = 15 * time.Second ) type EtcdConfig struct { @@ -233,6 +244,18 @@ type Config struct { // Logging controls various aspects of aperture logging. Logging *build.LogConfig `group:"logging" namespace:"logging"` + // WsPingInterval is the interval at which WebSocket-level pings are + // sent to connected clients. This keeps the underlying WebSocket + // connection alive through intermediary proxies and load balancers. + // Set to 0 to disable. + WsPingInterval time.Duration `long:"wspinginterval" description:"Interval for WebSocket-level ping messages to keep connections alive through proxies."` + + // WsPongWait is the duration to wait for a pong response after + // sending a WebSocket-level ping. If no pong is received within this + // duration, the WebSocket connection is considered dead. Must be + // strictly less than WsPingInterval when pings are enabled. + WsPongWait time.Duration `long:"wspongwait" description:"Duration to wait for a WebSocket pong response before closing the connection."` + // Blocklist is a list of IPs to deny access to. Blocklist []string `long:"blocklist" description:"List of IP addresses to block from accessing the proxy."` } @@ -252,6 +275,18 @@ func (c *Config) validate() error { return fmt.Errorf("invoice batch size must be greater than 0") } + if c.WsPingInterval < 0 { + return fmt.Errorf("wspinginterval must not be negative") + } + if c.WsPongWait < 0 { + return fmt.Errorf("wspongwait must not be negative") + } + if c.WsPingInterval > 0 && c.WsPongWait >= c.WsPingInterval { + return fmt.Errorf("wspongwait (%v) must be less than "+ + "wspinginterval (%v)", c.WsPongWait, + c.WsPingInterval) + } + return nil } @@ -277,6 +312,8 @@ func NewConfig() *Config { IdleTimeout: defaultIdleTimeout, ReadTimeout: defaultReadTimeout, WriteTimeout: defaultWriteTimeout, + WsPingInterval: defaultWsPingInterval, + WsPongWait: defaultWsPongWait, InvoiceBatchSize: defaultInvoiceBatchSize, Logging: build.DefaultLogConfig(), Blocklist: []string{}, diff --git a/sample-conf.yaml b/sample-conf.yaml index f3f4ac2c..dd28af0b 100644 --- a/sample-conf.yaml +++ b/sample-conf.yaml @@ -50,6 +50,17 @@ readtimeout: 15s # The maximum amount of time to wait for a response to be fully written. writetimeout: 30s +# Interval at which WebSocket-level pings are sent to connected clients. This +# keeps LNC connections alive through intermediary proxies and load balancers +# that may drop idle WebSocket connections. Set to 0 to disable. +# Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". +wspinginterval: 30s + +# Duration to wait for a WebSocket pong response after sending a ping. If no +# pong is received within this duration, the connection is considered dead. +# Must be strictly less than wspinginterval. +wspongwait: 15s + # Settings for the lnd node used to generate payment requests. All of these # options are required. authenticator: