From f6585fcd5f29e6e4ced04e280bd45033cb4b7e79 Mon Sep 17 00:00:00 2001 From: harrydevnull Date: Wed, 6 Jul 2016 11:16:31 -0400 Subject: [PATCH] Updated the library to create pcap files under the hood it makes use of the libpcap's capability to create pcap the add-on makes use of the Async worker --- .gitignore | 4 +- examples/pcap_dump_test.js | 17 ++++ package.json | 1 + pcap.js | 2 + pcap_binding.cc | 202 +++++++++++++++++++++++++++++++++++++ pcap_dump.js | 90 +++++++++++++++++ pcap_session.cc | 120 ---------------------- pcap_session.h | 2 - spec/pcap_dump.spec.js | 128 +++++++++++++++++++++++ spec/pcap_test.js | 100 ++++++++++++++++++ tmp.pcap | Bin 0 -> 24 bytes 11 files changed, 543 insertions(+), 123 deletions(-) create mode 100644 examples/pcap_dump_test.js create mode 100644 pcap_dump.js create mode 100644 spec/pcap_dump.spec.js create mode 100644 spec/pcap_test.js create mode 100644 tmp.pcap diff --git a/.gitignore b/.gitignore index 759f8bd..faf12b9 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,6 @@ node_modules build #OSX garbage -.DS_Store \ No newline at end of file +.DS_Store +examples/*.pcap +.vscode/ \ No newline at end of file diff --git a/examples/pcap_dump_test.js b/examples/pcap_dump_test.js new file mode 100644 index 0000000..6694472 --- /dev/null +++ b/examples/pcap_dump_test.js @@ -0,0 +1,17 @@ + + + +var pcap = require("../pcap"), + +pcap_dump = new pcap.PcapDumpSession('en0', "ip proto \\tcp",10*1024*1024,"tmp95.pcap",false,5); + +pcap_dump.on('pcap_write_complete_async',function(message){ + console.log("done.....",message); +}); + +pcap_dump.on('pcap_write_error',function(message){ + console.log("pcap_write_error.....",message); +}); + +//pcap_dump.start(); +pcap_dump.startAsyncCapture(); diff --git a/package.json b/package.json index 4ca95cf..46a69a6 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "istanbul": "^0.3.5", "mocha": "^2.1.0", "mocha-sinon": "^1.1.4", + "rewire": "^2.5.2", "should": "^5.0.0", "sinon": "^1.14.1" }, diff --git a/pcap.js b/pcap.js index cd31439..4863ecd 100644 --- a/pcap.js +++ b/pcap.js @@ -6,11 +6,13 @@ var decode = require("./decode").decode; var tcp_tracker = require("./tcp_tracker"); var DNSCache = require("./dns_cache"); var timers = require("timers"); +var pcap_dump = require("./pcap_dump"); exports.decode = decode; exports.TCPTracker = tcp_tracker.TCPTracker; exports.TCPSession = tcp_tracker.TCPSession; exports.DNSCache = DNSCache; +exports.PcapDumpSession = pcap_dump.PcapDumpSession; function PcapSession(is_live, device_name, filter, buffer_size, outfile, is_monitor) { this.is_live = is_live; diff --git a/pcap_binding.cc b/pcap_binding.cc index 06ff64f..0c23abd 100644 --- a/pcap_binding.cc +++ b/pcap_binding.cc @@ -12,6 +12,156 @@ #include "pcap_session.h" using namespace v8; +using Nan::Callback; +using Nan::AsyncQueueWorker; +using Nan::AsyncWorker; +using Nan::Callback; +using Nan::HandleScope; +using Nan::New; +using Nan::Null; +using Nan::To; + +class PcapWorker : public AsyncWorker { + public: + PcapWorker( + Callback *callback, + std::string device, + std::string filter, + int buffer_size, + std::string pcap_output_filename, + int num_packets + ) + : + AsyncWorker(callback), + device(device), + filter(filter), + buffer_size(buffer_size), + pcap_output_filename(pcap_output_filename), + num_packets(num_packets) + {} + ~PcapWorker() {} + + // Executed inside the worker-thread. + // It is not safe to access V8, or V8 data structures + // here, so everything we need for input and output + // should go on `this`. + void Execute () { + if (pcap_lookupnet(device.c_str(), &net, &mask, errbuf) == -1) { + net = 0; + mask = 0; + fprintf(stderr, "warning: %s - this may not actually work\n", errbuf); + SetErrorMessage(errbuf); + return; + } + pcap_handle = pcap_create(device.c_str(), errbuf); + if (pcap_handle == NULL) { + SetErrorMessage(errbuf); + return; + } + + // 64KB is the max IPv4 packet size + if (pcap_set_snaplen(pcap_handle, 65535) != 0) { + SetErrorMessage("error setting snaplen"); + return; + } + + // always use promiscuous mode + if (pcap_set_promisc(pcap_handle, 1) != 0) { + SetErrorMessage("error setting promiscuous mode"); + return; + } + + // Try to set buffer size. Sometimes the OS has a lower limit that it will silently enforce. + if (pcap_set_buffer_size(pcap_handle, buffer_size) != 0) { + SetErrorMessage("error setting buffer size"); + return; + } + + + // set "timeout" on read, even though we are also setting nonblock below. On Linux this is required. + if (pcap_set_timeout(pcap_handle, 1000) != 0) { + SetErrorMessage("error setting read timeout"); + return; + } + + if (pcap_activate(pcap_handle) != 0) { + SetErrorMessage(pcap_geterr(pcap_handle)); + return; + } + if ((pcap_output_filename.size()) > 0) { + pcap_dump_handle = pcap_dump_open(pcap_handle,pcap_output_filename.c_str()); + if (pcap_dump_handle == NULL) { + SetErrorMessage("error opening dump"); + return; + } + } + + + if (filter.size() != 0) { + if (pcap_compile(pcap_handle, &fp, filter.c_str(), 1, net) == -1) { + SetErrorMessage(pcap_geterr(pcap_handle)); + return; + } + + if (pcap_setfilter(pcap_handle, &fp) == -1) { + SetErrorMessage(pcap_geterr(pcap_handle)); + return; + } + + pcap_loop(pcap_handle, num_packets, OnPacketReady, (unsigned char *)pcap_dump_handle); + pcap_freecode(&fp); + /* + * Close the savefile opened in pcap_dump_open(). + */ + pcap_dump_close(pcap_dump_handle); + /* + * Close the packet capture device and free the memory used by the + * packet capture descriptor. + */ + pcap_close(pcap_handle); + } + } + + // Executed when the async work is complete + // this function will be run inside the main event loop + // so it is safe to use V8 again + void HandleOKCallback () { + + Nan::HandleScope scope; + + Local argv[] = { + Nan::Null() + , New(num_packets) + }; + + callback->Call(2, argv); + } + + + +static void OnPacketReady(u_char *s, const struct pcap_pkthdr* pkthdr, const u_char* packet) { + pcap_dump(s, pkthdr, packet); +} + + private: + + std::string device; + std::string filter; + int buffer_size; + std::string pcap_output_filename; + int num_packets; + struct bpf_program fp; + bpf_u_int32 mask; + bpf_u_int32 net; + pcap_t *pcap_handle; + pcap_dumper_t *pcap_dump_handle; + char errbuf[PCAP_ERRBUF_SIZE]; + + + + +}; + // Helper method, convert a sockaddr* (AF_INET or AF_INET6) to a string, and set it as the property // named 'key' in the Address object you pass in. @@ -135,6 +285,57 @@ NAN_METHOD(LibVersion) info.GetReturnValue().Set(Nan::New(pcap_lib_version()).ToLocalChecked()); } +// Asynchronous access to the `Estimate()` function +NAN_METHOD(PcapDumpAsync) { + + if (info.Length() == 8) { + if (!info[0]->IsString()) { + Nan::ThrowTypeError("pcap Open: info[0] must be a String"); + return; + } + if (!info[1]->IsString()) { + Nan::ThrowTypeError("pcap Open: info[1] must be a String"); + return; + } + if (!info[2]->IsInt32()) { + Nan::ThrowTypeError("pcap Open: info[2] must be a Number"); + return; + } + if (!info[3]->IsString()) { + Nan::ThrowTypeError("pcap Open: info[3] must be a String"); + return; + } + if (!info[4]->IsFunction()) { + Nan::ThrowTypeError("pcap Open: info[4] must be a Function"); + return; + } + if (!info[5]->IsBoolean()) { + Nan::ThrowTypeError("pcap Open: info[5] must be a Boolean"); + return; + } + if (!info[6]->IsInt32()) { + Nan::ThrowTypeError("pcap Open: info[6] must be a Number"); + return; + } + if (!info[7]->IsFunction()) { + Nan::ThrowTypeError("pcap Open: info[7] must be a Function"); + return; + } + } else { + Nan::ThrowTypeError("pcap CreatePcapDump: expecting 7 arguments"); + return; + } + Nan::Utf8String device(info[0]->ToString()); + Nan::Utf8String filter(info[1]->ToString()); + int buffer_size = info[2]->Int32Value(); + Nan::Utf8String pcap_output_filename(info[3]->ToString()); + int num_packets = info[6]->Int32Value(); + Callback *callback = new Callback(info[7].As()); + + AsyncQueueWorker(new PcapWorker(callback, std::string(*device),std::string(*filter),buffer_size, std::string(*pcap_output_filename),num_packets)); +} + + void Initialize(Handle exports) { Nan::HandleScope scope; @@ -144,6 +345,7 @@ void Initialize(Handle exports) exports->Set(Nan::New("findalldevs").ToLocalChecked(), Nan::New(FindAllDevs)->GetFunction()); exports->Set(Nan::New("default_device").ToLocalChecked(), Nan::New(DefaultDevice)->GetFunction()); exports->Set(Nan::New("lib_version").ToLocalChecked(), Nan::New(LibVersion)->GetFunction()); + exports->Set(Nan::New("create_pcap_dump_async").ToLocalChecked(), Nan::New(PcapDumpAsync)->GetFunction()); } NODE_MODULE(pcap_binding, Initialize) diff --git a/pcap_dump.js b/pcap_dump.js new file mode 100644 index 0000000..c691d1d --- /dev/null +++ b/pcap_dump.js @@ -0,0 +1,90 @@ +var util = require("util"); +var events = require("events"); +var binding = require("./build/Release/pcap_binding"); + + +function PcapDumpSession(device_name, filter, buffer_size, outfile, is_monitor, number_of_packets_to_be_read) { + + this.device_name = device_name; + this.filter = filter || ""; + this.buffer_size = buffer_size; + this.outfile = outfile || "tmp.pcap"; + this.is_monitor = Boolean(is_monitor); + this.opened = null; + this.packets_read = 0; + this.number_of_packets_to_be_read = number_of_packets_to_be_read || 1; + this.session = new binding.PcapSession(); + + if (typeof this.buffer_size === "number" && !isNaN(this.buffer_size)) { + this.buffer_size = Math.round(this.buffer_size); + } else { + this.buffer_size = 10 * 1024 * 1024; // Default buffer size is 10MB + } + + this.device_name = this.device_name || binding.default_device(); + events.EventEmitter.call(this); +} + +util.inherits(PcapDumpSession, events.EventEmitter); + +exports.lib_version = binding.lib_version(); + +exports.findalldevs = function () { + return binding.findalldevs(); +}; + + +PcapDumpSession.prototype.startAsyncCapture = function () { + + this.opened = true; + binding.create_pcap_dump_async( + this.device_name, + this.filter, + this.buffer_size, + this.outfile, + PcapDumpSession.prototype.on_packet.bind(this), + this.is_monitor, + this.number_of_packets_to_be_read, + PcapDumpSession.prototype.on_pcap_write_complete_async.bind(this) + ); +}; + + +PcapDumpSession.prototype.close = function () { + this.opened = false; + this.session.close(); +}; + +PcapDumpSession.prototype.stats = function () { + return this.session.stats(); +}; + +PcapDumpSession.prototype.on_pcap_write_complete_async = function (err, packet_count) { + + if (err) { + this.emit("pcap_write_error", err); + + } else { + this.emit("pcap_write_complete_async", { + "packets_read": packet_count, + "fileName": this.outfile + }); + } + +}; + + +PcapDumpSession.prototype.on_packet = function (packet) { + this.emit("packet", packet); + +}; + + + + +exports.PcapDumpSession = PcapDumpSession; + + +exports.createPcapDumpSession = function (device, filter, buffer_size, path, monitor, number_of_packets) { + return new PcapDumpSession(device, filter, buffer_size, path, monitor, number_of_packets); +}; \ No newline at end of file diff --git a/pcap_session.cc b/pcap_session.cc index bfa18d4..bdb030c 100644 --- a/pcap_session.cc +++ b/pcap_session.cc @@ -28,7 +28,6 @@ void PcapSession::Init(Handle exports) { Nan::SetPrototypeMethod(tpl, "close", Close); Nan::SetPrototypeMethod(tpl, "stats", Stats); Nan::SetPrototypeMethod(tpl, "inject", Inject); - Nan::SetPrototypeMethod(tpl, "create_pcapDump", CreatePcapDump); constructor.Reset(tpl->GetFunction()); exports->Set(Nan::New("PcapSession").ToLocalChecked(), tpl->GetFunction()); @@ -127,7 +126,6 @@ void PcapSession::Dispatch(const Nan::FunctionCallbackInfo& info) void PcapSession::Open(bool live, const Nan::FunctionCallbackInfo& info) { - //this.device_name, this.filter, this.buffer_size, this.outfile, packet_ready, this.is_monitor) Nan::HandleScope scope; char errbuf[PCAP_ERRBUF_SIZE]; @@ -382,121 +380,3 @@ void PcapSession::Inject(const Nan::FunctionCallbackInfo& info) } return; } - -void PcapSession::CreatePcapDump(const Nan::FunctionCallbackInfo& info) -{ - //this.device_name, this.filter, this.buffer_size, this.outfile, packet_ready, this.is_monitor) - Nan::HandleScope scope; - char errbuf[PCAP_ERRBUF_SIZE]; - - if (info.Length() == 7) { - if (!info[0]->IsString()) { - Nan::ThrowTypeError("pcap Open: info[0] must be a String"); - return; - } - if (!info[1]->IsString()) { - Nan::ThrowTypeError("pcap Open: info[1] must be a String"); - return; - } - if (!info[2]->IsInt32()) { - Nan::ThrowTypeError("pcap Open: info[2] must be a Number"); - return; - } - if (!info[3]->IsString()) { - Nan::ThrowTypeError("pcap Open: info[3] must be a String"); - return; - } - if (!info[4]->IsFunction()) { - Nan::ThrowTypeError("pcap Open: info[4] must be a Function"); - return; - } - if (!info[5]->IsBoolean()) { - Nan::ThrowTypeError("pcap Open: info[5] must be a Boolean"); - return; - } - if (!info[6]->IsInt32()) { - Nan::ThrowTypeError("pcap Open: info[6] must be a Number"); - return; - } - } else { - Nan::ThrowTypeError("pcap CreatePcapDump: expecting 7 arguments"); - return; - } - Nan::Utf8String device(info[0]->ToString()); - Nan::Utf8String filter(info[1]->ToString()); - int buffer_size = info[2]->Int32Value(); - Nan::Utf8String pcap_output_filename(info[3]->ToString()); - int noOfPackets = info[6]->Int32Value(); - - PcapSession* session = Nan::ObjectWrap::Unwrap(info.This()); - - session->packet_ready_cb.Reset(info[4].As()); - session->pcap_dump_handle = NULL; - - - if (pcap_lookupnet((char *) *device, &session->net, &session->mask, errbuf) == -1) { - session->net = 0; - session->mask = 0; - fprintf(stderr, "warning: %s - this may not actually work\n", errbuf); - } - - session->pcap_handle = pcap_create((char *) *device, errbuf); - if (session->pcap_handle == NULL) { - Nan::ThrowError(errbuf); - return; - } - - // 64KB is the max IPv4 packet size - if (pcap_set_snaplen(session->pcap_handle, 65535) != 0) { - Nan::ThrowError("error setting snaplen"); - return; - } - - // always use promiscuous mode - if (pcap_set_promisc(session->pcap_handle, 1) != 0) { - Nan::ThrowError("error setting promiscuous mode"); - return; - } - - // Try to set buffer size. Sometimes the OS has a lower limit that it will silently enforce. - if (pcap_set_buffer_size(session->pcap_handle, buffer_size) != 0) { - Nan::ThrowError("error setting buffer size"); - return; - } - - // set "timeout" on read, even though we are also setting nonblock below. On Linux this is required. - if (pcap_set_timeout(session->pcap_handle, 1000) != 0) { - Nan::ThrowError("error setting read timeout"); - return; - } - - - - if (pcap_activate(session->pcap_handle) != 0) { - Nan::ThrowError(pcap_geterr(session->pcap_handle)); - return; - } - - if (strlen((char *) *pcap_output_filename) > 0) { - session->pcap_dump_handle = pcap_dump_open(session->pcap_handle, (char *) *pcap_output_filename); - if (session->pcap_dump_handle == NULL) { - Nan::ThrowError("error opening dump"); - return; - } - } - - - if (filter.length() != 0) { - if (pcap_compile(session->pcap_handle, &session->fp, (char *) *filter, 1, session->net) == -1) { - Nan::ThrowError(pcap_geterr(session->pcap_handle)); - return; - } - - if (pcap_setfilter(session->pcap_handle, &session->fp) == -1) { - Nan::ThrowError(pcap_geterr(session->pcap_handle)); - return; - } - pcap_freecode(&session->fp); - } - -} diff --git a/pcap_session.h b/pcap_session.h index 6c30e3a..57bbfc3 100644 --- a/pcap_session.h +++ b/pcap_session.h @@ -21,8 +21,6 @@ class PcapSession : public Nan::ObjectWrap { static void Close(const Nan::FunctionCallbackInfo& info); static void Stats(const Nan::FunctionCallbackInfo& info); static void Inject(const Nan::FunctionCallbackInfo& info); - static void CreatePcapDump(const Nan::FunctionCallbackInfo& info); - static void PacketReady(u_char *callback_p, const struct pcap_pkthdr* pkthdr, const u_char* packet); Nan::Persistent packet_ready_cb; diff --git a/spec/pcap_dump.spec.js b/spec/pcap_dump.spec.js new file mode 100644 index 0000000..4366a95 --- /dev/null +++ b/spec/pcap_dump.spec.js @@ -0,0 +1,128 @@ +var + rewire = require("rewire"), + pcap_dump = rewire("../pcap_dump"), + should = require("should"), + sinon = require("sinon"); + +describe("pcap_dump", function() { + beforeEach(function() { + var pcapServiceMock = { + + default_device: function() { + return "en0"; + }, + PcapSession: function() { + return mysessionObject; + }, + findalldevs: function() { + return ["en0", "eth0"]; + }, + /* jshint ignore:start */ + create_pcap_dump_async: function(device_name, filter, buffer_size, outfile, packet_ready, is_monitor, packet_write_complete) { + return "LINKTYPE_ETHERNET"; + } + /* jshint ignore:end */ + }; + var mysessionObject = { + /* jshint ignore:start */ + open_live: function(device_name, filter, buffer_size, outfile, packet_ready, is_monitor) { + return "LINKTYPE_ETHERNET"; + }, + open_offline: function(device_name, filter, buffer_size, outfile, packet_ready, is_monitor) { + + return "LINKTYPE_ETHERNET"; + }, + + fileno: function() { + return "123"; + }, + + stats: function() { + + }, + close: function() { + + } + /* jshint ignore:end */ + }; + pcap_dump.__set__({ + "binding": pcapServiceMock + + }); + + }); + + + + describe("#start should take the default values ", function() { + beforeEach(function() { + this.instance = pcap_dump.createPcapDumpSession(); + }); + it("buffer size should be 10485760 ", function() { + this.instance.startAsyncCapture(); + should(this.instance.buffer_size).be.equal(10485760); + }); + + it("outfile should be tmp.pcap ", function() { + this.instance.startAsyncCapture(); + + should(this.instance.outfile).be.equal("tmp.pcap"); + + }); + + it("number_of_packets_to_be_read should be 1", function() { + this.instance.startAsyncCapture(); + + should(this.instance.number_of_packets_to_be_read).be.equal(1); + + }); + + it("is_monitor should be false", function() { + this.instance.startAsyncCapture(); + + should(this.instance.is_monitor).be.equal(false); + + }); + + it("calls sesssion.close on calling close", function() { + var mock = sinon.mock(this.instance.session); + mock.expects("close").returns(true); + this.instance.close(); + mock.verify(); + mock.restore(); + + }); + + it("calls sesssion.stats on calling stats", function() { + var mock = sinon.mock(this.instance.session); + mock.expects("stats").returns(true); + this.instance.stats(); + mock.verify(); + mock.restore(); + + }); + + + }); + + + describe("#start should values given in ", function() { + beforeEach(function() { + //(device_name, filter, buffer_size, outfile, packet_ready, is_monitor) + this.instance = pcap_dump.createPcapDumpSession("eth1", "dst", 1000, "", "", false); + }); + it("buffer size should be 1000 ", function() { + this.instance.startAsyncCapture(); + should(this.instance.buffer_size).be.equal(1000); + }); + it("findalldevs should return the arrayy ", function() { + this.instance.startAsyncCapture(); + var devices = pcap_dump.findalldevs(); + should(devices[0]).be.equal("en0"); + should(devices[1]).be.equal("eth0"); + + }); + + }); + +}); \ No newline at end of file diff --git a/spec/pcap_test.js b/spec/pcap_test.js new file mode 100644 index 0000000..7958ce2 --- /dev/null +++ b/spec/pcap_test.js @@ -0,0 +1,100 @@ +var + rewire = require("rewire"), + pcap = rewire("../pcap"); + +describe("pcap Tests", function() { + beforeEach(function() { + //is_live, device_name, filter, buffer_size, outfile, is_monitor + var pcapServiceMock = { + + default_device: function() { + return "en0"; + }, + PcapSession: function() { + return mysessionObject; + }, + findalldevs: function(){ + return ["en0","eth0"]; + } + }; + + var mysessionObject = { + /* jshint ignore:start */ + open_live: function(device_name, filter, buffer_size, outfile, packet_ready, is_monitor) { + return "LINKTYPE_ETHERNET"; + }, + open_offline: function(device_name, filter, buffer_size, outfile, packet_ready, is_monitor) { + + return "LINKTYPE_ETHERNET"; + }, + /* jshint ignore:end */ + fileno: function() { + return "123"; + } + + }; + var SocketWatcherMock = function() { + + }; + + /* jshint ignore:start */ + SocketWatcherMock.prototype.set = function(fd, two, three) { + + }; + /* jshint ignore:end */ + SocketWatcherMock.prototype.start = function() { + + }; + + pcap.__set__({ + "binding": pcapServiceMock, + "SocketWatcher": SocketWatcherMock + }); + + }); + + describe("#Initializing with default values ", function() { + + + it("link type should be LINKTYPE_ETHERNET", function() { + this.instance = pcap.createSession("en0", "ip tcp", 1000, "", false); + this.instance.link_type.should.equal("LINKTYPE_ETHERNET"); + + }); + + it(" device_name should be en0", function() { + this.instance = pcap.createSession("en0", "ip tcp", 1000, "", false); + + this.instance.device_name.should.equal("en0"); + + }); + + it(" filter should be ip tcp", function() { + this.instance = pcap.createSession("en0", "ip tcp", 1000, "", false); + this.instance.filter.should.equal("ip tcp"); + + }); + + it(" monitor should be false", function() { + this.instance = pcap.createSession("en0", "ip tcp", 1000, "", false); + this.instance.is_monitor.should.equal(false); + + }); + + it(" buffer_size should be 1000", function() { + this.instance = pcap.createSession("en0", "ip tcp", 1000, "", false); + this.instance.buffer_size.should.equal(1000); + + }); + it(" buffer_size should be 10 * 1024 * 1024 by default", function() { + this.instance = pcap.createSession("en0", "ip tcp", "", "", false); + this.instance.buffer_size.should.equal(10 * 1024 * 1024); + + }); + + + }); + + + +}); \ No newline at end of file diff --git a/tmp.pcap b/tmp.pcap new file mode 100644 index 0000000000000000000000000000000000000000..a324304509bdd12aef4a69458a409cefefafe614 GIT binary patch literal 24 Ycmca|c+)~A1{MYw`2U}Qff2|708wKE@Bjb+ literal 0 HcmV?d00001