Fix enum bug (was on dictionary even when no data). PcapDecoder can load multiple...
[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 //#include <anna/diameter/defines.hpp> // typedef unsigned int ApplicationId;
59 #include <anna/diameter/codec/functions.hpp> // ApplicationId anna::diameter::codec::functions::getApplicationId(const anna::DataBlock &) throw(anna::RuntimeException);
60
61
62 using namespace anna;
63 using namespace anna::diameter;
64
65 // Payload and frame metadata /////////////////////////////////////////////////////////////////////////////
66 class Payload {
67
68   std::string _sourceIP;
69   std::string _destinationIP;
70   time_t _timestamp;
71   int _timestampU; // usecs
72   std::string _data;
73   size_t _diameterLength;
74
75 public:
76   Payload() {
77     reset();
78   }
79
80   void setDiameterLength(size_t dl) {
81     if(_diameterLength == -1) {
82       _diameterLength = dl;
83       LOGDEBUG(
84         Logger::debug(anna::functions::asString("Diameter message length: %d bytes", dl), ANNA_FILE_LOCATION));
85     }
86   }
87
88   void setSourceIP(const std::string &srcIP) throw() {
89     _sourceIP = srcIP;
90   }
91   void setDestinationIP(const std::string &dstIP) throw() {
92     _destinationIP = dstIP;
93   }
94   void setTimestamp(time_t ts) throw() {
95     _timestamp = ts;
96   }
97   void setTimestampU(int tsu) throw() {
98     _timestampU = tsu;
99   }
100   // Returns true if completed:
101   bool appendData(const char *data, size_t size) throw(RuntimeException) {
102     LOGDEBUG(
103       Logger::debug(anna::functions::asString("Appending %d bytes", size), ANNA_FILE_LOCATION));
104     _data.append(data, size);
105
106     if(_data.size() > _diameterLength)
107       throw RuntimeException(
108         "Data overflow (unexpected offset exceed diameter message length)",
109         ANNA_FILE_LOCATION);
110
111     if(_data.size() < _diameterLength)
112       return false;
113
114     LOGDEBUG(anna::Logger::debug("Completed!", ANNA_FILE_LOCATION));
115     return true;
116   }
117
118   void reset() throw() {
119     _sourceIP = "";
120     _destinationIP = "";
121     _timestamp = 0;
122     _timestampU = 0;
123     _data = "";
124     _diameterLength = -1; // not calculated yet
125   }
126
127   const std::string &getSourceIP() const throw() {
128     return _sourceIP;
129   }
130   const std::string &getDestinationIP() const throw() {
131     return _destinationIP;
132   }
133   time_t getTimestamp() const throw() {
134     return _timestamp;
135   }
136   int getTimestampU() const throw() {
137     return _timestampU;
138   }
139   const std::string &getData() const throw() {
140     return _data;
141   }
142   std::string getDataAsHex() const throw() {
143     return anna::functions::asHexString(
144              anna::DataBlock(_data.c_str(), _data.size()));
145   }
146 };
147
148 // Data maps //////////////////////////////////////////////////////////////////////////////////////////////
149 typedef std::map < int /* frame */, Payload > payloads_t;
150 typedef std::map < int /* frame */, Payload >::const_iterator payloads_it;
151 payloads_t G_payloads;
152 anna::diameter::codec::Message G_codecMsg;
153 anna::diameter::codec::Engine *G_codecEngine;
154
155 // Sniffing structures ////////////////////////////////////////////////////////////////////////////////////
156
157 /* ethernet headers are always exactly 14 bytes */
158 #define SIZE_ETHERNET 14
159 /* Ethernet addresses are 6 bytes */
160 #define ETHER_ADDR_LEN  6
161
162 /* Ethernet header */
163 struct sniff_ethernet {
164   u_char ether_dhost[ETHER_ADDR_LEN]; /* Destination host address */
165   u_char ether_shost[ETHER_ADDR_LEN]; /* Source host address */
166   u_short ether_type; /* IP? ARP? RARP? etc */
167 };
168
169 /* IP header */
170 struct sniff_ip {
171   u_char ip_vhl; /* version << 4 | header length >> 2 */
172   u_char ip_tos; /* type of service */
173   u_short ip_len; /* total length */
174   u_short ip_id; /* identification */
175   u_short ip_off; /* fragment offset field */
176 #define IP_RF 0x8000    /* reserved fragment flag */
177 #define IP_DF 0x4000    /* dont fragment flag */
178 #define IP_MF 0x2000    /* more fragments flag */
179 #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
180   u_char ip_ttl; /* time to live */
181   u_char ip_p; /* protocol */
182   u_short ip_sum; /* checksum */
183   struct in_addr ip_src, ip_dst; /* source and dest address */
184 };
185 #define IP_OFF(ip)              (((ip)->ip_off) & IP_OFFMASK)
186 #define IP_DF_VAL(ip)           ((((ip)->ip_off) & IP_DF) >> 14)
187 #define IP_MF_VAL(ip)           ((((ip)->ip_off) & IP_MF) >> 13)
188 #define IP_HL(ip)   (((ip)->ip_vhl) & 0x0f)
189 #define IP_V(ip)    (((ip)->ip_vhl) >> 4)
190
191 /* TCP header */
192 typedef u_int tcp_seq;
193
194 struct sniff_tcp {
195   u_short th_sport; /* source port */
196   u_short th_dport; /* destination port */
197   tcp_seq th_seq; /* sequence number */
198   tcp_seq th_ack; /* acknowledgement number */
199   u_char th_offx2; /* data offset, rsvd */
200 #define TH_OFF(th)  (((th)->th_offx2 & 0xf0) >> 4)
201   u_char th_flags;
202 #define TH_FIN 0x01
203 #define TH_SYN 0x02
204 #define TH_RST 0x04
205 #define TH_PUSH 0x08
206 #define TH_ACK 0x10
207 #define TH_URG 0x20
208 #define TH_ECE 0x40
209 #define TH_CWR 0x80
210 #define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
211   u_short th_win; /* window */
212   u_short th_sum; /* checksum */
213   u_short th_urp; /* urgent pointer */
214 };
215
216 // Payload extraction /////////////////////////////////////////////////////////////////////////////////////
217 u_char *getPayload(const u_char* packet, int packetSize, int &payloadSize,
218                    std::string &srcIp, std::string &dstIp, int &fragmentId, bool &dfFlag,
219                    bool &mfFlag, int &fragmentOffset) {
220   const struct sniff_ethernet *ethernet; /* The ethernet header */
221   const struct sniff_ip *ip; /* The IP header */
222   const struct sniff_tcp *tcp; /* The TCP header */
223   u_int size_ip;
224   u_int size_tcp;
225   ethernet = (struct sniff_ethernet*)(packet);
226   ip = (struct sniff_ip*)(packet + SIZE_ETHERNET);
227   size_ip = IP_HL(ip) * 4; // 4 bytes per 32 bits word
228
229   if(size_ip < 20) {
230     LOGDEBUG(
231       Logger::debug(anna::functions::asString("Invalid IP header length: %d bytes", size_ip), ANNA_FILE_LOCATION));
232     return NULL;
233   }
234
235   static char str[INET_ADDRSTRLEN];
236   inet_ntop(AF_INET, &(ip->ip_src), str, INET_ADDRSTRLEN);
237   srcIp = str;
238   inet_ntop(AF_INET, &(ip->ip_dst), str, INET_ADDRSTRLEN);
239   dstIp = str;
240   LOGDEBUG(
241     Logger::debug(anna::functions::asString("ip_id: %d | ip_off: %d", ip->ip_id, ip->ip_off), ANNA_FILE_LOCATION));
242   fragmentId = ip->ip_id;
243   dfFlag = IP_DF_VAL(ip);
244   mfFlag = IP_MF_VAL(ip);
245   fragmentOffset = IP_OFF(ip);
246   tcp = (struct sniff_tcp*)(packet + SIZE_ETHERNET + size_ip);
247   size_tcp = TH_OFF(tcp) * 4;
248
249   if(size_tcp < 20) {
250     LOGDEBUG(
251       Logger::debug(anna::functions::asString("Invalid TCP header length: %d bytes", size_tcp), ANNA_FILE_LOCATION));
252     return NULL;
253   }
254
255   int payloadOffset = SIZE_ETHERNET + size_ip + size_tcp;
256   LOGDEBUG(
257     Logger::debug(anna::functions::asString("PayloadOffset=%d", payloadOffset), ANNA_FILE_LOCATION));
258   payloadSize = packetSize - payloadOffset;
259   return ((u_char *)(packet + payloadOffset));
260 }
261
262 // Sniffing callback //////////////////////////////////////////////////////////////////////////////////////
263 void my_callback(u_char *useless, const struct pcap_pkthdr* pkthdr,
264                  const u_char* packet) {
265   static int count = 1;
266   static Payload auxPayload;
267   int packetSize = pkthdr->len;
268   int payloadSize;
269   std::string srcIp, dstIp;
270   int fragmentId, fragmentOffset;
271   bool dfFlag, mfFlag;
272   const u_char* payload = getPayload(packet, packetSize, payloadSize, srcIp,
273                                      dstIp, fragmentId, dfFlag, mfFlag, fragmentOffset);
274
275   if(payload && (payloadSize > 0)) {
276     LOGDEBUG(
277       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);
278       Logger::debug(msg, ANNA_FILE_LOCATION););
279     auxPayload.setDiameterLength(
280       (payload[1] << 16) + (payload[2] << 8) + payload[3]);
281     auxPayload.setSourceIP(srcIp);
282     auxPayload.setDestinationIP(dstIp);
283     auxPayload.setTimestamp(pkthdr->ts.tv_sec);
284     auxPayload.setTimestampU(pkthdr->ts.tv_usec);
285     bool completed = auxPayload.appendData((const char *) payload, payloadSize);
286
287     if(completed) {
288       G_payloads[count] = auxPayload;
289       auxPayload.reset();
290     }
291   }
292
293   count++;
294 }
295
296 bool getDataBlockFromHexFile(const std::string &pathfile, anna::DataBlock &db) throw() {
297   // Get hex string
298   static char buffer[8192];
299   std::ifstream infile(pathfile.c_str(), std::ifstream::in);
300
301   if(infile.is_open()) {
302     infile >> buffer;
303     std::string hexString(buffer, strlen(buffer));
304     // Allow colon separator in hex string: we have to remove them before processing with 'fromHexString':
305     hexString.erase(std::remove(hexString.begin(), hexString.end(), ':'), hexString.end());
306     LOGDEBUG(
307       std::string msg = "Hex string (remove colons if exists): ";
308       msg += hexString;
309       anna::Logger::debug(msg, ANNA_FILE_LOCATION);
310     );
311     anna::functions::fromHexString(hexString, db);
312     // Close file
313     infile.close();
314     return true;
315   }
316
317   return false;
318 }
319
320
321 void _exit(const std::string &message, int resultCode = 1) {
322   if(resultCode)
323     std::cerr << message << std::endl << std::endl;
324   else
325     std::cout << message << std::endl << std::endl;
326
327   exit(resultCode);
328 }
329
330 // Decodes a diameter message coming from a datablock
331 void decodeDataBlock(const anna::DataBlock &db, unsigned int & detectedApplicationId) throw() {
332   try {
333     detectedApplicationId = anna::diameter::codec::functions::getApplicationId(db);
334     G_codecEngine->setDictionary(detectedApplicationId);
335     G_codecMsg.decode(db);
336   } catch(RuntimeException &ex) {
337     _exit(ex.asString());
338   }
339 }
340
341
342 //-------------------------------------------------------------------
343 int main(int argc, char **argv) {
344   std::string exec = argv[0];
345   std::string execBN = exec.substr(exec.find_last_of("/") + 1);
346   std::string filetrace = execBN + ".trace";
347   std::cout << std::endl;
348
349   //check command line arguments
350   if(argc < 3) {
351     std::string msg = "Usage: "; msg += exec;
352     msg += " <stacks> <input file> [--no-validation] [--ignore-flags]\n\n";
353     msg += "       stacks:                  <id1#dictionary1,id2#dictionary2,...,idN#dictionaryN>\n";
354     msg += "                                This is a list of comma-separated stacks defined by a #-separated pair <application-id#xml dictionary pathfile>\n";
355     msg += "                                If only one stack is provided, application-id could be omitted and then, all the messages will be decoded with the\n";
356     msg += "                                dictionary regardless the value of the application-id (the stack will be registered with id=0).\n";
357     msg += "       Input file:              normally a pcap file, but hexadecimal content (colons allowed) can also be decoded (use '.hex' extension).\n";
358     msg += "       --no-validation:         no validation is performed.\n";
359     msg += "       --ignore-flags:          wrong flags regarding dictionary are ignored in xml representation.";
360     _exit(msg);
361   }
362
363   // Command-line parameters:
364   std::string stacks = argv[1];
365   std::string inputFile = argv[2];
366   bool isHex = (inputFile.substr(inputFile.find_last_of(".") + 1) == "hex");
367   std::string outputFile = inputFile; // extension will be added later
368   std::string optionals;
369   int indx = 3;
370
371   while(indx < argc) { optionals += " "; optionals += argv[indx]; indx++; }
372
373   bool no_validation = (optionals.find("--no-validation") != std::string::npos);
374   bool ignore_flags = (optionals.find("--ignore-flags") != std::string::npos);
375   Logger::setLevel(Logger::Debug);
376   Logger::initialize(execBN.c_str(), new TraceWriter(filetrace.c_str(), 2048000));
377   G_codecEngine = new anna::diameter::codec::Engine();
378   anna::diameter::stack::Engine &stackEngine =
379     anna::diameter::stack::Engine::instantiate();
380
381   // Register stacks:
382   try {
383     anna::Tokenizer stacksTok;
384     stacksTok.apply(stacks, ",");
385     anna::Tokenizer::const_iterator stacks_it, stack_it;
386
387     for(stacks_it = stacksTok.begin(); stacks_it != stacksTok.end(); stacks_it++) {
388       std::string stack = anna::Tokenizer::data(stacks_it);
389       anna::Tokenizer stackTok;
390       stackTok.apply(stack, "#");
391
392       if(stackTok.size() == 1) {
393         if(stacksTok.size() != 1)
394           throw anna::RuntimeException("Application Id value is mandatory when more than one stack is going to be configured", ANNA_FILE_LOCATION);
395
396         anna::diameter::stack::Dictionary * d = stackEngine.createDictionary(0 /* no matter */, stack); // the stack is the dictionary
397         G_codecEngine->setDictionary(d);
398         break;
399       }
400
401       if(stackTok.size() != 2)
402         throw anna::RuntimeException("Each stack must be in the form '<application-id>#<xml dictionary pathfile>'", ANNA_FILE_LOCATION);
403
404       stack_it = stackTok.begin();
405       unsigned int stackId = atoll(anna::Tokenizer::data(stack_it));
406       stack_it++;
407       std::string file = anna::Tokenizer::data(stack_it);
408       anna::diameter::stack::Dictionary * d = stackEngine.createDictionary(stackId, file);
409     }
410
411     std::cout << "Stacks provided:          " << std::endl;
412     std::cout << anna::functions::tab(stackEngine.asString(false /* light */));
413     std::cout << std::endl;
414     std::cout << "Input file provided:      " << inputFile << std::endl;
415     std::cout << "Validation:               " << (!no_validation ? "yes" : "no") << std::endl;
416     std::cout << "Ignore Flags:             " << (ignore_flags ? "yes" : "no") << std::endl;
417     std::cout << std::endl;
418   } catch(anna::RuntimeException &ex) {
419     _exit(ex.asString());
420   }
421
422   // Validation kindness
423   if(no_validation) G_codecEngine->setValidationMode(anna::diameter::codec::EngineImpl::ValidationMode::Never);
424
425   if(ignore_flags) G_codecEngine->ignoreFlagsOnValidation(true);
426
427   // Tracing:
428   //if (cl.exists("trace"))
429   //   anna::Logger::setLevel(anna::Logger::asLevel(cl.getValue("trace")));
430   // Check hex content input file (look extension):
431   anna::DataBlock db_aux(true);
432   unsigned int detectedApplicationId;
433
434   if(isHex) {
435     if(!getDataBlockFromHexFile(inputFile, db_aux))
436       _exit("Error reading hex file provided");
437
438     // Decode datablock:
439     decodeDataBlock(db_aux, detectedApplicationId);
440     // Open output file:
441     outputFile += ".as.xml";
442     std::ofstream out(outputFile, std::ifstream::out);
443     out << G_codecMsg.asXMLString();
444     // Close output file:
445     out.close();
446     std::string msg = "Open '"; msg += filetrace; msg += "' in order to see process traces.\n";
447     msg += "Open '"; msg += outputFile; msg += "' to see decoding results.";
448     _exit(msg, 0);
449   }
450
451   // Normal input: pcap file:
452   // SNIFFING //////////////////////////////////////////////////////////////////////////////////////////////7
453   //temporary packet buffers
454   struct pcap_pkthdr header; // The header that pcap gives us
455   const u_char *packet;      // The actual packet
456   //------------------
457   //open the pcap file
458   pcap_t *handle;
459   char errbuf[PCAP_ERRBUF_SIZE];        //not sure what to do with this, oh well
460   handle = pcap_open_offline(inputFile.c_str(), errbuf); //call pcap library function
461
462   if(handle == NULL) _exit(errbuf, 2);
463
464   //begin processing the packets in this particular file
465   int packets = -1;
466
467   try {
468     while(packets != 0)
469       packets = pcap_dispatch(handle, -1, (pcap_handler) my_callback, NULL);
470   } catch(RuntimeException &ex) {
471     _exit(ex.asString());
472   }
473
474   pcap_close(handle);  //close the pcap file
475   // Print payloads //////////////////////////////////////////////////////////////////////////////////////////////
476   // Open output file:
477   outputFile += ".report";
478   std::ofstream out(outputFile, std::ifstream::out);
479
480   for(payloads_it it = G_payloads.begin(); it != G_payloads.end(); it++) {
481     LOGDEBUG(
482       Logger::debug(anna::functions::asString("Dumping frame %d", it->first), ANNA_FILE_LOCATION));
483     time_t ts = (it->second).getTimestamp();
484     int tsu = (it->second).getTimestampU();
485     std::string ts_str = ctime(&ts);
486     ts_str.erase(ts_str.find("\n"));
487     out << std::endl;
488     out
489         << "==================================================================================================="
490         << std::endl;
491     out << "Date:            " << ts_str << std::endl;
492     out << "Timestamp:       " << std::to_string(ts) << "."
493         << std::to_string(tsu) << std::endl;
494     out << "Origin IP:       " << (it->second).getSourceIP() << std::endl;
495     out << "Destination IP:  " << (it->second).getDestinationIP() << std::endl;
496     // decode hex string:
497     anna::functions::fromHexString((it->second).getDataAsHex(), db_aux);
498     // Decode datablock:
499     decodeDataBlock(db_aux, detectedApplicationId);
500     // Stack identification:
501     //out << "Application Id:  " << detectedApplicationId << std::endl;
502     out << "Dictionary used: " << G_codecEngine->getDictionary()->getName() << std::endl;
503     out << std::endl;
504     out << G_codecMsg.asXMLString();
505   }
506
507   // Close output file:
508   out.close();
509   std::string msg = "Open '"; msg += filetrace; msg += "' in order to see process traces.\n";
510   msg += "Open '"; msg += outputFile; msg += "' to see decoding results.";
511   _exit(msg, 0);
512 }
513