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