source: etherws/trunk/etherws.py @ 140

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