Skip to content

Commit

Permalink
minecraft/conn.go: More control over behaviour on invalid packets.
Browse files Browse the repository at this point in the history
Automatically reject packets from the client that should not be sent by the client.
  • Loading branch information
Sandertv committed Jun 13, 2023
1 parent b81a4ee commit 03802fe
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 24 deletions.
5 changes: 4 additions & 1 deletion minecraft/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ type Conn struct {
compression packet.Compression
readerLimits bool

disconnectOnUnknownPacket bool
disconnectOnInvalidPacket bool

identityData login.IdentityData
clientData login.ClientData

Expand Down Expand Up @@ -668,7 +671,7 @@ func (conn *Conn) handleRequestNetworkSettings(pk *packet.RequestNetworkSettings
for _, pro := range conn.acceptedProto {
if pro.ID() == pk.ClientProtocol {
conn.proto = pro
conn.pool = pro.Packets()
conn.pool = pro.Packets(true)
found = true
break
}
Expand Down
14 changes: 13 additions & 1 deletion minecraft/dial.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ type Dialer struct {
// The boolean returned determines if the pack will be downloaded or not.
DownloadResourcePack func(id uuid.UUID, version string, current, total int) bool

// DisconnectOnUnknownPackets specifies if the connection should disconnect if packets received are not present
// in the packet pool. If true, such packets lead to the connection being closed immediately.
// If set to false, the packets will be returned as a packet.Unknown.
DisconnectOnUnknownPackets bool

// DisconnectOnInvalidPackets specifies if invalid packets (either too few bytes or too many bytes) should be
// allowed. If true, such packets lead to the connection being closed immediately. If false,
// packets with too many bytes will be returned while packets with too few bytes will be skipped.
DisconnectOnInvalidPackets bool

// Protocol is the Protocol version used to communicate with the target server. By default, this field is
// set to the current protocol as implemented in the minecraft/protocol package. Note that packets written
// to and read from the Conn are always any of those found in the protocol/packet package, as packets
Expand Down Expand Up @@ -171,12 +181,14 @@ func (d Dialer) DialContext(ctx context.Context, network, address string) (conn
}

conn = newConn(netConn, key, d.ErrorLog, d.Protocol, d.FlushRate, false)
conn.pool = conn.proto.Packets()
conn.pool = conn.proto.Packets(false)
conn.identityData = d.IdentityData
conn.clientData = d.ClientData
conn.packetFunc = d.PacketFunc
conn.downloadResourcePack = d.DownloadResourcePack
conn.cacheEnabled = d.EnableClientCache
conn.disconnectOnInvalidPacket = d.DisconnectOnInvalidPackets
conn.disconnectOnUnknownPacket = d.DisconnectOnUnknownPackets

defaultIdentityData(&conn.identityData)
defaultClientData(address, conn.identityData.DisplayName, &conn.clientData)
Expand Down
14 changes: 13 additions & 1 deletion minecraft/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ type ListenConfig struct {
// accepted into the server.
MaximumPlayers int

// AllowUnknownPackets specifies if connections of this Listener are allowed to send packets not present
// in the packet pool. If false (by default), such packets lead to the connection being closed immediately.
// If set to true, the packets will be returned as a packet.Unknown.
AllowUnknownPackets bool

// AllowInvalidPackets specifies if invalid packets (either too few bytes or too many bytes) should be
// allowed. If false (by default), such packets lead to the connection being closed immediately. If true,
// packets with too many bytes will be returned while packets with too few bytes will be skipped.
AllowInvalidPackets bool

// StatusProvider is the ServerStatusProvider of the Listener. When set to nil, the default provider,
// ListenerStatusProvider, is used as provider.
StatusProvider ServerStatusProvider
Expand Down Expand Up @@ -220,14 +230,16 @@ func (listener *Listener) createConn(netConn net.Conn) {
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
conn.pool = conn.proto.Packets()
conn.pool = conn.proto.Packets(true)

conn.packetFunc = listener.cfg.PacketFunc
conn.texturePacksRequired = listener.cfg.TexturePacksRequired
conn.resourcePacks = listener.cfg.ResourcePacks
conn.biomes = listener.cfg.Biomes
conn.gameData.WorldName = listener.status().ServerName
conn.authEnabled = !listener.cfg.AuthenticationDisabled
conn.disconnectOnUnknownPacket = !listener.cfg.AllowUnknownPackets
conn.disconnectOnInvalidPacket = !listener.cfg.AllowInvalidPackets

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.
Expand Down
9 changes: 9 additions & 0 deletions minecraft/packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func (p *packetData) decode(conn *Conn) (pks []packet.Packet, err error) {
if !ok {
// No packet with the ID. This may be a custom packet of some sorts.
pk = &packet.Unknown{PacketID: p.h.PacketID}
if conn.disconnectOnUnknownPacket {
_ = conn.Close()
fmt.Println("unknown packet:", p.h.PacketID)
return nil, fmt.Errorf("unknown packet with ID %v", p.h.PacketID)
}
} else {
pk = pkFunc()
}
Expand All @@ -51,5 +56,9 @@ func (p *packetData) decode(conn *Conn) (pks []packet.Packet, err error) {
if p.payload.Len() != 0 {
err = fmt.Errorf("%T: %v unread bytes left: 0x%x", pk, p.payload.Len(), p.payload.Bytes())
}
if conn.disconnectOnInvalidPacket && err != nil {
_ = conn.Close()
return nil, err
}
return conn.proto.ConvertToLatest(pk, conn), err
}
19 changes: 13 additions & 6 deletions minecraft/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ type Protocol interface {
ID() int32
// Ver returns the Minecraft version associated with this Protocol, such as "1.18.10".
Ver() string
// Packets returns a packet.Pool with all packets registered for this Protocol. It is used to lookup packets by a
// packet ID.
Packets() packet.Pool
// Packets returns a packet.Pool with all packets registered for this
// Protocol. It is used to lookup packets by a packet ID. If listener is set
// 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
// NewReader returns a protocol.IO that implements reading operations for reading types
// that are used for this Protocol.
NewReader(r reader, shieldID int32, enableLimits bool) protocol.IO
Expand Down Expand Up @@ -51,9 +53,14 @@ type writer 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() packet.Pool { return packet.NewPool() }
func (proto) ID() int32 { return protocol.CurrentProtocol }
func (p proto) Ver() string { return protocol.CurrentVersion }
func (p proto) Packets(listener bool) packet.Pool {
if listener {
packet.NewClientPool()
}
return packet.NewServerPool()
}
func (p proto) NewReader(r reader, shieldID int32, enableLimits bool) protocol.IO {
return protocol.NewReader(r, shieldID, enableLimits)
}
Expand Down
2 changes: 1 addition & 1 deletion minecraft/protocol/packet/map_create_locked_copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"github.com/sandertv/gophertunnel/minecraft/protocol"
)

// MapCreateLockedCopy is sent by the server to create a locked copy of one map into another map. In vanilla,
// MapCreateLockedCopy is sent by the client to create a locked copy of one map into another map. In vanilla,
// it is used in the cartography table to create a map that is locked and cannot be modified.
type MapCreateLockedCopy struct {
// OriginalMapID is the ID of the map that is being copied. The locked copy will obtain all content that
Expand Down
125 changes: 111 additions & 14 deletions minecraft/protocol/packet/pool.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,51 @@
package packet

// Register registers a function that returns a packet for a specific ID. Packets with this ID coming in from
// connections will resolve to the packet returned by the function passed.
// noinspection GoUnusedExportedFunction
func Register(id uint32, pk func() Packet) {
registeredPackets[id] = pk
// RegisterPacketFromClient registers a function that returns a packet for a
// specific ID. Packets with this ID coming in from connections will resolve to
// the packet returned by the function passed. noinspection
func RegisterPacketFromClient(id uint32, pk func() Packet) {
packetsFromClient[id] = pk
}

// registeredPackets holds packets registered by the user.
var registeredPackets = map[uint32]func() Packet{}
// RegisterPacketFromServer registers a function that returns a packet for a
// specific ID. Packets with this ID coming in from connections will resolve to
// the packet returned by the function passed. noinspection
func RegisterPacketFromServer(id uint32, pk func() Packet) {
packetsFromServer[id] = pk
}

// packetsFromClient holds packets that could be sent by the client.
var packetsFromClient = map[uint32]func() Packet{}

// packetsFromServer holds packets that could be sent by the server.
var packetsFromServer = map[uint32]func() Packet{}

// Pool is a map holding packets indexed by a packet ID.
type Pool map[uint32]func() Packet

// NewPool returns a new pool with all supported packets sent. Packets may be retrieved from it simply by
// indexing it with the packet ID.
func NewPool() Pool {
// NewClientPool returns a new pool containing packets sent by a client.
// Packets may be retrieved from it simply by indexing it with the packet ID.
func NewClientPool() Pool {
p := Pool{}
for id, pk := range packetsFromClient {
p[id] = pk
}
return p
}

// NewServerPool returns a new pool containing packets sent by a server.
// Packets may be retrieved from it simply by indexing it with the packet ID.
func NewServerPool() Pool {
p := Pool{}
for id, pk := range registeredPackets {
for id, pk := range packetsFromServer {
p[id] = pk
}
return p
}

func init() {
packets := map[uint32]func() Packet{
// TODO: Remove packets from this list that are not sent by the server.
serverOriginating := map[uint32]func() Packet{
IDLogin: func() Packet { return &Login{} },
IDPlayStatus: func() Packet { return &PlayStatus{} },
IDServerToClientHandshake: func() Packet { return &ServerToClientHandshake{} },
Expand Down Expand Up @@ -229,7 +250,83 @@ func init() {
IDTrimData: func() Packet { return &TrimData{} },
IDOpenSign: func() Packet { return &OpenSign{} },
}
for id, pk := range packets {
Register(id, pk)
for id, pk := range serverOriginating {
RegisterPacketFromServer(id, pk)
}
}

// Packets sent by the client:
func init() {
clientOriginating := map[uint32]func() Packet{
IDLogin: func() Packet { return &Login{} },
IDClientToServerHandshake: func() Packet { return &ClientToServerHandshake{} },
IDResourcePackClientResponse: func() Packet { return &ResourcePackClientResponse{} },
IDText: func() Packet { return &Text{} },
IDMovePlayer: func() Packet { return &MovePlayer{} },
IDPassengerJump: func() Packet { return &PassengerJump{} },
IDTickSync: func() Packet { return &TickSync{} },
IDInventoryTransaction: func() Packet { return &InventoryTransaction{} },
IDMobEquipment: func() Packet { return &MobEquipment{} },
IDInteract: func() Packet { return &Interact{} },
IDBlockPickRequest: func() Packet { return &BlockPickRequest{} },
IDActorPickRequest: func() Packet { return &ActorPickRequest{} },
IDPlayerAction: func() Packet { return &PlayerAction{} },
IDRespawn: func() Packet { return &Respawn{} },
IDContainerOpen: func() Packet { return &ContainerOpen{} },
IDContainerClose: func() Packet { return &ContainerClose{} },
IDCraftingEvent: func() Packet { return &CraftingEvent{} },
IDAdventureSettings: func() Packet { return &AdventureSettings{} },
IDPlayerInput: func() Packet { return &PlayerInput{} },
IDSetPlayerGameType: func() Packet { return &SetPlayerGameType{} },
IDMapInfoRequest: func() Packet { return &MapInfoRequest{} },
IDRequestChunkRadius: func() Packet { return &RequestChunkRadius{} },
IDCommandRequest: func() Packet { return &CommandRequest{} },
IDCommandBlockUpdate: func() Packet { return &CommandBlockUpdate{} },
IDResourcePackChunkRequest: func() Packet { return &ResourcePackChunkRequest{} },
IDStructureBlockUpdate: func() Packet { return &StructureBlockUpdate{} },
IDPurchaseReceipt: func() Packet { return &PurchaseReceipt{} },
IDPlayerSkin: func() Packet { return &PlayerSkin{} },
IDSubClientLogin: func() Packet { return &SubClientLogin{} },
IDAutomationClientConnect: func() Packet { return &AutomationClientConnect{} },
IDBookEdit: func() Packet { return &BookEdit{} },
IDNPCRequest: func() Packet { return &NPCRequest{} },
IDModalFormRequest: func() Packet { return &ModalFormRequest{} },
IDServerSettingsRequest: func() Packet { return &ServerSettingsRequest{} },
IDSetDefaultGameType: func() Packet { return &SetDefaultGameType{} },
IDLabTable: func() Packet { return &LabTable{} },
IDSetLocalPlayerAsInitialised: func() Packet { return &SetLocalPlayerAsInitialised{} },
IDNetworkStackLatency: func() Packet { return &NetworkStackLatency{} },
IDScriptCustomEvent: func() Packet { return &ScriptCustomEvent{} },
IDLecternUpdate: func() Packet { return &LecternUpdate{} },
IDClientCacheStatus: func() Packet { return &ClientCacheStatus{} },
IDMapCreateLockedCopy: func() Packet { return &MapCreateLockedCopy{} },
IDStructureTemplateDataResponse: func() Packet { return &StructureTemplateDataResponse{} },
IDClientCacheBlobStatus: func() Packet { return &ClientCacheBlobStatus{} },
IDEmote: func() Packet { return &Emote{} },
IDMultiPlayerSettings: func() Packet { return &MultiPlayerSettings{} },
IDSettingsCommand: func() Packet { return &SettingsCommand{} },
IDAnvilDamage: func() Packet { return &AnvilDamage{} },
IDPlayerAuthInput: func() Packet { return &PlayerAuthInput{} },
IDItemStackRequest: func() Packet { return &ItemStackRequest{} },
IDUpdatePlayerGameType: func() Packet { return &UpdatePlayerGameType{} },
IDEmoteList: func() Packet { return &EmoteList{} },
IDPositionTrackingDBClientRequest: func() Packet { return &PositionTrackingDBClientRequest{} },
IDDebugInfo: func() Packet { return &DebugInfo{} },
IDPacketViolationWarning: func() Packet { return &PacketViolationWarning{} },
IDFilterText: func() Packet { return &FilterText{} },
IDCreatePhoto: func() Packet { return &CreatePhoto{} },
IDPhotoInfoRequest: func() Packet { return &PhotoInfoRequest{} },
IDSubChunkRequest: func() Packet { return &SubChunkRequest{} },
IDScriptMessage: func() Packet { return &ScriptMessage{} },
IDCodeBuilderSource: func() Packet { return &CodeBuilderSource{} },
IDRequestAbility: func() Packet { return &RequestAbility{} },
IDRequestPermissions: func() Packet { return &RequestPermissions{} },
IDEditorNetwork: func() Packet { return &EditorNetwork{} },
IDRequestNetworkSettings: func() Packet { return &RequestNetworkSettings{} },
IDGameTestResults: func() Packet { return &GameTestResults{} },
IDOpenSign: func() Packet { return &OpenSign{} },
}
for id, pk := range clientOriginating {
RegisterPacketFromClient(id, pk)
}
}

0 comments on commit 03802fe

Please sign in to comment.