Skip to content

Commit 35b8b98

Browse files
committed
Log cases where an onion failure cannot be attributed or interpreted
Create more visibility into these edge cases. The non-attributable failure in particular can be used to disrupt sender operation and it is therefore good to at least log these cases clearly.
1 parent eaeed77 commit 35b8b98

File tree

1 file changed

+91
-33
lines changed

1 file changed

+91
-33
lines changed

lightning/src/ln/onion_utils.rs

Lines changed: 91 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,22 +1044,31 @@ where
10441044
let amt_to_forward = htlc_msat - route_hop.fee_msat;
10451045
htlc_msat = amt_to_forward;
10461046

1047-
let err_packet = match decrypt_onion_error_packet(&mut encrypted_packet, shared_secret) {
1048-
Ok(p) => p,
1049-
Err(_) => return,
1050-
};
1047+
let decrypt_result = decrypt_onion_error_packet(&mut encrypted_packet, shared_secret);
1048+
10511049
let um = gen_um_from_shared_secret(shared_secret.as_ref());
10521050
let mut hmac = HmacEngine::<Sha256>::new(&um);
1053-
hmac.input(&err_packet.encode()[32..]);
1051+
hmac.input(&encrypted_packet[32..]);
10541052

1055-
if !fixed_time_eq(&Hmac::from_engine(hmac).to_byte_array(), &err_packet.hmac) {
1053+
if !fixed_time_eq(&Hmac::from_engine(hmac).to_byte_array(), &encrypted_packet[..32]) {
10561054
return;
10571055
}
1056+
1057+
let err_packet = match decrypt_result {
1058+
Ok(p) => p,
1059+
Err(_) => {
1060+
log_warn!(logger, "Unreadable failure from {}", route_hop.pubkey);
1061+
return;
1062+
},
1063+
};
1064+
10581065
let error_code_slice = match err_packet.failuremsg.get(0..2) {
10591066
Some(s) => s,
10601067
None => {
10611068
// Useless packet that we can't use but it passed HMAC, so it definitely came from the peer
10621069
// in question
1070+
log_warn!(logger, "Missing error code in failure from {}", route_hop.pubkey);
1071+
10631072
let network_update = Some(NetworkUpdate::NodeFailure {
10641073
node_id: route_hop.pubkey,
10651074
is_permanent: true,
@@ -1219,6 +1228,12 @@ where
12191228
} else {
12201229
// only not set either packet unparseable or hmac does not match with any
12211230
// payment not retryable only when garbage is from the final node
1231+
log_warn!(
1232+
logger,
1233+
"Non-attributable failure encountered on route {}",
1234+
path.hops.iter().map(|h| h.pubkey.to_string()).collect::<Vec<_>>().join("->")
1235+
);
1236+
12221237
DecodedOnionFailure {
12231238
network_update: None,
12241239
short_channel_id: None,
@@ -1764,7 +1779,10 @@ fn decode_next_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(
17641779

17651780
#[cfg(test)]
17661781
mod tests {
1782+
use std::sync::Arc;
1783+
17671784
use crate::io;
1785+
use crate::ln::channelmanager::PaymentId;
17681786
use crate::ln::msgs;
17691787
use crate::routing::router::{Path, PaymentParameters, Route, RouteHop};
17701788
use crate::types::features::{ChannelFeatures, NodeFeatures};
@@ -1773,6 +1791,7 @@ mod tests {
17731791

17741792
#[allow(unused_imports)]
17751793
use crate::prelude::*;
1794+
use crate::util::test_utils::TestLogger;
17761795

17771796
use bitcoin::hex::FromHex;
17781797
use bitcoin::secp256k1::Secp256k1;
@@ -1785,38 +1804,43 @@ mod tests {
17851804
SecretKey::from_slice(&<Vec<u8>>::from_hex(hex).unwrap()[..]).unwrap()
17861805
}
17871806

1807+
fn build_test_path() -> Path {
1808+
Path { hops: vec![
1809+
RouteHop {
1810+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
1811+
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
1812+
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1813+
},
1814+
RouteHop {
1815+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
1816+
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
1817+
short_channel_id: 1, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1818+
},
1819+
RouteHop {
1820+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(),
1821+
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
1822+
short_channel_id: 2, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1823+
},
1824+
RouteHop {
1825+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()[..]).unwrap(),
1826+
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
1827+
short_channel_id: 3, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1828+
},
1829+
RouteHop {
1830+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()[..]).unwrap(),
1831+
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
1832+
short_channel_id: 4, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1833+
},
1834+
], blinded_tail: None }
1835+
}
1836+
17881837
fn build_test_onion_keys() -> Vec<OnionKeys> {
17891838
// Keys from BOLT 4, used in both test vector tests
17901839
let secp_ctx = Secp256k1::new();
17911840

1841+
let path = build_test_path();
17921842
let route = Route {
1793-
paths: vec![Path { hops: vec![
1794-
RouteHop {
1795-
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
1796-
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
1797-
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1798-
},
1799-
RouteHop {
1800-
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
1801-
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
1802-
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1803-
},
1804-
RouteHop {
1805-
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(),
1806-
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
1807-
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1808-
},
1809-
RouteHop {
1810-
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()[..]).unwrap(),
1811-
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
1812-
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1813-
},
1814-
RouteHop {
1815-
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()[..]).unwrap(),
1816-
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
1817-
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1818-
},
1819-
], blinded_tail: None }],
1843+
paths: vec![path],
18201844
route_params: None,
18211845
};
18221846

@@ -2078,6 +2102,40 @@ mod tests {
20782102
);
20792103
let hex = "9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d";
20802104
assert_eq!(onion_packet_5.data, <Vec<u8>>::from_hex(hex).unwrap());
2105+
2106+
let logger: Arc<TestLogger> = Arc::new(TestLogger::new());
2107+
let ctx_full = Secp256k1::new();
2108+
let path = build_test_path();
2109+
let htlc_source = HTLCSource::OutboundRoute {
2110+
path: path,
2111+
session_priv: get_test_session_key(),
2112+
first_hop_htlc_msat: 0,
2113+
payment_id: PaymentId([1; 32])
2114+
, };
2115+
2116+
// Assert that the original failure can be retrieved and that all hmacs check out.
2117+
let decrypted_failure = process_onion_failure(&ctx_full, &logger, &htlc_source, onion_packet_5.data);
2118+
2119+
assert_eq!(decrypted_failure.onion_error_code, Some(0x2002));
2120+
}
2121+
2122+
#[test]
2123+
fn test_non_attributable_failure_packet_onion() {
2124+
let corrupt_failure_packet = vec![1u8; 292];
2125+
2126+
let logger: Arc<TestLogger> = Arc::new(TestLogger::new());
2127+
let ctx_full = Secp256k1::new();
2128+
let path = build_test_path();
2129+
let htlc_source = HTLCSource::OutboundRoute {
2130+
path: path,
2131+
session_priv: get_test_session_key(),
2132+
first_hop_htlc_msat: 0,
2133+
payment_id: PaymentId([1; 32])
2134+
, };
2135+
2136+
// For a corrupt failure message, the failing channel cannot be identified.
2137+
let decrypted_failure = process_onion_failure(&ctx_full, &logger, &htlc_source, corrupt_failure_packet);
2138+
assert_eq!(decrypted_failure.short_channel_id, None);
20812139
}
20822140

20832141
struct RawOnionHopData {

0 commit comments

Comments
 (0)