diff --git a/constant/proxy.go b/constant/proxy.go index 82a703b2..620b0b0c 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -32,7 +32,7 @@ const ( TypeOCM = "ocm" TypeOOMKiller = "oom-killer" TypeXBoard = "xboard" - TypeSBProxy = "sbproxy" + TypeSBProxy = "sbproxy-mc" ) const ( @@ -95,7 +95,7 @@ func ProxyDisplayName(proxyType string) string { case TypeURLTest: return "URLTest" case TypeSBProxy: - return "SBProxy" + return "SBProxy-MC" default: return "Unknown" } diff --git a/option/sbproxy.go b/option/sbproxy.go index 6f937ec3..c6e70ef4 100644 --- a/option/sbproxy.go +++ b/option/sbproxy.go @@ -9,6 +9,7 @@ type SBProxyInboundOptions struct { MaxPlayers int `json:"max_players,omitempty"` Version string `json:"version,omitempty"` ProtocolType string `json:"protocol_type,omitempty"` // java or bedrock + Dest string `json:"dest,omitempty"` } type SBProxyOutboundOptions struct { diff --git a/protocol/sbproxy/bedrock_server.go b/protocol/sbproxy/bedrock_server.go index 512def81..05ca1345 100644 --- a/protocol/sbproxy/bedrock_server.go +++ b/protocol/sbproxy/bedrock_server.go @@ -20,6 +20,25 @@ func (h *Inbound) startBedrockListener() { } defer l.Close() + // Setup RakNet Pong Data (Active scanning response) + motd := h.options.MOTD + if motd == "" { + motd = "A Minecraft Server" + } + ver := h.options.Version + if ver == "" { + ver = "1.20.1" + } + + // Bedrock Pong format: MCPE;MOTD;Protocol;Version;Online;Max;ServerID;SubMOTD;GameMode;1;Port;Port; + pongData := fmt.Sprintf("MCPE;%s;594;%s;0;%d;%d;SBProxy;Creative;1;19132;19132;", + motd, ver, h.options.MaxPlayers, time.Now().UnixNano()) + l.ExpirablePongData(func(p net.Addr) []byte { + return []byte(pongData) + }) + + h.logger.Info("Bedrock listener started with Version: ", ver) + for { conn, err := l.Accept() if err != nil { @@ -31,5 +50,17 @@ func (h *Inbound) startBedrockListener() { func (h *Inbound) handleBedrockConnection(conn net.Conn) { defer conn.Close() - // Placeholder for Bedrock handshake and tunneling + h.logger.Info("Bedrock connection accepted from: ", conn.RemoteAddr()) + + // For Bedrock, we eventually want to do a similar handshake + // using Bedrock-specific packets (e.g., Login, RequestChunkRadius, etc.) + // and then a custom Play-state packet for encapsulated proxy data. + + // Implementation follows the same pattern as Java: + // Handle Handshake -> Handle Login -> Handle Play -> Tunnel data. + + // KeepAlive is handled by RakNet itself. + + // Placeholder for Bedrock-specific tunneling logic + select {} } diff --git a/protocol/sbproxy/inbound.go b/protocol/sbproxy/inbound.go index e8f05cf7..87130e11 100644 --- a/protocol/sbproxy/inbound.go +++ b/protocol/sbproxy/inbound.go @@ -41,10 +41,14 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo options: options, users: options.Users, } + networks := []string{N.NetworkTCP} + if options.ProtocolType == "bedrock" { + networks = nil // Handled by separate raknet listener + } inbound.listener = listener.New(listener.Options{ Context: ctx, Logger: logger, - Network: []string{N.NetworkTCP, N.NetworkUDP}, + Network: networks, Listen: options.ListenOptions, ConnectionHandler: inbound, PacketHandler: inbound, @@ -58,11 +62,11 @@ func (h *Inbound) Start(stage adapter.StartStage) error { if stage != adapter.StartStateStart { return nil } - err := h.listener.Start() - if err == nil && h.options.ProtocolType == "bedrock" { + if h.options.ProtocolType == "bedrock" { go h.startBedrockListener() + return nil } - return err + return h.listener.Start() } func (h *Inbound) Close() error { return common.Close(h.listener) } diff --git a/protocol/sbproxy/java_server.go b/protocol/sbproxy/java_server.go index a6cef892..cd298823 100644 --- a/protocol/sbproxy/java_server.go +++ b/protocol/sbproxy/java_server.go @@ -75,7 +75,9 @@ func (h *Inbound) handleJavaStatus(conn net.Conn) { conn.Write(body.Bytes()) } else if packetID == 0x01 { // Ping var b [8]byte - io.ReadFull(conn, b[:]) + if _, err := io.ReadFull(conn, b[:]); err != nil { + return + } var body bytes.Buffer WriteVarInt(&body, 0x01) @@ -90,10 +92,9 @@ func (h *Inbound) handleJavaStatus(conn net.Conn) { } } -func (h *Inbound) handleJavaLogin(ctx context.Context, conn net.Conn, username string, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - // Note: Packet length is already read in switch or caller +func (h *Inbound) handleJavaLogin(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { // Login Start - packetLen, _, err := ReadVarInt(conn) + _, _, err := ReadVarInt(conn) if err != nil { conn.Close() return diff --git a/protocol/sbproxy/tunnel.go b/protocol/sbproxy/tunnel.go index dd8f29aa..2dcb60c6 100644 --- a/protocol/sbproxy/tunnel.go +++ b/protocol/sbproxy/tunnel.go @@ -68,13 +68,19 @@ func (c *javaTunnelConn) SetDeadline(t time.Time) error { return nil } func (c *javaTunnelConn) SetReadDeadline(t time.Time) error { return nil } func (c *javaTunnelConn) SetWriteDeadline(t time.Time) error { return nil } -func (h *Inbound) startJavaTunnel(ctx context.Context, conn net.Conn, username string, encrypter, decrypter cipher.Stream, metadata adapter.InboundContext) *javaTunnelConn { +func (h *Inbound) startJavaTunnel(ctx context.Context, conn net.Conn, username string, encrypter, decrypter cipher.Stream, inboundMetadata adapter.InboundContext) *javaTunnelConn { tunnel := &javaTunnelConn{ h: h, conn: conn, encrypter: encrypter, } tunnel.readCond = sync.NewCond(&tunnel.readMutex) - go h.router.RouteConnectionEx(ctx, tunnel, metadata, M.Metadata{}, nil) + + metadata := M.Metadata{} + if h.options.Dest != "" { + metadata.Destination = M.ParseAddress(h.options.Dest) + } + + go h.router.RouteConnectionEx(ctx, tunnel, inboundMetadata, metadata, nil) return tunnel } diff --git a/service/xboard/service.go b/service/xboard/service.go index 237c1d4f..b77fc693 100644 --- a/service/xboard/service.go +++ b/service/xboard/service.go @@ -1455,13 +1455,14 @@ func (s *Service) setupNode() error { } inboundOptions = opts s.logger.Info("Xboard AnyTLS configured") - case "sbproxy": + case "sbproxy-mc": opts := &option.SBProxyInboundOptions{ ListenOptions: listen, MOTD: common.PtrValueOrDefault(&inner.MOTD, "A Minecraft Server"), Version: common.PtrValueOrDefault(&inner.Version_, "1.20.1"), MaxPlayers: common.PtrValueOrDefault(&inner.MaxPlayers, 100), ProtocolType: common.PtrValueOrDefault(&inner.ProtocolType, "java"), + Dest: inner.Dest, } inboundOptions = opts s.logger.Info("Xboard SBProxy configured. MOTD: ", opts.MOTD)