increse revision to 0.6.1 and log level of missing-dump-dir-message to notice
[pingcheck] / src / icmp / icmppacketfactory.cpp
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
46 using namespace std;
47 using boost::asio::ip::icmp;
48 using I2n::Logger::GlobalLogger;
49
50 //-----------------------------------------------------------------------------
51 // IcmpPacketFactory
52 //-----------------------------------------------------------------------------
53
54 // set default value
55 DumpMode    IcmpPacketFactory::PacketDumpMode = DUMP_IF_ERROR;
56 std::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  */
67 IcmpPacketItem 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.notice() << "Exception during ICMP parse: " << ex.what()
135                               << std::endl;
136         icmp_packet.reset();
137     }
138     catch ( ... )
139     {
140         GlobalLogger.notice() << "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 occured 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  */
169 IcmpPacketItem 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
200 void 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.notice() << "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
233 void 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.notice() << "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 }