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