SS(socks5)代理客户端的实验版实现

基本的上网通道。学习一下实现方法。

简单说起来就是:

在本地,把流量发送到客户端端口,经由本地运行的代理转发到远端。远端执行转发过来的请求。

转发过程中可以加密,因此中间节点难以识别报文的具体信息。


可以看见,http报文承载于tcp,因此浏览器的大部分网页浏览就没有问题了。

客户端的功能主要就是链接的建立过程和加密。如果只实现tcp部分转发,代码并不会很长。

以下代码基于shadowsocks2.9版本。(使用了它的加密库)

#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division,\
    with_statement
import os,sys
reload(sys)
sys.setdefaultencoding('utf-8')
import json
import SocketServer
import socket
import struct
import select
import binascii

sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../'))
from shadowsocks import encrypt


def send_all(sock, data):
    bytes_sent = 0
    while True:
        r = sock.sendall(data[bytes_sent:])
        #print "r:" + str(r)
        return len(data)
        

class ThreadingTCPServer(SocketServer.ThreadingMixIn,SocketServer.TCPServer):
#class ThreadingTCPServer(SocketServer.TCPServer): #单线程调试时使用
    #继承了这两个类第一个类放前面表示优先查找他的方法,而他有多线程的handler
    #init的方法,在第二个类中找到,于是入参就是端口加RequestHandlerClass
    #process_request Overridden by ThreadingMixIn.
    allow_reuse_address = True

class Socks5Server(SocketServer.StreamRequestHandler):
    #继承自Base class for request handler classes,自定义handle方法即可

    def encrypt(self, data):
        return self._encryptor.encrypt(data)

    def decrypt(self, data):
        return self._encryptor.decrypt(data)

    def send_encrypt(self, sock, data):
        sock.send(self.encrypt(data))

    def handle_socks5(self):
        self._encryptor = encrypt.Encryptor(str(KEY),str(METHOD))

        sock = self.connection  #self.connection就是最初的socket  经历了requestconnection名字的变化而已
         rec = sock.recv(262)
        #print "AUTHENTICATION :",rec.encode('hex')
        sock.send("\x05\x00")
        data = self.rfile.read(4)
        mode = ord(data[1])  # CMD == 0x01 (connect)
        if mode != 1:
            print "\n\n\nerror :mode != 1\n\n\n\n"  #1connect2bing3udp
            return
        addrtype = ord(data[3])  # indicate destination address type
        print "mode:" + str(mode) + " addrtype:" + str(addrtype) + " ",
        addr_to_send = data[3]
        if addrtype == 1:  # IPv4
            addr_ip = self.rfile.read(4)  # 4 bytes IPv4 address (big endian)

            addr = socket.inet_ntoa(addr_ip)
            print "addr_ip:%s" %addr,
            addr_to_send += addr_ip
        elif addrtype == 3:  # FQDN (Fully Qualified Domain Name)
            addr_len = self.rfile.read(1)  # Domain name's Length
            addr = self.rfile.read(ord(addr_len))  # Followed by domain name(e.g. www.google.com)
            addr_to_send += addr_len + addr
        else:
            print "\n\n\n\n warning :addr_type not support\n\n\n\n"
            print "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
            print " warning :addr_type not support"
            return
        addr_port = self.rfile.read(2)
        addr_to_send += addr_port  # addr_to_send = ATYP + [Length] + dst addr/domain name + port
        port = struct.unpack('>H',
                             addr_port)  # prase the big endian port number. Note: The result is a tuple even if it contains exactly one item.

        reply = "\x05\x00\x00\x01"  # VER REP RSV ATYP
        reply += socket.inet_aton('0.0.0.0') + struct.pack(">H",
                                                           5678)  #this port num no important
        self.wfile.write(reply)  # response packet
        # reply immediately
        if '-6' in sys.argv[1:]:                # IPv6 support
            remote = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
        else:
            remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        remote.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)  # turn off Nagling
        print "addr_to_send:" ,addr_to_send
        remote.connect((SERVER, REMOTE_PORT))
        self.send_encrypt(remote, addr_to_send)  # encrypted
        print "head len",str(len(addr_to_send))
        #print "head len",str(len(addr_to_send + (100-len(addr_to_send)) * b'\x00'))
        #print "send encrypt head len", str(len(self._encryptor.encrypt(addr_to_send + (100-len(addr_to_send)) * b'\x00')))
        #print "connecting %s:%d" % (addr, port[0])

        self.handle_tcp(sock, remote)



    def handle_tcp(self,sock, remote):

        fdset = [sock, remote]
        while True:
            #print "handle tcp loop"
            r, w, e = select.select(fdset, [], [])  # use select I/O multiplexing model
            if sock in r:  # if local socket is ready for reading
                data = sock.recv(4096)
                #print "sock send data:",  len(data), binascii.b2a_hex(data), "*\n", str(data), "#"
                #print "sock data" + str(data) + "end"
                if len(data) <= 0:  # received all data
                    #print 'sock len' + str(len(data))
                    break
                #print 'sock sendall'
                result = send_all(remote, self.encrypt(data))  # send data after encrypting
                if result < len(data):
                    pass
                    #print 'len' + str(result) + 'failed to send all data'
                    #raise Exception('failed to send all data sock')

            if remote in r:  # remote socket(proxy) ready for reading
                data = remote.recv(4096)
                #print "sock send data:", len(data), hex(self.decrypt(data)[0]),hex(self.decrypt(data)[1]),binascii.b2a_hex(str(self.decrypt(data))), "*\n", str(self.decrypt(data)), "#"
                if len(data) <= 0:
                    #print 'remote len' + str(len(data))
                    break
                #print 'remote sendall'
                result = send_all(sock, self.decrypt(data))  # send to local socket(application)
                if result < len(data):
                    pass
                    #print 'failed to send all data remote'
                    #raise Exception('failed to send all data remote')


    def handle(self):
        self.handle_socks5()


if __name__ == '__main__':
    os.chdir(os.path.dirname(__file__) or './')
    with open('config.json', 'rb') as f:
        config = json.load(f)
    SERVER = config['server']
    REMOTE_PORT = config['server_port']
    PORT = config['local_port']
    KEY = config['password']
    METHOD = config['method']

    server = ThreadingTCPServer(('', PORT), Socks5Server)
    print "server start prot: %d" % PORT
    server.serve_forever()
    """serve_forever -> _handle_request_noblock -> process_request -> finish_request ->
    -> self.RequestHandlerClass(request, client_address, self) -> handle()
    所以重写了requestHandlerClass"""

{
    "server": "x.x.x.x",
    "server_port": x,
    "password": "x",
    "local_port": x,
    "method":"aes-256-cfb"
}

from __future__ import absolute_import作用是更好组织文件目录

因此文件放在shadowsocks-2.9.1\shadowsocks下

使用:设置浏览器使用sock5,端口、密码为所设置。部署远端shadowsocks。

windows下需要安装Win64OpenSSL-1_1_0g,linux下同样需要加密库。

实际上shadowsocks的实现复杂多了,还有很多要学习的地方。

运行起来就是这样啦


参考:

https://zhuanlan.zhihu.com/p/28675737

https://loggerhead.me/posts/shadowsocks-yuan-ma-fen-xi-tcp-dai-li.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章