Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some Improvements (Outlined Below) #30

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions config.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[settings]
listen_PORT = 2500
Cloudflare_IP = 194.36.55.89, 195.245.221.245, 195.245.221.245, 195.245.221.245, 195.245.221.245, 195.245.221.245, 45.131.6.224, 176.126.206.97, 185.72.49.127, 207.189.149.181, 45.14.174.17, 185.207.92.5, 203.32.120.29, 104.20.190.33, 45.131.6.131, 185.201.139.152, 45.131.208.5, 203.34.28.236, 203.13.32.50, 195.245.221.214, 45.12.31.113
Cloudflare_port = 443
L_fragment = 77
fragment_sleep = 0.2
my_socket_timeout = 60
first_time_sleep = 0.01
accept_time_sleep = 0.01
220 changes: 104 additions & 116 deletions pyprox_tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,147 +2,135 @@

import socket
import threading
from pathlib import Path
import os
import copy
from concurrent.futures import ThreadPoolExecutor
import time
import datetime
import os
import sys
import argparse
import logging
from logging.handlers import TimedRotatingFileHandler

import configparser
import random
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import itertools


# Check if the OS is Linux and set the maximum number of open files to 128000
if os.name == 'posix':
print('os is linux')
import resource # ( -> pip install python-resources )
# set linux max_num_open_socket from 1024 to 128k
import resource
resource.setrlimit(resource.RLIMIT_NOFILE, (127000, 128000))

# Function to send data in fragments
def send_data_in_fragment(data, sock):
for i in range(0, len(data), L_fragment):
fragment_data = data[i:i+L_fragment]
logging.debug(f'[SEND] {len(fragment_data)} bytes')
sock.sendall(fragment_data)
time.sleep(fragment_sleep)
logging.debug('[SEND] ----------finish------------')


listen_PORT = 2500 # pyprox listening to 127.0.0.1:listen_PORT

Cloudflare_IP = '162.159.135.42' # plos.org (can be any dirty cloudflare ip)
Cloudflare_port = 443

L_fragment = 77 # length of fragments of Client Hello packet (L_fragment Byte in each chunk)
fragment_sleep = 0.2 # sleep between each fragment to make GFW-cache full so it forget previous chunks. LOL.



# ignore description below , its for old code , just leave it intact.
my_socket_timeout = 60 # default for google is ~21 sec , recommend 60 sec unless you have low ram and need close soon
first_time_sleep = 0.01 # speed control , avoid server crash if huge number of users flooding (default 0.1)
accept_time_sleep = 0.01 # avoid server crash on flooding request -> max 100 sockets per second



class ThreadedServer(object):
def __init__(self, host, port):
self.host = host
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind((self.host, self.port))

def listen(self):
self.sock.listen(128) # up to 128 concurrent unaccepted socket queued , the more is refused untill accepting those.
while True:
client_sock , client_addr = self.sock.accept()
client_sock.settimeout(my_socket_timeout)

#print('someone connected')
time.sleep(accept_time_sleep) # avoid server crash on flooding request
thread_up = threading.Thread(target = self.my_upstream , args =(client_sock,) )
thread_up.daemon = True #avoid memory leak by telling os its belong to main program , its not a separate program , so gc collect it when thread finish
thread_up.start()


def my_upstream(self, client_sock):
first_flag = True
backend_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Function to handle upstream traffic from the client to the backend server
def my_upstream(client_sock):
first_flag = True
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as backend_sock:
backend_sock.settimeout(my_socket_timeout)
while True:
try:
if( first_flag == True ):
if first_flag:
first_flag = False

time.sleep(first_time_sleep) # speed control + waiting for packet to fully recieve
time.sleep(first_time_sleep)
data = client_sock.recv(16384)
#print('len data -> ',str(len(data)))
#print('user talk :')

if data:
backend_sock.connect((Cloudflare_IP,Cloudflare_port))
thread_down = threading.Thread(target = self.my_downstream , args = (backend_sock , client_sock) )
if data:
backend_ip = get_next_backend_ip()
print(f'Using backend IP: {backend_ip}') # Print the selected backend IP
backend_sock.connect((backend_ip, Cloudflare_port))
thread_down = threading.Thread(target=my_downstream, args=(backend_sock, client_sock))
thread_down.daemon = True
thread_down.start()
# backend_sock.sendall(data)
send_data_in_fragment(data,backend_sock)

else:
send_data_in_fragment(data, backend_sock)
else:
raise Exception('cli syn close')

else:
data = client_sock.recv(4096)
if data:
backend_sock.sendall(data)
else:
raise Exception('cli pipe close')

except Exception as e:
#print('upstream : '+ repr(e) )
time.sleep(2) # wait two second for another thread to flush
logging.debug(f'[UPSTREAM] {repr(e)}')
time.sleep(2)
client_sock.close()
backend_sock.close()
return False




def my_downstream(self, backend_sock , client_sock):
first_flag = True
while True:
try:
if( first_flag == True ):
first_flag = False
data = backend_sock.recv(16384)
if data:
client_sock.sendall(data)
else:
raise Exception('backend pipe close at first')

# Function to handle downstream traffic from the backend server to the client
def my_downstream(backend_sock, client_sock):
first_flag = True
while True:
try:
if first_flag:
first_flag = False
data = backend_sock.recv(16384)
if data:
client_sock.sendall(data)
else:
data = backend_sock.recv(4096)
if data:
client_sock.sendall(data)
else:
raise Exception('backend pipe close')

except Exception as e:
#print('downstream '+backend_name +' : '+ repr(e))
time.sleep(2) # wait two second for another thread to flush
backend_sock.close()
client_sock.close()
return False


def send_data_in_fragment(data , sock):

for i in range(0, len(data), L_fragment):
fragment_data = data[i:i+L_fragment]
print('send ',len(fragment_data),' bytes')

# sock.send(fragment_data)
sock.sendall(fragment_data)

time.sleep(fragment_sleep)

print('----------finish------------')


print ("Now listening at: 127.0.0.1:"+str(listen_PORT))
ThreadedServer('',listen_PORT).listen()




raise Exception('backend pipe close at first')
else:
data = backend_sock.recv(4096)
if data:
client_sock.sendall(data)
else:
raise Exception('backend pipe close')
except Exception as e:
logging.debug(f'[DOWNSTREAM] {repr(e)}')
time.sleep(2)
client_sock.close()
return False

# Function to parse command line arguments
def parse_args():
parser = argparse.ArgumentParser(description='Python Proxy')
parser.add_argument('--config', type=str, default='config.ini', help='Path to the configuration file')
return parser.parse_args()

# Function to load configuration from a file
def load_config(config_path):
config = configparser.ConfigParser()
config.read(config_path)

global listen_PORT, Cloudflare_IPs, Cloudflare_port, L_fragment, fragment_sleep, my_socket_timeout, first_time_sleep, accept_time_sleep
listen_PORT = int(config.get('settings', 'listen_PORT'))
Cloudflare_IPs = [ip.strip() for ip in config.get('settings', 'Cloudflare_IP').split(',')]
Cloudflare_port = int(config.get('settings', 'Cloudflare_port'))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cloudflare_IPs = itertools.cycle(Cloudflare_IPs)

L_fragment = int(config.get('settings', 'L_fragment'))
fragment_sleep = float(config.get('settings', 'fragment_sleep'))
my_socket_timeout = int(config.get('settings', 'my_socket_timeout'))
first_time_sleep = float(config.get('settings', 'first_time_sleep'))
accept_time_sleep = float(config.get('settings', 'accept_time_sleep'))

# Function to get the next backend IP using round-robin load balancing
def get_next_backend_ip():
global Cloudflare_IPs
selected_ip = random.choice(Cloudflare_IPs)
Cloudflare_IPs = Cloudflare_IPs[1:] + [selected_ip]
return selected_ip
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def get_next_backend_ip():
global Cloudflare_IPs
return next(Cloudflare_IPs)


# Main function to start the proxy server
def main():
args = parse_args()
load_config(args.config)

print(f'Proxy server listening on 127.0.0.1:{listen_PORT}')


with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_sock:
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind(('', listen_PORT))
server_sock.listen(128)

with ThreadPoolExecutor(max_workers=128) as executor:
while True:
client_sock, client_addr = server_sock.accept()
client_sock.settimeout(my_socket_timeout)
time.sleep(accept_time_sleep)
executor.submit(my_upstream, client_sock)

if __name__ == "__main__":
main()