aboutsummaryrefslogtreecommitdiffstats
path: root/docs/dev/addingviews.html
blob: f6ba645c60099fffe971db0e1396f04ea7384490 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
As discussed in [the Flow View section of the mitmproxy
overview](@!urlTo("mitmproxy.html")!@), mitmproxy allows you to inspect and
manipulate flows.  When inspecting a single flow, mitmproxy uses a number of
heuristics to show a friendly view of various content types; if mitmproxy
cannot show a friendly view, mitmproxy defaults to a __raw__ view.

Each content type invokes a different flow viewer to parse the data and display
the friendly view. Users can add custom content viewers by adding a view class
to contentview.py, discussed below.

## Adding a new View class to contentview.py

The content viewers used by mitmproxy to present a friendly view of various
content types are stored in contentview.py.  Reviewing this file shows a number
of classes named ViewSomeDataType, each with the properties: __name__,
__prompt__, and __content\_types__ and a function named __\_\_call\_\___.

Adding a new content viewer to parse a data type is as simple as writing a new
View class.  Your new content viewer View class should have the same properties
as the other View classes: __name__, __prompt__, and __content\_types__ and a
__\_\_call\_\___ function to parse the content of the request/response.

* The __name__ property should be a string describing the contents and new content viewer;
* The __prompt__ property should be a two item tuple:

  - __1__: A string that will be used to display the new content viewer's type; and
  - __2__: A one character string that will be the hotkey used to select the new content viewer from the Flow View screen;

* The __content\_types__ property should be a list of strings of HTTP Content\-Types that the new content viewer can parse.
  * Note that mitmproxy will use the content\_types to try and heuristically show a friendly view of content and that you can override the built-in views by populating content\_types with values for content\_types that are already parsed -- e.g. "image/png".

After defining the __name__, __prompt__, and __content\_types__ properties of
the class, you should write the __\_\_call\_\___ function, which will parse the
request/response data and provide a friendly view of the data.  The
__\_\_call\_\___ function should take the following arguments: __self__,
__hdrs__, __content__, __limit__; __hdrs__ is a MultiDict object containing
the headers of the request/response; __content__ is the content of the
request/response, and __limit__ is an integer representing the amount of data
to display in the view window.

The __\_\_call\_\___ function returns two values: (1) a string describing the
parsed data; and (2) the parsed data for friendly display.  The parsed data to
be displayed should be a list of strings formatted for display.  You can use
the __\_view\_text__ function in contentview.py to format text for display.
Alternatively, you can display content as a series of key-value pairs; to do
so, prepare a list of lists, where each list item is a two item list -- a key
that describes the data, and then the data itself; after preparing the list of
lists, use the __common.format\_keyvals__ function on it to prepare it as text
for display.

If the new content viewer fails or throws an exception, mitmproxy will default
to a __raw__ view.
und-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
"""Bridge control utilities.
"""
import os
import os.path
import re
import sys

CMD_IFCONFIG = 'ifconfig'
CMD_ROUTE    = 'route'
CMD_BRCTL    = 'brctl'
CMD_IPTABLES = "iptables"

opts = None

class Opts:

    def __init__(self, defaults):
        for (k, v) in defaults.items():
            setattr(self, k, v)
        pass

def cmd(p, s):
    """Print and execute command 'p' with args 's'.
    """
    global opts
    c = p + ' ' + s
    if opts.verbose: print c
    if not opts.dryrun:
        os.system(c)

bridgeRE = re.compile(r'([^\t]*)\t*[^\t]*\t*[^\t]*\t*([^\t]*)')
def get_state():
    fin = os.popen(CMD_BRCTL + ' show', 'r')
    try:
        bridges = {}
        brlist = None
        brname = None
        first = True
        for line in fin:
            if first:
                first = False
            elif line[0] == '\t':
                brlist.append(line.strip())
            else:
                if brname:
                    bridges[brname] = brlist
                m = bridgeRE.match(line)
                brname = m.group(1)
                brlist = [m.group(2).strip()]
        if brname:
            bridges[brname] = brlist
        return bridges
    finally:
        fin.close()

def vif_bridge_add(params):
    """Add the network interface for vif on dom to a bridge.
    """
    cmd(CMD_BRCTL, 'addif %(bridge)s %(vif)s' % params)

def vif_bridge_rem(params):
    """Remove the network interface for vif on dom from a bridge.
    """
    cmd(CMD_BRCTL, 'delif %(bridge)s %(vif)s' % params)

def vif_restrict_addr(vif, addr, delete=0):
    d = { 'vif': vif, 'addr': addr}
    if delete:
        d['flag'] = '-D'
    else:
        d['flag'] = '-A'
    cmd(CMD_IPTABLES, '-P FORWARD DROP')
    cmd(CMD_IPTABLES, '%(flag)s FORWARD -m physdev --physdev-in %(vif)s -s %(addr)s -j ACCEPT' % d)
    cmd(CMD_IPTABLES, '%(flag)s FORWARD -m physdev --physdev-out %(vif)s -d %(addr)s -j ACCEPT' % d)

def bridge_create(bridge, **kwd):
    """Create a bridge.
    Defaults hello time to 0, forward delay to 0 and stp off.
    """
    cmd(CMD_BRCTL, 'addbr %s' % bridge)
    if kwd.get('hello', None) is None:
        kwd['hello'] = 0
    if kwd.get('fd', None) is None:
        kwd['fd'] = 0
    if kwd.get('stp', None) is None:
        kwd['stp'] = 'off'
    bridge_set(bridge, **kwd)
    cmd(CMD_IFCONFIG, "%s up" % bridge)

def bridge_set(bridge, hello=None, fd=None, stp=None):
    """Set bridge parameters.
    """
    if hello is not None:
        cmd(CMD_BRCTL, 'sethello %s %d' % (bridge, hello))
    if fd is not None:
        cmd(CMD_BRCTL, 'setfd %s %d' % (bridge, fd))
    if stp is not None:
        cmd(CMD_BRCTL, 'stp %s %s' % (bridge, stp))

def bridge_del(bridge):
    """Delete a bridge.
    """
    cmd(CMD_IFCONFIG, '%s down' % bridge)
    cmd(CMD_BRCTL, 'delbr %s' % bridge)

def routes():
    """Return a list of the routes.
    """
    fin = os.popen(CMD_ROUTE + ' -n', 'r')
    routes = []
    for x in fin:
        if x.startswith('Kernel'): continue
        if x.startswith('Destination'): continue
        x = x.strip()
        y = x.split()
        z = { 'destination': y[0],
              'gateway'    : y[1],
              'mask'       : y[2],
              'flags'      : y[3],
              'metric'     : y[4],
              'ref'        : y[5],
              'use'        : y[6],
              'interface'  : y[7] }
        routes.append(z)
    return routes

def ifconfig(interface):
    """Return the ip config for an interface,
    """
    fin = os.popen(CMD_IFCONFIG + ' %s' % interface, 'r')
    inetre = re.compile('\s*inet\s*addr:(?P<address>\S*)\s*Bcast:(?P<broadcast>\S*)\s*Mask:(?P<mask>\S*)')
    info = None
    for x in fin:
        m = inetre.match(x)
        if not m: continue
        info = m.groupdict()
        info['interface'] = interface
        break
    return info

def reconfigure(interface, bridge):
    """Reconfigure an interface to be attached to a bridge, and give the bridge
    the IP address etc. from interface. Move the default route to the interface
    to the bridge.

    """
    global opts
    intf_info = ifconfig(interface)
    if not intf_info:
        print >>sys.stderr, 'Interface not found:', interface
        return
    #bridge_info = ifconfig(bridge)
    #if not bridge_info:
    #    print >>sys.stderr, 'Bridge not found:', bridge
    #    return
    route_info = routes()
    intf_info['bridge'] = bridge
    intf_info['gateway'] = None
    for r in route_info:
        if (r['destination'] == '0.0.0.0' and
            'G' in r['flags'] and
            r['interface'] == interface):
            intf_info['gateway'] = r['gateway']
    if not intf_info['gateway']:
        print >>sys.stderr, 'Gateway not found: ', interface
        return
    cmd(CMD_IFCONFIG,
        '%(bridge)s %(address)s netmask %(mask)s broadcast %(broadcast)s up'
        % intf_info)
    cmd(CMD_ROUTE,
        'add default gateway %(gateway)s dev %(bridge)s'
        % intf_info)
    cmd(CMD_BRCTL, 'addif %(bridge)s %(interface)s' % intf_info)
    cmd(CMD_IFCONFIG, '%(interface)s 0.0.0.0' % intf_info)

defaults = {
    'verbose'  : 1,
    'dryrun'   : 0,
    }

opts = Opts(defaults)

def set_opts(val):
    global opts
    opts = val
    return opts