source: etherws/trunk/etherws.py @ 139

Revision 139, 5.8 KB checked in by atzm, 12 years ago (diff)
  • add direct binary transmission support (to improve performance)
  • Property svn:keywords set to Id
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4#               EtherWebSocket tunneling Server/Client
5#
6# depends on:
7#   - python-2.7.2
8#   - python-pytun-0.2
9#   - websocket-client-0.7.0
10#   - tornado-2.2.1
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
42import argparse
43import threading
44
45import pytun
46import websocket
47import tornado.httpserver
48import tornado.ioloop
49import tornado.web
50import tornado.websocket
51
52
53class TapHandler(object):
54    def __init__(self, dev, debug=False):
55        self._debug = debug
56        self._clients = []
57        self._tap = pytun.TunTapDevice(dev, pytun.IFF_TAP | pytun.IFF_NO_PI)
58        self._tap.up()
59        self._write_lock = threading.Lock()
60
61    def fileno(self):
62        return self._tap.fileno()
63
64    def register_client(self, client):
65        self._clients.append(client)
66
67    def unregister_client(self, client):
68        self._clients.remove(client)
69
70    def write(self, caller, message):
71        if self._debug:
72            sys.stderr.write('%s: %s\n' % (caller.__class__.__name__,
73                                           message.encode('hex')))
74        try:
75            self._write_lock.acquire()
76
77            clients = self._clients[:]
78
79            if caller is not self:
80                clients.remove(caller)
81                self._tap.write(message)
82
83            for c in clients:
84                c.write_message(message, True)
85
86        finally:
87            self._write_lock.release()
88
89    def __call__(self, fd, events):
90        self.write(self, self._tap.read(self._tap.mtu))
91
92
93class EtherWebSocket(tornado.websocket.WebSocketHandler):
94    def __init__(self, app, req, tap, debug=False):
95        super(EtherWebSocket, self).__init__(app, req)
96        self._tap = tap
97        self._debug = debug
98
99    def open(self):
100        self._tap.register_client(self)
101
102    def on_message(self, message):
103        self._tap.write(self, message)
104
105    def on_close(self):
106        self._tap.unregister_client(self)
107
108
109def daemonize(nochdir=False, noclose=False):
110    if os.fork() > 0:
111        sys.exit(0)
112
113    os.setsid()
114
115    if os.fork() > 0:
116        sys.exit(0)
117
118    if not nochdir:
119        os.chdir('/')
120
121    if not noclose:
122        os.umask(0)
123        sys.stdin.close()
124        sys.stdout.close()
125        sys.stderr.close()
126        os.close(0)
127        os.close(1)
128        os.close(2)
129        sys.stdin = open(os.devnull)
130        sys.stdout = open(os.devnull, 'a')
131        sys.stderr = open(os.devnull, 'a')
132
133
134def server_main(args):
135    tap = TapHandler(args.device, debug=args.debug)
136    app = tornado.web.Application([
137        (args.path, EtherWebSocket, {'tap': tap, 'debug': args.debug}),
138    ])
139    server = tornado.httpserver.HTTPServer(app)
140    server.listen(args.port, address=args.address)
141
142    ioloop = tornado.ioloop.IOLoop.instance()
143    ioloop.add_handler(tap.fileno(), tap, ioloop.READ)
144    ioloop.start()
145
146
147def client_main(args):
148    if args.debug:
149        websocket.enableTrace(True)
150
151    tap = TapHandler(args.device, debug=args.debug)
152    client = websocket.WebSocketApp(args.uri)
153    client.on_message = lambda s, m: tap.write(client, m)
154    client.write_message = \
155        lambda m, b: client.sock.send(m, websocket.ABNF.OPCODE_BINARY)
156    tap.register_client(client)
157
158    t = threading.Thread(target=client.run_forever)
159    t.setDaemon(True)
160    t.start()
161
162    ioloop = tornado.ioloop.IOLoop.instance()
163    ioloop.add_handler(tap.fileno(), tap, ioloop.READ)
164    ioloop.start()
165
166
167def main():
168    parser = argparse.ArgumentParser()
169    parser.add_argument('--device', action='store', default='ethws%d')
170    parser.add_argument('--foreground', action='store_true', default=False)
171    parser.add_argument('--debug', action='store_true', default=False)
172
173    subparsers = parser.add_subparsers(dest='subcommand')
174
175    parser_server = subparsers.add_parser('server')
176    parser_server.add_argument('--address', action='store', default='')
177    parser_server.add_argument('--port', action='store', type=int, default=80)
178    parser_server.add_argument('--path', action='store', default='/')
179
180    parser_client = subparsers.add_parser('client')
181    parser_client.add_argument('--uri', action='store', required=True)
182
183    args = parser.parse_args()
184
185    if not args.foreground:
186        daemonize()
187
188    if args.subcommand == 'server':
189        server_main(args)
190    elif args.subcommand == 'client':
191        client_main(args)
192
193
194if __name__ == '__main__':
195    main()
Note: See TracBrowser for help on using the repository browser.