- #!/usr/bin/env python3
-
- import argparse
- import logging
- import os
- import platform
- import signal
- import struct
- import sys
- import threading
- from socket import AF_INET, SOCK_STREAM, socket
- from socketserver import BaseServer, StreamRequestHandler, ThreadingTCPServer
-
- __author__ = 'Youchao Feng'
- support_os = ('Darwin', 'Linux')
- current_os = platform.system()
-
-
- def byte_to_int(b):
- """
- Convert Unsigned byte to int
- :param b: byte value
- :return: int value
- """
- return b & 0xFF
-
-
- def port_from_byte(b1, b2):
- """
- :param b1: First byte of port
- :param b2: Second byte of port
- :return: Port in Int
- """
- return byte_to_int(b1) << 8 | byte_to_int(b2)
-
-
- def host_from_ip(a, b, c, d):
- a = byte_to_int(a)
- b = byte_to_int(b)
- c = byte_to_int(c)
- d = byte_to_int(d)
- return "%d.%d.%d.%d" % (a, b, c, d)
-
-
- def get_command_name(value):
- """
- Gets command name by value
- :param value: value of Command
- :return: Command Name
- """
- if value == 1:
- return 'CONNECT'
- elif value == 2:
- return 'BIND'
- elif value == 3:
- return 'UDP_ASSOCIATE'
- else:
- return None
-
-
- def build_command_response(reply):
- start = b'\x05%s\x00\x01\x00\x00\x00\x00\x00\x00'
- return start % reply.get_byte_string()
-
-
- def close_session(session):
- session.get_client_socket().close()
- logging.info("Session[%s] closed", session.get_id())
-
-
- def run_daemon_process(stdout='/dev/null',
- stderr=None,
- stdin='/dev/null',
- pid_file=None,
- start_msg='started with pid %s'):
- """
- This forks the current process into a daemon.
- The stdin, stdout, and stderr arguments are file names that
- will be opened and be used to replace the standard file descriptors
- in sys.stdin, sys.stdout, and sys.stderr.
- These arguments are optional and default to /dev/null.
- Note that stderr is opened unbuffered, so
- if it shares a file with stdout then interleaved output
- may not appear in the order that you expect.
- """
- # flush io
- sys.stdout.flush()
- sys.stderr.flush()
- # Do first fork.
- try:
- if os.fork() > 0:
- sys.exit(0) # Exit first parent.
- except OSError as e:
- sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
- sys.exit(1)
- # Decouple from parent environment.
- os.chdir("/")
- os.umask(0)
- os.setsid()
- # Do second fork.
- try:
- if os.fork() > 0:
- sys.exit(0) # Exit second parent.
- except OSError as e:
- sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
- sys.exit(1)
- # Open file descriptors and print start message
- if not stderr:
- stderr = stdout
- si = open(stdin, 'r')
- so = open(stdout, 'a+')
- se = open(stderr, 'ba+', 0) # unbuffered
- pid = str(os.getpid())
- sys.stderr.write(start_msg % pid)
- sys.stderr.flush()
- if pid_file:
- open(pid_file, 'w+').write("%s\n" % pid)
- # Redirect standard file descriptors.
- os.dup2(si.fileno(), sys.stdin.fileno())
- os.dup2(so.fileno(), sys.stdout.fileno())
- os.dup2(se.fileno(), sys.stderr.fileno())
-
-
- class Session:
- index = 0
-
- def __init__(self, client_socket):
- Session.index += 1
- self.__id = Session.index
- self.__client_socket = client_socket
- self._attr = {}
-
- def get_id(self):
- return self.__id
-
- def set_attr(self, key, value):
- self._attr[key] = value
-
- def get_client_socket(self):
- return self.__client_socket
-
-
- class AddressType:
- IPV4 = 1
- DOMAIN_NAME = 3
- IPV6 = 4
-
-
- class SocksCommand:
- CONNECT = 1
- BIND = 2
- UDP_ASSOCIATE = 3
-
-
- class SocksMethod:
- NO_AUTHENTICATION_REQUIRED = 0
- GSS_API = 1
- USERNAME_PASSWORD = 2
-
-
- class ServerReply:
- def __init__(self, value):
- self.__value = value
-
- def get_byte_string(self):
- if self.__value == 0:
- return b'\x00'
- elif self.__value == 1:
- return b'\x01'
- elif self.__value == 2:
- return b'\x02'
- elif self.__value == 3:
- return b'\x03'
- elif self.__value == 4:
- return b'\x04'
- elif self.__valu