Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong expectedSizeInWords calculation throws exception #375

Open
biemster opened this issue Jan 24, 2025 · 0 comments
Open

Wrong expectedSizeInWords calculation throws exception #375

biemster opened this issue Jan 24, 2025 · 0 comments

Comments

@biemster
Copy link

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant