mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-08-21 14:37:16 +00:00
Xray works
This commit is contained in:
55
vpn/xray_api_v2/models/__init__.py
Normal file
55
vpn/xray_api_v2/models/__init__.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""Xray API models"""
|
||||
from .base import (
|
||||
BaseXrayModel, XrayConfig, XrayProtocol,
|
||||
TransportProtocol, SecurityType
|
||||
)
|
||||
from .protocols import (
|
||||
# VLESS
|
||||
VLESSAccount, VLESSClient, VLESSInboundConfig,
|
||||
# VMess
|
||||
VMeSSAccount, VMeSSUser, VMeSSInboundConfig, VMeSSSecurityConfig,
|
||||
# Trojan
|
||||
TrojanAccount, TrojanUser, TrojanServerConfig, TrojanFallback,
|
||||
# Shadowsocks
|
||||
ShadowsocksAccount, ShadowsocksUser, ShadowsocksServerConfig,
|
||||
# Utils
|
||||
generate_uuid, validate_uuid, create_protocol_config
|
||||
)
|
||||
from .transports import (
|
||||
StreamSettings, TCPSettings, WebSocketSettings, GRPCSettings,
|
||||
HTTPSettings, XHTTPSettings, KCPSettings, QUICSettings, DomainSocketSettings,
|
||||
create_tcp_stream, create_ws_stream, create_grpc_stream, create_http_stream, create_xhttp_stream
|
||||
)
|
||||
from .security import (
|
||||
TLSConfig, REALITYConfig, XTLSConfig, Certificate,
|
||||
create_tls_config, create_reality_config, create_reality_client_config,
|
||||
generate_self_signed_certificate, create_tls_config_with_self_signed
|
||||
)
|
||||
from .inbound import (
|
||||
InboundConfig, ReceiverConfig, SniffingConfig, InboundBuilder
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Base
|
||||
'BaseXrayModel', 'XrayConfig', 'XrayProtocol', 'TransportProtocol', 'SecurityType',
|
||||
|
||||
# Protocols
|
||||
'VLESSAccount', 'VLESSClient', 'VLESSInboundConfig',
|
||||
'VMeSSAccount', 'VMeSSUser', 'VMeSSInboundConfig', 'VMeSSSecurityConfig',
|
||||
'TrojanAccount', 'TrojanUser', 'TrojanServerConfig', 'TrojanFallback',
|
||||
'ShadowsocksAccount', 'ShadowsocksUser', 'ShadowsocksServerConfig',
|
||||
'generate_uuid', 'validate_uuid', 'create_protocol_config',
|
||||
|
||||
# Transports
|
||||
'StreamSettings', 'TCPSettings', 'WebSocketSettings', 'GRPCSettings',
|
||||
'HTTPSettings', 'XHTTPSettings', 'KCPSettings', 'QUICSettings', 'DomainSocketSettings',
|
||||
'create_tcp_stream', 'create_ws_stream', 'create_grpc_stream', 'create_http_stream', 'create_xhttp_stream',
|
||||
|
||||
# Security
|
||||
'TLSConfig', 'REALITYConfig', 'XTLSConfig', 'Certificate',
|
||||
'create_tls_config', 'create_reality_config', 'create_reality_client_config',
|
||||
'generate_self_signed_certificate', 'create_tls_config_with_self_signed',
|
||||
|
||||
# Inbound
|
||||
'InboundConfig', 'ReceiverConfig', 'SniffingConfig', 'InboundBuilder',
|
||||
]
|
97
vpn/xray_api_v2/models/base.py
Normal file
97
vpn/xray_api_v2/models/base.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""Base models for xray-api library"""
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, asdict, field
|
||||
from typing import Dict, Any, Optional, Type, TypeVar
|
||||
import json
|
||||
from enum import Enum
|
||||
|
||||
T = TypeVar('T', bound='BaseXrayModel')
|
||||
|
||||
|
||||
class BaseXrayModel(ABC):
|
||||
"""Base class for all Xray configuration models"""
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert model to dictionary for storage"""
|
||||
if hasattr(self, '__dataclass_fields__'):
|
||||
return self._clean_dict(asdict(self))
|
||||
return self._clean_dict(self.__dict__.copy())
|
||||
|
||||
def to_xray_json(self) -> Dict[str, Any]:
|
||||
"""Convert model to Xray API format"""
|
||||
return self.to_dict()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls: Type[T], data: Dict[str, Any]) -> T:
|
||||
"""Create model instance from dictionary"""
|
||||
return cls(**data)
|
||||
|
||||
def _clean_dict(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Remove None values and empty collections"""
|
||||
cleaned = {}
|
||||
for key, value in data.items():
|
||||
if value is None:
|
||||
continue
|
||||
if isinstance(value, (list, dict)) and not value:
|
||||
continue
|
||||
if isinstance(value, BaseXrayModel):
|
||||
cleaned[key] = value.to_dict()
|
||||
elif isinstance(value, list):
|
||||
cleaned[key] = [
|
||||
item.to_dict() if isinstance(item, BaseXrayModel) else item
|
||||
for item in value
|
||||
]
|
||||
elif isinstance(value, Enum):
|
||||
cleaned[key] = value.value
|
||||
else:
|
||||
cleaned[key] = value
|
||||
return cleaned
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Convert to JSON string"""
|
||||
return json.dumps(self.to_dict(), indent=2)
|
||||
|
||||
|
||||
@dataclass
|
||||
class XrayConfig(BaseXrayModel):
|
||||
"""Base configuration class"""
|
||||
_TypedMessage_: Optional[str] = field(default=None, init=False)
|
||||
|
||||
def __post_init__(self):
|
||||
"""Set TypedMessage after initialization"""
|
||||
if self._TypedMessage_ is None and hasattr(self, '__xray_type__'):
|
||||
self._TypedMessage_ = self.__xray_type__
|
||||
|
||||
|
||||
class XrayProtocol(str, Enum):
|
||||
"""Supported Xray protocols"""
|
||||
VLESS = "vless"
|
||||
VMESS = "vmess"
|
||||
TROJAN = "trojan"
|
||||
SHADOWSOCKS = "shadowsocks"
|
||||
DOKODEMO = "dokodemo-door"
|
||||
FREEDOM = "freedom"
|
||||
BLACKHOLE = "blackhole"
|
||||
DNS = "dns"
|
||||
HTTP = "http"
|
||||
SOCKS = "socks"
|
||||
|
||||
|
||||
class TransportProtocol(str, Enum):
|
||||
"""Transport protocols"""
|
||||
TCP = "tcp"
|
||||
KCP = "kcp"
|
||||
WS = "ws"
|
||||
HTTP = "http"
|
||||
XHTTP = "xhttp"
|
||||
DOMAINSOCKET = "domainsocket"
|
||||
QUIC = "quic"
|
||||
GRPC = "grpc"
|
||||
|
||||
|
||||
class SecurityType(str, Enum):
|
||||
"""Security types"""
|
||||
NONE = "none"
|
||||
TLS = "tls"
|
||||
REALITY = "reality"
|
||||
XTLS = "xtls"
|
176
vpn/xray_api_v2/models/inbound.py
Normal file
176
vpn/xray_api_v2/models/inbound.py
Normal file
@@ -0,0 +1,176 @@
|
||||
"""Inbound configuration models"""
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Dict, Any, List, Union
|
||||
|
||||
from .base import BaseXrayModel, XrayConfig, XrayProtocol
|
||||
from .protocols import (
|
||||
VLESSInboundConfig, VMeSSInboundConfig,
|
||||
TrojanServerConfig, ShadowsocksServerConfig
|
||||
)
|
||||
from .transports import StreamSettings
|
||||
|
||||
|
||||
@dataclass
|
||||
class SniffingConfig(BaseXrayModel):
|
||||
"""Traffic sniffing configuration"""
|
||||
enabled: bool = True
|
||||
destOverride: Optional[List[str]] = None
|
||||
metadataOnly: bool = False
|
||||
|
||||
def __post_init__(self):
|
||||
if self.destOverride is None:
|
||||
self.destOverride = ["http", "tls"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReceiverConfig(XrayConfig):
|
||||
"""Receiver configuration for inbound"""
|
||||
__xray_type__ = "xray.app.proxyman.ReceiverConfig"
|
||||
|
||||
listen: str = "0.0.0.0"
|
||||
port: Optional[int] = None
|
||||
portList: Optional[Union[int, str]] = None # Can be int or range like "10000-20000"
|
||||
streamSettings: Optional[StreamSettings] = None
|
||||
|
||||
def to_xray_json(self) -> Dict[str, Any]:
|
||||
"""Convert to Xray format"""
|
||||
config = {"listen": self.listen}
|
||||
|
||||
# Either port or portList must be set
|
||||
if self.port is not None:
|
||||
config["port"] = self.port
|
||||
elif self.portList is not None:
|
||||
config["portList"] = self.portList
|
||||
else:
|
||||
raise ValueError("Either port or portList must be specified")
|
||||
|
||||
if self.streamSettings:
|
||||
config["streamSettings"] = self.streamSettings.to_xray_json()
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@dataclass
|
||||
class InboundConfig(BaseXrayModel):
|
||||
"""Complete inbound configuration"""
|
||||
tag: str
|
||||
protocol: XrayProtocol
|
||||
settings: Union[VLESSInboundConfig, VMeSSInboundConfig, TrojanServerConfig, ShadowsocksServerConfig]
|
||||
listen: str = "0.0.0.0"
|
||||
port: Optional[int] = None
|
||||
portList: Optional[Union[int, str]] = None
|
||||
streamSettings: Optional[StreamSettings] = None
|
||||
sniffing: Optional[SniffingConfig] = None
|
||||
|
||||
def to_xray_json(self) -> Dict[str, Any]:
|
||||
"""Convert to Xray API format with proper field order and structure"""
|
||||
config = {
|
||||
"listen": self.listen,
|
||||
"tag": self.tag,
|
||||
"protocol": self.protocol.value if hasattr(self.protocol, 'value') else str(self.protocol),
|
||||
}
|
||||
|
||||
# Add port or portList (port comes before protocol in working format)
|
||||
if self.port is not None:
|
||||
config["port"] = self.port
|
||||
elif self.portList is not None:
|
||||
config["portList"] = self.portList
|
||||
else:
|
||||
raise ValueError("Either port or portList must be specified")
|
||||
|
||||
# Add protocol settings with _TypedMessage_
|
||||
settings = self.settings.to_xray_json()
|
||||
if "_TypedMessage_" not in settings:
|
||||
# Add _TypedMessage_ based on protocol
|
||||
protocol_type_map = {
|
||||
"vless": "xray.proxy.vless.inbound.Config",
|
||||
"vmess": "xray.proxy.vmess.inbound.Config",
|
||||
"trojan": "xray.proxy.trojan.inbound.Config",
|
||||
"shadowsocks": "xray.proxy.shadowsocks.inbound.Config"
|
||||
}
|
||||
protocol_name = self.protocol.value if hasattr(self.protocol, 'value') else str(self.protocol)
|
||||
if protocol_name in protocol_type_map:
|
||||
settings["_TypedMessage_"] = protocol_type_map[protocol_name]
|
||||
|
||||
config["settings"] = settings
|
||||
|
||||
# Add stream settings
|
||||
if self.streamSettings:
|
||||
config["streamSettings"] = self.streamSettings.to_xray_json()
|
||||
else:
|
||||
config["streamSettings"] = {"network": "tcp"} # Default TCP
|
||||
|
||||
# Add sniffing
|
||||
if self.sniffing:
|
||||
config["sniffing"] = self.sniffing.to_dict()
|
||||
|
||||
return config
|
||||
|
||||
|
||||
# Builder for easier configuration
|
||||
class InboundBuilder:
|
||||
"""Builder for creating inbound configurations"""
|
||||
|
||||
def __init__(self, tag: str, protocol: XrayProtocol):
|
||||
self.tag = tag
|
||||
self.protocol = protocol
|
||||
self._settings = None
|
||||
self._listen = "0.0.0.0"
|
||||
self._port = None
|
||||
self._port_list = None
|
||||
self._stream_settings = None
|
||||
self._sniffing = None
|
||||
|
||||
def listen(self, address: str) -> 'InboundBuilder':
|
||||
"""Set listen address"""
|
||||
self._listen = address
|
||||
return self
|
||||
|
||||
def port(self, port: int) -> 'InboundBuilder':
|
||||
"""Set single port"""
|
||||
self._port = port
|
||||
self._port_list = None
|
||||
return self
|
||||
|
||||
def port_range(self, start: int, end: int) -> 'InboundBuilder':
|
||||
"""Set port range"""
|
||||
self._port_list = f"{start}-{end}"
|
||||
self._port = None
|
||||
return self
|
||||
|
||||
def stream_settings(self, settings: StreamSettings) -> 'InboundBuilder':
|
||||
"""Set stream settings"""
|
||||
self._stream_settings = settings
|
||||
return self
|
||||
|
||||
def sniffing(self, enabled: bool = True, dest_override: Optional[List[str]] = None) -> 'InboundBuilder':
|
||||
"""Configure sniffing"""
|
||||
self._sniffing = SniffingConfig(
|
||||
enabled=enabled,
|
||||
destOverride=dest_override
|
||||
)
|
||||
return self
|
||||
|
||||
def protocol_settings(self, settings: Union[VLESSInboundConfig, VMeSSInboundConfig, TrojanServerConfig, ShadowsocksServerConfig]) -> 'InboundBuilder':
|
||||
"""Set protocol-specific settings"""
|
||||
self._settings = settings
|
||||
return self
|
||||
|
||||
def build(self) -> InboundConfig:
|
||||
"""Build the inbound configuration"""
|
||||
if not self._settings:
|
||||
raise ValueError("Protocol settings must be specified")
|
||||
|
||||
if not self._port and not self._port_list:
|
||||
raise ValueError("Either port or port range must be specified")
|
||||
|
||||
return InboundConfig(
|
||||
tag=self.tag,
|
||||
protocol=self.protocol,
|
||||
settings=self._settings,
|
||||
listen=self._listen,
|
||||
port=self._port,
|
||||
portList=self._port_list,
|
||||
streamSettings=self._stream_settings,
|
||||
sniffing=self._sniffing
|
||||
)
|
266
vpn/xray_api_v2/models/protocols.py
Normal file
266
vpn/xray_api_v2/models/protocols.py
Normal file
@@ -0,0 +1,266 @@
|
||||
"""Protocol models for Xray"""
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional, Dict, Any
|
||||
from uuid import uuid4
|
||||
import re
|
||||
|
||||
from .base import BaseXrayModel, XrayConfig, XrayProtocol
|
||||
|
||||
|
||||
def validate_uuid(uuid_str: str) -> bool:
|
||||
"""Validate UUID format"""
|
||||
pattern = re.compile(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', re.I)
|
||||
return bool(pattern.match(uuid_str))
|
||||
|
||||
|
||||
def generate_uuid() -> str:
|
||||
"""Generate new UUID"""
|
||||
return str(uuid4())
|
||||
|
||||
|
||||
# VLESS Protocol
|
||||
@dataclass
|
||||
class VLESSAccount(XrayConfig):
|
||||
"""VLESS account configuration"""
|
||||
__xray_type__ = "xray.proxy.vless.Account"
|
||||
|
||||
id: str
|
||||
flow: Optional[str] = None
|
||||
encryption: str = "none"
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
if not validate_uuid(self.id):
|
||||
raise ValueError(f"Invalid UUID: {self.id}")
|
||||
|
||||
|
||||
@dataclass
|
||||
class VLESSClient(BaseXrayModel):
|
||||
"""VLESS client configuration"""
|
||||
email: str
|
||||
account: VLESSAccount
|
||||
level: int = 0
|
||||
|
||||
@classmethod
|
||||
def create(cls, email: str, uuid: Optional[str] = None, flow: Optional[str] = None) -> 'VLESSClient':
|
||||
"""Create VLESS client with optional UUID generation"""
|
||||
if uuid is None:
|
||||
uuid = generate_uuid()
|
||||
account = VLESSAccount(id=uuid, flow=flow)
|
||||
return cls(email=email, account=account)
|
||||
|
||||
|
||||
@dataclass
|
||||
class VLESSInboundConfig(XrayConfig):
|
||||
"""VLESS inbound configuration"""
|
||||
__xray_type__ = "xray.proxy.vless.inbound.Config"
|
||||
|
||||
clients: List[VLESSClient] = field(default_factory=list)
|
||||
decryption: str = "none"
|
||||
fallbacks: Optional[List[Dict[str, Any]]] = None
|
||||
|
||||
def to_xray_json(self) -> Dict[str, Any]:
|
||||
"""Convert to Xray API format with proper structure"""
|
||||
config = {
|
||||
"_TypedMessage_": self.__xray_type__,
|
||||
"clients": [],
|
||||
"decryption": self.decryption
|
||||
}
|
||||
|
||||
# Convert clients to proper format
|
||||
for client in self.clients:
|
||||
client_data = {
|
||||
"id": client.account.id,
|
||||
"level": client.level,
|
||||
"email": client.email
|
||||
}
|
||||
if client.account.flow:
|
||||
client_data["flow"] = client.account.flow
|
||||
config["clients"].append(client_data)
|
||||
|
||||
if self.fallbacks:
|
||||
config["fallbacks"] = self.fallbacks
|
||||
|
||||
return config
|
||||
|
||||
|
||||
# VMess Protocol
|
||||
@dataclass
|
||||
class VMeSSSecurityConfig(BaseXrayModel):
|
||||
"""VMess security configuration"""
|
||||
type: str = "AUTO" # AUTO, AES-128-GCM, CHACHA20-POLY1305, NONE
|
||||
|
||||
|
||||
@dataclass
|
||||
class VMeSSAccount(XrayConfig):
|
||||
"""VMess account configuration"""
|
||||
__xray_type__ = "xray.proxy.vmess.Account"
|
||||
|
||||
id: str
|
||||
securitySettings: Optional[VMeSSSecurityConfig] = None
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
if not validate_uuid(self.id):
|
||||
raise ValueError(f"Invalid UUID: {self.id}")
|
||||
if self.securitySettings is None:
|
||||
self.securitySettings = VMeSSSecurityConfig()
|
||||
|
||||
|
||||
@dataclass
|
||||
class VMeSSUser(BaseXrayModel):
|
||||
"""VMess user configuration"""
|
||||
email: str
|
||||
account: VMeSSAccount
|
||||
level: int = 0
|
||||
|
||||
@classmethod
|
||||
def create(cls, email: str, uuid: Optional[str] = None, security: str = "AUTO") -> 'VMeSSUser':
|
||||
"""Create VMess user with optional UUID generation"""
|
||||
if uuid is None:
|
||||
uuid = generate_uuid()
|
||||
account = VMeSSAccount(
|
||||
id=uuid,
|
||||
securitySettings=VMeSSSecurityConfig(type=security)
|
||||
)
|
||||
return cls(email=email, account=account)
|
||||
|
||||
|
||||
@dataclass
|
||||
class VMeSSInboundConfig(XrayConfig):
|
||||
"""VMess inbound configuration"""
|
||||
__xray_type__ = "xray.proxy.vmess.inbound.Config"
|
||||
|
||||
user: List[VMeSSUser] = field(default_factory=list)
|
||||
disableInsecureEncryption: bool = False
|
||||
|
||||
def to_xray_json(self) -> Dict[str, Any]:
|
||||
"""Convert to Xray API format with proper structure"""
|
||||
config = {
|
||||
"_TypedMessage_": self.__xray_type__,
|
||||
"clients": []
|
||||
}
|
||||
|
||||
# Convert users to proper format
|
||||
for user in self.user:
|
||||
client_data = {
|
||||
"id": user.account.id,
|
||||
"level": user.level,
|
||||
"email": user.email,
|
||||
"alterId": 0 # VMess specific
|
||||
}
|
||||
config["clients"].append(client_data)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
# Trojan Protocol
|
||||
@dataclass
|
||||
class TrojanAccount(XrayConfig):
|
||||
"""Trojan account configuration"""
|
||||
__xray_type__ = "xray.proxy.trojan.Account"
|
||||
|
||||
password: str
|
||||
|
||||
@classmethod
|
||||
def generate_password(cls) -> str:
|
||||
"""Generate secure password"""
|
||||
return generate_uuid()
|
||||
|
||||
|
||||
@dataclass
|
||||
class TrojanUser(BaseXrayModel):
|
||||
"""Trojan user configuration"""
|
||||
email: str
|
||||
account: TrojanAccount
|
||||
level: int = 0
|
||||
|
||||
@classmethod
|
||||
def create(cls, email: str, password: Optional[str] = None) -> 'TrojanUser':
|
||||
"""Create Trojan user with optional password generation"""
|
||||
if password is None:
|
||||
password = TrojanAccount.generate_password()
|
||||
account = TrojanAccount(password=password)
|
||||
return cls(email=email, account=account)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TrojanFallback(BaseXrayModel):
|
||||
"""Trojan fallback configuration"""
|
||||
dest: str
|
||||
type: str = "tcp"
|
||||
xver: int = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class TrojanServerConfig(XrayConfig):
|
||||
"""Trojan server configuration"""
|
||||
__xray_type__ = "xray.proxy.trojan.ServerConfig"
|
||||
|
||||
users: List[TrojanUser] = field(default_factory=list)
|
||||
fallbacks: Optional[List[TrojanFallback]] = None
|
||||
|
||||
def to_xray_json(self) -> Dict[str, Any]:
|
||||
"""Convert to Xray API format with proper structure"""
|
||||
config = {
|
||||
"_TypedMessage_": self.__xray_type__,
|
||||
"clients": []
|
||||
}
|
||||
|
||||
# Convert users to proper format
|
||||
for user in self.users:
|
||||
client_data = {
|
||||
"password": user.account.password,
|
||||
"level": user.level,
|
||||
"email": user.email
|
||||
}
|
||||
config["clients"].append(client_data)
|
||||
|
||||
if self.fallbacks:
|
||||
config["fallbacks"] = [fb.to_dict() for fb in self.fallbacks]
|
||||
|
||||
return config
|
||||
|
||||
|
||||
# Shadowsocks Protocol
|
||||
@dataclass
|
||||
class ShadowsocksAccount(XrayConfig):
|
||||
"""Shadowsocks account configuration"""
|
||||
__xray_type__ = "xray.proxy.shadowsocks.Account"
|
||||
|
||||
method: str # aes-256-gcm, aes-128-gcm, chacha20-poly1305, etc.
|
||||
password: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class ShadowsocksUser(BaseXrayModel):
|
||||
"""Shadowsocks user configuration"""
|
||||
email: str
|
||||
account: ShadowsocksAccount
|
||||
level: int = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class ShadowsocksServerConfig(XrayConfig):
|
||||
"""Shadowsocks server configuration"""
|
||||
__xray_type__ = "xray.proxy.shadowsocks.ServerConfig"
|
||||
|
||||
users: List[ShadowsocksUser] = field(default_factory=list)
|
||||
network: str = "tcp,udp"
|
||||
|
||||
|
||||
# Protocol config factory
|
||||
def create_protocol_config(protocol: XrayProtocol, **kwargs) -> XrayConfig:
|
||||
"""Factory to create protocol configurations"""
|
||||
protocol_map = {
|
||||
XrayProtocol.VLESS: VLESSInboundConfig,
|
||||
XrayProtocol.VMESS: VMeSSInboundConfig,
|
||||
XrayProtocol.TROJAN: TrojanServerConfig,
|
||||
XrayProtocol.SHADOWSOCKS: ShadowsocksServerConfig,
|
||||
}
|
||||
|
||||
config_class = protocol_map.get(protocol)
|
||||
if not config_class:
|
||||
raise ValueError(f"Unsupported protocol: {protocol}")
|
||||
|
||||
return config_class(**kwargs)
|
389
vpn/xray_api_v2/models/security.py
Normal file
389
vpn/xray_api_v2/models/security.py
Normal file
@@ -0,0 +1,389 @@
|
||||
"""Security configuration models for Xray"""
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, List, Dict, Any, Tuple
|
||||
from pathlib import Path
|
||||
import secrets
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from .base import BaseXrayModel, XrayConfig, SecurityType
|
||||
|
||||
|
||||
# TLS Configuration
|
||||
@dataclass
|
||||
class Certificate(BaseXrayModel):
|
||||
"""TLS certificate configuration"""
|
||||
certificateFile: Optional[str] = None
|
||||
keyFile: Optional[str] = None
|
||||
certificate: Optional[List[str]] = None # PEM format lines
|
||||
key: Optional[List[str]] = None # PEM format lines
|
||||
usage: str = "encipherment"
|
||||
ocspStapling: int = 3600
|
||||
oneTimeLoading: bool = False
|
||||
|
||||
def to_xray_json(self) -> Dict[str, Any]:
|
||||
"""Convert to Xray format"""
|
||||
config = {}
|
||||
|
||||
if self.certificateFile and self.keyFile:
|
||||
config["certificateFile"] = self.certificateFile
|
||||
config["keyFile"] = self.keyFile
|
||||
elif self.certificate and self.key:
|
||||
config["certificate"] = self.certificate
|
||||
config["key"] = self.key
|
||||
|
||||
config["usage"] = self.usage
|
||||
|
||||
if self.ocspStapling:
|
||||
config["ocspStapling"] = self.ocspStapling
|
||||
|
||||
if self.oneTimeLoading:
|
||||
config["OneTimeLoading"] = self.oneTimeLoading
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@dataclass
|
||||
class TLSConfig(XrayConfig):
|
||||
"""TLS configuration"""
|
||||
__xray_type__ = "xray.transport.internet.tls.Config"
|
||||
|
||||
serverName: Optional[str] = None
|
||||
allowInsecure: bool = False
|
||||
alpn: Optional[List[str]] = None
|
||||
minVersion: str = "1.2"
|
||||
maxVersion: str = "1.3"
|
||||
preferServerCipherSuites: bool = True
|
||||
cipherSuites: Optional[str] = None
|
||||
certificates: Optional[List[Certificate]] = None
|
||||
disableSystemRoot: bool = False
|
||||
enableSessionResumption: bool = False
|
||||
fingerprint: Optional[str] = None # Client-side
|
||||
pinnedPeerCertificateChainSha256: Optional[List[str]] = None
|
||||
rejectUnknownSni: bool = False # Server-side
|
||||
|
||||
def to_xray_json(self) -> Dict[str, Any]:
|
||||
"""Convert to Xray format with proper field handling"""
|
||||
config = super().to_xray_json()
|
||||
|
||||
# Handle certificates properly
|
||||
if self.certificates:
|
||||
config["certificates"] = [cert.to_xray_json() for cert in self.certificates]
|
||||
|
||||
return config
|
||||
|
||||
|
||||
# REALITY Configuration
|
||||
@dataclass
|
||||
class REALITYConfig(XrayConfig):
|
||||
"""REALITY configuration"""
|
||||
__xray_type__ = "xray.transport.internet.reality.Config"
|
||||
|
||||
# Server-side
|
||||
show: bool = False
|
||||
dest: Optional[str] = None # e.g., "example.com:443"
|
||||
xver: int = 0
|
||||
serverNames: Optional[List[str]] = None
|
||||
privateKey: Optional[str] = None
|
||||
shortIds: Optional[List[str]] = None
|
||||
|
||||
# Client-side
|
||||
serverName: Optional[str] = None
|
||||
fingerprint: str = "chrome"
|
||||
publicKey: Optional[str] = None
|
||||
shortId: Optional[str] = None
|
||||
spiderX: Optional[str] = None
|
||||
|
||||
@classmethod
|
||||
def generate_keys(cls) -> Dict[str, str]:
|
||||
"""Generate REALITY key pair using xray x25519"""
|
||||
import subprocess
|
||||
try:
|
||||
# Use xray x25519 to generate proper keys
|
||||
result = subprocess.run(['xray', 'x25519'], capture_output=True, text=True, check=True)
|
||||
lines = result.stdout.strip().split('\n')
|
||||
|
||||
private_key = ""
|
||||
public_key = ""
|
||||
|
||||
for line in lines:
|
||||
if line.startswith('Private key:'):
|
||||
private_key = line.split(': ')[1].strip()
|
||||
elif line.startswith('Public key:'):
|
||||
public_key = line.split(': ')[1].strip()
|
||||
|
||||
return {
|
||||
"privateKey": private_key,
|
||||
"publicKey": public_key
|
||||
}
|
||||
except (subprocess.CalledProcessError, FileNotFoundError, IndexError):
|
||||
# Fallback to base64 encoded random bytes (32 bytes for X25519)
|
||||
import base64
|
||||
private_bytes = secrets.token_bytes(32)
|
||||
public_bytes = secrets.token_bytes(32)
|
||||
return {
|
||||
"privateKey": base64.b64encode(private_bytes).decode().rstrip('='),
|
||||
"publicKey": base64.b64encode(public_bytes).decode().rstrip('=')
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def generate_short_id(cls) -> str:
|
||||
"""Generate random short ID (1-16 hex chars)"""
|
||||
# Generate 1-8 bytes (2-16 hex chars)
|
||||
length = secrets.randbelow(8) + 1
|
||||
return secrets.token_hex(length)
|
||||
|
||||
|
||||
# XTLS Configuration (deprecated but still supported)
|
||||
@dataclass
|
||||
class XTLSConfig(XrayConfig):
|
||||
"""XTLS configuration (legacy)"""
|
||||
__xray_type__ = "xray.transport.internet.xtls.Config"
|
||||
|
||||
serverName: Optional[str] = None
|
||||
allowInsecure: bool = False
|
||||
alpn: Optional[List[str]] = None
|
||||
minVersion: str = "1.2"
|
||||
maxVersion: str = "1.3"
|
||||
preferServerCipherSuites: bool = True
|
||||
cipherSuites: Optional[str] = None
|
||||
certificates: Optional[List[Certificate]] = None
|
||||
disableSystemRoot: bool = False
|
||||
fingerprint: Optional[str] = None
|
||||
rejectUnknownSni: bool = False
|
||||
|
||||
|
||||
# Security factory
|
||||
def create_tls_config(
|
||||
server_name: Optional[str] = None,
|
||||
cert_file: Optional[str] = None,
|
||||
key_file: Optional[str] = None,
|
||||
alpn: Optional[List[str]] = None,
|
||||
fingerprint: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> TLSConfig:
|
||||
"""Create TLS configuration"""
|
||||
config = TLSConfig(
|
||||
serverName=server_name,
|
||||
alpn=alpn or ["h2", "http/1.1"],
|
||||
fingerprint=fingerprint,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
if cert_file and key_file:
|
||||
config.certificates = [Certificate(
|
||||
certificateFile=cert_file,
|
||||
keyFile=key_file
|
||||
)]
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def create_reality_config(
|
||||
dest: str,
|
||||
server_names: Optional[List[str]] = None,
|
||||
private_key: Optional[str] = None,
|
||||
short_ids: Optional[List[str]] = None,
|
||||
**kwargs
|
||||
) -> REALITYConfig:
|
||||
"""Create REALITY configuration for server"""
|
||||
if not private_key:
|
||||
keys = REALITYConfig.generate_keys()
|
||||
private_key = keys["privateKey"]
|
||||
|
||||
if not short_ids:
|
||||
short_ids = [REALITYConfig.generate_short_id()]
|
||||
|
||||
return REALITYConfig(
|
||||
show=False,
|
||||
dest=dest,
|
||||
serverNames=server_names or [dest.split(":")[0]],
|
||||
privateKey=private_key,
|
||||
shortIds=short_ids,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
def create_reality_client_config(
|
||||
server_name: str,
|
||||
public_key: str,
|
||||
short_id: str,
|
||||
fingerprint: str = "chrome",
|
||||
**kwargs
|
||||
) -> REALITYConfig:
|
||||
"""Create REALITY configuration for client"""
|
||||
return REALITYConfig(
|
||||
serverName=server_name,
|
||||
publicKey=public_key,
|
||||
shortId=short_id,
|
||||
fingerprint=fingerprint,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
# Certificate generation utilities
|
||||
def generate_self_signed_certificate(
|
||||
common_name: str = "localhost",
|
||||
san_list: Optional[List[str]] = None,
|
||||
days: int = 365,
|
||||
key_size: int = 2048,
|
||||
save_to_files: bool = False,
|
||||
cert_path: Optional[str] = None,
|
||||
key_path: Optional[str] = None
|
||||
) -> Tuple[str, str]:
|
||||
"""
|
||||
Generate self-signed certificate
|
||||
|
||||
Args:
|
||||
common_name: Certificate common name
|
||||
san_list: Subject Alternative Names (domains/IPs)
|
||||
days: Certificate validity in days
|
||||
key_size: RSA key size
|
||||
save_to_files: Whether to save to files
|
||||
cert_path: Path to save certificate
|
||||
key_path: Path to save private key
|
||||
|
||||
Returns:
|
||||
Tuple of (certificate_pem, private_key_pem)
|
||||
"""
|
||||
try:
|
||||
from cryptography import x509
|
||||
from cryptography.x509.oid import NameOID, ExtensionOID
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
except ImportError:
|
||||
raise ImportError("cryptography package is required for certificate generation. Install with: pip install cryptography")
|
||||
|
||||
# Generate private key
|
||||
private_key = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=key_size,
|
||||
backend=default_backend()
|
||||
)
|
||||
|
||||
# Create certificate subject
|
||||
subject = issuer = x509.Name([
|
||||
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
|
||||
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "State"),
|
||||
x509.NameAttribute(NameOID.LOCALITY_NAME, "City"),
|
||||
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Xray Self-Signed"),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, common_name),
|
||||
])
|
||||
|
||||
# Create certificate builder
|
||||
builder = x509.CertificateBuilder()
|
||||
builder = builder.subject_name(subject)
|
||||
builder = builder.issuer_name(issuer)
|
||||
builder = builder.public_key(private_key.public_key())
|
||||
builder = builder.serial_number(x509.random_serial_number())
|
||||
builder = builder.not_valid_before(datetime.utcnow())
|
||||
builder = builder.not_valid_after(datetime.utcnow() + timedelta(days=days))
|
||||
|
||||
# Add Subject Alternative Names
|
||||
san_list = san_list or [common_name]
|
||||
alt_names = []
|
||||
for san in san_list:
|
||||
if san.replace('.', '').isdigit(): # IP address
|
||||
alt_names.append(x509.IPAddress(ipaddress.ip_address(san)))
|
||||
else: # Domain name
|
||||
alt_names.append(x509.DNSName(san))
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.SubjectAlternativeName(alt_names),
|
||||
critical=False,
|
||||
)
|
||||
|
||||
# Add basic constraints
|
||||
builder = builder.add_extension(
|
||||
x509.BasicConstraints(ca=True, path_length=0),
|
||||
critical=True,
|
||||
)
|
||||
|
||||
# Add key usage
|
||||
builder = builder.add_extension(
|
||||
x509.KeyUsage(
|
||||
digital_signature=True,
|
||||
content_commitment=False,
|
||||
key_encipherment=True,
|
||||
data_encipherment=False,
|
||||
key_agreement=False,
|
||||
key_cert_sign=True,
|
||||
crl_sign=False,
|
||||
encipher_only=False,
|
||||
decipher_only=False,
|
||||
),
|
||||
critical=True,
|
||||
)
|
||||
|
||||
# Sign certificate
|
||||
certificate = builder.sign(private_key, hashes.SHA256(), default_backend())
|
||||
|
||||
# Serialize to PEM
|
||||
cert_pem = certificate.public_bytes(serialization.Encoding.PEM).decode('utf-8')
|
||||
key_pem = private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption()
|
||||
).decode('utf-8')
|
||||
|
||||
# Save to files if requested
|
||||
if save_to_files:
|
||||
cert_file = cert_path or f"{common_name}_cert.pem"
|
||||
key_file = key_path or f"{common_name}_key.pem"
|
||||
|
||||
with open(cert_file, 'w') as f:
|
||||
f.write(cert_pem)
|
||||
|
||||
with open(key_file, 'w') as f:
|
||||
f.write(key_pem)
|
||||
|
||||
# Set appropriate permissions
|
||||
Path(key_file).chmod(0o600)
|
||||
|
||||
return cert_pem, key_pem
|
||||
|
||||
|
||||
def create_tls_config_with_self_signed(
|
||||
common_name: str = "localhost",
|
||||
san_list: Optional[List[str]] = None,
|
||||
alpn: Optional[List[str]] = None,
|
||||
**tls_kwargs
|
||||
) -> Tuple[TLSConfig, str, str]:
|
||||
"""
|
||||
Create TLS configuration with self-signed certificate
|
||||
|
||||
Returns:
|
||||
Tuple of (TLSConfig, certificate_pem, private_key_pem)
|
||||
"""
|
||||
cert_pem, key_pem = generate_self_signed_certificate(
|
||||
common_name=common_name,
|
||||
san_list=san_list
|
||||
)
|
||||
|
||||
# Convert PEM to lines for Xray format
|
||||
cert_lines = cert_pem.strip().split('\n')
|
||||
key_lines = key_pem.strip().split('\n')
|
||||
|
||||
# Create certificate config
|
||||
certificate = Certificate(
|
||||
certificate=cert_lines,
|
||||
key=key_lines,
|
||||
oneTimeLoading=True
|
||||
)
|
||||
|
||||
# Create TLS config
|
||||
tls_config = TLSConfig(
|
||||
serverName=common_name,
|
||||
alpn=alpn or ["h2", "http/1.1"],
|
||||
certificates=[certificate],
|
||||
**tls_kwargs
|
||||
)
|
||||
|
||||
return tls_config, cert_pem, key_pem
|
||||
|
||||
|
||||
# Add missing import
|
||||
try:
|
||||
import ipaddress
|
||||
except ImportError:
|
||||
ipaddress = None
|
241
vpn/xray_api_v2/models/transports.py
Normal file
241
vpn/xray_api_v2/models/transports.py
Normal file
@@ -0,0 +1,241 @@
|
||||
"""Transport configuration models for Xray"""
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Dict, Any, List
|
||||
|
||||
from .base import BaseXrayModel, XrayConfig, TransportProtocol
|
||||
|
||||
|
||||
# TCP Transport
|
||||
@dataclass
|
||||
class TCPSettings(XrayConfig):
|
||||
"""TCP transport settings"""
|
||||
__xray_type__ = "xray.transport.internet.tcp.Config"
|
||||
|
||||
acceptProxyProtocol: bool = False
|
||||
header: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
# KCP Transport
|
||||
@dataclass
|
||||
class KCPSettings(XrayConfig):
|
||||
"""KCP transport settings"""
|
||||
__xray_type__ = "xray.transport.internet.kcp.Config"
|
||||
|
||||
mtu: int = 1350
|
||||
tti: int = 50
|
||||
uplinkCapacity: int = 5
|
||||
downlinkCapacity: int = 20
|
||||
congestion: bool = False
|
||||
readBufferSize: int = 2
|
||||
writeBufferSize: int = 2
|
||||
header: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
# WebSocket Transport
|
||||
@dataclass
|
||||
class WebSocketSettings(XrayConfig):
|
||||
"""WebSocket transport settings"""
|
||||
__xray_type__ = "xray.transport.internet.websocket.Config"
|
||||
|
||||
path: str = "/"
|
||||
headers: Optional[Dict[str, str]] = None
|
||||
acceptProxyProtocol: bool = False
|
||||
|
||||
def to_xray_json(self) -> Dict[str, Any]:
|
||||
"""Convert to Xray format"""
|
||||
config = super().to_xray_json()
|
||||
# Ensure headers is a dict even if empty
|
||||
if self.headers:
|
||||
config["headers"] = self.headers
|
||||
return config
|
||||
|
||||
|
||||
# HTTP/2 Transport
|
||||
@dataclass
|
||||
class HTTPSettings(XrayConfig):
|
||||
"""HTTP/2 transport settings"""
|
||||
__xray_type__ = "xray.transport.internet.http.Config"
|
||||
|
||||
path: str = "/"
|
||||
host: Optional[List[str]] = None
|
||||
method: str = "PUT"
|
||||
headers: Optional[Dict[str, List[str]]] = None
|
||||
|
||||
|
||||
# XHTTP Transport (New)
|
||||
@dataclass
|
||||
class XHTTPSettings(XrayConfig):
|
||||
"""XHTTP transport settings"""
|
||||
__xray_type__ = "xray.transport.internet.xhttp.Config"
|
||||
|
||||
path: str = "/"
|
||||
host: Optional[str] = None
|
||||
method: str = "GET"
|
||||
headers: Optional[Dict[str, Any]] = None
|
||||
mode: str = "auto"
|
||||
|
||||
|
||||
# Domain Socket Transport
|
||||
@dataclass
|
||||
class DomainSocketSettings(XrayConfig):
|
||||
"""Domain socket transport settings"""
|
||||
__xray_type__ = "xray.transport.internet.domainsocket.Config"
|
||||
|
||||
path: str
|
||||
abstract: bool = False
|
||||
padding: bool = False
|
||||
|
||||
|
||||
# QUIC Transport
|
||||
@dataclass
|
||||
class QUICSettings(XrayConfig):
|
||||
"""QUIC transport settings"""
|
||||
__xray_type__ = "xray.transport.internet.quic.Config"
|
||||
|
||||
security: str = "none"
|
||||
key: str = ""
|
||||
header: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
# gRPC Transport
|
||||
@dataclass
|
||||
class GRPCSettings(XrayConfig):
|
||||
"""gRPC transport settings"""
|
||||
__xray_type__ = "xray.transport.internet.grpc.encoding.Config"
|
||||
|
||||
serviceName: str = ""
|
||||
multiMode: bool = False
|
||||
idle_timeout: int = 60
|
||||
health_check_timeout: int = 20
|
||||
permit_without_stream: bool = False
|
||||
initial_windows_size: int = 0
|
||||
|
||||
|
||||
# Stream Settings
|
||||
@dataclass
|
||||
class StreamSettings(BaseXrayModel):
|
||||
"""Stream settings for inbound/outbound"""
|
||||
|
||||
network: TransportProtocol = TransportProtocol.TCP
|
||||
security: Optional[str] = None
|
||||
tlsSettings: Optional[Any] = None
|
||||
xtlsSettings: Optional[Any] = None
|
||||
realitySettings: Optional[Any] = None
|
||||
tcpSettings: Optional[TCPSettings] = None
|
||||
kcpSettings: Optional[KCPSettings] = None
|
||||
wsSettings: Optional[WebSocketSettings] = None
|
||||
httpSettings: Optional[HTTPSettings] = None
|
||||
xhttpSettings: Optional[XHTTPSettings] = None
|
||||
dsSettings: Optional[DomainSocketSettings] = None
|
||||
quicSettings: Optional[QUICSettings] = None
|
||||
grpcSettings: Optional[GRPCSettings] = None
|
||||
sockopt: Optional[Dict[str, Any]] = None
|
||||
|
||||
def to_xray_json(self) -> Dict[str, Any]:
|
||||
"""Convert to Xray format with correct field names"""
|
||||
config = {
|
||||
"network": self.network.value if isinstance(self.network, TransportProtocol) else self.network
|
||||
}
|
||||
|
||||
if self.security:
|
||||
config["security"] = self.security
|
||||
|
||||
# Map transport settings
|
||||
transport_map = {
|
||||
TransportProtocol.TCP: ("tcpSettings", self.tcpSettings),
|
||||
TransportProtocol.KCP: ("kcpSettings", self.kcpSettings),
|
||||
TransportProtocol.WS: ("wsSettings", self.wsSettings),
|
||||
TransportProtocol.HTTP: ("httpSettings", self.httpSettings),
|
||||
TransportProtocol.XHTTP: ("xhttpSettings", self.xhttpSettings),
|
||||
TransportProtocol.DOMAINSOCKET: ("dsSettings", self.dsSettings),
|
||||
TransportProtocol.QUIC: ("quicSettings", self.quicSettings),
|
||||
TransportProtocol.GRPC: ("grpcSettings", self.grpcSettings),
|
||||
}
|
||||
|
||||
network = self.network if isinstance(self.network, TransportProtocol) else TransportProtocol(self.network)
|
||||
field_name, settings = transport_map.get(network, (None, None))
|
||||
|
||||
if field_name and settings:
|
||||
config[field_name] = settings.to_xray_json() if hasattr(settings, 'to_xray_json') else settings
|
||||
|
||||
# Add security settings
|
||||
if self.tlsSettings:
|
||||
config["tlsSettings"] = self.tlsSettings if isinstance(self.tlsSettings, dict) else self.tlsSettings.to_xray_json()
|
||||
if self.xtlsSettings:
|
||||
config["xtlsSettings"] = self.xtlsSettings if isinstance(self.xtlsSettings, dict) else self.xtlsSettings.to_xray_json()
|
||||
if self.realitySettings:
|
||||
config["realitySettings"] = self.realitySettings if isinstance(self.realitySettings, dict) else self.realitySettings.to_xray_json()
|
||||
|
||||
if self.sockopt:
|
||||
config["sockopt"] = self.sockopt
|
||||
|
||||
return config
|
||||
|
||||
|
||||
# Factory functions
|
||||
def create_tcp_stream(
|
||||
security: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> StreamSettings:
|
||||
"""Create TCP stream settings"""
|
||||
return StreamSettings(
|
||||
network=TransportProtocol.TCP,
|
||||
security=security,
|
||||
tcpSettings=TCPSettings(**kwargs) if kwargs else None
|
||||
)
|
||||
|
||||
|
||||
def create_ws_stream(
|
||||
path: str = "/",
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
security: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> StreamSettings:
|
||||
"""Create WebSocket stream settings"""
|
||||
return StreamSettings(
|
||||
network=TransportProtocol.WS,
|
||||
security=security,
|
||||
wsSettings=WebSocketSettings(path=path, headers=headers, **kwargs)
|
||||
)
|
||||
|
||||
|
||||
def create_grpc_stream(
|
||||
service_name: str = "",
|
||||
security: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> StreamSettings:
|
||||
"""Create gRPC stream settings"""
|
||||
return StreamSettings(
|
||||
network=TransportProtocol.GRPC,
|
||||
security=security,
|
||||
grpcSettings=GRPCSettings(serviceName=service_name, **kwargs)
|
||||
)
|
||||
|
||||
|
||||
def create_http_stream(
|
||||
path: str = "/",
|
||||
host: Optional[List[str]] = None,
|
||||
security: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> StreamSettings:
|
||||
"""Create HTTP/2 stream settings"""
|
||||
return StreamSettings(
|
||||
network=TransportProtocol.HTTP,
|
||||
security=security,
|
||||
httpSettings=HTTPSettings(path=path, host=host, **kwargs)
|
||||
)
|
||||
|
||||
|
||||
def create_xhttp_stream(
|
||||
path: str = "/",
|
||||
host: Optional[str] = None,
|
||||
security: Optional[str] = None,
|
||||
mode: str = "auto",
|
||||
**kwargs
|
||||
) -> StreamSettings:
|
||||
"""Create XHTTP stream settings"""
|
||||
return StreamSettings(
|
||||
network=TransportProtocol.XHTTP,
|
||||
security=security,
|
||||
xhttpSettings=XHTTPSettings(path=path, host=host, mode=mode, **kwargs)
|
||||
)
|
Reference in New Issue
Block a user