mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-21 08:36:53 +00:00
infra/conf/transport_internet.go: Split into multiple files; Rename network to method (#6426)
https://github.com/XTLS/Xray-core/issues/6376#issuecomment-4817591444 https://github.com/XTLS/Xray-core/pull/6426#issuecomment-4899048255 --------- Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
This commit is contained in:
@@ -1,41 +0,0 @@
|
|||||||
package conf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/xtls/xray-core/transport/internet/grpc"
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GRPCConfig struct {
|
|
||||||
Authority string `json:"authority"`
|
|
||||||
ServiceName string `json:"serviceName"`
|
|
||||||
MultiMode bool `json:"multiMode"`
|
|
||||||
IdleTimeout int32 `json:"idle_timeout"`
|
|
||||||
HealthCheckTimeout int32 `json:"health_check_timeout"`
|
|
||||||
PermitWithoutStream bool `json:"permit_without_stream"`
|
|
||||||
InitialWindowsSize int32 `json:"initial_windows_size"`
|
|
||||||
UserAgent string `json:"user_agent"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GRPCConfig) Build() (proto.Message, error) {
|
|
||||||
if g.IdleTimeout <= 0 {
|
|
||||||
g.IdleTimeout = 0
|
|
||||||
}
|
|
||||||
if g.HealthCheckTimeout <= 0 {
|
|
||||||
g.HealthCheckTimeout = 0
|
|
||||||
}
|
|
||||||
if g.InitialWindowsSize < 0 {
|
|
||||||
// default window size of gRPC-go
|
|
||||||
g.InitialWindowsSize = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return &grpc.Config{
|
|
||||||
Authority: g.Authority,
|
|
||||||
ServiceName: g.ServiceName,
|
|
||||||
MultiMode: g.MultiMode,
|
|
||||||
IdleTimeout: g.IdleTimeout,
|
|
||||||
HealthCheckTimeout: g.HealthCheckTimeout,
|
|
||||||
PermitWithoutStream: g.PermitWithoutStream,
|
|
||||||
InitialWindowsSize: g.InitialWindowsSize,
|
|
||||||
UserAgent: g.UserAgent,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
package conf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
"github.com/xtls/xray-core/common/utils"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/headers/http"
|
|
||||||
"github.com/xtls/xray-core/transport/internet/headers/noop"
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NoOpConnectionAuthenticator struct{}
|
|
||||||
|
|
||||||
func (NoOpConnectionAuthenticator) Build() (proto.Message, error) {
|
|
||||||
return new(noop.ConnectionConfig), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthenticatorRequest struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
Path StringList `json:"path"`
|
|
||||||
Headers map[string]*StringList `json:"headers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortMapKeys(m map[string]*StringList) []string {
|
|
||||||
var keys []string
|
|
||||||
for key := range m {
|
|
||||||
keys = append(keys, key)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
|
|
||||||
config := &http.RequestConfig{
|
|
||||||
Uri: []string{"/"},
|
|
||||||
Header: []*http.Header{
|
|
||||||
{
|
|
||||||
Name: "Host",
|
|
||||||
Value: []string{"www.baidu.com", "www.bing.com"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "User-Agent",
|
|
||||||
Value: []string{utils.ChromeUA},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Sec-CH-UA",
|
|
||||||
Value: []string{utils.ChromeUACH},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Sec-CH-UA-Mobile",
|
|
||||||
Value: []string{"?0"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Sec-CH-UA-Platform",
|
|
||||||
Value: []string{"Windows"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Sec-Fetch-Mode",
|
|
||||||
Value: []string{"no-cors", "cors", "same-origin"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Sec-Fetch-Dest",
|
|
||||||
Value: []string{"empty"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Sec-Fetch-Site",
|
|
||||||
Value: []string{"none"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Sec-Fetch-User",
|
|
||||||
Value: []string{"?1"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Accept-Encoding",
|
|
||||||
Value: []string{"gzip, deflate"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Connection",
|
|
||||||
Value: []string{"keep-alive"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Pragma",
|
|
||||||
Value: []string{"no-cache"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(v.Version) > 0 {
|
|
||||||
config.Version = &http.Version{Value: v.Version}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(v.Method) > 0 {
|
|
||||||
config.Method = &http.Method{Value: v.Method}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(v.Path) > 0 {
|
|
||||||
config.Uri = append([]string(nil), v.Path...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(v.Headers) > 0 {
|
|
||||||
config.Header = make([]*http.Header, 0, len(v.Headers))
|
|
||||||
headerNames := sortMapKeys(v.Headers)
|
|
||||||
for _, key := range headerNames {
|
|
||||||
value := v.Headers[key]
|
|
||||||
if value == nil {
|
|
||||||
return nil, errors.New("empty HTTP header value: " + key).AtError()
|
|
||||||
}
|
|
||||||
config.Header = append(config.Header, &http.Header{
|
|
||||||
Name: key,
|
|
||||||
Value: append([]string(nil), (*value)...),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthenticatorResponse struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
Reason string `json:"reason"`
|
|
||||||
Headers map[string]*StringList `json:"headers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *AuthenticatorResponse) Build() (*http.ResponseConfig, error) {
|
|
||||||
config := &http.ResponseConfig{
|
|
||||||
Header: []*http.Header{
|
|
||||||
{
|
|
||||||
Name: "Content-Type",
|
|
||||||
Value: []string{"application/octet-stream", "video/mpeg"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Transfer-Encoding",
|
|
||||||
Value: []string{"chunked"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Connection",
|
|
||||||
Value: []string{"keep-alive"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Pragma",
|
|
||||||
Value: []string{"no-cache"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Cache-Control",
|
|
||||||
Value: []string{"private", "no-cache"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(v.Version) > 0 {
|
|
||||||
config.Version = &http.Version{Value: v.Version}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(v.Status) > 0 || len(v.Reason) > 0 {
|
|
||||||
config.Status = &http.Status{
|
|
||||||
Code: "200",
|
|
||||||
Reason: "OK",
|
|
||||||
}
|
|
||||||
if len(v.Status) > 0 {
|
|
||||||
config.Status.Code = v.Status
|
|
||||||
}
|
|
||||||
if len(v.Reason) > 0 {
|
|
||||||
config.Status.Reason = v.Reason
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(v.Headers) > 0 {
|
|
||||||
config.Header = make([]*http.Header, 0, len(v.Headers))
|
|
||||||
headerNames := sortMapKeys(v.Headers)
|
|
||||||
for _, key := range headerNames {
|
|
||||||
value := v.Headers[key]
|
|
||||||
if value == nil {
|
|
||||||
return nil, errors.New("empty HTTP header value: " + key).AtError()
|
|
||||||
}
|
|
||||||
config.Header = append(config.Header, &http.Header{
|
|
||||||
Name: key,
|
|
||||||
Value: append([]string(nil), (*value)...),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Authenticator struct {
|
|
||||||
Request AuthenticatorRequest `json:"request"`
|
|
||||||
Response AuthenticatorResponse `json:"response"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Authenticator) Build() (proto.Message, error) {
|
|
||||||
config := new(http.Config)
|
|
||||||
requestConfig, err := v.Request.Build()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.Request = requestConfig
|
|
||||||
|
|
||||||
responseConfig, err := v.Response.Build()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.Response = responseConfig
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,871 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"net/netip"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/header"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask/noise"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask/realm"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask/salamander"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask/xdns"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask/xicmp"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
customVarNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
|
||||||
|
|
||||||
|
tcpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||||
|
"header-custom": func() interface{} { return new(HeaderCustomTCP) },
|
||||||
|
"fragment": func() interface{} { return new(FragmentMask) },
|
||||||
|
"sudoku": func() interface{} { return new(Sudoku) },
|
||||||
|
}, "type", "settings")
|
||||||
|
|
||||||
|
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||||
|
"header-custom": func() interface{} { return new(HeaderCustomUDP) },
|
||||||
|
"mkcp-legacy": func() interface{} { return new(MkcpLegacy) },
|
||||||
|
"noise": func() interface{} { return new(NoiseMask) },
|
||||||
|
"salamander": func() interface{} { return new(Salamander) },
|
||||||
|
"sudoku": func() interface{} { return new(Sudoku) },
|
||||||
|
"xdns": func() interface{} { return new(Xdns) },
|
||||||
|
"xicmp": func() interface{} { return new(Xicmp) },
|
||||||
|
"realm": func() interface{} { return new(Realm) },
|
||||||
|
}, "type", "settings")
|
||||||
|
)
|
||||||
|
|
||||||
|
type TCPItem struct {
|
||||||
|
Delay Int32Range `json:"delay"`
|
||||||
|
Rand int32 `json:"rand"`
|
||||||
|
RandRange *Int32Range `json:"randRange"`
|
||||||
|
Capture string `json:"capture"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Reuse string `json:"reuse"`
|
||||||
|
Transform *CustomTransform `json:"transform"`
|
||||||
|
Packet json.RawMessage `json:"packet"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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 err := validateCustomItemSpec(item.Capture, item.Packet, item.Rand, item.Reuse, item.Transform); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, value := range c.Servers {
|
||||||
|
for _, item := range value {
|
||||||
|
if err := validateCustomItemSpec(item.Capture, item.Packet, item.Rand, item.Reuse, item.Transform); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, value := range c.Errors {
|
||||||
|
for _, item := range value {
|
||||||
|
if err := validateCustomItemSpec(item.Capture, item.Packet, item.Rand, item.Reuse, item.Transform); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errInvalidRange := errors.New("invalid randRange")
|
||||||
|
|
||||||
|
clients := make([]*custom.TCPSequence, len(c.Clients))
|
||||||
|
for i, value := range c.Clients {
|
||||||
|
clients[i] = &custom.TCPSequence{}
|
||||||
|
for _, item := range value {
|
||||||
|
if item.RandRange == nil {
|
||||||
|
item.RandRange = &Int32Range{From: 0, To: 255}
|
||||||
|
}
|
||||||
|
if item.RandRange.From < 0 || item.RandRange.To > 255 {
|
||||||
|
return nil, errInvalidRange
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
transform, err := buildCustomTransform(item.Transform)
|
||||||
|
if 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,
|
||||||
|
RandMin: item.RandRange.From,
|
||||||
|
RandMax: item.RandRange.To,
|
||||||
|
Packet: item.Packet,
|
||||||
|
Save: item.Capture,
|
||||||
|
Var: item.Reuse,
|
||||||
|
Expr: transform,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
servers := make([]*custom.TCPSequence, len(c.Servers))
|
||||||
|
for i, value := range c.Servers {
|
||||||
|
servers[i] = &custom.TCPSequence{}
|
||||||
|
for _, item := range value {
|
||||||
|
if item.RandRange == nil {
|
||||||
|
item.RandRange = &Int32Range{From: 0, To: 255}
|
||||||
|
}
|
||||||
|
if item.RandRange.From < 0 || item.RandRange.To > 255 {
|
||||||
|
return nil, errInvalidRange
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
transform, err := buildCustomTransform(item.Transform)
|
||||||
|
if 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,
|
||||||
|
RandMin: item.RandRange.From,
|
||||||
|
RandMax: item.RandRange.To,
|
||||||
|
Packet: item.Packet,
|
||||||
|
Save: item.Capture,
|
||||||
|
Var: item.Reuse,
|
||||||
|
Expr: transform,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errors := make([]*custom.TCPSequence, len(c.Errors))
|
||||||
|
for i, value := range c.Errors {
|
||||||
|
errors[i] = &custom.TCPSequence{}
|
||||||
|
for _, item := range value {
|
||||||
|
if item.RandRange == nil {
|
||||||
|
item.RandRange = &Int32Range{From: 0, To: 255}
|
||||||
|
}
|
||||||
|
if item.RandRange.From < 0 || item.RandRange.To > 255 {
|
||||||
|
return nil, errInvalidRange
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
transform, err := buildCustomTransform(item.Transform)
|
||||||
|
if 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,
|
||||||
|
RandMin: item.RandRange.From,
|
||||||
|
RandMax: item.RandRange.To,
|
||||||
|
Packet: item.Packet,
|
||||||
|
Save: item.Capture,
|
||||||
|
Var: item.Reuse,
|
||||||
|
Expr: transform,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
Lengths []Int32Range `json:"lengths"`
|
||||||
|
Delays []Int32Range `json:"delays"`
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.Lengths) > 0 {
|
||||||
|
for _, r := range c.Lengths {
|
||||||
|
config.LengthsMin = append(config.LengthsMin, int64(r.From))
|
||||||
|
config.LengthsMax = append(config.LengthsMax, int64(r.To))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
config.LengthsMin = append(config.LengthsMin, int64(c.Length.From))
|
||||||
|
config.LengthsMax = append(config.LengthsMax, int64(c.Length.To))
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.LengthsMin[len(config.LengthsMin)-1] == 0 {
|
||||||
|
return nil, errors.New("last lengths entry min can't be 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.Delays) > 0 {
|
||||||
|
for _, r := range c.Delays {
|
||||||
|
config.DelaysMin = append(config.DelaysMin, int64(r.From))
|
||||||
|
config.DelaysMax = append(config.DelaysMax, int64(r.To))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
config.DelaysMin = append(config.DelaysMin, int64(c.Delay.From))
|
||||||
|
config.DelaysMax = append(config.DelaysMax, 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"`
|
||||||
|
RandRange *Int32Range `json:"randRange"`
|
||||||
|
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 {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
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),
|
||||||
|
RandRangeMin: item.RandRange.From,
|
||||||
|
RandRangeMax: item.RandRange.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 {
|
||||||
|
Rand int32 `json:"rand"`
|
||||||
|
RandRange *Int32Range `json:"randRange"`
|
||||||
|
Capture string `json:"capture"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Reuse string `json:"reuse"`
|
||||||
|
Transform *CustomTransform `json:"transform"`
|
||||||
|
Packet json.RawMessage `json:"packet"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomTransform struct {
|
||||||
|
Op string `json:"op"`
|
||||||
|
Args []CustomTransformArg `json:"args"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomTransformArg struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Bytes json.RawMessage `json:"bytes"`
|
||||||
|
U64 *uint64 `json:"u64"`
|
||||||
|
Reuse string `json:"reuse"`
|
||||||
|
Metadata string `json:"metadata"`
|
||||||
|
Transform *CustomTransform `json:"transform"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateCustomVarName(name string) error {
|
||||||
|
if name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !customVarNamePattern.MatchString(name) {
|
||||||
|
return errors.New("invalid variable name")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateCustomItemSpec(capture string, packet json.RawMessage, rand int32, reuse string, transform *CustomTransform) error {
|
||||||
|
if err := validateCustomVarName(capture); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := validateCustomVarName(reuse); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
kindCount := 0
|
||||||
|
if len(packet) > 0 {
|
||||||
|
kindCount++
|
||||||
|
}
|
||||||
|
if rand > 0 {
|
||||||
|
kindCount++
|
||||||
|
}
|
||||||
|
if reuse != "" {
|
||||||
|
kindCount++
|
||||||
|
}
|
||||||
|
if transform != nil {
|
||||||
|
kindCount++
|
||||||
|
}
|
||||||
|
if kindCount > 1 {
|
||||||
|
return errors.New("exactly one item kind must be set")
|
||||||
|
}
|
||||||
|
if kindCount == 0 && capture != "" {
|
||||||
|
return errors.New("exactly one item kind must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCustomTransform(transform *CustomTransform) (*custom.Expr, error) {
|
||||||
|
if transform == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if transform.Op == "" {
|
||||||
|
return nil, errors.New("transform op is required")
|
||||||
|
}
|
||||||
|
if len(transform.Args) == 0 {
|
||||||
|
return nil, errors.New("transform args are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
args := make([]*custom.ExprArg, 0, len(transform.Args))
|
||||||
|
for _, arg := range transform.Args {
|
||||||
|
parsedArg, err := buildCustomTransformArg(arg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
args = append(args, parsedArg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &custom.Expr{
|
||||||
|
Op: transform.Op,
|
||||||
|
Args: args,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCustomTransformArg(arg CustomTransformArg) (*custom.ExprArg, error) {
|
||||||
|
kindCount := 0
|
||||||
|
if len(arg.Bytes) > 0 {
|
||||||
|
kindCount++
|
||||||
|
}
|
||||||
|
if arg.U64 != nil {
|
||||||
|
kindCount++
|
||||||
|
}
|
||||||
|
if arg.Reuse != "" {
|
||||||
|
kindCount++
|
||||||
|
}
|
||||||
|
if arg.Metadata != "" {
|
||||||
|
kindCount++
|
||||||
|
}
|
||||||
|
if arg.Transform != nil {
|
||||||
|
kindCount++
|
||||||
|
}
|
||||||
|
if kindCount != 1 {
|
||||||
|
return nil, errors.New("transform arg must set exactly one value")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(arg.Bytes) > 0 {
|
||||||
|
value, err := PraseByteSlice(arg.Bytes, arg.Type)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &custom.ExprArg{
|
||||||
|
Value: &custom.ExprArg_Bytes{
|
||||||
|
Bytes: value,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if arg.U64 != nil {
|
||||||
|
return &custom.ExprArg{
|
||||||
|
Value: &custom.ExprArg_U64{
|
||||||
|
U64: *arg.U64,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if arg.Reuse != "" {
|
||||||
|
if err := validateCustomVarName(arg.Reuse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &custom.ExprArg{
|
||||||
|
Value: &custom.ExprArg_Var{
|
||||||
|
Var: arg.Reuse,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if arg.Metadata != "" {
|
||||||
|
return &custom.ExprArg{
|
||||||
|
Value: &custom.ExprArg_Metadata{
|
||||||
|
Metadata: arg.Metadata,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedExpr, err := buildCustomTransform(arg.Transform)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &custom.ExprArg{
|
||||||
|
Value: &custom.ExprArg_Expr{
|
||||||
|
Expr: parsedExpr,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type HeaderCustomUDP struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Client []UDPItem `json:"client"`
|
||||||
|
Server []UDPItem `json:"server"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HeaderCustomUDP) Build() (proto.Message, error) {
|
||||||
|
switch c.Mode {
|
||||||
|
case "", "prefix", "standalone":
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknown udp mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range c.Client {
|
||||||
|
if err := validateCustomItemSpec(item.Capture, item.Packet, item.Rand, item.Reuse, item.Transform); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, item := range c.Server {
|
||||||
|
if err := validateCustomItemSpec(item.Capture, item.Packet, item.Rand, item.Reuse, item.Transform); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client := make([]*custom.UDPItem, 0, len(c.Client))
|
||||||
|
for _, item := range c.Client {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
transform, err := buildCustomTransform(item.Transform)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client = append(client, &custom.UDPItem{
|
||||||
|
Rand: item.Rand,
|
||||||
|
RandMin: item.RandRange.From,
|
||||||
|
RandMax: item.RandRange.To,
|
||||||
|
Packet: item.Packet,
|
||||||
|
Save: item.Capture,
|
||||||
|
Var: item.Reuse,
|
||||||
|
Expr: transform,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
server := make([]*custom.UDPItem, 0, len(c.Server))
|
||||||
|
for _, item := range c.Server {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
transform, err := buildCustomTransform(item.Transform)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
server = append(server, &custom.UDPItem{
|
||||||
|
Rand: item.Rand,
|
||||||
|
RandMin: item.RandRange.From,
|
||||||
|
RandMax: item.RandRange.To,
|
||||||
|
Packet: item.Packet,
|
||||||
|
Save: item.Capture,
|
||||||
|
Var: item.Reuse,
|
||||||
|
Expr: transform,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Mode == "standalone" {
|
||||||
|
return &custom.UDPStandaloneConfig{
|
||||||
|
Client: client,
|
||||||
|
Server: server,
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
return &custom.UDPConfig{
|
||||||
|
Client: client,
|
||||||
|
Server: server,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MkcpLegacy struct {
|
||||||
|
Header string `json:"header"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MkcpLegacy) Build() (proto.Message, error) {
|
||||||
|
if len(c.Header) == 0 {
|
||||||
|
if len(c.Value) == 0 {
|
||||||
|
return &original.Config{}, nil
|
||||||
|
} else {
|
||||||
|
return &aes128gcm.Config{Password: c.Value}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch strings.ToLower(c.Header) {
|
||||||
|
case "dns":
|
||||||
|
domain := c.Value
|
||||||
|
if len(domain) == 0 {
|
||||||
|
domain = "www.baidu.com"
|
||||||
|
}
|
||||||
|
return &header.Config{ID: 0, Domain: domain}, nil
|
||||||
|
case "dtls":
|
||||||
|
return &header.Config{ID: 1}, nil
|
||||||
|
case "srtp":
|
||||||
|
return &header.Config{ID: 2}, nil
|
||||||
|
case "utp":
|
||||||
|
return &header.Config{ID: 3}, nil
|
||||||
|
case "wechat":
|
||||||
|
return &header.Config{ID: 4}, nil
|
||||||
|
case "wireguard":
|
||||||
|
return &header.Config{ID: 5}, nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("invalid header ", c.Header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Salamander struct {
|
||||||
|
Password string `json:"password"`
|
||||||
|
PacketSize Int32Range `json:"packetSize"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Salamander) Build() (proto.Message, error) {
|
||||||
|
if c.PacketSize.To > 0 {
|
||||||
|
if c.PacketSize.From <= 0 || c.PacketSize.To > 2048 {
|
||||||
|
return nil, errors.New("gecko: invalid min/max packet size")
|
||||||
|
}
|
||||||
|
return &salamander.GeckoConfig{
|
||||||
|
Password: c.Password,
|
||||||
|
MinPacketSize: c.PacketSize.From,
|
||||||
|
MaxPacketSize: c.PacketSize.To,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &salamander.Config{
|
||||||
|
Password: c.Password,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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 &sudoku.Config{
|
||||||
|
Password: c.Password,
|
||||||
|
Ascii: c.ASCII,
|
||||||
|
CustomTable: customTable,
|
||||||
|
CustomTables: customTables,
|
||||||
|
PaddingMin: paddingMin,
|
||||||
|
PaddingMax: paddingMax,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Xdns struct {
|
||||||
|
Domain json.RawMessage `json:"domain"`
|
||||||
|
|
||||||
|
Domains []string `json:"domains"`
|
||||||
|
Resolvers []string `json:"resolvers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Xdns) Build() (proto.Message, error) {
|
||||||
|
if c.Domain != nil {
|
||||||
|
return nil, errors.PrintRemovedFeatureError("domain", "domains(server) & resolvers(client)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.Domains) == 0 && len(c.Resolvers) == 0 {
|
||||||
|
return nil, errors.New("empty domains & empty resolvers")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range c.Resolvers {
|
||||||
|
if !strings.Contains(r, "+udp://") {
|
||||||
|
return nil, errors.New("invalid resolver ", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &xdns.Config{
|
||||||
|
Domains: c.Domains,
|
||||||
|
Resolvers: c.Resolvers,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Xicmp struct {
|
||||||
|
DGRAM bool `json:"dgram"`
|
||||||
|
IPs []string `json:"ips"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Xicmp) Build() (proto.Message, error) {
|
||||||
|
for _, ip := range c.IPs {
|
||||||
|
if _, err := netip.ParseAddr(ip); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &xicmp.Config{
|
||||||
|
DGRAM: c.DGRAM,
|
||||||
|
IPs: c.IPs,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Realm struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
StunServers []string `json:"stunServers"`
|
||||||
|
TlsConfig *TLSConfig `json:"tlsConfig"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Realm) Build() (proto.Message, error) {
|
||||||
|
var scheme, host, port, token, id string
|
||||||
|
var stunServers []string
|
||||||
|
var tlsConfig *tls.Config
|
||||||
|
|
||||||
|
u, err := url.Parse(c.Url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "realm":
|
||||||
|
scheme = "https"
|
||||||
|
case "realm+http":
|
||||||
|
scheme = "http"
|
||||||
|
default:
|
||||||
|
return nil, errors.New("invalid scheme", u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
host = u.Hostname()
|
||||||
|
if host == "" {
|
||||||
|
return nil, errors.New("invalid host", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
port = u.Port()
|
||||||
|
if port == "" {
|
||||||
|
port = "443"
|
||||||
|
if scheme == "http" {
|
||||||
|
port = "80"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err = url.PathUnescape(u.User.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if token == "" {
|
||||||
|
return nil, errors.New("invalid token", token)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err = url.PathUnescape(strings.TrimPrefix(u.EscapedPath(), "/"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if id == "" {
|
||||||
|
return nil, errors.New("invalid id", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.StunServers) == 0 {
|
||||||
|
return nil, errors.New("empty stunServers")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range c.StunServers {
|
||||||
|
_, _, err = net.SplitHostPort(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stunServers = c.StunServers
|
||||||
|
|
||||||
|
if c.TlsConfig != nil {
|
||||||
|
tc, err := c.TlsConfig.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConfig = tc.(*tls.Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &realm.Config{
|
||||||
|
Scheme: scheme,
|
||||||
|
Host: host,
|
||||||
|
Port: port,
|
||||||
|
Token: token,
|
||||||
|
ID: id,
|
||||||
|
StunServers: stunServers,
|
||||||
|
TlsConfig: tlsConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mask struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Settings *json.RawMessage `json:"settings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Mask) Build(tcp bool) (proto.Message, error) {
|
||||||
|
loader := udpmaskLoader
|
||||||
|
if tcp {
|
||||||
|
loader = tcpmaskLoader
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type QuicParamsConfig struct {
|
||||||
|
Congestion string `json:"congestion"`
|
||||||
|
Debug bool `json:"debug"`
|
||||||
|
BbrProfile string `json:"bbrProfile"`
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FinalMask struct {
|
||||||
|
Tcp []Mask `json:"tcp"`
|
||||||
|
Udp []Mask `json:"udp"`
|
||||||
|
QuicParams *QuicParamsConfig `json:"quicParams"`
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,814 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"math/big"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
|
"github.com/xtls/xray-core/common/serial"
|
||||||
|
"github.com/xtls/xray-core/common/utils"
|
||||||
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/grpc"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/headers/http"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/headers/noop"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/httpupgrade"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/hysteria"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/kcp"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/splithttp"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/tcp"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/websocket"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NoOpConnectionAuthenticator struct{}
|
||||||
|
|
||||||
|
func (NoOpConnectionAuthenticator) Build() (proto.Message, error) {
|
||||||
|
return new(noop.ConnectionConfig), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthenticatorRequest struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Path StringList `json:"path"`
|
||||||
|
Headers map[string]*StringList `json:"headers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortMapKeys(m map[string]*StringList) []string {
|
||||||
|
var keys []string
|
||||||
|
for key := range m {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
|
||||||
|
config := &http.RequestConfig{
|
||||||
|
Uri: []string{"/"},
|
||||||
|
Header: []*http.Header{
|
||||||
|
{
|
||||||
|
Name: "Host",
|
||||||
|
Value: []string{"www.baidu.com", "www.bing.com"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "User-Agent",
|
||||||
|
Value: []string{utils.ChromeUA},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Sec-CH-UA",
|
||||||
|
Value: []string{utils.ChromeUACH},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Sec-CH-UA-Mobile",
|
||||||
|
Value: []string{"?0"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Sec-CH-UA-Platform",
|
||||||
|
Value: []string{"Windows"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Sec-Fetch-Mode",
|
||||||
|
Value: []string{"no-cors", "cors", "same-origin"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Sec-Fetch-Dest",
|
||||||
|
Value: []string{"empty"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Sec-Fetch-Site",
|
||||||
|
Value: []string{"none"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Sec-Fetch-User",
|
||||||
|
Value: []string{"?1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Accept-Encoding",
|
||||||
|
Value: []string{"gzip, deflate"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Connection",
|
||||||
|
Value: []string{"keep-alive"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Pragma",
|
||||||
|
Value: []string{"no-cache"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.Version) > 0 {
|
||||||
|
config.Version = &http.Version{Value: v.Version}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.Method) > 0 {
|
||||||
|
config.Method = &http.Method{Value: v.Method}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.Path) > 0 {
|
||||||
|
config.Uri = append([]string(nil), v.Path...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.Headers) > 0 {
|
||||||
|
config.Header = make([]*http.Header, 0, len(v.Headers))
|
||||||
|
headerNames := sortMapKeys(v.Headers)
|
||||||
|
for _, key := range headerNames {
|
||||||
|
value := v.Headers[key]
|
||||||
|
if value == nil {
|
||||||
|
return nil, errors.New("empty HTTP header value: " + key).AtError()
|
||||||
|
}
|
||||||
|
config.Header = append(config.Header, &http.Header{
|
||||||
|
Name: key,
|
||||||
|
Value: append([]string(nil), (*value)...),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthenticatorResponse struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
Headers map[string]*StringList `json:"headers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *AuthenticatorResponse) Build() (*http.ResponseConfig, error) {
|
||||||
|
config := &http.ResponseConfig{
|
||||||
|
Header: []*http.Header{
|
||||||
|
{
|
||||||
|
Name: "Content-Type",
|
||||||
|
Value: []string{"application/octet-stream", "video/mpeg"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Transfer-Encoding",
|
||||||
|
Value: []string{"chunked"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Connection",
|
||||||
|
Value: []string{"keep-alive"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Pragma",
|
||||||
|
Value: []string{"no-cache"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Cache-Control",
|
||||||
|
Value: []string{"private", "no-cache"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.Version) > 0 {
|
||||||
|
config.Version = &http.Version{Value: v.Version}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.Status) > 0 || len(v.Reason) > 0 {
|
||||||
|
config.Status = &http.Status{
|
||||||
|
Code: "200",
|
||||||
|
Reason: "OK",
|
||||||
|
}
|
||||||
|
if len(v.Status) > 0 {
|
||||||
|
config.Status.Code = v.Status
|
||||||
|
}
|
||||||
|
if len(v.Reason) > 0 {
|
||||||
|
config.Status.Reason = v.Reason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v.Headers) > 0 {
|
||||||
|
config.Header = make([]*http.Header, 0, len(v.Headers))
|
||||||
|
headerNames := sortMapKeys(v.Headers)
|
||||||
|
for _, key := range headerNames {
|
||||||
|
value := v.Headers[key]
|
||||||
|
if value == nil {
|
||||||
|
return nil, errors.New("empty HTTP header value: " + key).AtError()
|
||||||
|
}
|
||||||
|
config.Header = append(config.Header, &http.Header{
|
||||||
|
Name: key,
|
||||||
|
Value: append([]string(nil), (*value)...),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Authenticator struct {
|
||||||
|
Request AuthenticatorRequest `json:"request"`
|
||||||
|
Response AuthenticatorResponse `json:"response"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Authenticator) Build() (proto.Message, error) {
|
||||||
|
config := new(http.Config)
|
||||||
|
requestConfig, err := v.Request.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.Request = requestConfig
|
||||||
|
|
||||||
|
responseConfig, err := v.Response.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.Response = responseConfig
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var tcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||||
|
"none": func() interface{} { return new(NoOpConnectionAuthenticator) },
|
||||||
|
"http": func() interface{} { return new(Authenticator) },
|
||||||
|
}, "type", "")
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return nil, errors.New("invalid TCP header config").Base(err).AtError()
|
||||||
|
}
|
||||||
|
ts, err := headerConfig.(Buildable).Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("invalid TCP header config").Base(err).AtError()
|
||||||
|
}
|
||||||
|
config.HeaderSettings = serial.ToTypedMessage(ts)
|
||||||
|
}
|
||||||
|
if c.AcceptProxyProtocol {
|
||||||
|
config.AcceptProxyProtocol = c.AcceptProxyProtocol
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type SplitHTTPConfig struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Headers map[string]string `json:"headers"`
|
||||||
|
XPaddingBytes Int32Range `json:"xPaddingBytes"`
|
||||||
|
XPaddingObfsMode bool `json:"xPaddingObfsMode"`
|
||||||
|
XPaddingKey string `json:"xPaddingKey"`
|
||||||
|
XPaddingHeader string `json:"xPaddingHeader"`
|
||||||
|
XPaddingPlacement string `json:"xPaddingPlacement"`
|
||||||
|
XPaddingMethod string `json:"xPaddingMethod"`
|
||||||
|
UplinkHTTPMethod string `json:"uplinkHTTPMethod"`
|
||||||
|
SessionIDPlacement string `json:"sessionIDPlacement"`
|
||||||
|
SessionIDKey string `json:"sessionIDKey"`
|
||||||
|
SessionIDTable string `json:"sessionIDTable"`
|
||||||
|
SessionIDLength Int32Range `json:"sessionIDLength"`
|
||||||
|
SeqPlacement string `json:"seqPlacement"`
|
||||||
|
SeqKey string `json:"seqKey"`
|
||||||
|
UplinkDataPlacement string `json:"uplinkDataPlacement"`
|
||||||
|
UplinkDataKey string `json:"uplinkDataKey"`
|
||||||
|
UplinkChunkSize Int32Range `json:"uplinkChunkSize"`
|
||||||
|
NoGRPCHeader bool `json:"noGRPCHeader"`
|
||||||
|
NoSSEHeader bool `json:"noSSEHeader"`
|
||||||
|
ScMaxEachPostBytes Int32Range `json:"scMaxEachPostBytes"`
|
||||||
|
ScMinPostsIntervalMs Int32Range `json:"scMinPostsIntervalMs"`
|
||||||
|
ScMaxBufferedPosts int64 `json:"scMaxBufferedPosts"`
|
||||||
|
ScStreamUpServerSecs Int32Range `json:"scStreamUpServerSecs"`
|
||||||
|
ServerMaxHeaderBytes int32 `json:"serverMaxHeaderBytes"`
|
||||||
|
Xmux XmuxConfig `json:"xmux"`
|
||||||
|
DownloadSettings *StreamConfig `json:"downloadSettings"`
|
||||||
|
Extra json.RawMessage `json:"extra"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type XmuxConfig struct {
|
||||||
|
MaxConcurrency Int32Range `json:"maxConcurrency"`
|
||||||
|
MaxConnections Int32Range `json:"maxConnections"`
|
||||||
|
CMaxReuseTimes Int32Range `json:"cMaxReuseTimes"`
|
||||||
|
HMaxRequestTimes Int32Range `json:"hMaxRequestTimes"`
|
||||||
|
HMaxReusableSecs Int32Range `json:"hMaxReusableSecs"`
|
||||||
|
HKeepAlivePeriod int64 `json:"hKeepAlivePeriod"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRangeConfig(input Int32Range) *splithttp.RangeConfig {
|
||||||
|
return &splithttp.RangeConfig{
|
||||||
|
From: input.From,
|
||||||
|
To: input.To,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build implements Buildable.
|
||||||
|
func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c.Mode {
|
||||||
|
case "":
|
||||||
|
c.Mode = "auto"
|
||||||
|
case "auto", "packet-up", "stream-up", "stream-one":
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported mode: " + c.Mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority (client): host > serverName > address
|
||||||
|
for k := range c.Headers {
|
||||||
|
if strings.ToLower(k) == "host" {
|
||||||
|
return nil, errors.New(`"headers" can't contain "host"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.XPaddingBytes != (Int32Range{}) && (c.XPaddingBytes.From <= 0 || c.XPaddingBytes.To <= 0) {
|
||||||
|
return nil, errors.New("xPaddingBytes cannot be disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "":
|
||||||
|
c.UplinkDataPlacement = splithttp.PlacementAuto
|
||||||
|
case splithttp.PlacementAuto, splithttp.PlacementBody:
|
||||||
|
case splithttp.PlacementCookie, splithttp.PlacementHeader:
|
||||||
|
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.SessionIDPlacement {
|
||||||
|
case "":
|
||||||
|
c.SessionIDPlacement = "path"
|
||||||
|
case "path", "cookie", "header", "query":
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported session placement: " + c.SessionIDPlacement)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c.SeqPlacement {
|
||||||
|
case "":
|
||||||
|
c.SeqPlacement = "path"
|
||||||
|
case "path", "cookie", "header", "query":
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported seq placement: " + c.SeqPlacement)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SessionIDPlacement != "path" && c.SessionIDKey == "" {
|
||||||
|
switch c.SessionIDPlacement {
|
||||||
|
case "cookie", "query":
|
||||||
|
c.SessionIDKey = "x_session"
|
||||||
|
case "header":
|
||||||
|
c.SessionIDKey = "X-Session"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SessionIDTable != "" {
|
||||||
|
if predefined, ok := splithttp.PredefinedTable[c.SessionIDTable]; ok {
|
||||||
|
c.SessionIDTable = predefined
|
||||||
|
}
|
||||||
|
room := roomSize(len(c.SessionIDTable), c.SessionIDLength.From, c.SessionIDLength.To)
|
||||||
|
// 2.1B possiblities should be enough
|
||||||
|
if room.Cmp(big.NewInt(2<<30)) < 0 {
|
||||||
|
return nil, errors.New("sessionIDTable or sessionIDLength is too small")
|
||||||
|
}
|
||||||
|
if c.SessionIDLength.From <= 0 {
|
||||||
|
return nil, errors.New("sessionIDLength.from must be greater than 0")
|
||||||
|
}
|
||||||
|
for i := 0; i < len(c.SessionIDTable); i++ {
|
||||||
|
if c.SessionIDTable[i] >= 0x80 {
|
||||||
|
return nil, errors.New("sessionIDTable must contain only ASCII characters")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SeqPlacement != "path" && c.SeqKey == "" {
|
||||||
|
switch c.SeqPlacement {
|
||||||
|
case "cookie", "query":
|
||||||
|
c.SeqKey = "x_seq"
|
||||||
|
case "header":
|
||||||
|
c.SeqKey = "X-Seq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.UplinkDataPlacement != splithttp.PlacementBody && c.UplinkDataKey == "" {
|
||||||
|
switch c.UplinkDataPlacement {
|
||||||
|
case splithttp.PlacementCookie:
|
||||||
|
c.UplinkDataKey = "x_data"
|
||||||
|
case splithttp.PlacementAuto, splithttp.PlacementHeader:
|
||||||
|
c.UplinkDataKey = "X-Data"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ServerMaxHeaderBytes < 0 {
|
||||||
|
return nil, errors.New("invalid negative value of maxHeaderBytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Xmux.MaxConnections.To > 0 && c.Xmux.MaxConcurrency.To > 0 {
|
||||||
|
return nil, errors.New("maxConnections cannot be specified together with maxConcurrency")
|
||||||
|
}
|
||||||
|
if c.Xmux == (XmuxConfig{}) {
|
||||||
|
c.Xmux.MaxConnections.From = 6
|
||||||
|
c.Xmux.MaxConnections.To = 6
|
||||||
|
c.Xmux.HMaxRequestTimes.From = 600
|
||||||
|
c.Xmux.HMaxRequestTimes.To = 900
|
||||||
|
c.Xmux.HMaxReusableSecs.From = 1800
|
||||||
|
c.Xmux.HMaxReusableSecs.To = 3000
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &splithttp.Config{
|
||||||
|
Host: c.Host,
|
||||||
|
Path: c.Path,
|
||||||
|
Mode: c.Mode,
|
||||||
|
Headers: c.Headers,
|
||||||
|
XPaddingBytes: newRangeConfig(c.XPaddingBytes),
|
||||||
|
XPaddingObfsMode: c.XPaddingObfsMode,
|
||||||
|
XPaddingKey: c.XPaddingKey,
|
||||||
|
XPaddingHeader: c.XPaddingHeader,
|
||||||
|
XPaddingPlacement: c.XPaddingPlacement,
|
||||||
|
XPaddingMethod: c.XPaddingMethod,
|
||||||
|
UplinkHTTPMethod: c.UplinkHTTPMethod,
|
||||||
|
SessionIDPlacement: c.SessionIDPlacement,
|
||||||
|
SeqPlacement: c.SeqPlacement,
|
||||||
|
SessionIDKey: c.SessionIDKey,
|
||||||
|
SeqKey: c.SeqKey,
|
||||||
|
UplinkDataPlacement: c.UplinkDataPlacement,
|
||||||
|
UplinkDataKey: c.UplinkDataKey,
|
||||||
|
UplinkChunkSize: newRangeConfig(c.UplinkChunkSize),
|
||||||
|
NoGRPCHeader: c.NoGRPCHeader,
|
||||||
|
NoSSEHeader: c.NoSSEHeader,
|
||||||
|
ScMaxEachPostBytes: newRangeConfig(c.ScMaxEachPostBytes),
|
||||||
|
ScMinPostsIntervalMs: newRangeConfig(c.ScMinPostsIntervalMs),
|
||||||
|
ScMaxBufferedPosts: c.ScMaxBufferedPosts,
|
||||||
|
ScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs),
|
||||||
|
ServerMaxHeaderBytes: c.ServerMaxHeaderBytes,
|
||||||
|
SessionIDTable: c.SessionIDTable,
|
||||||
|
SessionIDLength: newRangeConfig(c.SessionIDLength),
|
||||||
|
Xmux: &splithttp.XmuxConfig{
|
||||||
|
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
|
||||||
|
MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
|
||||||
|
CMaxReuseTimes: newRangeConfig(c.Xmux.CMaxReuseTimes),
|
||||||
|
HMaxRequestTimes: newRangeConfig(c.Xmux.HMaxRequestTimes),
|
||||||
|
HMaxReusableSecs: newRangeConfig(c.Xmux.HMaxReusableSecs),
|
||||||
|
HKeepAlivePeriod: c.Xmux.HKeepAlivePeriod,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.DownloadSettings != nil {
|
||||||
|
if c.Mode == "stream-one" {
|
||||||
|
return nil, errors.New(`Can not use "downloadSettings" in "stream-one" mode.`)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if config.DownloadSettings, err = c.DownloadSettings.Build(); err != nil {
|
||||||
|
return nil, errors.New(`Failed to build "downloadSettings".`).Base(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func roomSize(tableSize int, min, max int32) *big.Int {
|
||||||
|
base := big.NewInt(int64(tableSize))
|
||||||
|
sum := new(big.Int)
|
||||||
|
term := new(big.Int)
|
||||||
|
for k := min; k <= max; k++ {
|
||||||
|
term.Exp(base, big.NewInt(int64(k)), nil)
|
||||||
|
sum.Add(sum, term)
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
type KCPConfig struct {
|
||||||
|
Mtu *uint32 `json:"mtu"`
|
||||||
|
Tti *uint32 `json:"tti"`
|
||||||
|
UpCap *uint32 `json:"uplinkCapacity"`
|
||||||
|
DownCap *uint32 `json:"downlinkCapacity"`
|
||||||
|
CwndMultiplier *uint32 `json:"cwndMultiplier"`
|
||||||
|
MaxSendingWindow *uint32 `json:"maxSendingWindow"`
|
||||||
|
|
||||||
|
HeaderConfig json.RawMessage `json:"header"`
|
||||||
|
Seed *string `json:"seed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build implements Buildable.
|
||||||
|
func (c *KCPConfig) Build() (proto.Message, error) {
|
||||||
|
if c.HeaderConfig != nil || c.Seed != nil {
|
||||||
|
return nil, errors.PrintRemovedFeatureError("mkcp header & seed", "finalmask/udp header-* & mkcp-original & mkcp-aes128gcm")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := common.Must2(internet.CreateTransportConfig(kcp.ProtocolName)).(*kcp.Config)
|
||||||
|
|
||||||
|
if c.Mtu != nil {
|
||||||
|
config.Mtu = *c.Mtu
|
||||||
|
}
|
||||||
|
if c.Tti != nil {
|
||||||
|
config.Tti = *c.Tti
|
||||||
|
}
|
||||||
|
if c.UpCap != nil {
|
||||||
|
config.UplinkCapacity = *c.UpCap
|
||||||
|
}
|
||||||
|
if c.DownCap != nil {
|
||||||
|
config.DownlinkCapacity = *c.DownCap
|
||||||
|
}
|
||||||
|
if c.CwndMultiplier != nil {
|
||||||
|
config.CwndMultiplier = *c.CwndMultiplier
|
||||||
|
}
|
||||||
|
if c.MaxSendingWindow != nil {
|
||||||
|
config.MaxSendingWindow = *c.MaxSendingWindow
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Mtu < 21 {
|
||||||
|
return nil, errors.New("Mtu must be at least 21").AtError()
|
||||||
|
}
|
||||||
|
if config.Tti < 10 || config.Tti > 1000 {
|
||||||
|
return nil, errors.New("invalid mKCP TTI: ", c.Tti).AtError()
|
||||||
|
}
|
||||||
|
if config.CwndMultiplier < 1 {
|
||||||
|
return nil, errors.New("CwndMultiplier must be at least 1").AtError()
|
||||||
|
}
|
||||||
|
if config.GetSendingBufferSize() == 0 {
|
||||||
|
return nil, errors.New("MaxSendingWindow must be >= Mtu").AtError()
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GRPCConfig struct {
|
||||||
|
Authority string `json:"authority"`
|
||||||
|
ServiceName string `json:"serviceName"`
|
||||||
|
MultiMode bool `json:"multiMode"`
|
||||||
|
IdleTimeout int32 `json:"idle_timeout"`
|
||||||
|
HealthCheckTimeout int32 `json:"health_check_timeout"`
|
||||||
|
PermitWithoutStream bool `json:"permit_without_stream"`
|
||||||
|
InitialWindowsSize int32 `json:"initial_windows_size"`
|
||||||
|
UserAgent string `json:"user_agent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GRPCConfig) Build() (proto.Message, error) {
|
||||||
|
if g.IdleTimeout <= 0 {
|
||||||
|
g.IdleTimeout = 0
|
||||||
|
}
|
||||||
|
if g.HealthCheckTimeout <= 0 {
|
||||||
|
g.HealthCheckTimeout = 0
|
||||||
|
}
|
||||||
|
if g.InitialWindowsSize < 0 {
|
||||||
|
// default window size of gRPC-go
|
||||||
|
g.InitialWindowsSize = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return &grpc.Config{
|
||||||
|
Authority: g.Authority,
|
||||||
|
ServiceName: g.ServiceName,
|
||||||
|
MultiMode: g.MultiMode,
|
||||||
|
IdleTimeout: g.IdleTimeout,
|
||||||
|
HealthCheckTimeout: g.HealthCheckTimeout,
|
||||||
|
PermitWithoutStream: g.PermitWithoutStream,
|
||||||
|
InitialWindowsSize: g.InitialWindowsSize,
|
||||||
|
UserAgent: g.UserAgent,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebSocketConfig struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Headers map[string]string `json:"headers"`
|
||||||
|
AcceptProxyProtocol bool `json:"acceptProxyProtocol"`
|
||||||
|
HeartbeatPeriod uint32 `json:"heartbeatPeriod"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build implements Buildable.
|
||||||
|
func (c *WebSocketConfig) Build() (proto.Message, error) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Priority (client): host > serverName > address
|
||||||
|
for k, v := range c.Headers {
|
||||||
|
if strings.ToLower(k) == "host" {
|
||||||
|
errors.PrintDeprecatedFeatureWarning(`"host" in "headers"`, `independent "host"`)
|
||||||
|
if c.Host == "" {
|
||||||
|
c.Host = v
|
||||||
|
}
|
||||||
|
delete(c.Headers, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config := &websocket.Config{
|
||||||
|
Path: path,
|
||||||
|
Host: c.Host,
|
||||||
|
Header: c.Headers,
|
||||||
|
AcceptProxyProtocol: c.AcceptProxyProtocol,
|
||||||
|
Ed: ed,
|
||||||
|
HeartbeatPeriod: c.HeartbeatPeriod,
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpUpgradeConfig struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Headers map[string]string `json:"headers"`
|
||||||
|
AcceptProxyProtocol bool `json:"acceptProxyProtocol"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build implements Buildable.
|
||||||
|
func (c *HttpUpgradeConfig) Build() (proto.Message, error) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Priority (client): host > serverName > address
|
||||||
|
for k := range c.Headers {
|
||||||
|
if strings.ToLower(k) == "host" {
|
||||||
|
return nil, errors.New(`"headers" can't contain "host"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config := &httpupgrade.Config{
|
||||||
|
Path: path,
|
||||||
|
Host: c.Host,
|
||||||
|
Header: c.Headers,
|
||||||
|
AcceptProxyProtocol: c.AcceptProxyProtocol,
|
||||||
|
Ed: ed,
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
PortList PortList `json:"ports"`
|
||||||
|
Interval Int32Range `json:"interval"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HysteriaConfig struct {
|
||||||
|
Version int32 `json:"version"`
|
||||||
|
Auth string `json:"auth"`
|
||||||
|
|
||||||
|
Congestion *string `json:"congestion"`
|
||||||
|
Up *Bandwidth `json:"up"`
|
||||||
|
Down *Bandwidth `json:"down"`
|
||||||
|
UdpHop *UdpHop `json:"udphop"`
|
||||||
|
|
||||||
|
UdpIdleTimeout int64 `json:"udpIdleTimeout"`
|
||||||
|
Masquerade Masquerade `json:"masquerade"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HysteriaConfig) Build() (proto.Message, error) {
|
||||||
|
if c.Version != 2 {
|
||||||
|
return nil, errors.New("version != 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.UdpIdleTimeout != 0 && (c.UdpIdleTimeout < 2 || c.UdpIdleTimeout > 600) {
|
||||||
|
return nil, errors.New("UdpIdleTimeout must be between 2 and 600")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &hysteria.Config{}
|
||||||
|
config.Auth = c.Auth
|
||||||
|
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
|
||||||
|
|
||||||
|
if config.UdpIdleTimeout == 0 {
|
||||||
|
config.UdpIdleTimeout = 60
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFileOrString(f string, s []string) ([]byte, error) {
|
||||||
|
if len(f) > 0 {
|
||||||
|
return filesystem.ReadCert(f)
|
||||||
|
}
|
||||||
|
if len(s) > 0 {
|
||||||
|
return []byte(strings.Join(s, "\n")), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("both file and bytes are empty.")
|
||||||
|
}
|
||||||
@@ -0,0 +1,402 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/reality"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LimitFallback struct {
|
||||||
|
AfterBytes uint64
|
||||||
|
BytesPerSec uint64
|
||||||
|
BurstBytesPerSec uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type REALITYConfig struct {
|
||||||
|
MasterKeyLog string `json:"masterKeyLog"`
|
||||||
|
Show bool `json:"show"`
|
||||||
|
Target json.RawMessage `json:"target"`
|
||||||
|
Dest json.RawMessage `json:"dest"`
|
||||||
|
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"`
|
||||||
|
Mldsa65Seed string `json:"mldsa65Seed"`
|
||||||
|
|
||||||
|
LimitFallbackUpload LimitFallback `json:"limitFallbackUpload"`
|
||||||
|
LimitFallbackDownload LimitFallback `json:"limitFallbackDownload"`
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *REALITYConfig) Build() (proto.Message, error) {
|
||||||
|
config := new(reality.Config)
|
||||||
|
config.MasterKeyLog = c.MasterKeyLog
|
||||||
|
config.Show = c.Show
|
||||||
|
var err error
|
||||||
|
if c.Target != nil {
|
||||||
|
c.Dest = c.Target
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
s = "localhost:" + s
|
||||||
|
}
|
||||||
|
if _, _, err = net.SplitHostPort(s); err == nil {
|
||||||
|
c.Type = "tcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.Type == "" {
|
||||||
|
return nil, errors.New(`please fill in a valid value for "target"`)
|
||||||
|
}
|
||||||
|
if c.Xver > 2 {
|
||||||
|
return nil, errors.New(`invalid PROXY protocol version, "xver" only accepts 0, 1, 2`)
|
||||||
|
}
|
||||||
|
if len(c.ServerNames) == 0 {
|
||||||
|
return nil, errors.New(`empty "serverNames"`)
|
||||||
|
}
|
||||||
|
if c.PrivateKey == "" {
|
||||||
|
return nil, errors.New(`empty "privateKey"`)
|
||||||
|
}
|
||||||
|
if config.PrivateKey, err = base64.RawURLEncoding.DecodeString(c.PrivateKey); err != nil || len(config.PrivateKey) != 32 {
|
||||||
|
return nil, errors.New(`invalid "privateKey": `, c.PrivateKey)
|
||||||
|
}
|
||||||
|
if c.MinClientVer != "" {
|
||||||
|
config.MinClientVer = make([]byte, 3)
|
||||||
|
var u uint64
|
||||||
|
for i, s := range strings.Split(c.MinClientVer, ".") {
|
||||||
|
if i == 3 {
|
||||||
|
return nil, errors.New(`invalid "minClientVer": `, c.MinClientVer)
|
||||||
|
}
|
||||||
|
if u, err = strconv.ParseUint(s, 10, 8); err != nil {
|
||||||
|
return nil, errors.New(`"minClientVer[`, i, `]" should be less than 256`)
|
||||||
|
} 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 {
|
||||||
|
return nil, errors.New(`invalid "maxClientVer": `, c.MaxClientVer)
|
||||||
|
}
|
||||||
|
if u, err = strconv.ParseUint(s, 10, 8); err != nil {
|
||||||
|
return nil, errors.New(`"maxClientVer[`, i, `]" should be less than 256`)
|
||||||
|
} else {
|
||||||
|
config.MaxClientVer[i] = byte(u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(c.ShortIds) == 0 {
|
||||||
|
return nil, errors.New(`empty "shortIds"`)
|
||||||
|
}
|
||||||
|
config.ShortIds = make([][]byte, len(c.ShortIds))
|
||||||
|
for i, s := range c.ShortIds {
|
||||||
|
if len(s) > 16 {
|
||||||
|
return nil, errors.New(`too long "shortIds[`, i, `]": `, s)
|
||||||
|
}
|
||||||
|
config.ShortIds[i] = make([]byte, 8)
|
||||||
|
if _, err = hex.Decode(config.ShortIds[i], []byte(s)); err != nil {
|
||||||
|
return nil, errors.New(`invalid "shortIds[`, i, `]": `, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.Dest = s
|
||||||
|
config.Type = c.Type
|
||||||
|
config.Xver = c.Xver
|
||||||
|
config.ServerNames = c.ServerNames
|
||||||
|
config.MaxTimeDiff = c.MaxTimeDiff
|
||||||
|
|
||||||
|
if c.Mldsa65Seed != "" {
|
||||||
|
if c.Mldsa65Seed == c.PrivateKey {
|
||||||
|
return nil, errors.New(`"mldsa65Seed" and "privateKey" can not be the same value: `, c.Mldsa65Seed)
|
||||||
|
}
|
||||||
|
if config.Mldsa65Seed, err = base64.RawURLEncoding.DecodeString(c.Mldsa65Seed); err != nil || len(config.Mldsa65Seed) != 32 {
|
||||||
|
return nil, errors.New(`invalid "mldsa65Seed": `, c.Mldsa65Seed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sn := range config.ServerNames {
|
||||||
|
if strings.Contains(sn, "apple") || strings.Contains(sn, "icloud") {
|
||||||
|
errors.LogWarning(context.Background(), `REALITY: Choosing apple, icloud, etc. as the target may get your IP blocked by the GFW`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
config.Fingerprint = strings.ToLower(c.Fingerprint)
|
||||||
|
if config.Fingerprint == "unsafe" || config.Fingerprint == "hellogolang" {
|
||||||
|
return nil, errors.New(`invalid "fingerprint": `, config.Fingerprint)
|
||||||
|
}
|
||||||
|
if tls.GetFingerprint(config.Fingerprint) == nil {
|
||||||
|
return nil, errors.New(`unknown "fingerprint": `, config.Fingerprint)
|
||||||
|
}
|
||||||
|
if len(c.ServerNames) != 0 {
|
||||||
|
return nil, errors.New(`non-empty "serverNames", please use "serverName" instead`)
|
||||||
|
}
|
||||||
|
if c.Password != "" {
|
||||||
|
c.PublicKey = c.Password
|
||||||
|
}
|
||||||
|
if c.PublicKey == "" {
|
||||||
|
return nil, errors.New(`empty "password"`)
|
||||||
|
}
|
||||||
|
if config.PublicKey, err = base64.RawURLEncoding.DecodeString(c.PublicKey); err != nil || len(config.PublicKey) != 32 {
|
||||||
|
return nil, errors.New(`invalid "password": `, c.PublicKey)
|
||||||
|
}
|
||||||
|
if len(c.ShortIds) != 0 {
|
||||||
|
return nil, errors.New(`non-empty "shortIds", please use "shortId" instead`)
|
||||||
|
}
|
||||||
|
if len(c.ShortId) > 16 {
|
||||||
|
return nil, errors.New(`too long "shortId": `, c.ShortId)
|
||||||
|
}
|
||||||
|
config.ShortId = make([]byte, 8)
|
||||||
|
if _, err = hex.Decode(config.ShortId, []byte(c.ShortId)); err != nil {
|
||||||
|
return nil, errors.New(`invalid "shortId": `, c.ShortId)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.SpiderX == "" {
|
||||||
|
c.SpiderX = "/"
|
||||||
|
}
|
||||||
|
if c.SpiderX[0] != '/' {
|
||||||
|
return nil, errors.New(`invalid "spiderX": `, c.SpiderX)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type TLSCertConfig struct {
|
||||||
|
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"`
|
||||||
|
BuildChain bool `json:"buildChain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build implements Buildable.
|
||||||
|
func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
|
||||||
|
certificate := new(tls.Certificate)
|
||||||
|
|
||||||
|
cert, err := readFileOrString(c.CertFile, c.CertStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to parse certificate").Base(err)
|
||||||
|
}
|
||||||
|
certificate.Certificate = cert
|
||||||
|
certificate.CertificatePath = c.CertFile
|
||||||
|
|
||||||
|
if len(c.KeyFile) > 0 || len(c.KeyStr) > 0 {
|
||||||
|
key, err := readFileOrString(c.KeyFile, c.KeyStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to parse key").Base(err)
|
||||||
|
}
|
||||||
|
certificate.Key = key
|
||||||
|
certificate.KeyPath = c.KeyFile
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if certificate.KeyPath == "" && certificate.CertificatePath == "" {
|
||||||
|
certificate.OneTimeLoading = true
|
||||||
|
} else {
|
||||||
|
certificate.OneTimeLoading = c.OneTimeLoading
|
||||||
|
}
|
||||||
|
certificate.OcspStapling = c.OcspStapling
|
||||||
|
certificate.BuildChain = c.BuildChain
|
||||||
|
|
||||||
|
return certificate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TLSConfig struct {
|
||||||
|
AllowInsecure bool `json:"allowInsecure"`
|
||||||
|
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"`
|
||||||
|
PinnedPeerCertSha256 string `json:"pinnedPeerCertSha256"`
|
||||||
|
VerifyPeerCertByName string `json:"verifyPeerCertByName"`
|
||||||
|
ECHServerKeys string `json:"echServerKeys"`
|
||||||
|
ECHConfigList string `json:"echConfigList"`
|
||||||
|
ECHSocketSettings *SocketConfig `json:"echSockopt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
if len(config.NextProtocol) > 1 {
|
||||||
|
for _, p := range config.NextProtocol {
|
||||||
|
if tls.IsFromMitm(p) {
|
||||||
|
return nil, errors.New(`only one element is allowed in "alpn" when using "fromMitm" in it`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.CurvePreferences != nil && len(*c.CurvePreferences) > 0 {
|
||||||
|
config.CurvePreferences = []string(*c.CurvePreferences)
|
||||||
|
}
|
||||||
|
config.EnableSessionResumption = c.EnableSessionResumption
|
||||||
|
config.DisableSystemRoot = c.DisableSystemRoot
|
||||||
|
config.MinVersion = c.MinVersion
|
||||||
|
config.MaxVersion = c.MaxVersion
|
||||||
|
config.CipherSuites = c.CipherSuites
|
||||||
|
config.Fingerprint = strings.ToLower(c.Fingerprint)
|
||||||
|
if config.Fingerprint != "unsafe" && tls.GetFingerprint(config.Fingerprint) == nil {
|
||||||
|
return nil, errors.New(`unknown "fingerprint": `, config.Fingerprint)
|
||||||
|
}
|
||||||
|
config.RejectUnknownSni = c.RejectUnknownSNI
|
||||||
|
config.MasterKeyLog = c.MasterKeyLog
|
||||||
|
|
||||||
|
if c.AllowInsecure {
|
||||||
|
return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"(pcs) and "verifyPeerCertByName"(vcn)`)
|
||||||
|
}
|
||||||
|
if c.PinnedPeerCertSha256 != "" {
|
||||||
|
for v := range strings.SplitSeq(c.PinnedPeerCertSha256, ",") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// remove colons for OpenSSL format
|
||||||
|
hashValue, err := hex.DecodeString(strings.ReplaceAll(v, ":", ""))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(hashValue) != 32 {
|
||||||
|
return nil, errors.New("incorrect pinnedPeerCertSha256 length: ", v)
|
||||||
|
}
|
||||||
|
config.PinnedPeerCertSha256 = append(config.PinnedPeerCertSha256, hashValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.VerifyPeerCertByName != "" {
|
||||||
|
for v := range strings.SplitSeq(c.VerifyPeerCertByName, ",") {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
config.VerifyPeerCertByName = append(config.VerifyPeerCertByName, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
config.EchConfigList = c.ECHConfigList
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomSockoptConfig struct {
|
||||||
|
Syetem string `json:"system"`
|
||||||
|
Network string `json:"network"`
|
||||||
|
Level string `json:"level"`
|
||||||
|
Opt string `json:"opt"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type SocketConfig struct {
|
||||||
|
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"`
|
||||||
|
TrustedXForwardedFor []string `json:"trustedXForwardedFor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build implements Buildable.
|
||||||
|
func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
||||||
|
tfo := int32(0) // don't invoke setsockopt() for TFO
|
||||||
|
if c.TFO != nil {
|
||||||
|
switch v := c.TFO.(type) {
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
tfo = 256
|
||||||
|
} else {
|
||||||
|
tfo = -1 // TFO need to be disabled
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
tfo = int32(math.Min(v, math.MaxInt32))
|
||||||
|
default:
|
||||||
|
return nil, errors.New("tcpFastOpen: only boolean and integer value is acceptable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
dStrategy := internet.DomainStrategy_AS_IS
|
||||||
|
switch strings.ToLower(c.DomainStrategy) {
|
||||||
|
case "asis", "":
|
||||||
|
dStrategy = internet.DomainStrategy_AS_IS
|
||||||
|
case "useip":
|
||||||
|
dStrategy = internet.DomainStrategy_USE_IP
|
||||||
|
case "useipv4":
|
||||||
|
dStrategy = internet.DomainStrategy_USE_IP4
|
||||||
|
case "useipv6":
|
||||||
|
dStrategy = internet.DomainStrategy_USE_IP6
|
||||||
|
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:
|
||||||
|
return nil, errors.New("unsupported domain strategy: ", c.DomainStrategy)
|
||||||
|
}
|
||||||
|
|
||||||
|
var customSockopts []*internet.CustomSockopt
|
||||||
|
|
||||||
|
for _, copt := range c.CustomSockopt {
|
||||||
|
customSockopt := &internet.CustomSockopt{
|
||||||
|
System: copt.Syetem,
|
||||||
|
Network: copt.Network,
|
||||||
|
Level: copt.Level,
|
||||||
|
Opt: copt.Opt,
|
||||||
|
Value: copt.Value,
|
||||||
|
Type: copt.Type,
|
||||||
|
}
|
||||||
|
customSockopts = append(customSockopts, customSockopt)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
return &internet.SocketConfig{
|
||||||
|
Mark: c.Mark,
|
||||||
|
Tfo: tfo,
|
||||||
|
Tproxy: tproxy,
|
||||||
|
DomainStrategy: dStrategy,
|
||||||
|
AcceptProxyProtocol: c.AcceptProxyProtocol,
|
||||||
|
DialerProxy: c.DialerProxy,
|
||||||
|
TcpKeepAliveInterval: c.TCPKeepAliveInterval,
|
||||||
|
TcpKeepAliveIdle: c.TCPKeepAliveIdle,
|
||||||
|
TcpCongestion: c.TCPCongestion,
|
||||||
|
TcpWindowClamp: c.TCPWindowClamp,
|
||||||
|
TcpMaxSeg: c.TCPMaxSeg,
|
||||||
|
Penetrate: c.Penetrate,
|
||||||
|
TcpUserTimeout: c.TCPUserTimeout,
|
||||||
|
V6Only: c.V6only,
|
||||||
|
Interface: c.Interface,
|
||||||
|
TcpMptcp: c.TcpMptcp,
|
||||||
|
CustomSockopt: customSockopts,
|
||||||
|
AddressPortStrategy: addressPortStrategy,
|
||||||
|
HappyEyeballs: happyEyeballs,
|
||||||
|
TrustedXForwardedFor: c.TrustedXForwardedFor,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user