首页 >> Python >> 内容页

150行python代码实现一个多路复用的SOCKS5代理服务器

学习socks5协议,使用python实现一个socks5代理服务器。

环境: windows10

python: 3.7

SOCKS5通讯流程

client(浏览器)发送:

VER NMETHODS METHODS
1 1 1-255

VER是SOCKS版本,这里应该是0x05;

NMETHODS是METHODS部分的长度;

METHODS是客户端支持的认证方式列表,每个方法占1字节。当前的定义是:

0x00 不需要认证

0x01 GSSAPI

0x02 用户名、密码认证

0x03 - 0x7F由IANA分配(保留)

0x80 - 0xFE为私人方法保留

0xFF 无可接受的方法

服务器从客户端提供的方法中选择一个并通过以下消息通知客户端(以字节为单位):

VER METHOD
1 1

VER是SOCKS版本,这里应该是0x05;

METHOD是服务端选中的方法。如果返回0xFF表示没有一个认证方法被选中,客户端需要关闭连接。

之后客户端和服务端根据选定的认证方式执行对应的认证。

认证结束后客户端就可以发送请求信息。如果认证方法有特殊封装要求,请求必须按照方法所定义的方式进行封装。

SOCKS5请求格式(以字节为单位):

VER CMD RSV ATYP DST.ADDR DST.PORT
1 1 0x00 1 动态 2

VER是SOCKS版本,这里应该是0x05;

CMD是SOCK的命令码

0x01表示CONNECT请求

0x02表示BIND请求

0x03表示UDP转发

RSV 0x00,保留

ATYP DST.ADDR类型

0x01 IPv4地址,DST.ADDR部分4字节长度

0x03 域名,DST.ADDR部分第一个字节为域名长度,DST.ADDR剩余的内容为域名,没有\0结尾。

0x04 IPv6地址,16个字节长度。

DST.ADDR 目的地址

DST.PORT 网络字节序表示的目的端口

服务器按以下格式回应客户端的请求(以字节为单位):

VER REP RSV ATYP BND.ADDR BND.PORT
1 1 0x00 1 动态 2

VER是SOCKS版本,这里应该是0x05;

REP应答字段

0x00表示成功

0x01普通SOCKS服务器连接失败

0x02现有规则不允许连接

0x03网络不可达

0x04主机不可达

0x05连接被拒

0x06 TTL超时

0x07不支持的命令

0x08不支持的地址类型

0x09 - 0xFF未定义

RSV 0x00,保留

ATYP BND.ADDR类型

0x01 IPv4地址,DST.ADDR部分4字节长度

0x03域名,DST.ADDR部分第一个字节为域名长度,DST.ADDR剩余的内容为域名,没有\0结尾。

0x04 IPv6地址,16个字节长度。

BND.ADDR 服务器绑定的地址

BND.PORT 网络字节序表示的服务器绑定的端口

代码

import select, socket, struct

fd_map = {}
r_list = set()

class Sockv5:

    def __init__(self, ip, port):

        self.sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock_server.bind((ip, port))
        self.sock_server.setblocking(False)
        self.sock_server.listen(1024)

    def handle(self, sock):
        conn, addr = self.sock_server.accept()
        fileno = conn.fileno()
        session = ScokSession(conn)
        fd_map[fileno] = (conn, session)
        r_list.add(fileno)


class ScokSession:

    def __init__(self, conn:socket.socket):
        self.conn = conn
        self.stage = 'init'
        self.remote = None

    def _send_data(self, data):
        self.conn.send(data)

    def handle(self, sock):
        data = None
        try:
            data = sock.recv(32 * 1024)
        except BaseException as e:
            print('get data error {err}'.format(err = e))

        if not data:
            self.destory()

        if self.stage == 'init' and sock == self.conn:
            self._init_stage(data)
        elif self.stage == 'addr' and sock == self.conn:
            self._addr_stage(data)
        elif self.stage == 'connect':
            self._connect_stage(data, sock)
        else:
            self.destory()

    def _init_stage(self, data):
        self._send_data(b'\x05\x00')
        self.stage = 'addr'

    def _addr_stage(self, data):
        ver, cmd, _, atyp = struct.unpack('!BBBB', data[0:4])
        data = data[4:]
        # 取得要访问的地址
        if atyp == 1:
            address = socket.inet_ntoa(data[0:4])
            data = data[4:]
        elif atyp == 3:
            domain_length = struct.unpack('!H', data[0])
            address = data[1: domain_length+1]
            data = data[domain_length+1:]
        elif atyp == 4:
            addr_ip = data[0: 16]
            address = socket.inet_ntop(socket.AF_INET6, addr_ip)
            data = data[16:]
        else:
            self.destory()
            return

        port = struct.unpack('!H', data[0:2])[0]

        if cmd == 1:
            print('accessing: {ip}:{port}'.format(ip=address, port=port))

            self.remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.remote.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
            self.remote.settimeout(5)
            try:
                self.remote.connect((address, port))
                bind_address = self.remote.getsockname()
                addr = struct.unpack('!I', socket.inet_aton(bind_address[0]))[0]
                port = bind_address[1]
                REP = 0
            except BaseException as e:
                print('connecting error : {err}'.format(err=e))
                REP = 5
                addr = struct.unpack("!I", socket.inet_aton('127.0.0.1'))[0]

            reply = struct.pack('!BBBBIH', 5, REP, 0, atyp, addr, port)
            self._send_data(reply)

            if REP == 5:
                self.destory()
                return

            fileno = self.remote.fileno()
            fd_map[fileno] = (self.remote, self)
            r_list.add(fileno)

            self.stage = 'connect'
        else:
            self.destory()
            return 

    def _connect_stage(self, data, sock):
        try:
            if sock == self.conn:
                self.remote.send(data)
            else:
                self.conn.send(data)
            pass
        except BaseException as e:
            print('exchange data error : {err}'.format(err=e))

    def destory(self):
        self.stage = 'DESTORYED'

        if self.conn.fileno() in r_list:
            r_list.remove(self.conn.fileno())

        if self.remote and self.remote.fileno() in r_list:
            r_list.remove(self.remote.fileno())
            self.remote.close()

        self.conn.close()


def main():
    S = Sockv5('127.0.0.1', 9990)
    fileno = S.sock_server.fileno()
    fd_map[fileno] = (S.sock_server, S)
    r_list.add(fileno) 

    while True:
        try:
            r, w, e = select.select(r_list, [], [])
        except (OSError, IOError) as e:
            continue

        for h in r:
            handler = fd_map[h][1]
            sock = fd_map[h][0]
            handler.handle(sock)

if __name__ == "__main__":
    main()