Skip to content

Wrong expectedSizeInWords calculation throws exception #375

Open
@biemster

Description

@biemster

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions