mirror of
https://github.com/XTLS/Xray-core.git
synced 2024-11-25 14:46:04 +03:00
93cff1a576
It turns out that some panels like to set `"xmux": {"maxConnections": 0, "maxConcurrency": 0}`, and of course that fails now. To make their job easier, let's treat `0` the same as not setting the parameter. Again, I don't like that xray's defaults are hardcoded all over the place. I would have liked a different default value for Xmux in a future version, but it actually can't be done in practice because everybody just copypastes the defaults from the docs into their own sourcecode (and sometimes changes them silently to their own idea of a good default)
942 lines
30 KiB
Go
942 lines
30 KiB
Go
package conf
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"math"
|
|
"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/common/platform/filesystem"
|
|
"github.com/xtls/xray-core/common/serial"
|
|
"github.com/xtls/xray-core/transport/internet"
|
|
httpheader "github.com/xtls/xray-core/transport/internet/headers/http"
|
|
"github.com/xtls/xray-core/transport/internet/http"
|
|
"github.com/xtls/xray-core/transport/internet/httpupgrade"
|
|
"github.com/xtls/xray-core/transport/internet/kcp"
|
|
"github.com/xtls/xray-core/transport/internet/reality"
|
|
"github.com/xtls/xray-core/transport/internet/splithttp"
|
|
"github.com/xtls/xray-core/transport/internet/tcp"
|
|
"github.com/xtls/xray-core/transport/internet/tls"
|
|
"github.com/xtls/xray-core/transport/internet/websocket"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
var (
|
|
kcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
|
"none": func() interface{} { return new(NoOpAuthenticator) },
|
|
"srtp": func() interface{} { return new(SRTPAuthenticator) },
|
|
"utp": func() interface{} { return new(UTPAuthenticator) },
|
|
"wechat-video": func() interface{} { return new(WechatVideoAuthenticator) },
|
|
"dtls": func() interface{} { return new(DTLSAuthenticator) },
|
|
"wireguard": func() interface{} { return new(WireguardAuthenticator) },
|
|
"dns": func() interface{} { return new(DNSAuthenticator) },
|
|
}, "type", "")
|
|
|
|
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
|
|
if mtu < 576 || mtu > 1460 {
|
|
return nil, errors.New("invalid mKCP MTU size: ", mtu).AtError()
|
|
}
|
|
config.Mtu = &kcp.MTU{Value: mtu}
|
|
}
|
|
if c.Tti != nil {
|
|
tti := *c.Tti
|
|
if tti < 10 || tti > 100 {
|
|
return nil, errors.New("invalid mKCP TTI: ", tti).AtError()
|
|
}
|
|
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}
|
|
}
|
|
}
|
|
if len(c.HeaderConfig) > 0 {
|
|
headerConfig, _, err := kcpHeaderLoader.Load(c.HeaderConfig)
|
|
if err != nil {
|
|
return nil, errors.New("invalid mKCP header config.").Base(err).AtError()
|
|
}
|
|
ts, err := headerConfig.(Buildable).Build()
|
|
if err != nil {
|
|
return nil, errors.New("invalid mKCP header config").Base(err).AtError()
|
|
}
|
|
config.HeaderConfig = serial.ToTypedMessage(ts)
|
|
}
|
|
|
|
if c.Seed != nil {
|
|
config.Seed = &kcp.EncryptionSeed{Seed: *c.Seed}
|
|
}
|
|
|
|
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 {
|
|
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 WebSocketConfig struct {
|
|
Host string `json:"host"`
|
|
Path string `json:"path"`
|
|
Headers map[string]string `json:"headers"`
|
|
AcceptProxyProtocol bool `json:"acceptProxyProtocol"`
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
}
|
|
// If http host is not set in the Host field, but in headers field, we add it to Host Field here.
|
|
// If we don't do that, http host will be overwritten as address.
|
|
// Host priority: Host field > headers field > address.
|
|
if c.Host == "" && c.Headers["host"] != "" {
|
|
c.Host = c.Headers["host"]
|
|
} else if c.Host == "" && c.Headers["Host"] != "" {
|
|
c.Host = c.Headers["Host"]
|
|
}
|
|
config := &websocket.Config{
|
|
Path: path,
|
|
Host: c.Host,
|
|
Header: c.Headers,
|
|
AcceptProxyProtocol: c.AcceptProxyProtocol,
|
|
Ed: ed,
|
|
}
|
|
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()
|
|
}
|
|
}
|
|
// If http host is not set in the Host field, but in headers field, we add it to Host Field here.
|
|
// If we don't do that, http host will be overwritten as address.
|
|
// Host priority: Host field > headers field > address.
|
|
if c.Host == "" && c.Headers["host"] != "" {
|
|
c.Host = c.Headers["host"]
|
|
delete(c.Headers, "host")
|
|
} else if c.Host == "" && c.Headers["Host"] != "" {
|
|
c.Host = c.Headers["Host"]
|
|
delete(c.Headers, "Host")
|
|
}
|
|
config := &httpupgrade.Config{
|
|
Path: path,
|
|
Host: c.Host,
|
|
Header: c.Headers,
|
|
AcceptProxyProtocol: c.AcceptProxyProtocol,
|
|
Ed: ed,
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
type SplitHTTPConfig struct {
|
|
Host string `json:"host"`
|
|
Path string `json:"path"`
|
|
Headers map[string]string `json:"headers"`
|
|
ScMaxConcurrentPosts *Int32Range `json:"scMaxConcurrentPosts"`
|
|
ScMaxEachPostBytes *Int32Range `json:"scMaxEachPostBytes"`
|
|
ScMinPostsIntervalMs *Int32Range `json:"scMinPostsIntervalMs"`
|
|
NoSSEHeader bool `json:"noSSEHeader"`
|
|
XPaddingBytes *Int32Range `json:"xPaddingBytes"`
|
|
Xmux Xmux `json:"xmux"`
|
|
}
|
|
|
|
type Xmux struct {
|
|
MaxConcurrency *Int32Range `json:"maxConcurrency"`
|
|
MaxConnections *Int32Range `json:"maxConnections"`
|
|
CMaxReuseTimes *Int32Range `json:"cMaxReuseTimes"`
|
|
CMaxLifetimeMs *Int32Range `json:"cMaxLifetimeMs"`
|
|
}
|
|
|
|
func splithttpNewRandRangeConfig(input *Int32Range) *splithttp.RandRangeConfig {
|
|
if input == nil {
|
|
return nil
|
|
}
|
|
|
|
return &splithttp.RandRangeConfig{
|
|
From: input.From,
|
|
To: input.To,
|
|
}
|
|
}
|
|
|
|
// Build implements Buildable.
|
|
func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
|
// If http host is not set in the Host field, but in headers field, we add it to Host Field here.
|
|
// If we don't do that, http host will be overwritten as address.
|
|
// Host priority: Host field > headers field > address.
|
|
if c.Host == "" && c.Headers["host"] != "" {
|
|
c.Host = c.Headers["host"]
|
|
} else if c.Host == "" && c.Headers["Host"] != "" {
|
|
c.Host = c.Headers["Host"]
|
|
}
|
|
|
|
if c.Xmux.MaxConnections != nil && c.Xmux.MaxConnections.To > 0 && c.Xmux.MaxConcurrency != nil && c.Xmux.MaxConcurrency.To > 0 {
|
|
return nil, errors.New("maxConnections cannot be specified together with maxConcurrency")
|
|
}
|
|
|
|
// Multiplexing config
|
|
muxProtobuf := splithttp.Multiplexing{
|
|
MaxConcurrency: splithttpNewRandRangeConfig(c.Xmux.MaxConcurrency),
|
|
MaxConnections: splithttpNewRandRangeConfig(c.Xmux.MaxConnections),
|
|
CMaxReuseTimes: splithttpNewRandRangeConfig(c.Xmux.CMaxReuseTimes),
|
|
CMaxLifetimeMs: splithttpNewRandRangeConfig(c.Xmux.CMaxLifetimeMs),
|
|
}
|
|
|
|
config := &splithttp.Config{
|
|
Path: c.Path,
|
|
Host: c.Host,
|
|
Header: c.Headers,
|
|
ScMaxConcurrentPosts: splithttpNewRandRangeConfig(c.ScMaxConcurrentPosts),
|
|
ScMaxEachPostBytes: splithttpNewRandRangeConfig(c.ScMaxEachPostBytes),
|
|
ScMinPostsIntervalMs: splithttpNewRandRangeConfig(c.ScMinPostsIntervalMs),
|
|
NoSSEHeader: c.NoSSEHeader,
|
|
XPaddingBytes: splithttpNewRandRangeConfig(c.XPaddingBytes),
|
|
Xmux: &muxProtobuf,
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
type HTTPConfig struct {
|
|
Host *StringList `json:"host"`
|
|
Path string `json:"path"`
|
|
ReadIdleTimeout int32 `json:"read_idle_timeout"`
|
|
HealthCheckTimeout int32 `json:"health_check_timeout"`
|
|
Method string `json:"method"`
|
|
Headers map[string]*StringList `json:"headers"`
|
|
}
|
|
|
|
// Build implements Buildable.
|
|
func (c *HTTPConfig) Build() (proto.Message, error) {
|
|
if c.ReadIdleTimeout <= 0 {
|
|
c.ReadIdleTimeout = 0
|
|
}
|
|
if c.HealthCheckTimeout <= 0 {
|
|
c.HealthCheckTimeout = 0
|
|
}
|
|
config := &http.Config{
|
|
Path: c.Path,
|
|
IdleTimeout: c.ReadIdleTimeout,
|
|
HealthCheckTimeout: c.HealthCheckTimeout,
|
|
}
|
|
if c.Host != nil {
|
|
config.Host = []string(*c.Host)
|
|
}
|
|
if c.Method != "" {
|
|
config.Method = c.Method
|
|
}
|
|
if len(c.Headers) > 0 {
|
|
config.Header = make([]*httpheader.Header, 0, len(c.Headers))
|
|
headerNames := sortMapKeys(c.Headers)
|
|
for _, key := range headerNames {
|
|
value := c.Headers[key]
|
|
if value == nil {
|
|
return nil, errors.New("empty HTTP header value: " + key).AtError()
|
|
}
|
|
config.Header = append(config.Header, &httpheader.Header{
|
|
Name: key,
|
|
Value: append([]string(nil), (*value)...),
|
|
})
|
|
}
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
func readFileOrString(f string, s []string) ([]byte, error) {
|
|
if len(f) > 0 {
|
|
return filesystem.ReadFile(f)
|
|
}
|
|
if len(s) > 0 {
|
|
return []byte(strings.Join(s, "\n")), nil
|
|
}
|
|
return nil, errors.New("both file and bytes are empty.")
|
|
}
|
|
|
|
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 {
|
|
Insecure 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"`
|
|
PinnedPeerCertificateChainSha256 *[]string `json:"pinnedPeerCertificateChainSha256"`
|
|
PinnedPeerCertificatePublicKeySha256 *[]string `json:"pinnedPeerCertificatePublicKeySha256"`
|
|
MasterKeyLog string `json:"masterKeyLog"`
|
|
}
|
|
|
|
// 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
|
|
config.AllowInsecure = c.Insecure
|
|
if len(c.ServerName) > 0 {
|
|
config.ServerName = serverName
|
|
}
|
|
if c.ALPN != nil && len(*c.ALPN) > 0 {
|
|
config.NextProtocol = []string(*c.ALPN)
|
|
}
|
|
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 != "" && tls.GetFingerprint(config.Fingerprint) == nil {
|
|
return nil, errors.New(`unknown fingerprint: `, config.Fingerprint)
|
|
}
|
|
config.RejectUnknownSni = c.RejectUnknownSNI
|
|
|
|
if c.PinnedPeerCertificateChainSha256 != nil {
|
|
config.PinnedPeerCertificateChainSha256 = [][]byte{}
|
|
for _, v := range *c.PinnedPeerCertificateChainSha256 {
|
|
hashValue, err := base64.StdEncoding.DecodeString(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
config.PinnedPeerCertificateChainSha256 = append(config.PinnedPeerCertificateChainSha256, hashValue)
|
|
}
|
|
}
|
|
|
|
if c.PinnedPeerCertificatePublicKeySha256 != nil {
|
|
config.PinnedPeerCertificatePublicKeySha256 = [][]byte{}
|
|
for _, v := range *c.PinnedPeerCertificatePublicKeySha256 {
|
|
hashValue, err := base64.StdEncoding.DecodeString(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
config.PinnedPeerCertificatePublicKeySha256 = append(config.PinnedPeerCertificatePublicKeySha256, hashValue)
|
|
}
|
|
}
|
|
|
|
config.MasterKeyLog = c.MasterKeyLog
|
|
|
|
return config, nil
|
|
}
|
|
|
|
type REALITYConfig struct {
|
|
Show bool `json:"show"`
|
|
MasterKeyLog string `json:"masterKeyLog"`
|
|
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"`
|
|
|
|
Fingerprint string `json:"fingerprint"`
|
|
ServerName string `json:"serverName"`
|
|
PublicKey string `json:"publicKey"`
|
|
ShortId string `json:"shortId"`
|
|
SpiderX string `json:"spiderX"`
|
|
}
|
|
|
|
func (c *REALITYConfig) Build() (proto.Message, error) {
|
|
config := new(reality.Config)
|
|
config.Show = c.Show
|
|
config.MasterKeyLog = c.MasterKeyLog
|
|
var err error
|
|
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 = "127.0.0.1:" + 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 "dest"`)
|
|
}
|
|
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 lesser 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 lesser 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 {
|
|
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
|
|
} else {
|
|
if c.Fingerprint == "" {
|
|
return nil, errors.New(`empty "fingerprint"`)
|
|
}
|
|
if config.Fingerprint = strings.ToLower(c.Fingerprint); tls.GetFingerprint(config.Fingerprint) == nil {
|
|
return nil, errors.New(`unknown "fingerprint": `, config.Fingerprint)
|
|
}
|
|
if config.Fingerprint == "hellogolang" {
|
|
return nil, errors.New(`invalid "fingerprint": `, config.Fingerprint)
|
|
}
|
|
if len(c.ServerNames) != 0 {
|
|
return nil, errors.New(`non-empty "serverNames", please use "serverName" instead`)
|
|
}
|
|
if c.PublicKey == "" {
|
|
return nil, errors.New(`empty "publicKey"`)
|
|
}
|
|
if config.PublicKey, err = base64.RawURLEncoding.DecodeString(c.PublicKey); err != nil || len(config.PublicKey) != 32 {
|
|
return nil, errors.New(`invalid "publicKey": `, c.PublicKey)
|
|
}
|
|
if len(c.ShortIds) != 0 {
|
|
return nil, errors.New(`non-empty "shortIds", please use "shortId" instead`)
|
|
}
|
|
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.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 TransportProtocol string
|
|
|
|
// Build implements Buildable.
|
|
func (p TransportProtocol) Build() (string, error) {
|
|
switch strings.ToLower(string(p)) {
|
|
case "tcp":
|
|
return "tcp", nil
|
|
case "kcp", "mkcp":
|
|
return "mkcp", nil
|
|
case "ws", "websocket":
|
|
return "websocket", nil
|
|
case "h2", "http":
|
|
return "http", nil
|
|
case "grpc", "gun":
|
|
return "grpc", nil
|
|
case "httpupgrade":
|
|
return "httpupgrade", nil
|
|
case "splithttp":
|
|
return "splithttp", nil
|
|
default:
|
|
return "", errors.New("Config: unknown transport protocol: ", p)
|
|
}
|
|
}
|
|
|
|
type CustomSockoptConfig struct {
|
|
Level string `json:"level"`
|
|
Opt string `json:"opt"`
|
|
Value string `json:"value"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
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"`
|
|
TcpNoDelay bool `json:"tcpNoDelay"`
|
|
TCPUserTimeout int32 `json:"tcpUserTimeout"`
|
|
V6only bool `json:"v6only"`
|
|
Interface string `json:"interface"`
|
|
TcpMptcp bool `json:"tcpMptcp"`
|
|
CustomSockopt []*CustomSockoptConfig `json:"customSockopt"`
|
|
}
|
|
|
|
// 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{
|
|
Level: copt.Level,
|
|
Opt: copt.Opt,
|
|
Value: copt.Value,
|
|
Type: copt.Type,
|
|
}
|
|
customSockopts = append(customSockopts, customSockopt)
|
|
}
|
|
|
|
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,
|
|
TcpNoDelay: c.TcpNoDelay,
|
|
TcpUserTimeout: c.TCPUserTimeout,
|
|
V6Only: c.V6only,
|
|
Interface: c.Interface,
|
|
TcpMptcp: c.TcpMptcp,
|
|
CustomSockopt: customSockopts,
|
|
}, nil
|
|
}
|
|
|
|
type StreamConfig struct {
|
|
Network *TransportProtocol `json:"network"`
|
|
Security string `json:"security"`
|
|
TLSSettings *TLSConfig `json:"tlsSettings"`
|
|
REALITYSettings *REALITYConfig `json:"realitySettings"`
|
|
TCPSettings *TCPConfig `json:"tcpSettings"`
|
|
KCPSettings *KCPConfig `json:"kcpSettings"`
|
|
WSSettings *WebSocketConfig `json:"wsSettings"`
|
|
HTTPSettings *HTTPConfig `json:"httpSettings"`
|
|
SocketSettings *SocketConfig `json:"sockopt"`
|
|
GRPCConfig *GRPCConfig `json:"grpcSettings"`
|
|
GUNConfig *GRPCConfig `json:"gunSettings"`
|
|
HTTPUPGRADESettings *HttpUpgradeConfig `json:"httpupgradeSettings"`
|
|
SplitHTTPSettings *SplitHTTPConfig `json:"splithttpSettings"`
|
|
}
|
|
|
|
// Build implements Buildable.
|
|
func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
|
|
config := &internet.StreamConfig{
|
|
ProtocolName: "tcp",
|
|
}
|
|
if c.Network != nil {
|
|
protocol, err := c.Network.Build()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
config.ProtocolName = protocol
|
|
}
|
|
switch strings.ToLower(c.Security) {
|
|
case "", "none":
|
|
case "tls":
|
|
tlsSettings := c.TLSSettings
|
|
if tlsSettings == nil {
|
|
tlsSettings = &TLSConfig{}
|
|
}
|
|
ts, err := tlsSettings.Build()
|
|
if err != nil {
|
|
return nil, errors.New("Failed to build TLS config.").Base(err)
|
|
}
|
|
tm := serial.ToTypedMessage(ts)
|
|
config.SecuritySettings = append(config.SecuritySettings, tm)
|
|
config.SecurityType = tm.Type
|
|
case "reality":
|
|
if config.ProtocolName != "tcp" && config.ProtocolName != "http" && config.ProtocolName != "grpc" {
|
|
return nil, errors.New("REALITY only supports TCP, H2 and gRPC for now.")
|
|
}
|
|
if c.REALITYSettings == nil {
|
|
return nil, errors.New(`REALITY: Empty "realitySettings".`)
|
|
}
|
|
ts, err := c.REALITYSettings.Build()
|
|
if err != nil {
|
|
return nil, errors.New("Failed to build REALITY config.").Base(err)
|
|
}
|
|
tm := serial.ToTypedMessage(ts)
|
|
config.SecuritySettings = append(config.SecuritySettings, tm)
|
|
config.SecurityType = tm.Type
|
|
case "xtls":
|
|
return nil, errors.PrintRemovedFeatureError(`Legacy XTLS`, `xtls-rprx-vision with TLS or REALITY`)
|
|
default:
|
|
return nil, errors.New(`Unknown security "` + c.Security + `".`)
|
|
}
|
|
if c.TCPSettings != nil {
|
|
ts, err := c.TCPSettings.Build()
|
|
if err != nil {
|
|
return nil, errors.New("Failed to build TCP config.").Base(err)
|
|
}
|
|
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
|
ProtocolName: "tcp",
|
|
Settings: serial.ToTypedMessage(ts),
|
|
})
|
|
}
|
|
if c.KCPSettings != nil {
|
|
ts, err := c.KCPSettings.Build()
|
|
if err != nil {
|
|
return nil, errors.New("Failed to build mKCP config.").Base(err)
|
|
}
|
|
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
|
ProtocolName: "mkcp",
|
|
Settings: serial.ToTypedMessage(ts),
|
|
})
|
|
}
|
|
if c.WSSettings != nil {
|
|
ts, err := c.WSSettings.Build()
|
|
if err != nil {
|
|
return nil, errors.New("Failed to build WebSocket config.").Base(err)
|
|
}
|
|
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
|
ProtocolName: "websocket",
|
|
Settings: serial.ToTypedMessage(ts),
|
|
})
|
|
}
|
|
if c.HTTPSettings != nil {
|
|
ts, err := c.HTTPSettings.Build()
|
|
if err != nil {
|
|
return nil, errors.New("Failed to build HTTP config.").Base(err)
|
|
}
|
|
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
|
ProtocolName: "http",
|
|
Settings: serial.ToTypedMessage(ts),
|
|
})
|
|
}
|
|
if c.GRPCConfig == nil {
|
|
c.GRPCConfig = c.GUNConfig
|
|
}
|
|
if c.GRPCConfig != nil {
|
|
gs, err := c.GRPCConfig.Build()
|
|
if err != nil {
|
|
return nil, errors.New("Failed to build gRPC config.").Base(err)
|
|
}
|
|
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
|
ProtocolName: "grpc",
|
|
Settings: serial.ToTypedMessage(gs),
|
|
})
|
|
}
|
|
if c.HTTPUPGRADESettings != nil {
|
|
hs, err := c.HTTPUPGRADESettings.Build()
|
|
if err != nil {
|
|
return nil, errors.New("Failed to build HttpUpgrade config.").Base(err)
|
|
}
|
|
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
|
ProtocolName: "httpupgrade",
|
|
Settings: serial.ToTypedMessage(hs),
|
|
})
|
|
}
|
|
if c.SplitHTTPSettings != nil {
|
|
hs, err := c.SplitHTTPSettings.Build()
|
|
if err != nil {
|
|
return nil, errors.New("Failed to build SplitHTTP config.").Base(err)
|
|
}
|
|
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
|
ProtocolName: "splithttp",
|
|
Settings: serial.ToTypedMessage(hs),
|
|
})
|
|
}
|
|
if c.SocketSettings != nil {
|
|
ss, err := c.SocketSettings.Build()
|
|
if err != nil {
|
|
return nil, errors.New("Failed to build sockopt").Base(err)
|
|
}
|
|
config.SocketSettings = ss
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
type ProxyConfig struct {
|
|
Tag string `json:"tag"`
|
|
|
|
// TransportLayerProxy: For compatibility.
|
|
TransportLayerProxy bool `json:"transportLayer"`
|
|
}
|
|
|
|
// Build implements Buildable.
|
|
func (v *ProxyConfig) Build() (*internet.ProxyConfig, error) {
|
|
if v.Tag == "" {
|
|
return nil, errors.New("Proxy tag is not set.")
|
|
}
|
|
return &internet.ProxyConfig{
|
|
Tag: v.Tag,
|
|
TransportLayerProxy: v.TransportLayerProxy,
|
|
}, nil
|
|
}
|