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