1use crate::{MacAddress, error::FormatError};
2
3#[derive(Debug, Clone, Copy)]
4pub enum PingOutcome {
5 HostDidntRespondToPingMethods { tcp: bool, icmp: bool },
10 IcmpEchoPacket,
12 HostLocalNetworkDidntRespondArpWhoIsQuery,
15 ArpWhoIsQuery(MacAddress),
16 IcmpUnreachPacketInResponseToTcpSynPacket { to: u16 },
18 RepliedTcpSynPacketWithRstAck { to: u16 },
20 RepliedTcpSynPacketWithSynAck { to: u16 },
22 RepliedTcpSynPacketWithFinAck { to: u16 },
24 EmittedTcpSynPacket { from: u16, to: u16 },
26 EmittedTcpAckPacket { from: u16, to: u16 },
28 EmittedTcpPushAckPacket { from: u16, to: u16 },
30 IcmpUnreachPacket,
32 EmittedUdpPacket { from: u16, to: u16 },
34 LocalScanner,
36}
37
38impl PingOutcome {
39 pub(crate) fn from_plugin_output(plugin_output: &str) -> Result<Self, FormatError> {
40 const LOCAL_SCANNER: &str = "The remote host is up\nThe host is the local scanner.";
41 const ICMP_ECHO_PACKET: &str =
42 "The remote host is up\nThe remote host replied to an ICMP echo packet";
43 const ICMP_UNREACH_PACKET: &str =
44 "The remote host is up\nThe remote host replied with an ICMP unreach packet.";
45 const ARP_WHO_IS_QUERY_PREFIX: &str =
46 "The remote host is up\nThe host replied to an ARP who-is query.\nHardware address : ";
47 const REPLIED_TCP_SYN_PACKET_PREFIX: &str =
48 "The remote host is up\nThe remote host replied to a TCP SYN packet sent to port ";
49 const WITH_RST_ACK_SUFFIX: &str = " with a RST,ACK packet";
50 const WITH_SYN_ACK_SUFFIX: &str = " with a SYN,ACK packet";
51 const WITH_FIN_ACK_SUFFIX: &str = " with a FIN,ACK packet";
52 const ICMP_UNREACH_PACKET_RESPONSE_TO_TCP_SYN_PACKET_PREFIX: &str = "The remote host is up\nThe remote host replied with an ICMP unreach packet sent in response to a TCP SYN packet sent to port ";
53 const EMITTED_UDP_PACKET_PREFIX: &str =
54 "The remote host is up\nThe remote host emited a UDP packet from port ";
55 const EMITTED_PACKET_MIDDLE: &str = "going to port ";
56 const EMITTED_TCP_SYN_PACKET_PREFIX: &str =
57 "The remote host is up\nThe remote host emitted a TCP SYN packet from port ";
58 const EMITTED_TCP_ACK_PACKET_PREFIX: &str =
59 "The remote host is up\nThe remote host emitted a TCP ACK packet from port ";
60 const EMITTED_TCP_PUSH_ACK_PACKET_PREFIX: &str =
61 "The remote host is up\nThe remote host emitted a TCP PUSH,ACK packet from port ";
62 const DEAD_HOST_NEEDLE: &str = ") is considered as dead - not scanning\nThe remote host (";
63 const FAILED_TO_REPLY_ARP_SUFFIX: &str =
64 ") is on the local network and failed to reply to an ARP who-is query.";
65 const DIDNT_RESPOND_TO_PING_METHODS_NEEDLE: &str =
66 ") did not respond to the following ping methods :\n";
67
68 match plugin_output {
69 ICMP_ECHO_PACKET => Ok(Self::IcmpEchoPacket),
70 LOCAL_SCANNER => Ok(Self::LocalScanner),
71 ICMP_UNREACH_PACKET => Ok(Self::IcmpUnreachPacket),
72 text => {
73 if text.contains(DEAD_HOST_NEEDLE) {
74 if text.ends_with(FAILED_TO_REPLY_ARP_SUFFIX) {
75 Ok(Self::HostLocalNetworkDidntRespondArpWhoIsQuery)
76 } else if let Some((_, ping_methods)) =
77 text.split_once(DIDNT_RESPOND_TO_PING_METHODS_NEEDLE)
78 {
79 let mut tcp = false;
80 let mut icmp = false;
81 for ping_method in ping_methods.trim_end().split('\n') {
82 match ping_method {
83 "- TCP ping" => tcp = true,
84 "- ICMP ping" => icmp = true,
85 _ => {
86 return Err(FormatError::UnexpectedPingMethod(
87 plugin_output.into(),
88 ));
89 }
90 }
91 }
92 if !tcp && !icmp {
93 Err(FormatError::UnexpectedPingFormat(plugin_output.into()))
94 } else {
95 Ok(Self::HostDidntRespondToPingMethods { tcp, icmp })
96 }
97 } else {
98 Err(FormatError::UnexpectedDeadHostReason(plugin_output.into()))
99 }
100 } else if let Some(mac) = text.strip_prefix(ARP_WHO_IS_QUERY_PREFIX) {
101 Ok(Self::ArpWhoIsQuery(mac.parse()?))
102 } else if let Some(port) =
103 text.strip_prefix(ICMP_UNREACH_PACKET_RESPONSE_TO_TCP_SYN_PACKET_PREFIX)
104 {
105 Ok(Self::IcmpUnreachPacketInResponseToTcpSynPacket { to: port.parse()? })
106 } else if let Some(port_and_suffix) =
107 text.strip_prefix(REPLIED_TCP_SYN_PACKET_PREFIX)
108 {
109 if let Some(port) = port_and_suffix.strip_suffix(WITH_RST_ACK_SUFFIX) {
110 Ok(Self::RepliedTcpSynPacketWithRstAck { to: port.parse()? })
111 } else if let Some(port) = port_and_suffix.strip_suffix(WITH_SYN_ACK_SUFFIX) {
112 Ok(Self::RepliedTcpSynPacketWithSynAck { to: port.parse()? })
113 } else if let Some(port) = port_and_suffix.strip_suffix(WITH_FIN_ACK_SUFFIX) {
114 Ok(Self::RepliedTcpSynPacketWithFinAck { to: port.parse()? })
115 } else {
116 Err(FormatError::UnexpectedPingTcpResponse(plugin_output.into()))
117 }
118 } else if let Some(from_port_and_rest) =
119 text.strip_prefix(EMITTED_TCP_SYN_PACKET_PREFIX)
120 && let Some((from_port, rest)) = from_port_and_rest.split_once(' ')
121 && let Some(to_port) = rest.strip_prefix(EMITTED_PACKET_MIDDLE)
122 {
123 Ok(Self::EmittedTcpSynPacket {
124 from: from_port.parse()?,
125 to: to_port.parse()?,
126 })
127 } else if let Some(from_port_and_rest) =
129 text.strip_prefix(EMITTED_TCP_ACK_PACKET_PREFIX)
130 && let Some((from_port, rest)) = from_port_and_rest.split_once(' ')
131 && let Some(to_port) = rest.strip_prefix(EMITTED_PACKET_MIDDLE)
132 {
133 Ok(Self::EmittedTcpAckPacket {
134 from: from_port.parse()?,
135 to: to_port.parse()?,
136 })
137 } else if let Some(from_port_and_rest) =
138 text.strip_prefix(EMITTED_TCP_PUSH_ACK_PACKET_PREFIX)
139 && let Some((from_port, rest)) = from_port_and_rest.split_once(' ')
140 && let Some(to_port) = rest.strip_prefix(EMITTED_PACKET_MIDDLE)
141 {
142 Ok(Self::EmittedTcpAckPacket {
143 from: from_port.parse()?,
144 to: to_port.parse()?,
145 })
146 } else if let Some(from_port_and_rest) =
147 text.strip_prefix(EMITTED_UDP_PACKET_PREFIX)
148 && let Some((from_port, rest)) = from_port_and_rest.split_once(' ')
149 && let Some(to_port) = rest.strip_prefix(EMITTED_PACKET_MIDDLE)
150 {
151 Ok(Self::EmittedUdpPacket {
152 from: from_port.parse()?,
153 to: to_port.parse()?,
154 })
155 } else {
156 Err(FormatError::UnexpectedPingFormat(plugin_output.into()))
157 }
158 }
159 }
160 }
161}