Skip to content

Commit

Permalink
docs: improve README
Browse files Browse the repository at this point in the history
  • Loading branch information
fiksn committed Aug 30, 2023
1 parent 6bf88af commit ab3190b
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 35 deletions.
50 changes: 46 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ This is yet another [LDK](https://lightningdevkit.org/) [demo](https://github.co

The current goal of this tool is to determine whether a lightning node has connectivity issues just by listening to gossip messages.

Trick is that peers disable their channel with a node as soon as TCP connection breaks. So if more than n (configurable threshold) nodes disable a channel with a specific node we can tell with a high probability that the given lightning node is unavailable.

Trick is that peers disable their channel with a node as soon as TCP connection breaks. So if more than n (configurable threshold) nodes disable a channel with a specific node we can tell with a high probability that it is unavailable.
All without sending a single probe packet to the target.

The tool is completely stateless, everything it needs is queried through `QueryShortChannelIds` and cached later on.

## Fingerprinting Lightning implementations
## Fingerprinting lightning implementations

While developing this I have found a simple way to fingerprint lightnig node implementations.
While developing this I have found a simple way to fingerprint lightning node implementations.

* LND internally creates a `GossipSyncer` for the connection only when node advertises it understands `gossip_queries` [BOLT-09](https://github.com/lightning/bolts/blob/master/09-features.md)
So if you don't do that `QueryShortChannelIds` will not yield any reply. All other implementations do not need this feature advertised.
Expand All @@ -33,3 +32,46 @@ You can send something like
and stil get a normal reply.

* CLN: does not do any of the previous things (which is unique too)

## Example

```
2023-08-30 17:16:52.156 INFO [gossiper::voter:73] THRESHOLD BREACHED num: 28 node: 036d306e01a65128e5130c99a004119f36bc296ee6f002a7c3c478530b8249a548 alias: Dawn of Freedom
2023-08-30 17:16:52.157 TRACE [gossiper::voter:50] DISABLE chid: 836085134498594816 direction: 1 node: 036d306e01a65128e5130c99a004119f36bc296ee6f002a7c3c478530b8249a548 alias: Dawn of Freedom
2023-08-30 17:16:52.157 INFO [gossiper::voter:73] THRESHOLD BREACHED num: 28 node: 036d306e01a65128e5130c99a004119f36bc296ee6f002a7c3c478530b8249a548 alias: Dawn of Freedom
2023-08-30 17:16:52.157 TRACE [gossiper::voter:50] DISABLE chid: 819663928352047104 direction: 1 node: 036d306e01a65128e5130c99a004119f36bc296ee6f002a7c3c478530b8249a548 alias: Dawn of Freedom
2023-08-30 17:16:52.157 TRACE [gossiper::voter:50] DISABLE chid: 820341227486904321 direction: 1 node: 036d306e01a65128e5130c99a004119f36bc296ee6f002a7c3c478530b8249a548 alias: Dawn of Freedom
2023-08-30 17:16:52.157 INFO [gossiper::voter:73] THRESHOLD BREACHED num: 28 node: 036d306e01a65128e5130c99a004119f36bc296ee6f002a7c3c478530b8249a548 alias: Dawn of Freedom
2023-08-30 17:16:52.157 TRACE [gossiper::voter:50] DISABLE chid: 882218443410571264 direction: 1 node: 03fbc17549ec667bccf397ababbcb4cdc0e3394345e4773079ab2774612ec9be61 alias: node201.fmt.mempool.space
2023-08-30 17:16:52.157 INFO [gossiper::voter:73] THRESHOLD BREACHED num: 28 node: 036d306e01a65128e5130c99a004119f36bc296ee6f002a7c3c478530b8249a548 alias: Dawn of Freedom
2023-08-30 17:16:52.157 TRACE [gossiper::voter:50] DISABLE chid: 849020888796364801 direction: 0 node: 02ad4afb6e50ae4635ec5ddf5a57c44d4cc4b376ac6580f78cda0454a86e5fa6c2 alias: wyssblitz
2023-08-30 17:16:52.157 INFO [gossiper::voter:73] THRESHOLD BREACHED num: 29 node: 036d306e01a65128e5130c99a004119f36bc296ee6f002a7c3c478530b8249a548 alias: Dawn of Freedom
2023-08-30 17:16:52.157 TRACE [gossiper::voter:50] DISABLE chid: 857100100212817921 direction: 0 node: 021e3544a3b10379e833b85600f05de6d162a8ff93945eebd7def493fc024f1962 alias: TheFlash⚡
2023-08-30 17:16:52.157 TRACE [gossiper::voter:50] DISABLE chid: 829880590372896769 direction: 0 node: 036d306e01a65128e5130c99a004119f36bc296ee6f002a7c3c478530b8249a548 alias: Dawn of Freedom
2023-08-30 17:16:52.157 TRACE [gossiper::voter:50] DISABLE chid: 819762884369842176 direction: 1 node: 036d306e01a65128e5130c99a004119f36bc296ee6f002a7c3c478530b8249a548 alias: Dawn of Freedom
2023-08-30 17:16:52.157 INFO [gossiper::voter:73] THRESHOLD BREACHED num: 29 node: 036d306e01a65128e5130c99a004119f36bc296ee6f002a7c3c478530b8249a548 alias: Dawn of Freedom
2023-08-30 17:16:52.157 TRACE [gossiper::voter:100] ENABLE chid: 749630535234158592 direction: 1 node: 03c445275ee7d79ee5778ca2ac81b7c4d84aed7ee04629e8d8f35434b3c21e2da8 alias: Obi-Wan Cryptobi
2023-08-30 17:16:52.157 INFO [gossiper::voter:73] THRESHOLD BREACHED num: 30 node: 036d306e01a65128e5130c99a004119f36bc296ee6f002a7c3c478530b8249a548 alias: Dawn of Freedom
2023-08-30 17:16:52.157 TRACE [gossiper::voter:50] DISABLE chid: 820341227486904321 direction: 0 node: 025f1456582e70c4c06b61d5c8ed3ce229e6d0db538be337a2dc6d163b0ebc05a5 alias: Moon (paywithmoon.com)
2023-08-30 17:16:52.157 INFO [gossiper::voter:73] THRESHOLD BREACHED num: 30 node: 036d306e01a65128e5130c99a004119f36bc296ee6f002a7c3c478530b8249a548 alias: Dawn of Freedom
```

Currently the output is very crude but you can detect that `036d306e01a65128e5130c99a004119f36bc296ee6f002a7c3c478530b8249a548` actually seemed to have some issues during testing.
TODO: Need to fix the false positives that come from bigger nodes where a lot of people simultaneously have connectivity issues independently. Since this is stateless it is hard to tell number of channels for a node (unless I do a complete sync but that is slow).
11 changes: 7 additions & 4 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ use std::fs;
// getdir will search for library with given pattern in nix store
fn getdir(pattern: &str) -> Option<String> {
const DIR: &str = "/nix/store";

let entries = fs::read_dir(DIR).ok()?;

for entry in entries {
let entry = entry.ok()?;
let entry_path = entry.path();

if !entry_path.is_dir() || entry_path.file_name() == None {
continue;
}
Expand All @@ -31,7 +31,10 @@ fn main() {
println!("cargo:rustc-link-search=native={}/lib", dir)
}
if let Some(dir) = getdir("apple-framework-CoreFoundation") {
println!("cargo:rustc-link-search=framework={}/Library/Frameworks/", dir)
println!(
"cargo:rustc-link-search=framework={}/Library/Frameworks/",
dir
)
}
}
}
}
14 changes: 8 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ struct Args {
nodes: Vec<LightningNodeAddr>,

/// Threshold
#[arg(short, long, num_args = 1, default_value_t = 3)]
#[arg(short, long, num_args = 1, default_value_t = 5)]
threshold: u8,
}

const DEBUG: bool = false;

#[main]
async fn main() {
const DEBUG: bool = true;

let args = Args::parse();

// Init peripheral
Expand Down Expand Up @@ -138,10 +138,12 @@ async fn main() {

log_info!(logger, "Invoking query");

let chanid = 869059488412139521u64;

let nodeid1 = (*resolver)
.get_node(
(*resolver)
.get_endpoints_async(869059488412139521u64)
.get_endpoints_async(chanid)
.await
.expect("channel data")
.nodes[0],
Expand All @@ -151,7 +153,7 @@ async fn main() {
let nodeid2 = (*resolver)
.get_node(
(*resolver)
.get_endpoints_async(869059488412139521u64)
.get_endpoints_async(chanid)
.await
.expect("channel data")
.nodes[1],
Expand All @@ -162,7 +164,7 @@ async fn main() {
logger,
"{} --{}--> {}",
nodeid1,
869059488412139521u64,
chanid,
nodeid2
);
};
Expand Down
4 changes: 4 additions & 0 deletions src/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use parking_lot::lock_api::RawMutex;

const MAX_TIMEOUT: Duration = Duration::from_secs(128);
const POLL_INTERVAL: Duration = Duration::from_secs(10);
const DEBUG: bool = false;

pub type ResolvePeerManager = PeerManager<
SocketDescriptor,
Expand Down Expand Up @@ -323,6 +324,9 @@ where
msg: lightning::ln::msgs::ReplyShortChannelIdsEnd,
) -> Result<(), lightning::ln::msgs::LightningError> {
// Unlock the raw mutex
if DEBUG {
log_debug!(self.logger, "Short channel ids reply");
}
unsafe {
if self.server_lock.is_locked() {
self.server_lock.unlock();
Expand Down
62 changes: 41 additions & 21 deletions src/voter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use lightning::routing::gossip::NodeId;
use lightning::routing::gossip::{NodeId, NodeAlias};
use lightning::util::logger::Logger;
use lightning::*;
use std::collections::{HashMap, HashSet};
Expand All @@ -10,6 +10,11 @@ use super::resolve::*;

/// Voter is able to count votes regarding unavailability

pub struct NodeData {
pub node_id: NodeId,
pub alias: NodeAlias,
}

pub struct Voter<L: Deref + Send + std::marker::Sync + 'static>
where
L::Target: Logger,
Expand All @@ -36,16 +41,36 @@ where
*(self.resolver.lock().unwrap()) = Some(resolver.clone());
}

async fn get_node(&self, chanid: u64, direction: usize) -> NodeData {
let res = self.resolver.lock().unwrap().clone().unwrap();

let id = res.get_endpoints_async(chanid).await.expect("channel data").nodes[direction];

let n = res.get_node(id);

if n == None {
log_trace!(self.logger, "Node data for nodeid {} not available", id);

let result: [u8; 32] = [0; 32];

return NodeData {
node_id: id,
alias: NodeAlias(result),
};
}

let node = n.unwrap();

NodeData {
node_id: node.node_id,
alias: node.alias,
}

}

pub async fn disable(&self, chanid: u64, direction: usize) {
let res = self.resolver.lock().unwrap().clone().unwrap();
let node = res
.get_node(
res.get_endpoints_async(chanid)
.await
.expect("channel data")
.nodes[direction],
)
.expect("node data");
let node = self.get_node(chanid, direction).await;

log_trace!(
self.logger,
Expand Down Expand Up @@ -81,21 +106,16 @@ where
}

pub async fn enable(&self, chanid: u64, direction: usize) {
let res = self.resolver.lock().unwrap().clone().unwrap();
{
let res = self.resolver.lock().unwrap().clone().unwrap();

if !res.is_endpoint_cached(chanid) {
// Ignore enabling channels which we are unaware of
return;
if !res.is_endpoint_cached(chanid) {
// Ignore enabling channels which we are unaware of
return;
}
}

let node = res
.get_node(
res.get_endpoints_async(chanid)
.await
.expect("channel data")
.nodes[direction],
)
.expect("node data");
let node = self.get_node(chanid, direction).await;

log_trace!(
self.logger,
Expand Down

0 comments on commit ab3190b

Please sign in to comment.