Skip to content

Commit 700c83d

Browse files
committed
Clean up onion error handling
More closely mirroring the spec where the process is based on mainly the top byte of the error code.
1 parent 8e5ebe7 commit 700c83d

File tree

1 file changed

+75
-181
lines changed

1 file changed

+75
-181
lines changed

src/ln/channelmanager.rs

Lines changed: 75 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -2077,36 +2077,14 @@ impl ChannelManager {
20772077
// Process failure we got back from upstream on a payment we sent. Returns update and a boolean
20782078
// indicating that the payment itself failed
20792079
fn process_onion_failure(&self, htlc_source: &HTLCSource, mut packet_decrypted: Vec<u8>) -> (Option<msgs::HTLCFailChannelUpdate>, bool, Option<u16>) {
2080+
use util::errors::{get_onion_error_description, get_onion_debug_field};
20802081
if let &HTLCSource::OutboundRoute { ref route, ref session_priv, ref first_hop_htlc_msat } = htlc_source {
2081-
macro_rules! onion_failure_log {
2082-
( $error_code_textual: expr, $error_code: expr, $reported_name: expr, $reported_value: expr ) => {{
2083-
log_info!(self, "{}({:#x}) {}({})", $error_code_textual, $error_code, $reported_name, $reported_value);
2084-
}};
2085-
( $error_code_textual: expr, $error_code: expr ) => {{
2086-
log_info!(self, "{}({:#x})", $error_code_textual, $error_code);
2087-
}};
2088-
}
2089-
2090-
// when a node has sent invalid code or malformed failure msg
2091-
macro_rules! return_node_fail_update {
2092-
($res:ident, $node_id:expr, $retry:expr) => {{
2093-
$res = Some((Some(msgs::HTLCFailChannelUpdate::NodeFailure {
2094-
node_id: $node_id,
2095-
is_permanent: true,
2096-
}), $retry));
2097-
return;
2098-
}}
2099-
}
2100-
2101-
const BADONION: u16 = 0x8000;
2102-
const PERM: u16 = 0x4000;
2103-
const NODE: u16 = 0x2000;
2104-
const UPDATE: u16 = 0x1000;
21052082

21062083
let mut res = None;
21072084
let mut htlc_msat = *first_hop_htlc_msat;
21082085
let mut error_code_ret = None;
21092086
let mut next_route_hop_ix = 0;
2087+
let mut is_from_final_node = false;
21102088

21112089
// Handle packed channel/node updates for passing back for the route handler
21122090
Self::construct_onion_keys_callback(&self.secp_ctx, route, session_priv, |shared_secret, _, _, route_hop| {
@@ -2125,7 +2103,7 @@ impl ChannelManager {
21252103
chacha.process(&packet_decrypted, &mut decryption_tmp[..]);
21262104
packet_decrypted = decryption_tmp;
21272105

2128-
let is_from_final_node = route.hops.last().unwrap().pubkey == route_hop.pubkey;
2106+
is_from_final_node = route.hops.last().unwrap().pubkey == route_hop.pubkey;
21292107

21302108
if let Ok(err_packet) = msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) {
21312109
let um = ChannelManager::gen_um_from_shared_secret(&shared_secret[..]);
@@ -2138,175 +2116,92 @@ impl ChannelManager {
21382116
if err_packet.failuremsg.len() < 2 {
21392117
// Useless packet that we can't use but it passed HMAC, so it
21402118
// definitely came from the peer in question
2141-
return_node_fail_update!(res, route_hop.pubkey, !is_from_final_node);
2119+
res = Some((Some(msgs::HTLCFailChannelUpdate::NodeFailure {
2120+
node_id: route_hop.pubkey,
2121+
is_permanent: true,
2122+
}), !is_from_final_node));
21422123
} else {
2124+
2125+
const PERM: u16 = 0x4000;
2126+
const NODE: u16 = 0x2000;
2127+
const UPDATE: u16 = 0x1000;
2128+
21432129
let error_code = byte_utils::slice_to_be16(&err_packet.failuremsg[0..2]);
21442130
error_code_ret = Some(error_code);
21452131

2146-
// either from intermediate or final node
2147-
if error_code&0xff == 1 || error_code&0xff == 2 || error_code&0xff == 3 {
2148-
match error_code {
2149-
_c if _c == PERM|1 => onion_failure_log!("invalid_realm", error_code),
2150-
_c if _c == NODE|2 => onion_failure_log!("temporary_node_failure", error_code),
2151-
_c if _c == PERM|NODE|2 => onion_failure_log!("permanent_node_failure", error_code),
2152-
_c if _c == PERM|NODE|3 => onion_failure_log!("required_node_feature_mssing", error_code),
2153-
_ => return_node_fail_update!(res, route_hop.pubkey, !is_from_final_node),
2154-
}
2155-
// node returning invalid_realm is removed from network_map,
2156-
// although NODE flag is not set, TODO: or remove channel only?
2157-
// retry payment when removed node is not a final node
2158-
let is_permanent = error_code & PERM == PERM;
2159-
if error_code & 0xff00 & NODE == NODE {
2160-
res = Some((Some(msgs::HTLCFailChannelUpdate::NodeFailure {
2161-
node_id: route_hop.pubkey,
2162-
is_permanent,
2163-
}), !(error_code & PERM == PERM && is_from_final_node)));
2164-
} else {
2165-
res = Some((Some(msgs::HTLCFailChannelUpdate::ChannelClosed {
2166-
// failing incoming channel to the erring node
2167-
short_channel_id: route_hop.short_channel_id,
2168-
is_permanent,
2169-
}), !(error_code & PERM == PERM && is_from_final_node)));
2170-
}
2171-
return;
2172-
}
2132+
let (debug_field, debug_field_size) = get_onion_debug_field(error_code);
21732133

2174-
if is_from_final_node {
2175-
let payment_retryable = match error_code {
2176-
_c if _c == PERM|15 => { onion_failure_log!("unknwon_payment_hash", error_code); false },
2177-
_c if _c == PERM|16 => { onion_failure_log!("incorrect_payment_amount", error_code); false },
2178-
17 => { onion_failure_log!("final_expiry_too_soon", error_code); true },
2179-
18 if err_packet.failuremsg.len() == 6 => {
2180-
onion_failure_log!("final_incorrect_cltv_expiry", error_code, "cltv_expiry", byte_utils::slice_to_be32(&err_packet.failuremsg[2..2+4]));
2181-
true
2182-
},
2183-
19 if err_packet.failuremsg.len() == 10 => {
2184-
onion_failure_log!("final_incorrect_htlc_amount", error_code, "incoming_htlc_msat", byte_utils::slice_to_be64(&err_packet.failuremsg[2..2+8]));
2185-
true
2186-
},
2187-
_ => {
2188-
// A final node has sent us either an invalid code or an error_code that
2189-
// MUST be sent from the processing node, or the formmat of failuremsg
2190-
// does not coform to the spec.
2191-
// Remove it from the network map and don't may retry payment
2192-
return_node_fail_update!(res, route_hop.pubkey, false);
2193-
}
2194-
};
2195-
res = Some((None, payment_retryable));
2196-
return;
2197-
}
2134+
// indicate that payment parameter has failed and no need to
2135+
// update route db
2136+
let payment_failed = match error_code & 0xff {
2137+
15|16|17|18|19 => true,
2138+
_ => false,
2139+
} && is_from_final_node; // PERM bit observed below even this error is from the intermediate nodes
21982140

2199-
// now, error_code should be only from the intermediate nodes
2200-
match error_code {
2201-
_c if _c & 0xff00 == PERM|BADONION => {
2202-
if err_packet.failuremsg.len() == 2 + 32 {
2203-
let ref onion_hash = DebugBytes(&err_packet.failuremsg[2..2+32]);
2204-
match error_code & 0xff {
2205-
4 => onion_failure_log!("invalid_onion_version", error_code, "sha256_of_onion", onion_hash),
2206-
5 => onion_failure_log!("invalid_onion_hmac", error_code, "sha256_of_onion", onion_hash),
2207-
6 => onion_failure_log!("invalid_onion_key", error_code, "sha256_of_onion", onion_hash),
2208-
_ => return_node_fail_update!(res, route_hop.pubkey, true),
2209-
}
2210-
} else { return_node_fail_update!(res, route_hop.pubkey, true); }
2211-
2212-
res = Some((Some(msgs::HTLCFailChannelUpdate::ChannelClosed {
2213-
short_channel_id: route_hop.short_channel_id,
2214-
is_permanent: true,
2215-
}), true));
2216-
return;
2217-
},
2218-
_c if _c & 0xff00 == PERM => {
2219-
match error_code & 0xff {
2220-
8 => onion_failure_log!("permanent_channel_failure", error_code),
2221-
9 => onion_failure_log!("required_channel_feature_missing", error_code),
2222-
10 => onion_failure_log!("unknown_next_peer", error_code),
2223-
_ => return_node_fail_update!(res, route_hop.pubkey, true),
2224-
}
2225-
assert!(next_route_hop_ix < route.hops.len());
2226-
res = Some((Some(msgs::HTLCFailChannelUpdate::ChannelClosed {
2227-
short_channel_id: route.hops[next_route_hop_ix].short_channel_id,
2228-
is_permanent: true,
2229-
}), true));
2230-
return;
2231-
},
2232-
_c if _c & 0xff00 == UPDATE => {
2233-
let offset = match error_code & 0xff{
2234-
7 => 0, // temporary_channel_failure
2235-
11 => 8, // amount_below_minimum
2236-
12 => 8, // fee_insufficient
2237-
13 => 4, // incorrect_cltv_expiry
2238-
14 => 0, // expiry_too_soon
2239-
20 => 2, // channel_disabled
2240-
_ => return_node_fail_update!(res, route_hop.pubkey, true),
2241-
};
2242-
if err_packet.failuremsg.len() >= offset + 2 {
2243-
let update_len = byte_utils::slice_to_be16(&err_packet.failuremsg[offset+2..offset+4]) as usize;
2244-
if err_packet.failuremsg.len() >= offset + 4 + update_len {
2245-
if let Ok(chan_update) = msgs::ChannelUpdate::read(&mut Cursor::new(&err_packet.failuremsg[offset + 4..offset + 4 + update_len])) {
2246-
2247-
// if channel_update should NOT have caused the failure:
2248-
// MAY treat the channel_update as invalid.
2249-
let is_chan_update_invalid = match error_code & 0xff {
2250-
7 => {
2251-
onion_failure_log!("temporary_channel_failure", error_code);
2252-
false
2253-
},
2254-
11 => {
2255-
onion_failure_log!("amount_below_minimum", error_code, "htlc_msat", byte_utils::slice_to_be64(&err_packet.failuremsg[2..2+8]));
2256-
amt_to_forward > chan_update.contents.htlc_minimum_msat
2257-
},
2258-
12 => {
2259-
let new_fee = amt_to_forward.checked_mul(chan_update.contents.fee_proportional_millionths as u64).and_then(|prop_fee| { (prop_fee / 1000000).checked_add(chan_update.contents.fee_base_msat as u64) });
2260-
onion_failure_log!("fee_insufficient", error_code, "htlc_msat", byte_utils::slice_to_be64(&err_packet.failuremsg[2..2+8]));
2261-
new_fee.is_none() || incoming_htlc_msat >= new_fee.unwrap() && incoming_htlc_msat >= amt_to_forward + new_fee.unwrap()
2262-
}
2263-
13 => {
2264-
onion_failure_log!("incorrect_cltv_expiry", error_code, "cltv_expiry", byte_utils::slice_to_be32(&err_packet.failuremsg[2..2+4]));
2265-
route_hop.cltv_expiry_delta as u16 >= chan_update.contents.cltv_expiry_delta
2266-
},
2267-
14 => { // expiry_too_soon
2268-
onion_failure_log!("expiry_too_soon", error_code);
2269-
// always valid?
2270-
false
2271-
},
2272-
20 => {
2273-
onion_failure_log!("channel_disabled", error_code, "flags", byte_utils::slice_to_be16(&err_packet.failuremsg[2..2+2]));
2274-
chan_update.contents.flags & 0x0200 == 1
2275-
},
2276-
_ => return_node_fail_update!(res, route_hop.pubkey, true),
2277-
};
2278-
2279-
let msg = if is_chan_update_invalid { None } else {
2280-
Some(msgs::HTLCFailChannelUpdate::ChannelUpdateMessage {
2281-
msg: chan_update,
2282-
})
2283-
};
2284-
res = Some((msg, true));
2285-
return;
2286-
}
2141+
let mut fail_channel_update = None;
2142+
2143+
if error_code & NODE == NODE {
2144+
fail_channel_update = Some(msgs::HTLCFailChannelUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent: error_code & PERM == PERM });
2145+
}
2146+
else if error_code & PERM == PERM {
2147+
fail_channel_update = if payment_failed {None} else {Some(msgs::HTLCFailChannelUpdate::ChannelClosed {
2148+
short_channel_id: match error_code & 0xff {
2149+
8|9|10 => route.hops[next_route_hop_ix].short_channel_id, // outgoing (permanent_channel_failure/required_channel_feature_missing/unknown_next_peer)
2150+
_ => route_hop.short_channel_id, // incoming channel (invalid_realm & BADONION)
2151+
},
2152+
is_permanent: error_code & PERM == PERM,
2153+
})};
2154+
}
2155+
else if error_code & UPDATE == UPDATE {
2156+
if err_packet.failuremsg.len() >= debug_field_size + 2 {
2157+
let update_len = byte_utils::slice_to_be16(&err_packet.failuremsg[debug_field_size+2..debug_field_size+4]) as usize;
2158+
if err_packet.failuremsg.len() >= debug_field_size + 4 + update_len {
2159+
if let Ok(chan_update) = msgs::ChannelUpdate::read(&mut Cursor::new(&err_packet.failuremsg[debug_field_size + 4..debug_field_size + 4 + update_len])) {
2160+
// if channel_update should NOT have caused the failure:
2161+
// MAY treat the channel_update as invalid.
2162+
let is_chan_update_invalid = match error_code & 0xff {
2163+
7 => false,
2164+
11 => amt_to_forward > chan_update.contents.htlc_minimum_msat,
2165+
12 => {
2166+
let new_fee = amt_to_forward.checked_mul(chan_update.contents.fee_proportional_millionths as u64).and_then(|prop_fee| { (prop_fee / 1000000).checked_add(chan_update.contents.fee_base_msat as u64) });
2167+
new_fee.is_none() || incoming_htlc_msat >= new_fee.unwrap() && incoming_htlc_msat >= amt_to_forward + new_fee.unwrap()
2168+
}
2169+
13 => route_hop.cltv_expiry_delta as u16 >= chan_update.contents.cltv_expiry_delta,
2170+
14 => false, // expiry_too_soon; always valid?
2171+
20 => chan_update.contents.flags & 0x0200 == 1,
2172+
_ => false, // unknown error code; take channel_update as valid
2173+
};
2174+
fail_channel_update = if is_chan_update_invalid { None } else {
2175+
Some(msgs::HTLCFailChannelUpdate::ChannelUpdateMessage {
2176+
msg: chan_update,
2177+
})
2178+
};
22872179
}
22882180
}
2289-
return_node_fail_update!(res, route_hop.pubkey, true);
2290-
},
2291-
21 => {
2292-
onion_failure_log!("expiry_too_far", error_code);
2293-
res = Some((None, true));
2294-
return;
2295-
},
2296-
_ => {
2297-
return_node_fail_update!(res, route_hop.pubkey, true);
22982181
}
22992182
}
2183+
2184+
res = Some((fail_channel_update, !(error_code & PERM == PERM && is_from_final_node)));
2185+
2186+
let (description, title) = get_onion_error_description(error_code);
2187+
if debug_field_size > 0 && err_packet.failuremsg.len() >= 2 + debug_field_size {
2188+
log_warn!(self, "Onion Error[{}({:#x}) {}({})] {}", title, error_code, debug_field, DebugBytes(&err_packet.failuremsg[2..2+debug_field_size]), description);
2189+
}
2190+
else {
2191+
log_warn!(self, "Onion Error[{}({:#x})] {}", title, error_code, description);
2192+
}
23002193
}
23012194
}
23022195
}
23032196
}).expect("Route that we sent via spontaneously grew invalid keys in the middle of it?");
2304-
let (channel_update, payment_retryable) = res.expect("should have been set!");
2305-
(channel_update, payment_retryable, error_code_ret)
2306-
} else {
2307-
// htlc_source should have been matched in the callsite
2308-
unreachable!();
2309-
}
2197+
if let Some((channel_update, payment_retryable)) = res {
2198+
(channel_update, payment_retryable, error_code_ret)
2199+
} else {
2200+
// only not set either packet unparseable or hmac does not match with any
2201+
// payment not retryable only when garbage is from the final node
2202+
(None, !is_from_final_node, None)
2203+
}
2204+
} else { unreachable!(); }
23102205
}
23112206

23122207
fn internal_update_fail_htlc(&self, their_node_id: &PublicKey, msg: &msgs::UpdateFailHTLC) -> Result<(), MsgHandleErrInternal> {
@@ -4952,7 +4847,6 @@ mod tests {
49524847
assert!(updates_2.update_fee.is_none());
49534848

49544849
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates_2.update_fail_htlcs[0]).unwrap();
4955-
49564850
commitment_signed_dance!(nodes[0], nodes[1], updates_2.commitment_signed, false, true);
49574851

49584852
let events = nodes[0].node.get_and_clear_pending_events();

0 commit comments

Comments
 (0)