quarry icon indicating copy to clipboard operation
quarry copied to clipboard

Sending too much packets downstream causes the client to "Connection Lost" without any error message on the server.

Open vgalin opened this issue 4 years ago • 3 comments

Hello, after setting up a bridge and playing with maps (the item) and their content (the data displayed), I noticed that my client is "kicked" (Connection Lost) when too much packets are sent downstream too quickly.

There are no error messages either in the client's game output or in the bridge's logs.

I know that I may ask too much from the client or the bridge but I am wondering :

  • What is causing this issue ? Is it client side or bridge side ? I am unsure as there are no errors logged anywhere.
  • Is there a way to avoid being kicked without having to send less packets ?

How to reproduce the issue:

  • Create a new .py file with the following code:
from twisted.internet import reactor
from quarry.net.proxy import DownstreamFactory, Bridge

import random
from threading import Thread
import time

WHITE = 34
BLACK = 119

class CustomBridge(Bridge):

    animate = False

    def packet_upstream_chat_message(self, buff):
        buff.save()
        chat_message = self.read_chat(buff, "upstream")
            
        if chat_message.startswith("/anim"):
            Thread(target=self.test_animate_map).start()
        else:
            buff.restore()
            self.upstream.send_packet("chat_message", buff.read())

    def test_animate_map(self):
        if self.animate:
            return
        
        self.animate = True
        print('Starting animation...')
        for _ in range(100):
            # https://wiki.vg/Protocol#Map_Data
            map_id = 1
            scale = 0
            locked = False
            tracking_pos = False
            columns = 128
            rows = 128
            x, y = 0, 0
            length = 16384

            data = self.buff_type.pack_varint(map_id)
            data += self.buff_type.pack('B??', scale, locked, tracking_pos)
            data += self.buff_type.pack('B', columns)
            data += self.buff_type.pack('BBB', rows, x, y) 
            data += self.buff_type.pack_varint(length)
            
            # random noise
            mapdata = (random.choice([WHITE, BLACK]) for _ in range(length))
            data += self.buff_type.pack(f'{length}B', *mapdata)

            self.downstream.send_packet('map', data)
            time.sleep(0.10)

        self.animate = False
        print('Ended animation.')


    # from proxy_hide_chat.py
    def read_chat(self, buff, direction):
        buff.save()
        if direction == "upstream":
            p_text = buff.unpack_string()
            return p_text
        elif direction == "downstream":
            p_text = str(buff.unpack_chat())
            p_position = 0

            # 1.8.x+
            if self.upstream.protocol_version >= 47:
                p_position = buff.unpack('B')

            if p_position in (0, 1):
                return p_text

class QuietDownstreamFactory(DownstreamFactory):
    bridge_class = CustomBridge
    motd = "Custom Proxy Server"


def main(argv):
    # Parse options
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("-a", "--listen-host", default="", help="address to listen on")
    parser.add_argument("-p", "--listen-port", default=25565, type=int, help="port to listen on")
    parser.add_argument("-b", "--connect-host", default="127.0.0.1", help="address to connect to")
    parser.add_argument("-q", "--connect-port", default=25565, type=int, help="port to connect to")
    args = parser.parse_args(argv)

    # Create factory
    factory = QuietDownstreamFactory()
    factory.connect_host = args.connect_host
    factory.connect_port = args.connect_port

    # Listen
    factory.listen(args.listen_host, args.listen_port)
    reactor.run()


if __name__ == "__main__":
    import sys
    main(sys.argv[1:])
  • Run the script against an already running server (python scriptname.py -b SERVER_IP)
  • Connect to the bridge you just opened
  • Obtain the map that has the ID #1 (or change the map_id in the script) and hold it in your hand for example
  • Type /anim in the chat
  • Watch the map changing rapidly, and be eventually kicked due to Connection Lost.
  • (eventually change the value of time.sleep if you want to be kicked faster)

vgalin avatar Apr 14 '22 19:04 vgalin

IT IS NOT THE AMOUNT OF PACKETS

You cannot time.sleep in reactor, It will kick you because you are not sending or receiving packets for a extended time, This would not work.

BELOW IS FIRST ANSWER, but I just desided to write a fixed ver (lol)

You can try to use lib https://twistedmatrix.com/documents/9.0.0/api/twisted.internet.task.LoopingCall.html

from twisted.internet.task import LoopingCall

code:

            if not("lc" in self.data[self.downstream.uuid]):
                self.data[self.downstream.uuid]["lc"] = LoopingCall(self.loop)
                self.data[self.downstream.uuid]["lc"].start(0.3)

with the variable of time set to 0 on start, (looping call has a api for count IM BAD)

from quarry.net.proxy import DownstreamFactory, Bridge
from twisted.internet.task import LoopingCall
import struct as struct
import random
from threading import Thread
import time

WHITE = 34
BLACK = 119

class CustomBridge(Bridge):

    data = {}

    def packet_downstream_join_game(self,buff):
        self.start()
        buff.save()
        self.data[self.downstream.uuid]["ent_id"] = struct.unpack(">i",buff.read()[0:4])[0]
        buff.restore()
        print("NAME:"+self.downstream.display_name+" ENT ID:"+str(self.data[self.downstream.uuid]["ent_id"])+" Joined the game with uuid:"+str(self.downstream.uuid))
        self.downstream.send_packet("join_game",buff.read())


    def start(self):
        self.data[self.downstream.uuid] = {}
        self.data[self.downstream.uuid]["frame"] = 0
        self.data[self.downstream.uuid]["animate"] = False

        
    def packet_upstream_chat_message(self, buff):
        buff.save()
        chat_message = self.read_chat(buff, "upstream")
            
        if chat_message.startswith("/anim"):
            self.test_animate_map()
        else:
            buff.restore()
            self.upstream.send_packet("chat_message", buff.read())

    def ani_frame(self):
            # https://wiki.vg/Protocol#Map_Data
            #starts at 0 not 1
            map_id = 1
            scale = 0
            #locked was false should be true
            locked = True
            tracking_pos = False
            columns = 128
            rows = 128
            x, y = 0, 0
            length = 16384

            data = self.buff_type.pack_varint(map_id)
            data += self.buff_type.pack('B??', scale, locked, tracking_pos)
            data += self.buff_type.pack('B', columns)
            data += self.buff_type.pack('BBB', rows, x, y) 
            data += self.buff_type.pack_varint(length)
            
            # random noise
            mapdata = (random.choice([WHITE, BLACK]) for _ in range(length))
            data += self.buff_type.pack(f'{length}B', *mapdata)

            self.downstream.send_packet('map', data)

            if (self.data[self.downstream.uuid]["frame"] == 100):
                self.data[self.downstream.uuid]["animate"] = False
                self.data[self.downstream.uuid]["lc"].stop()
                print("anim end")
                
            self.data[self.downstream.uuid]["frame"]+=1

    def test_animate_map(self):
        if self.data[self.downstream.uuid]["animate"]:
            return

        print("Starting anim")
        self.data[self.downstream.uuid]["animate"] = True
        self.data[self.downstream.uuid]["frame"] = 0
        self.data[self.downstream.uuid]["lc"] = LoopingCall(self.ani_frame)
        self.data[self.downstream.uuid]["lc"].start(0.1)
        
        

    # from proxy_hide_chat.py
    def read_chat(self, buff, direction):
        buff.save()
        if direction == "upstream":
            p_text = buff.unpack_string()
            return p_text
        elif direction == "downstream":
            p_text = str(buff.unpack_chat())
            p_position = 0

            # 1.8.x+
            if self.upstream.protocol_version >= 47:
                p_position = buff.unpack('B')

            if p_position in (0, 1):
                return p_text

class QuietDownstreamFactory(DownstreamFactory):
    bridge_class = CustomBridge
    motd = "Custom Proxy Server"


def main(argv):
    # Parse options
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("-a", "--listen-host", default="", help="address to listen on")
    parser.add_argument("-p", "--listen-port", default=25565, type=int, help="port to listen on")
    parser.add_argument("-b", "--connect-host", default="127.0.0.1", help="address to connect to")
    parser.add_argument("-q", "--connect-port", default=25565, type=int, help="port to connect to")
    args = parser.parse_args(argv)

    # Create factory
    factory = QuietDownstreamFactory()
    factory.connect_host = args.connect_host
    factory.connect_port = args.connect_port

    # Listen
    factory.listen(args.listen_host, args.listen_port)
    reactor.run()


if __name__ == "__main__":
    import sys
    main(sys.argv[1:])```
    
   PLEASE RESPOND, I know its been two months but I relay hope I helped.

davidawesome02 avatar Jun 08 '22 01:06 davidawesome02

Thanks a lot for your answer. I'll try this when I can.

vgalin avatar Jun 08 '22 08:06 vgalin

If this worked for you you can close the issue after you test it.

davidawesome02 avatar Jun 08 '22 17:06 davidawesome02