03e14dc1e8d7ca88e931494c0e49ecad27c0332a
[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 void dump_packet(const std::string &data)
49 {
50     // create unique file name
51     std::stringstream temp_name;
52     temp_name << "/datastore/pingcheck.broken/icmp_";
53     time_t capture_time = time(0);
54     temp_name << capture_time;
55     temp_name << "_XXXXXX.pcap";
56     std::string temp_str = temp_name.str();
57     std::size_t temp_size = temp_str.size();
58     boost::scoped_array<char> secure_filename( new char[temp_size + 1] );
59     std::copy(temp_str.begin(), temp_str.end(), secure_filename.get());
60     secure_filename[temp_size] = '\0';
61     int fd = mkstemps(secure_filename.get(), 5);   // 5 = ".pcap".length
62     if (fd == -1)
63     {
64         GlobalLogger.warning() << "Failed to create temp file "
65             << secure_filename.get() << ": " << strerror(errno) << "!" << endl;
66         // maybe create containing directory if errno == ENOENT?
67         return;
68     }
69
70     // create file pointer for file descriptor
71     FILE *fp = fdopen(fd, "w");
72
73     // dump data
74     write_pcap_packet_data(data, fp, capture_time);
75
76     // clean up
77     fclose(fp);
78     close(fd);
79     GlobalLogger.debug() << "Dumped a copy of the data into "
80                          << secure_filename.get() << endl;
81 }
82 //-----------------------------------------------------------------------------
83 // IcmpPacketFactory
84 //-----------------------------------------------------------------------------
85
86 /**
87  * @brief Creates an ICMP packet from the input stream @c std::istream.
88  *
89  * @param protocol The packet's network layer protocol, IPv4 or IPv6.
90  * @param is The input stream.
91  * @param dump_mode: 0 for no dumping of packet data, 1 for dump if packet
92  *    creation failed and 2 for dumping always
93  *
94  * @return An ICMP Packet object.
95  */
96 IcmpPacketItem IcmpPacketFactory::create_icmp_packet(
97         const icmp::socket::protocol_type &protocol,
98         istream &is,
99         int dump_mode
100 )
101 {
102     IcmpPacketItem icmp_packet;
103
104     if ( icmp::v4() == protocol || icmp::v6() == protocol )
105         icmp_packet.reset( new IcmpPacket( protocol ) );
106     else
107     {
108         GlobalLogger.warning() << "ICMP packet creation failed: "
109             << "Unknown protocol, expect ICMP v4 or v6!" << endl;
110         icmp_packet.reset();    // --> (!icmp_packet) is true
111         return icmp_packet;
112     }
113
114     IcmpPacket::ReadReturnCode return_code;
115
116     // create buffer for saving data in it
117     stringbuf data_backup;
118     bool have_backup = false;
119
120     // create backup and parse data
121     try
122     {
123         // read packet from stream, possibly copying data first
124         if (dump_mode > 0)
125         {
126             // read all data into backup
127             ostream backup_filler(&data_backup);
128             backup_filler << is.rdbuf();
129
130             // create a new stream from backup buffer
131             //   and use that in read
132             istream is_2(&data_backup);
133             have_backup = true;
134             return_code = icmp_packet->read( is_2 );
135         }
136         else
137             return_code = icmp_packet->read( is );
138     }
139     catch ( const std::exception &ex )
140     {
141         GlobalLogger.notice() << "Exception during ICMP parse: " << ex.what()
142                               << std::endl;
143         icmp_packet.reset();
144     }
145     catch ( ... )
146     {
147         GlobalLogger.notice() << "Exception during ICMP parse." << std::endl;
148         icmp_packet.reset();
149     }
150
151     if ( return_code != IcmpPacket::ReadReturnCode_OK )
152     {
153         GlobalLogger.warning() << "ICMP packet creation failed: "
154             << IcmpPacket::return_code_to_string(return_code) << endl;
155         icmp_packet.reset();    // --> (!icmp_packet) is true
156            // --> icmp_pinger will not try to continue working with this packet
157     }
158     else if ( !is.good() )
159     {
160         GlobalLogger.warning() << "ICMP packet creation failed: "
161             << "Stream not good at end of creation!" << endl;
162         icmp_packet.reset();    // --> (!icmp_packet) is true
163     }
164
165     // dump data if had trouble with packet or dumping is set to always
166     if ( dump_mode == 2 || ( dump_mode==1 && !icmp_packet ) )
167     {
168         if ( have_backup )
169             dump_packet(data_backup.str());
170         else
171             GlobalLogger.warning() << "Would like to dump packet but "
172                 << "exception occured before backup was created!";
173     }
174
175     if (icmp_packet)
176         GlobalLogger.debug() << "Read packet " << icmp_packet->to_string()
177                              << endl;
178     else
179         GlobalLogger.debug() << "Read packet failed" << endl;
180
181     return icmp_packet;
182 }
183
184 /**
185  * @brief Creates an ICMP Echo Request packet.
186  *
187  * @param protocol The packet's network layer protocol, IPv4 or IPv6.
188  * @param identifier The packet's identifier number to aid in matching Echo
189  * Replies to this Echo Request. May be zero.
190  * @param sequence_number The packet's sequence number to aid in matching Echo
191  * Replies to this Echo Request. May be zero.
192  *
193  * @return An ICMP Echo Request packet object.
194  */
195 IcmpPacketItem IcmpPacketFactory::create_icmp_packet_echo_request(
196         const icmp::socket::protocol_type &protocol,
197         const uint16_t identifier,
198         const uint16_t sequence_number
199 )
200 {
201     BOOST_ASSERT( (icmp::v4() == protocol) || (icmp::v6() == protocol) );
202
203     uint8_t type;
204     if ( icmp::v4() == protocol )
205         type = static_cast<uint8_t>(Icmpv4Type_EchoRequest);
206     else if ( icmp::v6() == protocol )
207         type = static_cast<uint8_t>(Icmpv6Type_EchoRequest);
208     // other case caught be BOOST_ASSERT above
209
210     uint8_t code = 0;
211     IcmpHeader icmp_header( type, code );
212
213     IcmpDataPtr icmp_data( new IcmpEchoData( identifier, sequence_number,
214                                                 "ping-message" ) );
215
216     IcmpPacketItem icmp_packet( new IcmpPacket( protocol,
217                                                 icmp_header,
218                                                 icmp_data ) );
219     GlobalLogger.debug() << "IcmpPacketFactory: created echo request packet"
220                          << std::endl;
221
222     return icmp_packet;
223 }
224