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