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