Skip to content

Commit

Permalink
5.10.5 Socks5 accept UDP proxy for DNS.
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael-X-Net committed May 15, 2024
1 parent 03facc2 commit 91a121f
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 48 deletions.
5 changes: 4 additions & 1 deletion code/default/launcher/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@
config.set_var("global_proxy_username", "")
config.set_var("global_proxy_password", "")

config.load()
try:
config.load()
except Exception as e:
xlog.warn("loading config e:%r", e)

app_name = "XX-Net"
valid_language = ['en_US', 'fa_IR', 'zh_CN', 'ru_RU']
Expand Down
4 changes: 2 additions & 2 deletions code/default/lib/noarch/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ def check_domain_valid(hostname):


def str2hex(data):
data = to_str(data)
return ":".join("{:02x}".format(ord(c)) for c in data)
data = to_bytes(data)
return data.hex(':')


def get_ip_maskc(ip_str):
Expand Down
6 changes: 3 additions & 3 deletions code/default/lib/noarch/xconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def check_change(self):
def load(self):
self.last_load_time = time.time()
if os.path.isfile(self.config_path):
with open(self.config_path, 'r') as f:
with open(self.config_path, 'r', encoding='utf-8') as f:
content = f.read()
content = content.strip()
content = content.replace("\r", "")
Expand All @@ -51,8 +51,8 @@ def save(self):
else:
self.file_config[var_name] = getattr(self, var_name)

with open(self.config_path, "w") as f:
f.write(json.dumps(self.file_config, indent=2))
with open(self.config_path, "w", encoding='utf-8') as f:
f.write(json.dumps(self.file_config, indent=2, ensure_ascii=False))

def set_var(self, var_name, default_value):
self.default_config[var_name] = default_value
46 changes: 27 additions & 19 deletions code/default/lib/noarch/xlog.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
DEBUG = 10
NOTSET = 0

# full_log set by server, upload full log for debug (maybe next time start session), remove old log file on reset log
full_log = False

# keep log set by UI, keep all logs, never delete old log, also upload log to server.


class Logger():
def __init__(self, name, buffer_size=0, file_name=None, roll_num=1,
Expand All @@ -45,8 +50,8 @@ def __init__(self, name, buffer_size=0, file_name=None, roll_num=1,
if log_path and save_start_log:
now = datetime.now()
time_str = now.strftime("%Y-%m-%d_%H-%M-%S")
log_fn = os.path.join(log_path, "start_log_%s_%s.log" % (name, time_str))
self.start_log = open(log_fn, "w")
self.log_fn = os.path.join(log_path, "start_log_%s_%s.log" % (name, time_str))
self.start_log = open(self.log_fn, "w")
else:
self.start_log = None

Expand Down Expand Up @@ -75,33 +80,32 @@ def set_buffer(self, buffer_size):
pass

def reset_log_files(self):
if self.keep_log:
return

if self.start_log:
self.start_log.close()
self.start_log = None
if not (self.keep_log or full_log):
if self.start_log:
self.start_log.close()
self.start_log = None

if self.warning_log:
self.warning_log.close()
self.warning_log = None
if self.warning_log:
self.warning_log.close()
self.warning_log = None

if self.log_path:
if self.log_path and not self.keep_log:
for filename in os.listdir(self.log_path):
if not filename.endswith(".log"):
fp = os.path.join(self.log_path, filename)
if not filename.endswith(".log") or fp == self.log_fn or not filename.startswith("start_log_%s" % self.name):
continue

fp = os.path.join(self.log_path, filename)
try:
os.remove(fp)
except:
pass

if self.warning_log_fn:
if self.warning_log_fn and not self.keep_log:
self.warning_log = open(self.warning_log_fn, "a")

def keep_logs(self):
self.keep_log = True
# self.debug("keep log for %s", self.name)
if not self.log_path:
return

Expand Down Expand Up @@ -217,7 +221,7 @@ def log(self, level, console_color, html_color, fmt, *args, **kwargs):
pass
self.start_log_num += 1

if self.start_log_num > self.save_start_log and not self.keep_log:
if self.start_log_num > self.save_start_log and not self.keep_log and not full_log:
self.start_log.close()
self.start_log = None

Expand Down Expand Up @@ -357,9 +361,13 @@ def reset_log_files():
log.reset_log_files()


def keep_log():
for name, log in loggerDict.items():
log.keep_logs()
def keep_log(temp=False):
global full_log
if temp:
full_log = True
else:
for name, log in loggerDict.items():
log.keep_logs(temp)


default_log = getLogger()
Expand Down
1 change: 1 addition & 0 deletions code/default/smart_router/local/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def load_config():
config.set_var("dns_bind_ip", "127.0.0.1")
config.set_var("dns_port", 53)
config.set_var("dns_backup_port", 8053)
config.set_var("udp_relay_port", 8086)

config.set_var("proxy_bind_ip", "127.0.0.1")
config.set_var("proxy_port", 8086)
Expand Down
81 changes: 78 additions & 3 deletions code/default/smart_router/local/dns_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import socket
import time
import select
import struct

current_path = os.path.dirname(os.path.abspath(__file__))
root_path = os.path.abspath(os.path.join(current_path, os.pardir, os.pardir))
Expand All @@ -30,6 +31,8 @@
class DnsServer(object):
def __init__(self, bind_ip="127.0.0.1", port=53, backup_port=8053, ttl=24*3600):
self.sockets = []
self.udp_relay_sock = None
self.udp_relay_port = 0
self.listen_port = port
self.running = False
if isinstance(bind_ip, str):
Expand All @@ -51,6 +54,7 @@ def init_socket(self):
listen_all_v4 and '.' in ip or
listen_all_v6 and ':' in ip):
continue
self.bing_udp_relay(ip)
self.bing_listen(ip)

def bing_listen(self, bind_ip):
Expand Down Expand Up @@ -84,7 +88,25 @@ def bing_listen(self, bind_ip):
xlog.warn("Then: sudo setcap 'cap_net_bind_service=+ep' /usr/bin/python2.7")
xlog.warn("Or run as root")

def on_udp_query(self, rsock, req_data, addr):
def bing_udp_relay(self, bind_ip):
if ":" in bind_ip:
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
else:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

port = g.config.udp_relay_port
for port in range(port, port + 20):
try:
sock.bind((bind_ip, port))
xlog.info("start UDP relay server at %s:%d", bind_ip, port)
self.sockets.append(sock)
self.udp_relay_sock = sock
self.udp_relay_port = port
return
except:
xlog.warn("bind UDP %s:%d fail", bind_ip, self.port)

def dns_query(self, req_data, addr):
start_time = time.time()
try:
request = DNSRecord.parse(req_data)
Expand Down Expand Up @@ -118,12 +140,62 @@ def on_udp_query(self, rsock, req_data, addr):
reply.add_answer(RR(domain, rtype=dns_type, ttl=60, rdata=NS(ip)))
res_data = reply.pack()

rsock.sendto(res_data, addr)
xlog.debug("query:%s type:%d from:%s, return ip num:%d cost:%d", domain, dns_type, addr,
len(reply.rr), (time.time()-start_time)*1000)
return res_data
except Exception as e:
xlog.exception("on_query except:%r", e)

def on_udp_query(self, rsock, req_data, addr):
res_data = self.dns_query(req_data, addr)
rsock.sendto(res_data, addr)

def on_udp_relay(self, rsock, req_data, from_addr):
# We currently only support DNS query for UDP relay

# SOCKS5 UDP forward request
# reserved, frag, addr_type, domain_len, domain, port, data
try:
reserved = struct.unpack(">H", req_data[0:2])[0]
frag = ord(req_data[2:3])
if reserved != 0 or frag != 0:
xlog.warn("reserved:%d frag:%d", reserved, frag)
return

addr_type = ord(req_data[3:4])
if addr_type == 1: # IPv4
addr_pack = req_data[4:8]
addr = socket.inet_ntoa(addr_pack)
port = struct.unpack(">H", req_data[8:10])[0]
data = req_data[10:]
elif addr_type == 3: # Domain name
domain_len_pack = req_data[4:5]
domain_len = ord(domain_len_pack)
domain = req_data[5:5 + domain_len]
addr = domain
port = struct.unpack(">H", req_data[5 + domain_len:5 + domain_len + 2])[0]
data = req_data[5 + domain_len + 2:]
elif addr_type == 4: # IPv6
addr_pack = req_data[4:20]
addr = socket.inet_ntop(socket.AF_INET6, addr_pack)
port = struct.unpack(">H", req_data[20:22])[0]
data = req_data[22:]
else:
xlog.warn("request address type unknown:%d", addr_type)
return

xlog.debug("UDP relay from %s size:%d to:%s:%d", from_addr, len(data), addr, port)
head_length = len(req_data) - len(data)
head = req_data[:head_length]
res_data = self.dns_query(data, from_addr)
if not res_data:
return

rsock.sendto(head + res_data, from_addr)
xlog.debug("UDP relay from %s size:%d to:%s:%d res len:%d", from_addr, len(data), addr, port, len(res_data))
except Exception as e:
xlog.exception("on_udp_relay data:[%s] except:%r", utils.str2hex(req_data), e)

def server_forever(self):
while self.running:
r, w, e = select.select(self.sockets, [], [], 1)
Expand All @@ -137,7 +209,10 @@ def server_forever(self):
xlog.warn("recv except: %r", e)
break

threading.Thread(target=self.on_udp_query, args=(rsock, data, addr), name="DNSServer_udp_handler").start()
if rsock == self.udp_relay_sock:
threading.Thread(target=self.on_udp_relay, args=(rsock, data, addr), name="UDP_relay").start()
else:
threading.Thread(target=self.on_udp_query, args=(rsock, data, addr), name="DNSServer_udp_handler").start()

self.th = None

Expand Down
23 changes: 17 additions & 6 deletions code/default/smart_router/local/proxy_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,15 @@ def socks4_handler(self):
else:
handle_ip_proxy(sock, addr, port, self.client_address)

def handle_udp_associate(self, sock, addr, port, addrtype_pack, addr_pack):
udp_relay_port = g.dns_srv.udp_relay_port
xlog.debug("socks5 from:%r udp associate to %s:%d use udp_relay_port:%d", self.client_address, addr, port, udp_relay_port)
reply = b"\x05\x00\x00" + addrtype_pack + addr_pack + struct.pack(">H", udp_relay_port)
sock.send(reply)

self.rfile.read(1)
xlog.debug("socks5 from:%r udp associate to %s:%d closed", self.client_address, addr, port)

def socks5_handler(self):
sock = self.conn
socks_version = ord(self.read_bytes(1))
Expand All @@ -253,11 +262,6 @@ def socks5_handler(self):
return

command = ord(data[1:2])
if command != 1: # 1. Tcp connect
xlog.warn("request not supported command mode:%d", command)
sock.send(b"\x05\x07\x00\x01") # Command not supported
return

addrtype_pack = data[3:4]
addrtype = ord(addrtype_pack)
if addrtype == 1: # IPv4
Expand All @@ -276,9 +280,16 @@ def socks5_handler(self):
xlog.warn("request address type unknown:%d", addrtype)
sock.send(b"\x05\x07\x00\x01") # Command not supported
return

port = struct.unpack('>H', self.rfile.read(2))[0]

if command == 3: # 3. UDP associate
return self.handle_udp_associate(sock, addr, port, addrtype_pack, addr_pack)

if command != 1: # 1. Tcp connect
xlog.warn("request not supported command mode:%d", command)
sock.send(b"\x05\x07\x00\x01") # Command not supported
return

# xlog.debug("socks5 %r connect to %s:%d", self.client_address, addr, port)
reply = b"\x05\x00\x00" + addrtype_pack + addr_pack + struct.pack(">H", port)
sock.send(reply)
Expand Down
2 changes: 1 addition & 1 deletion code/default/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5.9.10
5.10.5
2 changes: 2 additions & 0 deletions code/default/x_tunnel/local/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ def load_config():
config.set_var("write_log_file", 0)
config.set_var("save_start_log", 1500)
config.set_var("show_debug", 0)
config.set_var("delay_collect_log", 3 * 60)
config.set_var("delay_collect_log2", 30)

config.set_var("encrypt_data", 0)
config.set_var("encrypt_password", "encrypt_pass")
Expand Down
13 changes: 11 additions & 2 deletions code/default/x_tunnel/local/proxy_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import xstruct as struct
import hashlib

from xlog import getLogger
from xlog import getLogger, keep_log
xlog = getLogger("x_tunnel")

import utils
Expand Down Expand Up @@ -422,6 +422,15 @@ def login_session(self):
xlog.warn("login_session time:%d fail, res:%d msg:%s", 1000 * time_cost, res, message)
return False

try:
msg_info = json.loads(message)
if msg_info.get("full_log"):
xlog.debug("keep full log")
keep_log(temp=True)
except Exception as e:
xlog.warn("login_session %s json error:%r", message, e)
msg_info = {}

g.last_api_error = ""
xlog.info("login_session %s time:%d msg:%s", self.session_id, 1000 * time_cost, message)
return True
Expand Down Expand Up @@ -581,7 +590,7 @@ def get_send_data(self, work_id):
self.wait_queue.wait(work_id)

xlog.debug("get_send_data on stop")
return "", ""
return b"", b""

def ack_process(self, ack):
self.lock.acquire()
Expand Down
Loading

0 comments on commit 91a121f

Please sign in to comment.