2020-11-25 19:01:53 +08:00
package conf
import (
2026-02-04 05:03:48 +08:00
"context"
2021-10-22 00:04:06 -04:00
"encoding/base64"
2023-02-15 16:07:12 +00:00
"encoding/hex"
2020-11-25 19:01:53 +08:00
"encoding/json"
2021-03-06 22:45:12 +08:00
"math"
2021-03-14 07:10:10 +00:00
"net/url"
2026-03-09 20:17:32 +08:00
"os"
2023-02-15 16:07:12 +00:00
"runtime"
2021-03-14 07:10:10 +00:00
"strconv"
2020-11-25 19:01:53 +08:00
"strings"
2023-02-15 16:07:12 +00:00
"syscall"
2026-02-04 05:03:48 +08:00
"time"
2020-11-25 19:01:53 +08:00
2024-06-29 14:32:57 -04:00
"github.com/xtls/xray-core/common/errors"
2023-02-15 16:07:12 +00:00
"github.com/xtls/xray-core/common/net"
2020-12-04 09:36:16 +08:00
"github.com/xtls/xray-core/common/platform/filesystem"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/transport/internet"
2026-03-07 23:42:18 +08:00
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
2026-01-31 21:53:19 +08:00
"github.com/xtls/xray-core/transport/internet/finalmask/header/dns"
"github.com/xtls/xray-core/transport/internet/finalmask/header/dtls"
"github.com/xtls/xray-core/transport/internet/finalmask/header/srtp"
"github.com/xtls/xray-core/transport/internet/finalmask/header/utp"
"github.com/xtls/xray-core/transport/internet/finalmask/header/wechat"
"github.com/xtls/xray-core/transport/internet/finalmask/header/wireguard"
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original"
2026-03-07 23:42:18 +08:00
"github.com/xtls/xray-core/transport/internet/finalmask/noise"
2026-01-13 21:31:51 +08:00
"github.com/xtls/xray-core/transport/internet/finalmask/salamander"
2026-03-08 02:21:35 +08:00
finalsudoku "github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
2026-01-31 21:53:19 +08:00
"github.com/xtls/xray-core/transport/internet/finalmask/xdns"
2026-02-02 16:52:34 +08:00
"github.com/xtls/xray-core/transport/internet/finalmask/xicmp"
2024-03-03 13:12:38 +08:00
"github.com/xtls/xray-core/transport/internet/httpupgrade"
2026-01-13 21:31:51 +08:00
"github.com/xtls/xray-core/transport/internet/hysteria"
2020-12-04 09:36:16 +08:00
"github.com/xtls/xray-core/transport/internet/kcp"
2023-02-15 16:07:12 +00:00
"github.com/xtls/xray-core/transport/internet/reality"
2024-06-18 07:36:36 +02:00
"github.com/xtls/xray-core/transport/internet/splithttp"
2020-12-04 09:36:16 +08:00
"github.com/xtls/xray-core/transport/internet/tcp"
"github.com/xtls/xray-core/transport/internet/tls"
"github.com/xtls/xray-core/transport/internet/websocket"
2023-08-10 04:43:34 +00:00
"google.golang.org/protobuf/proto"
2020-11-25 19:01:53 +08:00
)
var (
tcpHeaderLoader = NewJSONConfigLoader ( ConfigCreatorCache {
"none" : func ( ) interface { } { return new ( NoOpConnectionAuthenticator ) } ,
"http" : func ( ) interface { } { return new ( Authenticator ) } ,
} , "type" , "" )
)
type KCPConfig struct {
Mtu * uint32 ` json:"mtu" `
Tti * uint32 ` json:"tti" `
UpCap * uint32 ` json:"uplinkCapacity" `
DownCap * uint32 ` json:"downlinkCapacity" `
Congestion * bool ` json:"congestion" `
ReadBufferSize * uint32 ` json:"readBufferSize" `
WriteBufferSize * uint32 ` json:"writeBufferSize" `
HeaderConfig json . RawMessage ` json:"header" `
Seed * string ` json:"seed" `
}
// Build implements Buildable.
func ( c * KCPConfig ) Build ( ) ( proto . Message , error ) {
config := new ( kcp . Config )
if c . Mtu != nil {
mtu := * c . Mtu
2026-01-31 21:53:19 +08:00
// if mtu < 576 || mtu > 1460 {
// return nil, errors.New("invalid mKCP MTU size: ", mtu).AtError()
// }
2020-11-25 19:01:53 +08:00
config . Mtu = & kcp . MTU { Value : mtu }
}
if c . Tti != nil {
tti := * c . Tti
2026-03-07 17:41:56 +03:30
if tti < 10 || tti > 5000 {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "invalid mKCP TTI: " , tti ) . AtError ( )
2020-11-25 19:01:53 +08:00
}
config . Tti = & kcp . TTI { Value : tti }
}
if c . UpCap != nil {
config . UplinkCapacity = & kcp . UplinkCapacity { Value : * c . UpCap }
}
if c . DownCap != nil {
config . DownlinkCapacity = & kcp . DownlinkCapacity { Value : * c . DownCap }
}
if c . Congestion != nil {
config . Congestion = * c . Congestion
}
if c . ReadBufferSize != nil {
size := * c . ReadBufferSize
if size > 0 {
config . ReadBuffer = & kcp . ReadBuffer { Size : size * 1024 * 1024 }
} else {
config . ReadBuffer = & kcp . ReadBuffer { Size : 512 * 1024 }
}
}
if c . WriteBufferSize != nil {
size := * c . WriteBufferSize
if size > 0 {
config . WriteBuffer = & kcp . WriteBuffer { Size : size * 1024 * 1024 }
} else {
config . WriteBuffer = & kcp . WriteBuffer { Size : 512 * 1024 }
}
}
2026-01-31 21:53:19 +08:00
if c . HeaderConfig != nil || c . Seed != nil {
return nil , errors . PrintRemovedFeatureError ( "mkcp header & seed" , "finalmask/udp header-* & mkcp-original & mkcp-aes128gcm" )
2020-11-25 19:01:53 +08:00
}
return config , nil
}
type TCPConfig struct {
HeaderConfig json . RawMessage ` json:"header" `
AcceptProxyProtocol bool ` json:"acceptProxyProtocol" `
}
// Build implements Buildable.
func ( c * TCPConfig ) Build ( ) ( proto . Message , error ) {
config := new ( tcp . Config )
if len ( c . HeaderConfig ) > 0 {
headerConfig , _ , err := tcpHeaderLoader . Load ( c . HeaderConfig )
if err != nil {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "invalid TCP header config" ) . Base ( err ) . AtError ( )
2020-11-25 19:01:53 +08:00
}
ts , err := headerConfig . ( Buildable ) . Build ( )
if err != nil {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "invalid TCP header config" ) . Base ( err ) . AtError ( )
2020-11-25 19:01:53 +08:00
}
config . HeaderSettings = serial . ToTypedMessage ( ts )
}
if c . AcceptProxyProtocol {
config . AcceptProxyProtocol = c . AcceptProxyProtocol
}
return config , nil
}
type WebSocketConfig struct {
2024-03-29 02:27:02 -04:00
Host string ` json:"host" `
2020-11-25 19:01:53 +08:00
Path string ` json:"path" `
Headers map [ string ] string ` json:"headers" `
AcceptProxyProtocol bool ` json:"acceptProxyProtocol" `
2024-11-29 10:08:08 +08:00
HeartbeatPeriod uint32 ` json:"heartbeatPeriod" `
2020-11-25 19:01:53 +08:00
}
// Build implements Buildable.
func ( c * WebSocketConfig ) Build ( ) ( proto . Message , error ) {
path := c . Path
2021-03-14 07:10:10 +00:00
var ed uint32
if u , err := url . Parse ( path ) ; err == nil {
if q := u . Query ( ) ; q . Get ( "ed" ) != "" {
Ed , _ := strconv . Atoi ( q . Get ( "ed" ) )
ed = uint32 ( Ed )
q . Del ( "ed" )
u . RawQuery = q . Encode ( )
path = u . String ( )
}
}
2024-12-11 00:58:14 +00:00
// Priority (client): host > serverName > address
for k , v := range c . Headers {
2024-12-17 11:02:51 +00:00
if strings . ToLower ( k ) == "host" {
2024-12-17 16:53:16 +08:00
errors . PrintDeprecatedFeatureWarning ( ` "host" in "headers" ` , ` independent "host" ` )
if c . Host == "" {
c . Host = v
}
delete ( c . Headers , k )
2024-12-11 00:58:14 +00:00
}
2024-03-29 02:27:02 -04:00
}
2020-11-25 19:01:53 +08:00
config := & websocket . Config {
2024-03-17 20:43:19 +00:00
Path : path ,
2024-03-29 02:27:02 -04:00
Host : c . Host ,
Header : c . Headers ,
2024-03-17 20:43:19 +00:00
AcceptProxyProtocol : c . AcceptProxyProtocol ,
Ed : ed ,
2024-11-29 10:08:08 +08:00
HeartbeatPeriod : c . HeartbeatPeriod ,
2020-11-25 19:01:53 +08:00
}
return config , nil
}
2024-03-03 13:12:38 +08:00
type HttpUpgradeConfig struct {
2024-03-27 21:06:56 +08:00
Host string ` json:"host" `
2024-03-29 02:27:02 -04:00
Path string ` json:"path" `
2024-03-23 17:43:07 +00:00
Headers map [ string ] string ` json:"headers" `
2024-03-27 21:06:56 +08:00
AcceptProxyProtocol bool ` json:"acceptProxyProtocol" `
2024-03-03 13:12:38 +08:00
}
// Build implements Buildable.
func ( c * HttpUpgradeConfig ) Build ( ) ( proto . Message , error ) {
2024-03-17 20:43:19 +00:00
path := c . Path
var ed uint32
if u , err := url . Parse ( path ) ; err == nil {
if q := u . Query ( ) ; q . Get ( "ed" ) != "" {
Ed , _ := strconv . Atoi ( q . Get ( "ed" ) )
ed = uint32 ( Ed )
q . Del ( "ed" )
u . RawQuery = q . Encode ( )
path = u . String ( )
}
2024-03-03 13:12:38 +08:00
}
2024-12-11 00:58:14 +00:00
// Priority (client): host > serverName > address
for k := range c . Headers {
if strings . ToLower ( k ) == "host" {
return nil , errors . New ( ` "headers" can't contain "host" ` )
}
2024-03-27 21:06:56 +08:00
}
2024-03-17 20:43:19 +00:00
config := & httpupgrade . Config {
Path : path ,
Host : c . Host ,
2024-03-23 17:43:07 +00:00
Header : c . Headers ,
2024-03-17 20:43:19 +00:00
AcceptProxyProtocol : c . AcceptProxyProtocol ,
Ed : ed ,
2024-03-03 13:12:38 +08:00
}
return config , nil
}
2024-06-18 07:36:36 +02:00
type SplitHTTPConfig struct {
Host string ` json:"host" `
Path string ` json:"path" `
2024-12-11 14:05:39 +00:00
Mode string ` json:"mode" `
2024-06-18 07:36:36 +02:00
Headers map [ string ] string ` json:"headers" `
2024-12-11 14:05:39 +00:00
XPaddingBytes Int32Range ` json:"xPaddingBytes" `
2026-01-31 16:34:13 +03:00
XPaddingObfsMode bool ` json:"xPaddingObfsMode" `
XPaddingKey string ` json:"xPaddingKey" `
XPaddingHeader string ` json:"xPaddingHeader" `
XPaddingPlacement string ` json:"xPaddingPlacement" `
XPaddingMethod string ` json:"xPaddingMethod" `
UplinkHTTPMethod string ` json:"uplinkHTTPMethod" `
SessionPlacement string ` json:"sessionPlacement" `
SessionKey string ` json:"sessionKey" `
SeqPlacement string ` json:"seqPlacement" `
SeqKey string ` json:"seqKey" `
UplinkDataPlacement string ` json:"uplinkDataPlacement" `
UplinkDataKey string ` json:"uplinkDataKey" `
2026-03-07 12:34:41 +00:00
UplinkChunkSize Int32Range ` json:"uplinkChunkSize" `
2024-12-11 14:05:39 +00:00
NoGRPCHeader bool ` json:"noGRPCHeader" `
2024-07-29 06:32:04 +00:00
NoSSEHeader bool ` json:"noSSEHeader" `
2024-12-11 14:05:39 +00:00
ScMaxEachPostBytes Int32Range ` json:"scMaxEachPostBytes" `
ScMinPostsIntervalMs Int32Range ` json:"scMinPostsIntervalMs" `
2024-12-15 05:43:10 +00:00
ScMaxBufferedPosts int64 ` json:"scMaxBufferedPosts" `
2025-01-19 13:32:07 +00:00
ScStreamUpServerSecs Int32Range ` json:"scStreamUpServerSecs" `
2026-03-07 12:34:41 +00:00
ServerMaxHeaderBytes int32 ` json:"serverMaxHeaderBytes" `
2024-12-15 05:43:10 +00:00
Xmux XmuxConfig ` json:"xmux" `
2024-10-31 07:31:19 +00:00
DownloadSettings * StreamConfig ` json:"downloadSettings" `
2024-11-11 02:50:39 +00:00
Extra json . RawMessage ` json:"extra" `
2024-09-16 12:42:01 +00:00
}
2024-12-15 05:43:10 +00:00
type XmuxConfig struct {
MaxConcurrency Int32Range ` json:"maxConcurrency" `
MaxConnections Int32Range ` json:"maxConnections" `
CMaxReuseTimes Int32Range ` json:"cMaxReuseTimes" `
HMaxRequestTimes Int32Range ` json:"hMaxRequestTimes" `
2024-12-31 10:00:19 +00:00
HMaxReusableSecs Int32Range ` json:"hMaxReusableSecs" `
2024-12-15 05:43:10 +00:00
HKeepAlivePeriod int64 ` json:"hKeepAlivePeriod" `
2024-08-02 03:16:19 +02:00
}
2024-12-15 05:43:10 +00:00
func newRangeConfig ( input Int32Range ) * splithttp . RangeConfig {
return & splithttp . RangeConfig {
2024-08-02 03:16:19 +02:00
From : input . From ,
To : input . To ,
}
2024-06-18 07:36:36 +02:00
}
// Build implements Buildable.
func ( c * SplitHTTPConfig ) Build ( ) ( proto . Message , error ) {
2024-11-11 02:50:39 +00:00
if c . Extra != nil {
var extra SplitHTTPConfig
if err := json . Unmarshal ( c . Extra , & extra ) ; err != nil {
return nil , errors . New ( ` Failed to unmarshal "extra". ` ) . Base ( err )
}
extra . Host = c . Host
extra . Path = c . Path
extra . Mode = c . Mode
c = & extra
}
2024-12-11 14:05:39 +00:00
switch c . Mode {
case "" :
c . Mode = "auto"
case "auto" , "packet-up" , "stream-up" , "stream-one" :
default :
return nil , errors . New ( "unsupported mode: " + c . Mode )
}
2024-12-11 00:58:14 +00:00
// Priority (client): host > serverName > address
for k := range c . Headers {
if strings . ToLower ( k ) == "host" {
return nil , errors . New ( ` "headers" can't contain "host" ` )
}
2024-06-18 07:36:36 +02:00
}
2024-09-16 12:42:01 +00:00
2025-01-19 13:32:07 +00:00
if c . XPaddingBytes != ( Int32Range { } ) && ( c . XPaddingBytes . From <= 0 || c . XPaddingBytes . To <= 0 ) {
return nil , errors . New ( "xPaddingBytes cannot be disabled" )
}
2026-01-31 16:34:13 +03:00
if c . XPaddingKey == "" {
c . XPaddingKey = "x_padding"
}
if c . XPaddingHeader == "" {
c . XPaddingHeader = "X-Padding"
}
switch c . XPaddingPlacement {
case "" :
c . XPaddingPlacement = "queryInHeader"
case "cookie" , "header" , "query" , "queryInHeader" :
default :
return nil , errors . New ( "unsupported padding placement: " + c . XPaddingPlacement )
}
switch c . XPaddingMethod {
case "" :
c . XPaddingMethod = "repeat-x"
case "repeat-x" , "tokenish" :
default :
return nil , errors . New ( "unsupported padding method: " + c . XPaddingMethod )
}
switch c . UplinkDataPlacement {
case "" :
2026-03-07 12:34:41 +00:00
c . UplinkDataPlacement = splithttp . PlacementAuto
case splithttp . PlacementAuto , splithttp . PlacementBody :
case splithttp . PlacementCookie , splithttp . PlacementHeader :
2026-01-31 16:34:13 +03:00
if c . Mode != "packet-up" {
return nil , errors . New ( "UplinkDataPlacement can be " + c . UplinkDataPlacement + " only in packet-up mode" )
}
default :
return nil , errors . New ( "unsupported uplink data placement: " + c . UplinkDataPlacement )
}
if c . UplinkHTTPMethod == "" {
c . UplinkHTTPMethod = "POST"
}
c . UplinkHTTPMethod = strings . ToUpper ( c . UplinkHTTPMethod )
if c . UplinkHTTPMethod == "GET" && c . Mode != "packet-up" {
return nil , errors . New ( "uplinkHTTPMethod can be GET only in packet-up mode" )
}
switch c . SessionPlacement {
case "" :
c . SessionPlacement = "path"
2026-02-02 11:07:45 +03:00
case "path" , "cookie" , "header" , "query" :
2026-01-31 16:34:13 +03:00
default :
return nil , errors . New ( "unsupported session placement: " + c . SessionPlacement )
}
switch c . SeqPlacement {
case "" :
c . SeqPlacement = "path"
2026-02-02 11:07:45 +03:00
case "path" , "cookie" , "header" , "query" :
2026-01-31 16:34:13 +03:00
default :
return nil , errors . New ( "unsupported seq placement: " + c . SeqPlacement )
}
if c . SessionPlacement != "path" && c . SessionKey == "" {
switch c . SessionPlacement {
case "cookie" , "query" :
c . SessionKey = "x_session"
case "header" :
c . SessionKey = "X-Session"
}
}
if c . SeqPlacement != "path" && c . SeqKey == "" {
switch c . SeqPlacement {
case "cookie" , "query" :
c . SeqKey = "x_seq"
case "header" :
c . SeqKey = "X-Seq"
}
}
2026-03-07 12:34:41 +00:00
if c . UplinkDataPlacement != splithttp . PlacementBody && c . UplinkDataKey == "" {
2026-01-31 16:34:13 +03:00
switch c . UplinkDataPlacement {
2026-03-07 12:34:41 +00:00
case splithttp . PlacementCookie :
2026-01-31 16:34:13 +03:00
c . UplinkDataKey = "x_data"
2026-03-07 12:34:41 +00:00
case splithttp . PlacementAuto , splithttp . PlacementHeader :
2026-01-31 16:34:13 +03:00
c . UplinkDataKey = "X-Data"
}
}
2026-03-07 12:34:41 +00:00
if c . ServerMaxHeaderBytes < 0 {
return nil , errors . New ( "invalid negative value of maxHeaderBytes" )
2026-01-31 16:34:13 +03:00
}
2024-12-11 14:05:39 +00:00
if c . Xmux . MaxConnections . To > 0 && c . Xmux . MaxConcurrency . To > 0 {
2024-09-16 12:42:01 +00:00
return nil , errors . New ( "maxConnections cannot be specified together with maxConcurrency" )
}
2024-12-15 05:43:10 +00:00
if c . Xmux == ( XmuxConfig { } ) {
2025-10-14 23:33:06 +00:00
c . Xmux . MaxConcurrency . From = 1
c . Xmux . MaxConcurrency . To = 1
2024-12-31 10:00:19 +00:00
c . Xmux . HMaxRequestTimes . From = 600
2024-12-15 05:43:10 +00:00
c . Xmux . HMaxRequestTimes . To = 900
2024-12-31 10:00:19 +00:00
c . Xmux . HMaxReusableSecs . From = 1800
c . Xmux . HMaxReusableSecs . To = 3000
2024-11-09 11:05:41 +00:00
}
2024-06-18 07:36:36 +02:00
config := & splithttp . Config {
2024-08-02 03:16:19 +02:00
Host : c . Host ,
2024-12-11 14:05:39 +00:00
Path : c . Path ,
2024-11-09 11:05:41 +00:00
Mode : c . Mode ,
2024-12-11 14:05:39 +00:00
Headers : c . Headers ,
2024-12-15 05:43:10 +00:00
XPaddingBytes : newRangeConfig ( c . XPaddingBytes ) ,
2026-01-31 16:34:13 +03:00
XPaddingObfsMode : c . XPaddingObfsMode ,
XPaddingKey : c . XPaddingKey ,
XPaddingHeader : c . XPaddingHeader ,
XPaddingPlacement : c . XPaddingPlacement ,
XPaddingMethod : c . XPaddingMethod ,
UplinkHTTPMethod : c . UplinkHTTPMethod ,
SessionPlacement : c . SessionPlacement ,
SeqPlacement : c . SeqPlacement ,
SessionKey : c . SessionKey ,
SeqKey : c . SeqKey ,
UplinkDataPlacement : c . UplinkDataPlacement ,
UplinkDataKey : c . UplinkDataKey ,
2026-03-07 12:34:41 +00:00
UplinkChunkSize : newRangeConfig ( c . UplinkChunkSize ) ,
2024-11-21 05:45:49 +00:00
NoGRPCHeader : c . NoGRPCHeader ,
2024-12-11 14:05:39 +00:00
NoSSEHeader : c . NoSSEHeader ,
2024-12-15 05:43:10 +00:00
ScMaxEachPostBytes : newRangeConfig ( c . ScMaxEachPostBytes ) ,
ScMinPostsIntervalMs : newRangeConfig ( c . ScMinPostsIntervalMs ) ,
2024-12-11 14:05:39 +00:00
ScMaxBufferedPosts : c . ScMaxBufferedPosts ,
2025-01-19 13:32:07 +00:00
ScStreamUpServerSecs : newRangeConfig ( c . ScStreamUpServerSecs ) ,
2026-03-07 12:34:41 +00:00
ServerMaxHeaderBytes : c . ServerMaxHeaderBytes ,
2024-12-15 05:43:10 +00:00
Xmux : & splithttp . XmuxConfig {
MaxConcurrency : newRangeConfig ( c . Xmux . MaxConcurrency ) ,
MaxConnections : newRangeConfig ( c . Xmux . MaxConnections ) ,
CMaxReuseTimes : newRangeConfig ( c . Xmux . CMaxReuseTimes ) ,
HMaxRequestTimes : newRangeConfig ( c . Xmux . HMaxRequestTimes ) ,
2024-12-31 10:00:19 +00:00
HMaxReusableSecs : newRangeConfig ( c . Xmux . HMaxReusableSecs ) ,
2024-12-15 05:43:10 +00:00
HKeepAlivePeriod : c . Xmux . HKeepAlivePeriod ,
2024-12-11 14:05:39 +00:00
} ,
2024-06-18 07:36:36 +02:00
}
2024-12-11 14:05:39 +00:00
2024-10-31 07:31:19 +00:00
if c . DownloadSettings != nil {
2024-11-27 20:19:18 +00:00
if c . Mode == "stream-one" {
return nil , errors . New ( ` Can not use "downloadSettings" in "stream-one" mode. ` )
}
2024-12-11 14:05:39 +00:00
var err error
2024-10-31 07:31:19 +00:00
if config . DownloadSettings , err = c . DownloadSettings . Build ( ) ; err != nil {
return nil , errors . New ( ` Failed to build "downloadSettings". ` ) . Base ( err )
}
}
2024-12-11 14:05:39 +00:00
2024-06-18 07:36:36 +02:00
return config , nil
}
2026-01-13 21:31:51 +08:00
const (
Byte = 1
Kilobyte = 1024 * Byte
Megabyte = 1024 * Kilobyte
Gigabyte = 1024 * Megabyte
Terabyte = 1024 * Gigabyte
)
type Bandwidth string
func ( b Bandwidth ) Bps ( ) ( uint64 , error ) {
s := strings . TrimSpace ( strings . ToLower ( string ( b ) ) )
if s == "" {
return 0 , nil
}
idx := len ( s )
for i , c := range s {
if ( c < '0' || c > '9' ) && c != '.' {
idx = i
break
}
}
numStr := s [ : idx ]
unit := strings . TrimSpace ( s [ idx : ] )
val , err := strconv . ParseFloat ( numStr , 64 )
if err != nil {
return 0 , err
}
mul := uint64 ( 1 )
switch unit {
case "" , "b" , "bps" :
mul = Byte
case "k" , "kb" , "kbps" :
mul = Kilobyte
case "m" , "mb" , "mbps" :
mul = Megabyte
case "g" , "gb" , "gbps" :
mul = Gigabyte
case "t" , "tb" , "tbps" :
mul = Terabyte
default :
return 0 , errors . New ( "unsupported unit: " + unit )
}
return uint64 ( val * float64 ( mul ) ) / 8 , nil
}
type UdpHop struct {
2026-03-09 20:17:32 +08:00
PortList json . RawMessage ` json:"ports" `
2026-01-26 02:28:51 +08:00
Interval * Int32Range ` json:"interval" `
2026-01-13 21:31:51 +08:00
}
2026-02-12 22:56:06 +08:00
type Masquerade struct {
Type string ` json:"type" `
Dir string ` json:"dir" `
Url string ` json:"url" `
RewriteHost bool ` json:"rewriteHost" `
Insecure bool ` json:"insecure" `
Content string ` json:"content" `
Headers map [ string ] string ` json:"headers" `
StatusCode int32 ` json:"statusCode" `
}
2026-01-13 21:31:51 +08:00
type HysteriaConfig struct {
2026-03-09 20:17:32 +08:00
Version int32 ` json:"version" `
Auth string ` json:"auth" `
Congestion * string ` json:"congestion" `
Up * Bandwidth ` json:"up" `
Down * Bandwidth ` json:"down" `
UdpHop * UdpHop ` json:"udphop" `
2026-02-12 22:56:06 +08:00
UdpIdleTimeout int64 ` json:"udpIdleTimeout" `
Masquerade Masquerade ` json:"masquerade" `
2026-01-13 21:31:51 +08:00
}
func ( c * HysteriaConfig ) Build ( ) ( proto . Message , error ) {
if c . Version != 2 {
return nil , errors . New ( "version != 2" )
}
2026-01-17 21:29:50 +08:00
2026-03-09 20:17:32 +08:00
if c . Congestion != nil || c . Up != nil || c . Down != nil || c . UdpHop != nil {
errors . LogWarning ( context . Background ( ) , "congestion & up & down & udphop move to finalmask/quicParams" )
2026-01-13 21:31:51 +08:00
}
2026-02-12 22:56:06 +08:00
if c . UdpIdleTimeout != 0 && ( c . UdpIdleTimeout < 2 || c . UdpIdleTimeout > 600 ) {
return nil , errors . New ( "UdpIdleTimeout must be between 2 and 600" )
}
2026-01-13 21:31:51 +08:00
config := & hysteria . Config { }
2026-01-14 18:42:07 +08:00
config . Version = c . Version
2026-01-13 21:31:51 +08:00
config . Auth = c . Auth
2026-02-12 22:56:06 +08:00
config . UdpIdleTimeout = c . UdpIdleTimeout
config . MasqType = c . Masquerade . Type
config . MasqFile = c . Masquerade . Dir
config . MasqUrl = c . Masquerade . Url
config . MasqUrlRewriteHost = c . Masquerade . RewriteHost
config . MasqUrlInsecure = c . Masquerade . Insecure
config . MasqString = c . Masquerade . Content
config . MasqStringHeaders = c . Masquerade . Headers
config . MasqStringStatusCode = c . Masquerade . StatusCode
2026-01-13 21:31:51 +08:00
2026-02-12 22:56:06 +08:00
if config . UdpIdleTimeout == 0 {
config . UdpIdleTimeout = 60
}
2026-01-13 21:31:51 +08:00
return config , nil
}
2020-11-25 19:01:53 +08:00
func readFileOrString ( f string , s [ ] string ) ( [ ] byte , error ) {
if len ( f ) > 0 {
2025-03-24 16:26:48 +03:30
return filesystem . ReadCert ( f )
2020-11-25 19:01:53 +08:00
}
if len ( s ) > 0 {
return [ ] byte ( strings . Join ( s , "\n" ) ) , nil
}
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "both file and bytes are empty." )
2020-11-25 19:01:53 +08:00
}
type TLSCertConfig struct {
2021-02-20 02:15:57 +00:00
CertFile string ` json:"certificateFile" `
CertStr [ ] string ` json:"certificate" `
KeyFile string ` json:"keyFile" `
KeyStr [ ] string ` json:"key" `
Usage string ` json:"usage" `
OcspStapling uint64 ` json:"ocspStapling" `
OneTimeLoading bool ` json:"oneTimeLoading" `
2024-07-29 14:58:58 +08:00
BuildChain bool ` json:"buildChain" `
2020-11-25 19:01:53 +08:00
}
// Build implements Buildable.
func ( c * TLSCertConfig ) Build ( ) ( * tls . Certificate , error ) {
certificate := new ( tls . Certificate )
cert , err := readFileOrString ( c . CertFile , c . CertStr )
if err != nil {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "failed to parse certificate" ) . Base ( err )
2020-11-25 19:01:53 +08:00
}
certificate . Certificate = cert
2021-02-12 23:33:19 +08:00
certificate . CertificatePath = c . CertFile
2020-11-25 19:01:53 +08:00
if len ( c . KeyFile ) > 0 || len ( c . KeyStr ) > 0 {
key , err := readFileOrString ( c . KeyFile , c . KeyStr )
if err != nil {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "failed to parse key" ) . Base ( err )
2020-11-25 19:01:53 +08:00
}
certificate . Key = key
2021-02-12 23:33:19 +08:00
certificate . KeyPath = c . KeyFile
2020-11-25 19:01:53 +08:00
}
switch strings . ToLower ( c . Usage ) {
case "encipherment" :
certificate . Usage = tls . Certificate_ENCIPHERMENT
case "verify" :
certificate . Usage = tls . Certificate_AUTHORITY_VERIFY
case "issue" :
certificate . Usage = tls . Certificate_AUTHORITY_ISSUE
default :
certificate . Usage = tls . Certificate_ENCIPHERMENT
}
2021-02-20 02:15:57 +00:00
if certificate . KeyPath == "" && certificate . CertificatePath == "" {
certificate . OneTimeLoading = true
} else {
certificate . OneTimeLoading = c . OneTimeLoading
}
2020-12-25 16:01:20 +08:00
certificate . OcspStapling = c . OcspStapling
2024-07-29 14:58:58 +08:00
certificate . BuildChain = c . BuildChain
2020-12-25 15:10:12 +00:00
2020-11-25 19:01:53 +08:00
return certificate , nil
}
2026-03-07 16:46:40 +04:00
type QuicParamsConfig struct {
2026-03-09 20:17:32 +08:00
Congestion string ` json:"congestion" `
Debug bool ` json:"debug" `
BrutalUp Bandwidth ` json:"brutalUp" `
BrutalDown Bandwidth ` json:"brutalDown" `
UdpHop UdpHop ` json:"udpHop" `
InitStreamReceiveWindow uint64 ` json:"initStreamReceiveWindow" `
MaxStreamReceiveWindow uint64 ` json:"maxStreamReceiveWindow" `
InitConnectionReceiveWindow uint64 ` json:"initConnectionReceiveWindow" `
MaxConnectionReceiveWindow uint64 ` json:"maxConnectionReceiveWindow" `
MaxIdleTimeout int64 ` json:"maxIdleTimeout" `
KeepAlivePeriod int64 ` json:"keepAlivePeriod" `
DisablePathMTUDiscovery bool ` json:"disablePathMTUDiscovery" `
MaxIncomingStreams int64 ` json:"maxIncomingStreams" `
2026-03-07 16:46:40 +04:00
}
2020-11-25 19:01:53 +08:00
type TLSConfig struct {
2026-01-30 22:15:46 +00:00
AllowInsecure bool ` json:"allowInsecure" `
2026-01-09 08:11:24 +08:00
Certs [ ] * TLSCertConfig ` json:"certificates" `
ServerName string ` json:"serverName" `
ALPN * StringList ` json:"alpn" `
EnableSessionResumption bool ` json:"enableSessionResumption" `
DisableSystemRoot bool ` json:"disableSystemRoot" `
MinVersion string ` json:"minVersion" `
MaxVersion string ` json:"maxVersion" `
CipherSuites string ` json:"cipherSuites" `
Fingerprint string ` json:"fingerprint" `
RejectUnknownSNI bool ` json:"rejectUnknownSni" `
CurvePreferences * StringList ` json:"curvePreferences" `
MasterKeyLog string ` json:"masterKeyLog" `
2026-01-30 22:15:46 +00:00
PinnedPeerCertSha256 string ` json:"pinnedPeerCertSha256" `
VerifyPeerCertByName string ` json:"verifyPeerCertByName" `
2026-01-09 08:11:24 +08:00
VerifyPeerCertInNames [ ] string ` json:"verifyPeerCertInNames" `
ECHServerKeys string ` json:"echServerKeys" `
ECHConfigList string ` json:"echConfigList" `
ECHForceQuery string ` json:"echForceQuery" `
ECHSocketSettings * SocketConfig ` json:"echSockopt" `
2020-11-25 19:01:53 +08:00
}
// Build implements Buildable.
func ( c * TLSConfig ) Build ( ) ( proto . Message , error ) {
config := new ( tls . Config )
config . Certificate = make ( [ ] * tls . Certificate , len ( c . Certs ) )
for idx , certConf := range c . Certs {
cert , err := certConf . Build ( )
if err != nil {
return nil , err
}
config . Certificate [ idx ] = cert
}
serverName := c . ServerName
if len ( c . ServerName ) > 0 {
config . ServerName = serverName
}
if c . ALPN != nil && len ( * c . ALPN ) > 0 {
config . NextProtocol = [ ] string ( * c . ALPN )
}
2025-02-08 12:11:25 +00:00
if len ( config . NextProtocol ) > 1 {
for _ , p := range config . NextProtocol {
2025-08-02 17:47:55 +02:00
if tls . IsFromMitm ( p ) {
2025-02-08 12:11:25 +00:00
return nil , errors . New ( ` only one element is allowed in "alpn" when using "fromMitm" in it ` )
}
}
}
2024-11-11 12:21:28 +08:00
if c . CurvePreferences != nil && len ( * c . CurvePreferences ) > 0 {
config . CurvePreferences = [ ] string ( * c . CurvePreferences )
}
2021-01-01 11:33:09 +00:00
config . EnableSessionResumption = c . EnableSessionResumption
2020-11-25 19:01:53 +08:00
config . DisableSystemRoot = c . DisableSystemRoot
2020-12-16 15:59:04 +00:00
config . MinVersion = c . MinVersion
config . MaxVersion = c . MaxVersion
config . CipherSuites = c . CipherSuites
2021-03-29 10:08:29 +00:00
config . Fingerprint = strings . ToLower ( c . Fingerprint )
2024-12-17 11:02:51 +00:00
if config . Fingerprint != "unsafe" && tls . GetFingerprint ( config . Fingerprint ) == nil {
2025-02-08 12:11:25 +00:00
return nil , errors . New ( ` unknown "fingerprint": ` , config . Fingerprint )
2023-02-01 12:58:17 +00:00
}
2021-05-09 23:47:21 +08:00
config . RejectUnknownSni = c . RejectUnknownSNI
2026-01-30 22:15:46 +00:00
config . MasterKeyLog = c . MasterKeyLog
2021-10-22 00:04:06 -04:00
2026-01-30 22:15:46 +00:00
if c . AllowInsecure {
2026-02-04 05:03:48 +08:00
if time . Now ( ) . After ( time . Date ( 2026 , 6 , 1 , 0 , 0 , 0 , 0 , time . UTC ) ) {
return nil , errors . PrintRemovedFeatureError ( ` "allowInsecure" ` , ` "pinnedPeerCertSha256" ` )
} else {
errors . LogWarning ( context . Background ( ) , ` "allowInsecure" will be removed automatically after 2026-06-01, please use "pinnedPeerCertSha256"(pcs) and "verifyPeerCertByName"(vcn) instead, PLEASE CONTACT YOUR SERVICE PROVIDER (AIRPORT) ` )
config . AllowInsecure = true
}
2026-01-30 22:15:46 +00:00
}
2026-01-09 08:11:24 +08:00
if c . PinnedPeerCertSha256 != "" {
2026-01-30 22:15:46 +00:00
for v := range strings . SplitSeq ( c . PinnedPeerCertSha256 , "," ) {
2026-01-09 08:11:24 +08:00
v = strings . TrimSpace ( v )
if v == "" {
continue
2021-10-22 00:04:06 -04:00
}
2026-01-31 21:11:36 +08:00
// remove colons for OpenSSL format
hashValue , err := hex . DecodeString ( strings . ReplaceAll ( v , ":" , "" ) )
2023-02-17 16:01:24 +08:00
if err != nil {
return nil , err
}
2026-01-31 21:11:36 +08:00
if len ( hashValue ) != 32 {
return nil , errors . New ( "incorrect pinnedPeerCertSha256 length: " , v )
}
2026-01-09 08:11:24 +08:00
config . PinnedPeerCertSha256 = append ( config . PinnedPeerCertSha256 , hashValue )
2023-02-17 16:01:24 +08:00
}
}
2026-01-30 22:15:46 +00:00
if c . VerifyPeerCertInNames != nil {
return nil , errors . PrintRemovedFeatureError ( ` "verifyPeerCertInNames" ` , ` "verifyPeerCertByName" ` )
}
if c . VerifyPeerCertByName != "" {
for v := range strings . SplitSeq ( c . VerifyPeerCertByName , "," ) {
v = strings . TrimSpace ( v )
if v == "" {
continue
}
config . VerifyPeerCertByName = append ( config . VerifyPeerCertByName , v )
}
2025-01-25 10:51:44 +00:00
}
2023-11-27 10:08:34 -05:00
2025-07-26 16:47:27 +08:00
if c . ECHServerKeys != "" {
EchPrivateKey , err := base64 . StdEncoding . DecodeString ( c . ECHServerKeys )
if err != nil {
return nil , errors . New ( "invalid ECH Config" , c . ECHServerKeys )
}
config . EchServerKeys = EchPrivateKey
}
2025-08-03 18:15:42 +08:00
switch c . ECHForceQuery {
case "none" , "half" , "full" , "" :
config . EchForceQuery = c . ECHForceQuery
default :
return nil , errors . New ( ` invalid "echForceQuery": ` , c . ECHForceQuery )
}
2025-08-01 19:25:15 +08:00
config . EchForceQuery = c . ECHForceQuery
config . EchConfigList = c . ECHConfigList
2025-08-02 17:47:55 +02:00
if c . ECHSocketSettings != nil {
ss , err := c . ECHSocketSettings . Build ( )
if err != nil {
return nil , errors . New ( "Failed to build ech sockopt." ) . Base ( err )
}
config . EchSocketSettings = ss
}
2025-07-26 16:47:27 +08:00
2020-11-25 19:01:53 +08:00
return config , nil
}
2025-06-08 21:43:55 +08:00
type LimitFallback struct {
AfterBytes uint64
BytesPerSec uint64
BurstBytesPerSec uint64
}
2023-02-15 16:07:12 +00:00
type REALITYConfig struct {
2024-01-11 14:36:13 +08:00
MasterKeyLog string ` json:"masterKeyLog" `
2024-10-18 02:18:06 +00:00
Show bool ` json:"show" `
2024-10-05 00:13:46 +00:00
Target json . RawMessage ` json:"target" `
2024-10-18 02:18:06 +00:00
Dest json . RawMessage ` json:"dest" `
2023-02-15 16:07:12 +00:00
Type string ` json:"type" `
Xver uint64 ` json:"xver" `
ServerNames [ ] string ` json:"serverNames" `
PrivateKey string ` json:"privateKey" `
MinClientVer string ` json:"minClientVer" `
MaxClientVer string ` json:"maxClientVer" `
MaxTimeDiff uint64 ` json:"maxTimeDiff" `
ShortIds [ ] string ` json:"shortIds" `
2025-07-23 02:29:11 +00:00
Mldsa65Seed string ` json:"mldsa65Seed" `
2023-02-15 16:07:12 +00:00
2025-06-08 21:43:55 +08:00
LimitFallbackUpload LimitFallback ` json:"limitFallbackUpload" `
LimitFallbackDownload LimitFallback ` json:"limitFallbackDownload" `
2025-07-23 02:29:11 +00:00
Fingerprint string ` json:"fingerprint" `
ServerName string ` json:"serverName" `
Password string ` json:"password" `
PublicKey string ` json:"publicKey" `
ShortId string ` json:"shortId" `
Mldsa65Verify string ` json:"mldsa65Verify" `
SpiderX string ` json:"spiderX" `
2023-02-15 16:07:12 +00:00
}
func ( c * REALITYConfig ) Build ( ) ( proto . Message , error ) {
config := new ( reality . Config )
2024-01-11 14:36:13 +08:00
config . MasterKeyLog = c . MasterKeyLog
2024-10-18 02:18:06 +00:00
config . Show = c . Show
2023-02-15 16:07:12 +00:00
var err error
2024-10-18 02:18:06 +00:00
if c . Target != nil {
2024-10-05 00:13:46 +00:00
c . Dest = c . Target
}
2023-02-15 16:07:12 +00:00
if c . Dest != nil {
var i uint16
var s string
if err = json . Unmarshal ( c . Dest , & i ) ; err == nil {
s = strconv . Itoa ( int ( i ) )
} else {
_ = json . Unmarshal ( c . Dest , & s )
}
if c . Type == "" && s != "" {
switch s [ 0 ] {
case '@' , '/' :
c . Type = "unix"
if s [ 0 ] == '@' && len ( s ) > 1 && s [ 1 ] == '@' && ( runtime . GOOS == "linux" || runtime . GOOS == "android" ) {
fullAddr := make ( [ ] byte , len ( syscall . RawSockaddrUnix { } . Path ) ) // may need padding to work with haproxy
copy ( fullAddr , s [ 1 : ] )
s = string ( fullAddr )
}
default :
if _ , err = strconv . Atoi ( s ) ; err == nil {
2025-07-19 08:47:43 +08:00
s = "localhost:" + s
2023-02-15 16:07:12 +00:00
}
if _ , _ , err = net . SplitHostPort ( s ) ; err == nil {
c . Type = "tcp"
}
}
}
if c . Type == "" {
2024-10-18 02:18:06 +00:00
return nil , errors . New ( ` please fill in a valid value for "target" ` )
2023-02-15 16:07:12 +00:00
}
if c . Xver > 2 {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( ` invalid PROXY protocol version, "xver" only accepts 0, 1, 2 ` )
2023-02-15 16:07:12 +00:00
}
if len ( c . ServerNames ) == 0 {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( ` empty "serverNames" ` )
2023-02-15 16:07:12 +00:00
}
if c . PrivateKey == "" {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( ` empty "privateKey" ` )
2023-02-15 16:07:12 +00:00
}
if config . PrivateKey , err = base64 . RawURLEncoding . DecodeString ( c . PrivateKey ) ; err != nil || len ( config . PrivateKey ) != 32 {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( ` invalid "privateKey": ` , c . PrivateKey )
2023-02-15 16:07:12 +00:00
}
if c . MinClientVer != "" {
config . MinClientVer = make ( [ ] byte , 3 )
var u uint64
for i , s := range strings . Split ( c . MinClientVer , "." ) {
if i == 3 {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( ` invalid "minClientVer": ` , c . MinClientVer )
2023-02-15 16:07:12 +00:00
}
if u , err = strconv . ParseUint ( s , 10 , 8 ) ; err != nil {
2024-11-06 17:27:06 +03:00
return nil , errors . New ( ` "minClientVer[ ` , i , ` ]" should be less than 256 ` )
2023-02-15 16:07:12 +00:00
} else {
config . MinClientVer [ i ] = byte ( u )
}
}
}
if c . MaxClientVer != "" {
config . MaxClientVer = make ( [ ] byte , 3 )
var u uint64
for i , s := range strings . Split ( c . MaxClientVer , "." ) {
if i == 3 {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( ` invalid "maxClientVer": ` , c . MaxClientVer )
2023-02-15 16:07:12 +00:00
}
if u , err = strconv . ParseUint ( s , 10 , 8 ) ; err != nil {
2024-11-06 17:27:06 +03:00
return nil , errors . New ( ` "maxClientVer[ ` , i , ` ]" should be less than 256 ` )
2023-02-15 16:07:12 +00:00
} else {
config . MaxClientVer [ i ] = byte ( u )
}
}
}
if len ( c . ShortIds ) == 0 {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( ` empty "shortIds" ` )
2023-02-15 16:07:12 +00:00
}
config . ShortIds = make ( [ ] [ ] byte , len ( c . ShortIds ) )
for i , s := range c . ShortIds {
2025-11-19 03:25:32 +00:00
if len ( s ) > 16 {
return nil , errors . New ( ` too long "shortIds[ ` , i , ` ]": ` , s )
}
2023-02-15 16:07:12 +00:00
config . ShortIds [ i ] = make ( [ ] byte , 8 )
if _ , err = hex . Decode ( config . ShortIds [ i ] , [ ] byte ( s ) ) ; err != nil {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( ` invalid "shortIds[ ` , i , ` ]": ` , s )
2023-02-15 16:07:12 +00:00
}
}
config . Dest = s
config . Type = c . Type
config . Xver = c . Xver
config . ServerNames = c . ServerNames
config . MaxTimeDiff = c . MaxTimeDiff
2025-06-08 21:43:55 +08:00
2025-07-24 09:15:48 +08:00
if c . Mldsa65Seed != "" {
2025-07-26 08:36:58 +00:00
if c . Mldsa65Seed == c . PrivateKey {
return nil , errors . New ( ` "mldsa65Seed" and "privateKey" can not be the same value: ` , c . Mldsa65Seed )
}
2025-07-24 01:31:07 +00:00
if config . Mldsa65Seed , err = base64 . RawURLEncoding . DecodeString ( c . Mldsa65Seed ) ; err != nil || len ( config . Mldsa65Seed ) != 32 {
2025-07-24 09:15:48 +08:00
return nil , errors . New ( ` invalid "mldsa65Seed": ` , c . Mldsa65Seed )
}
2025-07-23 02:29:11 +00:00
}
2025-06-08 21:43:55 +08:00
config . LimitFallbackUpload = new ( reality . LimitFallback )
config . LimitFallbackUpload . AfterBytes = c . LimitFallbackUpload . AfterBytes
config . LimitFallbackUpload . BytesPerSec = c . LimitFallbackUpload . BytesPerSec
config . LimitFallbackUpload . BurstBytesPerSec = c . LimitFallbackUpload . BurstBytesPerSec
config . LimitFallbackDownload = new ( reality . LimitFallback )
config . LimitFallbackDownload . AfterBytes = c . LimitFallbackDownload . AfterBytes
config . LimitFallbackDownload . BytesPerSec = c . LimitFallbackDownload . BytesPerSec
config . LimitFallbackDownload . BurstBytesPerSec = c . LimitFallbackDownload . BurstBytesPerSec
2023-02-15 16:07:12 +00:00
} else {
2024-12-17 11:02:51 +00:00
config . Fingerprint = strings . ToLower ( c . Fingerprint )
if config . Fingerprint == "unsafe" || config . Fingerprint == "hellogolang" {
return nil , errors . New ( ` invalid "fingerprint": ` , config . Fingerprint )
2023-02-15 16:07:12 +00:00
}
2024-12-17 11:02:51 +00:00
if tls . GetFingerprint ( config . Fingerprint ) == nil {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( ` unknown "fingerprint": ` , config . Fingerprint )
2023-02-15 16:07:12 +00:00
}
2023-02-21 13:43:13 +00:00
if len ( c . ServerNames ) != 0 {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( ` non-empty "serverNames", please use "serverName" instead ` )
2023-02-21 13:43:13 +00:00
}
2025-03-04 05:29:03 +00:00
if c . Password != "" {
c . PublicKey = c . Password
}
2023-02-15 16:07:12 +00:00
if c . PublicKey == "" {
2025-03-04 05:29:03 +00:00
return nil , errors . New ( ` empty "password" ` )
2023-02-15 16:07:12 +00:00
}
if config . PublicKey , err = base64 . RawURLEncoding . DecodeString ( c . PublicKey ) ; err != nil || len ( config . PublicKey ) != 32 {
2025-03-04 05:29:03 +00:00
return nil , errors . New ( ` invalid "password": ` , c . PublicKey )
2023-02-15 16:07:12 +00:00
}
2023-02-21 13:43:13 +00:00
if len ( c . ShortIds ) != 0 {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( ` non-empty "shortIds", please use "shortId" instead ` )
2023-02-21 13:43:13 +00:00
}
2026-03-19 18:43:33 +08:00
if len ( c . ShortId ) > 16 {
2025-11-19 03:25:32 +00:00
return nil , errors . New ( ` too long "shortId": ` , c . ShortId )
}
2023-02-15 16:07:12 +00:00
config . ShortId = make ( [ ] byte , 8 )
if _ , err = hex . Decode ( config . ShortId , [ ] byte ( c . ShortId ) ) ; err != nil {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( ` invalid "shortId": ` , c . ShortId )
2023-02-15 16:07:12 +00:00
}
2025-07-24 09:15:48 +08:00
if c . Mldsa65Verify != "" {
if config . Mldsa65Verify , err = base64 . RawURLEncoding . DecodeString ( c . Mldsa65Verify ) ; err != nil || len ( config . Mldsa65Verify ) != 1952 {
return nil , errors . New ( ` invalid "mldsa65Verify": ` , c . Mldsa65Verify )
}
2025-07-23 02:29:11 +00:00
}
2023-02-15 16:07:12 +00:00
if c . SpiderX == "" {
2023-02-17 21:07:27 +08:00
c . SpiderX = "/"
2023-02-15 16:07:12 +00:00
}
if c . SpiderX [ 0 ] != '/' {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( ` invalid "spiderX": ` , c . SpiderX )
2023-02-15 16:07:12 +00:00
}
config . SpiderY = make ( [ ] int64 , 10 )
u , _ := url . Parse ( c . SpiderX )
q := u . Query ( )
parse := func ( param string , index int ) {
if q . Get ( param ) != "" {
s := strings . Split ( q . Get ( param ) , "-" )
if len ( s ) == 1 {
config . SpiderY [ index ] , _ = strconv . ParseInt ( s [ 0 ] , 10 , 64 )
config . SpiderY [ index + 1 ] , _ = strconv . ParseInt ( s [ 0 ] , 10 , 64 )
} else {
config . SpiderY [ index ] , _ = strconv . ParseInt ( s [ 0 ] , 10 , 64 )
config . SpiderY [ index + 1 ] , _ = strconv . ParseInt ( s [ 1 ] , 10 , 64 )
}
}
q . Del ( param )
}
parse ( "p" , 0 ) // padding
parse ( "c" , 2 ) // concurrency
parse ( "t" , 4 ) // times
parse ( "i" , 6 ) // interval
parse ( "r" , 8 ) // return
u . RawQuery = q . Encode ( )
config . SpiderX = u . String ( )
config . ServerName = c . ServerName
}
return config , nil
}
2020-11-25 19:01:53 +08:00
type TransportProtocol string
// Build implements Buildable.
func ( p TransportProtocol ) Build ( ) ( string , error ) {
switch strings . ToLower ( string ( p ) ) {
2024-09-28 05:25:59 +00:00
case "raw" , "tcp" :
2020-11-25 19:01:53 +08:00
return "tcp" , nil
2024-12-02 09:56:16 +00:00
case "xhttp" , "splithttp" :
return "splithttp" , nil
2020-11-25 19:01:53 +08:00
case "kcp" , "mkcp" :
return "mkcp" , nil
2024-10-18 01:03:44 +00:00
case "grpc" :
2026-01-23 23:45:20 +08:00
errors . PrintNonRemovalDeprecatedFeatureWarning ( "gRPC transport (with unnecessary costs, etc.)" , "XHTTP stream-up H2" )
2021-03-14 15:02:07 +00:00
return "grpc" , nil
2024-12-02 09:56:16 +00:00
case "ws" , "websocket" :
2026-01-23 23:45:20 +08:00
errors . PrintNonRemovalDeprecatedFeatureWarning ( "WebSocket transport (with ALPN http/1.1, etc.)" , "XHTTP H2 & H3" )
2024-12-02 09:56:16 +00:00
return "websocket" , nil
2024-03-03 13:12:38 +08:00
case "httpupgrade" :
2026-01-23 23:45:20 +08:00
errors . PrintNonRemovalDeprecatedFeatureWarning ( "HTTPUpgrade transport (with ALPN http/1.1, etc.)" , "XHTTP H2 & H3" )
2024-03-03 13:12:38 +08:00
return "httpupgrade" , nil
2024-12-02 09:56:16 +00:00
case "h2" , "h3" , "http" :
return "" , errors . PrintRemovedFeatureError ( "HTTP transport (without header padding, etc.)" , "XHTTP stream-one H2 & H3" )
case "quic" :
return "" , errors . PrintRemovedFeatureError ( "QUIC transport (without web service, etc.)" , "XHTTP stream-one H3" )
2026-01-13 21:31:51 +08:00
case "hysteria" :
return "hysteria" , nil
2020-11-25 19:01:53 +08:00
default :
2024-06-29 14:32:57 -04:00
return "" , errors . New ( "Config: unknown transport protocol: " , p )
2020-11-25 19:01:53 +08:00
}
}
2024-07-10 00:19:31 +08:00
type CustomSockoptConfig struct {
2025-04-18 10:30:47 +08:00
Syetem string ` json:"system" `
2025-03-21 18:48:46 +08:00
Network string ` json:"network" `
Level string ` json:"level" `
Opt string ` json:"opt" `
Value string ` json:"value" `
Type string ` json:"type" `
2024-07-10 00:19:31 +08:00
}
2025-06-07 16:50:06 +03:30
type HappyEyeballsConfig struct {
PrioritizeIPv6 bool ` json:"prioritizeIPv6" `
TryDelayMs uint64 ` json:"tryDelayMs" `
Interleave uint32 ` json:"interleave" `
MaxConcurrentTry uint32 ` json:"maxConcurrentTry" `
}
func ( h * HappyEyeballsConfig ) UnmarshalJSON ( data [ ] byte ) error {
var innerHappyEyeballsConfig = struct {
PrioritizeIPv6 bool ` json:"prioritizeIPv6" `
TryDelayMs uint64 ` json:"tryDelayMs" `
Interleave uint32 ` json:"interleave" `
MaxConcurrentTry uint32 ` json:"maxConcurrentTry" `
} { PrioritizeIPv6 : false , Interleave : 1 , TryDelayMs : 0 , MaxConcurrentTry : 4 }
if err := json . Unmarshal ( data , & innerHappyEyeballsConfig ) ; err != nil {
return err
}
h . PrioritizeIPv6 = innerHappyEyeballsConfig . PrioritizeIPv6
h . TryDelayMs = innerHappyEyeballsConfig . TryDelayMs
h . Interleave = innerHappyEyeballsConfig . Interleave
h . MaxConcurrentTry = innerHappyEyeballsConfig . MaxConcurrentTry
return nil
}
2020-11-25 19:01:53 +08:00
type SocketConfig struct {
2025-06-07 16:50:06 +03:30
Mark int32 ` json:"mark" `
TFO interface { } ` json:"tcpFastOpen" `
TProxy string ` json:"tproxy" `
AcceptProxyProtocol bool ` json:"acceptProxyProtocol" `
DomainStrategy string ` json:"domainStrategy" `
DialerProxy string ` json:"dialerProxy" `
TCPKeepAliveInterval int32 ` json:"tcpKeepAliveInterval" `
TCPKeepAliveIdle int32 ` json:"tcpKeepAliveIdle" `
TCPCongestion string ` json:"tcpCongestion" `
TCPWindowClamp int32 ` json:"tcpWindowClamp" `
TCPMaxSeg int32 ` json:"tcpMaxSeg" `
Penetrate bool ` json:"penetrate" `
TCPUserTimeout int32 ` json:"tcpUserTimeout" `
V6only bool ` json:"v6only" `
Interface string ` json:"interface" `
TcpMptcp bool ` json:"tcpMptcp" `
CustomSockopt [ ] * CustomSockoptConfig ` json:"customSockopt" `
AddressPortStrategy string ` json:"addressPortStrategy" `
HappyEyeballsSettings * HappyEyeballsConfig ` json:"happyEyeballs" `
2025-11-23 01:09:49 +00:00
TrustedXForwardedFor [ ] string ` json:"trustedXForwardedFor" `
2020-11-25 19:01:53 +08:00
}
// Build implements Buildable.
func ( c * SocketConfig ) Build ( ) ( * internet . SocketConfig , error ) {
2021-03-31 00:42:02 +08:00
tfo := int32 ( 0 ) // don't invoke setsockopt() for TFO
2020-11-25 19:01:53 +08:00
if c . TFO != nil {
2021-03-06 22:45:12 +08:00
switch v := c . TFO . ( type ) {
case bool :
if v {
tfo = 256
} else {
2021-03-31 00:42:02 +08:00
tfo = - 1 // TFO need to be disabled
2021-03-06 22:45:12 +08:00
}
case float64 :
tfo = int32 ( math . Min ( v , math . MaxInt32 ) )
default :
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "tcpFastOpen: only boolean and integer value is acceptable" )
2020-11-25 19:01:53 +08:00
}
}
var tproxy internet . SocketConfig_TProxyMode
switch strings . ToLower ( c . TProxy ) {
case "tproxy" :
tproxy = internet . SocketConfig_TProxy
case "redirect" :
tproxy = internet . SocketConfig_Redirect
default :
tproxy = internet . SocketConfig_Off
}
2021-10-19 12:57:14 -04:00
dStrategy := internet . DomainStrategy_AS_IS
2021-03-07 00:29:17 +08:00
switch strings . ToLower ( c . DomainStrategy ) {
2023-11-12 16:37:02 -05:00
case "asis" , "" :
dStrategy = internet . DomainStrategy_AS_IS
case "useip" :
2021-03-07 00:29:17 +08:00
dStrategy = internet . DomainStrategy_USE_IP
2023-11-12 16:37:02 -05:00
case "useipv4" :
2021-03-07 00:29:17 +08:00
dStrategy = internet . DomainStrategy_USE_IP4
2023-11-12 16:37:02 -05:00
case "useipv6" :
2021-03-07 00:29:17 +08:00
dStrategy = internet . DomainStrategy_USE_IP6
2023-11-12 16:37:02 -05:00
case "useipv4v6" :
dStrategy = internet . DomainStrategy_USE_IP46
case "useipv6v4" :
dStrategy = internet . DomainStrategy_USE_IP64
case "forceip" :
dStrategy = internet . DomainStrategy_FORCE_IP
case "forceipv4" :
dStrategy = internet . DomainStrategy_FORCE_IP4
case "forceipv6" :
dStrategy = internet . DomainStrategy_FORCE_IP6
case "forceipv4v6" :
dStrategy = internet . DomainStrategy_FORCE_IP46
case "forceipv6v4" :
dStrategy = internet . DomainStrategy_FORCE_IP64
default :
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "unsupported domain strategy: " , c . DomainStrategy )
2021-03-07 00:29:17 +08:00
}
2024-07-10 00:19:31 +08:00
var customSockopts [ ] * internet . CustomSockopt
for _ , copt := range c . CustomSockopt {
customSockopt := & internet . CustomSockopt {
2025-04-18 10:30:47 +08:00
System : copt . Syetem ,
2025-03-21 18:48:46 +08:00
Network : copt . Network ,
Level : copt . Level ,
Opt : copt . Opt ,
Value : copt . Value ,
Type : copt . Type ,
2024-07-10 00:19:31 +08:00
}
customSockopts = append ( customSockopts , customSockopt )
}
2025-03-02 21:07:55 +08:00
addressPortStrategy := internet . AddressPortStrategy_None
switch strings . ToLower ( c . AddressPortStrategy ) {
case "none" , "" :
addressPortStrategy = internet . AddressPortStrategy_None
case "srvportonly" :
addressPortStrategy = internet . AddressPortStrategy_SrvPortOnly
case "srvaddressonly" :
addressPortStrategy = internet . AddressPortStrategy_SrvAddressOnly
case "srvportandaddress" :
addressPortStrategy = internet . AddressPortStrategy_SrvPortAndAddress
case "txtportonly" :
addressPortStrategy = internet . AddressPortStrategy_TxtPortOnly
case "txtaddressonly" :
addressPortStrategy = internet . AddressPortStrategy_TxtAddressOnly
case "txtportandaddress" :
addressPortStrategy = internet . AddressPortStrategy_TxtPortAndAddress
default :
return nil , errors . New ( "unsupported address and port strategy: " , c . AddressPortStrategy )
}
2025-06-07 16:50:06 +03:30
var happyEyeballs = & internet . HappyEyeballsConfig { Interleave : 1 , PrioritizeIpv6 : false , TryDelayMs : 0 , MaxConcurrentTry : 4 }
if c . HappyEyeballsSettings != nil {
happyEyeballs . PrioritizeIpv6 = c . HappyEyeballsSettings . PrioritizeIPv6
happyEyeballs . Interleave = c . HappyEyeballsSettings . Interleave
happyEyeballs . TryDelayMs = c . HappyEyeballsSettings . TryDelayMs
happyEyeballs . MaxConcurrentTry = c . HappyEyeballsSettings . MaxConcurrentTry
}
2020-11-25 19:01:53 +08:00
return & internet . SocketConfig {
2021-10-12 11:39:08 -04:00
Mark : c . Mark ,
Tfo : tfo ,
Tproxy : tproxy ,
DomainStrategy : dStrategy ,
AcceptProxyProtocol : c . AcceptProxyProtocol ,
DialerProxy : c . DialerProxy ,
TcpKeepAliveInterval : c . TCPKeepAliveInterval ,
2022-07-31 09:55:40 -04:00
TcpKeepAliveIdle : c . TCPKeepAliveIdle ,
2022-12-25 19:37:35 -05:00
TcpCongestion : c . TCPCongestion ,
2023-03-17 13:17:01 +08:00
TcpWindowClamp : c . TCPWindowClamp ,
2023-05-22 04:59:58 +02:00
TcpMaxSeg : c . TCPMaxSeg ,
2024-12-31 11:10:17 +00:00
Penetrate : c . Penetrate ,
2023-03-12 17:44:41 +08:00
TcpUserTimeout : c . TCPUserTimeout ,
2023-02-25 00:54:40 +08:00
V6Only : c . V6only ,
2023-01-09 22:26:23 +08:00
Interface : c . Interface ,
2023-09-08 01:32:27 +08:00
TcpMptcp : c . TcpMptcp ,
2024-07-10 00:19:31 +08:00
CustomSockopt : customSockopts ,
2025-03-02 21:07:55 +08:00
AddressPortStrategy : addressPortStrategy ,
2025-06-07 16:50:06 +03:30
HappyEyeballs : happyEyeballs ,
2025-11-23 01:09:49 +00:00
TrustedXForwardedFor : c . TrustedXForwardedFor ,
2020-11-25 19:01:53 +08:00
} , nil
}
2026-03-07 23:42:18 +08:00
func PraseByteSlice ( data json . RawMessage , typ string ) ( [ ] byte , error ) {
switch strings . ToLower ( typ ) {
case "" , "array" :
if len ( data ) == 0 {
return data , nil
}
var packet [ ] byte
if err := json . Unmarshal ( data , & packet ) ; err != nil {
return nil , err
}
return packet , nil
case "str" :
var str string
if err := json . Unmarshal ( data , & str ) ; err != nil {
return nil , err
}
return [ ] byte ( str ) , nil
case "hex" :
var str string
if err := json . Unmarshal ( data , & str ) ; err != nil {
return nil , err
}
return hex . DecodeString ( str )
case "base64" :
var str string
if err := json . Unmarshal ( data , & str ) ; err != nil {
return nil , err
}
return base64 . StdEncoding . DecodeString ( str )
default :
return nil , errors . New ( "unknown type" )
}
}
2026-01-13 21:31:51 +08:00
var (
2026-03-07 23:42:18 +08:00
tcpmaskLoader = NewJSONConfigLoader ( ConfigCreatorCache {
"header-custom" : func ( ) interface { } { return new ( HeaderCustomTCP ) } ,
"fragment" : func ( ) interface { } { return new ( FragmentMask ) } ,
2026-03-08 02:21:35 +08:00
"sudoku" : func ( ) interface { } { return new ( Sudoku ) } ,
2026-03-07 23:42:18 +08:00
} , "type" , "settings" )
2026-01-13 21:31:51 +08:00
udpmaskLoader = NewJSONConfigLoader ( ConfigCreatorCache {
2026-03-07 23:42:18 +08:00
"header-custom" : func ( ) interface { } { return new ( HeaderCustomUDP ) } ,
2026-01-31 21:53:19 +08:00
"header-dns" : func ( ) interface { } { return new ( Dns ) } ,
"header-dtls" : func ( ) interface { } { return new ( Dtls ) } ,
"header-srtp" : func ( ) interface { } { return new ( Srtp ) } ,
"header-utp" : func ( ) interface { } { return new ( Utp ) } ,
"header-wechat" : func ( ) interface { } { return new ( Wechat ) } ,
"header-wireguard" : func ( ) interface { } { return new ( Wireguard ) } ,
"mkcp-original" : func ( ) interface { } { return new ( Original ) } ,
"mkcp-aes128gcm" : func ( ) interface { } { return new ( Aes128Gcm ) } ,
2026-03-07 23:42:18 +08:00
"noise" : func ( ) interface { } { return new ( NoiseMask ) } ,
2026-01-31 21:53:19 +08:00
"salamander" : func ( ) interface { } { return new ( Salamander ) } ,
2026-03-08 02:21:35 +08:00
"sudoku" : func ( ) interface { } { return new ( Sudoku ) } ,
2026-01-31 21:53:19 +08:00
"xdns" : func ( ) interface { } { return new ( Xdns ) } ,
2026-02-02 16:52:34 +08:00
"xicmp" : func ( ) interface { } { return new ( Xicmp ) } ,
2026-01-13 21:31:51 +08:00
} , "type" , "settings" )
)
2026-03-07 23:42:18 +08:00
type TCPItem struct {
2026-03-21 17:04:22 +08:00
Delay Int32Range ` json:"delay" `
Rand int32 ` json:"rand" `
RandRange * Int32Range ` json:"randRange" `
Type string ` json:"type" `
Packet json . RawMessage ` json:"packet" `
2026-03-07 23:42:18 +08:00
}
type HeaderCustomTCP struct {
Clients [ ] [ ] TCPItem ` json:"clients" `
Servers [ ] [ ] TCPItem ` json:"servers" `
Errors [ ] [ ] TCPItem ` json:"errors" `
}
func ( c * HeaderCustomTCP ) Build ( ) ( proto . Message , error ) {
for _ , value := range c . Clients {
for _ , item := range value {
if len ( item . Packet ) > 0 && item . Rand > 0 {
return nil , errors . New ( "len(item.Packet) > 0 && item.Rand > 0" )
}
}
}
for _ , value := range c . Servers {
for _ , item := range value {
if len ( item . Packet ) > 0 && item . Rand > 0 {
return nil , errors . New ( "len(item.Packet) > 0 && item.Rand > 0" )
}
}
}
for _ , value := range c . Errors {
for _ , item := range value {
if len ( item . Packet ) > 0 && item . Rand > 0 {
return nil , errors . New ( "len(item.Packet) > 0 && item.Rand > 0" )
}
}
}
2026-03-21 17:04:22 +08:00
errInvalidRange := errors . New ( "invalid randRange" )
2026-03-07 23:42:18 +08:00
clients := make ( [ ] * custom . TCPSequence , len ( c . Clients ) )
for i , value := range c . Clients {
clients [ i ] = & custom . TCPSequence { }
for _ , item := range value {
2026-03-21 17:04:22 +08:00
if item . RandRange == nil {
item . RandRange = & Int32Range { From : 0 , To : 255 }
}
if item . RandRange . From < 0 || item . RandRange . To > 255 {
return nil , errInvalidRange
}
2026-03-07 23:42:18 +08:00
var err error
if item . Packet , err = PraseByteSlice ( item . Packet , item . Type ) ; err != nil {
return nil , err
}
clients [ i ] . Sequence = append ( clients [ i ] . Sequence , & custom . TCPItem {
DelayMin : int64 ( item . Delay . From ) ,
DelayMax : int64 ( item . Delay . To ) ,
Rand : item . Rand ,
2026-03-21 17:04:22 +08:00
RandMin : item . RandRange . From ,
RandMax : item . RandRange . To ,
2026-03-07 23:42:18 +08:00
Packet : item . Packet ,
} )
}
}
servers := make ( [ ] * custom . TCPSequence , len ( c . Servers ) )
for i , value := range c . Servers {
servers [ i ] = & custom . TCPSequence { }
for _ , item := range value {
2026-03-21 17:04:22 +08:00
if item . RandRange == nil {
item . RandRange = & Int32Range { From : 0 , To : 255 }
}
if item . RandRange . From < 0 || item . RandRange . To > 255 {
return nil , errInvalidRange
}
2026-03-07 23:42:18 +08:00
var err error
if item . Packet , err = PraseByteSlice ( item . Packet , item . Type ) ; err != nil {
return nil , err
}
servers [ i ] . Sequence = append ( servers [ i ] . Sequence , & custom . TCPItem {
DelayMin : int64 ( item . Delay . From ) ,
DelayMax : int64 ( item . Delay . To ) ,
Rand : item . Rand ,
2026-03-21 17:04:22 +08:00
RandMin : item . RandRange . From ,
RandMax : item . RandRange . To ,
2026-03-07 23:42:18 +08:00
Packet : item . Packet ,
} )
}
}
errors := make ( [ ] * custom . TCPSequence , len ( c . Errors ) )
for i , value := range c . Errors {
errors [ i ] = & custom . TCPSequence { }
for _ , item := range value {
2026-03-21 17:04:22 +08:00
if item . RandRange == nil {
item . RandRange = & Int32Range { From : 0 , To : 255 }
}
if item . RandRange . From < 0 || item . RandRange . To > 255 {
return nil , errInvalidRange
}
2026-03-07 23:42:18 +08:00
var err error
if item . Packet , err = PraseByteSlice ( item . Packet , item . Type ) ; err != nil {
return nil , err
}
errors [ i ] . Sequence = append ( errors [ i ] . Sequence , & custom . TCPItem {
DelayMin : int64 ( item . Delay . From ) ,
DelayMax : int64 ( item . Delay . To ) ,
Rand : item . Rand ,
2026-03-21 17:04:22 +08:00
RandMin : item . RandRange . From ,
RandMax : item . RandRange . To ,
2026-03-07 23:42:18 +08:00
Packet : item . Packet ,
} )
}
}
return & custom . TCPConfig {
Clients : clients ,
Servers : servers ,
Errors : errors ,
} , nil
}
type FragmentMask struct {
Packets string ` json:"packets" `
Length Int32Range ` json:"length" `
Delay Int32Range ` json:"delay" `
MaxSplit Int32Range ` json:"maxSplit" `
}
func ( c * FragmentMask ) Build ( ) ( proto . Message , error ) {
config := & fragment . Config { }
switch strings . ToLower ( c . Packets ) {
case "tlshello" :
config . PacketsFrom = 0
config . PacketsTo = 1
case "" :
config . PacketsFrom = 0
config . PacketsTo = 0
default :
from , to , err := ParseRangeString ( c . Packets )
if err != nil {
return nil , errors . New ( "Invalid PacketsFrom" ) . Base ( err )
}
config . PacketsFrom = int64 ( from )
config . PacketsTo = int64 ( to )
if config . PacketsFrom == 0 {
return nil , errors . New ( "PacketsFrom can't be 0" )
}
}
config . LengthMin = int64 ( c . Length . From )
config . LengthMax = int64 ( c . Length . To )
if config . LengthMin == 0 {
return nil , errors . New ( "LengthMin can't be 0" )
}
config . DelayMin = int64 ( c . Delay . From )
config . DelayMax = int64 ( c . Delay . To )
config . MaxSplitMin = int64 ( c . MaxSplit . From )
config . MaxSplitMax = int64 ( c . MaxSplit . To )
return config , nil
}
type NoiseItem struct {
Rand Int32Range ` json:"rand" `
Type string ` json:"type" `
Packet json . RawMessage ` json:"packet" `
Delay Int32Range ` json:"delay" `
}
type NoiseMask struct {
Reset Int32Range ` json:"reset" `
Noise [ ] NoiseItem ` json:"noise" `
}
func ( c * NoiseMask ) Build ( ) ( proto . Message , error ) {
for _ , item := range c . Noise {
if len ( item . Packet ) > 0 && item . Rand . To > 0 {
return nil , errors . New ( "len(item.Packet) > 0 && item.Rand.To > 0" )
}
}
noiseSlice := make ( [ ] * noise . Item , 0 , len ( c . Noise ) )
for _ , item := range c . Noise {
var err error
if item . Packet , err = PraseByteSlice ( item . Packet , item . Type ) ; err != nil {
return nil , err
}
noiseSlice = append ( noiseSlice , & noise . Item {
RandMin : int64 ( item . Rand . From ) ,
RandMax : int64 ( item . Rand . To ) ,
Packet : item . Packet ,
DelayMin : int64 ( item . Delay . From ) ,
DelayMax : int64 ( item . Delay . To ) ,
} )
}
return & noise . Config {
ResetMin : int64 ( c . Reset . From ) ,
ResetMax : int64 ( c . Reset . To ) ,
Items : noiseSlice ,
} , nil
}
type UDPItem struct {
2026-03-21 17:04:22 +08:00
Rand int32 ` json:"rand" `
RandRange * Int32Range ` json:"randRange" `
Type string ` json:"type" `
Packet json . RawMessage ` json:"packet" `
2026-03-07 23:42:18 +08:00
}
type HeaderCustomUDP struct {
Client [ ] UDPItem ` json:"client" `
Server [ ] UDPItem ` json:"server" `
}
func ( c * HeaderCustomUDP ) Build ( ) ( proto . Message , error ) {
for _ , item := range c . Client {
if len ( item . Packet ) > 0 && item . Rand > 0 {
return nil , errors . New ( "len(item.Packet) > 0 && item.Rand > 0" )
}
}
for _ , item := range c . Server {
if len ( item . Packet ) > 0 && item . Rand > 0 {
return nil , errors . New ( "len(item.Packet) > 0 && item.Rand > 0" )
}
}
client := make ( [ ] * custom . UDPItem , 0 , len ( c . Client ) )
for _ , item := range c . Client {
2026-03-21 17:04:22 +08:00
if item . RandRange == nil {
item . RandRange = & Int32Range { From : 0 , To : 255 }
}
if item . RandRange . From < 0 || item . RandRange . To > 255 {
return nil , errors . New ( "invalid randRange" )
}
2026-03-07 23:42:18 +08:00
var err error
if item . Packet , err = PraseByteSlice ( item . Packet , item . Type ) ; err != nil {
return nil , err
}
client = append ( client , & custom . UDPItem {
2026-03-21 17:04:22 +08:00
Rand : item . Rand ,
RandMin : item . RandRange . From ,
RandMax : item . RandRange . To ,
Packet : item . Packet ,
2026-03-07 23:42:18 +08:00
} )
}
server := make ( [ ] * custom . UDPItem , 0 , len ( c . Server ) )
for _ , item := range c . Server {
2026-03-21 17:04:22 +08:00
if item . RandRange == nil {
item . RandRange = & Int32Range { From : 0 , To : 255 }
}
if item . RandRange . From < 0 || item . RandRange . To > 255 {
return nil , errors . New ( "invalid randRange" )
}
2026-03-07 23:42:18 +08:00
var err error
if item . Packet , err = PraseByteSlice ( item . Packet , item . Type ) ; err != nil {
return nil , err
}
server = append ( server , & custom . UDPItem {
2026-03-21 17:04:22 +08:00
Rand : item . Rand ,
RandMin : item . RandRange . From ,
RandMax : item . RandRange . To ,
Packet : item . Packet ,
2026-03-07 23:42:18 +08:00
} )
}
return & custom . UDPConfig {
Client : client ,
Server : server ,
} , nil
}
2026-01-31 21:53:19 +08:00
type Dns struct {
Domain string ` json:"domain" `
}
func ( c * Dns ) Build ( ) ( proto . Message , error ) {
config := & dns . Config { }
config . Domain = "www.baidu.com"
if len ( c . Domain ) > 0 {
config . Domain = c . Domain
}
return config , nil
}
type Dtls struct { }
func ( c * Dtls ) Build ( ) ( proto . Message , error ) {
return & dtls . Config { } , nil
}
type Srtp struct { }
func ( c * Srtp ) Build ( ) ( proto . Message , error ) {
return & srtp . Config { } , nil
}
type Utp struct { }
func ( c * Utp ) Build ( ) ( proto . Message , error ) {
return & utp . Config { } , nil
}
type Wechat struct { }
func ( c * Wechat ) Build ( ) ( proto . Message , error ) {
return & wechat . Config { } , nil
}
type Wireguard struct { }
func ( c * Wireguard ) Build ( ) ( proto . Message , error ) {
return & wireguard . Config { } , nil
}
type Original struct { }
func ( c * Original ) Build ( ) ( proto . Message , error ) {
return & original . Config { } , nil
}
type Aes128Gcm struct {
Password string ` json:"password" `
}
func ( c * Aes128Gcm ) Build ( ) ( proto . Message , error ) {
return & aes128gcm . Config {
Password : c . Password ,
} , nil
}
2026-01-13 21:31:51 +08:00
type Salamander struct {
Password string ` json:"password" `
}
func ( c * Salamander ) Build ( ) ( proto . Message , error ) {
config := & salamander . Config { }
config . Password = c . Password
return config , nil
}
2026-03-08 02:21:35 +08:00
type Sudoku struct {
Password string ` json:"password" `
ASCII string ` json:"ascii" `
CustomTable string ` json:"customTable" `
LegacyCustomTable string ` json:"custom_table" `
CustomTables [ ] string ` json:"customTables" `
LegacyCustomSets [ ] string ` json:"custom_tables" `
PaddingMin uint32 ` json:"paddingMin" `
LegacyPaddingMin uint32 ` json:"padding_min" `
PaddingMax uint32 ` json:"paddingMax" `
LegacyPaddingMax uint32 ` json:"padding_max" `
}
func ( c * Sudoku ) Build ( ) ( proto . Message , error ) {
customTable := c . CustomTable
if customTable == "" {
customTable = c . LegacyCustomTable
}
customTables := c . CustomTables
if len ( customTables ) == 0 {
customTables = c . LegacyCustomSets
}
paddingMin := c . PaddingMin
if paddingMin == 0 {
paddingMin = c . LegacyPaddingMin
}
paddingMax := c . PaddingMax
if paddingMax == 0 {
paddingMax = c . LegacyPaddingMax
}
return & finalsudoku . Config {
Password : c . Password ,
Ascii : c . ASCII ,
CustomTable : customTable ,
CustomTables : customTables ,
PaddingMin : paddingMin ,
PaddingMax : paddingMax ,
} , nil
}
2026-01-31 21:53:19 +08:00
type Xdns struct {
Domain string ` json:"domain" `
}
func ( c * Xdns ) Build ( ) ( proto . Message , error ) {
if c . Domain == "" {
return nil , errors . New ( "empty domain" )
}
return & xdns . Config {
Domain : c . Domain ,
} , nil
}
2026-02-02 16:52:34 +08:00
type Xicmp struct {
ListenIp string ` json:"listenIp" `
Id uint16 ` json:"id" `
}
func ( c * Xicmp ) Build ( ) ( proto . Message , error ) {
config := & xicmp . Config {
Ip : c . ListenIp ,
Id : int32 ( c . Id ) ,
}
if config . Ip == "" {
config . Ip = "0.0.0.0"
}
return config , nil
}
2026-01-31 21:53:19 +08:00
type Mask struct {
2026-01-13 21:31:51 +08:00
Type string ` json:"type" `
Settings * json . RawMessage ` json:"settings" `
}
2026-01-31 21:53:19 +08:00
func ( c * Mask ) Build ( tcp bool ) ( proto . Message , error ) {
2026-01-13 21:31:51 +08:00
loader := udpmaskLoader
2026-01-31 21:53:19 +08:00
if tcp {
2026-03-07 23:42:18 +08:00
loader = tcpmaskLoader
2026-01-13 21:31:51 +08:00
}
settings := [ ] byte ( "{}" )
if c . Settings != nil {
settings = ( [ ] byte ) ( * c . Settings )
}
rawConfig , err := loader . LoadWithID ( settings , c . Type )
if err != nil {
return nil , err
}
ts , err := rawConfig . ( Buildable ) . Build ( )
if err != nil {
return nil , err
}
return ts , nil
}
2026-01-31 21:53:19 +08:00
type FinalMask struct {
2026-03-07 16:46:40 +04:00
Tcp [ ] Mask ` json:"tcp" `
Udp [ ] Mask ` json:"udp" `
QuicParams * QuicParamsConfig ` json:"quicParams" `
2026-01-31 21:53:19 +08:00
}
2020-11-25 19:01:53 +08:00
type StreamConfig struct {
2024-10-31 07:31:19 +00:00
Address * Address ` json:"address" `
Port uint16 ` json:"port" `
2024-09-19 17:20:12 +02:00
Network * TransportProtocol ` json:"network" `
Security string ` json:"security" `
2026-01-31 21:53:19 +08:00
FinalMask * FinalMask ` json:"finalmask" `
2024-09-19 17:20:12 +02:00
TLSSettings * TLSConfig ` json:"tlsSettings" `
REALITYSettings * REALITYConfig ` json:"realitySettings" `
2024-09-28 05:25:59 +00:00
RAWSettings * TCPConfig ` json:"rawSettings" `
2024-09-19 17:20:12 +02:00
TCPSettings * TCPConfig ` json:"tcpSettings" `
2024-12-02 09:56:16 +00:00
XHTTPSettings * SplitHTTPConfig ` json:"xhttpSettings" `
SplitHTTPSettings * SplitHTTPConfig ` json:"splithttpSettings" `
2024-09-19 17:20:12 +02:00
KCPSettings * KCPConfig ` json:"kcpSettings" `
2024-12-02 09:56:16 +00:00
GRPCSettings * GRPCConfig ` json:"grpcSettings" `
2024-09-19 17:20:12 +02:00
WSSettings * WebSocketConfig ` json:"wsSettings" `
HTTPUPGRADESettings * HttpUpgradeConfig ` json:"httpupgradeSettings" `
2026-01-13 21:31:51 +08:00
HysteriaSettings * HysteriaConfig ` json:"hysteriaSettings" `
2024-12-02 09:56:16 +00:00
SocketSettings * SocketConfig ` json:"sockopt" `
2020-11-25 19:01:53 +08:00
}
// Build implements Buildable.
func ( c * StreamConfig ) Build ( ) ( * internet . StreamConfig , error ) {
config := & internet . StreamConfig {
2024-10-31 07:31:19 +00:00
Port : uint32 ( c . Port ) ,
2020-11-25 19:01:53 +08:00
ProtocolName : "tcp" ,
}
2024-10-31 07:31:19 +00:00
if c . Address != nil {
config . Address = c . Address . Build ( )
}
2020-11-25 19:01:53 +08:00
if c . Network != nil {
protocol , err := c . Network . Build ( )
if err != nil {
return nil , err
}
config . ProtocolName = protocol
}
2026-01-13 21:31:51 +08:00
2023-03-04 15:39:27 +00:00
switch strings . ToLower ( c . Security ) {
case "" , "none" :
case "tls" :
2020-11-25 19:01:53 +08:00
tlsSettings := c . TLSSettings
if tlsSettings == nil {
tlsSettings = & TLSConfig { }
}
ts , err := tlsSettings . Build ( )
if err != nil {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "Failed to build TLS config." ) . Base ( err )
2020-11-25 19:01:53 +08:00
}
tm := serial . ToTypedMessage ( ts )
config . SecuritySettings = append ( config . SecuritySettings , tm )
config . SecurityType = tm . Type
2023-03-04 15:39:27 +00:00
case "reality" :
2024-12-02 09:56:16 +00:00
if config . ProtocolName != "tcp" && config . ProtocolName != "splithttp" && config . ProtocolName != "grpc" {
return nil , errors . New ( "REALITY only supports RAW, XHTTP and gRPC for now." )
2023-02-15 16:07:12 +00:00
}
if c . REALITYSettings == nil {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( ` REALITY: Empty "realitySettings". ` )
2023-02-15 16:07:12 +00:00
}
ts , err := c . REALITYSettings . Build ( )
if err != nil {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "Failed to build REALITY config." ) . Base ( err )
2020-11-25 19:01:53 +08:00
}
tm := serial . ToTypedMessage ( ts )
config . SecuritySettings = append ( config . SecuritySettings , tm )
config . SecurityType = tm . Type
2023-03-04 15:39:27 +00:00
case "xtls" :
2024-09-15 12:55:54 +08:00
return nil , errors . PrintRemovedFeatureError ( ` Legacy XTLS ` , ` xtls-rprx-vision with TLS or REALITY ` )
2023-03-04 15:39:27 +00:00
default :
2024-06-29 14:32:57 -04:00
return nil , errors . New ( ` Unknown security " ` + c . Security + ` ". ` )
2020-11-25 19:01:53 +08:00
}
2026-01-13 21:31:51 +08:00
2024-10-18 02:18:06 +00:00
if c . RAWSettings != nil {
2024-09-28 05:25:59 +00:00
c . TCPSettings = c . RAWSettings
}
2020-11-25 19:01:53 +08:00
if c . TCPSettings != nil {
ts , err := c . TCPSettings . Build ( )
if err != nil {
2024-09-28 05:25:59 +00:00
return nil , errors . New ( "Failed to build RAW config." ) . Base ( err )
2020-11-25 19:01:53 +08:00
}
config . TransportSettings = append ( config . TransportSettings , & internet . TransportConfig {
ProtocolName : "tcp" ,
Settings : serial . ToTypedMessage ( ts ) ,
} )
}
2024-12-02 09:56:16 +00:00
if c . XHTTPSettings != nil {
c . SplitHTTPSettings = c . XHTTPSettings
2020-11-25 19:01:53 +08:00
}
2024-12-02 09:56:16 +00:00
if c . SplitHTTPSettings != nil {
hs , err := c . SplitHTTPSettings . Build ( )
2020-11-25 19:01:53 +08:00
if err != nil {
2024-12-02 09:56:16 +00:00
return nil , errors . New ( "Failed to build XHTTP config." ) . Base ( err )
2020-11-25 19:01:53 +08:00
}
config . TransportSettings = append ( config . TransportSettings , & internet . TransportConfig {
2024-12-02 09:56:16 +00:00
ProtocolName : "splithttp" ,
Settings : serial . ToTypedMessage ( hs ) ,
2020-11-25 19:01:53 +08:00
} )
}
2024-12-02 09:56:16 +00:00
if c . KCPSettings != nil {
ts , err := c . KCPSettings . Build ( )
2020-11-25 19:01:53 +08:00
if err != nil {
2024-12-02 09:56:16 +00:00
return nil , errors . New ( "Failed to build mKCP config." ) . Base ( err )
2020-11-25 19:01:53 +08:00
}
config . TransportSettings = append ( config . TransportSettings , & internet . TransportConfig {
2024-12-02 09:56:16 +00:00
ProtocolName : "mkcp" ,
2020-11-25 19:01:53 +08:00
Settings : serial . ToTypedMessage ( ts ) ,
} )
}
2024-12-02 09:56:16 +00:00
if c . GRPCSettings != nil {
gs , err := c . GRPCSettings . Build ( )
2021-03-14 15:02:07 +00:00
if err != nil {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "Failed to build gRPC config." ) . Base ( err )
2021-03-14 15:02:07 +00:00
}
config . TransportSettings = append ( config . TransportSettings , & internet . TransportConfig {
ProtocolName : "grpc" ,
Settings : serial . ToTypedMessage ( gs ) ,
} )
}
2024-12-02 09:56:16 +00:00
if c . WSSettings != nil {
ts , err := c . WSSettings . Build ( )
2024-03-03 13:12:38 +08:00
if err != nil {
2024-12-02 09:56:16 +00:00
return nil , errors . New ( "Failed to build WebSocket config." ) . Base ( err )
2024-03-03 13:12:38 +08:00
}
config . TransportSettings = append ( config . TransportSettings , & internet . TransportConfig {
2024-12-02 09:56:16 +00:00
ProtocolName : "websocket" ,
Settings : serial . ToTypedMessage ( ts ) ,
2024-03-03 13:12:38 +08:00
} )
}
2024-12-02 09:56:16 +00:00
if c . HTTPUPGRADESettings != nil {
hs , err := c . HTTPUPGRADESettings . Build ( )
2024-06-18 07:36:36 +02:00
if err != nil {
2024-12-02 09:56:16 +00:00
return nil , errors . New ( "Failed to build HTTPUpgrade config." ) . Base ( err )
2024-06-18 07:36:36 +02:00
}
config . TransportSettings = append ( config . TransportSettings , & internet . TransportConfig {
2024-12-02 09:56:16 +00:00
ProtocolName : "httpupgrade" ,
2024-06-18 07:36:36 +02:00
Settings : serial . ToTypedMessage ( hs ) ,
} )
}
2026-01-13 21:31:51 +08:00
if c . HysteriaSettings != nil {
hs , err := c . HysteriaSettings . Build ( )
if err != nil {
return nil , errors . New ( "Failed to build Hysteria config." ) . Base ( err )
}
config . TransportSettings = append ( config . TransportSettings , & internet . TransportConfig {
ProtocolName : "hysteria" ,
Settings : serial . ToTypedMessage ( hs ) ,
} )
}
2020-11-25 19:01:53 +08:00
if c . SocketSettings != nil {
ss , err := c . SocketSettings . Build ( )
if err != nil {
2024-12-02 09:56:16 +00:00
return nil , errors . New ( "Failed to build sockopt." ) . Base ( err )
2020-11-25 19:01:53 +08:00
}
config . SocketSettings = ss
}
2026-01-13 21:31:51 +08:00
2026-01-31 21:53:19 +08:00
if c . FinalMask != nil {
for _ , mask := range c . FinalMask . Tcp {
u , err := mask . Build ( true )
if err != nil {
return nil , errors . New ( "failed to build mask with type " , mask . Type ) . Base ( err )
}
config . Tcpmasks = append ( config . Tcpmasks , serial . ToTypedMessage ( u ) )
}
for _ , mask := range c . FinalMask . Udp {
u , err := mask . Build ( false )
if err != nil {
return nil , errors . New ( "failed to build mask with type " , mask . Type ) . Base ( err )
}
config . Udpmasks = append ( config . Udpmasks , serial . ToTypedMessage ( u ) )
2026-01-13 21:31:51 +08:00
}
2026-03-07 16:46:40 +04:00
if c . FinalMask . QuicParams != nil {
2026-03-09 20:17:32 +08:00
up , err := c . FinalMask . QuicParams . BrutalUp . Bps ( )
2026-03-07 16:46:40 +04:00
if err != nil {
return nil , err
}
2026-03-09 20:17:32 +08:00
down , err := c . FinalMask . QuicParams . BrutalDown . Bps ( )
if err != nil {
return nil , err
}
2026-03-07 16:46:40 +04:00
if up > 0 && up < 65536 {
2026-03-09 20:17:32 +08:00
return nil , errors . New ( "BrutalUp must be at least 65536 bytes per second" )
2026-03-07 16:46:40 +04:00
}
2026-03-09 20:17:32 +08:00
if down > 0 && down < 65536 {
return nil , errors . New ( "BrutalDown must be at least 65536 bytes per second" )
}
c . FinalMask . QuicParams . Congestion = strings . ToLower ( c . FinalMask . QuicParams . Congestion )
2026-03-07 16:46:40 +04:00
switch c . FinalMask . QuicParams . Congestion {
2026-03-09 20:17:32 +08:00
case "" , "brutal" , "reno" , "bbr" :
2026-03-07 16:46:40 +04:00
case "force-brutal" :
if up == 0 {
return nil , errors . New ( "force-brutal requires up" )
}
default :
2026-03-09 20:17:32 +08:00
return nil , errors . New ( "unknown congestion control: " , c . FinalMask . QuicParams . Congestion , ", valid values: reno, bbr, brutal, force-brutal" )
}
var hop * PortList
if err := json . Unmarshal ( c . FinalMask . QuicParams . UdpHop . PortList , & hop ) ; err != nil {
hop = & PortList { }
}
var inertvalMin , inertvalMax int64
if c . FinalMask . QuicParams . UdpHop . Interval != nil {
inertvalMin = int64 ( c . FinalMask . QuicParams . UdpHop . Interval . From )
inertvalMax = int64 ( c . FinalMask . QuicParams . UdpHop . Interval . To )
2026-03-07 16:46:40 +04:00
}
2026-03-09 20:17:32 +08:00
if ( inertvalMin != 0 && inertvalMin < 5 ) || ( inertvalMax != 0 && inertvalMax < 5 ) {
return nil , errors . New ( "Interval must be at least 5" )
}
if c . FinalMask . QuicParams . InitStreamReceiveWindow > 0 && c . FinalMask . QuicParams . InitStreamReceiveWindow < 16384 {
return nil , errors . New ( "InitStreamReceiveWindow must be at least 16384" )
}
if c . FinalMask . QuicParams . MaxStreamReceiveWindow > 0 && c . FinalMask . QuicParams . MaxStreamReceiveWindow < 16384 {
return nil , errors . New ( "MaxStreamReceiveWindow must be at least 16384" )
}
if c . FinalMask . QuicParams . InitConnectionReceiveWindow > 0 && c . FinalMask . QuicParams . InitConnectionReceiveWindow < 16384 {
return nil , errors . New ( "InitConnectionReceiveWindow must be at least 16384" )
}
if c . FinalMask . QuicParams . MaxConnectionReceiveWindow > 0 && c . FinalMask . QuicParams . MaxConnectionReceiveWindow < 16384 {
return nil , errors . New ( "MaxConnectionReceiveWindow must be at least 16384" )
}
if c . FinalMask . QuicParams . MaxIdleTimeout != 0 && ( c . FinalMask . QuicParams . MaxIdleTimeout < 4 || c . FinalMask . QuicParams . MaxIdleTimeout > 120 ) {
return nil , errors . New ( "MaxIdleTimeout must be between 4 and 120" )
}
if c . FinalMask . QuicParams . KeepAlivePeriod != 0 && ( c . FinalMask . QuicParams . KeepAlivePeriod < 2 || c . FinalMask . QuicParams . KeepAlivePeriod > 60 ) {
return nil , errors . New ( "KeepAlivePeriod must be between 2 and 60" )
}
if c . FinalMask . QuicParams . MaxIncomingStreams != 0 && c . FinalMask . QuicParams . MaxIncomingStreams < 8 {
return nil , errors . New ( "MaxIncomingStreams must be at least 8" )
}
if c . FinalMask . QuicParams . Debug {
os . Setenv ( "HYSTERIA_BBR_DEBUG" , "true" )
os . Setenv ( "HYSTERIA_BRUTAL_DEBUG" , "true" )
}
2026-03-07 16:46:40 +04:00
config . QuicParams = & internet . QuicParams {
Congestion : c . FinalMask . QuicParams . Congestion ,
2026-03-09 20:17:32 +08:00
BrutalUp : up ,
BrutalDown : down ,
UdpHop : & internet . UdpHop {
Ports : hop . Build ( ) . Ports ( ) ,
IntervalMin : inertvalMin ,
IntervalMax : inertvalMax ,
} ,
InitStreamReceiveWindow : c . FinalMask . QuicParams . InitStreamReceiveWindow ,
MaxStreamReceiveWindow : c . FinalMask . QuicParams . MaxStreamReceiveWindow ,
InitConnReceiveWindow : c . FinalMask . QuicParams . InitConnectionReceiveWindow ,
MaxConnReceiveWindow : c . FinalMask . QuicParams . MaxConnectionReceiveWindow ,
MaxIdleTimeout : c . FinalMask . QuicParams . MaxIdleTimeout ,
KeepAlivePeriod : c . FinalMask . QuicParams . KeepAlivePeriod ,
DisablePathMtuDiscovery : c . FinalMask . QuicParams . DisablePathMTUDiscovery ,
MaxIncomingStreams : c . FinalMask . QuicParams . MaxIncomingStreams ,
2026-03-07 16:46:40 +04:00
}
}
2026-01-13 21:31:51 +08:00
}
2020-11-25 19:01:53 +08:00
return config , nil
}
type ProxyConfig struct {
Tag string ` json:"tag" `
2021-03-07 00:29:17 +08:00
// TransportLayerProxy: For compatibility.
TransportLayerProxy bool ` json:"transportLayer" `
2020-11-25 19:01:53 +08:00
}
// Build implements Buildable.
func ( v * ProxyConfig ) Build ( ) ( * internet . ProxyConfig , error ) {
if v . Tag == "" {
2024-06-29 14:32:57 -04:00
return nil , errors . New ( "Proxy tag is not set." )
2020-11-25 19:01:53 +08:00
}
return & internet . ProxyConfig {
2021-03-07 00:29:17 +08:00
Tag : v . Tag ,
TransportLayerProxy : v . TransportLayerProxy ,
2020-11-25 19:01:53 +08:00
} , nil
}