From: Matt Corallo Date: Thu, 20 Dec 2018 19:51:18 +0000 (-0500) Subject: Move process_onion_failure into onion_utils X-Git-Tag: v0.0.12~251^2~4 X-Git-Url: http://git.bitcoin.ninja/?a=commitdiff_plain;h=04ff26efa9c31c0543b17422f6f2bde1d0413d21;p=rust-lightning Move process_onion_failure into onion_utils --- diff --git a/src/ln/channelmanager.rs b/src/ln/channelmanager.rs index b5ee9b562..03d7e0866 100644 --- a/src/ln/channelmanager.rs +++ b/src/ln/channelmanager.rs @@ -39,7 +39,6 @@ use util::ser::{Readable, ReadableArgs, Writeable, Writer}; use util::chacha20::ChaCha20; use util::logger::Logger; use util::errors::APIError; -use util::errors; use std::{cmp, mem}; use std::collections::{HashMap, hash_map, HashSet}; @@ -1297,9 +1296,9 @@ impl ChannelManager { match &onion_error { &HTLCFailReason::ErrorPacket { ref err } => { #[cfg(test)] - let (channel_update, payment_retryable, onion_error_code) = self.process_onion_failure(&source, err.data.clone()); + let (channel_update, payment_retryable, onion_error_code) = onion_utils::process_onion_failure(&self.secp_ctx, &self.logger, &source, err.data.clone()); #[cfg(not(test))] - let (channel_update, payment_retryable, _) = self.process_onion_failure(&source, err.data.clone()); + let (channel_update, payment_retryable, _) = onion_utils::process_onion_failure(&self.secp_ctx, &self.logger, &source, err.data.clone()); // TODO: If we decided to blame ourselves (or one of our channels) in // process_onion_failure we should close that channel as it implies our // next-hop is needlessly blaming us! @@ -1865,155 +1864,6 @@ impl ChannelManager { Ok(()) } - // Process failure we got back from upstream on a payment we sent. Returns update and a boolean - // indicating that the payment itself failed - fn process_onion_failure(&self, htlc_source: &HTLCSource, mut packet_decrypted: Vec) -> (Option, bool, Option) { - if let &HTLCSource::OutboundRoute { ref route, ref session_priv, ref first_hop_htlc_msat } = htlc_source { - - let mut res = None; - let mut htlc_msat = *first_hop_htlc_msat; - let mut error_code_ret = None; - let mut next_route_hop_ix = 0; - let mut is_from_final_node = false; - - // Handle packed channel/node updates for passing back for the route handler - onion_utils::construct_onion_keys_callback(&self.secp_ctx, route, session_priv, |shared_secret, _, _, route_hop| { - next_route_hop_ix += 1; - if res.is_some() { return; } - - let amt_to_forward = htlc_msat - route_hop.fee_msat; - htlc_msat = amt_to_forward; - - let ammag = onion_utils::gen_ammag_from_shared_secret(&shared_secret[..]); - - let mut decryption_tmp = Vec::with_capacity(packet_decrypted.len()); - decryption_tmp.resize(packet_decrypted.len(), 0); - let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]); - chacha.process(&packet_decrypted, &mut decryption_tmp[..]); - packet_decrypted = decryption_tmp; - - is_from_final_node = route.hops.last().unwrap().pubkey == route_hop.pubkey; - - if let Ok(err_packet) = msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) { - let um = onion_utils::gen_um_from_shared_secret(&shared_secret[..]); - let mut hmac = HmacEngine::::new(&um); - hmac.input(&err_packet.encode()[32..]); - - if fixed_time_eq(&Hmac::from_engine(hmac).into_inner(), &err_packet.hmac) { - if let Some(error_code_slice) = err_packet.failuremsg.get(0..2) { - const PERM: u16 = 0x4000; - const NODE: u16 = 0x2000; - const UPDATE: u16 = 0x1000; - - let error_code = byte_utils::slice_to_be16(&error_code_slice); - error_code_ret = Some(error_code); - - let (debug_field, debug_field_size) = errors::get_onion_debug_field(error_code); - - // indicate that payment parameter has failed and no need to - // update Route object - let payment_failed = (match error_code & 0xff { - 15|16|17|18|19 => true, - _ => false, - } && is_from_final_node) // PERM bit observed below even this error is from the intermediate nodes - || error_code == 21; // Special case error 21 as the Route object is bogus, TODO: Maybe fail the node if the CLTV was reasonable? - - let mut fail_channel_update = None; - - if error_code & NODE == NODE { - fail_channel_update = Some(msgs::HTLCFailChannelUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent: error_code & PERM == PERM }); - } - else if error_code & PERM == PERM { - fail_channel_update = if payment_failed {None} else {Some(msgs::HTLCFailChannelUpdate::ChannelClosed { - short_channel_id: route.hops[next_route_hop_ix - if next_route_hop_ix == route.hops.len() { 1 } else { 0 }].short_channel_id, - is_permanent: true, - })}; - } - else if error_code & UPDATE == UPDATE { - if let Some(update_len_slice) = err_packet.failuremsg.get(debug_field_size+2..debug_field_size+4) { - let update_len = byte_utils::slice_to_be16(&update_len_slice) as usize; - if let Some(update_slice) = err_packet.failuremsg.get(debug_field_size + 4..debug_field_size + 4 + update_len) { - if let Ok(chan_update) = msgs::ChannelUpdate::read(&mut Cursor::new(&update_slice)) { - // if channel_update should NOT have caused the failure: - // MAY treat the channel_update as invalid. - let is_chan_update_invalid = match error_code & 0xff { - 7 => false, - 11 => amt_to_forward > chan_update.contents.htlc_minimum_msat, - 12 => { - 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) }); - new_fee.is_some() && route_hop.fee_msat >= new_fee.unwrap() - } - 13 => route_hop.cltv_expiry_delta as u16 >= chan_update.contents.cltv_expiry_delta, - 14 => false, // expiry_too_soon; always valid? - 20 => chan_update.contents.flags & 2 == 0, - _ => false, // unknown error code; take channel_update as valid - }; - fail_channel_update = if is_chan_update_invalid { - // This probably indicates the node which forwarded - // to the node in question corrupted something. - Some(msgs::HTLCFailChannelUpdate::ChannelClosed { - short_channel_id: route_hop.short_channel_id, - is_permanent: true, - }) - } else { - Some(msgs::HTLCFailChannelUpdate::ChannelUpdateMessage { - msg: chan_update, - }) - }; - } - } - } - if fail_channel_update.is_none() { - // They provided an UPDATE which was obviously bogus, not worth - // trying to relay through them anymore. - fail_channel_update = Some(msgs::HTLCFailChannelUpdate::NodeFailure { - node_id: route_hop.pubkey, - is_permanent: true, - }); - } - } else if !payment_failed { - // We can't understand their error messages and they failed to - // forward...they probably can't understand our forwards so its - // really not worth trying any further. - fail_channel_update = Some(msgs::HTLCFailChannelUpdate::NodeFailure { - node_id: route_hop.pubkey, - is_permanent: true, - }); - } - - // TODO: Here (and a few other places) we assume that BADONION errors - // are always "sourced" from the node previous to the one which failed - // to decode the onion. - res = Some((fail_channel_update, !(error_code & PERM == PERM && is_from_final_node))); - - let (description, title) = errors::get_onion_error_description(error_code); - if debug_field_size > 0 && err_packet.failuremsg.len() >= 4 + debug_field_size { - log_warn!(self, "Onion Error[{}({:#x}) {}({})] {}", title, error_code, debug_field, log_bytes!(&err_packet.failuremsg[4..4+debug_field_size]), description); - } - else { - log_warn!(self, "Onion Error[{}({:#x})] {}", title, error_code, description); - } - } else { - // Useless packet that we can't use but it passed HMAC, so it - // definitely came from the peer in question - res = Some((Some(msgs::HTLCFailChannelUpdate::NodeFailure { - node_id: route_hop.pubkey, - is_permanent: true, - }), !is_from_final_node)); - } - } - } - }).expect("Route that we sent via spontaneously grew invalid keys in the middle of it?"); - if let Some((channel_update, payment_retryable)) = res { - (channel_update, payment_retryable, error_code_ret) - } else { - // only not set either packet unparseable or hmac does not match with any - // payment not retryable only when garbage is from the final node - (None, !is_from_final_node, None) - } - } else { unreachable!(); } - } - fn internal_update_fail_htlc(&self, their_node_id: &PublicKey, msg: &msgs::UpdateFailHTLC) -> Result<(), MsgHandleErrInternal> { let mut channel_lock = self.channel_state.lock().unwrap(); let channel_state = channel_lock.borrow_parts(); diff --git a/src/ln/onion_utils.rs b/src/ln/onion_utils.rs index 85f98ffdd..97ec0ed9b 100644 --- a/src/ln/onion_utils.rs +++ b/src/ln/onion_utils.rs @@ -1,12 +1,14 @@ -use ln::channelmanager::PaymentHash; +use ln::channelmanager::{PaymentHash, HTLCSource}; use ln::msgs; use ln::router::{Route,RouteHop}; -use util::internal_traits; +use util::{byte_utils, internal_traits}; use util::chacha20::ChaCha20; -use util::errors::APIError; -use util::ser::Writeable; +use util::errors::{self, APIError}; +use util::ser::{Readable, Writeable}; +use util::logger::Logger; use bitcoin_hashes::{Hash, HashEngine}; +use bitcoin_hashes::cmp::fixed_time_eq; use bitcoin_hashes::hmac::{Hmac, HmacEngine}; use bitcoin_hashes::sha256::Hash as Sha256; @@ -16,6 +18,8 @@ use secp256k1::ecdh::SharedSecret; use secp256k1; use std::ptr; +use std::io::Cursor; +use std::sync::Arc; pub(super) struct OnionKeys { #[cfg(test)] @@ -261,6 +265,158 @@ pub(super) fn build_first_hop_failure_packet(shared_secret: &[u8], failure_type: encrypt_failure_packet(shared_secret, &failure_packet.encode()[..]) } +struct LogHolder<'a> { logger: &'a Arc } +/// Process failure we got back from upstream on a payment we sent (implying htlc_source is an +/// OutboundRoute). +/// Returns update, a boolean indicating that the payment itself failed, and the error code. +pub(super) fn process_onion_failure(secp_ctx: &Secp256k1, logger: &Arc, htlc_source: &HTLCSource, mut packet_decrypted: Vec) -> (Option, bool, Option) { + if let &HTLCSource::OutboundRoute { ref route, ref session_priv, ref first_hop_htlc_msat } = htlc_source { + let mut res = None; + let mut htlc_msat = *first_hop_htlc_msat; + let mut error_code_ret = None; + let mut next_route_hop_ix = 0; + let mut is_from_final_node = false; + + // Handle packed channel/node updates for passing back for the route handler + construct_onion_keys_callback(secp_ctx, route, session_priv, |shared_secret, _, _, route_hop| { + next_route_hop_ix += 1; + if res.is_some() { return; } + + let amt_to_forward = htlc_msat - route_hop.fee_msat; + htlc_msat = amt_to_forward; + + let ammag = gen_ammag_from_shared_secret(&shared_secret[..]); + + let mut decryption_tmp = Vec::with_capacity(packet_decrypted.len()); + decryption_tmp.resize(packet_decrypted.len(), 0); + let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]); + chacha.process(&packet_decrypted, &mut decryption_tmp[..]); + packet_decrypted = decryption_tmp; + + is_from_final_node = route.hops.last().unwrap().pubkey == route_hop.pubkey; + + if let Ok(err_packet) = msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) { + let um = gen_um_from_shared_secret(&shared_secret[..]); + let mut hmac = HmacEngine::::new(&um); + hmac.input(&err_packet.encode()[32..]); + + if fixed_time_eq(&Hmac::from_engine(hmac).into_inner(), &err_packet.hmac) { + if let Some(error_code_slice) = err_packet.failuremsg.get(0..2) { + const PERM: u16 = 0x4000; + const NODE: u16 = 0x2000; + const UPDATE: u16 = 0x1000; + + let error_code = byte_utils::slice_to_be16(&error_code_slice); + error_code_ret = Some(error_code); + + let (debug_field, debug_field_size) = errors::get_onion_debug_field(error_code); + + // indicate that payment parameter has failed and no need to + // update Route object + let payment_failed = (match error_code & 0xff { + 15|16|17|18|19 => true, + _ => false, + } && is_from_final_node) // PERM bit observed below even this error is from the intermediate nodes + || error_code == 21; // Special case error 21 as the Route object is bogus, TODO: Maybe fail the node if the CLTV was reasonable? + + let mut fail_channel_update = None; + + if error_code & NODE == NODE { + fail_channel_update = Some(msgs::HTLCFailChannelUpdate::NodeFailure { node_id: route_hop.pubkey, is_permanent: error_code & PERM == PERM }); + } + else if error_code & PERM == PERM { + fail_channel_update = if payment_failed {None} else {Some(msgs::HTLCFailChannelUpdate::ChannelClosed { + short_channel_id: route.hops[next_route_hop_ix - if next_route_hop_ix == route.hops.len() { 1 } else { 0 }].short_channel_id, + is_permanent: true, + })}; + } + else if error_code & UPDATE == UPDATE { + if let Some(update_len_slice) = err_packet.failuremsg.get(debug_field_size+2..debug_field_size+4) { + let update_len = byte_utils::slice_to_be16(&update_len_slice) as usize; + if let Some(update_slice) = err_packet.failuremsg.get(debug_field_size + 4..debug_field_size + 4 + update_len) { + if let Ok(chan_update) = msgs::ChannelUpdate::read(&mut Cursor::new(&update_slice)) { + // if channel_update should NOT have caused the failure: + // MAY treat the channel_update as invalid. + let is_chan_update_invalid = match error_code & 0xff { + 7 => false, + 11 => amt_to_forward > chan_update.contents.htlc_minimum_msat, + 12 => { + 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) }); + new_fee.is_some() && route_hop.fee_msat >= new_fee.unwrap() + } + 13 => route_hop.cltv_expiry_delta as u16 >= chan_update.contents.cltv_expiry_delta, + 14 => false, // expiry_too_soon; always valid? + 20 => chan_update.contents.flags & 2 == 0, + _ => false, // unknown error code; take channel_update as valid + }; + fail_channel_update = if is_chan_update_invalid { + // This probably indicates the node which forwarded + // to the node in question corrupted something. + Some(msgs::HTLCFailChannelUpdate::ChannelClosed { + short_channel_id: route_hop.short_channel_id, + is_permanent: true, + }) + } else { + Some(msgs::HTLCFailChannelUpdate::ChannelUpdateMessage { + msg: chan_update, + }) + }; + } + } + } + if fail_channel_update.is_none() { + // They provided an UPDATE which was obviously bogus, not worth + // trying to relay through them anymore. + fail_channel_update = Some(msgs::HTLCFailChannelUpdate::NodeFailure { + node_id: route_hop.pubkey, + is_permanent: true, + }); + } + } else if !payment_failed { + // We can't understand their error messages and they failed to + // forward...they probably can't understand our forwards so its + // really not worth trying any further. + fail_channel_update = Some(msgs::HTLCFailChannelUpdate::NodeFailure { + node_id: route_hop.pubkey, + is_permanent: true, + }); + } + + // TODO: Here (and a few other places) we assume that BADONION errors + // are always "sourced" from the node previous to the one which failed + // to decode the onion. + res = Some((fail_channel_update, !(error_code & PERM == PERM && is_from_final_node))); + + let (description, title) = errors::get_onion_error_description(error_code); + if debug_field_size > 0 && err_packet.failuremsg.len() >= 4 + debug_field_size { + let log_holder = LogHolder { logger }; + log_warn!(log_holder, "Onion Error[{}({:#x}) {}({})] {}", title, error_code, debug_field, log_bytes!(&err_packet.failuremsg[4..4+debug_field_size]), description); + } + else { + let log_holder = LogHolder { logger }; + log_warn!(log_holder, "Onion Error[{}({:#x})] {}", title, error_code, description); + } + } else { + // Useless packet that we can't use but it passed HMAC, so it + // definitely came from the peer in question + res = Some((Some(msgs::HTLCFailChannelUpdate::NodeFailure { + node_id: route_hop.pubkey, + is_permanent: true, + }), !is_from_final_node)); + } + } + } + }).expect("Route that we sent via spontaneously grew invalid keys in the middle of it?"); + if let Some((channel_update, payment_retryable)) = res { + (channel_update, payment_retryable, error_code_ret) + } else { + // only not set either packet unparseable or hmac does not match with any + // payment not retryable only when garbage is from the final node + (None, !is_from_final_node, None) + } + } else { unreachable!(); } +} + #[cfg(test)] mod tests { use ln::channelmanager::PaymentHash;