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

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