基本的上網通道。學習一下實現方法。
簡單說起來就是:
在本地,把流量發送到客戶端端口,經由本地運行的代理轉發到遠端。遠端執行轉發過來的請求。
轉發過程中可以加密,因此中間節點難以識別報文的具體信息。
可以看見,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 經歷了request跟connection名字的變化而已 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" #1爲connect,2bing,3udp 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