Description
I'm still very new to capnproto so I might be wrong here, but it seems pycapnp is not able to properly work with the tunnelrpc.capnp
from cloudflared (I noticed many devs here are working for CF so I'm cutting a bit of intro context). When I try to create a tunnel using registerConnection
from the mentioned .capnp
I get the following error:
capnp.lib.capnp.KjException: capnp/serialize-async.c++:778: disconnected: expected expectedSizeInWords <= options.traversalLimitInWords [4234896083 <= 8388608]; incoming RPC message exceeds size limit
The calculated value for expectedSizeInWords changes for every new call of this function, leading me to believe it's calculating it from an uninitialized variable?
A minimal example to get to this exception is below:
#!/usr/bin/env python
import os
import asyncio
import requests
import json
from base64 import b64decode
import uuid
import dns.resolver
import socket
import ssl
import capnp
import tunnelrpc_capnp
TUNNEL_CONFIG = 'tunnel.json'
SRV_SERVICE = 'v2-origintunneld'
SRV_NAME = 'argotunnel.com'
HTTP2_SNI_HOST = 'h2.cftunnel.com'
CERT = 'cf_root.pem'
this_dir = os.path.dirname(os.path.abspath(__file__))
def tunnel_cfg():
if not os.path.exists(TUNNEL_CONFIG):
r = requests.post('https://api.trycloudflare.com/tunnel')
if r.ok:
res = json.loads(r.content)['result']
with open(TUNNEL_CONFIG, 'w') as f:
json.dump(res, f, indent=2)
print(f'Created tunnel {res["hostname"]}')
else:
print('Error creating Quick Tunnel')
exit(0)
return json.load(open(TUNNEL_CONFIG, 'r'))
def edge_discovery():
print(f'Discovering Edge')
dig = dns.resolver.resolve(f'_{SRV_SERVICE}._tcp.{SRV_NAME}', 'SRV')
edge_host = str(dig[0].target)[:-1]
edge_port = dig[0].port
for rdata in dig:
if rdata.priority == 1:
edge_host = str(rdata.target)[:-1]
edge_port = rdata.port
return edge_host, edge_port
async def edge_connect(edge_host, edge_port, cfg):
print(f'Connecting to Edge on {edge_host}:{edge_port}')
ctx = ssl.create_default_context(
ssl.Purpose.SERVER_AUTH, cafile=os.path.join(this_dir, CERT)
)
stream = await capnp.AsyncIoStream.create_connection(
edge_host, edge_port, ssl=ctx, server_hostname=HTTP2_SNI_HOST, family=socket.AF_INET
)
client = capnp.TwoPartyClient(stream)#, traversal_limit_in_words=2**63)
tunnelserver = client.bootstrap().cast_as(tunnelrpc_capnp.TunnelServer)
client_info = tunnelrpc_capnp.ClientInfo.new_message(
clientId=uuid.uuid4().bytes,
#features=["feature1", "feature2"],
#version="1.0.0",
#arch="x86_64"
)
connection_options = tunnelrpc_capnp.ConnectionOptions.new_message(
client=client_info,
originLocalIp=b"192.168.1.1",
replaceExisting=True,
compressionQuality=3,
numPreviousAttempts=0
)
tunnel_auth = tunnelrpc_capnp.TunnelAuth.new_message(
accountTag=cfg['account_tag'],
tunnelSecret=b64decode(cfg['secret'])
)
response = await tunnelserver.registerConnection(tunnel_auth, uuid.UUID(cfg['id']).bytes, 0, connection_options)
result = response.result
if result.which() == 'connectionDetails':
details = result.connectionDetails
print(f"Tunnel registered with UUID: {details.uuid}")
print(f"Location: {details.locationName}")
print(f"Remotely managed: {details.tunnelIsRemotelyManaged}")
else:
error = result.error
print(f"Registration failed: {error.cause}")
if error.shouldRetry:
print(f"Retry after: {error.retryAfter} ns")
async def main():
cfg = tunnel_cfg()
await edge_connect(*edge_discovery(), cfg)
if __name__ == '__main__':
asyncio.run(capnp.run(main()))
This works in capnp-rs, as can be seen in https://github.com/devsnek/cf-tunnel-rs.
https://github.com/devsnek/cf-tunnel-rs/blob/main/tunnelrpc.capnp
https://github.com/devsnek/cf-tunnel-rs/blob/main/cf_root.pem
Am I doing something wrong here, or is this a real issue?