protocol-capabilities.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. // Pure-function ports of the legacy Inbound class capability predicates
  2. // (canEnableTls, canEnableReality, canEnableTlsFlow, canEnableStream,
  3. // canEnableVisionSeed, isSS2022). Each accepts the minimal
  4. // slice of an InboundFormValues it needs, so the same predicate can be
  5. // called against a partial-row, a full form value, or a hand-built test
  6. // fixture without the caller projecting a whole object.
  7. const TLS_ELIGIBLE_PROTOCOLS = ['vmess', 'vless', 'trojan', 'shadowsocks'];
  8. const TLS_NETWORKS = ['tcp', 'ws', 'http', 'grpc', 'httpupgrade', 'xhttp'];
  9. const REALITY_ELIGIBLE_PROTOCOLS = ['vless', 'trojan'];
  10. const REALITY_NETWORKS = ['tcp', 'http', 'grpc', 'xhttp'];
  11. const STREAM_PROTOCOLS = [
  12. 'vmess',
  13. 'vless',
  14. 'trojan',
  15. 'shadowsocks',
  16. 'hysteria',
  17. 'wireguard',
  18. 'tunnel',
  19. 'masque',
  20. ];
  21. const VISION_FLOW = 'xtls-rprx-vision';
  22. const SS_2022_PREFIX = '2022';
  23. export interface CapabilityProtocolSlice {
  24. protocol: string;
  25. settings?: { encryption?: string; decryption?: string };
  26. streamSettings?: { network?: string; security?: string };
  27. }
  28. export interface CapabilityVlessSlice extends CapabilityProtocolSlice {
  29. settings?: { encryption?: string; decryption?: string; clients?: { flow?: string }[] };
  30. }
  31. export interface CapabilityShadowsocksSlice extends CapabilityProtocolSlice {
  32. settings?: { encryption?: string; method?: string };
  33. }
  34. export function canEnableTls(values: CapabilityProtocolSlice): boolean {
  35. if (values.protocol === 'hysteria' || values.protocol === 'masque') return true;
  36. if (!TLS_ELIGIBLE_PROTOCOLS.includes(values.protocol)) return false;
  37. return TLS_NETWORKS.includes(values.streamSettings?.network ?? '');
  38. }
  39. // An XDRIVE outbound's tlsSettings shape only its HTTPS calls to the storage API (SNI, uTLS
  40. // fingerprint); xray-core 26.10.10 reads them on the dial side, never on a listener.
  41. export function canEnableOutboundTls(values: CapabilityProtocolSlice): boolean {
  42. if (canEnableTls(values)) return true;
  43. return (
  44. TLS_ELIGIBLE_PROTOCOLS.includes(values.protocol) && values.streamSettings?.network === 'xdrive'
  45. );
  46. }
  47. export function canEnableReality(values: CapabilityProtocolSlice): boolean {
  48. if (!REALITY_ELIGIBLE_PROTOCOLS.includes(values.protocol)) return false;
  49. return REALITY_NETWORKS.includes(values.streamSettings?.network ?? '');
  50. }
  51. // VLESS encryption (vlessenc / ML-KEM) is on when encryption or decryption holds
  52. // a generated value (e.g. "mlkem768x25519plus.native.0rtt.<key>") rather than
  53. // the "none"/"" sentinel. The value is never the literal "vlessenc" (that is the
  54. // `xray vlessenc` subcommand). decryption is the server-side value; encryption is
  55. // stored for link generation — either being set means it is on.
  56. function hasVlessEncryption(settings: CapabilityProtocolSlice['settings']): boolean {
  57. const isSet = (v?: string) => v != null && v !== '' && v !== 'none';
  58. return isSet(settings?.encryption) || isSet(settings?.decryption);
  59. }
  60. export function canEnableTlsFlow(values: CapabilityProtocolSlice): boolean {
  61. if (values.protocol !== 'vless') return false;
  62. const network = values.streamSettings?.network;
  63. const security = values.streamSettings?.security;
  64. // Classic XTLS Vision: raw TCP carried over TLS or REALITY.
  65. if (network === 'tcp' && (security === 'tls' || security === 'reality')) return true;
  66. // vlessenc carries Vision over XHTTP without transport TLS.
  67. if (network === 'xhttp' && hasVlessEncryption(values.settings)) return true;
  68. return false;
  69. }
  70. export function canEnableStream(values: { protocol: string }): boolean {
  71. return STREAM_PROTOCOLS.includes(values.protocol);
  72. }
  73. // mtproto and amneziawg are served by an external process/interface, not
  74. // Xray, so the Xray sniffing block does not apply to either. Every other
  75. // inbound supports sniffing.
  76. export function canEnableSniffing(values: { protocol: string }): boolean {
  77. return (
  78. values.protocol !== 'mtproto' && values.protocol !== 'amneziawg' && values.protocol !== 'tuic'
  79. );
  80. }
  81. // Vision seed applies only when XTLS Vision (TCP/TLS) flow is selected
  82. // AND at least one VLESS client uses the vision flow. Excludes UDP variant.
  83. export function canEnableVisionSeed(values: CapabilityVlessSlice): boolean {
  84. if (!canEnableTlsFlow(values)) return false;
  85. const clients = values.settings?.clients;
  86. if (!Array.isArray(clients)) return false;
  87. return clients.some((c) => c?.flow === VISION_FLOW);
  88. }
  89. export function isSS2022(values: CapabilityShadowsocksSlice): boolean {
  90. const method = values.protocol === 'shadowsocks' ? (values.settings?.method ?? '') : '';
  91. return method.substring(0, 4) === SS_2022_PREFIX;
  92. }