Format astyle
[anna.git] / example / diameter / pcapDecoder / main.cpp
1 // ANNA - Anna is Not Nothingness Anymore
2 //
3 // (c) Copyright 2005-2014 Eduardo Ramos Testillano & Francisco Ruiz Rayo
4 //
5 // http://redmine.teslayout.com/projects/anna-suite
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 //
11 //     * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 //     * Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
16 // distribution.
17 //     *  Neither the name of the copyright holder nor the names of its
18 // contributors may be used to endorse or promote products derived from
19 // this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 //
33 // Authors: eduardo.ramos.testillano@gmail.com
34 //          cisco.tierra@gmail.com
35
36 // Standard
37 #include <pcap.h>
38 #include <stdlib.h>
39 #include <netinet/ip.h>
40 #include <arpa/inet.h>
41 #include <iostream>
42 #include <fstream>
43
44 // STL
45 #include <string>
46 #include <map>
47
48 #include <anna/core/DataBlock.hpp>
49 #include <anna/core/util/Tokenizer.hpp>
50 #include <anna/core/functions.hpp>
51 #include <anna/core/tracing/Logger.hpp>
52 #include <anna/core/tracing/TraceWriter.hpp>
53 #include <anna/core/RuntimeException.hpp>
54 #include <anna/xml/xml.hpp>
55 #include <anna/diameter/stack/Engine.hpp>
56 #include <anna/diameter/codec/Engine.hpp>
57 #include <anna/diameter/codec/Message.hpp>
58
59 using namespace anna;
60 using namespace anna::diameter;
61
62 // Payload and frame metadata /////////////////////////////////////////////////////////////////////////////
63 class Payload {
64
65   std::string _sourceIP;
66   std::string _destinationIP;
67   time_t _timestamp;
68   int _timestampU; // usecs
69   std::string _data;
70   size_t _diameterLength;
71
72 public:
73   Payload() {
74     reset();
75   }
76
77   void setDiameterLength(size_t dl) {
78     if(_diameterLength == -1) {
79       _diameterLength = dl;
80       LOGDEBUG(
81         Logger::debug(anna::functions::asString("Diameter message length: %d bytes", dl), ANNA_FILE_LOCATION));
82     }
83   }
84
85   void setSourceIP(const std::string &srcIP) throw() {
86     _sourceIP = srcIP;
87   }
88   void setDestinationIP(const std::string &dstIP) throw() {
89     _destinationIP = dstIP;
90   }
91   void setTimestamp(time_t ts) throw() {
92     _timestamp = ts;
93   }
94   void setTimestampU(int tsu) throw() {
95     _timestampU = tsu;
96   }
97   // Returns true if completed:
98   bool appendData(const char *data, size_t size) throw(RuntimeException) {
99     LOGDEBUG(
100       Logger::debug(anna::functions::asString("Appending %d bytes", size), ANNA_FILE_LOCATION));
101     _data.append(data, size);
102
103     if(_data.size() > _diameterLength)
104       throw RuntimeException(
105         "Data overflow (unexpected offset exceed diameter message length)",
106         ANNA_FILE_LOCATION);
107
108     if(_data.size() < _diameterLength)
109       return false;
110
111     LOGDEBUG(anna::Logger::debug("Completed!", ANNA_FILE_LOCATION));
112     return true;
113   }
114
115   void reset() throw() {
116     _sourceIP = "";
117     _destinationIP = "";
118     _timestamp = 0;
119     _timestampU = 0;
120     _data = "";
121     _diameterLength = -1; // not calculated yet
122   }
123
124   const std::string &getSourceIP() const throw() {
125     return _sourceIP;
126   }
127   const std::string &getDestinationIP() const throw() {
128     return _destinationIP;
129   }
130   time_t getTimestamp() const throw() {
131     return _timestamp;
132   }
133   int getTimestampU() const throw() {
134     return _timestampU;
135   }
136   const std::string &getData() const throw() {
137     return _data;
138   }
139   std::string getDataAsHex() const throw() {
140     return anna::functions::asHexString(
141              anna::DataBlock(_data.c_str(), _data.size()));
142   }
143 };
144
145 // Data maps //////////////////////////////////////////////////////////////////////////////////////////////
146 typedef std::map < int /* frame */, Payload > payloads_t;
147 typedef std::map < int /* frame */, Payload >::const_iterator payloads_it;
148 payloads_t G_payloads;
149 anna::diameter::codec::Message G_codecMsg;
150
151 // Sniffing structures ////////////////////////////////////////////////////////////////////////////////////
152
153 /* ethernet headers are always exactly 14 bytes */
154 #define SIZE_ETHERNET 14
155 /* Ethernet addresses are 6 bytes */
156 #define ETHER_ADDR_LEN  6
157
158 /* Ethernet header */
159 struct sniff_ethernet {
160   u_char ether_dhost[ETHER_ADDR_LEN]; /* Destination host address */
161   u_char ether_shost[ETHER_ADDR_LEN]; /* Source host address */
162   u_short ether_type; /* IP? ARP? RARP? etc */
163 };
164
165 /* IP header */
166 struct sniff_ip {
167   u_char ip_vhl; /* version << 4 | header length >> 2 */
168   u_char ip_tos; /* type of service */
169   u_short ip_len; /* total length */
170   u_short ip_id; /* identification */
171   u_short ip_off; /* fragment offset field */
172 #define IP_RF 0x8000    /* reserved fragment flag */
173 #define IP_DF 0x4000    /* dont fragment flag */
174 #define IP_MF 0x2000    /* more fragments flag */
175 #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
176   u_char ip_ttl; /* time to live */
177   u_char ip_p; /* protocol */
178   u_short ip_sum; /* checksum */
179   struct in_addr ip_src, ip_dst; /* source and dest address */
180 };
181 #define IP_OFF(ip)              (((ip)->ip_off) & IP_OFFMASK)
182 #define IP_DF_VAL(ip)           ((((ip)->ip_off) & IP_DF) >> 14)
183 #define IP_MF_VAL(ip)           ((((ip)->ip_off) & IP_MF) >> 13)
184 #define IP_HL(ip)   (((ip)->ip_vhl) & 0x0f)
185 #define IP_V(ip)    (((ip)->ip_vhl) >> 4)
186
187 /* TCP header */
188 typedef u_int tcp_seq;
189
190 struct sniff_tcp {
191   u_short th_sport; /* source port */
192   u_short th_dport; /* destination port */
193   tcp_seq th_seq; /* sequence number */
194   tcp_seq th_ack; /* acknowledgement number */
195   u_char th_offx2; /* data offset, rsvd */
196 #define TH_OFF(th)  (((th)->th_offx2 & 0xf0) >> 4)
197   u_char th_flags;
198 #define TH_FIN 0x01
199 #define TH_SYN 0x02
200 #define TH_RST 0x04
201 #define TH_PUSH 0x08
202 #define TH_ACK 0x10
203 #define TH_URG 0x20
204 #define TH_ECE 0x40
205 #define TH_CWR 0x80
206 #define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
207   u_short th_win; /* window */
208   u_short th_sum; /* checksum */
209   u_short th_urp; /* urgent pointer */
210 };
211
212 // Payload extraction /////////////////////////////////////////////////////////////////////////////////////
213 u_char *getPayload(const u_char* packet, int packetSize, int &payloadSize,
214                    std::string &srcIp, std::string &dstIp, int &fragmentId, bool &dfFlag,
215                    bool &mfFlag, int &fragmentOffset) {
216   const struct sniff_ethernet *ethernet; /* The ethernet header */
217   const struct sniff_ip *ip; /* The IP header */
218   const struct sniff_tcp *tcp; /* The TCP header */
219   u_int size_ip;
220   u_int size_tcp;
221   ethernet = (struct sniff_ethernet*)(packet);
222   ip = (struct sniff_ip*)(packet + SIZE_ETHERNET);
223   size_ip = IP_HL(ip) * 4; // 4 bytes per 32 bits word
224
225   if(size_ip < 20) {
226     LOGDEBUG(
227       Logger::debug(anna::functions::asString("Invalid IP header length: %d bytes", size_ip), ANNA_FILE_LOCATION));
228     return NULL;
229   }
230
231   static char str[INET_ADDRSTRLEN];
232   inet_ntop(AF_INET, &(ip->ip_src), str, INET_ADDRSTRLEN);
233   srcIp = str;
234   inet_ntop(AF_INET, &(ip->ip_dst), str, INET_ADDRSTRLEN);
235   dstIp = str;
236   LOGDEBUG(
237     Logger::debug(anna::functions::asString("ip_id: %d | ip_off: %d", ip->ip_id, ip->ip_off), ANNA_FILE_LOCATION));
238   fragmentId = ip->ip_id;
239   dfFlag = IP_DF_VAL(ip);
240   mfFlag = IP_MF_VAL(ip);
241   fragmentOffset = IP_OFF(ip);
242   tcp = (struct sniff_tcp*)(packet + SIZE_ETHERNET + size_ip);
243   size_tcp = TH_OFF(tcp) * 4;
244
245   if(size_tcp < 20) {
246     LOGDEBUG(
247       Logger::debug(anna::functions::asString("Invalid TCP header length: %d bytes", size_tcp), ANNA_FILE_LOCATION));
248     return NULL;
249   }
250
251   int payloadOffset = SIZE_ETHERNET + size_ip + size_tcp;
252   LOGDEBUG(
253     Logger::debug(anna::functions::asString("PayloadOffset=%d", payloadOffset), ANNA_FILE_LOCATION));
254   payloadSize = packetSize - payloadOffset;
255   return ((u_char *)(packet + payloadOffset));
256 }
257
258 // Sniffing callback //////////////////////////////////////////////////////////////////////////////////////
259 void my_callback(u_char *useless, const struct pcap_pkthdr* pkthdr,
260                  const u_char* packet) {
261   static int count = 1;
262   static Payload auxPayload;
263   int packetSize = pkthdr->len;
264   int payloadSize;
265   std::string srcIp, dstIp;
266   int fragmentId, fragmentOffset;
267   bool dfFlag, mfFlag;
268   const u_char* payload = getPayload(packet, packetSize, payloadSize, srcIp,
269                                      dstIp, fragmentId, dfFlag, mfFlag, fragmentOffset);
270
271   if(payload && (payloadSize > 0)) {
272     LOGDEBUG(
273       std::string msg; msg += anna::functions::asString("\nFrame %d:", count); msg += anna::functions::asHexString(anna::DataBlock((const char *)packet, pkthdr->len)); time_t time = pkthdr->ts.tv_sec; msg += "\n"; msg += anna::functions::asString("\ntimestamp %d.%d", pkthdr->ts.tv_sec, pkthdr->ts.tv_usec); msg += anna::functions::asString("\ndate %s", ctime(&time)); msg += anna::functions::asString("\ncaplen %d", pkthdr->caplen); msg += anna::functions::asString("\npacketSize %d", packetSize); msg += anna::functions::asString("\npayloadSize %d", payloadSize); msg += "\nPayload:"; msg += anna::functions::asHexString(anna::DataBlock((const char *)payload, payloadSize)); msg += "\n"; msg += anna::functions::asString("\nsourceIP %s", srcIp.c_str()); msg += anna::functions::asString("\ndestinationIP %s", dstIp.c_str()); msg += "\n"; msg += anna::functions::asString("\nfragmentId %d:", fragmentId); msg += anna::functions::asString("\nDF %s:", (dfFlag ? "1" : "0")); msg += anna::functions::asString("\nMF %s:", (mfFlag ? "1" : "0")); msg += anna::functions::asString("\nfragmentOffset %d:", fragmentOffset);
274       Logger::debug(msg, ANNA_FILE_LOCATION););
275     auxPayload.setDiameterLength(
276       (payload[1] << 16) + (payload[2] << 8) + payload[3]);
277     auxPayload.setSourceIP(srcIp);
278     auxPayload.setDestinationIP(dstIp);
279     auxPayload.setTimestamp(pkthdr->ts.tv_sec);
280     auxPayload.setTimestampU(pkthdr->ts.tv_usec);
281     bool completed = auxPayload.appendData((const char *) payload, payloadSize);
282
283     if(completed) {
284       G_payloads[count] = auxPayload;
285       auxPayload.reset();
286     }
287   }
288
289   count++;
290 }
291
292 //-------------------------------------------------------------------
293 int main(int argc, char **argv) {
294   std::string exec = argv[0];
295   std::cout << std::endl;
296
297   //check command line arguments
298   if(argc < 3) {
299     std::cout << "Usage: " << exec
300               << " <list of comma-separated xml dictionaries> <pcap file> [--ignore-flags: non-strict validation]"
301               << std::endl << std::endl;
302     return 1;
303   }
304
305   // Command-line parameters:
306   std::string dictionaries = argv[1];
307   std::string pcapFile = argv[2];
308   std::string optional = argv[3] ? argv[3] : "";
309   bool ignoreFlags = ((argc == 4) && (optional == "--ignore-flags"));
310   std::cout << "Dictionary(ies) provided: " << dictionaries << std::endl;
311   std::cout << "Pcap file provided:       " << pcapFile << std::endl;
312   std::cout << "Validation kindness:      "
313             << (ignoreFlags ? "non strict" : "strict") << std::endl;
314   // Logger and engines:
315   Logger::setLevel(Logger::Debug);
316   Logger::initialize("pcapDecoder", new TraceWriter("file.trace", 2048000));
317   anna::diameter::codec::Engine *codecEngine =
318     new anna::diameter::codec::Engine();
319   anna::diameter::stack::Engine &stackEngine =
320     anna::diameter::stack::Engine::instantiate();
321
322   try {
323     anna::diameter::stack::Dictionary * d = stackEngine.createDictionary(
324         0 /* stack id */);
325     // Analyze comma-separated list:
326     anna::Tokenizer lst;
327     lst.apply(dictionaries, ",");
328
329     if(lst.size() >= 1) {  // always true (at least one, because -dictionary is mandatory)
330       anna::Tokenizer::const_iterator tok_min(lst.begin());
331       anna::Tokenizer::const_iterator tok_max(lst.end());
332       anna::Tokenizer::const_iterator tok_iter;
333       std::string pathFile;
334       d->allowUpdates();
335
336       for(tok_iter = tok_min; tok_iter != tok_max; tok_iter++) {
337         pathFile = anna::Tokenizer::data(tok_iter);
338         d->load(pathFile);
339       }
340     }
341
342     codecEngine->setDictionary(d);
343     //LOGDEBUG(anna::Logger::debug(codecEngine->asString(), ANNA_FILE_LOCATION));
344
345     if(lst.size() > 1) {
346       std::string all_in_one = "./dictionary-all-in-one.xml";
347       std::ofstream out(all_in_one, std::ifstream::out);
348       std::string buffer = d->asXMLString();
349       out.write(buffer.c_str(), buffer.size());
350       out.close();
351       std::cout << "Written '" << all_in_one
352                 << "' (provide it next time to be more comfortable)." << std::endl;
353     }
354   } catch(anna::RuntimeException &ex) {
355     std::cerr << ex.asString() << std::endl << std::endl;
356     return 1;
357   }
358
359   codecEngine->ignoreFlagsOnValidation(ignoreFlags);
360   // Tracing:
361   //if (cl.exists("trace"))
362   //   anna::Logger::setLevel(anna::Logger::asLevel(cl.getValue("trace")));
363   // SNIFFING //////////////////////////////////////////////////////////////////////////////////////////////7
364   //temporary packet buffers
365   struct pcap_pkthdr header; // The header that pcap gives us
366   const u_char *packet;      // The actual packet
367   //------------------
368   //open the pcap file
369   pcap_t *handle;
370   char errbuf[PCAP_ERRBUF_SIZE];        //not sure what to do with this, oh well
371   handle = pcap_open_offline(pcapFile.c_str(), errbuf); //call pcap library function
372
373   if(handle == NULL) {
374     std::cerr << errbuf << std::endl << std::endl;
375     return 2;
376   }
377
378   //begin processing the packets in this particular file
379   int packets = -1;
380
381   try {
382     while(packets != 0)
383       packets = pcap_dispatch(handle, -1, (pcap_handler) my_callback, NULL);
384   } catch(RuntimeException &ex) {
385     std::cerr << ex.asString() << std::endl << std::endl;
386     return 1;
387   }
388
389   pcap_close(handle);  //close the pcap file
390   // Print payloads //////////////////////////////////////////////////////////////////////////////////////////////
391   // Open output file:
392   std::string output = pcapFile;
393   output += ".report";
394   std::ofstream out(output, std::ifstream::out);
395   std::string xmlStr;
396   anna::DataBlock db_aux(true);
397
398   //out.write(str.c_str(), str.size());
399
400   for(payloads_it it = G_payloads.begin(); it != G_payloads.end(); it++) {
401     LOGDEBUG(
402       Logger::debug(anna::functions::asString("Dumping frame %d", it->first), ANNA_FILE_LOCATION));
403     time_t ts = (it->second).getTimestamp();
404     int tsu = (it->second).getTimestampU();
405     std::string ts_str = ctime(&ts);
406     ts_str.erase(ts_str.find("\n"));
407     out << std::endl;
408     out
409         << "==================================================================================================="
410         << std::endl;
411     out << "Date:           " << ts_str << std::endl;
412     out << "Timestamp:      " << std::to_string(ts) << "."
413         << std::to_string(tsu) << std::endl;
414     out << "Origin IP:      " << (it->second).getSourceIP() << std::endl;
415     out << "Destination IP: " << (it->second).getDestinationIP() << std::endl;
416     out << std::endl;
417     // decode hex string:
418     anna::functions::fromHexString((it->second).getDataAsHex(), db_aux);
419
420     try {
421       G_codecMsg.decode(db_aux);
422     } catch(RuntimeException &ex) {
423       std::cerr << ex.asString() << std::endl << std::endl;
424       return 1;
425     }
426
427     out << G_codecMsg.asXMLString();
428   }
429
430   // Close output file:
431   out.close();
432   std::cout << "Open 'file.trace' in order to see process traces." << std::endl;
433   std::cout << "Open '" << output << "' to see conversion results."
434             << std::endl;
435   std::cout << std::endl;
436   return 0;
437 }
438