feat(log): the launcher's log names the addresses each connection resolved to and tried, and how each ended #106

Merged
HumanBean merged 1 commit from madebyjordan/LongshotLauncher:feat/log-connections into main 2026-11-15 17:39:30 +00:00
Contributor

Players can't reach git.magicbean.dev from the launcher, and #93's log still leaves the main cause open. A timeout reads the same whether it's broken IPv6, an ISP dropping Cloudflare's addresses, or a firewall dropping packets. A TLS failure can't show whether DNS sent the name to a block page instead of Cloudflare (Longshot#738's "Cannot determine the frame size" is exactly that kind of answer). This puts the addresses in the log.

What changes

For each new connection, the launcher log now gets:

  • the name's addresses, as DNS gave them;
  • each address tried, in order, with how long it took and how it ended: connected, the socket error (ConnectionRefused, NetworkUnreachable, TimedOut…), or no answer, stopped when the request gave up.

A pooled connection that is used again isn't logged again.

17:35:29.662 DNS git-cdn.magicbean.dev: 172.67.170.160, 104.21.55.71 (17 ms)
17:35:29.720 connect git-cdn.magicbean.dev 172.67.170.160:443: connected in 39 ms
17:35:29.876 GET https://git-cdn.magicbean.dev/api/v1/repos/MagicBeanDev/Longshot/releases?limit=50&page=1: HTTP 200, 273 ms

On a PC with broken IPv6, this would read connect git-cdn.magicbean.dev [2606:4700:…]:443: no answer, stopped after …. On a DNS-filtered network, the DNS line shows an address that isn't Cloudflare's. The forge's addresses are 104.21.55.71, 172.67.170.160, 2606:4700:3032::6815:3747 and 2606:4700:3037::ac43:aaa0.

How

  • HttpFetcher uses a SocketsHttpHandler (what HttpClientHandler wraps) with Connections.ConnectAsync as its ConnectCallback.

  • It connects the way .NET's own connect does:

    • each address in turn, in the order DNS gives them, keeping the first that connects;
    • IPv6 only where Socket.OSSupportsIPv6 (so DOTNET_SYSTEM_NET_DISABLEIPV6=1 still works);
    • it throws .NET's own errors (the DNS SocketException, the last address's SocketException, or the timeout's cancellation).

    So NetFailure gives the same words as before: I checked refused, DNS, not-TLS and timeout through HttpFetcher.

  • The proxy is unchanged: the system's or HTTPS_PROXY. Through a proxy, the lines name the proxy's address.

  • This is logging only: no change to which addresses are tried, in what order, or for how long. Racing IPv4 against a slow IPv6 (Happy Eyeballs) would be a separate pull request.

  • One ordering quirk: .NET keeps a connect going after the request that asked for it has timed out. So a connect's no answer, stopped line can come a few seconds after its request's timed out line. I measured ~5 s on Windows. That's in the doc comment.

Tested (Windows 11, .NET SDK 10.0.404)

  • python -I tests/test_launcher_core.py: 1194 checks pass. The new ConnectionChecks.cs covers:
    • with a fake resolver and connect: IPv6 then IPv4 tried in order, each logged with how it ended; the address a timeout was spent on; every address failing throws the last one's SocketException; IPv6 left out when it isn't usable; a failed lookup is logged and still throws;
    • through HttpFetcher: .invalid still reads "could not look up … address"; a plain-HTTP server on 127.0.0.1 logs connected, then still fails as a secure-connection failure; refused port 1 logs ConnectionRefused.
  • dotnet build App -c Release -p:RestoreLockedMode=true -warnaserror: 0 warnings.
  • dotnet format --verify-no-changes on the changed files: clean.
  • The real update check, run through this branch's Core against the live servers, with a console harness and not the app (the log above). Also run against a blackholed address (10.255.255.1), where the connect line arrived ~5 s after the request's timeout.
  • Not run: mise run ci:smoke (no mise here); Linux.

🤖 Generated with Claude Code

Players can't reach git.magicbean.dev from the launcher, and #93's log still leaves the main cause open. A timeout reads the same whether it's broken IPv6, an ISP dropping Cloudflare's addresses, or a firewall dropping packets. A TLS failure can't show whether DNS sent the name to a block page instead of Cloudflare (Longshot#738's "Cannot determine the frame size" is exactly that kind of answer). This puts the addresses in the log. ## What changes For each **new** connection, the launcher log now gets: - the name's addresses, as DNS gave them; - each address tried, in order, with how long it took and how it ended: `connected`, the socket error (`ConnectionRefused`, `NetworkUnreachable`, `TimedOut`…), or `no answer, stopped` when the request gave up. A pooled connection that is used again isn't logged again. ``` 17:35:29.662 DNS git-cdn.magicbean.dev: 172.67.170.160, 104.21.55.71 (17 ms) 17:35:29.720 connect git-cdn.magicbean.dev 172.67.170.160:443: connected in 39 ms 17:35:29.876 GET https://git-cdn.magicbean.dev/api/v1/repos/MagicBeanDev/Longshot/releases?limit=50&page=1: HTTP 200, 273 ms ``` On a PC with broken IPv6, this would read `connect git-cdn.magicbean.dev [2606:4700:…]:443: no answer, stopped after …`. On a DNS-filtered network, the `DNS` line shows an address that isn't Cloudflare's. The forge's addresses are 104.21.55.71, 172.67.170.160, 2606:4700:3032::6815:3747 and 2606:4700:3037::ac43:aaa0. ## How - `HttpFetcher` uses a `SocketsHttpHandler` (what `HttpClientHandler` wraps) with `Connections.ConnectAsync` as its `ConnectCallback`. - **It connects the way .NET's own connect does:** - each address in turn, in the order DNS gives them, keeping the first that connects; - IPv6 only where `Socket.OSSupportsIPv6` (so `DOTNET_SYSTEM_NET_DISABLEIPV6=1` still works); - it throws .NET's own errors (the DNS `SocketException`, the last address's `SocketException`, or the timeout's cancellation). So `NetFailure` gives the same words as before: I checked refused, DNS, not-TLS and timeout through `HttpFetcher`. - **The proxy is unchanged:** the system's or `HTTPS_PROXY`. Through a proxy, the lines name the proxy's address. - **This is logging only:** no change to which addresses are tried, in what order, or for how long. Racing IPv4 against a slow IPv6 (Happy Eyeballs) would be a separate pull request. - **One ordering quirk:** .NET keeps a connect going after the request that asked for it has timed out. So a connect's `no answer, stopped` line can come a few seconds after its request's `timed out` line. I measured ~5 s on Windows. That's in the doc comment. ## Tested (Windows 11, .NET SDK 10.0.404) - `python -I tests/test_launcher_core.py`: 1194 checks pass. The new `ConnectionChecks.cs` covers: - with a fake resolver and connect: IPv6 then IPv4 tried in order, each logged with how it ended; the address a timeout was spent on; every address failing throws the last one's `SocketException`; IPv6 left out when it isn't usable; a failed lookup is logged and still throws; - through `HttpFetcher`: `.invalid` still reads "could not look up … address"; a plain-HTTP server on 127.0.0.1 logs `connected`, then still fails as a secure-connection failure; refused port 1 logs `ConnectionRefused`. - `dotnet build App -c Release -p:RestoreLockedMode=true -warnaserror`: 0 warnings. - `dotnet format --verify-no-changes` on the changed files: clean. - The real update check, run through this branch's Core against the live servers, with a console harness and not the app (the log above). Also run against a blackholed address (10.255.255.1), where the connect line arrived ~5 s after the request's timeout. - Not run: `mise run ci:smoke` (no mise here); Linux. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(log): the launcher's log names the addresses each connection resolved to and tried, and how each ended
Some checks are pending
CI / Compile and test (pull_request) Blocked by required conditions
CI / Conventional Commit title (pull_request) Blocked by required conditions
e87121d8d6
A timeout in a player's report read the same for broken IPv6, an ISP dropping Cloudflare's addresses
and a firewall dropping packets, and a TLS failure could not show whether DNS had sent the name to a
block page instead of Cloudflare. The log now has, for each new connection, the name's addresses and
each address tried with its time and end (connected, the socket error, or no answer when the request
gave up).

HttpFetcher uses a SocketsHttpHandler with Connections.ConnectAsync as its ConnectCallback, which
connects as .NET's own does: each address in turn, in the name service's order, IPv6 only where this
PC has it. The errors it throws are .NET's own, so NetFailure reads them as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
HumanBean deleted branch feat/log-connections 2026-11-15 17:39:31 +00:00
Some checks are pending
CI / Compile and test (pull_request) Blocked by required conditions
CI / Conventional Commit title (pull_request) Blocked by required conditions
Some workflows are waiting to be reviewed.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/LongshotLauncher!106
No description provided.