diff --git a/service/xboard/service.go b/service/xboard/service.go index de17112b..6f1e767c 100644 --- a/service/xboard/service.go +++ b/service/xboard/service.go @@ -104,6 +104,7 @@ type XNodeConfig struct { ServerName string `json:"server_name,omitempty"` ServerPortText string `json:"server_port_text,omitempty"` Network string `json:"network"` + Multiplex *XMultiplexConfig `json:"multiplex,omitempty"` NetworkSettings json.RawMessage `json:"network_settings"` NetworkSettings_ json.RawMessage `json:"networkSettings"` @@ -159,6 +160,7 @@ type XInnerConfig struct { Dest string `json:"dest,omitempty"` ServerName string `json:"server_name,omitempty"` Network string `json:"network"` + Multiplex *XMultiplexConfig `json:"multiplex,omitempty"` NetworkSettings json.RawMessage `json:"network_settings"` NetworkSettings_ json.RawMessage `json:"networkSettings"` StreamSettings json.RawMessage `json:"streamSettings"` @@ -166,6 +168,22 @@ type XInnerConfig struct { DownMbps int `json:"down_mbps"` } +type XMultiplexConfig struct { + Enabled bool `json:"enabled"` + Protocol string `json:"protocol,omitempty"` + MaxConnections int `json:"max_connections,omitempty"` + MinStreams int `json:"min_streams,omitempty"` + MaxStreams int `json:"max_streams,omitempty"` + Padding bool `json:"padding,omitempty"` + Brutal *XBrutalConfig `json:"brutal,omitempty"` +} + +type XBrutalConfig struct { + Enabled bool `json:"enabled,omitempty"` + UpMbps int `json:"up_mbps,omitempty"` + DownMbps int `json:"down_mbps,omitempty"` +} + type HttpNetworkConfig struct { Header struct { Type string `json:"type"` @@ -403,6 +421,25 @@ func mergedTLSSettings(inner XInnerConfig, config *XNodeConfig) *XTLSSettings { return tlsSettings } +func buildInboundMultiplex(config *XMultiplexConfig) *option.InboundMultiplexOptions { + if config == nil || !config.Enabled { + return nil + } + var brutal *option.BrutalOptions + if config.Brutal != nil { + brutal = &option.BrutalOptions{ + Enabled: config.Brutal.Enabled, + UpMbps: config.Brutal.UpMbps, + DownMbps: config.Brutal.DownMbps, + } + } + return &option.InboundMultiplexOptions{ + Enabled: config.Enabled, + Padding: config.Padding, + Brutal: brutal, + } +} + func (s *Service) Start(stage adapter.StartStage) error { if stage != adapter.StartStateStart { return nil @@ -556,14 +593,22 @@ func (s *Service) setupNode() error { if inner.Protocol == "" { inner.Protocol = config.NodeType } + portSource := "" if inner.Port == 0 { if inner.ServerPort != 0 { inner.Port = inner.ServerPort + portSource = "inner.server_port" } else if config.Port != 0 { inner.Port = config.Port + portSource = "config.port" } else { inner.Port = config.ServerPort + if config.ServerPort != 0 { + portSource = "config.server_port" + } } + } else { + portSource = "inner.port" } if inner.Cipher == "" { inner.Cipher = config.Cipher @@ -574,6 +619,9 @@ func (s *Service) setupNode() error { if inner.Network == "" { inner.Network = config.Network } + if inner.Multiplex == nil { + inner.Multiplex = config.Multiplex + } if len(inner.NetworkSettings) == 0 { inner.NetworkSettings = config.NetworkSettings } @@ -714,6 +762,20 @@ func (s *Service) setupNode() error { } // ── Build inbound per protocol (matching V2bX core/sing/node.go) ── + multiplex := buildInboundMultiplex(inner.Multiplex) + s.logger.Info( + "Xboard node config resolved. protocol=", protocol, + ", listen_ip=", inner.ListenIP, + ", listen_port=", inner.Port, + " (source=", portSource, ")", + ", inner_server_port=", inner.ServerPort, + ", config_port=", config.Port, + ", config_server_port=", config.ServerPort, + ", network=", networkType, + ", tls=", securityType, + ", multiplex=", multiplex != nil, + ) + var inboundOptions any switch protocol { case "vmess", "vless": @@ -729,6 +791,7 @@ func (s *Service) setupNode() error { InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ TLS: &tlsOptions, }, + Multiplex: multiplex, } if transport != nil { opts.Transport = transport @@ -740,6 +803,7 @@ func (s *Service) setupNode() error { InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ TLS: &tlsOptions, }, + Multiplex: multiplex, } if transport != nil { opts.Transport = transport @@ -765,6 +829,7 @@ func (s *Service) setupNode() error { ssOptions := &option.ShadowsocksInboundOptions{ ListenOptions: listen, Method: method, + Multiplex: multiplex, } isSS2022 := strings.Contains(method, "2022") @@ -805,6 +870,7 @@ func (s *Service) setupNode() error { InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ TLS: &tlsOptions, }, + Multiplex: multiplex, } if transport != nil { opts.Transport = transport @@ -902,6 +968,7 @@ func (s *Service) setupNode() error { // Remove old if exists s.inboundManager.Remove(inboundTag) + s.logger.Info("Xboard creating inbound [", inboundTag, "] on ", inner.ListenIP, ":", inner.Port, " (protocol: ", protocol, ")") // Create new inbound err = s.inboundManager.Create(s.ctx, s.router, s.logger, inboundTag, protocol, inboundOptions) @@ -983,6 +1050,12 @@ func (s *Service) fetchConfig() (*XNodeConfig, error) { // Try unmarshaling WITHOUT "data" wrapper var flatResult XNodeConfig if err2 := json.Unmarshal(body, &flatResult); err2 == nil { + s.logger.Info( + "Xboard config fetched (flat). protocol=", flatResult.Protocol, + ", node_type=", flatResult.NodeType, + ", port=", flatResult.Port, + ", server_port=", flatResult.ServerPort, + ) return &flatResult, nil } @@ -995,6 +1068,12 @@ func (s *Service) fetchConfig() (*XNodeConfig, error) { if result.Data.Protocol == "" && len(result.Data.ServerConfig) == 0 && len(result.Data.Config) == 0 && result.Data.NodeType == "" && result.Data.ServerPort == 0 { s.logger.Error("Xboard config mapping failed (fields missing). Data: ", string(body)) } + s.logger.Info( + "Xboard config fetched. protocol=", result.Data.Protocol, + ", node_type=", result.Data.NodeType, + ", port=", result.Data.Port, + ", server_port=", result.Data.ServerPort, + ) return &result.Data, nil } diff --git a/service/xboard/service_test.go b/service/xboard/service_test.go index 3b6f52cc..3f51d183 100644 --- a/service/xboard/service_test.go +++ b/service/xboard/service_test.go @@ -93,3 +93,26 @@ func TestExpandNodeOptions(t *testing.T) { t.Fatalf("second node = %+v", nodes[1]) } } + +func TestBuildInboundMultiplex(t *testing.T) { + config := &XMultiplexConfig{ + Enabled: true, + Padding: true, + Brutal: &XBrutalConfig{ + Enabled: true, + UpMbps: 100, + DownMbps: 200, + }, + } + + got := buildInboundMultiplex(config) + if got == nil { + t.Fatal("buildInboundMultiplex() returned nil") + } + if !got.Enabled || !got.Padding { + t.Fatalf("buildInboundMultiplex() = %+v", got) + } + if got.Brutal == nil || !got.Brutal.Enabled || got.Brutal.UpMbps != 100 || got.Brutal.DownMbps != 200 { + t.Fatalf("buildInboundMultiplex() brutal = %+v", got.Brutal) + } +}