Skip to content

Let callers supply their own dialer - #71

Open
christhomas wants to merge 1 commit into
secsy:masterfrom
antimatter-studios:cth/dial-func
Open

Let callers supply their own dialer#71
christhomas wants to merge 1 commit into
secsy:masterfrom
antimatter-studios:cth/dial-func

Conversation

@christhomas

Copy link
Copy Markdown

Adds Config.DialFunc. When set, it opens every TCP connection the client makes — control connections and data connections alike — in place of the package's own dialer.

Why

Some callers need the connection itself, not only what travels over it:

  • counting bytes for transfer statistics
  • routing through a proxy or a tunnel
  • returning something that is not really a socket

There is currently no way to reach the connection, so those callers cannot use this package at all. That is what brought me here — I need per-mount byte accounting for an FTP-backed filesystem, and every other option meant giving up this library.

What it does

Both dial sites now funnel through one Config.dial helper, and that is the part worth having. A caller who supplied a dialer to count bytes and only saw the control connection would miss every transfer — nearly all the bytes there are — and would have no way to tell.

Two things become the caller's responsibility, documented on the field because neither is guessable:

  • Config.Timeout no longer applies to the dial. It still governs reads and writes on the returned connection.
  • In TLSImplicit mode the returned connection is wrapped in TLS by this package, so DialFunc should return a plain connection. The handshake happens at dial time rather than on first read, matching what tls.DialWithDialer does.

Leaving DialFunc nil keeps the previous behaviour exactly, including the timeout.

Tests

The new tests need no server: both call sites go through Config.dial, so exercising that covers them by construction.

They did not run at first, though. TestMain stops with log.Fatal when ./build_test_server.sh has not been run, so on a machine that cannot build pure-ftpd and proftpd no test runs at all — including the ones that do not care.

GOFTP_SKIP_SERVERS now skips the four tests that need a live server and runs the rest. The skip is on the tests themselves rather than in the runner, so they report as skipped with a reason instead of failing on a connection refused — which reads like a bug in the package rather than a missing prerequisite.

Happy to drop that second part into its own PR if you would rather keep this to the feature.

Adds Config.DialFunc. When set, it opens every TCP connection the client
makes — control connections and data connections alike — in place of the
package's own dialer.

It exists for callers who need the connection itself rather than only
what travels over it: counting bytes for transfer statistics, routing
through a proxy or a tunnel, or returning something that is not really a
socket. Today there is no way to reach the connection, so those callers
cannot use this package at all.

Both dial sites now funnel through one Config.dial helper, which is what
makes the data connections reachable too. That is the part worth having:
a caller who supplied a dialer to count bytes and only saw the control
connection would miss every transfer, which is nearly all the bytes
there are, and would have no way to tell.

Two things become the caller's responsibility, documented on the field
because neither is guessable:

- Config.Timeout no longer applies to the dial. It still governs reads
  and writes on the returned connection.
- In TLSImplicit mode the returned connection is wrapped in TLS by this
  package, so DialFunc should return a plain connection. The handshake
  happens at dial time rather than on first read, matching what
  tls.DialWithDialer does.

Leaving DialFunc nil keeps the previous behaviour exactly, including the
timeout.

## Running the tests without the test servers

The new tests need no server: both call sites go through Config.dial, so
exercising that covers them by construction. But the package's TestMain
stops with log.Fatal when ./build_test_server.sh has not been run, so on
a machine that cannot build pure-ftpd and proftpd no test runs at all —
including the ones that do not care.

GOFTP_SKIP_SERVERS now skips the four tests that need a live server and
runs the rest. Skipping is on the tests themselves rather than the
runner, so they report as skipped with the reason rather than failing on
a connection refused, which reads like a bug in the package instead of a
missing prerequisite.
@christhomas

Copy link
Copy Markdown
Author

I've since found #55, which proposes the same thing — same field name, same two dial sites — and predates this by some years. Credit to @machship-mm; I didn't see it before opening this. If you'd rather take that one, this can close.

Two differences, in case they're useful either way:

Implicit TLS. #55 replaces the TLS dial when DialFunc is set, so a caller who asked for TLSMode: TLSImplicit gets a plain connection unless their own DialFunc performs the handshake. This branch dials via DialFunc and then wraps in tls.Client, so TLSImplicit is honoured either way and the caller's dialer stays a plain dialer. (I hit this in a downstream driver: it had to handshake inside its dial function to keep byte counting below the TLS layer, which works but means the setting no longer means what it says.)

One dial path. Both sites go through a single Config.dial helper rather than an if/else in each, which is why the data connections are covered by construction rather than by remembering to add the second branch.

This branch also has tests — the ones for Config.dial need no server, which matters because TestMain currently can't start without the compiled ones (see #73).

Either way, the point @rpetti made in #50 is the one worth preserving: a hook that only covers the control connection doesn't help, because FTP negotiates data connections separately. Both #55 and this cover both.

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

Successfully merging this pull request may close these issues.

1 participant