From dd66c85fcbf1fce804f899dde8c5bb76ff59d246 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 9 Mar 2022 17:30:36 +0100 Subject: [PATCH] grpc-plugin: Make the grpc port to listen on configurable Changelog-Added: cln-grpc-plugin: The plugin can be configured to listen on a specific port using the `grpc-port` option --- plugins/grpc-plugin/src/main.rs | 36 ++++++++++++++++++--------------- plugins/src/lib.rs | 13 ++++++++++++ tests/test_cln_rs.py | 18 +++++++++++++---- 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/plugins/grpc-plugin/src/main.rs b/plugins/grpc-plugin/src/main.rs index f0cd4b1e3..8636fc5ad 100644 --- a/plugins/grpc-plugin/src/main.rs +++ b/plugins/grpc-plugin/src/main.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use cln_grpc::pb::node_server::NodeServer; use cln_plugin::{options, Builder}; use log::{debug, warn}; @@ -10,7 +10,6 @@ mod tls; #[derive(Clone, Debug)] struct PluginState { rpc_path: PathBuf, - bind_address: SocketAddr, identity: tls::Identity, ca_cert: Vec, } @@ -19,14 +18,12 @@ struct PluginState { async fn main() -> Result<()> { debug!("Starting grpc plugin"); let path = Path::new("lightning-rpc"); - let addr: SocketAddr = "0.0.0.0:50051".parse().unwrap(); let directory = std::env::current_dir()?; let (identity, ca_cert) = tls::init(&directory)?; let state = PluginState { rpc_path: path.into(), - bind_address: addr, identity, ca_cert, }; @@ -34,14 +31,21 @@ async fn main() -> Result<()> { let plugin = Builder::new(state.clone(), tokio::io::stdin(), tokio::io::stdout()) .option(options::ConfigOption::new( "grpc-port", - options::Value::Integer(29735), + options::Value::Integer(50051), "Which port should the grpc plugin listen for incoming connections?", )) .start() .await?; + let bind_port = match plugin.option("grpc-port") { + Some(options::Value::Integer(i)) => i, + None => return Err(anyhow!("Missing 'grpc-port' option")), + Some(o) => return Err(anyhow!("grpc-port is not a valid integer: {:?}", o)), + }; + let bind_addr: SocketAddr = format!("0.0.0.0:{}", bind_port).parse().unwrap(); + tokio::spawn(async move { - if let Err(e) = run_interface(state).await { + if let Err(e) = run_interface(bind_addr, state).await { warn!("Error running the grpc interface: {}", e); } }); @@ -49,12 +53,7 @@ async fn main() -> Result<()> { plugin.join().await } -async fn run_interface(state: PluginState) -> Result<()> { - debug!( - "Connecting to {:?} and serving grpc on {:?}", - &state.rpc_path, &state.bind_address - ); - +async fn run_interface(bind_addr: SocketAddr, state: PluginState) -> Result<()> { let identity = state.identity.to_tonic_identity(); let ca_cert = tonic::transport::Certificate::from_pem(state.ca_cert); @@ -62,7 +61,7 @@ async fn run_interface(state: PluginState) -> Result<()> { .identity(identity) .client_ca_root(ca_cert); - tonic::transport::Server::builder() + let server = tonic::transport::Server::builder() .tls_config(tls) .context("configuring tls")? .add_service(NodeServer::new( @@ -70,9 +69,14 @@ async fn run_interface(state: PluginState) -> Result<()> { .await .context("creating NodeServer instance")?, )) - .serve(state.bind_address) - .await - .context("serving requests")?; + .serve(bind_addr); + + debug!( + "Connecting to {:?} and serving grpc on {:?}", + &state.rpc_path, &bind_addr + ); + + server.await.context("serving requests")?; Ok(()) } diff --git a/plugins/src/lib.rs b/plugins/src/lib.rs index dfa996210..66630244b 100644 --- a/plugins/src/lib.rs +++ b/plugins/src/lib.rs @@ -331,6 +331,19 @@ where sender: tokio::sync::mpsc::Sender, } +impl Plugin +where + S: Clone + Send, +{ + pub fn option(&self, name: &str) -> Option { + self.options + .iter() + .filter(|o| o.name() == name) + .next() + .map(|co| co.value.clone().unwrap_or(co.default().clone())) + } +} + /// The [PluginDriver] is used to run the IO loop, reading messages /// from the Lightning daemon, dispatching calls and notifications to /// the plugin, and returning responses to the the daemon. We also use diff --git a/tests/test_cln_rs.py b/tests/test_cln_rs.py index 2d8581b78..e2b0ef53d 100644 --- a/tests/test_cln_rs.py +++ b/tests/test_cln_rs.py @@ -2,6 +2,7 @@ from fixtures import * # noqa: F401,F403 from node_pb2_grpc import NodeStub from pathlib import Path from pyln.testing.utils import env, TEST_NETWORK +from ephemeral_port_reserve import reserve import grpc import node_pb2 as nodepb import pytest @@ -67,8 +68,9 @@ def test_plugin_start(node_factory): def test_grpc_connect(node_factory): """Attempts to connect to the grpc interface and call getinfo""" + grpc_port = reserve() bin_path = Path.cwd() / "target" / "debug" / "grpc-plugin" - l1 = node_factory.get_node(options={"plugin": str(bin_path)}) + l1 = node_factory.get_node(options={"plugin": str(bin_path), "grpc-port": str(grpc_port)}) p = Path(l1.daemon.lightning_dir) / TEST_NETWORK cert_path = p / "client.pem" @@ -80,8 +82,9 @@ def test_grpc_connect(node_factory): certificate_chain=cert_path.open('rb').read() ) + l1.daemon.wait_for_log(r'serving grpc on 0.0.0.0:') channel = grpc.secure_channel( - "localhost:50051", + f"localhost:{grpc_port}", creds, options=(('grpc.ssl_target_name_override', 'cln'),) ) @@ -101,9 +104,11 @@ def test_grpc_generate_certificate(node_factory): - If we have certs, we they should just get loaded - If we delete one cert or its key it should get regenerated. """ + grpc_port = reserve() bin_path = Path.cwd() / "target" / "debug" / "grpc-plugin" l1 = node_factory.get_node(options={ "plugin": str(bin_path), + "grpc-port": str(grpc_port), }, start=False) p = Path(l1.daemon.lightning_dir) / TEST_NETWORK @@ -140,8 +145,13 @@ def test_grpc_wrong_auth(node_factory): We create two instances, each generates its own certs and keys, and then we try to cross the wires. """ + grpc_port = reserve() bin_path = Path.cwd() / "target" / "debug" / "grpc-plugin" - l1, l2 = node_factory.get_nodes(2, opts={"plugin": str(bin_path), "start": False}) + l1, l2 = node_factory.get_nodes(2, opts={ + "plugin": str(bin_path), + "start": False, + "grpc-port": str(grpc_port), + }) l1.start() l1.daemon.wait_for_log(r'serving grpc on 0.0.0.0:') @@ -159,7 +169,7 @@ def test_grpc_wrong_auth(node_factory): ) channel = grpc.secure_channel( - "localhost:50051", + f"localhost:{grpc_port}", creds, options=(('grpc.ssl_target_name_override', 'cln'),) )