Writing a Proxy

A quarry proxy has five main parts:

Class Superclass Description
DownstreamFactory ServerFactory Spawns Downstream objects for connecting clients
Downstream ServerProtocol Connection with an external client
Bridge PacketDispatcher Forwards packets between the up/downstream
UpstreamFactory ClientFactory Spawns an Upstream
Upstream ClientProtocol Connection with an external server

In ASCII art:

+--------+       +--------------------------------+       +--------+
| mojang | ----> |              QUARRY            | ----> | mojang |
| client | <---- | downstream | bridge | upstream | <---- | server |
+--------+       +--------------------------------+       +--------+

Typically the Bridge and DownstreamFactory are customized.

When the user connects, the DownstreamFactory creates a Downstream object to communicate with the external client. If we’re running in online-mode, we go through server-side auth with mojang.

Once the user is authed, we spawn a UpstreamFactory, which makes a connection to the external server and spawns an Upstream to handle it. If requested we go through client-side auth.

At this point both endpoints of the proxy are authenticated and switched to “play” mode. The Bridge assumes responsibility for passing packets between the endpoints. Proxy business logic is typically implemented by defining packet handlers in a Bridge subclass, much like in client and server protocols. Unlike clients and servers, the method name must include the packet direction before its name, e.g.:

# Hook server-to-client keep alive
def packet_upstream_tab_complete(self, buff):
    # Unpack the packet
    p_text = buff.unpack_string()

    # Do a custom thing
    if p_text.startswith("/msg"):
        return # Drop the packet

    # Forward the packet
    buff.restore()
    self.upstream.send_packet("tab_complete", buff.read())

If a packet is hooked but not explicitly forwarded it is effectively dropped. Unhooked packets are handled by Bridge.packet_unhandled(), which forwards packets by default.

Skeleton Proxy

The proxy below will do online-mode authentication with a client connecting on port 25565, then connect in offline mode to a server running on port 25566 and begin exchanging packets via the bridge.

from twisted.internet import reactor
from quarry.net.proxy import DownstreamFactory, Bridge


class ExampleBridge(Bridge):
    pass


def main(argv):
    factory = DownstreamFactory()
    factory.bridge_class = ExampleBridge
    factory.connect_host = "127.0.0.1"
    factory.connect_port = 25566
    factory.listen("127.0.0.1", 25565)
    reactor.run()


if __name__ == "__main__":
    import sys
    main(sys.argv[1:])

Downstream Factories

class quarry.net.proxy.DownstreamFactory[source]

Subclass of quarry.net.server.ServerFactory. Additional attributes:

bridge_class = <class 'quarry.net.proxy.Bridge'>
connect_host = None
connect_port = None

Bridges

class quarry.net.proxy.Bridge(downstream_factory, downstream)[source]

This class exchanges packets between the upstream and downstream.

upstream_factory_class = <class 'quarry.net.proxy.UpstreamFactory'>
log_level = 20
logger = None
downstream_factory = None
downstream = None
upstream_profile = None
upstream_factory = None
upstream = None
make_profile()[source]

Returns the profile to use for the upstream connection. By default, use an offline profile with the same display name as the remote client.

connect()[source]

Connect to the remote server.

downstream_ready()[source]

Called when the downstream is waiting for forwarding to begin. By default, this method begins a connection to the remote server.

upstream_ready()[source]

Called when the upstream is waiting for forwarding to begin. By default, enables forwarding.

downstream_disconnected()[source]

Called when the connection to the remote client is closed.

upstream_disconnected()[source]

Called when the connection to the remote server is closed.

enable_forwarding()[source]

Enables forwarding. Packet handlers in the Upstream and Downstream cease to be called, and all packets are routed via the Bridge. This method is called by upstream_ready() by default.

enable_fast_forwarding()[source]

Enables fast forwarding. Quarry passes network data between endpoints without decoding packets, and therefore all packet handlers cease to be called. Both parts of the proxy must be operating at the same compression threshold. This method is not called by default.

packet_received(buff, direction, name)[source]

Called when a packet is received a remote. Usually this method dispatches the packet to a method named packet_<direction>_<packet name>, or calls packet_unhandled() if no such methods exists. You might want to override this to implement your own dispatch logic or logging.

packet_unhandled(buff, direction, name)[source]

Called when a packet is received that is not hooked. The default implementation forwards the packet.