Basic uploading and attribute configs work.
diff --git a/app.py b/app.py
index 1649113..9a87349 100644
--- a/app.py
+++ b/app.py
@@ -1,22 +1,50 @@
#!/usr/bin/env python
+import collections
import json
import os.path
import random
import re
import sys
+import traceback
import urllib
import wsgiref.handlers
import wsgiref.simple_server
import webapp2
import tornado.template
+from google.appengine.api import memcache
+from google.appengine.api import users
from google.appengine.ext import blobstore
+from google.appengine.ext import ndb
from google.appengine.ext.webapp import blobstore_handlers
+from google.appengine.ext.webapp import util
import wifipacket
+BROADCAST = 'ff:ff:ff:ff:ff:ff'
+
loader = tornado.template.Loader('.')
+def GoogleLoginRequired(func):
+ def Handler(self, *args, **kwargs):
+ user = users.get_current_user()
+ if not user:
+ self.redirect(users.create_login_url('/'))
+ elif not user.email().endswith('@google.com'):
+ self.response.set_status(401, 'Unauthorized')
+ self.response.write("Sorry. You're not an authorized user.")
+ else:
+ return func(self, *args, **kwargs)
+ return Handler
+
+
+class PcapData(ndb.Model):
+ create_time = ndb.DateTimeProperty(auto_now_add=True)
+ filename = ndb.StringProperty()
+ show_hosts = ndb.StringProperty(repeated=True)
+ aliases = ndb.PickleProperty()
+
+
class _BaseHandler(webapp2.RequestHandler):
def render(self, template, **kwargs):
d = dict()
@@ -25,9 +53,13 @@
class MainHandler(_BaseHandler):
+ @GoogleLoginRequired
def get(self):
upload_url = blobstore.create_upload_url('/upload')
- self.render('index.html', upload_url=upload_url)
+ q = PcapData.query().order(-PcapData.create_time).fetch(10)
+ self.render('index.html',
+ upload_url=upload_url,
+ recents=q)
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
@@ -38,13 +70,81 @@
self.redirect('/view/%s' % blob_info.key())
-class ViewHandler(_BaseHandler):
- def get(self, blobres):
- blobres = str(urllib.unquote(blobres))
- blob_info = blobstore.BlobInfo.get(blobres)
+def _Boxes(blob_info):
+ boxes = memcache.get(str(blob_info.key()), namespace='boxes')
+ if not boxes:
reader = blob_info.open()
- packets = wifipacket.Packetize(reader)
- self.render('view.html', blob=blob_info, packets=packets)
+ boxes = collections.defaultdict(lambda: 0)
+ for p, frame in wifipacket.Packetize(reader):
+ if 'flags' in p and p.flags & wifipacket.Flags.BAD_FCS: continue
+ if 'ta' in p and 'ra' in p:
+ boxes[p.ta] += 1
+ boxes[p.ra] += 1
+ memcache.add(key=str(blob_info.key()), value=dict(boxes),
+ namespace='boxes')
+ return boxes
+
+
+class ViewHandler(_BaseHandler):
+ @GoogleLoginRequired
+ def get(self, blobres):
+ blob_info = blobstore.BlobInfo.get(str(urllib.unquote(blobres)))
+ capdefault = PcapData.get_or_insert(str('*'), show_hosts=[], aliases={})
+ pcapdata = PcapData.get_or_insert(str(blob_info.key()),
+ filename=blob_info.filename,
+ show_hosts=[], aliases={})
+ try:
+ boxes = _Boxes(blob_info)
+ except ValueError as e:
+ self.response.set_status(500, 'Server error')
+ self.response.write('<pre>%s</pre>' % traceback.format_exc())
+ return
+
+ cutoff = max(boxes.itervalues()) * 0.01
+ cutboxes = [(b, n)
+ for b, n
+ in sorted(boxes.iteritems(), key=lambda x: -x[1])
+ if n >= cutoff and b != BROADCAST]
+ other = sum((n for n in boxes.itervalues() if n < cutoff))
+ aliases = pcapdata.aliases
+ if pcapdata.show_hosts:
+ checked = dict((h, 1) for h in pcapdata.show_hosts)
+ else:
+ checked = {}
+ for b, n in cutboxes:
+ checked[b] = (n > cutoff * 10)
+ for b in boxes.keys():
+ if b not in aliases:
+ aliases[b] = capdefault.aliases.get(b, b)
+ self.render('view.html',
+ blob=blob_info,
+ boxes=cutboxes,
+ other=other,
+ aliases=aliases,
+ checked=checked)
+
+
+class SaveHandler(_BaseHandler):
+ @GoogleLoginRequired
+ def post(self, blobres):
+ blob_info = blobstore.BlobInfo.get(str(urllib.unquote(blobres)))
+ capdefault = PcapData.get_or_insert(str('*'), show_hosts=[], aliases={})
+ pcapdata = PcapData.get_or_insert(str(blob_info.key()),
+ show_hosts=[], aliases={})
+ boxes = _Boxes(blob_info)
+ pcapdata.show_hosts = []
+ for b in boxes.keys():
+ alias = self.request.get('name-%s' % b)
+ if alias:
+ pcapdata.aliases[b] = alias
+ capdefault.aliases[b] = alias
+ else:
+ pcapdata.aliases[b] = b
+ if self.request.get('show-%s' % b):
+ pcapdata.show_hosts.append(b)
+ capdefault.put()
+ pcapdata.put()
+ self.response.write('done')
settings = dict(
@@ -55,4 +155,5 @@
(r'/', MainHandler),
(r'/upload', UploadHandler),
(r'/view/([^/]+)$', ViewHandler),
+ (r'/save/([^/]+)$', SaveHandler),
], **settings)
diff --git a/index.html b/index.html
index ee5f0e4..f7f72a5 100644
--- a/index.html
+++ b/index.html
@@ -1,12 +1,33 @@
<html>
+<head>
+ <style>
+ * {
+ font-family: monospace;
+ }
+ td {
+ padding-right: 1em;
+ padding-left: 1em;
+ }
+ </style>
+</head>
+
<body>
{% block body %}
- This is text.
+ <div style='float: right; border: 1px solid black; padding: 1em;'>
+ Upload a wifi packet capture!<p>
<form action="{{upload_url}}" method="POST" enctype="multipart/form-data">
<input name="capfile" type="file" />
- <input type="submit" />
+ <input type="submit" value="Upload" />
</form>
+ </div>
+
+ Recent uploads:<ul>
+ {% for r in recents %}
+ <li>{{r.create_time}}
+ <a href="/view/{{r.key.id()}}">{{r.filename}}</a></li>
+ {% end %}
+ </ul>
{% end %}
</body>
</html>
diff --git a/view.html b/view.html
index 068275c..ec8003b 100644
--- a/view.html
+++ b/view.html
@@ -3,12 +3,33 @@
{% block body %}
<a href='/'>← Index</a><p>
- This is viewer.<p>
+ Filename: {{blob.filename}}<p>
+ Size: {{'%.2f' % (blob.size/1e6)}} Mbytes<p>
- filename: {{blob.filename}}<p>
- size: {{blob.size}}<p>
-
- {% for p, frame in packets %}
- {{p.get('ta')}}<br>
- {% end %}
+ <form action="/save/{{blob.key()}}" method="POST">
+ <table>
+ <tr>
+ <th>Show?</th>
+ <th>Pkts</th>
+ <th>MAC Addr</th>
+ <th>Alias</th>
+ </tr>
+ {% for b, n in boxes %}
+ <tr>
+ <td><input type='checkbox' name='show-{{b}}'
+ {{'checked' if checked.get(b) else ''}} /></td>
+ <td>{{n}}</td>
+ <td>{{b}}</td>
+ <td><input name='name-{{b}}' value='{{aliases.get(b, "")}}' /></td>
+ </tr>
+ {% end %}
+ <tr>
+ <td><input type='checkbox' name='show-other' /></td>
+ <td>{{other}}</td>
+ <td>(other)</td>
+ <td></td>
+ </tr>
+ </table>
+ <input type='submit' value='Save and View' style='margin: 1em' />
+ </form>
{% end %}