pcap_layer.cc 11.2 KB
Newer Older
garciay's avatar
garciay committed
#if (OSTYPE == linux)
garciay's avatar
garciay committed
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <chrono>

#include "Port.hh"
garciay's avatar
garciay committed
#include "pcap_layer_factory.hh"
garciay's avatar
garciay committed

#include "loggers.hh"

garciay's avatar
garciay committed
pcap_layer::pcap_layer(const std::string& p_type, const std::string& param) : layer(p_type), PORT(p_type.c_str()), _params(), _device(NULL), _pcap_h(-1), _thread(NULL), _running(FALSE), _resume(), _sent_file(NULL), _time_key("pcap_layer::Handle_Fd_Event_Readable") {
garciay's avatar
garciay committed
  loggers::get_instance().log(">>> pcap_layer::pcap_layer: %s, %s", to_string().c_str(), param.c_str());
garciay's avatar
garciay committed
  _fd[0] = -1; _fd[1] = -1;
garciay's avatar
garciay committed
  // Setup parameters
garciay's avatar
garciay committed
  params::convert(_params, param);
garciay's avatar
garciay committed
  //_params.log();
garciay's avatar
garciay committed
  // Prepare capture processing
  char error_buffer[PCAP_ERRBUF_SIZE];
garciay's avatar
garciay committed
  params::const_iterator it = _params.find(params::nic); 
garciay's avatar
garciay committed
  if ((it != _params.end()) && !it->second.empty()) { // Use online capture
    // Fetch the network address and network mask
    bpf_u_int32 mask; // subnet mask
garciay's avatar
garciay committed
    bpf_u_int32 net; // ip address
garciay's avatar
garciay committed
    if (pcap_lookupnet(_params[params::nic].c_str(), &net, &mask, error_buffer) != 0) {
      loggers::get_instance().error("pcap_layer::pcap_layer: pcap_layer::pcap_layer: Failed to fetch newtork address for device %s", _params[params::nic].c_str());
garciay's avatar
garciay committed
    }
garciay's avatar
garciay committed
    loggers::get_instance().log("pcap_layer::pcap_layer: Device %s Network address: %d", _params[params::nic].c_str(), net);
garciay's avatar
garciay committed
    // Open the device
garciay's avatar
garciay committed
    _device = pcap_open_live(_params[params::nic].c_str(), 65536, 1, 1000, error_buffer); // TODO Replace hard coded values by pcap_layer::<constants>
garciay's avatar
garciay committed
    if (_device == NULL) {
garciay's avatar
garciay committed
      loggers::get_instance().error("pcap_layer::pcap_layer: Failed to open device %s", _params[params::nic].c_str());
garciay's avatar
garciay committed
    } // else, continue
    // Set non-blocking flag for the polling procedure
    if (pcap_setnonblock(_device, 1, error_buffer) != 0) {
garciay's avatar
garciay committed
      loggers::get_instance().error("pcap_layer::pcap_layer: Failed to set blocking mode: %s", error_buffer);
garciay's avatar
garciay committed
    }
    // Retrieve the device file handler
    _pcap_h = pcap_get_selectable_fd(_device);
    if (_pcap_h == -1) {
garciay's avatar
garciay committed
      loggers::get_instance().error("pcap_layer::pcap_layer: Failed to get device handler");
garciay's avatar
garciay committed
    }
  } else {
    // Check file name
    it = _params.find(std::string("file"));
garciay's avatar
garciay committed
    if ((it != _params.cend()) && !it->second.empty()) { // Use offline capture
garciay's avatar
garciay committed
      struct stat s = {0};
      if ((stat(_params["file"].c_str(), &s) != 0) || !S_ISREG(s.st_mode)) {
garciay's avatar
garciay committed
        loggers::get_instance().error("pcap_layer::pcap_layer: Failed to acces PCAP file %s", _params["file"].c_str());
garciay's avatar
garciay committed
      }
      // File exist, open it
      _device = pcap_open_offline(_params["file"].c_str(), error_buffer);
      if (_device == NULL) {
garciay's avatar
garciay committed
        loggers::get_instance().error("pcap_layer::pcap_layer: Failed to open PCAP file %s", error_buffer);	
garciay's avatar
garciay committed
      } // else, continue
garciay's avatar
garciay committed
      // Create the dump file for the sent packet based on the openned file name and the current time in milliseconds
      it =  _params.find("save_mode");
      if ((it != _params.cend()) && (it->second.compare("1") == 0)) {
garciay's avatar
garciay committed
        unsigned long ms = std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1);
        std::string ext("_" + std::to_string(ms));
        int i = _params["file"].find(".pcap");
        if (i > 0) {
          std::string f(_params["file"].substr(0, i) + ext + ".pcap");
garciay's avatar
garciay committed
          loggers::get_instance().log("pcap_layer::pcap_layer: Save file name: %s", f.c_str());
garciay's avatar
garciay committed
          if ((_sent_file = pcap_dump_open(_device, f.c_str())) == NULL) {
garciay's avatar
garciay committed
            loggers::get_instance().warning("pcap_layer::pcap_layer: Failed to open save file %s", f.c_str());
garciay's avatar
garciay committed
          }
        }
garciay's avatar
garciay committed
      } // else, nothing to do
garciay's avatar
garciay committed
    } else {
garciay's avatar
garciay committed
      loggers::get_instance().error("pcap_layer::pcap_layer: Failed to open PCAP file %s", error_buffer);
garciay's avatar
garciay committed
    }
  }
  // Setup filter
garciay's avatar
garciay committed
  std::string filter = "";
garciay's avatar
garciay committed
  it = _params.find(params::mac_src);
garciay's avatar
garciay committed
  if (it == _params.end()) { // Not found
garciay's avatar
garciay committed
    loggers::get_instance().error("pcap_layer::pcap_layer: mac_src parameter not found, cannot continue");
garciay's avatar
garciay committed
  } else {
    // Reject ITS messages sent by this component
garciay's avatar
garciay committed
    filter = "not ether src " + _params[params::mac_src]; 
garciay's avatar
garciay committed
    // Accept ITS broadcasted to this componenet
garciay's avatar
garciay committed
    filter += " and (ether dst " + _params[params::mac_src];
garciay's avatar
garciay committed
    // Accept ITS broadcasted messages
garciay's avatar
garciay committed
    it = _params.find(params::mac_bc);
garciay's avatar
garciay committed
    if ((it != _params.end()) && !it->second.empty()) {
      filter += " or ether dst " + it->second + ")";
    } else {
      filter += " or ether dst ffffffffffff) ";
garciay's avatar
garciay committed
    }
garciay's avatar
garciay committed
    // Add user defined filter
    it = _params.find(std::string("filter"));
    if ((it != _params.end()) && !it->second.empty()) {
      filter += _params["filter"];
    } // else nothing to do
  }
  // Log final PCAP filter
garciay's avatar
garciay committed
  loggers::get_instance().user("pcap_layer::pcap_layer: Filter: %s", filter.c_str());
garciay's avatar
garciay committed
  if (!filter.empty()) {
    struct bpf_program f = {0};
    if (pcap_compile(_device, &f, filter.c_str(), 1, PCAP_NETMASK_UNKNOWN) != 0) {
garciay's avatar
garciay committed
      loggers::get_instance().error("pcap_layer::pcap_layer: Failed to compile PCAP filter");
garciay's avatar
garciay committed
    }
    if (pcap_setfilter(_device, &f) != 0) {
garciay's avatar
garciay committed
      loggers::get_instance().error("pcap_layer::pcap_layer: Failed to set PCAP filter");
garciay's avatar
garciay committed
    }
    pcap_freecode(&f);
  }
  // Pass the device file handler to the polling procedure
garciay's avatar
garciay committed
  if (_pcap_h != -1) { // Live capture
    Handler_Add_Fd_Read(_pcap_h);
garciay's avatar
garciay committed
  } else { // Offline capture
garciay's avatar
garciay committed
    // Create a pipe
    if (pipe2(_fd, O_NONBLOCK) == -1) {
garciay's avatar
garciay committed
      loggers::get_instance().error("pcap_layer::pcap_layer: Failed to create a pipe: %s", ::strerror(errno));
garciay's avatar
garciay committed
    }
    // Pass the pipe handler to the polling procedure
garciay's avatar
garciay committed
    loggers::get_instance().log("pcap_layer::pcap_layer: Call handler with descriptor %d", _fd[0]);
garciay's avatar
garciay committed
    Handler_Add_Fd_Read(_fd[0]);
garciay's avatar
garciay committed
    // Create the offline reader thread
garciay's avatar
garciay committed
    _thread = new std::thread(&pcap_layer::run, (void *)this);
garciay's avatar
garciay committed
    if (_thread == NULL) {
garciay's avatar
garciay committed
      loggers::get_instance().error("pcap_layer::pcap_layer: Failed to start offline thread");
garciay's avatar
garciay committed
    }
garciay's avatar
garciay committed
    // Start it to dispatch packet to a pipe
garciay's avatar
garciay committed
    while (_running == FALSE) {
garciay's avatar
garciay committed
      std::this_thread::sleep_for(std::chrono::milliseconds(500));
garciay's avatar
garciay committed
    }
garciay's avatar
garciay committed
    // Thread was started
garciay's avatar
garciay committed
    loggers::get_instance().log("<<< pcap_layer::pcap_layer");
garciay's avatar
garciay committed
  }
} // End of ctor

garciay's avatar
garciay committed
pcap_layer::~pcap_layer() {
  loggers::get_instance().log(">>> pcap_layer::~pcap_layer");
garciay's avatar
garciay committed
  
garciay's avatar
garciay committed
  if (_device != NULL) {
garciay's avatar
garciay committed
    if (_thread != NULL) {
garciay's avatar
garciay committed
      _running = FALSE;
      // Wait for the working thread to terminate
garciay's avatar
garciay committed
      _thread->join();
garciay's avatar
garciay committed
      loggers::get_instance().log("pcap_layer::~pcap_layer: Thread were stops");
garciay's avatar
garciay committed
      // Cleanup
garciay's avatar
garciay committed
      delete _thread;
garciay's avatar
garciay committed
      close(_fd[0]);
      close(_fd[1]);
    }
garciay's avatar
garciay committed
    if (_sent_file != NULL) {
      pcap_dump_close(_sent_file);
    }
garciay's avatar
garciay committed
    pcap_close(_device);
  }
} // End of dtor

garciay's avatar
garciay committed
void* pcap_layer::run(void* p_this) {
  loggers::get_instance().log(">>> pcap_layer::run");
garciay's avatar
garciay committed

  // Pointer the main object
garciay's avatar
garciay committed
  pcap_layer& p = *static_cast<pcap_layer *>(p_this);
garciay's avatar
garciay committed
  // Wait a little bit before to start sending packet
garciay's avatar
garciay committed
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
garciay's avatar
garciay committed
  params::const_iterator it = p._params.find("frame_offset");
garciay's avatar
garciay committed
  if ((it != p._params.cend()) && (it->second.compare("0") != 0)) {
    // TODO Try t use PCAP filter to start directly to the correct frame offset
    /*try {
      unsigned int offset = std::stoul(str_dec, &s);
      // Skip frames
      struct pcap_pkthdr *pkt_header;
      const u_char *pkt_data;
      int result = pcap_next_ex(_device, &pkt_header, &pkt_data);
      if (result == 1) { // Succeed
      }
garciay's avatar
garciay committed
      }
      catch (invalid_argument& i) {
      }
      catch (out_of_range& o) {
      }*/
garciay's avatar
garciay committed
  }
garciay's avatar
garciay committed
  // Let's go
garciay's avatar
garciay committed
  p._running = TRUE;
garciay's avatar
garciay committed
  while (p._running) { // Loop while _running flag is up
    if (p._resume.try_lock() == TRUE) { // Previous packet was consumed, lock for the next one
      write(p._fd[1], "\n", 1); // Any character will do the job
garciay's avatar
garciay committed
    } else { // not ready yet
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
garciay's avatar
garciay committed
    }
  }
garciay's avatar
garciay committed
  
garciay's avatar
garciay committed
  loggers::get_instance().log("<<< pcap_layer::run");
garciay's avatar
garciay committed
  return NULL;
garciay's avatar
garciay committed
void pcap_layer::send_data(OCTETSTRING& data, params& params) {
garciay's avatar
garciay committed
  loggers::get_instance().log_msg(">>> pcap_layer::send_data: ", data);
garciay's avatar
garciay committed

  if (_pcap_h != -1) { // Check if offline mode is used
    if (pcap_sendpacket(_device, static_cast<const unsigned char *>(data), data.lengthof()) == -1) {
garciay's avatar
garciay committed
      loggers::get_instance().error("pcap_layer::send_data: Failed to send packet: %s", pcap_geterr(_device));
garciay's avatar
garciay committed
    }
garciay's avatar
garciay committed
  } else if (_sent_file != NULL) {
    struct pcap_pkthdr  hdr;
    std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
    std::chrono::milliseconds ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
    hdr.ts.tv_sec = ms.count() / 1000;
    hdr.ts.tv_usec = (ms.count() % 1000) * 1000;
    hdr.caplen = data.lengthof();
    hdr.len = hdr.caplen;
    pcap_dump((u_char *)_sent_file, &hdr, static_cast<const unsigned char *>(data));
garciay's avatar
garciay committed
  } else {
garciay's avatar
garciay committed
    loggers::get_instance().log("pcap_layer::send_data: Offline mode, operation was skipped");
garciay's avatar
garciay committed
  }
garciay's avatar
garciay committed
}

garciay's avatar
garciay committed
void pcap_layer::receive_data(OCTETSTRING& data, params& params) {
garciay's avatar
garciay committed
  loggers::get_instance().log(">>> pcap_layer::receive_data: Received %d bytes", data.lengthof());
garciay's avatar
garciay committed
  loggers::get_instance().log_to_hexa("Packet dump", data);
  
garciay's avatar
garciay committed
  // Pass the packet to the upper layers
garciay's avatar
garciay committed
  receive_to_all_layers(data, params);
garciay's avatar
garciay committed
void pcap_layer::Handle_Fd_Event_Readable(int fd) {
  //loggers::get_instance().log(">>> pcap_layer::Handle_Fd_Event_Readable: %d", fd);
garciay's avatar
garciay committed
  
  struct pcap_pkthdr *pkt_header;
  const u_char *pkt_data;
  int result = pcap_next_ex(_device, &pkt_header, &pkt_data);
  if (result == 1) { // Succeed
    if (pkt_header->caplen > 14) { // Reject too small packet
garciay's avatar
garciay committed
      //loggers::get_instance().log("pcap_layer::Handle_Fd_Event_Readable: %.6d - %d", pkt_header->ts.tv_usec, pkt_header->len);
garciay's avatar
garciay committed
      // Fill parameters from PCAP layer
garciay's avatar
garciay committed
      params params;
garciay's avatar
garciay committed
      params.insert(std::pair<std::string, std::string>(std::string("timestamp"), std::to_string(pkt_header->ts.tv_usec)));
      // Process the packet at this layer
garciay's avatar
garciay committed
      OCTETSTRING os(pkt_header->caplen, pkt_data);
garciay's avatar
garciay committed
      //loggers::get_instance().log_to_hexa("pcap_layer::Handle_Fd_Event_Readable: ", os);
garciay's avatar
garciay committed
      // TODO Case of caplen != len !!!
garciay's avatar
garciay committed
      float duration;
      loggers::get_instance().set_start_time(_time_key);
garciay's avatar
garciay committed
      this->receive_data(os, params); // TODO Check execution time for decoding operation
garciay's avatar
garciay committed
      loggers::get_instance().set_stop_time(_time_key, duration);      
garciay's avatar
garciay committed
    }
  } // else, skip the packet
  // Specific to offline mode
  if (_fd[0] != -1) { // Check if offline mode is used
garciay's avatar
garciay committed
    //loggers::get_instance().log("pcap_layer::Handle_Fd_Event_Readable: Read pipe");
garciay's avatar
garciay committed
    char c[2];
    read(_fd[0], &c, 1);
    if (result == -2) { // End of file, therminate worker thread
      _running = FALSE;
    } else { // Get next packet
garciay's avatar
garciay committed
      //loggers::get_instance().log("pcap_layer::Handle_Fd_Event_Readable: pcap_next_ex failed: result=%d", result);
garciay's avatar
garciay committed
      _resume.unlock();
garciay's avatar
garciay committed
    }
  } // else, nothing to do
garciay's avatar
garciay committed
pcap_layer_factory pcap_layer_factory::_f;