Skip to content

Commit

Permalink
Add tests for tcp decode w/nodata
Browse files Browse the repository at this point in the history
jmaxxz committed Mar 15, 2015
1 parent 3cdc37c commit fb38fa9
Showing 3 changed files with 158 additions and 72 deletions.
101 changes: 54 additions & 47 deletions decode/tcp.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@

function TCPFlags() {
this.cwr = null;
this.ece = null;
this.urg = null;
this.ack = null;
this.psh = null;
this.rst = null;
this.syn = null;
this.fin = null;
this.nonce = undefined;
this.cwr = undefined;
this.ece = undefined;
this.urg = undefined;
this.ack = undefined;
this.psh = undefined;
this.rst = undefined;
this.syn = undefined;
this.fin = undefined;
}

TCPFlags.prototype.decode = function (first_byte, second_byte) {
this.nonce = Boolean(first_byte & 16);
this.cwr = Boolean(second_byte & 128);
this.ece = Boolean(second_byte & 64);
this.urg = Boolean(second_byte & 32);
this.ack = Boolean(second_byte & 16);
this.psh = Boolean(second_byte & 8);
this.rst = Boolean(second_byte & 4);
this.syn = Boolean(second_byte & 2);
this.fin = Boolean(second_byte & 1);
return this;
};

TCPFlags.prototype.toString = function () {
var ret = "[";

@@ -162,20 +176,19 @@ TCPOptions.prototype.toString = function () {
};

function TCP() {
this.sport = null;
this.dport = null;
this.seqno = null;
this.ackno = null;
this.data_offset = null;
this.header_bytes = null; // not part of packet but handy
this.reserved = null;
this.flags = new TCPFlags();
this.window_size = null;
this.checksum = null;
this.urgent_pointer = null;
this.options = null;
this.data = null;
this.data_bytes = null;
this.sport = undefined;
this.dport = undefined;
this.seqno = undefined;
this.ackno = undefined;
this.headerLength = undefined;
this.reserved = undefined;
this.flags = undefined;
this.windowSize = undefined;
this.checksum = undefined;
this.urgentPointer = undefined;
this.options = undefined;
this.data = undefined;
this.dataLength = undefined;
}

// If you get stuck trying to decode or understand the offset math, stick this block in to dump the contents:
@@ -195,56 +208,50 @@ TCP.prototype.decode = function (raw_packet, offset, len) {
offset += 4;
this.ackno = raw_packet.readUInt32BE(offset, true); // 8, 9, 10, 11
offset += 4;
this.data_offset = (raw_packet[offset] & 0xf0) >> 4; // first 4 bits of 12
if (this.data_offset < 5 || this.data_offset > 15) {
throw new Error("invalid data_offset: " + this.data_offset);
}
this.header_bytes = this.data_offset * 4; // convenience for using data_offset
this.reserved = raw_packet[offset] & 15; // second 4 bits of 12
offset += 1;
var all_flags = raw_packet[offset];
this.flags.cwr = (all_flags & 128) >> 7; // all flags packed into 13
this.flags.ece = (all_flags & 64) >> 6;
this.flags.urg = (all_flags & 32) >> 5;
this.flags.ack = (all_flags & 16) >> 4;
this.flags.psh = (all_flags & 8) >> 3;
this.flags.rst = (all_flags & 4) >> 2;
this.flags.syn = (all_flags & 2) >> 1;
this.flags.fin = all_flags & 1;
offset += 1;
this.window_size = raw_packet.readUInt16BE(offset, true); // 14, 15
// The first 4 bits of the next header * 4 tells use the length
// of the header.
this.headerLength = (raw_packet[offset] & 0xf0) >> 2;

this.flags = new TCPFlags().decode(raw_packet[offset], raw_packet[offset+1]);
offset += 2;
this.windowSize = raw_packet.readUInt16BE(offset, true); // 14, 15
offset += 2;
this.checksum = raw_packet.readUInt16BE(offset, true); // 16, 17
offset += 2;
this.urgent_pointer = raw_packet.readUInt16BE(offset, true); // 18, 19
this.urgentPointer = raw_packet.readUInt16BE(offset, true); // 18, 19
offset += 2;

this.options = new TCPOptions();
var options_len = this.header_bytes - (offset - orig_offset);
var options_len = this.headerLength - (offset - orig_offset);
if (options_len > 0) {
this.options.decode(raw_packet, offset, options_len);
offset += options_len;
}

this.data_bytes = len - this.header_bytes;
if (this.data_bytes > 0) {
this.dataLength = len - this.headerLength;
if (this.dataLength > 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.
this.data = raw_packet.slice(offset, offset + this.data_bytes);
} else {
// null indicated the value was set. Where as undefined
// means the value was never set. Since there is no data
// we explicity want to communicate this to consumers.
this.data = null;
}

return this;
};

TCP.prototype.toString = function () {
var ret = this.sport + "->" + this.dport + " seq " + this.seqno + " ack " + this.ackno + " flags " + this.flags + " " +
"win " + this.window_size + " csum " + this.checksum;
"win " + this.windowSize + " csum " + this.checksum;
if (this.urgent_pointer) {
ret += " urg " + this.urgent_pointer;
ret += " urg " + this.urgentPointer;
}
ret += " " + this.options.toString();
ret += " len " + this.data_bytes;
ret += " len " + this.dataLength;
return ret;
};

79 changes: 79 additions & 0 deletions spec/decode/tcp.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
var TCP = require("../../decode/tcp");
require("should");

describe("TCP", function(){
var exampleTcp, instance;
beforeEach(function () {
exampleTcp = new Buffer("b5dd00500aaf604e0000000060c2102044b2000002040218", "hex");
instance = new TCP();
});
describe("#decode()", function(){
it("is a function", function(){
instance.decode.should.be.type("function");
});

it("sets #sport to the source port", function() {
instance.decode(exampleTcp, 0);
instance.should.have.property("sport", 46557);
});

it("sets #dport to the destination port", function() {
instance.decode(exampleTcp, 0);
instance.should.have.property("dport", 80);
});

it("sets #seqno to the sequence number (not relative)", function() {
instance.decode(exampleTcp, 0);
instance.should.have.property("seqno", 179265614);
});

it("sets #headerLength to the length of the tcp header", function() {
instance.decode(exampleTcp, 0);
instance.should.have.property("headerLength", 24);
});

it("sets #flags to a decoded copy of the tcp flags", function() {
instance.decode(exampleTcp, 0);
instance.flags.should.have.property("nonce", false);
//Congestion Window Reduce
instance.flags.should.have.property("cwr", true);
//Enc-echo set
instance.flags.should.have.property("ece", true);
//Urgent
instance.flags.should.have.property("urg", false);
//Acknowledgement
instance.flags.should.have.property("ack", false);
//Push
instance.flags.should.have.property("psh", false);
//Reset
instance.flags.should.have.property("rst", false);
instance.flags.should.have.property("syn", true);
instance.flags.should.have.property("fin", false);
});

it("sets #windowSize to the window size", function() {
instance.decode(exampleTcp, 0);
instance.should.have.property("windowSize", 4128);
});

it("sets #checksum to the checksum", function() {
instance.decode(exampleTcp, 0);
instance.should.have.property("checksum", 17586);
});

it("sets #urgentPointer to urgentPointer", function() {
instance.decode(exampleTcp, 0);
instance.should.have.property("urgentPointer", 0);
});

it("sets #dataLength to the length of data", function() {
instance.decode(exampleTcp, 0, 24);
instance.should.have.property("dataLength", 0);
});

it("sets #data to null when there is no data", function() {
instance.decode(exampleTcp, 0);
instance.should.have.property("data", null);
});
});
});
50 changes: 25 additions & 25 deletions tcp_tracker.js
Original file line number Diff line number Diff line change
@@ -110,7 +110,7 @@ TCPSession.prototype.track = function (packet) {
this.send_window_scale = tcp.options.window_scale || 1; // multipler, not bit shift value
this.send_next_seq = tcp.seqno + 1;
this.send_bytes_ip = ip.headerLength;
this.send_bytes_tcp = tcp.header_bytes;
this.send_bytes_tcp = tcp.headerLength;
} else if (tcp.flags.syn && !tcp.flags.ack) {
this.emit("syn retry", this);
} else { // not a SYN, so run the state machine
@@ -124,8 +124,8 @@ TCPSession.prototype.SYN_SENT = function (packet) {
var src = ip.saddr + ":" + tcp.sport;

if (src === this.dst && tcp.flags.syn && tcp.flags.ack) {
this.recv_bytes_ip += ip.header_bytes;
this.recv_bytes_tcp += tcp.header_bytes;
this.recv_bytes_ip += ip.headerLength;
this.recv_bytes_tcp += tcp.headerLength;
this.recv_packets[tcp.seqno + 1] = this.current_cap_time;
this.recv_acks[tcp.ackno] = this.current_cap_time;
this.recv_isn = tcp.seqno;
@@ -145,8 +145,8 @@ TCPSession.prototype.SYN_RCVD = function (packet) {
var src = ip.saddr + ":" + tcp.sport;

if (src === this.src && tcp.flags.ack) { // TODO - make sure SYN flag isn't set, also match src and dst
this.send_bytes_ip += ip.header_bytes;
this.send_bytes_tcp += tcp.header_bytes;
this.send_bytes_ip += ip.headerLength;
this.send_bytes_tcp += tcp.headerLength;
this.send_acks[tcp.ackno] = this.current_cap_time;
this.connect_time = this.current_cap_time;
this.emit("start", this);
@@ -169,21 +169,21 @@ TCPSession.prototype.ESTAB = function (packet) {
var src = ip.saddr + ":" + tcp.sport;

if (src === this.src) { // this packet came from the active opener / client
this.send_bytes_ip += ip.header_bytes;
this.send_bytes_tcp += tcp.header_bytes;
if (tcp.data_bytes) {
if (this.send_packets[tcp.seqno + tcp.data_bytes]) {
this.emit("retransmit", this, "send", tcp.seqno + tcp.data_bytes);
if (this.send_retrans[tcp.seqno + tcp.data_bytes]) {
this.send_retrans[tcp.seqno + tcp.data_bytes] += 1;
this.send_bytes_ip += ip.headerLength;
this.send_bytes_tcp += tcp.headerLength;
if (tcp.dataLength > 0) {
if (this.send_packets[tcp.seqno + tcp.dataLength]) {
this.emit("retransmit", this, "send", tcp.seqno + tcp.dataLength);
if (this.send_retrans[tcp.seqno + tcp.dataLength]) {
this.send_retrans[tcp.seqno + tcp.dataLength] += 1;
} else {
this.send_retrans[tcp.seqno + tcp.data_bytes] = 1;
this.send_retrans[tcp.seqno + tcp.dataLength] = 1;
}
} else {
this.emit("data send", this, tcp.data);
}
this.send_bytes_payload += tcp.data_bytes;
this.send_packets[tcp.seqno + tcp.data_bytes] = this.current_cap_time;
this.send_bytes_payload += tcp.dataLength;
this.send_packets[tcp.seqno + tcp.dataLength] = this.current_cap_time;
}
if (this.recv_packets[tcp.ackno]) {
this.send_acks[tcp.ackno] = this.current_cap_time;
@@ -193,21 +193,21 @@ TCPSession.prototype.ESTAB = function (packet) {
this.state = "FIN_WAIT";
}
} else if (src === this.dst) { // this packet came from the passive opener / server
this.recv_bytes_ip += ip.header_bytes;
this.recv_bytes_tcp += tcp.header_bytes;
if (tcp.data_bytes) {
if (this.recv_packets[tcp.seqno + tcp.data_bytes]) {
this.emit("retransmit", this, "recv", tcp.seqno + tcp.data_bytes);
if (this.recv_retrans[tcp.seqno + tcp.data_bytes]) {
this.recv_retrans[tcp.seqno + tcp.data_bytes] += 1;
this.recv_bytes_ip += ip.headerLength;
this.recv_bytes_tcp += tcp.headerLength;
if (tcp.dataLength > 0) {
if (this.recv_packets[tcp.seqno + tcp.dataLength]) {
this.emit("retransmit", this, "recv", tcp.seqno + tcp.dataLength);
if (this.recv_retrans[tcp.seqno + tcp.dataLength]) {
this.recv_retrans[tcp.seqno + tcp.dataLength] += 1;
} else {
this.recv_retrans[tcp.seqno + tcp.data_bytes] = 1;
this.recv_retrans[tcp.seqno + tcp.dataLength] = 1;
}
} else {
this.emit("data recv", this, tcp.data);
}
this.recv_bytes_payload += tcp.data_bytes;
this.recv_packets[tcp.seqno + tcp.data_bytes] = this.current_cap_time;
this.recv_bytes_payload += tcp.dataLength;
this.recv_packets[tcp.seqno + tcp.dataLength] = this.current_cap_time;
}
if (this.send_packets[tcp.ackno]) {
this.recv_acks[tcp.ackno] = this.current_cap_time;

0 comments on commit fb38fa9

Please sign in to comment.