Opened 8 years ago

Closed 6 years ago

#208 closed defect (fixed)

accept the null connection hints

Reported by: Zooko Owned by:
Priority: major Milestone: 0.9.0
Component: network Version: 0.6.4
Keywords: Cc:

Description

Sometimes you don't want your foolscap tub to be reachable by any other tub (you'll use it only for outgoing connections, possibly because of some anonymity layer like Tor or I2P). In that case, you want to pass the empty string, or some other symbol meaning the null set, for the connection hints. Currently (I'm told) foolscap won't accept this.

Change History (8)

comment:1 Changed 8 years ago by Zooko

If this were fixed, then we could start passing empty string instead of unreachable.example.com:0 for the tub.location = UNREACHABLE setting of Tahoe-LAFS, as described in https://tahoe-lafs.org/trac/tahoe-lafs/ticket/1010#comment:39.

comment:2 Changed 7 years ago by Brian Warner

Zooko asked in IRC what my favorite API would be. I think it comes down to one of three options:

  • 1: tub.setLocation(None)
  • 2: tub.setLocation(foolscap.api.UNREACHABLE) (or other distinctive marker object)
  • 3: not calling tub.setLocation() at all

and Zooko seemed in favor of 3. I like the purity of that, but I'm worried about discoverability: if you just forget to set a location, but needed one, then at what point do you discover your mistake? I'm slightly against 2 (setLocation(None)) for that reason: it's fairly easy to get the initialization code shuffled in a way that uses None instead of a real string, and that should trigger an error rather than silently misinterpreting your intent.

Locations are necessary to generate FURLs that are hosted by this Tub, which happens when you call tub.registerReference(). It also happens when you send a local Referenceable over the wire (since the serialized form requires a FURL). Finally, it's also needed to provide the logport FURL, which can be retrieved explicitly (tub.getLogPortFurl), or delivered to the log gatherer if/when we connect (we don't initiate connections to the gatherer until the location has been set).

So forgetting to set the location will result in nicely-discoverable errors (albeit RuntimeErrors, which should be replaced with a specific exception class) from registerReference and callRemote calls that include local references in their argument graphs, and will cause the log gatherer to never be contacted (silently). That's probably ok.

It'd be nice to throw an error for the log gatherer, but that would impose an initialization-order constraint (setLocation must precede setLogGathererFURL), and I think we had some reason for not wanting to impose that constraint.

Another thing to consider: if you don't want the Tub to be reachable, really the way you indicate that is to never call tub.listenOn() (which is used to indicate the TCP port it should use to accept connections). No listening port == not reachable. The location is kind of secondary.

Maybe the better API would be to combine listenOn() and setLocation()? Calling one without the other doesn't entirely make sense, and calling neither might be the cleanest way to indicate a client-only (i.e. outbound-connection-only) Tub.

comment:3 Changed 7 years ago by dawuud

OK here's my solution: https://github.com/david415/foolscap/tree/208-accept-the-null-connection-hints

This is a very small and simple commit. If setLocation is called with UNREACHABLE then Tub set's it's self.unreachable to True... this causes method calls involving the location to throw an exception; so far my unit test tests for this behavior in registerReference and listenOn.

Please code review and let me know what I can do to help resolve this ticket.

comment:4 Changed 7 years ago by Brian Warner

Milestone: undecided0.8.0

comment:5 Changed 6 years ago by Brian Warner

Milestone: 0.8.00.9.0

Thinking about this further.. if Alice's Tub is unreachable, and she connects to Bob's Tub, and she sends a reference over the wire to Bob, that should work, right? Bob should be able to send messages to her object as long as she's connected, but the FURL Bob gets for her object won't have any connection hints, and he won't be able to connect to it without an existing connection.

So at least the internal form of registerReference() should still work (the one that's done when you pass an object over the wire), but should get FURLs with empty connection hints.

Is that discoverable? I'm imagining someone who forgets to fill in a location in a tahoe.cfg, and gets a FURL that is missing hints, but because they don't know what to look for, they might not notice, and get confused when their friends's programs can't deal with it.

Should we use a separate registerReference function for explicit registrations (which throws an error for unreachable Tubs), but allow the implicit function to work without hints?

I really want to get this new crypto stuff out, so I think I'm going to punt this to the next release. But I'll try to make that release a small one, and get it out earlier.

comment:6 Changed 6 years ago by Brian Warner

Component: unknownnetwork

comment:7 Changed 6 years ago by Brian Warner

Ok, I'm now leaning towards (3): don't call tub.listenOn() or tub.setLocation() at all. I'm writing the docs, and all the other options are weird: "hey user, don't call tub.listenOn() because there's no listener, but still do call tub.setLocation(), but with some weird non-location, because otherwise your tub.registerReference() calls will fail, except why are you calling tub.registerReference() when the FURLs you get back will be unusable".

So in this mode, you also aren't allowed to call tub.registerReference(). If you do, and you forgot to call tub.setLocation(), then you'll get an error: that should provide the necessary discoverability.

This option will cause one problem with Tahoe, for the "internal" services (logport, controlport). We'll need code to notice that we're going client-only and refrain from doing registerReference on those services. The logport is managed by Foolscap (with tub.setOption("logport-furlfile", NODEDIR/private/logport.furl), so I guess we'll make the Tub write an empty file, or one with an empty location hint, or something. We may need a kwarg like registerReference(obj, _even_without_location=True), to allow the logport management code to use the same registerReference as normal user code.

Note that Tahoe is free to use something different in the tahoe.cfg syntax: omitting tub.port=, using tub.port = UNREACHABLE, using tub.port=, etc. I don't think the foolscap calling convention should have too much influence on that choice.

comment:8 Changed 6 years ago by Brian Warner

Resolution: fixed
Status: newclosed

I did some testing, and tub.setOptions("logport-furlfile") works just fine without a .setLocation(). Internally, the Tub is waiting for a location to be set before it writes the furlfile, so if we never call setLocation, the file never gets written.

Sending an object from an unreachable Tub causes the reference slicer to omit the URL of the object, so the far end creates a RemoteReferenceTracker without a URL. I added code to make sure that "lame gifts" (i.e. when this FURL-less RemoteReference gets sent to a third Tub) are delivered as unresolveable (their-reference) sequences, so any call which includes them will fail with a suitable NoLocationHintsError.

The code already did the right thing when you omit .listenOn()/.setLocation(), so I just added some tests and docs (in [7e536c98]). The gifts stuff went into [bed1fba0].

Note: See TracTickets for help on using tickets.