sunrpc is a python library implementing RFC1057. The library from this repository is based on code snippets from various different other repositories. See the references section at the end of the document for a list of resources. The library is not complete yet and some operations and message types might be missing. However, it should already be sufficient to build and access simple RPC services like for example portmappers.
sunrpc can be installed via pip by running the following command:
[user@host ~]$ pip3 install --user sunrpc
Alternatively you can build the package from source running:
[user@host ~]$ pip3 install --user --upgrade build
[user@host ~]$ git clone https://github.com/usdAG/sunrpc
[user@host ~]$ cd sunrpc && python3 -m build
[user@host ~/sunrpc]$ pip3 install --user dist/*.whl
Creating servers and clients with sunrpc is pretty simple. The library uses an annotation based approach for creating the required RPC methods and abstracts the implementation details from the user. The following listing contains an example for an simple Hello World RPC Server:
from __future__ import annotations
from sunrpc.types import RpcStr
from sunrpc.server import TCPServer, rpc_server_obtain, rpc_server_return
class HelloWorldServer(TCPServer):
'''
RPC server that supports a single method expecting a string. Prints
the string to stdout and answers with 'Hello World :D'.
'''
def __init__(self, host: str, port: str) -> None:
'''
Initialize the server and add the available method
'''
# 1337 is the programm number and 2 the version number supported by the server
TCPServer.__init__(self, host, port, 1337, 2)
# assign the procedure number 1 for self.hello_world
self.add_method(1, self.hello_world)
# define the hello_world RPC method that expects an string as input argument
# and returns a string back to the client.
@rpc_server_obtain(RpcStr)
@rpc_server_return(RpcStr)
def hello_world(self, message: str) -> str:
'''
Obtain the message from the client, print it and return 'Hello World :D'
'''
print(f'[+] The client said: {message}')
return ['Hello World :D']
# create the server and start it. Calls are handeled automatically from here
server = HelloWorldServer('127.0.0.1', 4444)
server.bind()
server.listen()
The rpc_obtain_server
and rpc_return_server
decorators define the expected incoming argument
types and return values for RPC methods. Each RPC methods need to be registered within the
constructor and gets assigned a unique procedure ID. The client side code uses rpc_obtain_client
and rpc_send_client
to call RPC methods:
from __future__ import annotations
from sunrpc.types import RpcStr
from sunrpc.client import TCPClient, rpc_client_send, rpc_client_obtain
class HelloWorldClient(TCPClient):
'''
Client class that communicates to the HelloWorldServer.
'''
# hello_world calls the RPC method with procedure number 1 and sends one
# RpcStr as argument. The result is an RpcStr returned by the server that
# gets passed to the hello_world method.
@rpc_client_send(1, RpcStr)
@rpc_client_obtain(RpcStr)
def hello_world(self, result: str) -> str:
'''
Send a string to the server and print the result.
'''
return result
# 1337 is the programm number and 2 the version number supported by the sever
client = HelloWorldClient('127.0.0.1', 4444, 1337, 2)
client.connect()
response = client.hello_world('Hello :D')
print(response)
For more complex usage examples, check the portmapper-client and portmapper-server implementations from the examples folder. There is also an example for building a server with asnycio.
The sunrpc.proxy
namespace contains methods and classes for building RPC proxies. The following example
demonstrates how to write a portmapper proxy, that forwards incoming portmap requests to the specified
target:
from sunrpc.proxy import UDPProxy
class PortMapperProxy(UDPProxy):
'''
Example implementation for a portmapper proxy. Proxies traffic incoming portmapper
traffic to the targeted portmapper and dumps transmitted data to stdout.
'''
def __init__(self, host: str, port: str, target: str, tport: int = 111) -> None:
'''
Initialize the proxy
'''
UDPProxy.__init__(self, host, port, 100000, 2, target, tport, dump = True)
# Just create a dummy proxy from localhost:4444 to localhost:111
server = PortMapperProxy('127.0.0.1', 4444, '127.0.0.1', 111)
server.bind()
print('[+] Portmapper proxy started :)')
server.listen()
When dump
is set to True
, a hexdump is created for each forwarded message. Moreover,
hooks can be implemented to modify certain RPC requests.
The initial rpc implementation we used for this library can be found within the following repositories: