package v4 import ( "encoding/json" "strings" "google.golang.org/protobuf/types/known/anypb" core "github.com/v2fly/v2ray-core/v5" "github.com/v2fly/v2ray-core/v5/app/dispatcher" "github.com/v2fly/v2ray-core/v5/app/proxyman" "github.com/v2fly/v2ray-core/v5/app/stats" "github.com/v2fly/v2ray-core/v5/common/serial" "github.com/v2fly/v2ray-core/v5/infra/conf/cfgcommon" "github.com/v2fly/v2ray-core/v5/infra/conf/cfgcommon/loader" "github.com/v2fly/v2ray-core/v5/infra/conf/cfgcommon/muxcfg" "github.com/v2fly/v2ray-core/v5/infra/conf/cfgcommon/proxycfg" "github.com/v2fly/v2ray-core/v5/infra/conf/cfgcommon/sniffer" "github.com/v2fly/v2ray-core/v5/infra/conf/synthetic/dns" "github.com/v2fly/v2ray-core/v5/infra/conf/synthetic/log" "github.com/v2fly/v2ray-core/v5/infra/conf/synthetic/router" ) var ( inboundConfigLoader = loader.NewJSONConfigLoader(loader.ConfigCreatorCache{ "dokodemo-door": func() interface{} { return new(DokodemoConfig) }, "http": func() interface{} { return new(HTTPServerConfig) }, "shadowsocks": func() interface{} { return new(ShadowsocksServerConfig) }, "socks": func() interface{} { return new(SocksServerConfig) }, "vless": func() interface{} { return new(VLessInboundConfig) }, "vmess": func() interface{} { return new(VMessInboundConfig) }, "trojan": func() interface{} { return new(TrojanServerConfig) }, }, "protocol", "settings") outboundConfigLoader = loader.NewJSONConfigLoader(loader.ConfigCreatorCache{ "blackhole": func() interface{} { return new(BlackholeConfig) }, "freedom": func() interface{} { return new(FreedomConfig) }, "http": func() interface{} { return new(HTTPClientConfig) }, "shadowsocks": func() interface{} { return new(ShadowsocksClientConfig) }, "socks": func() interface{} { return new(SocksClientConfig) }, "vless": func() interface{} { return new(VLessOutboundConfig) }, "vmess": func() interface{} { return new(VMessOutboundConfig) }, "trojan": func() interface{} { return new(TrojanClientConfig) }, "dns": func() interface{} { return new(DNSOutboundConfig) }, "loopback": func() interface{} { return new(LoopbackConfig) }, }, "protocol", "settings") ) func toProtocolList(s []string) ([]proxyman.KnownProtocols, error) { kp := make([]proxyman.KnownProtocols, 0, 8) for _, p := range s { switch strings.ToLower(p) { case "http": kp = append(kp, proxyman.KnownProtocols_HTTP) case "https", "tls", "ssl": kp = append(kp, proxyman.KnownProtocols_TLS) default: return nil, newError("Unknown protocol: ", p) } } return kp, nil } type InboundDetourAllocationConfig struct { Strategy string `json:"strategy"` Concurrency *uint32 `json:"concurrency"` RefreshMin *uint32 `json:"refresh"` } // Build implements Buildable. func (c *InboundDetourAllocationConfig) Build() (*proxyman.AllocationStrategy, error) { config := new(proxyman.AllocationStrategy) switch strings.ToLower(c.Strategy) { case "always": config.Type = proxyman.AllocationStrategy_Always case "random": config.Type = proxyman.AllocationStrategy_Random case "external": config.Type = proxyman.AllocationStrategy_External default: return nil, newError("unknown allocation strategy: ", c.Strategy) } if c.Concurrency != nil { config.Concurrency = &proxyman.AllocationStrategy_AllocationStrategyConcurrency{ Value: *c.Concurrency, } } if c.RefreshMin != nil { config.Refresh = &proxyman.AllocationStrategy_AllocationStrategyRefresh{ Value: *c.RefreshMin, } } return config, nil } type InboundDetourConfig struct { Protocol string `json:"protocol"` PortRange *cfgcommon.PortRange `json:"port"` ListenOn *cfgcommon.Address `json:"listen"` Settings *json.RawMessage `json:"settings"` Tag string `json:"tag"` Allocation *InboundDetourAllocationConfig `json:"allocate"` StreamSetting *StreamConfig `json:"streamSettings"` DomainOverride *cfgcommon.StringList `json:"domainOverride"` SniffingConfig *sniffer.SniffingConfig `json:"sniffing"` } // Build implements Buildable. func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) { receiverSettings := &proxyman.ReceiverConfig{} if c.ListenOn == nil { // Listen on anyip, must set PortRange if c.PortRange == nil { return nil, newError("Listen on AnyIP but no Port(s) set in InboundDetour.") } receiverSettings.PortRange = c.PortRange.Build() } else { // Listen on specific IP or Unix Domain Socket receiverSettings.Listen = c.ListenOn.Build() listenDS := c.ListenOn.Family().IsDomain() && (c.ListenOn.Domain()[0] == '/' || c.ListenOn.Domain()[0] == '@') listenIP := c.ListenOn.Family().IsIP() || (c.ListenOn.Family().IsDomain() && c.ListenOn.Domain() == "localhost") switch { case listenIP: // Listen on specific IP, must set PortRange if c.PortRange == nil { return nil, newError("Listen on specific ip without port in InboundDetour.") } // Listen on IP:Port receiverSettings.PortRange = c.PortRange.Build() case listenDS: if c.PortRange != nil { // Listen on Unix Domain Socket, PortRange should be nil receiverSettings.PortRange = nil } default: return nil, newError("unable to listen on domain address: ", c.ListenOn.Domain()) } } if c.Allocation != nil { concurrency := -1 if c.Allocation.Concurrency != nil && c.Allocation.Strategy == "random" { concurrency = int(*c.Allocation.Concurrency) } portRange := int(c.PortRange.To - c.PortRange.From + 1) if concurrency >= 0 && concurrency >= portRange { return nil, newError("not enough ports. concurrency = ", concurrency, " ports: ", c.PortRange.From, " - ", c.PortRange.To) } as, err := c.Allocation.Build() if err != nil { return nil, err } receiverSettings.AllocationStrategy = as } if c.StreamSetting != nil { ss, err := c.StreamSetting.Build() if err != nil { return nil, err } receiverSettings.StreamSettings = ss } if c.SniffingConfig != nil { s, err := c.SniffingConfig.Build() if err != nil { return nil, newError("failed to build sniffing config").Base(err) } receiverSettings.SniffingSettings = s } if c.DomainOverride != nil { kp, err := toProtocolList(*c.DomainOverride) if err != nil { return nil, newError("failed to parse inbound detour config").Base(err) } receiverSettings.DomainOverride = kp } settings := []byte("{}") if c.Settings != nil { settings = ([]byte)(*c.Settings) } rawConfig, err := inboundConfigLoader.LoadWithID(settings, c.Protocol) if err != nil { return nil, newError("failed to load inbound detour config.").Base(err) } if dokodemoConfig, ok := rawConfig.(*DokodemoConfig); ok { receiverSettings.ReceiveOriginalDestination = dokodemoConfig.Redirect } ts, err := rawConfig.(cfgcommon.Buildable).Build() if err != nil { return nil, err } return &core.InboundHandlerConfig{ Tag: c.Tag, ReceiverSettings: serial.ToTypedMessage(receiverSettings), ProxySettings: serial.ToTypedMessage(ts), }, nil } type OutboundDetourConfig struct { Protocol string `json:"protocol"` SendThrough *cfgcommon.Address `json:"sendThrough"` Tag string `json:"tag"` Settings *json.RawMessage `json:"settings"` StreamSetting *StreamConfig `json:"streamSettings"` ProxySettings *proxycfg.ProxyConfig `json:"proxySettings"` MuxSettings *muxcfg.MuxConfig `json:"mux"` } // Build implements Buildable. func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { senderSettings := &proxyman.SenderConfig{} if c.SendThrough != nil { address := c.SendThrough if address.Family().IsDomain() { return nil, newError("unable to send through: " + address.String()) } senderSettings.Via = address.Build() } if c.StreamSetting != nil { ss, err := c.StreamSetting.Build() if err != nil { return nil, err } senderSettings.StreamSettings = ss } if c.ProxySettings != nil { ps, err := c.ProxySettings.Build() if err != nil { return nil, newError("invalid outbound detour proxy settings.").Base(err) } senderSettings.ProxySettings = ps } if c.MuxSettings != nil { senderSettings.MultiplexSettings = c.MuxSettings.Build() } settings := []byte("{}") if c.Settings != nil { settings = ([]byte)(*c.Settings) } rawConfig, err := outboundConfigLoader.LoadWithID(settings, c.Protocol) if err != nil { return nil, newError("failed to parse to outbound detour config.").Base(err) } ts, err := rawConfig.(cfgcommon.Buildable).Build() if err != nil { return nil, err } return &core.OutboundHandlerConfig{ SenderSettings: serial.ToTypedMessage(senderSettings), Tag: c.Tag, ProxySettings: serial.ToTypedMessage(ts), }, nil } type StatsConfig struct{} // Build implements Buildable. func (c *StatsConfig) Build() (*stats.Config, error) { return &stats.Config{}, nil } type Config struct { // Port of this Point server. // Deprecated: Port exists for historical compatibility // and should not be used. Port uint16 `json:"port"` // Deprecated: InboundConfig exists for historical compatibility // and should not be used. InboundConfig *InboundDetourConfig `json:"inbound"` // Deprecated: OutboundConfig exists for historical compatibility // and should not be used. OutboundConfig *OutboundDetourConfig `json:"outbound"` // Deprecated: InboundDetours exists for historical compatibility // and should not be used. InboundDetours []InboundDetourConfig `json:"inboundDetour"` // Deprecated: OutboundDetours exists for historical compatibility // and should not be used. OutboundDetours []OutboundDetourConfig `json:"outboundDetour"` LogConfig *log.LogConfig `json:"log"` RouterConfig *router.RouterConfig `json:"routing"` DNSConfig *dns.DNSConfig `json:"dns"` InboundConfigs []InboundDetourConfig `json:"inbounds"` OutboundConfigs []OutboundDetourConfig `json:"outbounds"` Transport *TransportConfig `json:"transport"` Policy *PolicyConfig `json:"policy"` API *APIConfig `json:"api"` Stats *StatsConfig `json:"stats"` Reverse *ReverseConfig `json:"reverse"` FakeDNS *FakeDNSConfig `json:"fakeDns"` BrowserForwarder *BrowserForwarderConfig `json:"browserForwarder"` Observatory *ObservatoryConfig `json:"observatory"` BurstObservatory *BurstObservatoryConfig `json:"burstObservatory"` MultiObservatory *MultiObservatoryConfig `json:"multiObservatory"` Services map[string]*json.RawMessage `json:"services"` } func (c *Config) findInboundTag(tag string) int { found := -1 for idx, ib := range c.InboundConfigs { if ib.Tag == tag { found = idx break } } return found } func (c *Config) findOutboundTag(tag string) int { found := -1 for idx, ob := range c.OutboundConfigs { if ob.Tag == tag { found = idx break } } return found } func applyTransportConfig(s *StreamConfig, t *TransportConfig) { if s.TCPSettings == nil { s.TCPSettings = t.TCPConfig } if s.KCPSettings == nil { s.KCPSettings = t.KCPConfig } if s.WSSettings == nil { s.WSSettings = t.WSConfig } if s.HTTPSettings == nil { s.HTTPSettings = t.HTTPConfig } if s.DSSettings == nil { s.DSSettings = t.DSConfig } } // Build implements Buildable. func (c *Config) Build() (*core.Config, error) { if err := PostProcessConfigureFile(c); err != nil { return nil, err } config := &core.Config{ App: []*anypb.Any{ serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&proxyman.InboundConfig{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), }, } if c.API != nil { apiConf, err := c.API.Build() if err != nil { return nil, err } config.App = append(config.App, serial.ToTypedMessage(apiConf)) } if c.Stats != nil { statsConf, err := c.Stats.Build() if err != nil { return nil, err } config.App = append(config.App, serial.ToTypedMessage(statsConf)) } var logConfMsg *anypb.Any if c.LogConfig != nil { logConfMsg = serial.ToTypedMessage(c.LogConfig.Build()) } else { logConfMsg = serial.ToTypedMessage(log.DefaultLogConfig()) } // let logger module be the first App to start, // so that other modules could print log during initiating config.App = append([]*anypb.Any{logConfMsg}, config.App...) if c.RouterConfig != nil { routerConfig, err := c.RouterConfig.Build() if err != nil { return nil, err } config.App = append(config.App, serial.ToTypedMessage(routerConfig)) } if c.DNSConfig != nil { dnsApp, err := c.DNSConfig.Build() if err != nil { return nil, newError("failed to parse DNS config").Base(err) } config.App = append(config.App, serial.ToTypedMessage(dnsApp)) } if c.Policy != nil { pc, err := c.Policy.Build() if err != nil { return nil, err } config.App = append(config.App, serial.ToTypedMessage(pc)) } if c.Reverse != nil { r, err := c.Reverse.Build() if err != nil { return nil, err } config.App = append(config.App, serial.ToTypedMessage(r)) } if c.FakeDNS != nil { r, err := c.FakeDNS.Build() if err != nil { return nil, err } config.App = append(config.App, serial.ToTypedMessage(r)) } if c.BrowserForwarder != nil { r, err := c.BrowserForwarder.Build() if err != nil { return nil, err } config.App = append(config.App, serial.ToTypedMessage(r)) } if c.Observatory != nil { r, err := c.Observatory.Build() if err != nil { return nil, err } config.App = append(config.App, serial.ToTypedMessage(r)) } if c.BurstObservatory != nil { r, err := c.BurstObservatory.Build() if err != nil { return nil, err } config.App = append(config.App, serial.ToTypedMessage(r)) } if c.MultiObservatory != nil { r, err := c.MultiObservatory.Build() if err != nil { return nil, err } config.App = append(config.App, serial.ToTypedMessage(r)) } // Load Additional Services that do not have a json translator if msg, err := c.BuildServices(c.Services); err != nil { developererr := newError("Loading a V2Ray Features as a service is intended for developers only. " + "This is used for developers to prototype new features or for an advanced client to use special features in V2Ray," + " instead of allowing end user to enable it without special tool and knowledge.") sb := strings.Builder{} return nil, newError("Cannot load service").Base(developererr).Base(err).Base(newError(sb.String())) } else { // nolint: revive // Using a else here is required to keep msg in scope config.App = append(config.App, msg...) } var inbounds []InboundDetourConfig if c.InboundConfig != nil { inbounds = append(inbounds, *c.InboundConfig) } if len(c.InboundDetours) > 0 { inbounds = append(inbounds, c.InboundDetours...) } if len(c.InboundConfigs) > 0 { inbounds = append(inbounds, c.InboundConfigs...) } // Backward compatibility. if len(inbounds) > 0 && inbounds[0].PortRange == nil && c.Port > 0 { inbounds[0].PortRange = &cfgcommon.PortRange{ From: uint32(c.Port), To: uint32(c.Port), } } for _, rawInboundConfig := range inbounds { if c.Transport != nil { if rawInboundConfig.StreamSetting == nil { rawInboundConfig.StreamSetting = &StreamConfig{} } applyTransportConfig(rawInboundConfig.StreamSetting, c.Transport) } ic, err := rawInboundConfig.Build() if err != nil { return nil, err } config.Inbound = append(config.Inbound, ic) } var outbounds []OutboundDetourConfig if c.OutboundConfig != nil { outbounds = append(outbounds, *c.OutboundConfig) } if len(c.OutboundDetours) > 0 { outbounds = append(outbounds, c.OutboundDetours...) } if len(c.OutboundConfigs) > 0 { outbounds = append(outbounds, c.OutboundConfigs...) } for _, rawOutboundConfig := range outbounds { if c.Transport != nil { if rawOutboundConfig.StreamSetting == nil { rawOutboundConfig.StreamSetting = &StreamConfig{} } applyTransportConfig(rawOutboundConfig.StreamSetting, c.Transport) } oc, err := rawOutboundConfig.Build() if err != nil { return nil, err } config.Outbound = append(config.Outbound, oc) } return config, nil }