762ce2ebf1238adc54d3dd3a8e41d4f7c35d2219
[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
37 #include "boost_assert_handler.h"
38 #include "icmp/icmpdata.h"
39 #include "icmp/icmpheader.h"
40 #include "icmp/icmptype.h"
41 #include "icmp/icmpechodata.h"
42 #include "tools/pcap.h"
43
44 using namespace std;
45 using boost::asio::ip::icmp;
46 using I2n::Logger::GlobalLogger;
47
48 //-----------------------------------------------------------------------------
49 // IcmpPacketFactory
50 //-----------------------------------------------------------------------------
51
52 // set default value
53 DumpMode    IcmpPacketFactory::PacketDumpMode = DUMP_IF_ERROR;
54 std::string IcmpPacketFactory::DumpFilePrefix = "/tmp/icmp_";
55
56 /**
57  * @brief Creates an ICMP packet from the input stream @c std::istream.
58  *
59  * @param protocol The packet's network layer protocol, IPv4 or IPv6.
60  * @param is The input stream.
61  *
62  * @return An ICMP Packet object.
63  */
64 IcmpPacketItem IcmpPacketFactory::create_icmp_packet(
65         const icmp::socket::protocol_type &protocol,
66         istream &is
67 )
68 {
69     IcmpPacketItem icmp_packet;
70
71     if ( icmp::v4() == protocol || icmp::v6() == protocol )
72         icmp_packet.reset( new IcmpPacket( protocol ) );
73     else
74     {
75         GlobalLogger.warning() << "ICMP packet creation failed: "
76             << "Unknown protocol, expect ICMP v4 or v6!" << endl;
77         icmp_packet.reset();    // --> (!icmp_packet) is true
78         return icmp_packet;
79     }
80
81     IcmpPacket::ReadReturnCode return_code;
82
83     // create buffer for saving data in it
84     stringbuf data_backup;
85     bool have_backup = false;
86
87     // create backup and parse data
88     try
89     {
90         // read packet from stream, possibly copying data first
91         if (PacketDumpMode != DUMP_NEVER)
92         {
93             // read all data into backup
94             ostream backup_filler(&data_backup);
95             backup_filler << is.rdbuf();
96
97             // create a new stream from backup buffer
98             //   and use that in read
99             istream is_2(&data_backup);
100             have_backup = true;
101             return_code = icmp_packet->read( is_2 );
102         }
103         else
104             return_code = icmp_packet->read( is );
105
106         if ( return_code != IcmpPacket::ReadReturnCode_OK )
107         {
108             GlobalLogger.warning() << "ICMP packet creation failed: "
109                 << IcmpPacket::return_code_to_string(return_code) << endl;
110             icmp_packet.reset();    // --> (!icmp_packet) is true
111                // --> icmp_pinger will not try to continue working with this packet
112         }
113         else if ( !is.good() )
114         {
115             GlobalLogger.warning() << "ICMP packet creation failed: "
116                 << "Stream not good at end of creation!" << endl;
117             icmp_packet.reset();    // --> (!icmp_packet) is true
118         }
119
120         // print end result within try-catch because to_string might also
121         // throw exception
122         if (icmp_packet)
123             GlobalLogger.debug() << "Read packet " << icmp_packet->to_string()
124                                  << endl;
125         else
126             GlobalLogger.debug() << "Read packet failed" << endl;
127
128     }
129     catch ( const std::exception &ex )
130     {
131         GlobalLogger.notice() << "Exception during ICMP parse: " << ex.what()
132                               << std::endl;
133         icmp_packet.reset();
134     }
135     catch ( ... )
136     {
137         GlobalLogger.notice() << "Exception during ICMP parse." << std::endl;
138         icmp_packet.reset();
139     }
140
141     // dump data if had trouble with packet or dumping is set to always
142     if ( PacketDumpMode == DUMP_ALWAYS ||
143        ( PacketDumpMode == DUMP_IF_ERROR && !icmp_packet ) )
144     {
145         if ( have_backup )
146             dump_packet(data_backup.str());
147         else
148             GlobalLogger.warning() << "Would like to dump packet but "
149                 << "trouble occured before backup was created!";
150     }
151
152     return icmp_packet;
153 }
154
155 /**
156  * @brief Creates an ICMP Echo Request packet.
157  *
158  * @param protocol The packet's network layer protocol, IPv4 or IPv6.
159  * @param identifier The packet's identifier number to aid in matching Echo
160  * Replies to this Echo Request. May be zero.
161  * @param sequence_number The packet's sequence number to aid in matching Echo
162  * Replies to this Echo Request. May be zero.
163  *
164  * @return An ICMP Echo Request packet object.
165  */
166 IcmpPacketItem IcmpPacketFactory::create_icmp_packet_echo_request(
167         const icmp::socket::protocol_type &protocol,
168         const uint16_t identifier,
169         const uint16_t sequence_number
170 )
171 {
172     BOOST_ASSERT( (icmp::v4() == protocol) || (icmp::v6() == protocol) );
173
174     uint8_t type;
175     if ( icmp::v4() == protocol )
176         type = static_cast<uint8_t>(Icmpv4Type_EchoRequest);
177     else if ( icmp::v6() == protocol )
178         type = static_cast<uint8_t>(Icmpv6Type_EchoRequest);
179     // other case caught be BOOST_ASSERT above
180
181     uint8_t code = 0;
182     IcmpHeader icmp_header( type, code );
183
184     IcmpDataPtr icmp_data( new IcmpEchoData( identifier, sequence_number,
185                                                 "ping-message" ) );
186
187     IcmpPacketItem icmp_packet( new IcmpPacket( protocol,
188                                                 icmp_header,
189                                                 icmp_data ) );
190     GlobalLogger.debug() << "IcmpPacketFactory: created echo request packet"
191                          << std::endl;
192
193     return icmp_packet;
194 }
195
196
197 /**
198  * @brief helper function for dump_packet methods, do not use elsewhere!
199  *
200  * creates a file descriptor and expects calling function dump_packet to deal
201  *   with it (i.e. test and close); also logs a dump_packet-related text
202  *   
203  * @returns file descriptor from mkstemps
204  */
205 int IcmpPacketFactory::create_dump_file(const time_t &capture_time)
206 {
207     std::stringstream temp_name;
208     temp_name << DumpFilePrefix;
209     temp_name << capture_time;
210     temp_name << "_XXXXXX.pcap";
211     std::string temp_str = temp_name.str();
212     std::size_t temp_size = temp_str.size();
213     boost::scoped_array<char> secure_filename( new char[temp_size + 1] );
214     std::copy(temp_str.begin(), temp_str.end(), secure_filename.get());
215     secure_filename[temp_size] = '\0';
216     int fd = mkstemps(secure_filename.get(), 5);   // 5 = ".pcap".length
217     if (fd == -1)
218         GlobalLogger.warning() << "Failed to create temp file "
219             << secure_filename.get() << ": " << strerror(errno) << "!" << endl;
220         // maybe create containing directory if errno == ENOENT?
221     else
222         GlobalLogger.debug() << "Dumping a copy of the data into "
223                              << secure_filename.get() << endl;
224     return fd;
225 }
226
227
228 void IcmpPacketFactory::dump_packet(const std::string &data)
229 {
230     // create unique file name
231     time_t capture_time = time(0);
232     int fd = create_dump_file(capture_time);
233     if (fd == -1)
234         return;
235
236     // create file pointer for file descriptor
237     FILE *fp = fdopen(fd, "w");
238
239     // dump data
240     write_pcap_packet_data(data, fp, capture_time);
241
242     // clean up
243     fclose(fp);
244     close(fd);
245 }
246
247 void IcmpPacketFactory::dump_packet(const IcmpPacket &packet)
248 {
249     // create unique file name
250     time_t capture_time = time(0);
251     int fd = create_dump_file(capture_time);
252     if (fd == -1)
253         return;
254
255     // create file pointer for file descriptor
256     FILE *fp = fdopen(fd, "w");
257
258     // create string with packet contents
259     std::stringstream data;
260     packet.dump(data);
261
262     // dump data
263     write_pcap_packet_data(data.str(), fp, capture_time);
264
265     // clean up
266     fclose(fp);
267     close(fd);
268 }