diff --git a/app/dashboard/public/locales/en.json b/app/dashboard/public/locales/en.json index 386eece26..a20a4dab9 100644 --- a/app/dashboard/public/locales/en.json +++ b/app/dashboard/public/locales/en.json @@ -50,6 +50,10 @@ "hostsDialog.fragment.info": "Correct pattern: length,interval,packets", "hostsDialog.fragment.info.attention": "Attention: currently, this feature only supported in streisand >= 1.6.12 and v2rayNG >= 1.8.16 (custom config)", "hostsDialog.fragment.info.examples": "Examples:", + "hostsDialog.noise": "Noise pattern", + "hostsDialog.noise.info": "Correct pattern: packets,delay", + "hostsDialog.noise.info.attention": "Attention: currently, this feature only supported in streisand >= 1.6.32 and v2rayNG >= 1.8.39 (custom config)", + "hostsDialog.noise.info.examples": "Examples:", "hostsDialog.host": "Request Host", "hostsDialog.host.info": "By default, if a request host is set in the Xray config, this host is used. However, you can set a custom request host here if needed.", "hostsDialog.host.multiHost": "To set multiple addresses, separate them with , Each time an address is chosen randomly.", @@ -184,4 +188,4 @@ "usersTable.noUserMatched": "It seems there is no user matched with what you are looking for", "usersTable.status": "status", "usersTable.total": "Total" -} \ No newline at end of file +} diff --git a/app/dashboard/public/locales/fa.json b/app/dashboard/public/locales/fa.json index 22f361646..a85930811 100644 --- a/app/dashboard/public/locales/fa.json +++ b/app/dashboard/public/locales/fa.json @@ -55,6 +55,11 @@ "hostsDialog.fragment.info": "length,interval,packet (e.g. 10-100,100-200,tlshello)", "hostsDialog.fragment.info.attention": "توجه: در حال حاضر، این ویژگی فقط در streisand >= 1.6.12 و v2rayNG >= 1.8.16 (پیکربندی سفارشی) پشتیبانی می شود", "hostsDialog.fragment.info.examples": "نمونه ها:", + "hostsDialog.noise": "الگو نویز", + "hostsDialog.noise.info": "packet,delay (e.g. rand:10-20,100-200)", + "hostsDialog.noise.info.attention": "توجه: در حال حاضر، این ویژگی فقط در streisand >= 1.6.32 و v2rayNG >= 1.8.39 (پیکربندی سفارشی) پشتیبانی می شود", + "hostsDialog.noise.info.examples": "نمونه ها:", + "hostsDialog.host": "هاست درخواست", "hostsDialog.host.info": "به‌طور پیش‌فرض، اگر هاست درخواستی در پیکربندی *** تنظیم شده باشد، این هاست استفاده می‌شود. اما می‌توانید یک هاست درخواستی متفاوت در اینجا قرار دهید.", "hostsDialog.host.multiHost": "برای تنظیم چند آدرس، با , از هم جدا کنید. هر دفعه آدرسی به صورت تصادفی قرار داده می‌شود.", @@ -189,4 +194,4 @@ "usersTable.noUserMatched": "به‌نظر میرسه کاربری که جستجو کردید، وجود ندارد", "usersTable.status": "وضعیت", "usersTable.total": "مجموع" -} \ No newline at end of file +} diff --git a/app/dashboard/public/locales/ru.json b/app/dashboard/public/locales/ru.json index 147458efd..4ad11a232 100644 --- a/app/dashboard/public/locales/ru.json +++ b/app/dashboard/public/locales/ru.json @@ -50,6 +50,10 @@ "hostsDialog.fragment.info": "length,interval,packet (e.g. 10-100,100-200,tlshello)", "hostsDialog.fragment.info.attention": "Attention: currently, this feature only supported in streisand >= 1.6.12 and v2rayNG >= 1.8.16 (custom config)", "hostsDialog.fragment.info.examples": "Examples:", + "hostsDialog.noise": "Шумовой паттерн", + "hostsDialog.noise.info": "packet,delay (e.g. rand:10-20,100-200)", + "hostsDialog.noise.info.attention": "Attention: currently, this feature only supported in streisand >= 1.6.32 and v2rayNG >= 1.8.39 (custom config)", + "hostsDialog.noise.info.examples": "Examples:", "hostsDialog.host": "Host", "hostsDialog.host.info": "По умолчанию, если в конфигурации XRAY задан запрашиваемый хост, то он и будет использоваться. Однако, если необходимо, вы можете установить здесь пользовательский запрашиваемый хост.", "hostsDialog.host.multiHost": "Чтобы установить несколько адресов, разделяйте их с помощью ,. Каждый раз будет выбран случайный адрес.", @@ -184,4 +188,4 @@ "usersTable.noUserMatched": "Похоже, нет пользователя, соответствующего вашему запросу", "usersTable.status": "Статус", "usersTable.total": "Всего" -} \ No newline at end of file +} diff --git a/app/dashboard/public/locales/zh.json b/app/dashboard/public/locales/zh.json index c5ac2cff2..d43e52dbd 100644 --- a/app/dashboard/public/locales/zh.json +++ b/app/dashboard/public/locales/zh.json @@ -50,6 +50,10 @@ "hostsDialog.fragment.info": "length,interval,packet (e.g. 10-100,100-200,tlshello)", "hostsDialog.fragment.info.attention": "请注意: 当前特性仅支持 streisand >= 1.6.12 and v2rayNG >= 1.8.16 (自定义配置)", "hostsDialog.fragment.info.examples": "示例:", + "hostsDialog.noise": "噪声模式", + "hostsDialog.noise.info": "packet,delay (e.g. rand:10-20,100-200)", + "hostsDialog.noise.info.attention": "Attention: currently, this feature only supported in streisand >= 1.6.32 and v2rayNG >= 1.8.39 (custom config)", + "hostsDialog.noise.info.examples": "Examples:", "hostsDialog.host": "请求主机", "hostsDialog.host.info": "默认情况下,如果在 Xray 配置中设置了请求主机,则使用该主机。但是,如果需要,您可以在此处设置自定义请求主机。", "hostsDialog.host.multiHost": "使用 , 作为分隔符设置多个地址,使用时,每次随机选择一个地址。", diff --git a/app/dashboard/src/components/HostsDialog.tsx b/app/dashboard/src/components/HostsDialog.tsx index 1f5f8ffc7..7c3760262 100644 --- a/app/dashboard/src/components/HostsDialog.tsx +++ b/app/dashboard/src/components/HostsDialog.tsx @@ -51,7 +51,8 @@ import { proxyHostSecurity, } from "constants/Proxies"; import { useHosts } from "contexts/HostsContext"; -import { FC, useEffect, useRef, useState } from "react"; +import { motion } from "framer-motion"; +import { FC, useEffect, useState } from "react"; import { Controller, FormProvider, @@ -67,7 +68,6 @@ import { useDashboard } from "../contexts/DashboardContext"; import { DeleteIcon } from "./DeleteUserModal"; import { Icon } from "./Icon"; import { Input as CustomInput } from "./Input"; -import { motion } from "framer-motion"; export const DublicateIcon = chakra(DocumentDuplicateIcon, { baseStyle: { @@ -147,6 +147,7 @@ const hostsSchema = z.record( allowinsecure: z.boolean().nullable().default(false), is_disabled: z.boolean().default(true), fragment_setting: z.string().nullable(), + noise_setting: z.string().nullable(), random_user_agent: z.boolean().default(false), security: z.string(), alpn: z.string(), @@ -206,6 +207,7 @@ const AccordionInbound: FC = ({ allowinsecure: false, is_disabled: false, fragment_setting: "", + noise_setting: "", random_user_agent: false, security: "inbound_default", alpn: "", @@ -999,6 +1001,71 @@ const AccordionInbound: FC = ({ )} + + + {t("hostsDialog.noise")} + + + + + + + + + + + {t("hostsDialog.noise.info")} + + + {t("hostsDialog.noise.info.examples")} + + + rand:10-20,10-20 + + + rand:10-20,10-20&base64:7nQBAAABAAAAAAAABnQtcmluZwZtc2VkZ2UDbmV0AAABAAE=,10-25 + + + {t("hostsDialog.noise.info.attention")} + + + + + + + {accordionErrors && + accordionErrors[index]?.noise_setting && ( + + { + accordionErrors[index]?.noise_setting + ?.message + } + + )} + + + None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('hosts', sa.Column('noise_setting', sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('hosts', 'noise_setting') + # ### end Alembic commands ### diff --git a/app/db/models.py b/app/db/models.py index 0ed4d6c82..806922625 100644 --- a/app/db/models.py +++ b/app/db/models.py @@ -214,6 +214,7 @@ class ProxyHost(Base): is_disabled = Column(Boolean, nullable=True, default=False) mux_enable = Column(Boolean, nullable=False, default=False, server_default='0') fragment_setting = Column(String(100), nullable=True) + noise_setting = Column(String(), nullable=True) random_user_agent = Column(Boolean, nullable=False, default=False, server_default='0') diff --git a/app/models/proxy.py b/app/models/proxy.py index 3e0d98321..162cc4e58 100644 --- a/app/models/proxy.py +++ b/app/models/proxy.py @@ -1,8 +1,8 @@ import json +import re from enum import Enum from typing import Optional, Union from uuid import UUID, uuid4 -import re from pydantic import BaseModel, Field, validator @@ -18,6 +18,9 @@ FRAGMENT_PATTERN = re.compile(r'^((\d{1,4}-\d{1,4})|(\d{1,4})),((\d{1,3}-\d{1,3})|(\d{1,3})),(tlshello|\d|\d\-\d)$') +NOISE_PATTERN = re.compile( + r'^(rand:(\d{1,4}-\d{1,4}|\d{1,4})|str:.+|base64:.+)(,(\d{1,4}-\d{1,4}|\d{1,4}))?(&(rand:(\d{1,4}-\d{1,4}|\d{1,4})|str:.+|base64:.+)(,(\d{1,4}-\d{1,4}|\d{1,4}))?)*$') + class ProxyTypes(str, Enum): # proxy_type = protocol @@ -149,6 +152,7 @@ class ProxyHost(BaseModel): is_disabled: Union[bool, None] = None mux_enable: Union[bool, None] = None fragment_setting: Optional[str] = Field(None, nullable=True) + noise_setting: Optional[str] = Field(None, nullable=True) random_user_agent: Union[bool, None] = None class Config: @@ -180,6 +184,14 @@ def validate_fragment(cls, v): ) return v + @validator("noise_setting", check_fields=False) + def validate_noise(cls, v): + if v and not NOISE_PATTERN.match(v): + raise ValueError( + "Noise setting must be like this: packet,delay (rand:10-20,100-200)." + ) + return v + class ProxyInbound(BaseModel): tag: str diff --git a/app/subscription/clash.py b/app/subscription/clash.py index 7c8e922b8..0579cbbe9 100644 --- a/app/subscription/clash.py +++ b/app/subscription/clash.py @@ -168,7 +168,7 @@ def make_node(self, if type == 'shadowsocks': type = 'ss' - if network == 'tcp' and headers == 'http': + if network in ('tcp', 'raw') and headers == 'http': network = 'http' if network == 'httpupgrade': network = 'ws' @@ -233,7 +233,7 @@ def make_node(self, elif network == 'h2': net_opts = self.h2_config(path=path, host=host) - elif network == 'tcp': + elif network in ('tcp', 'raw'): net_opts = self.tcp_config(path=path, host=host) else: @@ -369,7 +369,7 @@ def add(self, remark: str, address: str, inbound: dict, settings: dict): elif inbound['protocol'] == 'vless': node['uuid'] = settings['id'] - if inbound['network'] in ('tcp', 'kcp') and inbound['header_type'] != 'http' and inbound['tls'] != 'none': + if inbound['network'] in ('tcp', 'raw', 'kcp') and inbound['header_type'] != 'http' and inbound['tls'] != 'none': node['flow'] = settings.get('flow', '') elif inbound['protocol'] == 'trojan': diff --git a/app/subscription/share.py b/app/subscription/share.py index 208ec639b..ecd879432 100644 --- a/app/subscription/share.py +++ b/app/subscription/share.py @@ -16,9 +16,13 @@ if TYPE_CHECKING: from app.models.user import UserResponse -from config import (ACTIVE_STATUS_TEXT, DISABLED_STATUS_TEXT, - EXPIRED_STATUS_TEXT, LIMITED_STATUS_TEXT, - ONHOLD_STATUS_TEXT) +from config import ( + ACTIVE_STATUS_TEXT, + DISABLED_STATUS_TEXT, + EXPIRED_STATUS_TEXT, + LIMITED_STATUS_TEXT, + ONHOLD_STATUS_TEXT +) SERVER_IP = get_public_ip() SERVER_IPV6 = get_public_ipv6() @@ -298,6 +302,7 @@ def process_inbounds_and_tags( or inbound.get("allowinsecure", ""), "mux_enable": host["mux_enable"], "fragment_setting": host["fragment_setting"], + "noise_setting": host["noise_setting"], "random_user_agent": host["random_user_agent"], } ) diff --git a/app/subscription/singbox.py b/app/subscription/singbox.py index dfc1fa188..97aa4afc9 100644 --- a/app/subscription/singbox.py +++ b/app/subscription/singbox.py @@ -1,13 +1,17 @@ -import json import copy +import json from random import choice from jinja2.exceptions import TemplateNotFound from app.subscription.funcs import get_grpc_gun from app.templates import render_template -from config import (MUX_TEMPLATE, SINGBOX_SETTINGS_TEMPLATE, - SINGBOX_SUBSCRIPTION_TEMPLATE, USER_AGENT_TEMPLATE) +from config import ( + MUX_TEMPLATE, + SINGBOX_SETTINGS_TEMPLATE, + SINGBOX_SUBSCRIPTION_TEMPLATE, + USER_AGENT_TEMPLATE +) class SingBoxConfiguration(str): @@ -232,14 +236,14 @@ def make_outbound(self, "server_port": port, } - if net in ('tcp', 'kcp') and headers != 'http' and (tls or tls != 'none'): + if net in ('tcp', 'raw', 'kcp') and headers != 'http' and (tls or tls != 'none'): if flow: config["flow"] = flow if net == 'h2': net = 'http' alpn = 'h2' - elif net in ['tcp'] and headers == 'http': + elif net in ['tcp', 'raw'] and headers == 'http': net = 'http' if net in ['http', 'ws', 'quic', 'grpc', 'httpupgrade']: diff --git a/app/subscription/v2ray.py b/app/subscription/v2ray.py index 52024c881..1b99a56fb 100644 --- a/app/subscription/v2ray.py +++ b/app/subscription/v2ray.py @@ -1,6 +1,6 @@ import base64 -import json import copy +import json import urllib.parse as urlparse from random import choice from typing import Union @@ -11,9 +11,14 @@ from app.subscription.funcs import get_grpc_gun, get_grpc_multi from app.templates import render_template -from config import (EXTERNAL_CONFIG, GRPC_USER_AGENT_TEMPLATE, MUX_TEMPLATE, - USER_AGENT_TEMPLATE, V2RAY_SETTINGS_TEMPLATE, - V2RAY_SUBSCRIPTION_TEMPLATE) +from config import ( + EXTERNAL_CONFIG, + GRPC_USER_AGENT_TEMPLATE, + MUX_TEMPLATE, + USER_AGENT_TEMPLATE, + V2RAY_SETTINGS_TEMPLATE, + V2RAY_SUBSCRIPTION_TEMPLATE +) class V2rayShareLink(str): @@ -69,6 +74,7 @@ def add(self, remark: str, address: str, inbound: dict, settings: dict): sc_max_each_post_bytes=inbound.get('scMaxEachPostBytes', 1000000), sc_max_concurrent_posts=inbound.get('scMaxConcurrentPosts', 100), sc_min_posts_interval_ms=inbound.get('scMinPostsIntervalMs', 30), + x_padding_bytes=inbound.get("xPaddingBytes", "100-1000"), ) elif inbound["protocol"] == "vless": @@ -95,6 +101,7 @@ def add(self, remark: str, address: str, inbound: dict, settings: dict): sc_max_each_post_bytes=inbound.get('scMaxEachPostBytes', 1000000), sc_max_concurrent_posts=inbound.get('scMaxConcurrentPosts', 100), sc_min_posts_interval_ms=inbound.get('scMinPostsIntervalMs', 30), + x_padding_bytes=inbound.get("xPaddingBytes", "100-1000"), ) elif inbound["protocol"] == "trojan": @@ -121,6 +128,7 @@ def add(self, remark: str, address: str, inbound: dict, settings: dict): sc_max_each_post_bytes=inbound.get('scMaxEachPostBytes', 1000000), sc_max_concurrent_posts=inbound.get('scMaxConcurrentPosts', 100), sc_min_posts_interval_ms=inbound.get('scMinPostsIntervalMs', 30), + x_padding_bytes=inbound.get("xPaddingBytes", "100-1000"), ) elif inbound["protocol"] == "shadowsocks": @@ -160,6 +168,7 @@ def vmess( sc_max_each_post_bytes: int = 1000000, sc_max_concurrent_posts: int = 100, sc_min_posts_interval_ms: int = 30, + x_padding_bytes: str = "100-1000", ): payload = { "add": address, @@ -211,12 +220,13 @@ def vmess( payload["scMaxEachPostBytes"] = sc_max_each_post_bytes payload["scMaxConcurrentPosts"] = sc_max_concurrent_posts payload["scMinPostsIntervalMs"] = sc_min_posts_interval_ms + payload["xPaddingBytes"] = x_padding_bytes return ( - "vmess://" - + base64.b64encode( - json.dumps(payload, sort_keys=True).encode("utf-8") - ).decode() + "vmess://" + + base64.b64encode( + json.dumps(payload, sort_keys=True).encode("utf-8") + ).decode() ) @classmethod @@ -243,6 +253,7 @@ def vless(cls, sc_max_each_post_bytes: int = 1000000, sc_max_concurrent_posts: int = 100, sc_min_posts_interval_ms: int = 30, + x_padding_bytes: str = "100-1000", ): payload = { @@ -250,7 +261,7 @@ def vless(cls, "type": net, "headerType": type } - if flow and (tls in ('tls', 'reality') and net in ('tcp', 'kcp') and type != 'http'): + if flow and (tls in ('tls', 'reality') and net in ('tcp', 'raw', 'kcp') and type != 'http'): payload['flow'] = flow if net == 'grpc': @@ -275,6 +286,7 @@ def vless(cls, payload["scMaxEachPostBytes"] = sc_max_each_post_bytes payload["scMaxConcurrentPosts"] = sc_max_concurrent_posts payload["scMinPostsIntervalMs"] = sc_min_posts_interval_ms + payload["xPaddingBytes"] = x_padding_bytes elif net == 'kcp': payload['seed'] = path @@ -303,10 +315,10 @@ def vless(cls, payload["spx"] = spx return ( - "vless://" - + f"{id}@{address}:{port}?" - + urlparse.urlencode(payload) - + f"#{(urlparse.quote(remark))}" + "vless://" + + f"{id}@{address}:{port}?" + + urlparse.urlencode(payload) + + f"#{(urlparse.quote(remark))}" ) @classmethod @@ -333,6 +345,7 @@ def trojan(cls, sc_max_each_post_bytes: int = 1000000, sc_max_concurrent_posts: int = 100, sc_min_posts_interval_ms: int = 30, + x_padding_bytes: str = "100-1000", ): payload = { @@ -340,7 +353,7 @@ def trojan(cls, "type": net, "headerType": type } - if flow and (tls in ('tls', 'reality') and net in ('tcp', 'kcp') and type != 'http'): + if flow and (tls in ('tls', 'reality') and net in ('tcp', 'raw', 'kcp') and type != 'http'): payload['flow'] = flow if net == 'grpc': @@ -361,6 +374,7 @@ def trojan(cls, payload["scMaxEachPostBytes"] = sc_max_each_post_bytes payload["scMaxConcurrentPosts"] = sc_max_concurrent_posts payload["scMinPostsIntervalMs"] = sc_min_posts_interval_ms + payload["xPaddingBytes"] = x_padding_bytes elif net == 'quic': payload['key'] = path @@ -392,10 +406,10 @@ def trojan(cls, payload["spx"] = spx return ( - "trojan://" - + f"{urlparse.quote(password, safe=':')}@{address}:{port}?" - + urlparse.urlencode(payload) - + f"#{urlparse.quote(remark)}" + "trojan://" + + f"{urlparse.quote(password, safe=':')}@{address}:{port}?" + + urlparse.urlencode(payload) + + f"#{urlparse.quote(remark)}" ) @classmethod @@ -403,9 +417,9 @@ def shadowsocks( cls, remark: str, address: str, port: int, password: str, method: str ): return ( - "ss://" - + base64.b64encode(f"{method}:{password}".encode()).decode() - + f"@{address}:{port}#{urlparse.quote(remark)}" + "ss://" + + base64.b64encode(f"{method}:{password}".encode()).decode() + + f"@{address}:{port}#{urlparse.quote(remark)}" ) @@ -519,6 +533,13 @@ def splithttp_config(self, path=None, host=None, random_user_agent=None, sc_max_each_post_bytes: int = 1000000, sc_max_concurrent_posts: int = 100, sc_min_posts_interval_ms: int = 30, + x_padding_bytes: str = "100-1000", + xmux: dict = { + "maxConcurrency": 0, + "maxConnections": 0, + "cMaxReuseTimes": 0, + "cMaxLifetimeMs": 0 + }, ): config = copy.deepcopy(self.settings.get("splithttpSettings", {})) @@ -536,6 +557,9 @@ def splithttp_config(self, path=None, host=None, random_user_agent=None, config["scMaxEachPostBytes"] = sc_max_each_post_bytes config["scMaxConcurrentPosts"] = sc_max_concurrent_posts config["scMinPostsIntervalMs"] = sc_min_posts_interval_ms + config["xPaddingBytes"] = x_padding_bytes + if xmux: + config["xmux"] = xmux # core will ignore unknown variables @@ -581,11 +605,11 @@ def tcp_config(self, headers="none", path=None, host=None, random_user_agent=Non } })) else: - config = copy.deepcopy(self.settings.get("tcpSettings", { + config = copy.deepcopy(self.settings.get("tcpSettings", self.settings.get("rawSettings", { "header": { "type": "none" } - })) + }))) if "header" not in config: config["header"] = {} @@ -786,6 +810,53 @@ def make_fragment_outbound(packets="tlshello", length="100-200", interval="10-20 return outbound + @staticmethod + def make_fragment_and_noise_outbound(fragment="", noises=""): + outbound = { + "protocol": "freedom", + } + tag = "" + settings = {} + + if fragment: + fragment_settings = {} + try: + length, interval, packets = fragment.split(',') + fragment_settings["fragment"] = { + "packets": packets, + "length": length, + "interval": interval + } + settings.update(fragment_settings) + tag = "fragment" + except ValueError: + pass + if noises: + sn = noises.split("&") + noises_settings = [] + for n in sn: + try: + tp, delay = n.split(',') + _type, packet = tp.split(":") + noises_settings.append({ + "type": _type, + "packet": packet, + "delay": delay + }) + except ValueError: + pass + settings["noises"] = noises_settings + if not tag: + tag = "noises" + else: + tag += "_and_noises" + if not tag: + return + outbound["settings"] = settings + tag += "_out" + outbound["tag"] = tag + return outbound + def make_stream_setting(self, net='', path='', @@ -805,6 +876,8 @@ def make_stream_setting(self, sc_max_each_post_bytes: int = 1000000, sc_max_concurrent_posts: int = 100, sc_min_posts_interval_ms: int = 30, + x_padding_bytes: str = "100-1000", + xmux: dict = {}, ): if net == "ws": @@ -819,7 +892,7 @@ def make_stream_setting(self, elif net == "kcp": network_setting = self.kcp_config( seed=path, host=host, header=headers) - elif net == "tcp" and tls != "reality": + elif net in ("tcp", "raw") and tls != "reality": network_setting = self.tcp_config( headers=headers, path=path, host=host, random_user_agent=random_user_agent) elif net == "quic": @@ -832,7 +905,9 @@ def make_stream_setting(self, network_setting = self.splithttp_config(path=path, host=host, random_user_agent=random_user_agent, sc_max_each_post_bytes=sc_max_each_post_bytes, sc_max_concurrent_posts=sc_max_concurrent_posts, - sc_min_posts_interval_ms=sc_min_posts_interval_ms + sc_min_posts_interval_ms=sc_min_posts_interval_ms, + x_padding_bytes=x_padding_bytes, + xmux=xmux, ) else: network_setting = {} @@ -871,6 +946,7 @@ def add(self, remark: str, address: str, inbound: dict, settings: dict): tls = (inbound['tls']) headers = inbound['header_type'] fragment = inbound['fragment_setting'] + noise = inbound['noise_setting'] path = inbound["path"] multi_mode = inbound.get("multiMode", False) @@ -891,7 +967,7 @@ def add(self, remark: str, address: str, inbound: dict, settings: dict): id=settings['id']) elif inbound['protocol'] == 'vless': - if net in ('tcp', 'kcp') and headers != 'http' and tls in ('tls', 'reality'): + if net in ('tcp', 'raw', 'kcp') and headers != 'http' and tls in ('tls', 'reality'): flow = settings.get('flow', '') else: flow = None @@ -914,16 +990,10 @@ def add(self, remark: str, address: str, inbound: dict, settings: dict): outbounds = [outbound] dialer_proxy = '' - - if fragment: - try: - length, interval, packets = fragment.split(',') - fragment_outbound = self.make_fragment_outbound( - packets, length, interval) - outbounds.append(fragment_outbound) - dialer_proxy = fragment_outbound['tag'] - except ValueError: - pass + extra_outbound = self.make_fragment_and_noise_outbound(fragment, noise) + if extra_outbound: + dialer_proxy = extra_outbound['tag'] + outbounds.append(extra_outbound) alpn = inbound.get('alpn', None) outbound["streamSettings"] = self.make_stream_setting( @@ -945,6 +1015,8 @@ def add(self, remark: str, address: str, inbound: dict, settings: dict): sc_max_each_post_bytes=inbound.get('scMaxEachPostBytes', 1000000), sc_max_concurrent_posts=inbound.get('scMaxConcurrentPosts', 100), sc_min_posts_interval_ms=inbound.get('scMinPostsIntervalMs', 30), + x_padding_bytes=inbound.get("xPaddingBytes", "100-1000"), + xmux=inbound.get("xmux", {}), ) mux_json = json.loads(self.mux_template) diff --git a/app/xray/__init__.py b/app/xray/__init__.py index 2f4875037..5b9e6c52c 100644 --- a/app/xray/__init__.py +++ b/app/xray/__init__.py @@ -62,6 +62,7 @@ def hosts(storage: dict): "allowinsecure": host.allowinsecure, "mux_enable": host.mux_enable, "fragment_setting": host.fragment_setting, + "noise_setting": host.noise_setting, "random_user_agent": host.random_user_agent, } for host in inbound_hosts if not host.is_disabled ] diff --git a/app/xray/config.py b/app/xray/config.py index 55bd02efe..723e04e21 100644 --- a/app/xray/config.py +++ b/app/xray/config.py @@ -233,7 +233,7 @@ def _resolve_inbounds(self): except: settings['spx'] = "" - if net == 'tcp': + if net in ('tcp', 'raw'): header = net_settings.get('header', {}) request = header.get('request', {}) path = request.get('path') @@ -293,6 +293,8 @@ def _resolve_inbounds(self): settings['scMaxConcurrentPosts'] = net_settings.get('scMaxConcurrentPosts', net_settings.get('maxConcurrentUploads', 100)) settings['scMinPostsIntervalMs'] = net_settings.get('scMinPostsIntervalMs', 30) + settings['xPaddingBytes'] = net_settings.get('xPaddingBytes', "100-1000") + settings['xmux'] = net_settings.get('xmux', {}) elif net == 'kcp': header = net_settings.get('header', {}) @@ -391,12 +393,12 @@ def include_db_users(self) -> XRayConfig: # XTLS currently only supports transmission methods of TCP and mKCP if client.get('flow') and ( - inbound.get('network', 'tcp') not in ('tcp', 'kcp') + inbound.get('network', 'tcp') not in ('tcp', 'raw', 'kcp') or ( - inbound.get('network', 'tcp') in ('tcp', 'kcp') - and - inbound.get('tls') not in ('tls', 'reality') + inbound.get('network', 'tcp') in ('tcp', 'raw', 'kcp') + and + inbound.get('tls') not in ('tls', 'reality') ) or inbound.get('header_type') == 'http'