hiddifypanel 10.80.0.dev13__py3-none-any.whl → 10.80.0.dev15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hiddifypanel/VERSION +1 -1
- hiddifypanel/VERSION.py +1 -1
- hiddifypanel/hutils/proxy/__init__.py +1 -0
- hiddifypanel/hutils/proxy/clash.py +2 -2
- hiddifypanel/hutils/proxy/shared.py +6 -6
- hiddifypanel/hutils/proxy/singbox.py +1 -1
- hiddifypanel/hutils/proxy/wireguard.py +34 -0
- hiddifypanel/hutils/proxy/xray.py +2 -2
- hiddifypanel/hutils/proxy/xrayjson.py +4 -4
- hiddifypanel/models/config_enum.py +2 -2
- hiddifypanel/models/proxy.py +1 -1
- hiddifypanel/panel/admin/AdminstratorAdmin.py +7 -8
- hiddifypanel/panel/admin/DomainAdmin.py +131 -98
- hiddifypanel/panel/admin/QuickSetup.py +8 -8
- hiddifypanel/panel/admin/UserAdmin.py +58 -33
- hiddifypanel/panel/admin/templates/index.html +6 -4
- hiddifypanel/panel/admin/templates/model/user_list.html +11 -3
- hiddifypanel/panel/commercial/restapi/v1/tgbot.py +19 -1
- hiddifypanel/panel/common.py +1 -1
- hiddifypanel/panel/init_db.py +19 -16
- hiddifypanel/panel/user/user.py +6 -18
- hiddifypanel/translations.i18n/en.json +342 -1
- hiddifypanel/translations.i18n/fa.json +342 -1
- hiddifypanel/translations.i18n/fr.json +2 -2
- hiddifypanel/translations.i18n/my.json +2 -2
- hiddifypanel/translations.i18n/pt.json +342 -1
- hiddifypanel/translations.i18n/ru.json +342 -1
- hiddifypanel/translations.i18n/zh.json +342 -1
- {hiddifypanel-10.80.0.dev13.dist-info → hiddifypanel-10.80.0.dev15.dist-info}/METADATA +1 -1
- {hiddifypanel-10.80.0.dev13.dist-info → hiddifypanel-10.80.0.dev15.dist-info}/RECORD +33 -32
- {hiddifypanel-10.80.0.dev13.dist-info → hiddifypanel-10.80.0.dev15.dist-info}/LICENSE.md +0 -0
- {hiddifypanel-10.80.0.dev13.dist-info → hiddifypanel-10.80.0.dev15.dist-info}/WHEEL +0 -0
- {hiddifypanel-10.80.0.dev13.dist-info → hiddifypanel-10.80.0.dev15.dist-info}/entry_points.txt +0 -0
hiddifypanel/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
10.80.0.
|
1
|
+
10.80.0.dev15
|
hiddifypanel/VERSION.py
CHANGED
@@ -2,5 +2,5 @@ import importlib.metadata
|
|
2
2
|
from datetime import datetime
|
3
3
|
|
4
4
|
__version__ = importlib.metadata.version(__package__ or __name__)
|
5
|
-
__release_time__= datetime.strptime('2024-
|
5
|
+
__release_time__= datetime.strptime('2024-12-08T23:50:34','%Y-%m-%dT%H:%M:%S')
|
6
6
|
is_released_version=True
|
@@ -33,7 +33,7 @@ def to_clash(proxy, meta_or_normal):
|
|
33
33
|
|
34
34
|
if proxy['l3'] in ["kcp", ProxyL3.h3_quic]:
|
35
35
|
return {'name': name, 'msg': f"clash does not support {proxy['l3']}", 'type': 'debug'}
|
36
|
-
if proxy['transport'] in [ProxyTransport.
|
36
|
+
if proxy['transport'] in [ProxyTransport.xhttp, ProxyTransport.httpupgrade]:
|
37
37
|
return {'name': name, 'msg': f"clash does not support {proxy['transport']}", 'type': 'debug'}
|
38
38
|
# if proxy['proto'] in [Proxy.shado]:
|
39
39
|
|
@@ -44,7 +44,7 @@ def to_clash(proxy, meta_or_normal):
|
|
44
44
|
return {'name': name, 'msg': f"clash does not support {proxy['proto']}", 'type': 'debug'}
|
45
45
|
if proxy['proto'] in ["vless", 'tuic', 'hysteria2']:
|
46
46
|
return {'name': name, 'msg': f"{proxy['proto']} not supported in clash", 'type': 'debug'}
|
47
|
-
if proxy['transport'] in ["shadowtls", "
|
47
|
+
if proxy['transport'] in ["shadowtls", "xhttp"]:
|
48
48
|
return {'name': name, 'msg': f"{proxy['transport']} not supported in clash", 'type': 'debug'}
|
49
49
|
if proxy['l3'] == ProxyL3.tls_h2 and proxy['proto'] in [ProxyProto.vmess, ProxyProto.vless] and proxy['dbe'].cdn == ProxyCDN.direct:
|
50
50
|
return {'name': name, 'msg': "bug tls_h2 vmess and vless in clash meta", 'type': 'warning'}
|
@@ -134,8 +134,8 @@ def get_proxies(child_id: int = 0, only_enabled=False) -> list['Proxy']:
|
|
134
134
|
proxies = [c for c in proxies if 'trojan' not in c.proto]
|
135
135
|
if not hconfig(ConfigEnum.httpupgrade_enable, child_id):
|
136
136
|
proxies = [c for c in proxies if ProxyTransport.httpupgrade not in c.transport]
|
137
|
-
if not hconfig(ConfigEnum.
|
138
|
-
proxies = [c for c in proxies if ProxyTransport.
|
137
|
+
if not hconfig(ConfigEnum.xhttp_enable, child_id):
|
138
|
+
proxies = [c for c in proxies if ProxyTransport.xhttp not in c.transport]
|
139
139
|
if not hconfig(ConfigEnum.ws_enable, child_id):
|
140
140
|
proxies = [c for c in proxies if ProxyTransport.WS not in c.transport]
|
141
141
|
# if not hconfig(ConfigEnum.xtls_enable, child_id):
|
@@ -189,7 +189,7 @@ def get_valid_proxies(domains: list[Domain]) -> list[dict]:
|
|
189
189
|
noDomainProxies = False
|
190
190
|
if proxy.proto in [ProxyProto.ssh, ProxyProto.wireguard]:
|
191
191
|
noDomainProxies = True
|
192
|
-
if proxy.proto in [ProxyProto.ss] and proxy.transport not in [ProxyTransport.grpc, ProxyTransport.h2, ProxyTransport.WS, ProxyTransport.httpupgrade, ProxyTransport.
|
192
|
+
if proxy.proto in [ProxyProto.ss] and proxy.transport not in [ProxyTransport.grpc, ProxyTransport.h2, ProxyTransport.WS, ProxyTransport.httpupgrade, ProxyTransport.xhttp]:
|
193
193
|
noDomainProxies = True
|
194
194
|
options = []
|
195
195
|
key = f'{proxy.proto}{proxy.transport}{proxy.cdn}{proxy.l3}'
|
@@ -413,9 +413,9 @@ def make_proxy(hconfigs: dict, proxy: Proxy, domain_db: Domain, phttp=80, ptls=4
|
|
413
413
|
base['path'] = f'/{path[base["proto"]]}{hconfigs[ConfigEnum.path_httpupgrade]}'
|
414
414
|
base["host"] = domain
|
415
415
|
return base
|
416
|
-
if proxy.transport in [ProxyTransport.
|
417
|
-
base['transport'] = '
|
418
|
-
base['path'] = f'/{path[base["proto"]]}{hconfigs[ConfigEnum.
|
416
|
+
if proxy.transport in [ProxyTransport.xhttp]:
|
417
|
+
base['transport'] = 'xhttp'
|
418
|
+
base['path'] = f'/{path[base["proto"]]}{hconfigs[ConfigEnum.path_xhttp]}'
|
419
419
|
# if 0 and 'h2' in base['alpn'] or 'h3' in base['alpn']:
|
420
420
|
# base['path'] += "2"
|
421
421
|
# else:
|
@@ -47,7 +47,7 @@ def configs_as_json(domains: list[Domain], **kwargs) -> str:
|
|
47
47
|
def is_xray_proxy(proxy: dict):
|
48
48
|
if g.user_agent.get('is_hiddify_prefere_xray'):
|
49
49
|
return True
|
50
|
-
if proxy['transport'] == ProxyTransport.
|
50
|
+
if proxy['transport'] == ProxyTransport.xhttp:
|
51
51
|
return True
|
52
52
|
return False
|
53
53
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
def generate_wireguard_config(proxy: dict) -> str:
|
3
|
+
"""
|
4
|
+
Generates a WireGuard configuration from a given proxy dictionary.
|
5
|
+
|
6
|
+
Args:
|
7
|
+
proxy (dict): Dictionary containing WireGuard and proxy details.
|
8
|
+
|
9
|
+
Returns:
|
10
|
+
str: A WireGuard configuration string.
|
11
|
+
"""
|
12
|
+
name=f'{proxy["extra_info"]} {proxy["name"]}'
|
13
|
+
addrs = f"{proxy['wg_ipv4']}/32"
|
14
|
+
if proxy['wg_ipv6']:
|
15
|
+
addrs += f", {proxy['wg_ipv6']}/128"
|
16
|
+
config = f"""[Interface]
|
17
|
+
# Name = {name}
|
18
|
+
Address= {addrs}
|
19
|
+
PrivateKey = {proxy["wg_pk"]}
|
20
|
+
MTU = {proxy.get("mtu", 1380)}
|
21
|
+
DNS = {proxy.get("dns", "1.1.1.1")}
|
22
|
+
|
23
|
+
[Peer]
|
24
|
+
# Name = Public Peer for {name}
|
25
|
+
Endpoint = {proxy["server"]}:{proxy["port"]}
|
26
|
+
PublicKey = {proxy["wg_server_pub"]}
|
27
|
+
PresharedKey = {proxy['wg_psk']}
|
28
|
+
#PersistentKeepalive = {proxy.get("keep_alive", 25)}
|
29
|
+
"""
|
30
|
+
|
31
|
+
#Address = {proxy.get("wg_ipv4", "0.0.0.0/32")}
|
32
|
+
#AllowedIPs = {proxy.get("allowed_ips", "0.0.0.0/0")}
|
33
|
+
|
34
|
+
return config
|
@@ -126,10 +126,10 @@ def to_link(proxy: dict) -> str | dict:
|
|
126
126
|
baseurl += "&encryption=none"
|
127
127
|
if proxy.get('fingerprint', 'none') != 'none':
|
128
128
|
baseurl += "&fp=" + proxy['fingerprint']
|
129
|
-
if proxy.get('transport') in {ProxyTransport.
|
129
|
+
if proxy.get('transport') in {ProxyTransport.xhttp}:
|
130
130
|
baseurl += "&core=xray"
|
131
131
|
if proxy['l3'] != 'quic':
|
132
|
-
if proxy.get('l3') != ProxyL3.reality and (proxy.get('transport') in {ProxyTransport.tcp, ProxyTransport.httpupgrade, ProxyTransport.
|
132
|
+
if proxy.get('l3') != ProxyL3.reality and (proxy.get('transport') in {ProxyTransport.tcp, ProxyTransport.httpupgrade, ProxyTransport.xhttp}) and proxy['proto'] in [ProxyProto.vless, ProxyProto.trojan]:
|
133
133
|
baseurl += '&headerType=http'
|
134
134
|
else:
|
135
135
|
baseurl += '&headerType=None'
|
@@ -262,9 +262,9 @@ def add_stream_settings(base: dict, proxy: dict):
|
|
262
262
|
if proxy['transport'] == ProxyTransport.httpupgrade:
|
263
263
|
ss['network'] = proxy['transport']
|
264
264
|
add_httpupgrade_stream(ss, proxy)
|
265
|
-
if proxy['transport'] == ProxyTransport.
|
265
|
+
if proxy['transport'] == ProxyTransport.xhttp:
|
266
266
|
ss['network'] = proxy['transport']
|
267
|
-
|
267
|
+
add_xhttp_stream(ss, proxy)
|
268
268
|
if proxy['transport'] == 'ws':
|
269
269
|
ss['network'] = proxy['transport']
|
270
270
|
add_ws_stream(ss, proxy)
|
@@ -338,8 +338,8 @@ def add_httpupgrade_stream(ss: dict, proxy: dict):
|
|
338
338
|
}
|
339
339
|
|
340
340
|
|
341
|
-
def
|
342
|
-
ss['
|
341
|
+
def add_xhttp_stream(ss: dict, proxy: dict):
|
342
|
+
ss['xhttpSettings'] = {
|
343
343
|
'path': proxy['path'],
|
344
344
|
'host': proxy['host'],
|
345
345
|
"headers": {
|
@@ -239,7 +239,7 @@ class ConfigEnum(metaclass=FastEnum):
|
|
239
239
|
ws_enable = _BoolConfigDscr(ConfigCategory.proxies, ApplyMode.apply_config)
|
240
240
|
grpc_enable = _BoolConfigDscr(ConfigCategory.proxies, ApplyMode.apply_config)
|
241
241
|
httpupgrade_enable = _BoolConfigDscr(ConfigCategory.proxies, ApplyMode.apply_config)
|
242
|
-
|
242
|
+
xhttp_enable = _BoolConfigDscr(ConfigCategory.proxies, ApplyMode.apply_config)
|
243
243
|
|
244
244
|
vless_enable = _BoolConfigDscr(ConfigCategory.proxies, ApplyMode.apply_config)
|
245
245
|
trojan_enable = _BoolConfigDscr(ConfigCategory.proxies, ApplyMode.apply_config)
|
@@ -262,7 +262,7 @@ class ConfigEnum(metaclass=FastEnum):
|
|
262
262
|
path_v2ray = _StrConfigDscr(ConfigCategory.hidden, ApplyMode.apply_config, hide_in_virtual_child=True) # deprecated
|
263
263
|
path_ss = _StrConfigDscr(ConfigCategory.hidden, ApplyMode.apply_config, hide_in_virtual_child=True)
|
264
264
|
|
265
|
-
|
265
|
+
path_xhttp = _StrConfigDscr(ConfigCategory.too_advanced, ApplyMode.apply_config, hide_in_virtual_child=True)
|
266
266
|
path_httpupgrade = _StrConfigDscr(ConfigCategory.too_advanced, ApplyMode.apply_config, hide_in_virtual_child=True)
|
267
267
|
path_ws = _StrConfigDscr(ConfigCategory.too_advanced, ApplyMode.apply_config, hide_in_virtual_child=True)
|
268
268
|
path_tcp = _StrConfigDscr(ConfigCategory.too_advanced, ApplyMode.apply_config, hide_in_virtual_child=True)
|
hiddifypanel/models/proxy.py
CHANGED
@@ -143,19 +143,18 @@ class AdminstratorAdmin(AdminLTEModelView):
|
|
143
143
|
""")
|
144
144
|
|
145
145
|
def _max_active_users_formatter(view, context, model, name):
|
146
|
-
|
147
|
-
|
148
|
-
u = len(actives)
|
146
|
+
"""Optimized user count formatter using database queries"""
|
147
|
+
active_count = model.recursive_users_query().filter(User.is_active == True).count()
|
149
148
|
if model.mode == AdminMode.super_admin:
|
150
|
-
return f"{
|
149
|
+
return f"{active_count} / ∞"
|
151
150
|
t = model.max_active_users
|
152
|
-
rate = round(
|
153
|
-
|
154
|
-
|
151
|
+
rate = round(active_count * 100 / (t + 0.000001))
|
152
|
+
color = "#ff7e7e" if active_count >= t else ('#ffc107' if rate > 80 else '#9ee150')
|
153
|
+
|
155
154
|
return Markup(f"""
|
156
155
|
<div class="progress progress-lg position-relative" style="min-width: 100px;">
|
157
156
|
<div class="progress-bar progress-bar-striped" role="progressbar" style="width: {rate}%;background-color: {color};" aria-valuenow="{rate}" aria-valuemin="0" aria-valuemax="100"></div>
|
158
|
-
<span class='badge position-absolute' style="left:auto;right:auto;width: 100%;font-size:1em">{
|
157
|
+
<span class='badge position-absolute' style="left:auto;right:auto;width: 100%;font-size:1em">{active_count} {_('user.home.usage.from')} {t}</span>
|
159
158
|
|
160
159
|
</div>
|
161
160
|
""")
|
@@ -16,6 +16,7 @@ from hiddifypanel.panel import hiddify, custom_widgets
|
|
16
16
|
from .adminlte import AdminLTEModelView
|
17
17
|
from hiddifypanel import hutils
|
18
18
|
|
19
|
+
from loguru import logger
|
19
20
|
from flask import current_app
|
20
21
|
# Define a custom field type for the related domains
|
21
22
|
|
@@ -152,132 +153,164 @@ class DomainAdmin(AdminLTEModelView):
|
|
152
153
|
|
153
154
|
# TODO: refactor this function
|
154
155
|
def on_model_change(self, form, model, is_created):
|
155
|
-
|
156
|
+
# Sanitize domain input
|
157
|
+
model.domain = (model.domain or '').lower().strip()
|
158
|
+
|
159
|
+
# Basic validation
|
156
160
|
if model.domain == '' and model.mode != DomainType.fake:
|
157
161
|
raise ValidationError(_("domain.empty.allowed_for_fake_only"))
|
158
|
-
configs = get_hconfigs()
|
159
|
-
for c in configs:
|
160
|
-
if "domain" in c and c not in [ConfigEnum.decoy_domain, ConfigEnum.reality_fallback_domain] and c.category != 'hidden':
|
161
|
-
if model.domain == configs[c]:
|
162
|
-
raise ValidationError(_("You have used this domain in: ") + _(f"config.{c}.label"))
|
163
|
-
|
164
|
-
for td in Domain.query.filter(Domain.mode == DomainType.reality, Domain.domain != model.domain).all():
|
165
|
-
# print(td)
|
166
|
-
if td.servernames and (model.domain in td.servernames.split(",")):
|
167
|
-
raise ValidationError(_("You have used this domain in: ") + _(f"config.reality_server_names.label") + td.domain)
|
168
|
-
|
169
|
-
if is_created and Domain.query.filter(Domain.domain == model.domain, Domain.child_id == model.child_id).count() > 1:
|
170
|
-
raise ValidationError(_("You have used this domain in: "))
|
171
162
|
|
163
|
+
self._validate_not_used_before(model,is_created)
|
172
164
|
ipv4_list = hutils.network.get_ips(4)
|
173
165
|
ipv6_list = hutils.network.get_ips(6)
|
166
|
+
server_ips = [*ipv4_list, *ipv6_list]
|
174
167
|
|
175
|
-
if not
|
168
|
+
if not server_ips:
|
176
169
|
raise ValidationError(_("Couldn't find your ip addresses"))
|
177
170
|
|
171
|
+
# Validate domain based on mode
|
178
172
|
if "*" in model.domain and model.mode not in [DomainType.cdn, DomainType.auto_cdn_ip]:
|
179
173
|
raise ValidationError(_("Domain can not be resolved! there is a problem in your domain"))
|
180
174
|
|
181
|
-
|
175
|
+
cloudflare_updated=self._update_cloudflare(model, ipv4_list,ipv6_list)
|
176
|
+
|
177
|
+
if not (cloudflare_updated or "*" in model.domain or model.domain == ""):
|
178
|
+
self._validate_domain_ips(model, server_ips)
|
179
|
+
|
180
|
+
# Handle CDN IP settings
|
181
|
+
if model.mode == DomainType.direct and model.cdn_ip:
|
182
|
+
model.cdn_ip = ""
|
183
|
+
raise ValidationError(_("Specifying CDN IP is only valid for CDN mode"))
|
184
|
+
|
185
|
+
if model.mode == DomainType.fake and not model.cdn_ip:
|
186
|
+
model.cdn_ip = str(server_ips[0])
|
187
|
+
|
188
|
+
if model.cdn_ip:
|
189
|
+
try:
|
190
|
+
hutils.network.auto_ip_selector.get_clean_ip(str(model.cdn_ip))
|
191
|
+
except Exception:
|
192
|
+
raise ValidationError(_("Error in auto cdn format"))
|
193
|
+
|
194
|
+
# Update show domains
|
195
|
+
if len(model.show_domains) == Domain.query.count():
|
196
|
+
model.show_domains = []
|
197
|
+
|
198
|
+
# Handle mode-specific settings
|
199
|
+
if model.mode == DomainType.old_xtls_direct and not hconfig(ConfigEnum.xtls_enable):
|
200
|
+
set_hconfig(ConfigEnum.xtls_enable, True)
|
201
|
+
hutils.proxy.get_proxies().invalidate_all()
|
202
|
+
elif model.mode == DomainType.reality:
|
203
|
+
self._validate_reality_settings(model, server_ips)
|
204
|
+
|
205
|
+
# Signal config update if needed
|
206
|
+
old_db_domain = Domain.by_domain(model.domain)
|
207
|
+
if is_created or not old_db_domain or old_db_domain.mode != model.mode:
|
208
|
+
# return hiddify.reinstall_action(complete_install=False, domain_changed=True)
|
209
|
+
hutils.flask.flash_config_success(restart_mode=ApplyMode.apply_config, domain_changed=True)
|
210
|
+
|
211
|
+
|
212
|
+
|
213
|
+
def _update_cloudflare(self, model, ipv4_list,ipv6_list):
|
182
214
|
if hconfig(ConfigEnum.cloudflare) and model.mode not in [DomainType.fake, DomainType.relay, DomainType.reality]:
|
183
215
|
try:
|
184
216
|
proxied = model.mode in [DomainType.cdn, DomainType.auto_cdn_ip]
|
185
|
-
|
217
|
+
if ipv4_list:
|
218
|
+
hutils.network.cf_api.add_or_update_dns_record(model.domain, str(ipv4_list[0]), "A", proxied=proxied)
|
186
219
|
if ipv6_list:
|
187
220
|
hutils.network.cf_api.add_or_update_dns_record(model.domain, str(ipv6_list[0]), "AAAA", proxied=proxied)
|
188
|
-
|
189
|
-
skip_check = True
|
221
|
+
return True
|
190
222
|
except Exception as e:
|
191
223
|
raise ValidationError(__("cloudflare.error") + f' {e}')
|
192
|
-
|
193
|
-
# if model.alias and not model.alias.replace("_", "").isalnum():
|
194
|
-
# hutils.flask.flash(__("Using alias with special charachters may cause problem in some clients like FairVPN."), 'warning')
|
195
|
-
# raise ValidationError(_("You have to add your cloudflare api key to use this feature: "))
|
224
|
+
return False
|
196
225
|
|
197
|
-
|
198
|
-
|
199
|
-
if
|
200
|
-
|
201
|
-
|
202
|
-
elif not skip_check:
|
203
|
-
if not dips:
|
204
|
-
raise ValidationError(_("Domain can not be resolved! there is a problem in your domain")) # type: ignore
|
205
|
-
|
206
|
-
domain_ip_is_same_as_panel = False
|
207
|
-
|
208
|
-
for mip in server_ips:
|
209
|
-
domain_ip_is_same_as_panel |= mip in dips
|
210
|
-
server_ips_str = ', '.join(list(map(str, server_ips)))
|
211
|
-
dips_str = ', '.join(list(map(str, dips)))
|
212
|
-
|
213
|
-
if model.mode == DomainType.direct and not domain_ip_is_same_as_panel:
|
214
|
-
# hutils.flask.flash(__(f"Domain IP={dip} is not matched with your ip={', '.join(list(map(str, ipv4_list)))} which is required in direct mode"),category='error')
|
215
|
-
raise ValidationError(
|
216
|
-
__("Domain IP=%(domain_ip)s is not matched with your ip=%(server_ip)s which is required in direct mode", server_ip=server_ips_str, domain_ip=dips_str)) # type: ignore
|
217
|
-
|
218
|
-
if domain_ip_is_same_as_panel and model.mode in [DomainType.cdn, DomainType.relay, DomainType.fake, DomainType.auto_cdn_ip]:
|
219
|
-
# # hutils.flask.flash(__(f"In CDN mode, Domain IP={dip} should be different to your ip={', '.join(list(map(str, ipv4_list)))}"), 'warning')
|
220
|
-
raise ValidationError(__("In CDN mode, Domain IP=%(domain_ip)s should be different to your ip=%(server_ip)s",
|
221
|
-
server_ip=server_ips_str, domain_ip=dips_str)) # type: ignore
|
222
|
-
|
223
|
-
# if model.mode in [DomainType.ss_faketls, DomainType.telegram_faketls]:
|
224
|
-
# if len(Domain.query.filter(Domain.mode==model.mode and Domain.id!=model.id).all())>0:
|
225
|
-
# ValidationError(f"another {model.mode} is exist")
|
226
|
-
|
227
|
-
model.domain = model.domain.lower()
|
228
|
-
if model.mode == DomainType.direct and model.cdn_ip:
|
229
|
-
model.cdn_ip = ""
|
230
|
-
raise ValidationError(f"Specifying CDN IP is only valid for CDN mode")
|
226
|
+
def _validate_reality_settings(self, model, server_ips):
|
227
|
+
"""Validate REALITY protocol settings with proper error handling"""
|
228
|
+
if not hconfig(ConfigEnum.reality_enable):
|
229
|
+
set_hconfig(ConfigEnum.reality_enable, True)
|
230
|
+
hutils.proxy.get_proxies().invalidate_all()
|
231
231
|
|
232
|
-
|
233
|
-
|
232
|
+
model.servernames = (model.servernames or model.domain).lower().strip()
|
233
|
+
domains_to_check = set()
|
234
|
+
for v in [model.domain, model.servernames]:
|
235
|
+
domains_to_check.update(d.strip() for d in v.split(",") if d.strip())
|
234
236
|
|
235
|
-
|
236
|
-
|
237
|
+
for d in domains_to_check:
|
238
|
+
# Check REALITY compatibility
|
239
|
+
if not hutils.network.is_domain_reality_friendly(d):
|
240
|
+
raise ValidationError(_("Domain is not REALITY friendly!") + f' {d}')
|
237
241
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
set_hconfig(ConfigEnum.xtls_enable, True)
|
244
|
-
hutils.proxy.get_proxies().invalidate_all()
|
245
|
-
elif model.mode == DomainType.reality:
|
246
|
-
if not hconfig(ConfigEnum.reality_enable):
|
247
|
-
set_hconfig(ConfigEnum.reality_enable, True)
|
248
|
-
hutils.proxy.get_proxies().invalidate_all()
|
249
|
-
model.servernames = (model.servernames or model.domain).lower()
|
250
|
-
for v in set([model.domain, model.servernames]):
|
251
|
-
for d in v.split(","):
|
252
|
-
if not d:
|
253
|
-
continue
|
254
|
-
if not hutils.network.is_domain_reality_friendly(d): # the minimum requirement for the REALITY protocol is to have tls1.3 and h2
|
255
|
-
raise ValidationError(_("Domain is not REALITY friendly!") + f' {d}')
|
256
|
-
|
257
|
-
if not hutils.network.is_in_same_asn(d, server_ips[0]):
|
258
|
-
dip = next(iter(dips))
|
242
|
+
try:
|
243
|
+
if not hutils.network.is_in_same_asn(d, server_ips[0]):
|
244
|
+
domain_ips = hutils.network.get_domain_ips(d)
|
245
|
+
if domain_ips:
|
246
|
+
dip = next(iter(domain_ips))
|
259
247
|
server_asn = hutils.network.get_ip_asn(server_ips[0])
|
260
|
-
domain_asn = hutils.network.get_ip_asn(dip)
|
261
|
-
msg = _("domain.reality.asn_issue")
|
262
|
-
|
248
|
+
domain_asn = hutils.network.get_ip_asn(dip)
|
249
|
+
msg = _("domain.reality.asn_issue")
|
250
|
+
if server_asn or domain_asn:
|
251
|
+
msg += f"<br> Server ASN={server_asn}<br>{d}_ASN={domain_asn}"
|
263
252
|
hutils.flask.flash(msg, 'warning')
|
253
|
+
except Exception as e:
|
254
|
+
logger.warning(f"ASN check failed for domain {d}: {str(e)}")
|
264
255
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
256
|
+
# Check fallback compatibility
|
257
|
+
for d in model.servernames.split(","):
|
258
|
+
if d.strip() and not hutils.network.fallback_domain_compatible_with_servernames(model.domain, d):
|
259
|
+
msg = _("REALITY Fallback domain is not compatible with server names!") + f' {d} != {model.domain}'
|
260
|
+
hutils.flask.flash(msg, 'warning')
|
269
261
|
|
270
|
-
if (model.cdn_ip):
|
271
|
-
try:
|
272
|
-
hutils.network.auto_ip_selector.get_clean_ip(str(model.cdn_ip))
|
273
|
-
except BaseException:
|
274
|
-
raise ValidationError(_("Error in auto cdn format"))
|
275
262
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
263
|
+
def _validate_not_used_before(self, model,is_created):
|
264
|
+
configs = get_hconfigs()
|
265
|
+
for c in configs:
|
266
|
+
if "domain" in c and c not in [ConfigEnum.decoy_domain, ConfigEnum.reality_fallback_domain] and c.category != 'hidden':
|
267
|
+
if model.domain == configs[c]:
|
268
|
+
raise ValidationError(_("You have used this domain in: ") + _(f"config.{c}.label"))
|
269
|
+
|
270
|
+
for td in Domain.query.filter(Domain.mode == DomainType.reality, Domain.domain != model.domain).all():
|
271
|
+
# print(td)
|
272
|
+
if td.servernames and (model.domain in td.servernames.split(",")):
|
273
|
+
raise ValidationError(_("You have used this domain in: ") + _(f"config.reality_server_names.label") + td.domain)
|
280
274
|
|
275
|
+
if is_created and Domain.query.filter(Domain.domain == model.domain, Domain.child_id == model.child_id).count() > 1:
|
276
|
+
raise ValidationError(_("You have used this domain in: "))
|
277
|
+
|
278
|
+
def _validate_domain_ips(self, model, server_ips):
|
279
|
+
"""Validate domain IP resolution and matching"""
|
280
|
+
|
281
|
+
# Skip validation for wildcard or empty domains
|
282
|
+
if model.domain.startswith('*') or not model.domain:
|
283
|
+
return True
|
284
|
+
|
285
|
+
# Resolve domain IPs with timeout
|
286
|
+
try:
|
287
|
+
dips = hutils.network.get_domain_ips(model.domain, timeout=10)
|
288
|
+
except Exception as e:
|
289
|
+
logger.error(f"Error resolving domain {model.domain}: {str(e)}")
|
290
|
+
raise ValidationError(_("Domain cannot be resolved! Please check DNS settings"))
|
291
|
+
|
292
|
+
# Validate resolution success
|
293
|
+
if not dips:
|
294
|
+
raise ValidationError(_("Domain cannot be resolved! Please check DNS settings"))
|
295
|
+
|
296
|
+
# Check IP matching based on mode
|
297
|
+
domain_ip_matches_server = any(ip in dips for ip in server_ips)
|
298
|
+
server_ips_str = ', '.join(map(str, server_ips))
|
299
|
+
dips_str = ', '.join(map(str, dips))
|
300
|
+
|
301
|
+
if not domain_ip_matches_server and model.mode in [DomainType.direct]:
|
302
|
+
raise ValidationError(
|
303
|
+
__("Domain IP=%(domain_ip)s is not matched with your ip=%(server_ip)s which is required in direct mode",
|
304
|
+
server_ip=server_ips_str, domain_ip=dips_str))
|
305
|
+
|
306
|
+
if domain_ip_matches_server and model.mode in [DomainType.cdn, DomainType.relay, DomainType.fake, DomainType.auto_cdn_ip]:
|
307
|
+
raise ValidationError(
|
308
|
+
__("In CDN mode, Domain IP=%(domain_ip)s should be different to your ip=%(server_ip)s",
|
309
|
+
server_ip=server_ips_str, domain_ip=dips_str))
|
310
|
+
|
311
|
+
return True
|
312
|
+
|
313
|
+
|
281
314
|
# def after_model_change(self,form, model, is_created):
|
282
315
|
# if model.show_domains.count==0:
|
283
316
|
# db.session.bulk_save_objects(ShowDomain(model.id,model.id))
|
@@ -72,7 +72,7 @@ def get_lang_form(empty=False):
|
|
72
72
|
default=hconfig(ConfigEnum.admin_lang))
|
73
73
|
# lang=wtf.SelectField(_("config.lang.label"),choices=[("en",_("lang.en")),("fa",_("lang.fa"))],description=_("config.lang.description"),default=hconfig(ConfigEnum.lang))
|
74
74
|
country = wtf.SelectField(
|
75
|
-
_("config.country.label"), choices=[("ir", _("Iran")), ("zh", _("China")), ("other", "Others")],
|
75
|
+
_("config.country.label"), choices=[("ir", _("Iran")), ("zh", _("China")), ("ru", _("Russia")), ("other", "Others")],
|
76
76
|
description=_("config.country.description"),
|
77
77
|
default=hconfig(ConfigEnum.country))
|
78
78
|
lang_submit = wtf.SubmitField(_('Submit'))
|
@@ -251,11 +251,11 @@ def validate_domain(form, field):
|
|
251
251
|
if dip is None:
|
252
252
|
raise ValidationError(_("Domain can not be resolved! there is a problem in your domain"))
|
253
253
|
|
254
|
-
|
255
|
-
|
256
|
-
if dip
|
254
|
+
myips = hutils.network.get_ips()
|
255
|
+
# Fixed: Changed from get_ip(4) to get_ip(6)
|
256
|
+
if dip not in myips:
|
257
257
|
raise ValidationError(_("Domain (%(domain)s)-> IP=%(domain_ip)s is not matched with your ip=%(server_ip)s which is required in direct mode",
|
258
|
-
server_ip=
|
258
|
+
server_ip=myips, domain_ip=dip, domain=domain))
|
259
259
|
|
260
260
|
|
261
261
|
def validate_domain_cdn(form, field):
|
@@ -266,10 +266,10 @@ def validate_domain_cdn(form, field):
|
|
266
266
|
if dip is None:
|
267
267
|
raise ValidationError(_("Domain can not be resolved! there is a problem in your domain"))
|
268
268
|
|
269
|
-
|
270
|
-
if
|
269
|
+
myips = hutils.network.get_ips()
|
270
|
+
if dip in myips:
|
271
271
|
raise ValidationError(_("In CDN mode, Domain IP=%(domain_ip)s should be different to your ip=%(server_ip)s",
|
272
|
-
server_ip=
|
272
|
+
server_ip=myips, domain_ip=dip, domain=domain))
|
273
273
|
|
274
274
|
|
275
275
|
def admin_link():
|