Skip to content

Commit a82067d

Browse files
authored
Merge pull request #1013 from TheBlueMatt/2021-07-warning-msgs
2 parents bb24861 + d786bfa commit a82067d

11 files changed

+231
-76
lines changed

fuzz/src/msg_targets/gen_target.sh

+1
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@ GEN_TEST QueryShortChannelIds test_msg ""
4343
GEN_TEST ReplyChannelRange test_msg ""
4444

4545
GEN_TEST ErrorMessage test_msg_hole ", 32, 2"
46+
GEN_TEST WarningMessage test_msg_hole ", 32, 2"
4647
GEN_TEST ChannelUpdate test_msg_hole ", 108, 1"

fuzz/src/msg_targets/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ pub mod msg_node_announcement;
2828
pub mod msg_query_short_channel_ids;
2929
pub mod msg_reply_channel_range;
3030
pub mod msg_error_message;
31+
pub mod msg_warning_message;
3132
pub mod msg_channel_update;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
// This file is auto-generated by gen_target.sh based on msg_target_template.txt
11+
// To modify it, modify msg_target_template.txt and run gen_target.sh instead.
12+
13+
use lightning::ln::msgs;
14+
15+
use msg_targets::utils::VecWriter;
16+
use utils::test_logger;
17+
18+
#[inline]
19+
pub fn msg_warning_message_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
20+
test_msg_hole!(msgs::WarningMessage, data, 32, 2);
21+
}
22+
23+
#[no_mangle]
24+
pub extern "C" fn msg_warning_message_run(data: *const u8, datalen: usize) {
25+
let data = unsafe { std::slice::from_raw_parts(data, datalen) };
26+
test_msg_hole!(msgs::WarningMessage, data, 32, 2);
27+
}

lightning/src/ln/channel.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -3723,12 +3723,12 @@ impl<Signer: Sign> Channel<Signer> {
37233723
assert_eq!(self.channel_state & ChannelState::ShutdownComplete as u32, 0);
37243724

37253725
if !script::is_bolt2_compliant(&msg.scriptpubkey, their_features) {
3726-
return Err(ChannelError::Close(format!("Got a nonstandard scriptpubkey ({}) from remote peer", msg.scriptpubkey.to_bytes().to_hex())));
3726+
return Err(ChannelError::Warn(format!("Got a nonstandard scriptpubkey ({}) from remote peer", msg.scriptpubkey.to_bytes().to_hex())));
37273727
}
37283728

37293729
if self.counterparty_shutdown_scriptpubkey.is_some() {
37303730
if Some(&msg.scriptpubkey) != self.counterparty_shutdown_scriptpubkey.as_ref() {
3731-
return Err(ChannelError::Close(format!("Got shutdown request with a scriptpubkey ({}) which did not match their previous scriptpubkey.", msg.scriptpubkey.to_bytes().to_hex())));
3731+
return Err(ChannelError::Warn(format!("Got shutdown request with a scriptpubkey ({}) which did not match their previous scriptpubkey.", msg.scriptpubkey.to_bytes().to_hex())));
37323732
}
37333733
} else {
37343734
self.counterparty_shutdown_scriptpubkey = Some(msg.scriptpubkey.clone());

lightning/src/ln/channelmanager.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -613,8 +613,14 @@ impl MsgHandleErrInternal {
613613
Self {
614614
err: match err {
615615
ChannelError::Warn(msg) => LightningError {
616-
err: msg,
617-
action: msgs::ErrorAction::IgnoreError,
616+
err: msg.clone(),
617+
action: msgs::ErrorAction::SendWarningMessage {
618+
msg: msgs::WarningMessage {
619+
channel_id,
620+
data: msg
621+
},
622+
log_level: Level::Warn,
623+
},
618624
},
619625
ChannelError::Ignore(msg) => LightningError {
620626
err: msg,
@@ -1373,9 +1379,7 @@ macro_rules! convert_chan_err {
13731379
($self: ident, $err: expr, $short_to_id: expr, $channel: expr, $channel_id: expr) => {
13741380
match $err {
13751381
ChannelError::Warn(msg) => {
1376-
//TODO: Once warning messages are merged, we should send a `warning` message to our
1377-
//peer here.
1378-
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Ignore(msg), $channel_id.clone()))
1382+
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Warn(msg), $channel_id.clone()))
13791383
},
13801384
ChannelError::Ignore(msg) => {
13811385
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Ignore(msg), $channel_id.clone()))

lightning/src/ln/functional_test_utils.rs

+16
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,22 @@ macro_rules! get_closing_signed_broadcast {
780780
}
781781
}
782782

783+
#[cfg(test)]
784+
macro_rules! check_warn_msg {
785+
($node: expr, $recipient_node_id: expr, $chan_id: expr) => {{
786+
let msg_events = $node.node.get_and_clear_pending_msg_events();
787+
assert_eq!(msg_events.len(), 1);
788+
match msg_events[0] {
789+
MessageSendEvent::HandleError { action: ErrorAction::SendWarningMessage { ref msg, log_level: _ }, node_id } => {
790+
assert_eq!(node_id, $recipient_node_id);
791+
assert_eq!(msg.channel_id, $chan_id);
792+
msg.data.clone()
793+
},
794+
_ => panic!("Unexpected event"),
795+
}
796+
}}
797+
}
798+
783799
/// Check that a channel's closing channel update has been broadcasted, and optionally
784800
/// check whether an error message event has occurred.
785801
#[macro_export]

lightning/src/ln/msgs.rs

+75-10
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use bitcoin::hash_types::{Txid, BlockHash};
3333
use ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
3434

3535
use prelude::*;
36-
use core::{cmp, fmt};
36+
use core::fmt;
3737
use core::fmt::Debug;
3838
use io::{self, Read};
3939
use io_extras::read_to_end;
@@ -80,12 +80,29 @@ pub struct Init {
8080
/// An error message to be sent or received from a peer
8181
#[derive(Clone, Debug, PartialEq)]
8282
pub struct ErrorMessage {
83-
/// The channel ID involved in the error
83+
/// The channel ID involved in the error.
84+
///
85+
/// All-0s indicates a general error unrelated to a specific channel, after which all channels
86+
/// with the sending peer should be closed.
8487
pub channel_id: [u8; 32],
8588
/// A possibly human-readable error description.
86-
/// The string should be sanitized before it is used (e.g. emitted to logs
87-
/// or printed to stdout). Otherwise, a well crafted error message may trigger a security
88-
/// vulnerability in the terminal emulator or the logging subsystem.
89+
/// The string should be sanitized before it is used (e.g. emitted to logs or printed to
90+
/// stdout). Otherwise, a well crafted error message may trigger a security vulnerability in
91+
/// the terminal emulator or the logging subsystem.
92+
pub data: String,
93+
}
94+
95+
/// A warning message to be sent or received from a peer
96+
#[derive(Clone, Debug, PartialEq)]
97+
pub struct WarningMessage {
98+
/// The channel ID involved in the warning.
99+
///
100+
/// All-0s indicates a warning unrelated to a specific channel.
101+
pub channel_id: [u8; 32],
102+
/// A possibly human-readable warning description.
103+
/// The string should be sanitized before it is used (e.g. emitted to logs or printed to
104+
/// stdout). Otherwise, a well crafted error message may trigger a security vulnerability in
105+
/// the terminal emulator or the logging subsystem.
89106
pub data: String,
90107
}
91108

@@ -714,7 +731,16 @@ pub enum ErrorAction {
714731
/// The peer did something incorrect. Tell them.
715732
SendErrorMessage {
716733
/// The message to send.
717-
msg: ErrorMessage
734+
msg: ErrorMessage,
735+
},
736+
/// The peer did something incorrect. Tell them without closing any channels.
737+
SendWarningMessage {
738+
/// The message to send.
739+
msg: WarningMessage,
740+
/// The peer may have done something harmless that we weren't able to meaningfully process,
741+
/// though we should still tell them about it.
742+
/// If this event is logged, log it at the given level.
743+
log_level: logger::Level,
718744
},
719745
}
720746

@@ -1503,10 +1529,38 @@ impl Readable for ErrorMessage {
15031529
Ok(Self {
15041530
channel_id: Readable::read(r)?,
15051531
data: {
1506-
let mut sz: usize = <u16 as Readable>::read(r)? as usize;
1507-
let data = read_to_end(r)?;
1508-
sz = cmp::min(data.len(), sz);
1509-
match String::from_utf8(data[..sz as usize].to_vec()) {
1532+
let sz: usize = <u16 as Readable>::read(r)? as usize;
1533+
let mut data = Vec::with_capacity(sz);
1534+
data.resize(sz, 0);
1535+
r.read_exact(&mut data)?;
1536+
match String::from_utf8(data) {
1537+
Ok(s) => s,
1538+
Err(_) => return Err(DecodeError::InvalidValue),
1539+
}
1540+
}
1541+
})
1542+
}
1543+
}
1544+
1545+
impl Writeable for WarningMessage {
1546+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
1547+
self.channel_id.write(w)?;
1548+
(self.data.len() as u16).write(w)?;
1549+
w.write_all(self.data.as_bytes())?;
1550+
Ok(())
1551+
}
1552+
}
1553+
1554+
impl Readable for WarningMessage {
1555+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
1556+
Ok(Self {
1557+
channel_id: Readable::read(r)?,
1558+
data: {
1559+
let sz: usize = <u16 as Readable>::read(r)? as usize;
1560+
let mut data = Vec::with_capacity(sz);
1561+
data.resize(sz, 0);
1562+
r.read_exact(&mut data)?;
1563+
match String::from_utf8(data) {
15101564
Ok(s) => s,
15111565
Err(_) => return Err(DecodeError::InvalidValue),
15121566
}
@@ -2405,6 +2459,17 @@ mod tests {
24052459
assert_eq!(encoded_value, target_value);
24062460
}
24072461

2462+
#[test]
2463+
fn encoding_warning() {
2464+
let error = msgs::WarningMessage {
2465+
channel_id: [2; 32],
2466+
data: String::from("rust-lightning"),
2467+
};
2468+
let encoded_value = error.encode();
2469+
let target_value = hex::decode("0202020202020202020202020202020202020202020202020202020202020202000e727573742d6c696768746e696e67").unwrap();
2470+
assert_eq!(encoded_value, target_value);
2471+
}
2472+
24082473
#[test]
24092474
fn encoding_ping() {
24102475
let ping = msgs::Ping {

lightning/src/ln/peer_handler.rs

+42-10
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,11 @@ impl<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, L: Deref, CMH: Deref> P
821821
self.enqueue_message(peer, &msg);
822822
continue;
823823
},
824+
msgs::ErrorAction::SendWarningMessage { msg, log_level } => {
825+
log_given_level!(self.logger, log_level, "Error handling message{}; sending warning message with: {}", OptionalFromDebugger(&peer.their_node_id), e.err);
826+
self.enqueue_message(peer, &msg);
827+
continue;
828+
},
824829
}
825830
}
826831
}
@@ -897,25 +902,31 @@ impl<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, L: Deref, CMH: Deref> P
897902
Ok(x) => x,
898903
Err(e) => {
899904
match e {
900-
msgs::DecodeError::UnknownVersion => return Err(PeerHandleError { no_connection_possible: false }),
901-
msgs::DecodeError::UnknownRequiredFeature => {
905+
(msgs::DecodeError::UnknownRequiredFeature, _) => {
902906
log_gossip!(self.logger, "Got a channel/node announcement with an unknown required feature flag, you may want to update!");
903907
continue;
904908
}
905-
msgs::DecodeError::InvalidValue => {
909+
(msgs::DecodeError::UnsupportedCompression, _) => {
910+
log_gossip!(self.logger, "We don't support zlib-compressed message fields, sending a warning and ignoring message");
911+
self.enqueue_message(peer, &msgs::WarningMessage { channel_id: [0; 32], data: "Unsupported message compression: zlib".to_owned() });
912+
continue;
913+
}
914+
(_, Some(ty)) if is_gossip_msg(ty) => {
915+
log_gossip!(self.logger, "Got an invalid value while deserializing a gossip message");
916+
self.enqueue_message(peer, &msgs::WarningMessage { channel_id: [0; 32], data: "Unreadable/bogus gossip message".to_owned() });
917+
continue;
918+
}
919+
(msgs::DecodeError::UnknownVersion, _) => return Err(PeerHandleError { no_connection_possible: false }),
920+
(msgs::DecodeError::InvalidValue, _) => {
906921
log_debug!(self.logger, "Got an invalid value while deserializing message");
907922
return Err(PeerHandleError { no_connection_possible: false });
908923
}
909-
msgs::DecodeError::ShortRead => {
924+
(msgs::DecodeError::ShortRead, _) => {
910925
log_debug!(self.logger, "Deserialization failed due to shortness of message");
911926
return Err(PeerHandleError { no_connection_possible: false });
912927
}
913-
msgs::DecodeError::BadLengthDescriptor => return Err(PeerHandleError { no_connection_possible: false }),
914-
msgs::DecodeError::Io(_) => return Err(PeerHandleError { no_connection_possible: false }),
915-
msgs::DecodeError::UnsupportedCompression => {
916-
log_gossip!(self.logger, "We don't support zlib-compressed message fields, ignoring message");
917-
continue;
918-
}
928+
(msgs::DecodeError::BadLengthDescriptor, _) => return Err(PeerHandleError { no_connection_possible: false }),
929+
(msgs::DecodeError::Io(_), _) => return Err(PeerHandleError { no_connection_possible: false }),
919930
}
920931
}
921932
};
@@ -1022,6 +1033,21 @@ impl<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, L: Deref, CMH: Deref> P
10221033
return Err(PeerHandleError{ no_connection_possible: true }.into());
10231034
}
10241035
},
1036+
wire::Message::Warning(msg) => {
1037+
let mut data_is_printable = true;
1038+
for b in msg.data.bytes() {
1039+
if b < 32 || b > 126 {
1040+
data_is_printable = false;
1041+
break;
1042+
}
1043+
}
1044+
1045+
if data_is_printable {
1046+
log_debug!(self.logger, "Got warning message from {}: {}", log_pubkey!(peer.their_node_id.unwrap()), msg.data);
1047+
} else {
1048+
log_debug!(self.logger, "Got warning message from {} with non-ASCII error message", log_pubkey!(peer.their_node_id.unwrap()));
1049+
}
1050+
},
10251051

10261052
wire::Message::Ping(msg) => {
10271053
if msg.ponglen < 65532 {
@@ -1419,6 +1445,12 @@ impl<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, L: Deref, CMH: Deref> P
14191445
msg.data);
14201446
self.enqueue_message(get_peer_for_forwarding!(node_id), msg);
14211447
},
1448+
msgs::ErrorAction::SendWarningMessage { ref msg, ref log_level } => {
1449+
log_given_level!(self.logger, *log_level, "Handling SendWarningMessage HandleError event in peer_handler for node {} with message {}",
1450+
log_pubkey!(node_id),
1451+
msg.data);
1452+
self.enqueue_message(get_peer_for_forwarding!(node_id), msg);
1453+
},
14221454
}
14231455
},
14241456
MessageSendEvent::SendChannelRangeQuery { ref node_id, ref msg } => {

lightning/src/ln/shutdown_tests.rs

+15-31
Original file line numberDiff line numberDiff line change
@@ -415,13 +415,17 @@ fn test_upfront_shutdown_script() {
415415
let flags = InitFeatures::known();
416416
let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 1000000, 1000000, flags.clone(), flags.clone());
417417
nodes[0].node.close_channel(&OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id()).unwrap();
418-
let mut node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[2].node.get_our_node_id());
418+
let node_0_orig_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[2].node.get_our_node_id());
419+
let mut node_0_shutdown = node_0_orig_shutdown.clone();
419420
node_0_shutdown.scriptpubkey = Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script().to_p2sh();
420-
// Test we enforce upfront_scriptpbukey if by providing a diffrent one at closing that we disconnect peer
421+
// Test we enforce upfront_scriptpbukey if by providing a different one at closing that we warn
422+
// the peer and ignore the message.
421423
nodes[2].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &InitFeatures::known(), &node_0_shutdown);
422-
assert!(regex::Regex::new(r"Got shutdown request with a scriptpubkey \([A-Fa-f0-9]+\) which did not match their previous scriptpubkey.").unwrap().is_match(check_closed_broadcast!(nodes[2], true).unwrap().data.as_str()));
423-
check_closed_event!(nodes[2], 1, ClosureReason::ProcessingError { err: "Got shutdown request with a scriptpubkey (a91441c98a140039816273e50db317422c11c2bfcc8887) which did not match their previous scriptpubkey.".to_string() });
424-
check_added_monitors!(nodes[2], 1);
424+
assert!(regex::Regex::new(r"Got shutdown request with a scriptpubkey \([A-Fa-f0-9]+\) which did not match their previous scriptpubkey.")
425+
.unwrap().is_match(&check_warn_msg!(nodes[2], nodes[0].node.get_our_node_id(), chan.2)));
426+
// This allows nodes[2] to retry the shutdown message, which should get a response:
427+
nodes[2].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &InitFeatures::known(), &node_0_orig_shutdown);
428+
get_event_msg!(nodes[2], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
425429

426430
// We test that in case of peer committing upfront to a script, if it doesn't change at closing, we sign
427431
let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 1000000, 1000000, flags.clone(), flags.clone());
@@ -668,17 +672,8 @@ fn test_unsupported_anysegwit_shutdown_script() {
668672
node_0_shutdown.scriptpubkey = unsupported_shutdown_script.into_inner();
669673
nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_cfgs[1].features, &node_0_shutdown);
670674

671-
let events = nodes[0].node.get_and_clear_pending_msg_events();
672-
assert_eq!(events.len(), 2);
673-
match events[1] {
674-
MessageSendEvent::HandleError { action: ErrorAction::SendErrorMessage { ref msg }, node_id } => {
675-
assert_eq!(node_id, nodes[1].node.get_our_node_id());
676-
assert_eq!(msg.data, "Got a nonstandard scriptpubkey (60020028) from remote peer".to_owned());
677-
},
678-
_ => panic!("Unexpected event"),
679-
}
680-
check_added_monitors!(nodes[0], 1);
681-
check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: "Got a nonstandard scriptpubkey (60020028) from remote peer".to_string() });
675+
assert_eq!(&check_warn_msg!(nodes[0], nodes[1].node.get_our_node_id(), chan.2),
676+
"Got a nonstandard scriptpubkey (60020028) from remote peer");
682677
}
683678

684679
#[test]
@@ -704,17 +699,8 @@ fn test_invalid_shutdown_script() {
704699
.into_script();
705700
nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &InitFeatures::known(), &node_0_shutdown);
706701

707-
let events = nodes[0].node.get_and_clear_pending_msg_events();
708-
assert_eq!(events.len(), 2);
709-
match events[1] {
710-
MessageSendEvent::HandleError { action: ErrorAction::SendErrorMessage { ref msg }, node_id } => {
711-
assert_eq!(node_id, nodes[1].node.get_our_node_id());
712-
assert_eq!(msg.data, "Got a nonstandard scriptpubkey (00020000) from remote peer".to_owned())
713-
},
714-
_ => panic!("Unexpected event"),
715-
}
716-
check_added_monitors!(nodes[0], 1);
717-
check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: "Got a nonstandard scriptpubkey (00020000) from remote peer".to_string() });
702+
assert_eq!(&check_warn_msg!(nodes[0], nodes[1].node.get_our_node_id(), chan.2),
703+
"Got a nonstandard scriptpubkey (00020000) from remote peer");
718704
}
719705

720706
#[derive(PartialEq)]
@@ -762,10 +748,8 @@ fn do_test_closing_signed_reinit_timeout(timeout_step: TimeoutStep) {
762748

763749
if timeout_step != TimeoutStep::AfterShutdown {
764750
nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_closing_signed);
765-
// At this point nodes[1] should send back a warning message indicating it disagrees with the
766-
// given channel-closing fee. Currently we do not implement warning messages so instead we
767-
// remain silent here.
768-
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
751+
assert!(check_warn_msg!(nodes[1], nodes[0].node.get_our_node_id(), chan_id)
752+
.starts_with("Unable to come to consensus about closing feerate"));
769753

770754
// Now deliver a mutated closing_signed indicating a higher acceptable fee range, which
771755
// nodes[1] should happily accept and respond to.

0 commit comments

Comments
 (0)