source: etherws/tags/release-0.1/etherws.py @ 142

Revision 142, 5.9 KB checked in by atzm, 12 years ago (diff)
  • add tag release-0.1
  • Property svn:keywords set to Id
RevLine 
[133]1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
[141]4#              Ethernet over WebSocket tunneling server/client
[133]5#
6# depends on:
7#   - python-2.7.2
8#   - python-pytun-0.2
[136]9#   - websocket-client-0.7.0
10#   - tornado-2.2.1
[133]11#
[140]12# todo:
[141]13#   - SSL support
[140]14#   - servant mode (like typical p2p software)
15#
[133]16# ===========================================================================
17# Copyright (c) 2012, Atzm WATANABE <atzm@atzm.org>
18# All rights reserved.
19#
20# Redistribution and use in source and binary forms, with or without
21# modification, are permitted provided that the following conditions are met:
22#
23# 1. Redistributions of source code must retain the above copyright notice,
24#    this list of conditions and the following disclaimer.
25# 2. Redistributions in binary form must reproduce the above copyright
26#    notice, this list of conditions and the following disclaimer in the
27#    documentation and/or other materials provided with the distribution.
28#
29# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
30# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
33# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
34# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
35# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
36# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
37# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
39# POSSIBILITY OF SUCH DAMAGE.
40# ===========================================================================
41#
42# $Id$
43
44import os
45import sys
46import argparse
47import threading
48
49import pytun
50import websocket
51import tornado.httpserver
52import tornado.ioloop
53import tornado.web
54import tornado.websocket
55
56
[138]57class TapHandler(object):
[133]58    def __init__(self, dev, debug=False):
59        self._debug = debug
[138]60        self._clients = []
[133]61        self._tap = pytun.TunTapDevice(dev, pytun.IFF_TAP | pytun.IFF_NO_PI)
[138]62        self._tap.up()
63        self._write_lock = threading.Lock()
[133]64
[138]65    def fileno(self):
66        return self._tap.fileno()
[133]67
68    def register_client(self, client):
[138]69        self._clients.append(client)
[133]70
71    def unregister_client(self, client):
[138]72        self._clients.remove(client)
[133]73
74    def write(self, caller, message):
75        if self._debug:
76            sys.stderr.write('%s: %s\n' % (caller.__class__.__name__,
77                                           message.encode('hex')))
78        try:
[138]79            self._write_lock.acquire()
[137]80
[133]81            clients = self._clients[:]
82
[137]83            if caller is not self:
84                clients.remove(caller)
[138]85                self._tap.write(message)
[133]86
[137]87            for c in clients:
[139]88                c.write_message(message, True)
[133]89
[137]90        finally:
[138]91            self._write_lock.release()
[137]92
[138]93    def __call__(self, fd, events):
94        self.write(self, self._tap.read(self._tap.mtu))
[133]95
[135]96
[133]97class EtherWebSocket(tornado.websocket.WebSocketHandler):
98    def __init__(self, app, req, tap, debug=False):
99        super(EtherWebSocket, self).__init__(app, req)
100        self._tap = tap
101        self._debug = debug
102
103    def open(self):
104        self._tap.register_client(self)
105
106    def on_message(self, message):
[139]107        self._tap.write(self, message)
[133]108
109    def on_close(self):
110        self._tap.unregister_client(self)
111
112
[134]113def daemonize(nochdir=False, noclose=False):
114    if os.fork() > 0:
115        sys.exit(0)
116
117    os.setsid()
118
119    if os.fork() > 0:
120        sys.exit(0)
121
122    if not nochdir:
123        os.chdir('/')
124
125    if not noclose:
126        os.umask(0)
127        sys.stdin.close()
128        sys.stdout.close()
129        sys.stderr.close()
130        os.close(0)
131        os.close(1)
132        os.close(2)
133        sys.stdin = open(os.devnull)
134        sys.stdout = open(os.devnull, 'a')
135        sys.stderr = open(os.devnull, 'a')
136
137
[133]138def server_main(args):
[138]139    tap = TapHandler(args.device, debug=args.debug)
[133]140    app = tornado.web.Application([
141        (args.path, EtherWebSocket, {'tap': tap, 'debug': args.debug}),
142    ])
143    server = tornado.httpserver.HTTPServer(app)
144    server.listen(args.port, address=args.address)
145
[138]146    ioloop = tornado.ioloop.IOLoop.instance()
147    ioloop.add_handler(tap.fileno(), tap, ioloop.READ)
148    ioloop.start()
[133]149
150
151def client_main(args):
152    if args.debug:
153        websocket.enableTrace(True)
154
[138]155    tap = TapHandler(args.device, debug=args.debug)
[133]156    client = websocket.WebSocketApp(args.uri)
[139]157    client.on_message = lambda s, m: tap.write(client, m)
158    client.write_message = \
159        lambda m, b: client.sock.send(m, websocket.ABNF.OPCODE_BINARY)
[133]160    tap.register_client(client)
161
[138]162    t = threading.Thread(target=client.run_forever)
163    t.setDaemon(True)
164    t.start()
[133]165
[138]166    ioloop = tornado.ioloop.IOLoop.instance()
167    ioloop.add_handler(tap.fileno(), tap, ioloop.READ)
168    ioloop.start()
[133]169
[138]170
[133]171def main():
172    parser = argparse.ArgumentParser()
173    parser.add_argument('--device', action='store', default='ethws%d')
174    parser.add_argument('--foreground', action='store_true', default=False)
175    parser.add_argument('--debug', action='store_true', default=False)
176
177    subparsers = parser.add_subparsers(dest='subcommand')
178
179    parser_server = subparsers.add_parser('server')
180    parser_server.add_argument('--address', action='store', default='')
181    parser_server.add_argument('--port', action='store', type=int, default=80)
182    parser_server.add_argument('--path', action='store', default='/')
183
184    parser_client = subparsers.add_parser('client')
185    parser_client.add_argument('--uri', action='store', required=True)
186
187    args = parser.parse_args()
188
[134]189    if not args.foreground:
190        daemonize()
191
[133]192    if args.subcommand == 'server':
193        server_main(args)
194    elif args.subcommand == 'client':
195        client_main(args)
196
197
198if __name__ == '__main__':
199    main()
Note: See TracBrowser for help on using the repository browser.