source: etherws/trunk/etherws.py @ 192

Revision 192, 25.5 KB checked in by atzm, 12 years ago (diff)
  • adjust ctl outputs
  • Property svn:keywords set to Id
RevLine 
[133]1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
[186]4#                          Ethernet over WebSocket
[133]5#
6# depends on:
7#   - python-2.7.2
8#   - python-pytun-0.2
[136]9#   - websocket-client-0.7.0
[183]10#   - tornado-2.3
[133]11#
12# ===========================================================================
13# Copyright (c) 2012, Atzm WATANABE <atzm@atzm.org>
14# All rights reserved.
15#
16# Redistribution and use in source and binary forms, with or without
17# modification, are permitted provided that the following conditions are met:
18#
19# 1. Redistributions of source code must retain the above copyright notice,
20#    this list of conditions and the following disclaimer.
21# 2. Redistributions in binary form must reproduce the above copyright
22#    notice, this list of conditions and the following disclaimer in the
23#    documentation and/or other materials provided with the distribution.
24#
25# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
29# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35# POSSIBILITY OF SUCH DAMAGE.
36# ===========================================================================
37#
38# $Id$
39
40import os
41import sys
[156]42import ssl
[160]43import time
[183]44import json
[175]45import fcntl
[150]46import base64
[190]47import urllib2
[150]48import hashlib
[151]49import getpass
[133]50import argparse
[165]51import traceback
[133]52
[185]53import tornado
[133]54import websocket
55
[183]56from tornado.web import Application, RequestHandler
[182]57from tornado.websocket import WebSocketHandler
[183]58from tornado.httpserver import HTTPServer
59from tornado.ioloop import IOLoop
60
[166]61from pytun import TunTapDevice, IFF_TAP, IFF_NO_PI
[133]62
[166]63
[160]64class DebugMixIn(object):
[166]65    def dprintf(self, msg, func=lambda: ()):
[160]66        if self._debug:
67            prefix = '[%s] %s - ' % (time.asctime(), self.__class__.__name__)
[164]68            sys.stderr.write(prefix + (msg % func()))
[160]69
70
[164]71class EthernetFrame(object):
72    def __init__(self, data):
73        self.data = data
74
[176]75    @property
76    def dst_multicast(self):
77        return ord(self.data[0]) & 1
[164]78
79    @property
[176]80    def src_multicast(self):
81        return ord(self.data[6]) & 1
82
83    @property
[164]84    def dst_mac(self):
85        return self.data[:6]
86
87    @property
88    def src_mac(self):
89        return self.data[6:12]
90
91    @property
92    def tagged(self):
93        return ord(self.data[12]) == 0x81 and ord(self.data[13]) == 0
94
95    @property
96    def vid(self):
97        if self.tagged:
98            return ((ord(self.data[14]) << 8) | ord(self.data[15])) & 0x0fff
[183]99        return 0
[164]100
101
[166]102class FDB(DebugMixIn):
[167]103    def __init__(self, ageout, debug=False):
[164]104        self._ageout = ageout
105        self._debug = debug
[166]106        self._dict = {}
[164]107
108    def lookup(self, frame):
109        mac = frame.dst_mac
110        vid = frame.vid
111
[177]112        group = self._dict.get(vid)
[164]113        if not group:
114            return None
115
[177]116        entry = group.get(mac)
[164]117        if not entry:
118            return None
119
120        if time.time() - entry['time'] > self._ageout:
[183]121            port = self._dict[vid][mac]['port']
[166]122            del self._dict[vid][mac]
123            if not self._dict[vid]:
124                del self._dict[vid]
[183]125            self.dprintf('aged out: port:%d; vid:%d; mac:%s\n',
126                         lambda: (port.number, vid, mac.encode('hex')))
[164]127            return None
128
129        return entry['port']
130
[166]131    def learn(self, port, frame):
132        mac = frame.src_mac
133        vid = frame.vid
134
135        if vid not in self._dict:
136            self._dict[vid] = {}
137
138        self._dict[vid][mac] = {'time': time.time(), 'port': port}
[183]139        self.dprintf('learned: port:%d; vid:%d; mac:%s\n',
140                     lambda: (port.number, vid, mac.encode('hex')))
[166]141
[164]142    def delete(self, port):
[166]143        for vid in self._dict.keys():
144            for mac in self._dict[vid].keys():
[183]145                if self._dict[vid][mac]['port'].number == port.number:
[166]146                    del self._dict[vid][mac]
[183]147                    self.dprintf('deleted: port:%d; vid:%d; mac:%s\n',
148                                 lambda: (port.number, vid, mac.encode('hex')))
[166]149            if not self._dict[vid]:
150                del self._dict[vid]
[164]151
152
[183]153class SwitchPort(object):
154    def __init__(self, number, interface):
155        self.number = number
156        self.interface = interface
157        self.tx = 0
158        self.rx = 0
159        self.shut = False
160
161    @staticmethod
162    def cmp_by_number(x, y):
163        return cmp(x.number, y.number)
164
165
[166]166class SwitchingHub(DebugMixIn):
167    def __init__(self, fdb, debug=False):
168        self._fdb = fdb
[133]169        self._debug = debug
[183]170        self._table = {}
171        self._next = 1
[133]172
[183]173    @property
174    def portlist(self):
175        return sorted(self._table.itervalues(), cmp=SwitchPort.cmp_by_number)
[133]176
[183]177    def get_port(self, portnum):
178        return self._table[portnum]
179
180    def register_port(self, interface):
[186]181        try:
[187]182            self._set_privattr('portnum', interface, self._next)  # XXX
[186]183            self._table[self._next] = SwitchPort(self._next, interface)
184            return self._next
185        finally:
186            self._next += 1
[183]187
188    def unregister_port(self, interface):
[187]189        portnum = self._get_privattr('portnum', interface)
190        self._del_privattr('portnum', interface)
191        self._fdb.delete(self._table[portnum])
192        del self._table[portnum]
[183]193
194    def send(self, dst_interfaces, frame):
[187]195        portnums = (self._get_privattr('portnum', i) for i in dst_interfaces)
196        ports = (self._table[n] for n in portnums)
197        ports = (p for p in ports if not p.shut)
198        ports = sorted(ports, cmp=SwitchPort.cmp_by_number)
[183]199
200        for p in ports:
201            p.interface.write_message(frame.data, True)
202            p.tx += 1
203
204        if ports:
205            self.dprintf('sent: port:%s; vid:%d; %s -> %s\n',
206                         lambda: (','.join(str(p.number) for p in ports),
207                                  frame.vid,
208                                  frame.src_mac.encode('hex'),
209                                  frame.dst_mac.encode('hex')))
210
211    def receive(self, src_interface, frame):
[187]212        port = self._table[self._get_privattr('portnum', src_interface)]
[183]213
214        if not port.shut:
215            port.rx += 1
216            self._forward(port, frame)
217
218    def _forward(self, src_port, frame):
[166]219        try:
[176]220            if not frame.src_multicast:
[172]221                self._fdb.learn(src_port, frame)
[133]222
[176]223            if not frame.dst_multicast:
[166]224                dst_port = self._fdb.lookup(frame)
[164]225
[166]226                if dst_port:
[183]227                    self.send([dst_port.interface], frame)
[166]228                    return
[133]229
[187]230            ports = set(self.portlist) - set([src_port])
[183]231            self.send((p.interface for p in ports), frame)
[162]232
[166]233        except:  # ex. received invalid frame
234            traceback.print_exc()
[133]235
[187]236    def _privattr(self, name):
237        return '_%s_%s_%s' % (self.__class__.__name__, id(self), name)
[164]238
[187]239    def _set_privattr(self, name, obj, value):
240        return setattr(obj, self._privattr(name), value)
241
242    def _get_privattr(self, name, obj, defaults=None):
243        return getattr(obj, self._privattr(name), defaults)
244
245    def _del_privattr(self, name, obj):
246        return delattr(obj, self._privattr(name))
247
248
[179]249class Htpasswd(object):
250    def __init__(self, path):
251        self._path = path
252        self._stat = None
253        self._data = {}
254
255    def auth(self, name, passwd):
256        passwd = base64.b64encode(hashlib.sha1(passwd).digest())
257        return self._data.get(name) == passwd
258
259    def load(self):
260        old_stat = self._stat
261
262        with open(self._path) as fp:
263            fileno = fp.fileno()
264            fcntl.flock(fileno, fcntl.LOCK_SH | fcntl.LOCK_NB)
265            self._stat = os.fstat(fileno)
266
267            unchanged = old_stat and \
268                        old_stat.st_ino == self._stat.st_ino and \
269                        old_stat.st_dev == self._stat.st_dev and \
270                        old_stat.st_mtime == self._stat.st_mtime
271
272            if not unchanged:
273                self._data = self._parse(fp)
274
275        return self
276
277    def _parse(self, fp):
278        data = {}
279        for line in fp:
280            line = line.strip()
281            if 0 <= line.find(':'):
282                name, passwd = line.split(':', 1)
283                if passwd.startswith('{SHA}'):
284                    data[name] = passwd[5:]
285        return data
286
287
[182]288class BasicAuthMixIn(object):
289    def _execute(self, transforms, *args, **kwargs):
290        def do_execute():
291            sp = super(BasicAuthMixIn, self)
292            return sp._execute(transforms, *args, **kwargs)
293
294        def auth_required():
[185]295            stream = getattr(self, 'stream', self.request.connection.stream)
296            stream.write(tornado.escape.utf8(
[182]297                'HTTP/1.1 401 Authorization Required\r\n'
298                'WWW-Authenticate: Basic realm=etherws\r\n\r\n'
299            ))
[185]300            stream.close()
[182]301
302        try:
303            if not self._htpasswd:
304                return do_execute()
305
306            creds = self.request.headers.get('Authorization')
307
308            if not creds or not creds.startswith('Basic '):
309                return auth_required()
310
311            name, passwd = base64.b64decode(creds[6:]).split(':', 1)
312
313            if self._htpasswd.load().auth(name, passwd):
314                return do_execute()
315        except:
316            traceback.print_exc()
317
318        return auth_required()
319
320
[186]321class EtherWebSocketHandler(DebugMixIn, BasicAuthMixIn, WebSocketHandler):
[191]322    IFTYPE = 'server'
323
[186]324    def __init__(self, app, req, switch, htpasswd=None, debug=False):
325        super(EtherWebSocketHandler, self).__init__(app, req)
326        self._switch = switch
327        self._htpasswd = htpasswd
328        self._debug = debug
329
330    def get_target(self):
331        return self.request.remote_ip
332
333    def open(self):
334        try:
335            return self._switch.register_port(self)
336        finally:
337            self.dprintf('connected: %s\n', lambda: self.request.remote_ip)
338
339    def on_message(self, message):
340        self._switch.receive(self, EthernetFrame(message))
341
342    def on_close(self):
343        self._switch.unregister_port(self)
344        self.dprintf('disconnected: %s\n', lambda: self.request.remote_ip)
345
346
[166]347class TapHandler(DebugMixIn):
[191]348    IFTYPE = 'tap'
[166]349    READ_SIZE = 65535
350
[178]351    def __init__(self, ioloop, switch, dev, debug=False):
352        self._ioloop = ioloop
[166]353        self._switch = switch
354        self._dev = dev
355        self._debug = debug
356        self._tap = None
357
[186]358    def get_target(self):
[183]359        if self.closed:
360            return self._dev
361        return self._tap.name
362
[186]363    @property
364    def closed(self):
365        return not self._tap
366
[166]367    def open(self):
368        if not self.closed:
369            raise ValueError('already opened')
370        self._tap = TunTapDevice(self._dev, IFF_TAP | IFF_NO_PI)
371        self._tap.up()
[178]372        self._ioloop.add_handler(self.fileno(), self, self._ioloop.READ)
[186]373        return self._switch.register_port(self)
[166]374
375    def close(self):
376        if self.closed:
377            raise ValueError('I/O operation on closed tap')
[186]378        self._switch.unregister_port(self)
[178]379        self._ioloop.remove_handler(self.fileno())
[166]380        self._tap.close()
381        self._tap = None
382
383    def fileno(self):
384        if self.closed:
385            raise ValueError('I/O operation on closed tap')
386        return self._tap.fileno()
387
388    def write_message(self, message, binary=False):
389        if self.closed:
390            raise ValueError('I/O operation on closed tap')
391        self._tap.write(message)
392
[138]393    def __call__(self, fd, events):
[166]394        try:
[183]395            self._switch.receive(self, EthernetFrame(self._read()))
[166]396            return
397        except:
398            traceback.print_exc()
[178]399        self.close()
[166]400
401    def _read(self):
402        if self.closed:
403            raise ValueError('I/O operation on closed tap')
[162]404        buf = []
405        while True:
[166]406            buf.append(self._tap.read(self.READ_SIZE))
407            if len(buf[-1]) < self.READ_SIZE:
[162]408                break
[166]409        return ''.join(buf)
[162]410
411
[160]412class EtherWebSocketClient(DebugMixIn):
[191]413    IFTYPE = 'client'
414
[181]415    def __init__(self, ioloop, switch, url, ssl_=None, cred=None, debug=False):
[178]416        self._ioloop = ioloop
[166]417        self._switch = switch
[151]418        self._url = url
[181]419        self._ssl = ssl_
[160]420        self._debug = debug
[166]421        self._sock = None
[151]422        self._options = {}
423
[174]424        if isinstance(cred, dict) and cred['user'] and cred['passwd']:
425            token = base64.b64encode('%s:%s' % (cred['user'], cred['passwd']))
[151]426            auth = ['Authorization: Basic %s' % token]
427            self._options['header'] = auth
428
[186]429    def get_target(self):
430        return self._url
431
[160]432    @property
433    def closed(self):
434        return not self._sock
435
[151]436    def open(self):
[181]437        sslwrap = websocket._SSLSocketWrapper
438
[160]439        if not self.closed:
440            raise websocket.WebSocketException('already opened')
[151]441
[181]442        if self._ssl:
443            websocket._SSLSocketWrapper = self._ssl
444
445        try:
446            self._sock = websocket.WebSocket()
447            self._sock.connect(self._url, **self._options)
448            self._ioloop.add_handler(self.fileno(), self, self._ioloop.READ)
[186]449            return self._switch.register_port(self)
[181]450        finally:
451            websocket._SSLSocketWrapper = sslwrap
[186]452            self.dprintf('connected: %s\n', lambda: self._url)
[181]453
[151]454    def close(self):
[160]455        if self.closed:
456            raise websocket.WebSocketException('already closed')
[186]457        self._switch.unregister_port(self)
[178]458        self._ioloop.remove_handler(self.fileno())
[151]459        self._sock.close()
460        self._sock = None
[164]461        self.dprintf('disconnected: %s\n', lambda: self._url)
[151]462
[165]463    def fileno(self):
464        if self.closed:
465            raise websocket.WebSocketException('closed socket')
466        return self._sock.io_sock.fileno()
467
[151]468    def write_message(self, message, binary=False):
[160]469        if self.closed:
470            raise websocket.WebSocketException('closed socket')
[151]471        if binary:
472            flag = websocket.ABNF.OPCODE_BINARY
[160]473        else:
474            flag = websocket.ABNF.OPCODE_TEXT
[151]475        self._sock.send(message, flag)
476
[165]477    def __call__(self, fd, events):
[151]478        try:
[165]479            data = self._sock.recv()
480            if data is not None:
[183]481                self._switch.receive(self, EthernetFrame(data))
[165]482                return
483        except:
484            traceback.print_exc()
[178]485        self.close()
[151]486
487
[183]488class EtherWebSocketControlHandler(DebugMixIn, BasicAuthMixIn, RequestHandler):
489    NAMESPACE = 'etherws.control'
[191]490    IFTYPES = {
491        TapHandler.IFTYPE:           TapHandler,
492        EtherWebSocketClient.IFTYPE: EtherWebSocketClient,
[186]493    }
[183]494
495    def __init__(self, app, req, ioloop, switch, htpasswd=None, debug=False):
496        super(EtherWebSocketControlHandler, self).__init__(app, req)
497        self._ioloop = ioloop
498        self._switch = switch
499        self._htpasswd = htpasswd
500        self._debug = debug
501
502    def post(self):
503        id_ = None
504
505        try:
506            req = json.loads(self.request.body)
507            method = req['method']
508            params = req['params']
509            id_ = req.get('id')
510
511            if not method.startswith(self.NAMESPACE + '.'):
512                raise ValueError('invalid method: %s' % method)
513
514            if not isinstance(params, list):
515                raise ValueError('invalid params: %s' % params)
516
517            handler = 'handle_' + method[len(self.NAMESPACE) + 1:]
518            result = getattr(self, handler)(params)
519            self.finish({'result': result, 'error': None, 'id': id_})
520
521        except Exception as e:
522            traceback.print_exc()
[192]523            msg = '%s: %s' % (e.__class__.__name__, str(e))
524            self.finish({'result': None, 'error': {'message': msg}, 'id': id_})
[183]525
526    def handle_listPort(self, params):
[188]527        list_ = [self._portstat(p) for p in self._switch.portlist]
[183]528        return {'portlist': list_}
529
530    def handle_addPort(self, params):
[186]531        list_ = []
[183]532        for p in params:
[186]533            type_ = p['type']
534            target = p['target']
535            options = getattr(self, '_optparse_' + type_)(p.get('options', {}))
[191]536            klass = self.IFTYPES[type_]
[186]537            interface = klass(self._ioloop, self._switch, target, **options)
538            portnum = interface.open()
539            list_.append(self._portstat(self._switch.get_port(portnum)))
540        return {'portlist': list_}
[183]541
542    def handle_delPort(self, params):
[186]543        list_ = []
[183]544        for p in params:
[186]545            port = self._switch.get_port(int(p['port']))
546            list_.append(self._portstat(port))
547            port.interface.close()
548        return {'portlist': list_}
[183]549
550    def handle_shutPort(self, params):
[186]551        list_ = []
[183]552        for p in params:
[186]553            port = self._switch.get_port(int(p['port']))
[190]554            port.shut = bool(p['shut'])
[186]555            list_.append(self._portstat(port))
556        return {'portlist': list_}
[183]557
[186]558    def _optparse_tap(self, opt):
559        return {'debug': self._debug}
[183]560
[186]561    def _optparse_client(self, opt):
562        args = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': opt.get('cacerts')}
563        if opt.get('insecure'):
564            args = {}
565        ssl_ = lambda sock: ssl.wrap_socket(sock, **args)
566        cred = {'user': opt.get('user'), 'passwd': opt.get('passwd')}
567        return {'ssl_': ssl_, 'cred': cred, 'debug': self._debug}
[183]568
569    @staticmethod
[186]570    def _portstat(port):
571        return {
572            'port':   port.number,
[191]573            'type':   port.interface.IFTYPE,
[186]574            'target': port.interface.get_target(),
575            'tx':     port.tx,
576            'rx':     port.rx,
577            'shut':   port.shut,
578        }
[183]579
580
[190]581def start_sw(args):
[186]582    def daemonize(nochdir=False, noclose=False):
583        if os.fork() > 0:
584            sys.exit(0)
[134]585
[186]586        os.setsid()
[134]587
[186]588        if os.fork() > 0:
589            sys.exit(0)
[134]590
[186]591        if not nochdir:
592            os.chdir('/')
[134]593
[186]594        if not noclose:
595            os.umask(0)
596            sys.stdin.close()
597            sys.stdout.close()
598            sys.stderr.close()
599            os.close(0)
600            os.close(1)
601            os.close(2)
602            sys.stdin = open(os.devnull)
603            sys.stdout = open(os.devnull, 'a')
604            sys.stderr = open(os.devnull, 'a')
[134]605
[186]606    def checkabspath(ns, path):
[184]607        val = getattr(ns, path, '')
608        if not val.startswith('/'):
609            raise ValueError('invalid %: %s' % (path, val))
610
611    def getsslopt(ns, key, cert):
612        kval = getattr(ns, key, None)
613        cval = getattr(ns, cert, None)
614        if kval and cval:
615            return {'keyfile': kval, 'certfile': cval}
616        elif kval or cval:
617            raise ValueError('both %s and %s are required' % (key, cert))
618        return None
619
[186]620    def setrealpath(ns, *keys):
621        for k in keys:
622            v = getattr(ns, k, None)
623            if v is not None:
624                v = os.path.realpath(v)
625                open(v).close()  # check readable
626                setattr(ns, k, v)
627
[184]628    def setport(ns, port, isssl):
629        val = getattr(ns, port, None)
630        if val is None:
631            if isssl:
632                return setattr(ns, port, 443)
633            return setattr(ns, port, 80)
634        if not (0 <= val <= 65535):
635            raise ValueError('invalid %s: %s' % (port, val))
636
637    def sethtpasswd(ns, htpasswd):
638        val = getattr(ns, htpasswd, None)
639        if val:
640            return setattr(ns, htpasswd, Htpasswd(val))
641
[183]642    #if args.debug:
643    #    websocket.enableTrace(True)
644
645    if args.ageout <= 0:
646        raise ValueError('invalid ageout: %s' % args.ageout)
647
[186]648    setrealpath(args, 'htpasswd', 'sslkey', 'sslcert')
649    setrealpath(args, 'ctlhtpasswd', 'ctlsslkey', 'ctlsslcert')
[183]650
[186]651    checkabspath(args, 'path')
652    checkabspath(args, 'ctlpath')
[183]653
[184]654    sslopt = getsslopt(args, 'sslkey', 'sslcert')
655    ctlsslopt = getsslopt(args, 'ctlsslkey', 'ctlsslcert')
[143]656
[184]657    setport(args, 'port', sslopt)
658    setport(args, 'ctlport', ctlsslopt)
[143]659
[184]660    sethtpasswd(args, 'htpasswd')
661    sethtpasswd(args, 'ctlhtpasswd')
[167]662
[183]663    ioloop = IOLoop.instance()
[167]664    fdb = FDB(ageout=args.ageout, debug=args.debug)
[183]665    switch = SwitchingHub(fdb, debug=args.debug)
[167]666
[184]667    if args.port == args.ctlport and args.host == args.ctlhost:
668        if args.path == args.ctlpath:
669            raise ValueError('same path/ctlpath on same host')
670        if args.sslkey != args.ctlsslkey:
[189]671            raise ValueError('different sslkey/ctlsslkey on same host')
[184]672        if args.sslcert != args.ctlsslcert:
[189]673            raise ValueError('different sslcert/ctlsslcert on same host')
[133]674
[184]675        app = Application([
676            (args.path, EtherWebSocketHandler, {
677                'switch':   switch,
678                'htpasswd': args.htpasswd,
679                'debug':    args.debug,
680            }),
681            (args.ctlpath, EtherWebSocketControlHandler, {
682                'ioloop':   ioloop,
683                'switch':   switch,
684                'htpasswd': args.ctlhtpasswd,
685                'debug':    args.debug,
686            }),
687        ])
688        server = HTTPServer(app, ssl_options=sslopt)
689        server.listen(args.port, address=args.host)
[151]690
[184]691    else:
692        app = Application([(args.path, EtherWebSocketHandler, {
693            'switch':   switch,
694            'htpasswd': args.htpasswd,
695            'debug':    args.debug,
696        })])
697        server = HTTPServer(app, ssl_options=sslopt)
698        server.listen(args.port, address=args.host)
699
700        ctl = Application([(args.ctlpath, EtherWebSocketControlHandler, {
701            'ioloop':   ioloop,
702            'switch':   switch,
703            'htpasswd': args.ctlhtpasswd,
704            'debug':    args.debug,
705        })])
706        ctlserver = HTTPServer(ctl, ssl_options=ctlsslopt)
707        ctlserver.listen(args.ctlport, address=args.ctlhost)
708
[151]709    if not args.foreground:
710        daemonize()
711
[138]712    ioloop.start()
[133]713
714
[190]715def start_ctl(args):
716    import yaml
717
718    def request(args, method, params):
719        method = '.'.join([EtherWebSocketControlHandler.NAMESPACE, method])
720        data = json.dumps({'method': method, 'params': params})
721        req = urllib2.Request(args.ctlurl)
722        req.add_header('Content-type', 'application/json')
723        if args.ctluser:
724            if not args.ctlpasswd:
725                args.ctlpasswd = getpass.getpass()
726            token = base64.b64encode('%s:%s' % (args.ctluser, args.ctlpasswd))
727            req.add_header('Authorization', 'Basic %s' % token)
728        return json.loads(urllib2.urlopen(req, data).read())
729
730    def handle_ctl_addport(args):
[191]731        params = [{
732            'type':    args.type,
733            'target':  args.target,
734            'options': {
735                'instance': args.insecure,
736                'cacerts':  args.cacerts,
737                'user':     args.user,
738                'passwd':   args.passwd,
739            }
740        }]
[192]741        return request(args, 'addPort', params)
[190]742
743    def handle_ctl_shutport(args):
744        if args.port <= 0:
745            raise ValueError('invalid port: %d' % args.port)
[192]746        params = [{'port': args.port, 'shut': args.no}]
747        return request(args, 'shutPort', params)
[190]748
749    def handle_ctl_delport(args):
750        if args.port <= 0:
751            raise ValueError('invalid port: %d' % args.port)
[192]752        params = [{'port': args.port}]
753        return request(args, 'delPort', params)
[190]754
755    def handle_ctl_listport(args):
[192]756        return request(args, 'listPort', [])
[190]757
[192]758    res = locals()['handle_ctl_' + args.control_method](args)
[190]759
[192]760    if res['error']:
761        print(res['error']['message'])
762    else:
763        print(yaml.safe_dump(res['result']['portlist']).strip())
[190]764
[192]765
[186]766def main():
767    parser = argparse.ArgumentParser()
[190]768    subcommand = parser.add_subparsers(dest='subcommand')
[186]769
[190]770    # -- sw command parser
771    parser_s = subcommand.add_parser('sw')
772
[186]773    parser_s.add_argument('--debug', action='store_true', default=False)
774    parser_s.add_argument('--foreground', action='store_true', default=False)
[190]775    parser_s.add_argument('--ageout', type=int, default=300)
[186]776
[190]777    parser_s.add_argument('--path', default='/')
778    parser_s.add_argument('--host', default='')
779    parser_s.add_argument('--port', type=int)
780    parser_s.add_argument('--htpasswd')
781    parser_s.add_argument('--sslkey')
782    parser_s.add_argument('--sslcert')
[186]783
[190]784    parser_s.add_argument('--ctlpath', default='/ctl')
785    parser_s.add_argument('--ctlhost', default='')
786    parser_s.add_argument('--ctlport', type=int)
787    parser_s.add_argument('--ctlhtpasswd')
788    parser_s.add_argument('--ctlsslkey')
789    parser_s.add_argument('--ctlsslcert')
[186]790
[190]791    # -- ctl command parser
792    parser_c = subcommand.add_parser('ctl')
793    parser_c.add_argument('--ctlurl', default='http://localhost/ctl')
794    parser_c.add_argument('--ctluser')
795    parser_c.add_argument('--ctlpasswd')
796
797    control_method = parser_c.add_subparsers(dest='control_method')
798
799    parser_c_ap = control_method.add_parser('addport')
[191]800    parser_c_ap.add_argument(
801        'type', choices=EtherWebSocketControlHandler.IFTYPES.keys())
[190]802    parser_c_ap.add_argument('target')
803    parser_c_ap.add_argument('--insecure', action='store_true', default=False)
804    parser_c_ap.add_argument('--cacerts')
805    parser_c_ap.add_argument('--user')
806    parser_c_ap.add_argument('--passwd')
807
808    parser_c_sp = control_method.add_parser('shutport')
809    parser_c_sp.add_argument('port', type=int)
810    parser_c_sp.add_argument('--no', action='store_false', default=True)
811
812    parser_c_dp = control_method.add_parser('delport')
813    parser_c_dp.add_argument('port', type=int)
814
815    parser_c_lp = control_method.add_parser('listport')
816
817    # -- go
[186]818    args = parser.parse_args()
819    globals()['start_' + args.subcommand](args)
820
821
[133]822if __name__ == '__main__':
823    main()
Note: See TracBrowser for help on using the repository browser.