var closet = closet || {}; closet.reports = closet.reports || []; closet.reports.push({ name: 'visualize', title: 'Visualize', apply: function(pcap, fields) { return pcap.index.about.flows > 0; }, items:[{ name: 'flows', title: 'Flows', options: function(pcap, opts, cb) { closet.api.flows.clients(pcap._id, '*', 0.0, pcap.index.about.duration, function(data) { var clients = $.map(data.rows, function(row) { return row.key; }); opts.choice('client', '', { values: [''].concat(clients) }); closet.api.flows.servers(pcap._id, '*', 0.0, pcap.index.about.duration, function(data) { var servers = $.map(data.rows, function(row) { return row.key; }); opts.choice('server', '', { values: [''].concat(servers) }); closet.api.flows.services(pcap._id, '*', 0.0, pcap.index.about.duration, function(data) { var services = $.map(data.rows, function(row) { return row.key; }); opts.choice('service', '', { values: [''].concat(services) }); opts.choice('group', 'service', { values: [ 'service', 'src', 'dst', 'dport' ] }); opts.choice('speed', 'normal', { values: [ 'normal', 'slow' ] }); cb(); }); }); }); }, create: (function() { var globalReportId = 0; return function(ctx, $root, pcap, opts) { var reportId = ++globalReportId; var buckets = {}; var flows = {}; var packets = []; var npackets = 1; var timeout = { normal:50, slow:100 }[opts.speed]; var paused = false; var fetching = false; var timerId; $root.html( 'create' + '' + '

' + '

' + '' + '' + '' + '
' + '
' ); var $buckets = $root.find('table.replay tbody'); var $status = $root.find('div.status'); var $create = $root.find('a.create'); var $pause = $root.find('a.pause'); var $info = $root.find('div.info'); $pause.click(function() { paused = !paused; if (paused) { $pause.text('resume'); } else { $pause.text('pause'); $info.empty(); timerId = timerId || setTimeout(function() { _next(); }, timeout); } }); // Create the key used to bucketize the flows var _key = function(flow) { if (opts.group === 'dport') { return (flow.proto === 6 ? 'tcp/' : 'udp/') + flow.dport; } return flow[opts.group]; }; // If we are filtering on a service, map the service to the set // of ports so that we can filter the packets that belong to // that flow if (opts.service) { var q = 'flow.service:' + closet.util.escapeQuery(opts.service.replace(/-\/.*/, '')); var r = closet.mr.count('flow.dport'); closet.api.flows.report(pcap._id, q, r, function(data) { opts.ports = $.map(data.rows, function(row) { return row.key; }); }); } // Generate the query depending on the opts setting var _query = function(id) { var q = 'pkt.flow:>=1 pkt.id:>=' + (id+1); if (opts.client) { var client = closet.util.escapeQuery(opts.client); q += ' ((pkt.dir:0 pkt.src:' + client + ') || (pkt.dir:1 pkt.dst:' + client + '))'; } if (opts.server) { var server = closet.util.escapeQuery(opts.server); q += ' ((pkt.dir:0 pkt.dst:' + server + ') || (pkt.dir:1 pkt.src:' + server + '))'; } if (opts.ports) { var ports = opts.ports.join('||'); q += ' ((pkt.dir:0 udp.dstport|tcp.dstport:(' + ports + ')) || (pkt.dir:1 udp.srcport|tcp.srcport:(' + ports + ')))'; } if (opts.group === 'dport') { q += ' (ip.proto:(6||17))'; } return q; } // Add a flow to the list. If it's the first flow for a bucket, // create the bucket as well. var _add = function(flow) { var key = _key(flow); if (!buckets.hasOwnProperty(key)) { var $tr = $( '' + '' + closet.util.escapeHTML(key) + '' + '' + '' ); $buckets.append($tr); buckets[key] = {}; buckets[key].$td = $tr.find('td.flows'); buckets[key].nflows = 0; } var $span = $( '
' + '
' + '
' ); $span.click(function() { $info.html( 'Flow ' + flow.id + '. ' + '' + flow.src + '' + ' » ' + '' + flow.dst + '' + ' :: ' + '' + closet.util.escapeHTML(flow.service) + ' ' + closet.util.escapeHTML(flow.title.slice(0, 80)) + '

' + '' + 'Download pcap for this flow.' ); }); buckets[key].$td.append($span); buckets[key].nflows++; flow.$span = $span; flow.$fill = $span.find('div.fill'); flow.npackets = 1; flows[flow.id] = flow; }; // Remove a flow from the flow list and if it's the last // flow in the bucket, remove the bucketized row as well var _remove = function(flow) { var key = _key(flow); setTimeout(function() { delete flows[flow.id]; flow.$fill.css('background-color', '#dd1122'); flow.$span.fadeOut(1000, function() { $(this).remove(); if (--buckets[key].nflows === 0) { var s_entry = buckets[key]; delete buckets[key]; s_entry.$td.parent('tr').remove(); } }); }, 100); }; var location = ctx.app.getLocation(); var _next = function() { timerId = undefined; // Abort if the user navigates to a different URL if (ctx.app.getLocation() !== location) { return; } // Abort if the user changes 'opts' to create a new viz if (reportId !== globalReportId) { return; } // Don't process this packet, if the visualization is paused if (paused) { return; } // When we hit the low water-mark, prefetch (async) the // next set of packets if (packets.length === 30 && !fetching) { var last = packets[packets.length-1]; fetching = true; closet.api.packets.list(pcap._id, { q: _query(last.id), limit: 40, terms: false }, function(data) { fetching = false; packets = packets.concat(data.rows); timerId = timerId || setTimeout(function() { _next(); }, timeout); }); } // Process the next packet. If there are no more packets // and we are not in the middle of fetching more chunks, // then we are done. var pkt = packets.shift(); if (pkt === undefined) { if (!fetching) { $status.empty(); $pause.hide(); $info.empty(); $create.show(); } return; } // Update the status with the current packet $status.html( 'Packet  ' + pkt.src + '' + ' » ' + '' + pkt.dst + '' + ' :: ' + '' + closet.util.escapeHTML(pkt.service) + ' ' + closet.util.escapeHTML(pkt.title.slice(0, 80)) ); // Add/remove flows for this packet if (!flows.hasOwnProperty(pkt.flow)) { closet.api.flows.list(pcap._id, { q: 'flow.id:' + pkt.flow, limit: 1, terms: false }, function(data) { var flow = data.rows[0]; _add(flow); if (flow.first === flow.last) { _remove(flow); } timerId = timerId || setTimeout(function() { _next(); }, timeout); }); } else { var flow = flows[pkt.flow]; var key = _key(flow); flow.$fill.css('width', Math.floor((++flow.npackets)*100/flow.packets) + '%'); if (flow.last === pkt.id) { _remove(flow); } timerId = timerId || setTimeout(function() { _next(); }, timeout); } }; $create.click(function() { $(this).hide(); $pause.show(); $status.html('hang on'); closet.api.packets.list(pcap._id, { q: _query(0), limit: 100, terms: false }, function(data) { packets = data.rows; _next(); }); }); }; }()) }] });