如果不太明白SCOKS5具体通讯过程,请先阅读一篇很详细的文章SOCKS5代理原理探索
建立SOCKS5连接时获取访问的目标域名或者IP,如果在白名单中或者是你想要拦截的广告等可进行直连或者屏蔽,其他情况则连接设置的目标SOCKS5代理
话不多说,直接上源码
import datetime
import json
import os
import platform
import re
import socket
import select
import threading
import struct
import time
import traceback
conf = None # 全局配置
def loadJsonFile(path):
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
def add_log(s):
filename = datetime.datetime.now().strftime('%Y-%m-%d')
with open(filename + ".txt", 'a') as f:
f.write(str(s) + "\n")
class Conf(object):
white_list = None
black_list = None
proxy_ip = None
proxy_port = None
local_port = None
def __init__(self):
json_conf = loadJsonFile('./conf.json')
self.white_list = set(json_conf["white_list"])
self.black_list = set(json_conf["black_list"])
self.proxy_ip = json_conf["proxy_ip"]
self.proxy_port = int(json_conf["proxy_port"])
self.local_port = set(json_conf["local_port"])
# 客户端开始发送具体的请求
class S5Req:
def __init__(self, buf):
self.ver, self.cmd, self.rsv, self.atyp = struct.unpack("BBBB", buf)
self.dst_addr = None
self.dst_port = None
self.begin_buf = None
def parse_port(self, buf): # 解析端口
port = struct.unpack("H", buf[0:2])[0]
self.dst_port = int(socket.ntohs(int(port)))
def parse_ipv4(self, buf): # 解析IP
self.dst_addr = socket.inet_ntoa(buf[0:4])
self.parse_port(buf[4:])
def parse_domain_name(self, buf): # 解析域名
name_len = struct.unpack("B", buf[0:1])[0]
self.dst_addr = bytes.decode(buf[1:name_len + 1])
self.parse_port(buf[1 + name_len:])
def parse_netloc(self, buf): # 解析网络地址
if self.atyp == 3:
self.parse_domain_name(buf)
if self.atyp == 1:
self.parse_ipv4(buf)
# 服务端收到请求后,处理后返回
class S5Resp:
def __init__(self):
self.ver = 5
self.rep = 1
self.rsv = 0
self.atyp = 1
self.end_buf = None
def pack(self):
buf = struct.pack("BBBBIH", self.ver, self.rep, self.rsv, self.atyp, 0, 0)
return buf
class Socks5Error(Exception):
pass
class Socks5Thread(threading.Thread):
wait = 60.0
buf_size = 1024 * 4
buf1 = None
buf2 = None
buf3 = None
is_white = False
def __init__(self, s, ip, port, socks5):
self.s = s
self.dst_s = None
self.ip = ip
self.port = port
self.socks5 = socks5
threading.Thread.__init__(self)
def run(self):
resp = S5Resp()
try:
global conf
self.socks5.a += 1
self.buf1 = self.s.recv(255)
if not self.buf1:
raise socket.error
# 协议版本号x05 s5 METHOD x00 无需认证
self.s.send(b"\x05\x00")
self.buf2 = self.s.recv(4)
if not self.buf2 or len(self.buf2) != 4:
raise socket.error
req = S5Req(self.buf2)
if req.ver != 5: # 判断是否为客户端s5代理
resp.rep = 1
raise Socks5Error
count = 255
if req.atyp == 1:
count = 6
self.buf3 = self.s.recv(count)
req.parse_netloc(self.buf3)
if req.atyp == 3:
try:
addr = socket.gethostbyname(req.dst_addr)
except socket.error:
resp.rep = 4
raise Socks5Error
else:
addr = req.dst_addr
resp.rep = 0
self.s.send(resp.pack())
req.begin_buf = self.s.recv(256)
if not req.begin_buf:
raise socket.error
self.dst_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
if req.dst_addr in conf.black_list:
raise socket.error
elif req.dst_addr in conf.white_list or conf.proxy_ip == "":
self.is_white = True
self.dst_s.connect((addr, req.dst_port))
self.dst_s.send(req.begin_buf)
self.forward_loop(req=req)
else:
self.dst_s.connect((conf.proxy_ip, conf.proxy_port))
self.dst_s.send(self.buf1)
self.dst_s.recv(255)
self.dst_s.send(self.buf2 + self.buf3)
self.dst_s.recv(255)
self.dst_s.send(req.begin_buf)
self.forward_loop(req=req)
except socket.error:
raise socket.error
except Socks5Error:
# traceback.print_exc()
try:
self.s.send(resp.pack())
except Socks5Error:
pass
except socket.error:
# traceback.print_exc()
pass
except:
traceback.print_exc()
finally:
if self.s:
self.s.close()
if self.dst_s:
self.dst_s.close()
def forward_loop(self, req):
rs_time = 0
send_size = 0
recv_size = 0
begin_time = time.time()
try:
while 1:
r, w, x = select.select([self.s, self.dst_s], [], [], self.wait)
if not r:
return
begin_rs = time.time()
try:
for s in r:
if s is self.s:
buf = self.s.recv(self.buf_size)
if not buf:
raise socket.error
send_size += len(buf)
self.dst_s.send(buf)
if s is self.dst_s:
buf = self.dst_s.recv(self.buf_size)
if not buf:
raise socket.error
recv_size += len(buf)
self.s.send(buf)
finally:
rs_time += (time.time() - begin_rs)
finally:
if req.begin_buf:
send_size = send_size >> 10
recv_size = recv_size >> 10
info = "是否直连:" + str(self.is_white) + " " + req.dst_addr + " " + str(
req.dst_port) + " 发送:" + str(send_size) + "KB 接收:" + str(recv_size) + "KB 连接时长:" + \
str(round(time.time() - begin_time, 4)) + "秒 传输时长:" + str(round(rs_time, 4)) + "秒 首次内容:" + \
str(req.begin_buf)
print(info)
add_log(info)
class Socks5(threading.Thread):
def __init__(self, ip="0.0.0.0", port=8080):
self.ip = ip
self.port = port
self.s = None
threading.Thread.__init__(self)
self.a = 1
def run(self):
try:
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.s.bind((self.ip, self.port))
self.s.listen(5)
except socket.error as msg:
print(msg)
if self.s:
self.s.close()
self.s = None
return False
while 1:
try:
conn, addr = self.s.accept()
thread = Socks5Thread(conn, addr[0], addr[1], self)
thread.start()
except socket.error as msg:
print(msg)
self.s.close()
self.s = None
return False
pass
def kill_process(port):
ret = os.popen("netstat -nao|findstr " + str(port))
# 注意解码方式和cmd要相同,即为"gbk",否则输出乱码
str_list = ret.read()
ret_list = re.split('\n', str_list)
try:
process_pid = list(ret_list[0].split())[-1]
os.popen('taskkill /pid ' + str(process_pid) + ' /F')
except:
pass
def begin():
global conf
conf = Conf()
print("目标代理地址为:", end="")
if conf.proxy_ip == "":
print("无代理,直连")
else:
print(conf.proxy_ip, conf.proxy_port)
print("当前计算机类型", platform.system())
print("当前局域网地址:", socket.gethostbyname(socket.gethostname()))
print("开放的端口如下:")
ip_addr = "0.0.0.0"
socks_set = {}
for port in conf.local_port:
print(port)
kill_process(port)
s5 = Socks5(ip_addr, int(port))
s5.start()
socks_set[int(port)] = s5
if __name__ == '__main__':
begin()
{
"#white_list": "白名单,可以是IP,也可以是域名",
"white_list": [
"example1.com",
"1.1.1.1"
],
"#black_list": "黑名单,可以是IP,也可以是域名",
"black_list": [
"example2.com",
"2.2.2.2"
],
"#proxy_ip": "Socks5代理IP,当proxy_ip为空字符串时则无代理",
"#proxy_port": "Socks5代理端口,当proxy_ip为空字符串时则无代理",
"eg_proxy_ip": "1.1.1.1",
"proxy_ip": "",
"proxy_port": "8000",
"local_port": [
10073,
10074,
10075
]
}