Skip to content

Commit

Permalink
tedac-gophertunnel: Update.
Browse files Browse the repository at this point in the history
  • Loading branch information
didntpot committed Aug 18, 2024
1 parent 7e1534a commit 13b22f5
Show file tree
Hide file tree
Showing 20 changed files with 272 additions and 226 deletions.
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ require (
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/df-mc/atomic v1.10.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 // indirect
golang.org/x/image v0.17.0 // indirect
)

replace github.com/sandertv/go-raknet => github.com/tedacmc/tedac-raknet v0.0.4
12 changes: 8 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/df-mc/atomic v1.10.0 h1:0ZuxBKwR/hxcFGorKiHIp+hY7hgY+XBTzhCYD2NqSEg=
github.com/df-mc/atomic v1.10.0/go.mod h1:Gw9rf+rPIbydMjA329Jn4yjd/O2c/qusw3iNp4tFGSc=
github.com/go-gl/mathgl v1.1.0 h1:0lzZ+rntPX3/oGrDzYGdowSLC2ky8Osirvf5uAwfIEA=
github.com/go-gl/mathgl v1.1.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
Expand All @@ -19,19 +21,21 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sandertv/go-raknet v1.14.0 h1:2vtO1m1DFLFszeCcV7mVZfVgkDcAbSxcjM2BlrVrEGs=
github.com/sandertv/go-raknet v1.14.0/go.mod h1:/yysjwfCXm2+2OY8mBazLzcxJ3irnylKCyG3FLgUPVU=
github.com/sandertv/go-raknet v1.14.1 h1:V2Gslo+0x4jfj+p0PM48mWxmMbYkxSlgeKy//y3ZrzI=
github.com/sandertv/go-raknet v1.14.1/go.mod h1:/yysjwfCXm2+2OY8mBazLzcxJ3irnylKCyG3FLgUPVU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tedacmc/tedac-raknet v0.0.4 h1:GcRfp38iXARo/Wb+nfrCPHrYpqgAGi7XzapwunIA/LQ=
github.com/tedacmc/tedac-raknet v0.0.4/go.mod h1:vT0+qrD5NHYW9OElUncfIRT0brTgJhxyaozMZyjL2Zc=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco=
golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
Expand Down
21 changes: 10 additions & 11 deletions minecraft/auth/live.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ package auth
import (
"encoding/json"
"fmt"
"golang.org/x/oauth2"
"golang.org/x/oauth2/microsoft"
"io"
"net/http"
"net/url"
"os"
"time"

"golang.org/x/oauth2"
"golang.org/x/oauth2/microsoft"
)

// TokenSource holds an oauth2.TokenSource which uses device auth to get a code. The user authenticates using
Expand Down Expand Up @@ -74,7 +73,7 @@ func RequestLiveToken() (*oauth2.Token, error) {
// be printed to the io.Writer passed with a user code which the user must use to submit.
// Once fully authenticated, an oauth2 token is returned which may be used to login to XBOX Live.
func RequestLiveTokenWriter(w io.Writer) (*oauth2.Token, error) {
d, err := startDeviceAuth()
d, err := StartDeviceAuth()
if err != nil {
return nil, err
}
Expand All @@ -83,7 +82,7 @@ func RequestLiveTokenWriter(w io.Writer) (*oauth2.Token, error) {
defer ticker.Stop()

for range ticker.C {
t, err := pollDeviceAuth(d.DeviceCode)
t, err := PollDeviceAuth(d.DeviceCode)
if err != nil {
return nil, fmt.Errorf("error polling for device auth: %w", err)
}
Expand All @@ -97,9 +96,9 @@ func RequestLiveTokenWriter(w io.Writer) (*oauth2.Token, error) {
panic("unreachable")
}

// startDeviceAuth starts the device auth, retrieving a login URI for the user and a code the user needs to
// StartDeviceAuth starts the device auth, retrieving a login URI for the user and a code the user needs to
// enter.
func startDeviceAuth() (*deviceAuthConnect, error) {
func StartDeviceAuth() (*DeviceAuthConnect, error) {
resp, err := http.PostForm("https://login.live.com/oauth20_connect.srf", url.Values{
"client_id": {"0000000048183522"},
"scope": {"service::user.auth.xboxlive.com::MBI_SSL"},
Expand All @@ -114,13 +113,13 @@ func startDeviceAuth() (*deviceAuthConnect, error) {
if resp.StatusCode != 200 {
return nil, fmt.Errorf("POST https://login.live.com/oauth20_connect.srf: %v", resp.Status)
}
data := new(deviceAuthConnect)
data := new(DeviceAuthConnect)
return data, json.NewDecoder(resp.Body).Decode(data)
}

// pollDeviceAuth polls the token endpoint for the device code. A token is returned if the user authenticated
// PollDeviceAuth polls the token endpoint for the device code. A token is returned if the user authenticated
// successfully. If the user has not yet authenticated, err is nil but the token is nil too.
func pollDeviceAuth(deviceCode string) (t *oauth2.Token, err error) {
func PollDeviceAuth(deviceCode string) (t *oauth2.Token, err error) {
resp, err := http.PostForm(microsoft.LiveConnectEndpoint.TokenURL, url.Values{
"client_id": {"0000000048183522"},
"grant_type": {"urn:ietf:params:oauth:grant-type:device_code"},
Expand Down Expand Up @@ -177,7 +176,7 @@ func refreshToken(t *oauth2.Token) (*oauth2.Token, error) {
}, nil
}

type deviceAuthConnect struct {
type DeviceAuthConnect struct {
UserCode string `json:"user_code"`
DeviceCode string `json:"device_code"`
VerificationURI string `json:"verification_uri"`
Expand Down
6 changes: 2 additions & 4 deletions minecraft/auth/xbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ import (
"encoding/binary"
"encoding/json"
"fmt"
"net/http"
"time"

"github.com/google/uuid"
"golang.org/x/oauth2"
"net/http"
"time"
)

// XBLToken holds info on the authorization token used for authenticating with XBOX Live.
Expand Down Expand Up @@ -220,4 +219,3 @@ func parseXboxErrorCode(code string) string {
return fmt.Sprintf("unknown error code: %v", code)
}
}

57 changes: 45 additions & 12 deletions minecraft/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func newConn(netConn net.Conn, key *ecdsa.PrivateKey, log *log.Logger, proto Pro
}
_, _ = rand.Read(conn.salt)

conn.expectedIDs.Store([]uint32{packet.IDRequestNetworkSettings})
conn.expectedIDs.Store([]uint32{packet.IDLogin, packet.IDRequestNetworkSettings})

if flushRate <= 0 {
return conn
Expand Down Expand Up @@ -458,7 +458,7 @@ func (conn *Conn) Flush() error {
if len(conn.bufferedSend) > 0 {
if err := conn.enc.Encode(conn.bufferedSend); err != nil && !errors.Is(err, net.ErrClosed) {
// Should never happen.
panic(fmt.Errorf("error encoding packet batch: %w", err))
return fmt.Errorf("error encoding packet batch: %v", err)
}
// First manually clear out conn.bufferedSend so that re-using the slice after resetting its length to
// 0 doesn't result in an 'invisible' memory leak.
Expand Down Expand Up @@ -494,6 +494,11 @@ func (conn *Conn) RemoteAddr() net.Addr {
return conn.conn.RemoteAddr()
}

// Protocol returns the protocol used by the connection.
func (conn *Conn) Protocol() Protocol {
return conn.proto
}

// SetDeadline sets the read and write deadline of the connection. It is equivalent to calling SetReadDeadline
// and SetWriteDeadline at the same time.
func (conn *Conn) SetDeadline(t time.Time) error {
Expand Down Expand Up @@ -712,8 +717,13 @@ func (conn *Conn) handleRequestNetworkSettings(pk *packet.RequestNetworkSettings
return fmt.Errorf("send NetworkSettings: %w", err)
}
_ = conn.Flush()
conn.enc.EnableCompression(conn.compression)
conn.dec.EnableCompression()
compression := conn.compression
if pk.ClientProtocol >= 649 { // 1.20.60
// TODO: I hate this hack as much as the next person, but I don't see another other way out.
compression = packet.NewOnTheFlyCompression(compression)
}
conn.enc.EnableCompression(compression)
conn.dec.EnableCompression(compression)
return nil
}

Expand All @@ -723,15 +733,39 @@ func (conn *Conn) handleNetworkSettings(pk *packet.NetworkSettings) error {
if !ok {
return fmt.Errorf("unknown compression algorithm %v", pk.CompressionAlgorithm)
}
conn.enc.EnableCompression(alg)
conn.dec.EnableCompression()
compression := alg
if conn.proto.ID() >= 649 { // 1.20.60
// TODO: I hate this hack as much as the next person, but I don't see another other way out.
compression = packet.NewOnTheFlyCompression(compression)
}
conn.enc.EnableCompression(compression)
conn.dec.EnableCompression(compression)
conn.readyToLogin = true
return nil
}

// handleLogin handles an incoming login packet. It verifies and decodes the login request found in the packet
// and returns an error if it couldn't be done successfully.
func (conn *Conn) handleLogin(pk *packet.Login) error {
found := false
for _, pro := range conn.acceptedProto {
if pro.ID() == pk.ClientProtocol {
conn.proto = pro
conn.pool = pro.Packets(true)
found = true
break
}
}
if !found {
status := packet.PlayStatusLoginFailedClient
if pk.ClientProtocol > protocol.CurrentProtocol {
// The server is outdated in this case, so we have to change the status we send.
status = packet.PlayStatusLoginFailedServer
}
_ = conn.WritePacket(&packet.PlayStatus{Status: status})
return fmt.Errorf("%v connected with an incompatible protocol: expected protocol = %v, client protocol = %v", conn.identityData.DisplayName, protocol.CurrentProtocol, pk.ClientProtocol)
}

// The next expected packet is a response from the client to the handshake.
conn.expect(packet.IDClientToServerHandshake)
var (
Expand Down Expand Up @@ -835,8 +869,8 @@ func (conn *Conn) handleServerToClientHandshake(pk *packet.ServerToClientHandsha
keyBytes := sha256.Sum256(append(salt, sharedSecret...))

// Finally we enable encryption for the enc and dec using the secret pubKey bytes we produced.
conn.enc.EnableEncryption(keyBytes)
conn.dec.EnableEncryption(keyBytes)
conn.enc.EnableEncryption(conn.proto.Encryption(keyBytes))
conn.dec.EnableEncryption(conn.proto.Encryption(keyBytes))

// We write a ClientToServerHandshake packet (which has no payload) as a response.
_ = conn.WritePacket(&packet.ClientToServerHandshake{})
Expand Down Expand Up @@ -1420,9 +1454,8 @@ func (conn *Conn) enableEncryption(clientPublicKey *ecdsa.PublicKey) error {
keyBytes := sha256.Sum256(append(conn.salt, sharedSecret...))

// Finally we enable encryption for the encoder and decoder using the secret key bytes we produced.
conn.enc.EnableEncryption(keyBytes)
conn.dec.EnableEncryption(keyBytes)

conn.enc.EnableEncryption(conn.proto.Encryption(keyBytes))
conn.dec.EnableEncryption(conn.proto.Encryption(keyBytes))
return nil
}

Expand All @@ -1437,5 +1470,5 @@ func (conn *Conn) closeErr(op string) error {
if msg := *conn.disconnectMessage.Load(); msg != "" {
return conn.wrap(DisconnectError(msg), op)
}
return conn.wrap(errClosed, op)
return conn.wrap(net.ErrClosed, op)
}
2 changes: 0 additions & 2 deletions minecraft/err.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import (
)

var (
// TODO: Change this to net.ErrClosed in 1.16.
errClosed = errors.New("use of closed network connection")
errBufferTooSmall = errors.New("a message sent was larger than the buffer used to receive the message into")
errListenerClosed = errors.New("use of closed listener")
)
Expand Down
19 changes: 15 additions & 4 deletions minecraft/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"crypto/rand"
"errors"
"fmt"
"github.com/sandertv/go-raknet"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sandertv/gophertunnel/minecraft/resource"
Expand Down Expand Up @@ -140,7 +141,7 @@ func (cfg ListenConfig) Listen(network string, address string) (*Listener, error
}

// Actually start listening.
go listener.listen()
go listener.listen(n)
return listener, nil
}

Expand Down Expand Up @@ -219,7 +220,7 @@ func (listener *Listener) updatePongData() {

// listen starts listening for incoming connections and packets. When a player is fully connected, it submits
// it to the accepted connections channel so that a call to Accept can pick it up.
func (listener *Listener) listen() {
func (listener *Listener) listen(n Network) {
listener.updatePongData()
go func() {
ticker := time.NewTicker(time.Second * 4)
Expand All @@ -245,20 +246,22 @@ func (listener *Listener) listen() {
// close too.
return
}
listener.createConn(netConn)
listener.createConn(n, netConn)
}
}

// createConn creates a connection for the net.Conn passed and adds it to the listener, so that it may be
// accepted once its login sequence is complete.
func (listener *Listener) createConn(netConn net.Conn) {
func (listener *Listener) createConn(n Network, netConn net.Conn) {
listener.packsMu.RLock()
packs := slices.Clone(listener.packs)
listener.packsMu.RUnlock()

conn := newConn(netConn, listener.key, listener.cfg.ErrorLog, proto{}, listener.cfg.FlushRate, true)
conn.acceptedProto = append(listener.cfg.AcceptedProtocols, proto{})
conn.compression = listener.cfg.Compression
// Temporarily set the protocol to the latest: We don't know the actual protocol until we read the Login packet.
conn.proto = proto{}
conn.pool = conn.proto.Packets(true)

conn.packetFunc = listener.cfg.PacketFunc
Expand All @@ -270,6 +273,14 @@ func (listener *Listener) createConn(netConn net.Conn) {
conn.disconnectOnUnknownPacket = !listener.cfg.AllowUnknownPackets
conn.disconnectOnInvalidPacket = !listener.cfg.AllowInvalidPackets

// Enable compression based on the protocol.
// 10 was the last RakNet protocol version, that reading Login packet at the first packet
// before RequestNetworkSettings packet getting added on version 11.
if netConn.(*raknet.Conn).ProtocolVersion() <= 10 {
conn.enc.EnableCompression(n.Compression(netConn))
conn.dec.EnableCompression(n.Compression(netConn))
}

if listener.playerCount.Load() == int32(listener.cfg.MaximumPlayers) && listener.cfg.MaximumPlayers != 0 {
// The server was full. We kick the player immediately and close the connection.
_ = conn.WritePacket(&packet.PlayStatus{Status: packet.PlayStatusLoginFailedServerFull})
Expand Down
4 changes: 4 additions & 0 deletions minecraft/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package minecraft

import (
"context"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"net"
)

Expand All @@ -24,6 +25,9 @@ type Network interface {
// Specific features of the listener may be modified once it is returned, such as the used log and/or the
// accepted protocol.
Listen(address string) (NetworkListener, error)

// Compression returns a new compression instance used by this Protocol.
Compression(conn net.Conn) packet.Compression
}

// NetworkListener represents a listening connection to a remote server. It is the equivalent of net.Listener, but with extra
Expand Down
19 changes: 12 additions & 7 deletions minecraft/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ type Protocol interface {
// to true, the pool should be created for a Listener. This means that only
// packets that may be sent by a client should be allowed.
Packets(listener bool) packet.Pool

// Encryption returns a new encryption instance used by this Protocol.
Encryption(key [32]byte) packet.Encryption

// NewReader returns a protocol.IO that implements reading operations for reading types
// that are used for this Protocol.
NewReader(r ByteReader, shieldID int32, enableLimits bool) protocol.IO
Expand Down Expand Up @@ -53,22 +57,23 @@ type ByteWriter interface {
// convert any packets, as they are already of the right type.
type proto struct{}

func (proto) ID() int32 { return protocol.CurrentProtocol }
func (p proto) Ver() string { return protocol.CurrentVersion }
func (p proto) Packets(listener bool) packet.Pool {
func (proto) ID() int32 { return protocol.CurrentProtocol }
func (proto) Ver() string { return protocol.CurrentVersion }
func (proto) Packets(listener bool) packet.Pool {
if listener {
return packet.NewClientPool()
}
return packet.NewServerPool()
}
func (p proto) NewReader(r ByteReader, shieldID int32, enableLimits bool) protocol.IO {
func (proto) Encryption(key [32]byte) packet.Encryption { return packet.NewCTREncryption(key[:]) }
func (proto) NewReader(r ByteReader, shieldID int32, enableLimits bool) protocol.IO {
return protocol.NewReader(r, shieldID, enableLimits)
}
func (p proto) NewWriter(w ByteWriter, shieldID int32) protocol.IO {
func (proto) NewWriter(w ByteWriter, shieldID int32) protocol.IO {
return protocol.NewWriter(w, shieldID)
}
func (p proto) ConvertToLatest(pk packet.Packet, _ *Conn) []packet.Packet { return []packet.Packet{pk} }
func (p proto) ConvertFromLatest(pk packet.Packet, _ *Conn) []packet.Packet {
func (proto) ConvertToLatest(pk packet.Packet, _ *Conn) []packet.Packet { return []packet.Packet{pk} }
func (proto) ConvertFromLatest(pk packet.Packet, _ *Conn) []packet.Packet {
return []packet.Packet{pk}
}

Expand Down
Loading

0 comments on commit 13b22f5

Please sign in to comment.