Skip to content

Commit 91a121f

Browse files
committed
5.10.5 Socks5 accept UDP proxy for DNS.
1 parent 03facc2 commit 91a121f

File tree

12 files changed

+158
-48
lines changed

12 files changed

+158
-48
lines changed

code/default/launcher/config.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@
8282
config.set_var("global_proxy_username", "")
8383
config.set_var("global_proxy_password", "")
8484

85-
config.load()
85+
try:
86+
config.load()
87+
except Exception as e:
88+
xlog.warn("loading config e:%r", e)
8689

8790
app_name = "XX-Net"
8891
valid_language = ['en_US', 'fa_IR', 'zh_CN', 'ru_RU']

code/default/lib/noarch/utils.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ def check_domain_valid(hostname):
117117

118118

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

123123

124124
def get_ip_maskc(ip_str):

code/default/lib/noarch/xconfig.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def check_change(self):
2424
def load(self):
2525
self.last_load_time = time.time()
2626
if os.path.isfile(self.config_path):
27-
with open(self.config_path, 'r') as f:
27+
with open(self.config_path, 'r', encoding='utf-8') as f:
2828
content = f.read()
2929
content = content.strip()
3030
content = content.replace("\r", "")
@@ -51,8 +51,8 @@ def save(self):
5151
else:
5252
self.file_config[var_name] = getattr(self, var_name)
5353

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

5757
def set_var(self, var_name, default_value):
5858
self.default_config[var_name] = default_value

code/default/lib/noarch/xlog.py

+27-19
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
DEBUG = 10
2222
NOTSET = 0
2323

24+
# full_log set by server, upload full log for debug (maybe next time start session), remove old log file on reset log
25+
full_log = False
26+
27+
# keep log set by UI, keep all logs, never delete old log, also upload log to server.
28+
2429

2530
class Logger():
2631
def __init__(self, name, buffer_size=0, file_name=None, roll_num=1,
@@ -45,8 +50,8 @@ def __init__(self, name, buffer_size=0, file_name=None, roll_num=1,
4550
if log_path and save_start_log:
4651
now = datetime.now()
4752
time_str = now.strftime("%Y-%m-%d_%H-%M-%S")
48-
log_fn = os.path.join(log_path, "start_log_%s_%s.log" % (name, time_str))
49-
self.start_log = open(log_fn, "w")
53+
self.log_fn = os.path.join(log_path, "start_log_%s_%s.log" % (name, time_str))
54+
self.start_log = open(self.log_fn, "w")
5055
else:
5156
self.start_log = None
5257

@@ -75,33 +80,32 @@ def set_buffer(self, buffer_size):
7580
pass
7681

7782
def reset_log_files(self):
78-
if self.keep_log:
79-
return
80-
81-
if self.start_log:
82-
self.start_log.close()
83-
self.start_log = None
83+
if not (self.keep_log or full_log):
84+
if self.start_log:
85+
self.start_log.close()
86+
self.start_log = None
8487

85-
if self.warning_log:
86-
self.warning_log.close()
87-
self.warning_log = None
88+
if self.warning_log:
89+
self.warning_log.close()
90+
self.warning_log = None
8891

89-
if self.log_path:
92+
if self.log_path and not self.keep_log:
9093
for filename in os.listdir(self.log_path):
91-
if not filename.endswith(".log"):
94+
fp = os.path.join(self.log_path, filename)
95+
if not filename.endswith(".log") or fp == self.log_fn or not filename.startswith("start_log_%s" % self.name):
9296
continue
9397

94-
fp = os.path.join(self.log_path, filename)
9598
try:
9699
os.remove(fp)
97100
except:
98101
pass
99102

100-
if self.warning_log_fn:
103+
if self.warning_log_fn and not self.keep_log:
101104
self.warning_log = open(self.warning_log_fn, "a")
102105

103106
def keep_logs(self):
104107
self.keep_log = True
108+
# self.debug("keep log for %s", self.name)
105109
if not self.log_path:
106110
return
107111

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

220-
if self.start_log_num > self.save_start_log and not self.keep_log:
224+
if self.start_log_num > self.save_start_log and not self.keep_log and not full_log:
221225
self.start_log.close()
222226
self.start_log = None
223227

@@ -357,9 +361,13 @@ def reset_log_files():
357361
log.reset_log_files()
358362

359363

360-
def keep_log():
361-
for name, log in loggerDict.items():
362-
log.keep_logs()
364+
def keep_log(temp=False):
365+
global full_log
366+
if temp:
367+
full_log = True
368+
else:
369+
for name, log in loggerDict.items():
370+
log.keep_logs(temp)
363371

364372

365373
default_log = getLogger()

code/default/smart_router/local/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def load_config():
6868
config.set_var("dns_bind_ip", "127.0.0.1")
6969
config.set_var("dns_port", 53)
7070
config.set_var("dns_backup_port", 8053)
71+
config.set_var("udp_relay_port", 8086)
7172

7273
config.set_var("proxy_bind_ip", "127.0.0.1")
7374
config.set_var("proxy_port", 8086)

code/default/smart_router/local/dns_server.py

+78-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import socket
88
import time
99
import select
10+
import struct
1011

1112
current_path = os.path.dirname(os.path.abspath(__file__))
1213
root_path = os.path.abspath(os.path.join(current_path, os.pardir, os.pardir))
@@ -30,6 +31,8 @@
3031
class DnsServer(object):
3132
def __init__(self, bind_ip="127.0.0.1", port=53, backup_port=8053, ttl=24*3600):
3233
self.sockets = []
34+
self.udp_relay_sock = None
35+
self.udp_relay_port = 0
3336
self.listen_port = port
3437
self.running = False
3538
if isinstance(bind_ip, str):
@@ -51,6 +54,7 @@ def init_socket(self):
5154
listen_all_v4 and '.' in ip or
5255
listen_all_v6 and ':' in ip):
5356
continue
57+
self.bing_udp_relay(ip)
5458
self.bing_listen(ip)
5559

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

87-
def on_udp_query(self, rsock, req_data, addr):
91+
def bing_udp_relay(self, bind_ip):
92+
if ":" in bind_ip:
93+
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
94+
else:
95+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
96+
97+
port = g.config.udp_relay_port
98+
for port in range(port, port + 20):
99+
try:
100+
sock.bind((bind_ip, port))
101+
xlog.info("start UDP relay server at %s:%d", bind_ip, port)
102+
self.sockets.append(sock)
103+
self.udp_relay_sock = sock
104+
self.udp_relay_port = port
105+
return
106+
except:
107+
xlog.warn("bind UDP %s:%d fail", bind_ip, self.port)
108+
109+
def dns_query(self, req_data, addr):
88110
start_time = time.time()
89111
try:
90112
request = DNSRecord.parse(req_data)
@@ -118,12 +140,62 @@ def on_udp_query(self, rsock, req_data, addr):
118140
reply.add_answer(RR(domain, rtype=dns_type, ttl=60, rdata=NS(ip)))
119141
res_data = reply.pack()
120142

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

149+
def on_udp_query(self, rsock, req_data, addr):
150+
res_data = self.dns_query(req_data, addr)
151+
rsock.sendto(res_data, addr)
152+
153+
def on_udp_relay(self, rsock, req_data, from_addr):
154+
# We currently only support DNS query for UDP relay
155+
156+
# SOCKS5 UDP forward request
157+
# reserved, frag, addr_type, domain_len, domain, port, data
158+
try:
159+
reserved = struct.unpack(">H", req_data[0:2])[0]
160+
frag = ord(req_data[2:3])
161+
if reserved != 0 or frag != 0:
162+
xlog.warn("reserved:%d frag:%d", reserved, frag)
163+
return
164+
165+
addr_type = ord(req_data[3:4])
166+
if addr_type == 1: # IPv4
167+
addr_pack = req_data[4:8]
168+
addr = socket.inet_ntoa(addr_pack)
169+
port = struct.unpack(">H", req_data[8:10])[0]
170+
data = req_data[10:]
171+
elif addr_type == 3: # Domain name
172+
domain_len_pack = req_data[4:5]
173+
domain_len = ord(domain_len_pack)
174+
domain = req_data[5:5 + domain_len]
175+
addr = domain
176+
port = struct.unpack(">H", req_data[5 + domain_len:5 + domain_len + 2])[0]
177+
data = req_data[5 + domain_len + 2:]
178+
elif addr_type == 4: # IPv6
179+
addr_pack = req_data[4:20]
180+
addr = socket.inet_ntop(socket.AF_INET6, addr_pack)
181+
port = struct.unpack(">H", req_data[20:22])[0]
182+
data = req_data[22:]
183+
else:
184+
xlog.warn("request address type unknown:%d", addr_type)
185+
return
186+
187+
xlog.debug("UDP relay from %s size:%d to:%s:%d", from_addr, len(data), addr, port)
188+
head_length = len(req_data) - len(data)
189+
head = req_data[:head_length]
190+
res_data = self.dns_query(data, from_addr)
191+
if not res_data:
192+
return
193+
194+
rsock.sendto(head + res_data, from_addr)
195+
xlog.debug("UDP relay from %s size:%d to:%s:%d res len:%d", from_addr, len(data), addr, port, len(res_data))
196+
except Exception as e:
197+
xlog.exception("on_udp_relay data:[%s] except:%r", utils.str2hex(req_data), e)
198+
127199
def server_forever(self):
128200
while self.running:
129201
r, w, e = select.select(self.sockets, [], [], 1)
@@ -137,7 +209,10 @@ def server_forever(self):
137209
xlog.warn("recv except: %r", e)
138210
break
139211

140-
threading.Thread(target=self.on_udp_query, args=(rsock, data, addr), name="DNSServer_udp_handler").start()
212+
if rsock == self.udp_relay_sock:
213+
threading.Thread(target=self.on_udp_relay, args=(rsock, data, addr), name="UDP_relay").start()
214+
else:
215+
threading.Thread(target=self.on_udp_query, args=(rsock, data, addr), name="DNSServer_udp_handler").start()
141216

142217
self.th = None
143218

code/default/smart_router/local/proxy_handler.py

+17-6
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,15 @@ def socks4_handler(self):
233233
else:
234234
handle_ip_proxy(sock, addr, port, self.client_address)
235235

236+
def handle_udp_associate(self, sock, addr, port, addrtype_pack, addr_pack):
237+
udp_relay_port = g.dns_srv.udp_relay_port
238+
xlog.debug("socks5 from:%r udp associate to %s:%d use udp_relay_port:%d", self.client_address, addr, port, udp_relay_port)
239+
reply = b"\x05\x00\x00" + addrtype_pack + addr_pack + struct.pack(">H", udp_relay_port)
240+
sock.send(reply)
241+
242+
self.rfile.read(1)
243+
xlog.debug("socks5 from:%r udp associate to %s:%d closed", self.client_address, addr, port)
244+
236245
def socks5_handler(self):
237246
sock = self.conn
238247
socks_version = ord(self.read_bytes(1))
@@ -253,11 +262,6 @@ def socks5_handler(self):
253262
return
254263

255264
command = ord(data[1:2])
256-
if command != 1: # 1. Tcp connect
257-
xlog.warn("request not supported command mode:%d", command)
258-
sock.send(b"\x05\x07\x00\x01") # Command not supported
259-
return
260-
261265
addrtype_pack = data[3:4]
262266
addrtype = ord(addrtype_pack)
263267
if addrtype == 1: # IPv4
@@ -276,9 +280,16 @@ def socks5_handler(self):
276280
xlog.warn("request address type unknown:%d", addrtype)
277281
sock.send(b"\x05\x07\x00\x01") # Command not supported
278282
return
279-
280283
port = struct.unpack('>H', self.rfile.read(2))[0]
281284

285+
if command == 3: # 3. UDP associate
286+
return self.handle_udp_associate(sock, addr, port, addrtype_pack, addr_pack)
287+
288+
if command != 1: # 1. Tcp connect
289+
xlog.warn("request not supported command mode:%d", command)
290+
sock.send(b"\x05\x07\x00\x01") # Command not supported
291+
return
292+
282293
# xlog.debug("socks5 %r connect to %s:%d", self.client_address, addr, port)
283294
reply = b"\x05\x00\x00" + addrtype_pack + addr_pack + struct.pack(">H", port)
284295
sock.send(reply)

code/default/version.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
5.9.10
1+
5.10.5

code/default/x_tunnel/local/config.py

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ def load_config():
2525
config.set_var("write_log_file", 0)
2626
config.set_var("save_start_log", 1500)
2727
config.set_var("show_debug", 0)
28+
config.set_var("delay_collect_log", 3 * 60)
29+
config.set_var("delay_collect_log2", 30)
2830

2931
config.set_var("encrypt_data", 0)
3032
config.set_var("encrypt_password", "encrypt_pass")

code/default/x_tunnel/local/proxy_session.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import xstruct as struct
66
import hashlib
77

8-
from xlog import getLogger
8+
from xlog import getLogger, keep_log
99
xlog = getLogger("x_tunnel")
1010

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

425+
try:
426+
msg_info = json.loads(message)
427+
if msg_info.get("full_log"):
428+
xlog.debug("keep full log")
429+
keep_log(temp=True)
430+
except Exception as e:
431+
xlog.warn("login_session %s json error:%r", message, e)
432+
msg_info = {}
433+
425434
g.last_api_error = ""
426435
xlog.info("login_session %s time:%d msg:%s", self.session_id, 1000 * time_cost, message)
427436
return True
@@ -581,7 +590,7 @@ def get_send_data(self, work_id):
581590
self.wait_queue.wait(work_id)
582591

583592
xlog.debug("get_send_data on stop")
584-
return "", ""
593+
return b"", b""
585594

586595
def ack_process(self, ack):
587596
self.lock.acquire()

0 commit comments

Comments
 (0)