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