1 // ANNA - Anna is Not Nothingness Anymore
3 // (c) Copyright 2005-2014 Eduardo Ramos Testillano & Francisco Ruiz Rayo
5 // http://redmine.teslayout.com/projects/anna-suite
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
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
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.
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.
33 // Authors: eduardo.ramos.testillano@gmail.com
34 // cisco.tierra@gmail.com
39 #include <netinet/ip.h>
40 #include <arpa/inet.h>
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);
63 using namespace anna::diameter;
65 // Payload and frame metadata /////////////////////////////////////////////////////////////////////////////
68 std::string _sourceIP;
69 std::string _destinationIP;
71 int _timestampU; // usecs
73 size_t _diameterLength;
80 void setDiameterLength(size_t dl) {
81 if(_diameterLength == -1) {
84 Logger::debug(anna::functions::asString("Diameter message length: %d bytes", dl), ANNA_FILE_LOCATION));
88 void setSourceIP(const std::string &srcIP) throw() {
91 void setDestinationIP(const std::string &dstIP) throw() {
92 _destinationIP = dstIP;
94 void setTimestamp(time_t ts) throw() {
97 void setTimestampU(int tsu) throw() {
100 // Returns true if completed:
101 bool appendData(const char *data, size_t size) throw(RuntimeException) {
103 Logger::debug(anna::functions::asString("Appending %d bytes", size), ANNA_FILE_LOCATION));
104 _data.append(data, size);
106 if(_data.size() > _diameterLength)
107 throw RuntimeException(
108 "Data overflow (unexpected offset exceed diameter message length)",
111 if(_data.size() < _diameterLength)
114 LOGDEBUG(anna::Logger::debug("Completed!", ANNA_FILE_LOCATION));
118 void reset() throw() {
124 _diameterLength = -1; // not calculated yet
127 const std::string &getSourceIP() const throw() {
130 const std::string &getDestinationIP() const throw() {
131 return _destinationIP;
133 time_t getTimestamp() const throw() {
136 int getTimestampU() const throw() {
139 const std::string &getData() const throw() {
142 std::string getDataAsHex() const throw() {
143 return anna::functions::asHexString(
144 anna::DataBlock(_data.c_str(), _data.size()));
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;
155 // Sniffing structures ////////////////////////////////////////////////////////////////////////////////////
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
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 */
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 */
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)
192 typedef u_int tcp_seq;
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)
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 */
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 */
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
231 Logger::debug(anna::functions::asString("Invalid IP header length: %d bytes", size_ip), ANNA_FILE_LOCATION));
235 static char str[INET_ADDRSTRLEN];
236 inet_ntop(AF_INET, &(ip->ip_src), str, INET_ADDRSTRLEN);
238 inet_ntop(AF_INET, &(ip->ip_dst), str, INET_ADDRSTRLEN);
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;
251 Logger::debug(anna::functions::asString("Invalid TCP header length: %d bytes", size_tcp), ANNA_FILE_LOCATION));
255 int payloadOffset = SIZE_ETHERNET + size_ip + size_tcp;
257 Logger::debug(anna::functions::asString("PayloadOffset=%d", payloadOffset), ANNA_FILE_LOCATION));
258 payloadSize = packetSize - payloadOffset;
259 return ((u_char *)(packet + payloadOffset));
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;
269 std::string srcIp, dstIp;
270 int fragmentId, fragmentOffset;
272 const u_char* payload = getPayload(packet, packetSize, payloadSize, srcIp,
273 dstIp, fragmentId, dfFlag, mfFlag, fragmentOffset);
275 if(payload && (payloadSize > 0)) {
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);
288 G_payloads[count] = auxPayload;
296 bool getDataBlockFromHexFile(const std::string &pathfile, anna::DataBlock &db) throw() {
298 static char buffer[8192];
299 std::ifstream infile(pathfile.c_str(), std::ifstream::in);
301 if(infile.is_open()) {
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());
307 std::string msg = "Hex string (remove colons if exists): ";
309 anna::Logger::debug(msg, ANNA_FILE_LOCATION);
311 anna::functions::fromHexString(hexString, db);
321 void _exit(const std::string &message, int resultCode = 1) {
323 std::cerr << message << std::endl << std::endl;
325 std::cout << message << std::endl << std::endl;
330 // Decodes a diameter message coming from a datablock
331 void decodeDataBlock(const anna::DataBlock &db, unsigned int & detectedApplicationId) throw() {
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());
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;
349 //check command line arguments
351 std::string msg = "Usage: "; msg += exec;
352 msg += " <stacks> <input file> [--no-validation] [--ignore-flags] [--debug]\n\n";
353 msg += " stacks: <id1,dictionary1#id2,dictionary2#...#idN,dictionaryN>\n";
354 msg += " This is a list of #-separated stacks defined by a comma-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.\n";
360 msg += " --debug: activates debug level traces (warning by default).";
364 // Command-line parameters:
365 std::string stacks = argv[1];
366 std::string inputFile = argv[2];
367 bool isHex = (inputFile.substr(inputFile.find_last_of(".") + 1) == "hex");
368 std::string outputFile = inputFile; // extension will be added later
369 std::string optionals;
372 while(indx < argc) { optionals += " "; optionals += argv[indx]; indx++; }
374 bool no_validation = (optionals.find("--no-validation") != std::string::npos);
375 bool ignore_flags = (optionals.find("--ignore-flags") != std::string::npos);
376 bool debug = (optionals.find("--debug") != std::string::npos);
377 Logger::setLevel(debug ? Logger::Debug:Logger::Warning);
378 Logger::initialize(execBN.c_str(), new TraceWriter(filetrace.c_str(), 2048000));
379 G_codecEngine = new anna::diameter::codec::Engine();
380 anna::diameter::stack::Engine &stackEngine =
381 anna::diameter::stack::Engine::instantiate();
385 anna::Tokenizer stacksTok;
386 stacksTok.apply(stacks, "#");
387 anna::Tokenizer::const_iterator stacks_it, stack_it;
389 for(stacks_it = stacksTok.begin(); stacks_it != stacksTok.end(); stacks_it++) {
390 std::string stack = anna::Tokenizer::data(stacks_it);
391 anna::Tokenizer stackTok;
392 stackTok.apply(stack, ",");
394 if(stackTok.size() == 1) {
395 if(stacksTok.size() != 1)
396 throw anna::RuntimeException("Application Id value is mandatory when more than one stack is going to be configured", ANNA_FILE_LOCATION);
398 anna::diameter::stack::Dictionary * d = stackEngine.createDictionary(0 /* no matter */, stack); // the stack is the dictionary
399 G_codecEngine->setDictionary(d);
403 if(stackTok.size() != 2)
404 throw anna::RuntimeException("Each stack must be in the form '<application-id>#<xml dictionary pathfile>'", ANNA_FILE_LOCATION);
406 stack_it = stackTok.begin();
407 unsigned int stackId = atoll(anna::Tokenizer::data(stack_it));
409 std::string file = anna::Tokenizer::data(stack_it);
410 anna::diameter::stack::Dictionary * d = stackEngine.createDictionary(stackId, file);
413 std::cout << "Stacks provided: " << std::endl;
414 std::cout << anna::functions::tab(stackEngine.asString(false /* light */));
415 std::cout << std::endl;
416 std::cout << "Input file provided: " << inputFile << std::endl;
417 std::cout << "Validation: " << (!no_validation ? "yes" : "no") << std::endl;
418 std::cout << "Ignore Flags: " << (ignore_flags ? "yes" : "no") << std::endl;
419 std::cout << std::endl;
420 } catch(anna::RuntimeException &ex) {
421 _exit(ex.asString());
424 // Validation kindness
425 G_codecEngine->setValidationDepth(anna::diameter::codec::EngineImpl::ValidationDepth::Complete); // complete validation for better reports
426 if(no_validation) G_codecEngine->setValidationMode(anna::diameter::codec::EngineImpl::ValidationMode::Never);
428 if(ignore_flags) G_codecEngine->ignoreFlagsOnValidation(true);
431 //if (cl.exists("trace"))
432 // anna::Logger::setLevel(anna::Logger::asLevel(cl.getValue("trace")));
433 // Check hex content input file (look extension):
434 anna::DataBlock db_aux(true);
435 unsigned int detectedApplicationId;
438 if(!getDataBlockFromHexFile(inputFile, db_aux))
439 _exit("Error reading hex file provided");
442 decodeDataBlock(db_aux, detectedApplicationId);
444 outputFile += ".as.xml";
445 std::ofstream out(outputFile.c_str(), std::ifstream::out);
446 out << G_codecMsg.asXMLString();
447 // Close output file:
449 std::string msg = "Open '"; msg += filetrace; msg += "' in order to see process traces.\n";
450 msg += "Open '"; msg += outputFile; msg += "' to see decoding results.";
454 // Normal input: pcap file:
455 // SNIFFING //////////////////////////////////////////////////////////////////////////////////////////////7
456 //temporary packet buffers
457 struct pcap_pkthdr header; // The header that pcap gives us
458 const u_char *packet; // The actual packet
462 char errbuf[PCAP_ERRBUF_SIZE]; //not sure what to do with this, oh well
463 handle = pcap_open_offline(inputFile.c_str(), errbuf); //call pcap library function
465 if(handle == NULL) _exit(errbuf, 2);
467 //begin processing the packets in this particular file
472 packets = pcap_dispatch(handle, -1, (pcap_handler) my_callback, NULL);
473 } catch(RuntimeException &ex) {
474 _exit(ex.asString());
477 pcap_close(handle); //close the pcap file
478 // Print payloads //////////////////////////////////////////////////////////////////////////////////////////////
480 outputFile += ".report";
481 std::ofstream out(outputFile.c_str(), std::ifstream::out);
483 for(payloads_it it = G_payloads.begin(); it != G_payloads.end(); it++) {
485 Logger::debug(anna::functions::asString("Dumping frame %d", it->first), ANNA_FILE_LOCATION));
486 time_t ts = (it->second).getTimestamp();
487 int tsu = (it->second).getTimestampU();
488 std::string ts_str = ctime(&ts);
489 ts_str.erase(ts_str.find("\n"));
492 << "==================================================================================================="
494 out << "Date: " << ts_str << std::endl;
495 out << "Timestamp: " << anna::functions::asString((int)ts) << "."
496 << anna::functions::asString((int)tsu) << std::endl;
497 out << "Origin IP: " << (it->second).getSourceIP() << std::endl;
498 out << "Destination IP: " << (it->second).getDestinationIP() << std::endl;
499 // decode hex string:
500 anna::functions::fromHexString((it->second).getDataAsHex(), db_aux);
502 decodeDataBlock(db_aux, detectedApplicationId);
503 // Stack identification:
504 //out << "Application Id: " << detectedApplicationId << std::endl;
505 out << "Dictionary used: " << G_codecEngine->getDictionary()->getName() << std::endl;
507 out << G_codecMsg.asXMLString();
510 // Close output file:
512 std::string msg = "Open '"; msg += filetrace; msg += "' in order to see process traces.\n";
513 msg += "Open '"; msg += outputFile; msg += "' to see decoding results.";