-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;
use secp256k1;
use std::ptr;
+use std::io::Cursor;
+use std::sync::Arc;
pub(super) struct OnionKeys {
#[cfg(test)]
encrypt_failure_packet(shared_secret, &failure_packet.encode()[..])
}
+struct LogHolder<'a> { logger: &'a Arc<Logger> }
+/// 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<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, logger: &Arc<Logger>, htlc_source: &HTLCSource, mut packet_decrypted: Vec<u8>) -> (Option<msgs::HTLCFailChannelUpdate>, bool, Option<u16>) {
+ 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::<Sha256>::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;