scion_stack/stack/scmp_handler/
echo.rs1use anyhow::Context as _;
17use sciparse::{
18 dataplane_path::view::ScionDpPathViewExt,
19 packet::{
20 model::{ScionRawPacket, ScionScmpPacket},
21 view::ScionRawPacketView,
22 },
23 payload::scmp::{
24 model::{ScmpEchoReply, ScmpMessage},
25 view::ScmpMessageView,
26 },
27};
28
29use super::ScmpHandler;
30
31pub struct DefaultEchoHandler;
34
35impl Default for DefaultEchoHandler {
36 fn default() -> Self {
37 Self
38 }
39}
40
41impl DefaultEchoHandler {
42 pub fn new() -> Self {
44 Self
45 }
46
47 fn try_echo_reply(
48 &self,
49 p_raw: &ScionRawPacketView,
50 ) -> anyhow::Result<Option<ScionScmpPacket>> {
51 let p = p_raw
52 .try_as_scmp()
53 .context("Packet is not a valid SCMP packet")?;
54
55 let reply_msg = match p.scmp().message() {
56 ScmpMessageView::EchoRequest(r) => {
57 tracing::debug!("Echo request received, sending echo reply");
58 ScmpMessage::EchoReply(ScmpEchoReply::new(
59 r.identifier(),
60 r.sequence_number(),
61 r.data().to_vec(),
62 ))
63 }
64 _ => return Ok(None),
65 };
66 let reply_path = p
67 .header()
68 .path()
69 .to_model()
70 .try_into_reversed()
71 .map_err(|(_, e)| anyhow::anyhow!("Failed to reverse path: {e:?}"))?;
72
73 let src = p
74 .src_scion_addr()
75 .context("Failed to decode source address")?;
76
77 let dst = p
78 .dst_scion_addr()
79 .context("Failed to decode destination address")?;
80
81 let reply = ScionScmpPacket::new(dst, src, reply_path, reply_msg);
82
83 Ok(Some(reply))
84 }
85}
86
87impl ScmpHandler for DefaultEchoHandler {
88 fn handle(&self, p_raw: &ScionRawPacketView) -> Option<ScionRawPacket> {
89 match self.try_echo_reply(p_raw) {
90 Ok(Some(reply)) => {
91 tracing::debug!(
92 src = ?reply.src_scion_addr(),
93 dst = ?reply.dst_scion_addr(),
94 "Sending echo reply"
95 );
96 Some(reply.into())
97 }
98 Ok(None) => None,
99 Err(e) => {
100 tracing::info!(error = %e, "Received invalid SCMP echo request");
101 None
102 }
103 }
104 }
105}
106
107#[cfg(test)]
108mod default_echo_handler_tests {
109 use sciparse::{
110 address::ip_addr::ScionIpAddr,
111 core::model::Model,
112 identifier::{asn::Asn, isd::Isd, isd_asn::IsdAsn},
113 payload::scmp::model::ScmpEchoRequest,
114 util::test_builder::{TestPathBuilder, TestPathContext},
115 };
116
117 use super::*;
118
119 fn test_context() -> TestPathContext {
120 let src = ScionIpAddr::new(IsdAsn::new(Isd(1), Asn(10)), [192, 0, 2, 1].into());
121 let dst = ScionIpAddr::new(IsdAsn::new(Isd(1), Asn(20)), [198, 51, 100, 1].into());
122 TestPathBuilder::new(src.into(), dst.into())
123 .using_info_timestamp(42)
124 .up()
125 .add_hop(0, 11)
126 .add_hop(12, 0)
127 .build(77)
128 }
129
130 #[test]
131 fn replies_to_echo_request() {
132 let ctx = test_context();
133 let expected_src = ctx.dst_address;
134 let expected_dst = ctx.src_address;
135 let request = ctx
136 .scion_packet_scmp(ScmpEchoRequest::new(7, 9, b"payload".to_vec()).into())
137 .into_raw()
138 .try_encode_to_owned_view()
139 .expect("should encode");
140
141 let handler = DefaultEchoHandler::new();
142 let reply = handler.handle(&request);
143 assert!(reply.is_some());
144 let reply = reply.unwrap();
145 let reply = reply
146 .try_into_scmp()
147 .expect("valid SCMP packet in returning");
148 match &reply.payload {
149 ScmpMessage::EchoReply(r) => {
150 assert_eq!(r.identifier, 7);
151 assert_eq!(r.sequence_number, 9);
152 assert_eq!(r.data, b"payload");
153 }
154 other => panic!("unexpected reply message: {other:?}"),
155 }
156 assert_eq!(reply.src_scion_addr().unwrap(), expected_src);
157 assert_eq!(reply.dst_scion_addr().unwrap(), expected_dst);
158 }
159
160 #[test]
161 fn ignores_non_echo_messages() {
162 let ctx = test_context();
163 let handler = DefaultEchoHandler::new();
164
165 let non_echo = ctx
166 .scion_packet_scmp(ScmpEchoReply::new(1, 2, b"resp".to_vec()).into())
167 .into_raw()
168 .try_encode_to_owned_view()
169 .expect("should encode");
170
171 let reply = handler.handle(&non_echo);
172 assert!(reply.is_none());
173 }
174
175 #[test]
176 fn ignores_packets_that_fail_decoding() {
177 let ctx = test_context();
178 let handler = DefaultEchoHandler::new();
179
180 let wrong_protocol = ctx
181 .scion_packet_raw(b"not scmp")
182 .try_encode_to_owned_view()
183 .expect("should encode");
184 let reply = handler.handle(&wrong_protocol);
185 assert!(reply.is_none());
186 }
187}