forked from rafi1212122/AscNet
-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathproxy.py
More file actions
215 lines (164 loc) · 7.28 KB
/
Copy pathproxy.py
File metadata and controls
215 lines (164 loc) · 7.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
import os
from urllib.parse import parse_qsl, urlencode, urlparse, urlunparse
from mitmproxy import http
from mitmproxy import ctx
from mitmproxy.proxy import layer
def load(loader):
# ctx.options.web_open_browser = False
# We change the connection strategy to lazy so that next_layer happens before we actually connect upstream.
ctx.options.connection_strategy = "lazy"
ctx.options.upstream_cert = False
ctx.options.ssl_insecure = True
ctx.options.ignore_hosts = [
r".*sdk-prod-cdn-aws\.kurogame-service\.(com|xyz).*",
r".*qcloud-sg-datareceiver\.kurogame\.xyz.*",
r".*mp-gb-sdklog\.kurogames\.net.*",
r".*events\.appsflyer\.com.*",
r"pgr\.kurogame\.net:443",
]
def _normalise_connect_host(host):
if host in {None, "", "*", "0.0.0.0", "::", "[::]"}:
return "127.0.0.1"
return host
def _is_local_wildcard_host(host):
return host in {"*", "0.0.0.0", "::", "[::]"}
def _ascnet_target():
raw_target = os.environ.get("ASCNET_PROXY_TARGET", "http://127.0.0.1:8080").strip()
if "://" not in raw_target:
raw_target = f"http://{raw_target}"
parsed = urlparse(raw_target)
scheme = parsed.scheme or "http"
host = _normalise_connect_host(parsed.hostname)
port = parsed.port or (443 if scheme == "https" else 80)
return scheme, host, port
def _flow_log_path():
return os.environ.get("ASCNET_PROXY_LOG")
def _redact_url(url):
parsed = urlparse(url)
query = []
for key, value in parse_qsl(parsed.query, keep_blank_values=True):
if key.lower() in {"token", "access_token", "accesstoken", "refresh_token", "code", "password", "pwd"}:
value = "<redacted>"
query.append((key, value))
return urlunparse(parsed._replace(query=urlencode(query)))
def _log_flow(prefix, flow):
path = _flow_log_path()
if not path:
return
status = getattr(flow.response, "status_code", "-") if getattr(flow, "response", None) else "-"
line = f"{prefix} {flow.request.method} {_redact_url(flow.request.pretty_url)} -> {status}\n"
with open(path, "a", encoding="utf-8") as handle:
handle.write(line)
def _is_ascnet_host(host):
return host and (
host in {"sdkapi.kurogame-service.com", "sdkapi.kurogame-service.xyz"}
or (host.startswith(("prod-encdn-", "prod-twcdn-")) and host.endswith(".kurogame.net"))
)
def _is_upstream_notice_html_request(flow):
path = flow.request.path.split("?", 1)[0]
return (
_is_ascnet_host(flow.request.pretty_host)
and path.startswith("/prod/client/notice/html/")
)
def _is_ascnet_gate_request(flow):
return flow.request.path.split("?", 1)[0] == "/api/Login/Login"
def _is_feedback_request(flow):
return flow.request.pretty_host in {"prod.enzspnslog.kurogame.com", "prod.twzspnslog.kurogame.com"} and flow.request.path.split("?", 1)[0] == "/feedback"
def _is_wildcard_connect_request(flow):
return flow.request.method == "CONNECT" and _is_local_wildcard_host(flow.request.pretty_host)
def _is_wildcard_ascnet_request(flow):
path = flow.request.path.split("?", 1)[0]
return _is_local_wildcard_host(flow.request.pretty_host) and path.startswith(("/api/", "/prod/", "/sdkcom/"))
def _is_tw_config_request(flow):
host = flow.request.pretty_host
path = flow.request.path.split("?", 1)[0]
return (
host.startswith("prod-twcdn-")
and host.endswith(".kurogame.net")
and path.startswith("/prod/client/config/")
and path.endswith("/standalone/config.tab")
)
def _ascnet_origin():
scheme, host, port = _ascnet_target()
if port in (80, 443):
return f"{scheme}://{host}"
return f"{scheme}://{host}:{port}"
def _rewrite_login_url(value, target_origin):
# ServerListStr/ChannelServerListStr are `label#url` / `default#label#url`.
# Keep labels and metadata, replace only the final URL's origin with the
# local AscNet target while preserving its path.
head, sep, url = value.rpartition("#")
parsed = urlparse(url)
if not (sep and parsed.scheme and parsed.hostname):
return value
suffix = parsed.path + (f"?{parsed.query}" if parsed.query else "")
return head + sep + target_origin + suffix
def _rewrite_tw_config_body(body, target_origin):
lines = body.split("\n")
out = []
for line in lines:
cols = line.split("\t")
if len(cols) >= 3 and cols[0] in {"ServerListStr", "ChannelServerListStr"}:
cols[2] = _rewrite_login_url(cols[2], target_origin)
out.append("\t".join(cols))
return "\n".join(out)
def next_layer(nextlayer: layer.NextLayer):
# Only mark hosts we intend to rewrite. HTTPS proxying is intentionally
# avoided for pinned KRSDK/service hosts by the runner/environment.
sni = nextlayer.context.client.sni
if _is_ascnet_host(sni):
ctx.log.info("ascnet candidate sni:" + sni)
def http_connect(flow: http.HTTPFlow) -> None:
_log_flow("CONNECT", flow)
if not _is_wildcard_connect_request(flow):
return
flow.response = http.Response.make(
502,
b"AscNet blocked invalid CONNECT target 0.0.0.0/::; restart with run_steam.py so local SDK URLs use 127.0.0.1.\n",
{"Content-Type": "text/plain"},
)
_log_flow("CONNECT-BLOCK", flow)
def request(flow: http.HTTPFlow) -> None:
_log_flow("REQ", flow)
if _is_feedback_request(flow):
flow.response = http.Response.make(200, b"OK", {"Content-Type": "text/plain"})
_log_flow("SINK", flow)
return
# Notice metadata points at version-specific CDN HTML files. Keep those
# requests on the original CDN so new notices work without local fixtures.
if _is_upstream_notice_html_request(flow):
_log_flow("PASS", flow)
return
# TW config carries authoritative upstream metadata (doc/launch version,
# channel, CDN list) that local AscNet does not reproduce. Let it pass
# through to the real CDN unchanged; response() rewrites only the login
# endpoints to the local target.
if _is_tw_config_request(flow):
_log_flow("PASS", flow)
return
if not (_is_ascnet_host(flow.request.pretty_host) or _is_ascnet_gate_request(flow) or _is_wildcard_ascnet_request(flow)):
return
scheme, host, port = _ascnet_target()
original_host = flow.request.host
original_scheme = flow.request.scheme
flow.request.scheme = scheme
flow.request.host = host
flow.request.port = port
flow.request.headers["Host"] = host if port in (80, 443) else f"{host}:{port}"
flow.request.headers["X-Forwarded-Host"] = original_host
flow.request.headers["X-Forwarded-Proto"] = original_scheme
def response(flow: http.HTTPFlow) -> None:
_log_flow("RSP", flow)
# TW config was passed through upstream unchanged. Rewrite only the login
# endpoint URLs to the local target so the client reaches local AscNet,
# keeping all authoritative metadata (version, channel, CDNs, labels).
if not _is_tw_config_request(flow) or flow.response is None:
return
body = flow.response.content
if not body:
return
text = body.decode("utf-8", errors="replace")
rewritten = _rewrite_tw_config_body(text, _ascnet_origin())
if rewritten != text:
flow.response.content = rewritten.encode("utf-8")
_log_flow("TW-CONFIG-REWRITE", flow)