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¶
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.
-
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.
-
enable_forwarding
()[source]¶ Enables forwarding. Packet handlers in the
Upstream
andDownstream
cease to be called, and all packets are routed via theBridge
. This method is called byupstream_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 callspacket_unhandled()
if no such methods exists. You might want to override this to implement your own dispatch logic or logging.
-