github.com/xraypb/Xray-core@v1.8.1/infra/conf/xray.go (about) 1 package conf 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "log" 7 "os" 8 "strings" 9 10 "github.com/xraypb/Xray-core/app/dispatcher" 11 "github.com/xraypb/Xray-core/app/proxyman" 12 "github.com/xraypb/Xray-core/app/stats" 13 "github.com/xraypb/Xray-core/common/net" 14 "github.com/xraypb/Xray-core/common/serial" 15 core "github.com/xraypb/Xray-core/core" 16 "github.com/xraypb/Xray-core/transport/internet" 17 ) 18 19 var ( 20 inboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ 21 "dokodemo-door": func() interface{} { return new(DokodemoConfig) }, 22 "http": func() interface{} { return new(HTTPServerConfig) }, 23 "shadowsocks": func() interface{} { return new(ShadowsocksServerConfig) }, 24 "socks": func() interface{} { return new(SocksServerConfig) }, 25 "vless": func() interface{} { return new(VLessInboundConfig) }, 26 "vmess": func() interface{} { return new(VMessInboundConfig) }, 27 "trojan": func() interface{} { return new(TrojanServerConfig) }, 28 "mtproto": func() interface{} { return new(MTProtoServerConfig) }, 29 }, "protocol", "settings") 30 31 outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ 32 "blackhole": func() interface{} { return new(BlackholeConfig) }, 33 "loopback": func() interface{} { return new(LoopbackConfig) }, 34 "freedom": func() interface{} { return new(FreedomConfig) }, 35 "http": func() interface{} { return new(HTTPClientConfig) }, 36 "shadowsocks": func() interface{} { return new(ShadowsocksClientConfig) }, 37 "socks": func() interface{} { return new(SocksClientConfig) }, 38 "vless": func() interface{} { return new(VLessOutboundConfig) }, 39 "vmess": func() interface{} { return new(VMessOutboundConfig) }, 40 "trojan": func() interface{} { return new(TrojanClientConfig) }, 41 "mtproto": func() interface{} { return new(MTProtoClientConfig) }, 42 "dns": func() interface{} { return new(DNSOutboundConfig) }, 43 "wireguard": func() interface{} { return new(WireGuardConfig) }, 44 }, "protocol", "settings") 45 46 ctllog = log.New(os.Stderr, "xctl> ", 0) 47 ) 48 49 func toProtocolList(s []string) ([]proxyman.KnownProtocols, error) { 50 kp := make([]proxyman.KnownProtocols, 0, 8) 51 for _, p := range s { 52 switch strings.ToLower(p) { 53 case "http": 54 kp = append(kp, proxyman.KnownProtocols_HTTP) 55 case "https", "tls", "ssl": 56 kp = append(kp, proxyman.KnownProtocols_TLS) 57 default: 58 return nil, newError("Unknown protocol: ", p) 59 } 60 } 61 return kp, nil 62 } 63 64 type SniffingConfig struct { 65 Enabled bool `json:"enabled"` 66 DestOverride *StringList `json:"destOverride"` 67 DomainsExcluded *StringList `json:"domainsExcluded"` 68 MetadataOnly bool `json:"metadataOnly"` 69 RouteOnly bool `json:"routeOnly"` 70 } 71 72 // Build implements Buildable. 73 func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) { 74 var p []string 75 if c.DestOverride != nil { 76 for _, protocol := range *c.DestOverride { 77 switch strings.ToLower(protocol) { 78 case "http": 79 p = append(p, "http") 80 case "tls", "https", "ssl": 81 p = append(p, "tls") 82 case "quic": 83 p = append(p, "quic") 84 case "fakedns": 85 p = append(p, "fakedns") 86 case "fakedns+others": 87 p = append(p, "fakedns+others") 88 default: 89 return nil, newError("unknown protocol: ", protocol) 90 } 91 } 92 } 93 94 var d []string 95 if c.DomainsExcluded != nil { 96 for _, domain := range *c.DomainsExcluded { 97 d = append(d, strings.ToLower(domain)) 98 } 99 } 100 101 return &proxyman.SniffingConfig{ 102 Enabled: c.Enabled, 103 DestinationOverride: p, 104 DomainsExcluded: d, 105 MetadataOnly: c.MetadataOnly, 106 RouteOnly: c.RouteOnly, 107 }, nil 108 } 109 110 type MuxConfig struct { 111 Enabled bool `json:"enabled"` 112 Concurrency int16 `json:"concurrency"` 113 Only string `json:"only"` 114 } 115 116 // Build creates MultiplexingConfig, Concurrency < 0 completely disables mux. 117 func (m *MuxConfig) Build() *proxyman.MultiplexingConfig { 118 if m.Concurrency < 0 { 119 return nil 120 } 121 if m.Concurrency == 0 { 122 m.Concurrency = 8 123 } 124 125 config := &proxyman.MultiplexingConfig{ 126 Enabled: m.Enabled, 127 Concurrency: uint32(m.Concurrency), 128 } 129 130 switch strings.ToLower(m.Only) { 131 case "tcp": 132 config.Only = uint32(net.Network_TCP) 133 case "udp": 134 config.Only = uint32(net.Network_UDP) 135 } 136 137 return config 138 } 139 140 type InboundDetourAllocationConfig struct { 141 Strategy string `json:"strategy"` 142 Concurrency *uint32 `json:"concurrency"` 143 RefreshMin *uint32 `json:"refresh"` 144 } 145 146 // Build implements Buildable. 147 func (c *InboundDetourAllocationConfig) Build() (*proxyman.AllocationStrategy, error) { 148 config := new(proxyman.AllocationStrategy) 149 switch strings.ToLower(c.Strategy) { 150 case "always": 151 config.Type = proxyman.AllocationStrategy_Always 152 case "random": 153 config.Type = proxyman.AllocationStrategy_Random 154 case "external": 155 config.Type = proxyman.AllocationStrategy_External 156 default: 157 return nil, newError("unknown allocation strategy: ", c.Strategy) 158 } 159 if c.Concurrency != nil { 160 config.Concurrency = &proxyman.AllocationStrategy_AllocationStrategyConcurrency{ 161 Value: *c.Concurrency, 162 } 163 } 164 165 if c.RefreshMin != nil { 166 config.Refresh = &proxyman.AllocationStrategy_AllocationStrategyRefresh{ 167 Value: *c.RefreshMin, 168 } 169 } 170 171 return config, nil 172 } 173 174 type InboundDetourConfig struct { 175 Protocol string `json:"protocol"` 176 PortList *PortList `json:"port"` 177 ListenOn *Address `json:"listen"` 178 Settings *json.RawMessage `json:"settings"` 179 Tag string `json:"tag"` 180 Allocation *InboundDetourAllocationConfig `json:"allocate"` 181 StreamSetting *StreamConfig `json:"streamSettings"` 182 DomainOverride *StringList `json:"domainOverride"` 183 SniffingConfig *SniffingConfig `json:"sniffing"` 184 } 185 186 // Build implements Buildable. 187 func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) { 188 receiverSettings := &proxyman.ReceiverConfig{} 189 190 if c.ListenOn == nil { 191 // Listen on anyip, must set PortList 192 if c.PortList == nil { 193 return nil, newError("Listen on AnyIP but no Port(s) set in InboundDetour.") 194 } 195 receiverSettings.PortList = c.PortList.Build() 196 } else { 197 // Listen on specific IP or Unix Domain Socket 198 receiverSettings.Listen = c.ListenOn.Build() 199 listenDS := c.ListenOn.Family().IsDomain() && (c.ListenOn.Domain()[0] == '/' || c.ListenOn.Domain()[0] == '@') 200 listenIP := c.ListenOn.Family().IsIP() || (c.ListenOn.Family().IsDomain() && c.ListenOn.Domain() == "localhost") 201 if listenIP { 202 // Listen on specific IP, must set PortList 203 if c.PortList == nil { 204 return nil, newError("Listen on specific ip without port in InboundDetour.") 205 } 206 // Listen on IP:Port 207 receiverSettings.PortList = c.PortList.Build() 208 } else if listenDS { 209 if c.PortList != nil { 210 // Listen on Unix Domain Socket, PortList should be nil 211 receiverSettings.PortList = nil 212 } 213 } else { 214 return nil, newError("unable to listen on domain address: ", c.ListenOn.Domain()) 215 } 216 } 217 218 if c.Allocation != nil { 219 concurrency := -1 220 if c.Allocation.Concurrency != nil && c.Allocation.Strategy == "random" { 221 concurrency = int(*c.Allocation.Concurrency) 222 } 223 portRange := 0 224 225 for _, pr := range c.PortList.Range { 226 portRange += int(pr.To - pr.From + 1) 227 } 228 if concurrency >= 0 && concurrency >= portRange { 229 var ports strings.Builder 230 for _, pr := range c.PortList.Range { 231 fmt.Fprintf(&ports, "%d-%d ", pr.From, pr.To) 232 } 233 return nil, newError("not enough ports. concurrency = ", concurrency, " ports: ", ports.String()) 234 } 235 236 as, err := c.Allocation.Build() 237 if err != nil { 238 return nil, err 239 } 240 receiverSettings.AllocationStrategy = as 241 } 242 if c.StreamSetting != nil { 243 ss, err := c.StreamSetting.Build() 244 if err != nil { 245 return nil, err 246 } 247 receiverSettings.StreamSettings = ss 248 } 249 if c.SniffingConfig != nil { 250 s, err := c.SniffingConfig.Build() 251 if err != nil { 252 return nil, newError("failed to build sniffing config").Base(err) 253 } 254 receiverSettings.SniffingSettings = s 255 } 256 if c.DomainOverride != nil { 257 kp, err := toProtocolList(*c.DomainOverride) 258 if err != nil { 259 return nil, newError("failed to parse inbound detour config").Base(err) 260 } 261 receiverSettings.DomainOverride = kp 262 } 263 264 settings := []byte("{}") 265 if c.Settings != nil { 266 settings = ([]byte)(*c.Settings) 267 } 268 rawConfig, err := inboundConfigLoader.LoadWithID(settings, c.Protocol) 269 if err != nil { 270 return nil, newError("failed to load inbound detour config.").Base(err) 271 } 272 if dokodemoConfig, ok := rawConfig.(*DokodemoConfig); ok { 273 receiverSettings.ReceiveOriginalDestination = dokodemoConfig.Redirect 274 } 275 ts, err := rawConfig.(Buildable).Build() 276 if err != nil { 277 return nil, err 278 } 279 280 return &core.InboundHandlerConfig{ 281 Tag: c.Tag, 282 ReceiverSettings: serial.ToTypedMessage(receiverSettings), 283 ProxySettings: serial.ToTypedMessage(ts), 284 }, nil 285 } 286 287 type OutboundDetourConfig struct { 288 Protocol string `json:"protocol"` 289 SendThrough *Address `json:"sendThrough"` 290 Tag string `json:"tag"` 291 Settings *json.RawMessage `json:"settings"` 292 StreamSetting *StreamConfig `json:"streamSettings"` 293 ProxySettings *ProxyConfig `json:"proxySettings"` 294 MuxSettings *MuxConfig `json:"mux"` 295 } 296 297 func (c *OutboundDetourConfig) checkChainProxyConfig() error { 298 if c.StreamSetting == nil || c.ProxySettings == nil || c.StreamSetting.SocketSettings == nil { 299 return nil 300 } 301 if len(c.ProxySettings.Tag) > 0 && len(c.StreamSetting.SocketSettings.DialerProxy) > 0 { 302 return newError("proxySettings.tag is conflicted with sockopt.dialerProxy").AtWarning() 303 } 304 return nil 305 } 306 307 // Build implements Buildable. 308 func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { 309 senderSettings := &proxyman.SenderConfig{} 310 if err := c.checkChainProxyConfig(); err != nil { 311 return nil, err 312 } 313 314 if c.SendThrough != nil { 315 address := c.SendThrough 316 if address.Family().IsDomain() { 317 return nil, newError("unable to send through: " + address.String()) 318 } 319 senderSettings.Via = address.Build() 320 } 321 322 if c.StreamSetting != nil { 323 ss, err := c.StreamSetting.Build() 324 if err != nil { 325 return nil, err 326 } 327 senderSettings.StreamSettings = ss 328 } 329 330 if c.ProxySettings != nil { 331 ps, err := c.ProxySettings.Build() 332 if err != nil { 333 return nil, newError("invalid outbound detour proxy settings.").Base(err) 334 } 335 if ps.TransportLayerProxy { 336 if senderSettings.StreamSettings != nil { 337 if senderSettings.StreamSettings.SocketSettings != nil { 338 senderSettings.StreamSettings.SocketSettings.DialerProxy = ps.Tag 339 } else { 340 senderSettings.StreamSettings.SocketSettings = &internet.SocketConfig{DialerProxy: ps.Tag} 341 } 342 } else { 343 senderSettings.StreamSettings = &internet.StreamConfig{SocketSettings: &internet.SocketConfig{DialerProxy: ps.Tag}} 344 } 345 ps = nil 346 } 347 senderSettings.ProxySettings = ps 348 } 349 350 if c.MuxSettings != nil { 351 senderSettings.MultiplexSettings = c.MuxSettings.Build() 352 } 353 354 settings := []byte("{}") 355 if c.Settings != nil { 356 settings = ([]byte)(*c.Settings) 357 } 358 rawConfig, err := outboundConfigLoader.LoadWithID(settings, c.Protocol) 359 if err != nil { 360 return nil, newError("failed to parse to outbound detour config.").Base(err) 361 } 362 ts, err := rawConfig.(Buildable).Build() 363 if err != nil { 364 return nil, err 365 } 366 367 return &core.OutboundHandlerConfig{ 368 SenderSettings: serial.ToTypedMessage(senderSettings), 369 Tag: c.Tag, 370 ProxySettings: serial.ToTypedMessage(ts), 371 }, nil 372 } 373 374 type StatsConfig struct{} 375 376 // Build implements Buildable. 377 func (c *StatsConfig) Build() (*stats.Config, error) { 378 return &stats.Config{}, nil 379 } 380 381 type Config struct { 382 // Port of this Point server. 383 // Deprecated: Port exists for historical compatibility 384 // and should not be used. 385 Port uint16 `json:"port"` 386 387 // Deprecated: InboundConfig exists for historical compatibility 388 // and should not be used. 389 InboundConfig *InboundDetourConfig `json:"inbound"` 390 391 // Deprecated: OutboundConfig exists for historical compatibility 392 // and should not be used. 393 OutboundConfig *OutboundDetourConfig `json:"outbound"` 394 395 // Deprecated: InboundDetours exists for historical compatibility 396 // and should not be used. 397 InboundDetours []InboundDetourConfig `json:"inboundDetour"` 398 399 // Deprecated: OutboundDetours exists for historical compatibility 400 // and should not be used. 401 OutboundDetours []OutboundDetourConfig `json:"outboundDetour"` 402 403 LogConfig *LogConfig `json:"log"` 404 RouterConfig *RouterConfig `json:"routing"` 405 DNSConfig *DNSConfig `json:"dns"` 406 InboundConfigs []InboundDetourConfig `json:"inbounds"` 407 OutboundConfigs []OutboundDetourConfig `json:"outbounds"` 408 Transport *TransportConfig `json:"transport"` 409 Policy *PolicyConfig `json:"policy"` 410 API *APIConfig `json:"api"` 411 Metrics *MetricsConfig `json:"metrics"` 412 Stats *StatsConfig `json:"stats"` 413 Reverse *ReverseConfig `json:"reverse"` 414 FakeDNS *FakeDNSConfig `json:"fakeDns"` 415 Observatory *ObservatoryConfig `json:"observatory"` 416 } 417 418 func (c *Config) findInboundTag(tag string) int { 419 found := -1 420 for idx, ib := range c.InboundConfigs { 421 if ib.Tag == tag { 422 found = idx 423 break 424 } 425 } 426 return found 427 } 428 429 func (c *Config) findOutboundTag(tag string) int { 430 found := -1 431 for idx, ob := range c.OutboundConfigs { 432 if ob.Tag == tag { 433 found = idx 434 break 435 } 436 } 437 return found 438 } 439 440 // Override method accepts another Config overrides the current attribute 441 func (c *Config) Override(o *Config, fn string) { 442 // only process the non-deprecated members 443 444 if o.LogConfig != nil { 445 c.LogConfig = o.LogConfig 446 } 447 if o.RouterConfig != nil { 448 c.RouterConfig = o.RouterConfig 449 } 450 if o.DNSConfig != nil { 451 c.DNSConfig = o.DNSConfig 452 } 453 if o.Transport != nil { 454 c.Transport = o.Transport 455 } 456 if o.Policy != nil { 457 c.Policy = o.Policy 458 } 459 if o.API != nil { 460 c.API = o.API 461 } 462 if o.Metrics != nil { 463 c.Metrics = o.Metrics 464 } 465 if o.Stats != nil { 466 c.Stats = o.Stats 467 } 468 if o.Reverse != nil { 469 c.Reverse = o.Reverse 470 } 471 472 if o.FakeDNS != nil { 473 c.FakeDNS = o.FakeDNS 474 } 475 476 if o.Observatory != nil { 477 c.Observatory = o.Observatory 478 } 479 480 // deprecated attrs... keep them for now 481 if o.InboundConfig != nil { 482 c.InboundConfig = o.InboundConfig 483 } 484 if o.OutboundConfig != nil { 485 c.OutboundConfig = o.OutboundConfig 486 } 487 if o.InboundDetours != nil { 488 c.InboundDetours = o.InboundDetours 489 } 490 if o.OutboundDetours != nil { 491 c.OutboundDetours = o.OutboundDetours 492 } 493 // deprecated attrs 494 495 // update the Inbound in slice if the only one in overide config has same tag 496 if len(o.InboundConfigs) > 0 { 497 if len(c.InboundConfigs) > 0 && len(o.InboundConfigs) == 1 { 498 if idx := c.findInboundTag(o.InboundConfigs[0].Tag); idx > -1 { 499 c.InboundConfigs[idx] = o.InboundConfigs[0] 500 ctllog.Println("[", fn, "] updated inbound with tag: ", o.InboundConfigs[0].Tag) 501 } else { 502 c.InboundConfigs = append(c.InboundConfigs, o.InboundConfigs[0]) 503 ctllog.Println("[", fn, "] appended inbound with tag: ", o.InboundConfigs[0].Tag) 504 } 505 } else { 506 c.InboundConfigs = o.InboundConfigs 507 } 508 } 509 510 // update the Outbound in slice if the only one in overide config has same tag 511 if len(o.OutboundConfigs) > 0 { 512 if len(c.OutboundConfigs) > 0 && len(o.OutboundConfigs) == 1 { 513 if idx := c.findOutboundTag(o.OutboundConfigs[0].Tag); idx > -1 { 514 c.OutboundConfigs[idx] = o.OutboundConfigs[0] 515 ctllog.Println("[", fn, "] updated outbound with tag: ", o.OutboundConfigs[0].Tag) 516 } else { 517 if strings.Contains(strings.ToLower(fn), "tail") { 518 c.OutboundConfigs = append(c.OutboundConfigs, o.OutboundConfigs[0]) 519 ctllog.Println("[", fn, "] appended outbound with tag: ", o.OutboundConfigs[0].Tag) 520 } else { 521 c.OutboundConfigs = append(o.OutboundConfigs, c.OutboundConfigs...) 522 ctllog.Println("[", fn, "] prepended outbound with tag: ", o.OutboundConfigs[0].Tag) 523 } 524 } 525 } else { 526 c.OutboundConfigs = o.OutboundConfigs 527 } 528 } 529 } 530 531 func applyTransportConfig(s *StreamConfig, t *TransportConfig) { 532 if s.TCPSettings == nil { 533 s.TCPSettings = t.TCPConfig 534 } 535 if s.KCPSettings == nil { 536 s.KCPSettings = t.KCPConfig 537 } 538 if s.WSSettings == nil { 539 s.WSSettings = t.WSConfig 540 } 541 if s.HTTPSettings == nil { 542 s.HTTPSettings = t.HTTPConfig 543 } 544 if s.DSSettings == nil { 545 s.DSSettings = t.DSConfig 546 } 547 } 548 549 // Build implements Buildable. 550 func (c *Config) Build() (*core.Config, error) { 551 if err := PostProcessConfigureFile(c); err != nil { 552 return nil, err 553 } 554 555 config := &core.Config{ 556 App: []*serial.TypedMessage{ 557 serial.ToTypedMessage(&dispatcher.Config{}), 558 serial.ToTypedMessage(&proxyman.InboundConfig{}), 559 serial.ToTypedMessage(&proxyman.OutboundConfig{}), 560 }, 561 } 562 563 if c.API != nil { 564 apiConf, err := c.API.Build() 565 if err != nil { 566 return nil, err 567 } 568 config.App = append(config.App, serial.ToTypedMessage(apiConf)) 569 } 570 if c.Metrics != nil { 571 metricsConf, err := c.Metrics.Build() 572 if err != nil { 573 return nil, err 574 } 575 config.App = append(config.App, serial.ToTypedMessage(metricsConf)) 576 } 577 if c.Stats != nil { 578 statsConf, err := c.Stats.Build() 579 if err != nil { 580 return nil, err 581 } 582 config.App = append(config.App, serial.ToTypedMessage(statsConf)) 583 } 584 585 var logConfMsg *serial.TypedMessage 586 if c.LogConfig != nil { 587 logConfMsg = serial.ToTypedMessage(c.LogConfig.Build()) 588 } else { 589 logConfMsg = serial.ToTypedMessage(DefaultLogConfig()) 590 } 591 // let logger module be the first App to start, 592 // so that other modules could print log during initiating 593 config.App = append([]*serial.TypedMessage{logConfMsg}, config.App...) 594 595 if c.RouterConfig != nil { 596 routerConfig, err := c.RouterConfig.Build() 597 if err != nil { 598 return nil, err 599 } 600 config.App = append(config.App, serial.ToTypedMessage(routerConfig)) 601 } 602 603 if c.DNSConfig != nil { 604 dnsApp, err := c.DNSConfig.Build() 605 if err != nil { 606 return nil, newError("failed to parse DNS config").Base(err) 607 } 608 config.App = append(config.App, serial.ToTypedMessage(dnsApp)) 609 } 610 611 if c.Policy != nil { 612 pc, err := c.Policy.Build() 613 if err != nil { 614 return nil, err 615 } 616 config.App = append(config.App, serial.ToTypedMessage(pc)) 617 } 618 619 if c.Reverse != nil { 620 r, err := c.Reverse.Build() 621 if err != nil { 622 return nil, err 623 } 624 config.App = append(config.App, serial.ToTypedMessage(r)) 625 } 626 627 if c.FakeDNS != nil { 628 r, err := c.FakeDNS.Build() 629 if err != nil { 630 return nil, err 631 } 632 config.App = append([]*serial.TypedMessage{serial.ToTypedMessage(r)}, config.App...) 633 } 634 635 if c.Observatory != nil { 636 r, err := c.Observatory.Build() 637 if err != nil { 638 return nil, err 639 } 640 config.App = append(config.App, serial.ToTypedMessage(r)) 641 } 642 643 var inbounds []InboundDetourConfig 644 645 if c.InboundConfig != nil { 646 inbounds = append(inbounds, *c.InboundConfig) 647 } 648 649 if len(c.InboundDetours) > 0 { 650 inbounds = append(inbounds, c.InboundDetours...) 651 } 652 653 if len(c.InboundConfigs) > 0 { 654 inbounds = append(inbounds, c.InboundConfigs...) 655 } 656 657 // Backward compatibility. 658 if len(inbounds) > 0 && inbounds[0].PortList == nil && c.Port > 0 { 659 inbounds[0].PortList = &PortList{[]PortRange{{ 660 From: uint32(c.Port), 661 To: uint32(c.Port), 662 }}} 663 } 664 665 for _, rawInboundConfig := range inbounds { 666 if c.Transport != nil { 667 if rawInboundConfig.StreamSetting == nil { 668 rawInboundConfig.StreamSetting = &StreamConfig{} 669 } 670 applyTransportConfig(rawInboundConfig.StreamSetting, c.Transport) 671 } 672 ic, err := rawInboundConfig.Build() 673 if err != nil { 674 return nil, err 675 } 676 config.Inbound = append(config.Inbound, ic) 677 } 678 679 var outbounds []OutboundDetourConfig 680 681 if c.OutboundConfig != nil { 682 outbounds = append(outbounds, *c.OutboundConfig) 683 } 684 685 if len(c.OutboundDetours) > 0 { 686 outbounds = append(outbounds, c.OutboundDetours...) 687 } 688 689 if len(c.OutboundConfigs) > 0 { 690 outbounds = append(outbounds, c.OutboundConfigs...) 691 } 692 693 for _, rawOutboundConfig := range outbounds { 694 if c.Transport != nil { 695 if rawOutboundConfig.StreamSetting == nil { 696 rawOutboundConfig.StreamSetting = &StreamConfig{} 697 } 698 applyTransportConfig(rawOutboundConfig.StreamSetting, c.Transport) 699 } 700 oc, err := rawOutboundConfig.Build() 701 if err != nil { 702 return nil, err 703 } 704 config.Outbound = append(config.Outbound, oc) 705 } 706 707 return config, nil 708 }