clowwindy
11 years ago
6 changed files with 282 additions and 14 deletions
@ -0,0 +1,147 @@ |
|||
#!/usr/bin/python |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2014 clowwindy |
|||
# |
|||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
# of this software and associated documentation files (the "Software"), to deal |
|||
# in the Software without restriction, including without limitation the rights |
|||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
# copies of the Software, and to permit persons to whom the Software is |
|||
# furnished to do so, subject to the following conditions: |
|||
# |
|||
# The above copyright notice and this permission notice shall be included in |
|||
# all copies or substantial portions of the Software. |
|||
# |
|||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
# SOFTWARE. |
|||
|
|||
# from ssloop |
|||
# https://github.com/clowwindy/ssloop |
|||
|
|||
|
|||
import select |
|||
from collections import defaultdict |
|||
|
|||
|
|||
MODE_NULL = 0x00 |
|||
MODE_IN = 0x01 |
|||
MODE_OUT = 0x04 |
|||
MODE_ERR = 0x08 |
|||
MODE_HUP = 0x10 |
|||
MODE_NVAL = 0x20 |
|||
|
|||
|
|||
class EpollLoop(object): |
|||
|
|||
def __init__(self): |
|||
self._epoll = select.epoll() |
|||
|
|||
def poll(self, timeout): |
|||
return self._epoll.poll(timeout) |
|||
|
|||
def add_fd(self, fd, mode): |
|||
self._epoll.register(fd, mode) |
|||
|
|||
def remove_fd(self, fd): |
|||
self._epoll.unregister(fd) |
|||
|
|||
def modify_fd(self, fd, mode): |
|||
self._epoll.modify(fd, mode) |
|||
|
|||
|
|||
class KqueueLoop(object): |
|||
|
|||
MAX_EVENTS = 1024 |
|||
|
|||
def __init__(self): |
|||
self._kqueue = select.kqueue() |
|||
self._fds = {} |
|||
|
|||
def _control(self, fd, mode, flags): |
|||
events = [] |
|||
if mode & MODE_IN: |
|||
events.append(select.kevent(fd, select.KQ_FILTER_READ, flags)) |
|||
if mode & MODE_OUT: |
|||
events.append(select.kevent(fd, select.KQ_FILTER_WRITE, flags)) |
|||
for e in events: |
|||
self._kqueue.control([e], 0) |
|||
|
|||
def poll(self, timeout): |
|||
if timeout < 0: |
|||
timeout = None # kqueue behaviour |
|||
events = self._kqueue.control(None, KqueueLoop.MAX_EVENTS, timeout) |
|||
results = defaultdict(lambda: MODE_NULL) |
|||
for e in events: |
|||
fd = e.ident |
|||
if e.filter == select.KQ_FILTER_READ: |
|||
results[fd] |= MODE_IN |
|||
elif e.filter == select.KQ_FILTER_WRITE: |
|||
results[fd] |= MODE_OUT |
|||
return results.iteritems() |
|||
|
|||
def add_fd(self, fd, mode): |
|||
self._fds[fd] = mode |
|||
self._control(fd, mode, select.KQ_EV_ADD) |
|||
|
|||
def remove_fd(self, fd): |
|||
self._control(fd, self._fds[fd], select.KQ_EV_DELETE) |
|||
del self._fds[fd] |
|||
|
|||
def modify_fd(self, fd, mode): |
|||
self.remove_fd(fd) |
|||
self.add_fd(fd, mode) |
|||
|
|||
|
|||
class SelectLoop(object): |
|||
|
|||
def __init__(self): |
|||
self._r_list = set() |
|||
self._w_list = set() |
|||
self._x_list = set() |
|||
|
|||
def poll(self, timeout): |
|||
r, w, x = select.select(self._r_list, self._w_list, self._x_list, |
|||
timeout) |
|||
results = defaultdict(lambda: MODE_NULL) |
|||
for p in [(r, MODE_IN), (w, MODE_OUT), (x, MODE_ERR)]: |
|||
for fd in p[0]: |
|||
results[fd] |= p[1] |
|||
return results.items() |
|||
|
|||
def add_fd(self, fd, mode): |
|||
if mode & MODE_IN: |
|||
self._r_list.add(fd) |
|||
if mode & MODE_OUT: |
|||
self._w_list.add(fd) |
|||
if mode & MODE_ERR: |
|||
self._x_list.add(fd) |
|||
|
|||
def remove_fd(self, fd): |
|||
if fd in self._r_list: |
|||
self._r_list.remove(fd) |
|||
if fd in self._w_list: |
|||
self._w_list.remove(fd) |
|||
if fd in self._x_list: |
|||
self._x_list.remove(fd) |
|||
|
|||
def modify_fd(self, fd, mode): |
|||
self.remove_fd(fd) |
|||
self.add_fd(fd, mode) |
|||
|
|||
|
|||
EventLoop = None |
|||
|
|||
if hasattr(select, 'epoll'): |
|||
EventLoop = EpollLoop |
|||
elif hasattr(select, 'kqueue'): |
|||
EventLoop = KqueueLoop |
|||
elif hasattr(select, 'select'): |
|||
EventLoop = SelectLoop |
|||
else: |
|||
raise Exception('can not find any available functions in select package') |
@ -0,0 +1,117 @@ |
|||
#!/usr/bin/python |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
# Copyright (c) 2014 clowwindy |
|||
# |
|||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
# of this software and associated documentation files (the "Software"), to deal |
|||
# in the Software without restriction, including without limitation the rights |
|||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
# copies of the Software, and to permit persons to whom the Software is |
|||
# furnished to do so, subject to the following conditions: |
|||
# |
|||
# The above copyright notice and this permission notice shall be included in |
|||
# all copies or substantial portions of the Software. |
|||
# |
|||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
# SOFTWARE. |
|||
|
|||
# SOCKS5 UDP Request |
|||
# +----+------+------+----------+----------+----------+ |
|||
# |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | |
|||
# +----+------+------+----------+----------+----------+ |
|||
# | 2 | 1 | 1 | Variable | 2 | Variable | |
|||
# +----+------+------+----------+----------+----------+ |
|||
|
|||
# SOCKS5 UDP Response |
|||
# +----+------+------+----------+----------+----------+ |
|||
# |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | |
|||
# +----+------+------+----------+----------+----------+ |
|||
# | 2 | 1 | 1 | Variable | 2 | Variable | |
|||
# +----+------+------+----------+----------+----------+ |
|||
|
|||
# shadowsocks UDP Request (before encrypted) |
|||
# +------+----------+----------+----------+ |
|||
# | ATYP | DST.ADDR | DST.PORT | DATA | |
|||
# +------+----------+----------+----------+ |
|||
# | 1 | Variable | 2 | Variable | |
|||
# +------+----------+----------+----------+ |
|||
|
|||
# shadowsocks UDP Response (before encrypted) |
|||
# +------+----------+----------+----------+ |
|||
# | ATYP | DST.ADDR | DST.PORT | DATA | |
|||
# +------+----------+----------+----------+ |
|||
# | 1 | Variable | 2 | Variable | |
|||
# +------+----------+----------+----------+ |
|||
|
|||
# shadowsocks UDP Request and Response (after encrypted) |
|||
# +-------+--------------+ |
|||
# | IV | PAYLOAD | |
|||
# +-------+--------------+ |
|||
# | Fixed | Variable | |
|||
# +-------+--------------+ |
|||
|
|||
# HOW TO NAME THINGS |
|||
# ------------------ |
|||
# `dest` means destination server, which is from DST fields in the SOCKS5 |
|||
# request |
|||
# `local` means local server of shadowsocks |
|||
# `remote` means remote server of shadowsocks |
|||
# `client` means UDP clients that connects to other servers |
|||
# `server` means the UDP server that handle user requests |
|||
|
|||
|
|||
import threading |
|||
import socket |
|||
import event |
|||
|
|||
|
|||
class UDPRelay(object): |
|||
def __init__(self, listen_addr='127.0.0.1', listen_port=1080, |
|||
remote_addr='127.0.0.1', remote_port=8387, password=None, |
|||
method='table', timeout=300, is_local=True): |
|||
self._listen_addr = listen_addr |
|||
self._listen_port = listen_port |
|||
self._remote_addr = remote_addr |
|||
self._remote_port = remote_port |
|||
self._password = password |
|||
self._method = method |
|||
self._timeout = timeout |
|||
self._is_local = is_local |
|||
self._eventloop = event.EventLoop() |
|||
|
|||
def _handle_server(self, addr, sock, data): |
|||
# TODO |
|||
pass |
|||
|
|||
def _handle_client(self, addr, sock, data): |
|||
# TODO |
|||
pass |
|||
|
|||
def _run(self): |
|||
eventloop = self._eventloop |
|||
server_socket = self._server_socket |
|||
eventloop.add_fd(server_socket, event.MODE_IN) |
|||
is_local = self._is_local |
|||
while True: |
|||
r = eventloop.poll() |
|||
# TODO |
|||
|
|||
def start(self): |
|||
addrs = socket.getaddrinfo(self._listen_addr, self._listen_port, 0, |
|||
socket.SOCK_DGRAM, socket.SOL_UDP) |
|||
if len(addrs) == 0: |
|||
raise Exception("can't get addrinfo for %s:%d" % |
|||
(self._listen_addr, self._listen_port)) |
|||
af, socktype, proto, canonname, sa = addrs[0] |
|||
server_socket = socket.socket(af, socktype, proto) |
|||
server_socket.bind((self._listen_addr, self._listen_port)) |
|||
server_socket.setblocking(False) |
|||
self._server_socket = server_socket |
|||
|
|||
threading.Thread(target=self._run).start() |
Loading…
Reference in new issue