0f01c67c7770c4473c9ba18a91c765f54d363fea
[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 << IcmpPacketFactory::DumpFilePrefix;
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 // set default value
87 DumpMode    IcmpPacketFactory::PacketDumpMode = DUMP_IF_ERROR;
88 std::string IcmpPacketFactory::DumpFilePrefix = "/tmp/icmp_";
89
90 /**
91  * @brief Creates an ICMP packet from the input stream @c std::istream.
92  *
93  * @param protocol The packet's network layer protocol, IPv4 or IPv6.
94  * @param is The input stream.
95  *
96  * @return An ICMP Packet object.
97  */
98 IcmpPacketItem IcmpPacketFactory::create_icmp_packet(
99         const icmp::socket::protocol_type &protocol,
100         istream &is
101 )
102 {
103     IcmpPacketItem icmp_packet;
104
105     if ( icmp::v4() == protocol || icmp::v6() == protocol )
106         icmp_packet.reset( new IcmpPacket( protocol ) );
107     else
108     {
109         GlobalLogger.warning() << "ICMP packet creation failed: "
110             << "Unknown protocol, expect ICMP v4 or v6!" << endl;
111         icmp_packet.reset();    // --> (!icmp_packet) is true
112         return icmp_packet;
113     }
114
115     IcmpPacket::ReadReturnCode return_code;
116
117     // create buffer for saving data in it
118     stringbuf data_backup;
119     bool have_backup = false;
120
121     // create backup and parse data
122     try
123     {
124         // read packet from stream, possibly copying data first
125         if (PacketDumpMode != DUMP_NEVER)
126         {
127             // read all data into backup
128             ostream backup_filler(&data_backup);
129             backup_filler << is.rdbuf();
130
131             // create a new stream from backup buffer
132             //   and use that in read
133             istream is_2(&data_backup);
134             have_backup = true;
135             return_code = icmp_packet->read( is_2 );
136         }
137         else
138             return_code = icmp_packet->read( is );
139     }
140     catch ( const std::exception &ex )
141     {
142         GlobalLogger.notice() << "Exception during ICMP parse: " << ex.what()
143                               << std::endl;
144         icmp_packet.reset();
145     }
146     catch ( ... )
147     {
148         GlobalLogger.notice() << "Exception during ICMP parse." << std::endl;
149         icmp_packet.reset();
150     }
151
152     if ( return_code != IcmpPacket::ReadReturnCode_OK )
153     {
154         GlobalLogger.warning() << "ICMP packet creation failed: "
155             << IcmpPacket::return_code_to_string(return_code) << endl;
156         icmp_packet.reset();    // --> (!icmp_packet) is true
157            // --> icmp_pinger will not try to continue working with this packet
158     }
159     else if ( !is.good() )
160     {
161         GlobalLogger.warning() << "ICMP packet creation failed: "
162             << "Stream not good at end of creation!" << endl;
163         icmp_packet.reset();    // --> (!icmp_packet) is true
164     }
165
166     // dump data if had trouble with packet or dumping is set to always
167     if ( PacketDumpMode == DUMP_ALWAYS ||
168        ( PacketDumpMode == DUMP_IF_ERROR && !icmp_packet ) )
169     {
170         if ( have_backup )
171             dump_packet(data_backup.str());
172         else
173             GlobalLogger.warning() << "Would like to dump packet but "
174                 << "trouble occured before backup was created!";
175     }
176
177     if (icmp_packet)
178         GlobalLogger.debug() << "Read packet " << icmp_packet->to_string()
179                              << endl;
180     else
181         GlobalLogger.debug() << "Read packet failed" << endl;
182
183     return icmp_packet;
184 }
185
186 /**
187  * @brief Creates an ICMP Echo Request packet.
188  *
189  * @param protocol The packet's network layer protocol, IPv4 or IPv6.
190  * @param identifier The packet's identifier number to aid in matching Echo
191  * Replies to this Echo Request. May be zero.
192  * @param sequence_number The packet's sequence number to aid in matching Echo
193  * Replies to this Echo Request. May be zero.
194  *
195  * @return An ICMP Echo Request packet object.
196  */
197 IcmpPacketItem IcmpPacketFactory::create_icmp_packet_echo_request(
198         const icmp::socket::protocol_type &protocol,
199         const uint16_t identifier,
200         const uint16_t sequence_number
201 )
202 {
203     BOOST_ASSERT( (icmp::v4() == protocol) || (icmp::v6() == protocol) );
204
205     uint8_t type;
206     if ( icmp::v4() == protocol )
207         type = static_cast<uint8_t>(Icmpv4Type_EchoRequest);
208     else if ( icmp::v6() == protocol )
209         type = static_cast<uint8_t>(Icmpv6Type_EchoRequest);
210     // other case caught be BOOST_ASSERT above
211
212     uint8_t code = 0;
213     IcmpHeader icmp_header( type, code );
214
215     IcmpDataPtr icmp_data( new IcmpEchoData( identifier, sequence_number,
216                                                 "ping-message" ) );
217
218     IcmpPacketItem icmp_packet( new IcmpPacket( protocol,
219                                                 icmp_header,
220                                                 icmp_data ) );
221     GlobalLogger.debug() << "IcmpPacketFactory: created echo request packet"
222                          << std::endl;
223
224     return icmp_packet;
225 }
226