Changeset 186


Ignore:
Timestamp:
07/30/12 01:06:26 (12 years ago)
Author:
atzm
Message:
  • API seiri
File:
1 edited

Legend:

Unmodified
Added
Removed
  • etherws/trunk/etherws.py

    r185 r186  
    22# -*- coding: utf-8 -*- 
    33# 
    4 #              Ethernet over WebSocket tunneling server/client 
     4#                          Ethernet over WebSocket 
    55# 
    66# depends on: 
     
    99#   - websocket-client-0.7.0 
    1010#   - tornado-2.3 
    11 # 
    12 # todo: 
    13 #   - servant mode support (like typical p2p software) 
    1411# 
    1512# =========================================================================== 
     
    177174        return sorted(self._table.itervalues(), cmp=SwitchPort.cmp_by_number) 
    178175 
    179     def shut_port(self, portnum, flag=True): 
    180         self._table[portnum].shut = flag 
    181  
    182176    def get_port(self, portnum): 
    183177        return self._table[portnum] 
    184178 
    185179    def register_port(self, interface): 
    186         interface._switch_portnum = self._next  # XXX 
    187         self._table[self._next] = SwitchPort(self._next, interface) 
    188         self._next += 1 
     180        try: 
     181            interface._switch_portnum = self._next  # XXX 
     182            self._table[self._next] = SwitchPort(self._next, interface) 
     183            return self._next 
     184        finally: 
     185            self._next += 1 
    189186 
    190187    def unregister_port(self, interface): 
     
    307304 
    308305 
     306class EtherWebSocketHandler(DebugMixIn, BasicAuthMixIn, WebSocketHandler): 
     307    def __init__(self, app, req, switch, htpasswd=None, debug=False): 
     308        super(EtherWebSocketHandler, self).__init__(app, req) 
     309        self._switch = switch 
     310        self._htpasswd = htpasswd 
     311        self._debug = debug 
     312 
     313    @classmethod 
     314    def get_type(cls): 
     315        return 'server' 
     316 
     317    def get_target(self): 
     318        return self.request.remote_ip 
     319 
     320    def open(self): 
     321        try: 
     322            return self._switch.register_port(self) 
     323        finally: 
     324            self.dprintf('connected: %s\n', lambda: self.request.remote_ip) 
     325 
     326    def on_message(self, message): 
     327        self._switch.receive(self, EthernetFrame(message)) 
     328 
     329    def on_close(self): 
     330        self._switch.unregister_port(self) 
     331        self.dprintf('disconnected: %s\n', lambda: self.request.remote_ip) 
     332 
     333 
    309334class TapHandler(DebugMixIn): 
    310335    READ_SIZE = 65535 
     
    317342        self._tap = None 
    318343 
    319     @property 
    320     def closed(self): 
    321         return not self._tap 
    322  
    323     def get_type(self): 
     344    @classmethod 
     345    def get_type(cls): 
    324346        return 'tap' 
    325347 
    326     def get_name(self): 
     348    def get_target(self): 
    327349        if self.closed: 
    328350            return self._dev 
    329351        return self._tap.name 
     352 
     353    @property 
     354    def closed(self): 
     355        return not self._tap 
    330356 
    331357    def open(self): 
     
    334360        self._tap = TunTapDevice(self._dev, IFF_TAP | IFF_NO_PI) 
    335361        self._tap.up() 
    336         self._switch.register_port(self) 
    337362        self._ioloop.add_handler(self.fileno(), self, self._ioloop.READ) 
     363        return self._switch.register_port(self) 
    338364 
    339365    def close(self): 
    340366        if self.closed: 
    341367            raise ValueError('I/O operation on closed tap') 
     368        self._switch.unregister_port(self) 
    342369        self._ioloop.remove_handler(self.fileno()) 
    343         self._switch.unregister_port(self) 
    344370        self._tap.close() 
    345371        self._tap = None 
     
    374400 
    375401 
    376 class EtherWebSocketHandler(DebugMixIn, BasicAuthMixIn, WebSocketHandler): 
    377     def __init__(self, app, req, switch, htpasswd=None, debug=False): 
    378         super(EtherWebSocketHandler, self).__init__(app, req) 
    379         self._switch = switch 
    380         self._htpasswd = htpasswd 
    381         self._debug = debug 
    382  
    383     def get_type(self): 
    384         return 'server' 
    385  
    386     def get_name(self): 
    387         return self.request.remote_ip 
    388  
    389     def open(self): 
    390         self._switch.register_port(self) 
    391         self.dprintf('connected: %s\n', lambda: self.request.remote_ip) 
    392  
    393     def on_message(self, message): 
    394         self._switch.receive(self, EthernetFrame(message)) 
    395  
    396     def on_close(self): 
    397         self._switch.unregister_port(self) 
    398         self.dprintf('disconnected: %s\n', lambda: self.request.remote_ip) 
    399  
    400  
    401402class EtherWebSocketClient(DebugMixIn): 
    402403    def __init__(self, ioloop, switch, url, ssl_=None, cred=None, debug=False): 
     
    414415            self._options['header'] = auth 
    415416 
     417    @classmethod 
     418    def get_type(cls): 
     419        return 'client' 
     420 
     421    def get_target(self): 
     422        return self._url 
     423 
    416424    @property 
    417425    def closed(self): 
    418426        return not self._sock 
    419  
    420     def get_type(self): 
    421         return 'client' 
    422  
    423     def get_name(self): 
    424         return self._url 
    425427 
    426428    def open(self): 
     
    436438            self._sock = websocket.WebSocket() 
    437439            self._sock.connect(self._url, **self._options) 
    438             self._switch.register_port(self) 
    439440            self._ioloop.add_handler(self.fileno(), self, self._ioloop.READ) 
    440             self.dprintf('connected: %s\n', lambda: self._url) 
     441            return self._switch.register_port(self) 
    441442        finally: 
    442443            websocket._SSLSocketWrapper = sslwrap 
     444            self.dprintf('connected: %s\n', lambda: self._url) 
    443445 
    444446    def close(self): 
    445447        if self.closed: 
    446448            raise websocket.WebSocketException('already closed') 
     449        self._switch.unregister_port(self) 
    447450        self._ioloop.remove_handler(self.fileno()) 
    448         self._switch.unregister_port(self) 
    449451        self._sock.close() 
    450452        self._sock = None 
     
    478480class EtherWebSocketControlHandler(DebugMixIn, BasicAuthMixIn, RequestHandler): 
    479481    NAMESPACE = 'etherws.control' 
     482    INTERFACES = { 
     483        TapHandler.get_type():           TapHandler, 
     484        EtherWebSocketClient.get_type(): EtherWebSocketClient, 
     485    } 
    480486 
    481487    def __init__(self, app, req, ioloop, switch, htpasswd=None, debug=False): 
     
    512518        list_ = [] 
    513519        for port in self._switch.portlist: 
    514             list_.append({ 
    515                 'port': port.number, 
    516                 'type': port.interface.get_type(), 
    517                 'name': port.interface.get_name(), 
    518                 'tx':   port.tx, 
    519                 'rx':   port.rx, 
    520                 'shut': port.shut, 
    521             }) 
     520            list_.append(self._portstat(port)) 
    522521        return {'portlist': list_} 
    523522 
    524523    def handle_addPort(self, params): 
     524        list_ = [] 
    525525        for p in params: 
    526             getattr(self, '_openport_' + p['type'])(p) 
    527         return self.handle_listPort(params) 
     526            type_ = p['type'] 
     527            target = p['target'] 
     528            options = getattr(self, '_optparse_' + type_)(p.get('options', {})) 
     529            klass = self.INTERFACES[type_] 
     530            interface = klass(self._ioloop, self._switch, target, **options) 
     531            portnum = interface.open() 
     532            list_.append(self._portstat(self._switch.get_port(portnum))) 
     533        return {'portlist': list_} 
    528534 
    529535    def handle_delPort(self, params): 
     536        list_ = [] 
    530537        for p in params: 
    531             self._switch.get_port(int(p['port'])).interface.close() 
    532         return self.handle_listPort(params) 
     538            port = self._switch.get_port(int(p['port'])) 
     539            list_.append(self._portstat(port)) 
     540            port.interface.close() 
     541        return {'portlist': list_} 
    533542 
    534543    def handle_shutPort(self, params): 
     544        list_ = [] 
    535545        for p in params: 
    536             self._switch.shut_port(int(p['port']), bool(p['flag'])) 
    537         return self.handle_listPort(params) 
    538  
    539     def _openport_tap(self, p): 
    540         dev = p['device'] 
    541         tap = TapHandler(self._ioloop, self._switch, dev, debug=self._debug) 
    542         tap.open() 
    543  
    544     def _openport_client(self, p): 
    545         ssl_ = self._ssl_wrapper(p.get('insecure'), p.get('cacerts')) 
    546         cred = {'user': p.get('user'), 'passwd': p.get('passwd')} 
    547         url = p['url'] 
    548         client = EtherWebSocketClient(self._ioloop, self._switch, 
    549                                       url, ssl_, cred, self._debug) 
    550         client.open() 
     546            port = self._switch.get_port(int(p['port'])) 
     547            port.shut = bool(p['flag']) 
     548            list_.append(self._portstat(port)) 
     549        return {'portlist': list_} 
     550 
     551    def _optparse_tap(self, opt): 
     552        return {'debug': self._debug} 
     553 
     554    def _optparse_client(self, opt): 
     555        args = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': opt.get('cacerts')} 
     556        if opt.get('insecure'): 
     557            args = {} 
     558        ssl_ = lambda sock: ssl.wrap_socket(sock, **args) 
     559        cred = {'user': opt.get('user'), 'passwd': opt.get('passwd')} 
     560        return {'ssl_': ssl_, 'cred': cred, 'debug': self._debug} 
    551561 
    552562    @staticmethod 
    553     def _ssl_wrapper(insecure, ca_certs): 
    554         args = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': ca_certs} 
    555         if insecure: 
    556             args = {} 
    557         return lambda sock: ssl.wrap_socket(sock, **args) 
    558  
    559  
    560 def daemonize(nochdir=False, noclose=False): 
    561     if os.fork() > 0: 
    562         sys.exit(0) 
    563  
    564     os.setsid() 
    565  
    566     if os.fork() > 0: 
    567         sys.exit(0) 
    568  
    569     if not nochdir: 
    570         os.chdir('/') 
    571  
    572     if not noclose: 
    573         os.umask(0) 
    574         sys.stdin.close() 
    575         sys.stdout.close() 
    576         sys.stderr.close() 
    577         os.close(0) 
    578         os.close(1) 
    579         os.close(2) 
    580         sys.stdin = open(os.devnull) 
    581         sys.stdout = open(os.devnull, 'a') 
    582         sys.stderr = open(os.devnull, 'a') 
    583  
    584  
    585 def main(): 
    586     def realpath(ns, *keys): 
    587         for k in keys: 
    588             v = getattr(ns, k, None) 
    589             if v is not None: 
    590                 v = os.path.realpath(v) 
    591                 open(v).close()  # check readable 
    592                 setattr(ns, k, v) 
    593  
    594     def checkpath(ns, path): 
     563    def _portstat(port): 
     564        return { 
     565            'port':   port.number, 
     566            'type':   port.interface.get_type(), 
     567            'target': port.interface.get_target(), 
     568            'tx':     port.tx, 
     569            'rx':     port.rx, 
     570            'shut':   port.shut, 
     571        } 
     572 
     573 
     574def start_switch(args): 
     575    def daemonize(nochdir=False, noclose=False): 
     576        if os.fork() > 0: 
     577            sys.exit(0) 
     578 
     579        os.setsid() 
     580 
     581        if os.fork() > 0: 
     582            sys.exit(0) 
     583 
     584        if not nochdir: 
     585            os.chdir('/') 
     586 
     587        if not noclose: 
     588            os.umask(0) 
     589            sys.stdin.close() 
     590            sys.stdout.close() 
     591            sys.stderr.close() 
     592            os.close(0) 
     593            os.close(1) 
     594            os.close(2) 
     595            sys.stdin = open(os.devnull) 
     596            sys.stdout = open(os.devnull, 'a') 
     597            sys.stderr = open(os.devnull, 'a') 
     598 
     599    def checkabspath(ns, path): 
    595600        val = getattr(ns, path, '') 
    596601        if not val.startswith('/'): 
     
    606611        return None 
    607612 
     613    def setrealpath(ns, *keys): 
     614        for k in keys: 
     615            v = getattr(ns, k, None) 
     616            if v is not None: 
     617                v = os.path.realpath(v) 
     618                open(v).close()  # check readable 
     619                setattr(ns, k, v) 
     620 
    608621    def setport(ns, port, isssl): 
    609622        val = getattr(ns, port, None) 
     
    620633            return setattr(ns, htpasswd, Htpasswd(val)) 
    621634 
    622     parser = argparse.ArgumentParser() 
    623  
    624     parser.add_argument('--debug', action='store_true', default=False) 
    625     parser.add_argument('--foreground', action='store_true', default=False) 
    626     parser.add_argument('--ageout', action='store', type=int, default=300) 
    627  
    628     parser.add_argument('--path', action='store', default='/') 
    629     parser.add_argument('--host', action='store', default='') 
    630     parser.add_argument('--port', action='store', type=int) 
    631     parser.add_argument('--htpasswd', action='store') 
    632     parser.add_argument('--sslkey', action='store') 
    633     parser.add_argument('--sslcert', action='store') 
    634  
    635     parser.add_argument('--ctlpath', action='store', default='/ctl') 
    636     parser.add_argument('--ctlhost', action='store', default='') 
    637     parser.add_argument('--ctlport', action='store', type=int) 
    638     parser.add_argument('--ctlhtpasswd', action='store') 
    639     parser.add_argument('--ctlsslkey', action='store') 
    640     parser.add_argument('--ctlsslcert', action='store') 
    641  
    642     args = parser.parse_args() 
    643  
    644635    #if args.debug: 
    645636    #    websocket.enableTrace(True) 
     
    648639        raise ValueError('invalid ageout: %s' % args.ageout) 
    649640 
    650     realpath(args, 'htpasswd', 'sslkey', 'sslcert') 
    651     realpath(args, 'ctlhtpasswd', 'ctlsslkey', 'ctlsslcert') 
    652  
    653     checkpath(args, 'path') 
    654     checkpath(args, 'ctlpath') 
     641    setrealpath(args, 'htpasswd', 'sslkey', 'sslcert') 
     642    setrealpath(args, 'ctlhtpasswd', 'ctlsslkey', 'ctlsslcert') 
     643 
     644    checkabspath(args, 'path') 
     645    checkabspath(args, 'ctlpath') 
    655646 
    656647    sslopt = getsslopt(args, 'sslkey', 'sslcert') 
     
    715706 
    716707 
     708def main(): 
     709    parser = argparse.ArgumentParser() 
     710    subparsers = parser.add_subparsers(dest='subcommand') 
     711    parser_s = subparsers.add_parser('switch') 
     712    parser_c = subparsers.add_parser('control') 
     713 
     714    parser_s.add_argument('--debug', action='store_true', default=False) 
     715    parser_s.add_argument('--foreground', action='store_true', default=False) 
     716    parser_s.add_argument('--ageout', action='store', type=int, default=300) 
     717 
     718    parser_s.add_argument('--path', action='store', default='/') 
     719    parser_s.add_argument('--host', action='store', default='') 
     720    parser_s.add_argument('--port', action='store', type=int) 
     721    parser_s.add_argument('--htpasswd', action='store') 
     722    parser_s.add_argument('--sslkey', action='store') 
     723    parser_s.add_argument('--sslcert', action='store') 
     724 
     725    parser_s.add_argument('--ctlpath', action='store', default='/ctl') 
     726    parser_s.add_argument('--ctlhost', action='store', default='') 
     727    parser_s.add_argument('--ctlport', action='store', type=int) 
     728    parser_s.add_argument('--ctlhtpasswd', action='store') 
     729    parser_s.add_argument('--ctlsslkey', action='store') 
     730    parser_s.add_argument('--ctlsslcert', action='store') 
     731 
     732    args = parser.parse_args() 
     733 
     734    globals()['start_' + args.subcommand](args) 
     735 
     736 
    717737if __name__ == '__main__': 
    718738    main() 
Note: See TracChangeset for help on using the changeset viewer.