Socket
Socket
Sign inDemoInstall

pcap

Package Overview
Dependencies
Maintainers
2
Versions
36
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

pcap - npm Package Compare versions

Comparing version 1.2.0 to 2.0.0

.jshintrc

2

package.json
{
"name" : "pcap",
"version" : "1.2.0",
"version" : "2.0.0",
"description" : "raw packet capture, decoding, and analysis",

@@ -5,0 +5,0 @@ "author": "Matt Ranney <mjr@ranney.com>",

@@ -1,70 +0,70 @@

/*global process require exports console */
var util = require("util");
var events = require("events");
var binding = require("./build/Release/pcap_binding");
var SocketWatcher = require("socketwatcher").SocketWatcher;
var decode = require("./decode").decode;
var tcp_tracker = require("./tcp_tracker");
var DNSCache = require("./dns_cache");
var util = require('util'),
dns = require('dns'),
events = require('events'),
binding = require('./build/Release/pcap_binding'),
HTTPParser = process.binding('http_parser').HTTPParser,
url = require('url'),
SocketWatcher = require("socketwatcher").SocketWatcher;
exports.decode = decode;
exports.TCPTracker = tcp_tracker.TCPTracker;
exports.TCPSession = tcp_tracker.TCPSession;
exports.DNSCache = DNSCache;
function Pcap() {
this.opened = false;
function PcapSession(is_live, device_name, filter, buffer_size, outfile, is_monitor) {
this.is_live = is_live;
this.device_name = device_name;
this.filter = filter || "";
this.buffer_size = buffer_size;
this.outfile = outfile || "";
this.is_monitor = Boolean(is_monitor);
this.link_type = null;
this.fd = null;
this.opened = null;
this.buf = null;
this.header = null;
this.read_watcher = null;
this.empty_reads = 0;
this.packets_read = null;
events.EventEmitter.call(this);
}
util.inherits(Pcap, events.EventEmitter);
this.session = new binding.PcapSession();
exports.lib_version = binding.lib_version();
Pcap.prototype.findalldevs = function () {
return binding.findalldevs();
};
Pcap.prototype.open = function (live, device, filter, buffer_size, pcap_output_filename, monitor) {
var me = this;
if (typeof buffer_size === 'number' && !isNaN(buffer_size)) {
me.buffer_size = Math.round(buffer_size);
if (typeof this.buffer_size === "number" && !isNaN(this.buffer_size)) {
this.buffer_size = Math.round(this.buffer_size);
} else {
me.buffer_size = 10 * 1024 * 1024; // Default buffer size is 10MB
this.buffer_size = 10 * 1024 * 1024; // Default buffer size is 10MB
}
me.live = live;
me.session = new binding.PcapSession();
var self = this;
// called for each packet read by pcap
function packet_ready(header) {
header.link_type = me.link_type;
header.time_ms = (header.tv_sec * 1000) + (header.tv_usec / 1000);
me.buf.pcap_header = header;
me.emit('packet', me.buf);
function packet_ready() {
self.on_packet_ready();
}
if (live) {
me.device_name = device || binding.default_device();
me.link_type = me.session.open_live(me.device_name, filter || "", me.buffer_size, pcap_output_filename || "", packet_ready, monitor || false);
if (this.is_live) {
this.device_name = this.device_name || binding.default_device();
this.link_type = this.session.open_live(this.device_name, this.filter, this.buffer_size, this.outfile, packet_ready, this.is_monitor);
} else {
me.device_name = device;
me.link_type = me.session.open_offline(me.device_name, filter || "", me.buffer_size, pcap_output_filename || "", packet_ready);
this.link_type = this.session.open_offline(this.device_name, this.filter, this.buffer_size, this.outfile, packet_ready, this.is_monitor);
}
me.fd = me.session.fileno();
me.opened = true;
me.buf = new Buffer(65535);
if ( live ) {
me.readWatcher = new SocketWatcher();
me.empty_reads = 0;
this.fd = this.session.fileno();
this.opened = true;
this.buf = new Buffer(this.buffer_size || 65535);
this.header = new Buffer(16);
if (is_live) {
this.readWatcher = new SocketWatcher();
// readWatcher gets a callback when pcap has data to read. multiple packets may be readable.
me.readWatcher.callback = function pcap_read_callback() {
var packets_read = me.session.dispatch(me.buf);
this.readWatcher.callback = function pcap_read_callback() {
var packets_read = self.session.dispatch(self.buf, self.header);
if (packets_read < 1) {
// TODO - figure out what is causing this, and if it is bad.
me.empty_reads += 1;
this.empty_reads += 1;
}
};
me.readWatcher.set(me.fd, true, false);
me.readWatcher.start();
this.readWatcher.set(this.fd, true, false);
this.readWatcher.start();
} else {

@@ -74,1740 +74,54 @@ setImmediate(function() {

do {
packets = me.session.dispatch(me.buf);
} while ( packets > 0 )
me.emit('complete');
packets = self.session.dispatch(self.buf, self.header);
} while ( packets > 0 );
self.emit("complete");
});
}
};
Pcap.prototype.close = function () {
this.opened = false;
this.session.close();
// TODO - remove listeners so program will exit I guess?
};
Pcap.prototype.stats = function () {
return this.session.stats();
};
Pcap.prototype.inject = function (data) {
return this.session.inject(data);
};
exports.Pcap = Pcap;
exports.createSession = function (device, filter, buffer_size, monitor) {
var session = new Pcap();
session.open(true, device, filter, buffer_size, null, monitor);
return session;
};
exports.createOfflineSession = function (path, filter) {
var session = new Pcap();
session.open(false, path, filter, 0);
return session;
};
//
// Decoding functions
//
function lpad(str, len) {
while (str.length < len) {
str = "0" + str;
}
return str;
}
function dump_bytes(raw_packet, offset) {
for (var i = offset; i < raw_packet.pcap_header.caplen ; i += 1) {
console.log(i + ": " + raw_packet[i]);
}
}
var unpack = {
ethernet_addr: function (raw_packet, offset) {
return [
lpad(raw_packet[offset].toString(16), 2),
lpad(raw_packet[offset + 1].toString(16), 2),
lpad(raw_packet[offset + 2].toString(16), 2),
lpad(raw_packet[offset + 3].toString(16), 2),
lpad(raw_packet[offset + 4].toString(16), 2),
lpad(raw_packet[offset + 5].toString(16), 2)
].join(":");
},
sll_addr: function (raw_packet, offset, len) {
var res = [], i;
for (i=0; i<len; i++){
res.push(lpad(raw_packet[offset+i].toString(16), 2));
}
return res.join(":");
},
uint16: function (raw_packet, offset) {
return ((raw_packet[offset] * 256) + raw_packet[offset + 1]);
},
uint16_be: function (raw_packet, offset) {
return ((raw_packet[offset+1] * 256) + raw_packet[offset]);
},
uint32: function (raw_packet, offset) {
return (
(raw_packet[offset] * 16777216) +
(raw_packet[offset + 1] * 65536) +
(raw_packet[offset + 2] * 256) +
raw_packet[offset + 3]
);
},
uint64: function (raw_packet, offset) {
return (
(raw_packet[offset] * 72057594037927936) +
(raw_packet[offset + 1] * 281474976710656) +
(raw_packet[offset + 2] * 1099511627776) +
(raw_packet[offset + 3] * 4294967296) +
(raw_packet[offset + 4] * 16777216) +
(raw_packet[offset + 5] * 65536) +
(raw_packet[offset + 6] * 256) +
raw_packet[offset + 7]
);
},
ipv4_addr: function (raw_packet, offset) {
return [
raw_packet[offset],
raw_packet[offset + 1],
raw_packet[offset + 2],
raw_packet[offset + 3]
].join('.');
},
ipv6_addr: function (raw_packet, offset) {
var i;
var ret = '';
var octets = [];
for (i=offset; i<offset+16; i+=2) {
octets.push(unpack.uint16(raw_packet,i).toString(16));
}
var curr_start, curr_len;
var max_start, max_len;
for(i = 0; i < 8; i++){
if(octets[i] == "0"){
if(curr_start === undefined){
curr_len = 1;
curr_start = i;
}else{
curr_len++;
if(!max_start || curr_len > max_len){
max_start = curr_start;
max_len = curr_len;
}
}
}else{
curr_start = undefined;
}
}
if(max_start !== undefined){
var tosplice = max_start === 0 || (max_start + max_len > 7) ? ":" : "";
octets.splice(max_start, max_len,tosplice);
if(max_len == 8){octets.push("");}
}
ret = octets.join(":");
return ret;
}
};
exports.unpack = unpack;
var decode = {}; // convert raw packet data into JavaScript objects with friendly names
decode.packet = function (raw_packet) {
var packet = {};
packet.link_type = raw_packet.pcap_header.link_type;
switch (packet.link_type) {
case "LINKTYPE_ETHERNET":
packet.link = decode.ethernet(raw_packet, 0);
break;
case "LINKTYPE_NULL":
packet.link = decode.nulltype(raw_packet, 0);
break;
case "LINKTYPE_RAW":
packet.link = decode.rawtype(raw_packet, 0);
break;
case "LINKTYPE_IEEE802_11_RADIO":
packet.link = decode.ieee802_11_radio(raw_packet, 0);
break;
case "LINKTYPE_LINUX_SLL":
packet.link = decode.linux_sll(raw_packet, 0);
break;
default:
console.log("pcap.js: decode.packet() - Don't yet know how to decode link type " + raw_packet.pcap_header.link_type);
}
packet.pcap_header = raw_packet.pcap_header; // TODO - merge values here instead of putting ref on packet buffer
return packet;
};
decode.rawtype = function (raw_packet, offset) {
var ret = {};
ret.ip = decode.ip(raw_packet, 0);
return ret;
};
decode.nulltype = function (raw_packet, offset) {
var ret = {};
// an oddity about nulltype is that it starts with a 4 byte header, but I can't find a
// way to tell which byte order is used. The good news is that all address family
// values are 8 bits or less.
if (raw_packet[0] === 0 && raw_packet[1] === 0) { // must be one of the endians
ret.pftype = raw_packet[3];
} else { // and this is the other one
ret.pftype = raw_packet[0];
}
if (ret.pftype === 2) { // AF_INET, at least on my Linux and OSX machines right now
ret.ip = decode.ip(raw_packet, 4);
} else if (ret.pftype === 30) { // AF_INET6, often
ret.ip = decode.ip6(raw_packet, 4);
} else {
console.log("pcap.js: decode.nulltype() - Don't know how to decode protocol family " + ret.pftype);
}
return ret;
};
decode.ethernet = function (raw_packet, offset) {
var ret = {};
ret.dhost = unpack.ethernet_addr(raw_packet, 0);
ret.shost = unpack.ethernet_addr(raw_packet, 6);
ret.ethertype = unpack.uint16(raw_packet, 12);
offset = 14;
// Check for a tagged frame
switch (ret.ethertype) {
case 0x8100: // VLAN-tagged (802.1Q)
ret.vlan = decode.vlan(raw_packet, 14);
// Update the ethertype
ret.ethertype = unpack.uint16(raw_packet, 16);
offset = 18;
break;
}
if (ret.ethertype < 1536) {
// this packet is actually some 802.3 type without an ethertype
ret.ethertype = 0;
} else {
// http://en.wikipedia.org/wiki/EtherType
switch (ret.ethertype) {
case 0x800: // IPv4
ret.ip = decode.ip(raw_packet, offset);
break;
case 0x806: // ARP
ret.arp = decode.arp(raw_packet, offset);
break;
case 0x86dd: // IPv6 - http://en.wikipedia.org/wiki/IPv6
ret.ipv6 = decode.ip6(raw_packet, offset);
break;
case 0x88cc: // LLDP - http://en.wikipedia.org/wiki/Link_Layer_Discovery_Protocol
ret.lldp = "need to implement LLDP";
break;
default:
console.log("pcap.js: decode.ethernet() - Don't know how to decode ethertype " + ret.ethertype);
}
}
return ret;
};
decode.linux_sll = function (raw_packet, offset) {
var ret = {};
var types = {0:"HOST", 1:"BROADCAST", 2:"MULTICAST", 3:"OTHERHOST", 4:"OUTGOING"};
ret.sllPacketType = unpack.uint16(raw_packet, offset); offset+=2;
ret.sllAddressType = types[unpack.uint16(raw_packet, offset)]; offset+=2;
var sllAddressLength = unpack.uint16(raw_packet, offset); offset+=2;
ret.sllSource = unpack.sll_addr(raw_packet, offset, sllAddressLength);
offset+=8; //address field is fixed to 8 bytes from witch addresslength bytes are used
ret.sllProtocol = unpack.uint16(raw_packet, offset); offset+=2;
switch (ret.sllProtocol) {
case 0x800: // IPv4
ret.ip = decode.ip(raw_packet, offset);
break;
case 0x806: // ARP
ret.arp = decode.arp(raw_packet, offset);
break;
case 0x86dd: // IPv6 - http://en.wikipedia.org/wiki/IPv6
ret.ipv6 = decode.ip6(raw_packet, offset);
break;
case 0x88cc: // LLDP - http://en.wikipedia.org/wiki/Link_Layer_Discovery_Protocol
ret.lldp = "need to implement LLDP";
break;
default:
console.log("pcap.js: decode.linux_sll() - Don't know how to decode ethertype " + ret.sllProtocol);
}
return ret;
};
decode.ieee802_11_radio = function (raw_packet, offset) {
var ret = {};
var original_offset = offset;
ret.headerRevision = raw_packet[offset++];
ret.headerPad = raw_packet[offset++];
ret.headerLength = unpack.uint16_be(raw_packet, offset); offset += 2;
offset = original_offset + ret.headerLength;
ret.ieee802_11Frame = decode.ieee802_11_frame(raw_packet, offset);
if(ret.ieee802_11Frame && ret.ieee802_11Frame.llc && ret.ieee802_11Frame.llc.ip) {
ret.ip = ret.ieee802_11Frame.llc.ip;
delete ret.ieee802_11Frame.llc.ip;
ret.shost = ret.ieee802_11Frame.shost;
delete ret.ieee802_11Frame.shost;
ret.dhost = ret.ieee802_11Frame.dhost;
delete ret.ieee802_11Frame.dhost;
}
return ret;
};
decode.ieee802_11_frame = function (raw_packet, offset) {
var ret = {};
ret.frameControl = unpack.uint16_be(raw_packet, offset); offset += 2;
ret.type = (ret.frameControl >> 2) & 0x0003;
ret.subType = (ret.frameControl >> 4) & 0x000f;
ret.flags = (ret.frameControl >> 8) & 0xff;
ret.duration = unpack.uint16_be(raw_packet, offset); offset += 2;
ret.bssid = unpack.ethernet_addr(raw_packet, offset); offset += 6;
ret.shost = unpack.ethernet_addr(raw_packet, offset); offset += 6;
ret.dhost = unpack.ethernet_addr(raw_packet, offset); offset += 6;
ret.fragSeq = unpack.uint16_be(raw_packet, offset); offset += 2;
var strength = raw_packet[22];
ret.strength = -Math.abs(265 - strength);
switch(ret.subType) {
case 8: // QoS Data
ret.qosPriority = raw_packet[offset++];
ret.txop = raw_packet[offset++];
break;
}
if(ret.type == 2 && ret.subType == 4) {
// skip this is Null function (No data)
}
else if(ret.type == 2 && ret.subType == 12) {
// skip this is QoS Null function (No data)
}
else if(ret.type == 2 && ret.subType == 7) {
// skip this is CF-Ack/Poll
}
else if(ret.type == 2 && ret.subType == 6) {
// skip this is CF-Poll (No data)
}
else if(ret.type == 2) { // data
ret.llc = decode.logicalLinkControl(raw_packet, offset);
}
return ret;
};
decode.logicalLinkControl = function (raw_packet, offset) {
var ret = {};
ret.dsap = raw_packet[offset++];
ret.ssap = raw_packet[offset++];
if(((ret.dsap == 0xaa) && (ret.ssap == 0xaa)) ||
((ret.dsap === 0x00) && (ret.ssap === 0x00))) {
ret.controlField = raw_packet[offset++];
ret.orgCode = [
raw_packet[offset++],
raw_packet[offset++],
raw_packet[offset++]
];
ret.type = unpack.uint16(raw_packet, offset); offset += 2;
switch(ret.type) {
case 0x0800: // ip
ret.ip = decode.ip(raw_packet, offset);
break;
}
} else {
throw new Error("Unknown LLC types: DSAP: " + ret.dsap + ", SSAP: " + ret.ssap);
}
return ret;
};
decode.vlan = function (raw_packet, offset) {
var ret = {};
// http://en.wikipedia.org/wiki/IEEE_802.1Q
ret.priority = (raw_packet[offset] & 0xE0) >> 5;
ret.canonical_format = (raw_packet[offset] & 0x10) >> 4;
ret.id = ((raw_packet[offset] & 0x0F) << 8) | raw_packet[offset + 1];
return ret;
};
decode.arp = function (raw_packet, offset) {
var ret = {};
// http://en.wikipedia.org/wiki/Address_Resolution_Protocol
ret.htype = unpack.uint16(raw_packet, offset); // 0, 1
ret.ptype = unpack.uint16(raw_packet, offset + 2); // 2, 3
ret.hlen = raw_packet[offset + 4];
ret.plen = raw_packet[offset + 5];
ret.operation = unpack.uint16(raw_packet, offset + 6); // 6, 7
if (ret.operation === 1) {
ret.operation = "request";
}
else if (ret.operation === 2) {
ret.operation = "reply";
}
else {
ret.operation = "unknown";
}
if (ret.hlen === 6 && ret.plen === 4) { // ethernet + IPv4
ret.sender_ha = unpack.ethernet_addr(raw_packet, offset + 8); // 8, 9, 10, 11, 12, 13
ret.sender_pa = unpack.ipv4_addr(raw_packet, offset + 14); // 14, 15, 16, 17
ret.target_ha = unpack.ethernet_addr(raw_packet, offset + 18); // 18, 19, 20, 21, 22, 23
ret.target_pa = unpack.ipv4_addr(raw_packet, offset + 24); // 24, 25, 26, 27
}
// don't know how to decode more exotic ARP types
return ret;
};
decode.ip = function (raw_packet, offset) {
var ret = {};
// http://en.wikipedia.org/wiki/IPv4
ret.version = (raw_packet[offset] & 240) >> 4; // first 4 bits
ret.header_length = raw_packet[offset] & 15; // second 4 bits
ret.header_bytes = ret.header_length * 4;
ret.diffserv = raw_packet[offset + 1];
ret.total_length = unpack.uint16(raw_packet, offset + 2); // 2, 3
ret.identification = unpack.uint16(raw_packet, offset + 4); // 4, 5
ret.flags = {};
ret.flags.reserved = (raw_packet[offset + 6] & 128) >> 7;
ret.flags.df = (raw_packet[offset + 6] & 64) >> 6;
ret.flags.mf = (raw_packet[offset + 6] & 32) >> 5;
ret.fragment_offset = ((raw_packet[offset + 6] & 31) * 256) + raw_packet[offset + 7]; // 13-bits from 6, 7
ret.ttl = raw_packet[offset + 8];
ret.protocol = raw_packet[offset + 9];
ret.header_checksum = unpack.uint16(raw_packet, offset + 10); // 10, 11
ret.saddr = unpack.ipv4_addr(raw_packet, offset + 12); // 12, 13, 14, 15
ret.daddr = unpack.ipv4_addr(raw_packet, offset + 16); // 16, 17, 18, 19
// TODO - parse IP "options" if header_length > 5
switch (ret.protocol) {
case 1:
ret.protocol_name = "ICMP";
ret.icmp = decode.icmp(raw_packet, offset + (ret.header_length * 4));
break;
case 2:
ret.protocol_name = "IGMP";
ret.igmp = decode.igmp(raw_packet, offset + (ret.header_length * 4));
break;
case 6:
ret.protocol_name = "TCP";
ret.tcp = decode.tcp(raw_packet, offset + (ret.header_length * 4), ret);
break;
case 17:
ret.protocol_name = "UDP";
ret.udp = decode.udp(raw_packet, offset + (ret.header_length * 4));
break;
default:
ret.protocol_name = "Unknown";
}
return ret;
};
decode.ip6_header = function(raw_packet, next_header, ip, offset) {
switch (next_header) {
case 1:
ip.protocol_name = "ICMP";
ip.icmp = decode.icmp(raw_packet, offset);
break;
case 2:
ip.protocol_name = "IGMP";
ip.igmp = decode.igmp(raw_packet, offset);
break;
case 6:
ip.protocol_name = "TCP";
ip.tcp = decode.tcp(raw_packet, offset, ip);
break;
case 17:
ip.protocol_name = "UDP";
ip.udp = decode.udp(raw_packet, offset);
break;
default:
// TODO: capture the extensions
//decode.ip6_header(raw_packet, raw_packet[offset], offset + raw_packet[offset+1]);
}
};
decode.ip6 = function (raw_packet, offset) {
var ret = {};
// http://en.wikipedia.org/wiki/IPv6
ret.version = (raw_packet[offset] & 240) >> 4; // first 4 bits
ret.traffic_class = ((raw_packet[offset] & 15) << 4) + ((raw_packet[offset+1] & 240) >> 4);
ret.flow_label = ((raw_packet[offset + 1] & 15) << 16) +
(raw_packet[offset + 2] << 8) +
raw_packet[offset + 3];
ret.payload_length = unpack.uint16(raw_packet, offset+4);
ret.total_length = ret.payload_length + 40;
ret.next_header = raw_packet[offset+6];
ret.hop_limit = raw_packet[offset+7];
ret.saddr = unpack.ipv6_addr(raw_packet, offset+8);
ret.daddr = unpack.ipv6_addr(raw_packet, offset+24);
ret.header_bytes = 40;
decode.ip6_header(raw_packet, ret.next_header, ret, offset+40);
return ret;
};
decode.icmp = function (raw_packet, offset) {
var ret = {};
// http://en.wikipedia.org/wiki/Internet_Control_Message_Protocol
ret.type = raw_packet[offset];
ret.code = raw_packet[offset + 1];
ret.checksum = unpack.uint16(raw_packet, offset + 2); // 2, 3
ret.id = unpack.uint16(raw_packet, offset + 4); // 4, 5
ret.sequence = unpack.uint16(raw_packet, offset + 6); // 6, 7
switch (ret.type) {
case 0:
ret.type_desc = "Echo Reply";
break;
case 1:
case 2:
ret.type_desc = "Reserved";
break;
case 3:
switch (ret.code) {
case 0:
ret.type_desc = "Destination Network Unreachable";
break;
case 1:
ret.type_desc = "Destination Host Unreachable";
break;
case 2:
ret.type_desc = "Destination Protocol Unreachable";
break;
case 3:
ret.type_desc = "Destination Port Unreachable";
break;
case 4:
ret.type_desc = "Fragmentation required, and DF flag set";
break;
case 5:
ret.type_desc = "Source route failed";
break;
case 6:
ret.type_desc = "Destination network unknown";
break;
case 7:
ret.type_desc = "Destination host unknown";
break;
case 8:
ret.type_desc = "Source host isolated";
break;
case 9:
ret.type_desc = "Network administratively prohibited";
break;
case 10:
ret.type_desc = "Host administratively prohibited";
break;
case 11:
ret.type_desc = "Network unreachable for TOS";
break;
case 12:
ret.type_desc = "Host unreachable for TOS";
break;
case 13:
ret.type_desc = "Communication administratively prohibited";
break;
default:
ret.type_desc = "Destination Unreachable (unknown code " + ret.code + ")";
}
break;
case 4:
ret.type_desc = "Source Quench";
break;
case 5:
switch (ret.code) {
case 0:
ret.type_desc = "Redirect Network";
break;
case 1:
ret.type_desc = "Redirect Host";
break;
case 2:
ret.type_desc = "Redirect TOS and Network";
break;
case 3:
ret.type_desc = "Redirect TOS and Host";
break;
default:
ret.type_desc = "Redirect (unknown code " + ret.code + ")";
break;
}
break;
case 6:
ret.type_desc = "Alternate Host Address";
break;
case 7:
ret.type_desc = "Reserved";
break;
case 8:
ret.type_desc = "Echo Request";
break;
case 9:
ret.type_desc = "Router Advertisement";
break;
case 10:
ret.type_desc = "Router Solicitation";
break;
case 11:
switch (ret.code) {
case 0:
ret.type_desc = "TTL expired in transit";
break;
case 1:
ret.type_desc = "Fragment reassembly time exceeded";
break;
default:
ret.type_desc = "Time Exceeded (unknown code " + ret.code + ")";
}
break;
// TODO - decode the rest of the well-known ICMP messages
default:
ret.type_desc = "type " + ret.type + " code " + ret.code;
}
// There are usually more exciting things hiding in ICMP packets after the headers
return ret;
};
decode.igmp = function (raw_packet, offset) {
var ret = {};
// http://en.wikipedia.org/wiki/Internet_Group_Management_Protocol
ret.type = raw_packet[offset];
ret.max_response_time = raw_packet[offset + 1];
ret.checksum = unpack.uint16(raw_packet, offset + 2); // 2, 3
ret.group_address = unpack.ipv4_addr(raw_packet, offset + 4); // 4, 5, 6, 7
switch (ret.type) {
case 0x11:
ret.version = ret.max_response_time > 0 ? 2 : 1;
ret.type_desc = "Membership Query";
break;
case 0x12:
ret.version = 1;
ret.type_desc = "Membership Report";
break;
case 0x16:
ret.version = 2;
ret.type_desc = "Membership Report";
break;
case 0x17:
ret.version = 2;
ret.type_desc = "Leave Group";
break;
case 0x22:
ret.version = 3;
ret.type_desc = "Membership Report";
// TODO: Decode v3 message
break;
default:
ret.type_desc = "type " + ret.type;
break;
}
return ret;
};
decode.udp = function (raw_packet, offset) {
var ret = {};
// http://en.wikipedia.org/wiki/User_Datagram_Protocol
ret.sport = unpack.uint16(raw_packet, offset); // 0, 1
ret.dport = unpack.uint16(raw_packet, offset + 2); // 2, 3
ret.length = unpack.uint16(raw_packet, offset + 4); // 4, 5
ret.checksum = unpack.uint16(raw_packet, offset + 6); // 6, 7
ret.data_offset = offset + 8;
ret.data_end = ret.length + ret.data_offset - 8;
ret.data_bytes = ret.data_end - ret.data_offset;
// Follow tcp pattern and don't make a copy of the data payload
// Therefore its only valid for this pass throught the capture loop
if (ret.data_bytes > 0) {
ret.data = raw_packet.slice(ret.data_offset, ret.data_end);
ret.data.length = ret.data_bytes;
}
if (ret.sport === 53 || ret.dport === 53) {
ret.dns = decode.dns(raw_packet, offset + 8);
}
return ret;
};
decode.tcp = function (raw_packet, offset, ip) {
var ret = {}, option_offset, options_end;
// http://en.wikipedia.org/wiki/Transmission_Control_Protocol
ret.sport = unpack.uint16(raw_packet, offset); // 0, 1
ret.dport = unpack.uint16(raw_packet, offset + 2); // 2, 3
ret.seqno = unpack.uint32(raw_packet, offset + 4); // 4, 5, 6, 7
ret.ackno = unpack.uint32(raw_packet, offset + 8); // 8, 9, 10, 11
ret.data_offset = (raw_packet[offset + 12] & 0xf0) >> 4; // first 4 bits of 12
ret.header_bytes = ret.data_offset * 4; // convenience for using data_offset
ret.reserved = raw_packet[offset + 12] & 15; // second 4 bits of 12
ret.flags = {};
ret.flags.cwr = (raw_packet[offset + 13] & 128) >> 7; // all flags packed into 13
ret.flags.ece = (raw_packet[offset + 13] & 64) >> 6;
ret.flags.urg = (raw_packet[offset + 13] & 32) >> 5;
ret.flags.ack = (raw_packet[offset + 13] & 16) >> 4;
ret.flags.psh = (raw_packet[offset + 13] & 8) >> 3;
ret.flags.rst = (raw_packet[offset + 13] & 4) >> 2;
ret.flags.syn = (raw_packet[offset + 13] & 2) >> 1;
ret.flags.fin = raw_packet[offset + 13] & 1;
ret.window_size = unpack.uint16(raw_packet, offset + 14); // 14, 15
ret.checksum = unpack.uint16(raw_packet, offset + 16); // 16, 17
ret.urgent_pointer = unpack.uint16(raw_packet, offset + 18); // 18, 19
ret.options = {};
option_offset = offset + 20;
options_end = offset + (ret.data_offset * 4);
while (option_offset < options_end) {
switch (raw_packet[option_offset]) {
case 0:
option_offset += 1;
break;
case 1:
option_offset += 1;
break;
case 2:
ret.options.mss = unpack.uint16(raw_packet, option_offset + 2);
option_offset += 4;
break;
case 3:
ret.options.window_scale = Math.pow(2, (raw_packet[option_offset + 2]));
option_offset += 3;
break;
case 4:
ret.options.sack_ok = true;
option_offset += 2;
break;
case 5:
ret.options.sack = [];
switch (raw_packet[option_offset + 1]) {
case 10:
ret.options.sack.push([unpack.uint32(raw_packet, option_offset + 2), unpack.uint32(raw_packet, option_offset + 6)]);
option_offset += 10;
break;
case 18:
ret.options.sack.push([unpack.uint32(raw_packet, option_offset + 2), unpack.uint32(raw_packet, option_offset + 6)]);
ret.options.sack.push([unpack.uint32(raw_packet, option_offset + 10), unpack.uint32(raw_packet, option_offset + 14)]);
option_offset += 18;
break;
case 26:
ret.options.sack.push([unpack.uint32(raw_packet, option_offset + 2), unpack.uint32(raw_packet, option_offset + 6)]);
ret.options.sack.push([unpack.uint32(raw_packet, option_offset + 10), unpack.uint32(raw_packet, option_offset + 14)]);
ret.options.sack.push([unpack.uint32(raw_packet, option_offset + 18), unpack.uint32(raw_packet, option_offset + 22)]);
option_offset += 26;
break;
case 34:
ret.options.sack.push([unpack.uint32(raw_packet, option_offset + 2), unpack.uint32(raw_packet, option_offset + 6)]);
ret.options.sack.push([unpack.uint32(raw_packet, option_offset + 10), unpack.uint32(raw_packet, option_offset + 14)]);
ret.options.sack.push([unpack.uint32(raw_packet, option_offset + 18), unpack.uint32(raw_packet, option_offset + 22)]);
ret.options.sack.push([unpack.uint32(raw_packet, option_offset + 26), unpack.uint32(raw_packet, option_offset + 30)]);
option_offset += 34;
break;
default:
console.log("Invalid TCP SACK option length " + raw_packet[option_offset + 1]);
option_offset = options_end;
}
break;
case 8:
ret.options.timestamp = unpack.uint32(raw_packet, option_offset + 2);
ret.options.echo = unpack.uint32(raw_packet, option_offset + 6);
option_offset += 10;
break;
default:
throw new Error("Don't know how to process TCP option " + raw_packet[option_offset]);
}
}
ret.data_offset = offset + ret.header_bytes;
ret.data_end = offset + ip.total_length - ip.header_bytes;
ret.data_bytes = ret.data_end - ret.data_offset;
if (ret.data_bytes > 0) {
// add a buffer slice pointing to the data area of this TCP packet.
// Note that this does not make a copy, so ret.data is only valid for this current
// trip through the capture loop.
ret.data = raw_packet.slice(ret.data_offset, ret.data_end);
ret.data.length = ret.data_bytes;
}
// automatic protocol decode ends here. Higher level protocols can be decoded by using payload.
return ret;
};
// helpers for DNS decoder
var dns_util = {
type_to_string: function (type_num) {
switch (type_num) {
case 1:
return "A";
case 2:
return "NS";
case 3:
return "MD";
case 4:
return "MF";
case 5:
return "CNAME";
case 6:
return "SOA";
case 7:
return "MB";
case 8:
return "MG";
case 9:
return "MR";
case 10:
return "NULL";
case 11:
return "WKS";
case 12:
return "PTR";
case 13:
return "HINFO";
case 14:
return "MINFO";
case 15:
return "MX";
case 16:
return "TXT";
default:
return ("Unknown (" + type_num + ")");
}
},
qtype_to_string: function (qtype_num) {
switch (qtype_num) {
case 252:
return "AXFR";
case 253:
return "MAILB";
case 254:
return "MAILA";
case 255:
return "*";
default:
return dns_util.type_to_string(qtype_num);
}
},
class_to_string: function (class_num) {
switch (class_num) {
case 1:
return "IN";
case 2:
return "CS";
case 3:
return "CH";
case 4:
return "HS";
default:
return "Unknown (" + class_num + ")";
}
},
qclass_to_string: function (qclass_num) {
if (qclass_num === 255) {
return "*";
} else {
return dns_util.class_to_string(qclass_num);
}
},
expandRRData: function(raw_packet, offset, rrRecord) {
if(rrRecord.rrtype == 'A' && rrRecord.rrclass == 'IN' && rrRecord.rdlength == 4) {
var data = {};
data.ipAddress = raw_packet[offset] + '.' + raw_packet[offset+1] + '.' + raw_packet[offset+2] + '.' + raw_packet[offset+3];
return data;
}
return null;
},
readName: function(raw_packet, offset, internal_offset, result) {
if(offset + internal_offset > raw_packet.pcap_header.len) {
throw new Error("Malformed DNS RR. Offset is larger than the size of the packet (readName).");
}
var lenOrPtr = raw_packet[offset + internal_offset];
internal_offset++;
if(lenOrPtr === 0x00) {
return result;
}
if((lenOrPtr & 0xC0) == 0xC0) {
var nameOffset = ((lenOrPtr & ~0xC0) << 8) | raw_packet[offset + internal_offset];
internal_offset++;
return dns_util.readName(raw_packet, offset, nameOffset, result);
}
for(var i=0; i<lenOrPtr; i++) {
var ch = raw_packet[offset + internal_offset];
internal_offset++;
result += String.fromCharCode(ch);
}
result += '.';
return dns_util.readName(raw_packet, offset, internal_offset, result);
},
decodeRR: function(raw_packet, offset, internal_offset, result) {
if(internal_offset > raw_packet.pcap_header.len) {
throw new Error("Malformed DNS RR. Offset is larger than the size of the packet (decodeRR). offset: " + offset + ", internal_offset: " + internal_offset + ", packet length: " + raw_packet.pcap_header.len);
}
var compressedName = raw_packet[internal_offset];
if((compressedName & 0xC0) == 0xC0) {
result.name = "";
result.name = dns_util.readName(raw_packet, offset, internal_offset - offset, result.name);
result.name = result.name.replace(/\.$/, '');
internal_offset += 2;
} else {
result.name = "";
var ch;
while((ch = raw_packet[internal_offset++]) !== 0x00) {
result.name += String.fromCharCode(ch);
}
}
result.rrtype = dns_util.qtype_to_string(unpack.uint16(raw_packet, internal_offset));
internal_offset += 2;
result.rrclass = dns_util.qclass_to_string(unpack.uint16(raw_packet, internal_offset));
internal_offset += 2;
result.ttl = unpack.uint32(raw_packet, internal_offset);
internal_offset += 4;
result.rdlength = unpack.uint16(raw_packet, internal_offset);
internal_offset += 2;
var data = dns_util.expandRRData(raw_packet, internal_offset, result);
if(data) {
result.data = data;
}
// skip rdata. TODO: store the rdata somewhere?
internal_offset += result.rdlength;
return internal_offset;
},
decodeRRs: function(raw_packet, offset, internal_offset, count, results) {
for (var i = 0; i < count; i++) {
results[i] = {};
internal_offset = dns_util.decodeRR(raw_packet, offset, internal_offset, results[i]);
}
return internal_offset;
}
};
decode.dns = function (raw_packet, offset) {
var ret = {}, i, internal_offset, question_done, len, parts;
// http://tools.ietf.org/html/rfc1035
ret.header = {};
ret.header.id = unpack.uint16(raw_packet, offset); // 0, 1
ret.header.qr = (raw_packet[offset + 2] & 128) >> 7;
ret.header.opcode = (raw_packet[offset + 2] & 120) >> 3;
ret.header.aa = (raw_packet[offset + 2] & 4) >> 2;
ret.header.tc = (raw_packet[offset + 2] & 2) >> 1;
ret.header.rd = raw_packet[offset + 2] & 1;
ret.header.ra = (raw_packet[offset + 3] & 128) >> 7;
ret.header.z = 0; // spec says this MUST always be 0
ret.header.rcode = raw_packet[offset + 3] & 15;
ret.header.qdcount = unpack.uint16(raw_packet, offset + 4); // 4, 5
ret.header.ancount = unpack.uint16(raw_packet, offset + 6); // 6, 7
ret.header.nscount = unpack.uint16(raw_packet, offset + 8); // 8, 9
ret.header.arcount = unpack.uint16(raw_packet, offset + 10); // 10, 11
internal_offset = offset + 12;
ret.question = [];
for (i = 0; i < ret.header.qdcount ; i += 1) {
ret.question[i] = {};
question_done = false;
parts = [];
while (!question_done && internal_offset < raw_packet.pcap_header.caplen) {
len = raw_packet[internal_offset];
if (len > 0) {
parts.push(raw_packet.toString("ascii", internal_offset + 1, internal_offset + 1 + len));
} else {
question_done = true;
}
internal_offset += (len + 1);
}
ret.question[i].qname = parts.join('.');
ret.question[i].qtype = dns_util.qtype_to_string(unpack.uint16(raw_packet, internal_offset));
internal_offset += 2;
ret.question[i].qclass = dns_util.qclass_to_string(unpack.uint16(raw_packet, internal_offset));
internal_offset += 2;
}
ret.answer = [];
if(ret.header.ancount > 100) {
throw new Error("Malformed DNS record. Too many answers.");
}
internal_offset = dns_util.decodeRRs(raw_packet, offset, internal_offset, ret.header.ancount, ret.answer);
ret.authority = [];
if(ret.header.ancount > 100) {
throw new Error("Malformed DNS record. Too many authorities.");
}
internal_offset = dns_util.decodeRRs(raw_packet, offset, internal_offset, ret.header.nscount, ret.authority);
ret.additional = [];
if(ret.header.ancount > 100) {
throw new Error("Malformed DNS record. Too many additional.");
}
internal_offset = dns_util.decodeRRs(raw_packet, offset, internal_offset, ret.header.arcount, ret.additional);
return ret;
};
exports.decode = decode;
// cache reverse DNS lookups for the life of the program
var dns_cache = (function () {
var cache = {},
requests = {};
function lookup_ptr(ip, callback) {
if (cache[ip]) {
return cache[ip];
}
else {
if (! requests[ip]) {
requests[ip] = true;
dns.reverse(ip, function (err, domains) {
if (err) {
cache[ip] = ip;
// TODO - check for network and broadcast addrs, since we have iface info
} else {
cache[ip] = domains[0];
if (typeof callback === 'function') {
callback(domains[0]);
}
}
delete requests[ip];
});
}
return ip;
}
}
return {
ptr: function (ip, callback) {
return lookup_ptr(ip, callback);
}
};
}());
exports.dns_cache = dns_cache;
var print = {}; // simple printers for common types
print.dns = function (packet) {
var ret = " DNS", dns = packet.link.ip.udp.dns;
if (dns.header.qr === 0) {
ret += " question";
} else if (dns.header.qr === 1) {
ret += " answer";
} else {
return " DNS format invalid: qr = " + dns.header.qr;
}
ret += " " + dns.question[0].qname + " " + dns.question[0].qtype;
return ret;
};
print.ip = function (packet) {
var ret = "",
ip = packet.link.ip;
switch (ip.protocol_name) {
case "TCP":
ret += " " + dns_cache.ptr(ip.saddr) + ":" + ip.tcp.sport + " -> " + dns_cache.ptr(ip.daddr) + ":" + ip.tcp.dport +
" TCP len " + ip.total_length + " [" +
Object.keys(ip.tcp.flags).filter(function (v) {
if (ip.tcp.flags[v] === 1) {
return true;
}
return false;
}).join(",") + "]";
break;
case "UDP":
ret += " " + dns_cache.ptr(ip.saddr) + ":" + ip.udp.sport + " -> " + dns_cache.ptr(ip.daddr) + ":" + ip.udp.dport;
if (ip.udp.sport === 53 || ip.udp.dport === 53) {
ret += print.dns(packet);
} else {
ret += " UDP len " + ip.total_length;
}
break;
case "ICMP":
ret += " " + dns_cache.ptr(ip.saddr) + " -> " + dns_cache.ptr(ip.daddr) + " ICMP " + ip.icmp.type_desc + " " +
ip.icmp.sequence;
break;
case "IGMP":
ret += " " + dns_cache.ptr(ip.saddr) + " -> " + dns_cache.ptr(ip.daddr) + " IGMP " + ip.igmp.type_desc + " " +
ip.igmp.group_address;
break;
default:
ret += " proto " + ip.protocol_name;
break;
}
return ret;
};
print.arp = function (packet) {
var ret = "",
arp = packet.link.arp;
if (arp.htype === 1 && arp.ptype === 0x800 && arp.hlen === 6 && arp.plen === 4) {
ret += " " + arp.sender_pa + " ARP " + arp.operation + " " + arp.target_pa;
if (arp.operation === "reply") {
ret += " hwaddr " + arp.target_ha;
}
} else {
ret = " unknown arp type";
ret += util.inspect(arp);
}
return ret;
};
print.slltype = function (packet) {
var ret = "";
switch (packet.link.ethertype) {
case 0x0:
ret += " 802.3 type ";
break;
case 0x800:
ret += print.ip(packet);
break;
case 0x806:
ret += print.arp(packet);
break;
case 0x86dd:
ret += " IPv6 ";
break;
case 0x88cc:
ret += " LLDP ";
break;
default:
console.log("pcap.js: print.linuxsll() - Don't know how to print type " + packet.link.ethertype);
}
return ret;
};
print.ethernet = function (packet) {
var ret = packet.link.shost + " -> " + packet.link.dhost;
switch (packet.link.ethertype) {
case 0x0:
ret += " 802.3 type ";
break;
case 0x800:
ret += print.ip(packet);
break;
case 0x806:
ret += print.arp(packet);
break;
case 0x86dd:
ret += " IPv6 ";
break;
case 0x88cc:
ret += " LLDP ";
break;
default:
console.log("pcap.js: print.ethernet() - Don't know how to print ethertype " + packet.link.ethertype);
}
return ret;
};
print.rawtype = function (packet) {
var ret = "raw";
ret += print.ip(packet);
return ret;
};
print.nulltype = function (packet) {
var ret = "loopback";
if (packet.link.pftype === 2) { // AF_INET, at least on my Linux and OSX machines right now
ret += print.ip(packet);
} else if (packet.link.pftype === 30) { // AF_INET6, often
console.log("pcap.js: print.nulltype() - Don't know how to print IPv6 packets.");
} else {
console.log("pcap.js: print.nulltype() - Don't know how to print protocol family " + packet.link.pftype);
}
return ret;
};
print.packet = function (packet_to_print) {
var ret = "";
switch (packet_to_print.link_type) {
case "LINKTYPE_ETHERNET":
ret += print.ethernet(packet_to_print);
break;
case "LINKTYPE_NULL":
ret += print.nulltype(packet_to_print);
break;
case "LINKTYPE_RAW":
ret += print.rawtype(packet_to_print);
break;
case "LINKTYPE_LINUX_SSL":
ret += print.slltype(packet_to_print);
break;
default:
console.log("Don't yet know how to print link_type " + packet_to_print.link_type);
}
return ret;
};
exports.print = print;
// Meaningfully hold the different types of frames at some point
function WebSocketFrame() {
this.type = null;
this.data = "";
}
function WebSocketParser(flag) {
this.buffer = new Buffer(64 * 1024); // 64KB is the max message size
this.buffer.end = 0;
if (flag === "draft76") {
this.state = "skip_response";
this.skipped_bytes = 0;
} else {
this.state = "frame_type";
}
this.frame = new WebSocketFrame();
events.EventEmitter.call(this);
}
util.inherits(WebSocketParser, events.EventEmitter);
util.inherits(PcapSession, events.EventEmitter);
WebSocketParser.prototype.execute = function (incoming_buf) {
var pos = 0;
exports.lib_version = binding.lib_version();
while (pos < incoming_buf.length) {
switch (this.state) {
case "skip_response":
this.skipped_bytes += 1;
if (this.skipped_bytes === 16) {
this.state = "frame_type";
}
pos += 1;
break;
case "frame_type":
this.frame.type = incoming_buf[pos];
pos += 1;
this.state = "read_until_marker";
break;
case "read_until_marker":
if (incoming_buf[pos] !== 255) {
this.buffer[this.buffer.end] = incoming_buf[pos];
this.buffer.end += 1;
pos += 1;
} else {
this.frame.data = this.buffer.toString('utf8', 0, this.buffer.end);
this.emit("message", this.frame.data); // this gets converted to "websocket message" in TCP_Tracker
this.state = "frame_type";
this.buffer.end = 0;
pos += 1;
}
break;
default:
throw new Error("invalid state " + this.state);
}
}
PcapSession.prototype.findalldevs = function () {
return binding.findalldevs();
};
function TCP_tracker() {
this.sessions = {};
events.EventEmitter.call(this);
function PacketWithHeader(buf, header, link_type) {
this.buf = buf;
this.header = header;
this.link_type = link_type;
}
util.inherits(TCP_tracker, events.EventEmitter);
exports.TCP_tracker = TCP_tracker;
TCP_tracker.prototype.make_session_key = function (src, dst) {
return [ src, dst ].sort().join("-");
PcapSession.prototype.on_packet_ready = function () {
var full_packet = new PacketWithHeader(this.buf, this.header, this.link_type);
this.emit("packet", full_packet);
};
TCP_tracker.prototype.detect_http_request = function (buf) {
var str = buf.toString('utf8', 0, buf.length);
return (/^(OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT|COPY|LOCK|MKCOL|MOVE|PROPFIND|PROPPATCH|UNLOCK) [^\s\r\n]+ HTTP\/\d\.\d\r\n/.test(str));
PcapSession.prototype.close = function () {
this.opened = false;
this.session.close();
this.readWatcher.stop();
// TODO - remove listeners so program will exit I guess?
};
TCP_tracker.prototype.session_stats = function (session) {
var send_acks = Object.keys(session.send_acks),
recv_acks = Object.keys(session.recv_acks),
total_time = session.close_time - session.syn_time,
stats = {};
send_acks.sort();
recv_acks.sort();
stats.recv_times = {};
send_acks.forEach(function (v) {
if (session.recv_packets[v]) {
stats.recv_times[v] = session.send_acks[v] - session.recv_packets[v];
} else {
// console.log("send ACK with missing recv seqno: " + v);
}
});
stats.send_times = {};
recv_acks.forEach(function (v) {
if (session.send_packets[v]) {
stats.send_times[v] = session.recv_acks[v] - session.send_packets[v];
} else {
// console.log("recv ACK with missing send seqno: " + v);
}
});
stats.recv_retrans = {};
Object.keys(session.recv_retrans).forEach(function (v) {
stats.recv_retrans[v] = session.recv_retrans[v];
});
stats.total_time = total_time;
stats.send_overhead = session.send_bytes_ip + session.send_bytes_tcp;
stats.send_payload = session.send_bytes_payload;
stats.send_total = stats.send_overhead + stats.send_payload;
stats.recv_overhead = session.recv_bytes_ip + session.recv_bytes_tcp;
stats.recv_payload = session.recv_bytes_payload;
stats.recv_total = stats.recv_overhead + stats.recv_payload;
if (session.http.request) {
stats.http_request = session.http.request;
}
return stats;
PcapSession.prototype.stats = function () {
return this.session.stats();
};
TCP_tracker.prototype.setup_http_tracking = function (session) {
var self = this, http = {
request : {
headers : {},
url : "",
method : "",
body_len : 0,
http_version : null
},
response : {
headers : {},
status_code : null,
body_len : 0,
http_version : null
},
request_parser : new HTTPParser(HTTPParser.REQUEST),
response_parser : new HTTPParser(HTTPParser.RESPONSE)
};
http.request_parser.url = '';
http.request_parser.onHeaders = function(headers, url) {
http.request_parser.headers = (http.request_parser.headers || []).concat(headers);
http.request_parser.url += url;
};
http.request_parser.onHeadersComplete = function(info) {
http.request.method = info.method;
http.request.url = info.url || http.request_parser.url;
http.request.http_version = info.versionMajor + "." + info.versionMinor;
var headers = info.headers || http.request_parser.headers;
for ( var i = 0; i < headers.length; i += 2) {
http.request.headers[headers[i]] = headers[i + 1];
}
self.emit("http request", session, http);
};
http.request_parser.onBody = function(buf, start, len) {
http.request.body_len += len;
self.emit("http request body", session, http, buf.slice(start, start + len));
};
http.request_parser.onMessageComplete = function() {
self.emit("http request complete", session, http);
};
http.response_parser.onHeaders = function(headers, url) {
http.response_parser.headers = (http.response_parser.headers || []).concat(headers);
};
http.response_parser.onHeadersComplete = function(info) {
http.response.status_code = info.statusCode;
http.response.http_version = info.versionMajor + "." + info.versionMinor;
var headers = info.headers || http.response_parser.headers;
for ( var i = 0; i < headers.length; i += 2) {
http.response.headers[headers[i]] = headers[i + 1];
}
if (http.response.status_code === 101 && http.response.headers.Upgrade === "WebSocket") {
if (http.response.headers["Sec-WebSocket-Location"]) {
self.setup_websocket_tracking(session, "draft76");
} else {
self.setup_websocket_tracking(session);
}
self.emit('websocket upgrade', session, http);
session.http_detect = false;
session.websocket_detect = true;
delete http.response_parser.onMessageComplete;
} else {
self.emit('http response', session, http);
}
};
http.response_parser.onBody = function(buf, start, len) {
http.response.body_len += len;
self.emit('http response body', session, http, buf.slice(start, start + len));
};
http.response_parser.onMessageComplete = function() {
self.emit('http response complete', session, http);
};
session.http = http;
PcapSession.prototype.inject = function (data) {
return this.session.inject(data);
};
TCP_tracker.prototype.setup_websocket_tracking = function (session, flag) {
var self = this;
exports.Pcap = PcapSession;
exports.PcapSession = PcapSession;
session.websocket_parser_send = new WebSocketParser();
session.websocket_parser_send.on("message", function (message_string) {
self.emit("websocket message", session, "send", message_string);
});
session.websocket_parser_recv = new WebSocketParser(flag);
session.websocket_parser_recv.on("message", function (message_string) {
self.emit("websocket message", session, "recv", message_string);
});
exports.createSession = function (device, filter, buffer_size, monitor) {
return new PcapSession(true, device, filter, buffer_size, null, monitor);
};
TCP_tracker.prototype.track_states = {};
TCP_tracker.prototype.track_states.SYN_SENT = function (packet, session) {
var ip = packet.link.ip,
tcp = ip.tcp,
src = ip.saddr + ":" + tcp.sport;
if (src === session.dst && tcp.flags.syn && tcp.flags.ack) {
session.recv_bytes_ip += ip.header_bytes;
session.recv_bytes_tcp += tcp.header_bytes;
session.recv_packets[tcp.seqno + 1] = packet.pcap_header.time_ms;
session.recv_acks[tcp.ackno] = packet.pcap_header.time_ms;
session.recv_isn = tcp.seqno;
session.recv_window_scale = tcp.options.window_scale || 1; // multiplier, not bit shift value
session.state = "SYN_RCVD";
} else if (tcp.flags.rst) {
session.state = "CLOSED";
delete this.sessions[session.key];
this.emit('reset', session, "recv"); // TODO - check which direction did the reset, probably recv
} else {
// console.log("Didn't get SYN-ACK packet from dst while handshaking: " + util.inspect(tcp, false, 4));
}
exports.createOfflineSession = function (path, filter) {
return new PcapSession(false, path, filter, 0, null, null);
};
TCP_tracker.prototype.track_states.SYN_RCVD = function (packet, session) {
var ip = packet.link.ip,
tcp = ip.tcp,
src = ip.saddr + ":" + tcp.sport;
if (src === session.src && tcp.flags.ack) { // TODO - make sure SYN flag isn't set, also match src and dst
session.send_bytes_ip += ip.header_bytes;
session.send_bytes_tcp += tcp.header_bytes;
session.send_acks[tcp.ackno] = packet.pcap_header.time_ms;
session.handshake_time = packet.pcap_header.time_ms;
this.emit('start', session);
session.state = "ESTAB";
} else {
// console.log("Didn't get ACK packet from src while handshaking: " + util.inspect(tcp, false, 4));
}
};
TCP_tracker.prototype.track_states.ESTAB = function (packet, session) {
var ip = packet.link.ip,
tcp = ip.tcp,
src = ip.saddr + ":" + tcp.sport;
// TODO - actually implement SACK decoding and tracking
// if (tcp.options.sack) {
// console.log("SACK magic, handle this: " + util.inspect(tcp.options.sack));
// console.log(util.inspect(ip, false, 5));
// }
// TODO - check for tcp.flags.rst and emit reset event
if (src === session.src) { // this packet came from the active opener / client
session.send_bytes_ip += ip.header_bytes;
session.send_bytes_tcp += tcp.header_bytes;
if (tcp.data_bytes) {
if (session.send_bytes_payload === 0) {
session.http_detect = this.detect_http_request(tcp.data);
if (session.http_detect) {
this.setup_http_tracking(session);
}
}
session.send_bytes_payload += tcp.data_bytes;
if (session.send_packets[tcp.seqno + tcp.data_bytes]) {
this.emit('retransmit', session, "send", tcp.seqno + tcp.data_bytes);
} else {
if (session.http_detect) {
try {
session.http.request_parser.execute(tcp.data, 0, tcp.data.length);
} catch (request_err) {
this.emit('http error', session, "send", request_err);
}
} else if (session.websocket_detect) {
session.websocket_parser_send.execute(tcp.data);
// TODO - check for WS parser errors
}
}
session.send_packets[tcp.seqno + tcp.data_bytes] = packet.pcap_header.time_ms;
}
if (session.recv_packets[tcp.ackno]) {
if (session.send_acks[tcp.ackno]) {
// console.log("Already sent this ACK, which perhaps is fine.");
} else {
session.send_acks[tcp.ackno] = packet.pcap_header.time_ms;
}
} else {
// console.log("sending ACK for packet we didn't see received: " + tcp.ackno);
}
if (tcp.flags.fin) {
session.state = "FIN_WAIT";
}
} else if (src === session.dst) { // this packet came from the passive opener / server
session.recv_bytes_ip += ip.header_bytes;
session.recv_bytes_tcp += tcp.header_bytes;
if (tcp.data_bytes) {
session.recv_bytes_payload += tcp.data_bytes;
if (session.recv_packets[tcp.seqno + tcp.data_bytes]) {
this.emit('retransmit', session, "recv", tcp.seqno + tcp.data_bytes);
if (session.recv_retrans[tcp.seqno + tcp.data_bytes]) {
session.recv_retrans[tcp.seqno + tcp.data_bytes] += 1;
} else {
session.recv_retrans[tcp.seqno + tcp.data_bytes] = 1;
}
} else {
if (session.http_detect) {
try {
session.http.response_parser.execute(tcp.data, 0, tcp.data.length);
} catch (response_err) {
this.emit('http error', session, "recv", response_err);
}
} else if (session.websocket_detect) {
session.websocket_parser_recv.execute(tcp.data);
// TODO - check for WS parser errors
}
}
session.recv_packets[tcp.seqno + tcp.data_bytes] = packet.pcap_header.time_ms;
}
if (session.send_packets[tcp.ackno]) {
if (session.recv_acks[tcp.ackno]) {
// console.log("Already received this ACK, which I'm guessing is fine.");
} else {
session.recv_acks[tcp.ackno] = packet.pcap_header.time_ms;
}
} else {
// console.log("receiving ACK for packet we didn't see sent: " + tcp.ackno);
}
if (tcp.flags.fin) {
session.state = "CLOSE_WAIT";
}
} else {
console.log("non-matching packet in session: " + util.inspect(packet));
}
};
TCP_tracker.prototype.track_states.FIN_WAIT = function (packet, session) {
var ip = packet.link.ip,
tcp = ip.tcp,
src = ip.saddr + ":" + tcp.sport;
// TODO - need to track half-closed data
if (src === session.dst && tcp.flags.fin) {
session.state = "CLOSING";
}
};
TCP_tracker.prototype.track_states.CLOSE_WAIT = function (packet, session) {
var ip = packet.link.ip,
tcp = ip.tcp,
src = ip.saddr + ":" + tcp.sport;
// TODO - need to track half-closed data
if (src === session.src && tcp.flags.fin) {
session.state = "LAST_ACK";
}
};
TCP_tracker.prototype.track_states.LAST_ACK = function (packet, session) {
var ip = packet.link.ip,
tcp = ip.tcp,
src = ip.saddr + ":" + tcp.sport;
// TODO - need to track half-closed data
if (src === session.dst) {
session.close_time = packet.pcap_header.time_ms;
session.state = "CLOSED";
delete this.sessions[session.key];
this.emit('end', session);
}
};
TCP_tracker.prototype.track_states.CLOSING = function (packet, session) {
var ip = packet.link.ip,
tcp = ip.tcp,
src = ip.saddr + ":" + tcp.sport;
// TODO - need to track half-closed data
if (src === session.src) {
session.close_time = packet.pcap_header.time_ms;
session.state = "CLOSED";
delete this.sessions[session.key];
this.emit('end', session);
}
};
TCP_tracker.prototype.track_states.CLOSED = function (packet, session) {
var ip = packet.link.ip,
tcp = ip.tcp,
src = ip.saddr + ":" + tcp.sport;
// The states aren't quite right here. All possible states of FIN and FIN/ACKs aren't handled.
// So some of the bytes of the session may not be properly accounted for.
};
TCP_tracker.prototype.track_next = function (key, packet) {
var session = this.sessions[key];
if (typeof session !== 'object') {
throw new Error("track_next: couldn't find session for " + key);
}
if (typeof this.track_states[session.state] === 'function') {
this.track_states[session.state].call(this, packet, session);
} else {
console.log(util.debug(session));
throw new Error("Don't know how to handle session state " + session.state);
}
};
TCP_tracker.prototype.track_packet = function (packet) {
var ip, tcp, src, src_mac, dst, dst_mac, key, session, self = this;
if (packet.link && packet.link.ip && packet.link.ip.tcp) {
ip = packet.link.ip;
tcp = ip.tcp;
src = ip.saddr + ":" + tcp.sport;
src_mac = packet.link.shost;
dst = ip.daddr + ":" + tcp.dport;
dst_mac = packet.link.dhost;
key = this.make_session_key(src, dst);
session = this.sessions[key];
if (tcp.flags.syn && !tcp.flags.ack) {
if (session === undefined) {
this.sessions[key] = {
src: src, // the side the sent the initial SYN
src_mac: src_mac,
dst: dst, // the side that the initial SYN was sent to
dst_mac: dst_mac,
syn_time: packet.pcap_header.time_ms,
state: "SYN_SENT",
key: key, // so we can easily remove ourselves
send_isn: tcp.seqno,
send_window_scale: tcp.options.window_scale || 1, // multipler, not bit shift value
send_packets: {}, // send_packets is indexed by the expected ackno: seqno + length
send_acks: {},
send_retrans: {},
send_next_seq: tcp.seqno + 1,
send_acked_seq: null,
send_bytes_ip: ip.header_bytes,
send_bytes_tcp: tcp.header_bytes,
send_bytes_payload: 0,
recv_isn: null,
recv_window_scale: null,
recv_packets: {},
recv_acks: {},
recv_retrans: {},
recv_next_seq: null,
recv_acked_seq: null,
recv_bytes_ip: 0,
recv_bytes_tcp: 0,
recv_bytes_payload: 0
};
session = this.sessions[key];
session.send_packets[tcp.seqno + 1] = packet.pcap_header.time_ms;
session.src_name = dns_cache.ptr(ip.saddr, function (name) {
session.src_name = name + ":" + tcp.sport;
self.emit("reverse", ip.saddr, name);
}) + ":" + tcp.sport;
session.dst_name = dns_cache.ptr(ip.daddr, function (name) {
session.dst_name = name + ":" + tcp.dport;
self.emit("reverse", ip.daddr, name);
}) + ":" + tcp.dport;
session.current_cap_time = packet.pcap_header.time_ms;
} else { // SYN retry
this.emit('syn retry', session);
}
} else { // not a SYN
if (session) {
session.current_cap_time = packet.pcap_header.time_ms;
this.track_next(key, packet);
} else {
// silently ignore session in progress
// TODO - for sessions in progress, we should pretend that this is the first packet from
// the sender, go into ESTAB, and run HTTP detector. That way we might see HTTP
// requests on keepalive connections
}
}
} else {
// silently ignore any non IPv4 TCP packets
// user should filter these out with their pcap filter, but oh well.
}
return session;
};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc