mirror of
https://github.com/XTLS/Xray-core.git
synced 2024-11-26 23:26:03 +03:00
55dc26f228
Now you are able to configure REALITY gRPC client and server Duplicate of REALITY H2, perhaps, just for fun
966 lines
30 KiB
Go
966 lines
30 KiB
Go
package conf
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"math"
|
|
"net/url"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/xtls/xray-core/common/net"
|
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
|
"github.com/xtls/xray-core/common/protocol"
|
|
"github.com/xtls/xray-core/common/serial"
|
|
"github.com/xtls/xray-core/transport/internet"
|
|
"github.com/xtls/xray-core/transport/internet/domainsocket"
|
|
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/kcp"
|
|
"github.com/xtls/xray-core/transport/internet/quic"
|
|
"github.com/xtls/xray-core/transport/internet/reality"
|
|
"github.com/xtls/xray-core/transport/internet/tcp"
|
|
"github.com/xtls/xray-core/transport/internet/tls"
|
|
"github.com/xtls/xray-core/transport/internet/websocket"
|
|
"github.com/xtls/xray-core/transport/internet/xtls"
|
|
)
|
|
|
|
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, newError("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, newError("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, newError("invalid mKCP header config.").Base(err).AtError()
|
|
}
|
|
ts, err := headerConfig.(Buildable).Build()
|
|
if err != nil {
|
|
return nil, newError("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, newError("invalid TCP header config").Base(err).AtError()
|
|
}
|
|
ts, err := headerConfig.(Buildable).Build()
|
|
if err != nil {
|
|
return nil, newError("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 {
|
|
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
|
|
header := make([]*websocket.Header, 0, 32)
|
|
for key, value := range c.Headers {
|
|
header = append(header, &websocket.Header{
|
|
Key: key,
|
|
Value: value,
|
|
})
|
|
}
|
|
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()
|
|
}
|
|
}
|
|
config := &websocket.Config{
|
|
Path: path,
|
|
Header: header,
|
|
Ed: ed,
|
|
}
|
|
if c.AcceptProxyProtocol {
|
|
config.AcceptProxyProtocol = c.AcceptProxyProtocol
|
|
}
|
|
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, newError("empty HTTP header value: " + key).AtError()
|
|
}
|
|
config.Header = append(config.Header, &httpheader.Header{
|
|
Name: key,
|
|
Value: append([]string(nil), (*value)...),
|
|
})
|
|
}
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
type QUICConfig struct {
|
|
Header json.RawMessage `json:"header"`
|
|
Security string `json:"security"`
|
|
Key string `json:"key"`
|
|
}
|
|
|
|
// Build implements Buildable.
|
|
func (c *QUICConfig) Build() (proto.Message, error) {
|
|
config := &quic.Config{
|
|
Key: c.Key,
|
|
}
|
|
|
|
if len(c.Header) > 0 {
|
|
headerConfig, _, err := kcpHeaderLoader.Load(c.Header)
|
|
if err != nil {
|
|
return nil, newError("invalid QUIC header config.").Base(err).AtError()
|
|
}
|
|
ts, err := headerConfig.(Buildable).Build()
|
|
if err != nil {
|
|
return nil, newError("invalid QUIC header config").Base(err).AtError()
|
|
}
|
|
config.Header = serial.ToTypedMessage(ts)
|
|
}
|
|
|
|
var st protocol.SecurityType
|
|
switch strings.ToLower(c.Security) {
|
|
case "aes-128-gcm":
|
|
st = protocol.SecurityType_AES128_GCM
|
|
case "chacha20-poly1305":
|
|
st = protocol.SecurityType_CHACHA20_POLY1305
|
|
default:
|
|
st = protocol.SecurityType_NONE
|
|
}
|
|
|
|
config.Security = &protocol.SecurityConfig{
|
|
Type: st,
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
type DomainSocketConfig struct {
|
|
Path string `json:"path"`
|
|
Abstract bool `json:"abstract"`
|
|
Padding bool `json:"padding"`
|
|
}
|
|
|
|
// Build implements Buildable.
|
|
func (c *DomainSocketConfig) Build() (proto.Message, error) {
|
|
return &domainsocket.Config{
|
|
Path: c.Path,
|
|
Abstract: c.Abstract,
|
|
Padding: c.Padding,
|
|
}, 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, newError("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"`
|
|
}
|
|
|
|
// 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, newError("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, newError("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
|
|
|
|
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"`
|
|
PreferServerCipherSuites bool `json:"preferServerCipherSuites"`
|
|
Fingerprint string `json:"fingerprint"`
|
|
RejectUnknownSNI bool `json:"rejectUnknownSni"`
|
|
PinnedPeerCertificateChainSha256 *[]string `json:"pinnedPeerCertificateChainSha256"`
|
|
PinnedPeerCertificatePublicKeySha256 *[]string `json:"pinnedPeerCertificatePublicKeySha256"`
|
|
}
|
|
|
|
// 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.PreferServerCipherSuites = c.PreferServerCipherSuites
|
|
config.Fingerprint = strings.ToLower(c.Fingerprint)
|
|
if config.Fingerprint != "" && tls.GetFingerprint(config.Fingerprint) == nil {
|
|
return nil, newError(`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)
|
|
}
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
type XTLSCertConfig 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"`
|
|
}
|
|
|
|
// Build implements Buildable.
|
|
func (c *XTLSCertConfig) Build() (*xtls.Certificate, error) {
|
|
certificate := new(xtls.Certificate)
|
|
cert, err := readFileOrString(c.CertFile, c.CertStr)
|
|
if err != nil {
|
|
return nil, newError("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, newError("failed to parse key").Base(err)
|
|
}
|
|
certificate.Key = key
|
|
certificate.KeyPath = c.KeyFile
|
|
}
|
|
|
|
switch strings.ToLower(c.Usage) {
|
|
case "encipherment":
|
|
certificate.Usage = xtls.Certificate_ENCIPHERMENT
|
|
case "verify":
|
|
certificate.Usage = xtls.Certificate_AUTHORITY_VERIFY
|
|
case "issue":
|
|
certificate.Usage = xtls.Certificate_AUTHORITY_ISSUE
|
|
default:
|
|
certificate.Usage = xtls.Certificate_ENCIPHERMENT
|
|
}
|
|
if certificate.KeyPath == "" && certificate.CertificatePath == "" {
|
|
certificate.OneTimeLoading = true
|
|
} else {
|
|
certificate.OneTimeLoading = c.OneTimeLoading
|
|
}
|
|
certificate.OcspStapling = c.OcspStapling
|
|
|
|
return certificate, nil
|
|
}
|
|
|
|
type XTLSConfig struct {
|
|
Insecure bool `json:"allowInsecure"`
|
|
Certs []*XTLSCertConfig `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"`
|
|
PreferServerCipherSuites bool `json:"preferServerCipherSuites"`
|
|
Fingerprint string `json:"fingerprint"`
|
|
RejectUnknownSNI bool `json:"rejectUnknownSni"`
|
|
PinnedPeerCertificateChainSha256 *[]string `json:"pinnedPeerCertificateChainSha256"`
|
|
}
|
|
|
|
// Build implements Buildable.
|
|
func (c *XTLSConfig) Build() (proto.Message, error) {
|
|
config := new(xtls.Config)
|
|
config.Certificate = make([]*xtls.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.PreferServerCipherSuites = c.PreferServerCipherSuites
|
|
if c.Fingerprint != "" {
|
|
return nil, newError(`Old version of XTLS does not support fingerprint. Please use flow "xtls-rprx-vision" with "tls & tlsSettings" instead.`)
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
|
|
newError(`You are using an old version of XTLS, which is deprecated now and will be removed soon. Please use flow "xtls-rprx-vision" with "tls & tlsSettings" instead.`).AtWarning().WriteToLog()
|
|
|
|
return config, nil
|
|
}
|
|
|
|
type REALITYConfig struct {
|
|
Show bool `json:"show"`
|
|
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
|
|
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, newError(`please fill in a valid value for "dest"`)
|
|
}
|
|
if c.Xver > 2 {
|
|
return nil, newError(`invalid PROXY protocol version, "xver" only accepts 0, 1, 2`)
|
|
}
|
|
if len(c.ServerNames) == 0 {
|
|
return nil, newError(`empty "serverNames"`)
|
|
}
|
|
if c.PrivateKey == "" {
|
|
return nil, newError(`empty "privateKey"`)
|
|
}
|
|
if config.PrivateKey, err = base64.RawURLEncoding.DecodeString(c.PrivateKey); err != nil || len(config.PrivateKey) != 32 {
|
|
return nil, newError(`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, newError(`invalid "minClientVer": `, c.MinClientVer)
|
|
}
|
|
if u, err = strconv.ParseUint(s, 10, 8); err != nil {
|
|
return nil, newError(`"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, newError(`invalid "maxClientVer": `, c.MaxClientVer)
|
|
}
|
|
if u, err = strconv.ParseUint(s, 10, 8); err != nil {
|
|
return nil, newError(`"maxClientVer[`, i, `]" should be lesser than 256`)
|
|
} else {
|
|
config.MaxClientVer[i] = byte(u)
|
|
}
|
|
}
|
|
}
|
|
if len(c.ShortIds) == 0 {
|
|
return nil, newError(`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, newError(`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, newError(`empty "fingerprint"`)
|
|
}
|
|
if config.Fingerprint = strings.ToLower(c.Fingerprint); tls.GetFingerprint(config.Fingerprint) == nil {
|
|
return nil, newError(`unknown "fingerprint": `, config.Fingerprint)
|
|
}
|
|
if config.Fingerprint == "hellogolang" {
|
|
return nil, newError(`invalid "fingerprint": `, config.Fingerprint)
|
|
}
|
|
if len(c.ServerNames) != 0 {
|
|
return nil, newError(`non-empty "serverNames", please use "serverName" instead`)
|
|
}
|
|
if c.PublicKey == "" {
|
|
return nil, newError(`empty "publicKey"`)
|
|
}
|
|
if config.PublicKey, err = base64.RawURLEncoding.DecodeString(c.PublicKey); err != nil || len(config.PublicKey) != 32 {
|
|
return nil, newError(`invalid "publicKey": `, c.PublicKey)
|
|
}
|
|
if len(c.ShortIds) != 0 {
|
|
return nil, newError(`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, newError(`invalid "shortId": `, c.ShortId)
|
|
}
|
|
if c.SpiderX == "" {
|
|
c.SpiderX = "/"
|
|
}
|
|
if c.SpiderX[0] != '/' {
|
|
return nil, newError(`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 "ds", "domainsocket":
|
|
return "domainsocket", nil
|
|
case "quic":
|
|
return "quic", nil
|
|
case "grpc", "gun":
|
|
return "grpc", nil
|
|
default:
|
|
return "", newError("Config: unknown transport protocol: ", p)
|
|
}
|
|
}
|
|
|
|
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"`
|
|
V6only bool `json:"v6only"`
|
|
Interface string `json:"interface"`
|
|
}
|
|
|
|
// 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, newError("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 "useip", "use_ip":
|
|
dStrategy = internet.DomainStrategy_USE_IP
|
|
case "useip4", "useipv4", "use_ipv4", "use_ip_v4", "use_ip4":
|
|
dStrategy = internet.DomainStrategy_USE_IP4
|
|
case "useip6", "useipv6", "use_ipv6", "use_ip_v6", "use_ip6":
|
|
dStrategy = internet.DomainStrategy_USE_IP6
|
|
}
|
|
|
|
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,
|
|
V6Only: c.V6only,
|
|
Interface: c.Interface,
|
|
}, nil
|
|
}
|
|
|
|
type StreamConfig struct {
|
|
Network *TransportProtocol `json:"network"`
|
|
Security string `json:"security"`
|
|
TLSSettings *TLSConfig `json:"tlsSettings"`
|
|
XTLSSettings *XTLSConfig `json:"xtlsSettings"`
|
|
REALITYSettings *REALITYConfig `json:"realitySettings"`
|
|
TCPSettings *TCPConfig `json:"tcpSettings"`
|
|
KCPSettings *KCPConfig `json:"kcpSettings"`
|
|
WSSettings *WebSocketConfig `json:"wsSettings"`
|
|
HTTPSettings *HTTPConfig `json:"httpSettings"`
|
|
DSSettings *DomainSocketConfig `json:"dsSettings"`
|
|
QUICSettings *QUICConfig `json:"quicSettings"`
|
|
SocketSettings *SocketConfig `json:"sockopt"`
|
|
GRPCConfig *GRPCConfig `json:"grpcSettings"`
|
|
GUNConfig *GRPCConfig `json:"gunSettings"`
|
|
}
|
|
|
|
// 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
|
|
}
|
|
if strings.EqualFold(c.Security, "tls") {
|
|
tlsSettings := c.TLSSettings
|
|
if tlsSettings == nil {
|
|
if c.XTLSSettings != nil {
|
|
return nil, newError(`TLS: Please use "tlsSettings" instead of "xtlsSettings".`)
|
|
}
|
|
tlsSettings = &TLSConfig{}
|
|
}
|
|
ts, err := tlsSettings.Build()
|
|
if err != nil {
|
|
return nil, newError("Failed to build TLS config.").Base(err)
|
|
}
|
|
tm := serial.ToTypedMessage(ts)
|
|
config.SecuritySettings = append(config.SecuritySettings, tm)
|
|
config.SecurityType = tm.Type
|
|
}
|
|
if strings.EqualFold(c.Security, "xtls") {
|
|
if config.ProtocolName != "tcp" && config.ProtocolName != "mkcp" && config.ProtocolName != "domainsocket" {
|
|
return nil, newError("XTLS only supports TCP, mKCP and DomainSocket for now.")
|
|
}
|
|
xtlsSettings := c.XTLSSettings
|
|
if xtlsSettings == nil {
|
|
if c.TLSSettings != nil {
|
|
return nil, newError(`XTLS: Please use "xtlsSettings" instead of "tlsSettings".`)
|
|
}
|
|
xtlsSettings = &XTLSConfig{}
|
|
}
|
|
ts, err := xtlsSettings.Build()
|
|
if err != nil {
|
|
return nil, newError("Failed to build XTLS config.").Base(err)
|
|
}
|
|
tm := serial.ToTypedMessage(ts)
|
|
config.SecuritySettings = append(config.SecuritySettings, tm)
|
|
config.SecurityType = tm.Type
|
|
}
|
|
if strings.EqualFold(c.Security, "reality") {
|
|
if config.ProtocolName != "tcp" && config.ProtocolName != "http" && config.ProtocolName != "grpc" && config.ProtocolName != "domainsocket" {
|
|
return nil, newError("REALITY only supports TCP, H2, gRPC and DomainSocket for now.")
|
|
}
|
|
if c.REALITYSettings == nil {
|
|
return nil, newError(`REALITY: Empty "realitySettings".`)
|
|
}
|
|
ts, err := c.REALITYSettings.Build()
|
|
if err != nil {
|
|
return nil, newError("Failed to build REALITY config.").Base(err)
|
|
}
|
|
tm := serial.ToTypedMessage(ts)
|
|
config.SecuritySettings = append(config.SecuritySettings, tm)
|
|
config.SecurityType = tm.Type
|
|
}
|
|
if c.TCPSettings != nil {
|
|
ts, err := c.TCPSettings.Build()
|
|
if err != nil {
|
|
return nil, newError("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, newError("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, newError("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, newError("Failed to build HTTP config.").Base(err)
|
|
}
|
|
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
|
ProtocolName: "http",
|
|
Settings: serial.ToTypedMessage(ts),
|
|
})
|
|
}
|
|
if c.DSSettings != nil {
|
|
ds, err := c.DSSettings.Build()
|
|
if err != nil {
|
|
return nil, newError("Failed to build DomainSocket config.").Base(err)
|
|
}
|
|
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
|
ProtocolName: "domainsocket",
|
|
Settings: serial.ToTypedMessage(ds),
|
|
})
|
|
}
|
|
if c.QUICSettings != nil {
|
|
qs, err := c.QUICSettings.Build()
|
|
if err != nil {
|
|
return nil, newError("Failed to build QUIC config").Base(err)
|
|
}
|
|
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
|
ProtocolName: "quic",
|
|
Settings: serial.ToTypedMessage(qs),
|
|
})
|
|
}
|
|
if c.GRPCConfig == nil {
|
|
c.GRPCConfig = c.GUNConfig
|
|
}
|
|
if c.GRPCConfig != nil {
|
|
gs, err := c.GRPCConfig.Build()
|
|
if err != nil {
|
|
return nil, newError("Failed to build gRPC config.").Base(err)
|
|
}
|
|
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
|
|
ProtocolName: "grpc",
|
|
Settings: serial.ToTypedMessage(gs),
|
|
})
|
|
}
|
|
if c.SocketSettings != nil {
|
|
ss, err := c.SocketSettings.Build()
|
|
if err != nil {
|
|
return nil, newError("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, newError("Proxy tag is not set.")
|
|
}
|
|
return &internet.ProxyConfig{
|
|
Tag: v.Tag,
|
|
TransportLayerProxy: v.TransportLayerProxy,
|
|
}, nil
|
|
}
|