Update pingcheck to work with cmake 3.28
[pingcheck] / src / icmp / icmppacketfactory.cpp
... / ...
CommitLineData
1/*
2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
4
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
7
8 As a special exception, if other files instantiate templates or use macros
9 or inline functions from this file, or you compile this file and link it
10 with other works to produce a work based on this file, this file
11 does not by itself cause the resulting work to be covered
12 by the GNU General Public License.
13
14 However the source code for this file must still be made available
15 in accordance with section (3) of the GNU General Public License.
16
17 This exception does not invalidate any other reasons why a work based
18 on this file might be covered by the GNU General Public License.
19
20 Christian Herdtweck, Intra2net AG 2015
21 Based on an example in Boost Documentation (by Christopher M. Kohlhoff)
22 and adaptation by Guilherme M. Ferreira
23 */
24
25#include "icmp/icmppacketfactory.h"
26
27// for dumping packets
28#include <cstring>
29#include <cstdlib>
30#include <ctime>
31#include <iostream>
32#include <sstream>
33#include <boost/scoped_array.hpp>
34
35#include <logfunc.hpp>
36#include <filefunc.hxx>
37#include <tmpfstream.hpp>
38
39#include "boost_assert_handler.h"
40#include "icmp/icmpdata.h"
41#include "icmp/icmpheader.h"
42#include "icmp/icmptype.h"
43#include "icmp/icmpechodata.h"
44#include "tools/pcap.h"
45
46using namespace std;
47using boost::asio::ip::icmp;
48using I2n::Logger::GlobalLogger;
49
50//-----------------------------------------------------------------------------
51// IcmpPacketFactory
52//-----------------------------------------------------------------------------
53
54// set default value
55DumpMode IcmpPacketFactory::PacketDumpMode = DUMP_IF_ERROR;
56std::string IcmpPacketFactory::DumpFilePrefix =
57 "/datastore/pingcheck.broken/icmp_";
58
59/**
60 * @brief Creates an ICMP packet from the input stream @c std::istream.
61 *
62 * @param protocol The packet's network layer protocol, IPv4 or IPv6.
63 * @param is The input stream.
64 *
65 * @return An ICMP Packet object.
66 */
67IcmpPacketItem IcmpPacketFactory::create_icmp_packet(
68 const icmp::socket::protocol_type &protocol,
69 istream &is
70)
71{
72 IcmpPacketItem icmp_packet;
73
74 if ( icmp::v4() == protocol || icmp::v6() == protocol )
75 icmp_packet.reset( new IcmpPacket( protocol ) );
76 else
77 {
78 GlobalLogger.warning() << "ICMP packet creation failed: "
79 << "Unknown protocol arg, expect ICMP v4 or v6!" << endl;
80 icmp_packet.reset(); // --> (!icmp_packet) is true
81 return icmp_packet;
82 }
83
84 IcmpPacket::ReadReturnCode return_code;
85
86 // create buffer for saving data in it
87 stringbuf data_backup;
88 bool have_backup = false;
89
90 // create backup and parse data
91 try
92 {
93 // read packet from stream, possibly copying data first
94 if (PacketDumpMode != DUMP_NEVER)
95 {
96 // read all data into backup
97 ostream backup_filler(&data_backup);
98 backup_filler << is.rdbuf();
99
100 // create a new stream from backup buffer
101 // and use that in read
102 istream is_2(&data_backup);
103 have_backup = true;
104 return_code = icmp_packet->read( is_2 );
105 }
106 else
107 return_code = icmp_packet->read( is );
108
109 if ( return_code != IcmpPacket::ReadReturnCode_OK )
110 {
111 GlobalLogger.warning() << "ICMP packet creation failed: "
112 << IcmpPacket::return_code_to_string(return_code) << endl;
113 icmp_packet.reset(); // --> (!icmp_packet) is true
114 // --> icmp_pinger will not try to continue working with this packet
115 }
116 else if ( !is.good() )
117 {
118 GlobalLogger.warning() << "ICMP packet creation failed: "
119 << "Stream not good at end of creation!" << endl;
120 icmp_packet.reset(); // --> (!icmp_packet) is true
121 }
122
123 // print end result within try-catch because to_string might also
124 // throw exception
125 if (icmp_packet)
126 GlobalLogger.debug() << "Read packet " << icmp_packet->to_string()
127 << endl;
128 else
129 GlobalLogger.debug() << "Read packet failed" << endl;
130
131 }
132 catch ( const std::exception &ex )
133 {
134 GlobalLogger.info() << "Exception during ICMP parse: " << ex.what()
135 << std::endl;
136 icmp_packet.reset();
137 }
138 catch ( ... )
139 {
140 GlobalLogger.info() << "Exception during ICMP parse." << std::endl;
141 icmp_packet.reset();
142 }
143
144 // dump data if had trouble with packet or dumping is set to always
145 if ( PacketDumpMode == DUMP_ALWAYS ||
146 ( PacketDumpMode == DUMP_IF_ERROR && !icmp_packet ) )
147 {
148 if ( have_backup )
149 dump_packet(data_backup.str());
150 else
151 GlobalLogger.warning() << "Would like to dump packet but "
152 << "trouble occurred before backup was created!";
153 }
154
155 return icmp_packet;
156}
157
158/**
159 * @brief Creates an ICMP Echo Request packet.
160 *
161 * @param protocol The packet's network layer protocol, IPv4 or IPv6.
162 * @param identifier The packet's identifier number to aid in matching Echo
163 * Replies to this Echo Request. May be zero.
164 * @param sequence_number The packet's sequence number to aid in matching Echo
165 * Replies to this Echo Request. May be zero.
166 *
167 * @return An ICMP Echo Request packet object.
168 */
169IcmpPacketItem IcmpPacketFactory::create_icmp_packet_echo_request(
170 const icmp::socket::protocol_type &protocol,
171 const uint16_t identifier,
172 const uint16_t sequence_number
173)
174{
175 BOOST_ASSERT( (icmp::v4() == protocol) || (icmp::v6() == protocol) );
176
177 uint8_t type;
178 if ( icmp::v4() == protocol )
179 type = static_cast<uint8_t>(Icmpv4Type_EchoRequest);
180 else if ( icmp::v6() == protocol )
181 type = static_cast<uint8_t>(Icmpv6Type_EchoRequest);
182 // other case caught be BOOST_ASSERT above
183
184 uint8_t code = 0;
185 IcmpHeader icmp_header( type, code );
186
187 IcmpDataPtr icmp_data( new IcmpEchoData( identifier, sequence_number,
188 "ping-message" ) );
189
190 IcmpPacketItem icmp_packet( new IcmpPacket( protocol,
191 icmp_header,
192 icmp_data ) );
193 GlobalLogger.debug() << "IcmpPacketFactory: created echo request packet"
194 << std::endl;
195
196 return icmp_packet;
197}
198
199
200void IcmpPacketFactory::dump_packet(const std::string &data)
201{
202 // create unique file name
203 time_t capture_time = time(0);
204 std::stringstream temp_name;
205 temp_name << DumpFilePrefix << capture_time << ".pcap.XXXXXX";
206
207 // check if directory exists
208 if ( !I2n::path_exists( I2n::dirname(temp_name.str()) ) )
209 {
210 GlobalLogger.info() << "Not saving packet data because directory "
211 << I2n::dirname(temp_name.str()) << " does not exist";
212 return;
213 }
214
215 // open file
216 I2n::tmpfstream temp_stream;
217 if ( !temp_stream.open(temp_name.str()) )
218 {
219 GlobalLogger.warning() << "Failed to create temp file "
220 << temp_name.str() << ": " << strerror(errno) << "!" << endl;
221 return;
222 }
223
224 // write
225 write_pcap_packet_data(data, temp_stream, capture_time);
226
227 // close
228 GlobalLogger.debug() << "Dumped a copy of the ICMP data into "
229 << temp_stream.get_tmp_filename() << endl;
230 temp_stream.close();
231}
232
233void IcmpPacketFactory::dump_packet(const IcmpPacket &packet)
234{
235 // create unique file name
236 time_t capture_time = time(0);
237 std::stringstream temp_name;
238 temp_name << DumpFilePrefix << capture_time << ".pcap.XXXXXX";
239
240 // check if directory exists
241 if ( !I2n::path_exists( I2n::dirname(temp_name.str()) ) )
242 {
243 GlobalLogger.info() << "Not saving packet data because directory "
244 << I2n::dirname(temp_name.str()) << " does not exist";
245 return;
246 }
247
248 // open file
249 I2n::tmpfstream temp_stream;
250 if ( !temp_stream.open(temp_name.str()) )
251 {
252 GlobalLogger.warning() << "Failed to create temp file "
253 << temp_name.str() << ": " << strerror(errno) << "!" << endl;
254 return;
255 }
256
257 // dump data
258 packet.dump(temp_stream);
259
260 // close
261 GlobalLogger.debug() << "Dumped a copy of the packet into "
262 << temp_stream.get_tmp_filename() << endl;
263 temp_stream.close();
264}