Skip to content

Add SNI support#708

Merged
LukeButters merged 4 commits intomainfrom
luke/sni
Mar 29, 2026
Merged

Add SNI support#708
LukeButters merged 4 commits intomainfrom
luke/sni

Conversation

@LukeButters
Copy link
Copy Markdown
Contributor

@LukeButters LukeButters commented Mar 26, 2026

AI Background

ref CLOUDPT-11160
ref EFT-3141

Add ForceResolveHost to ServiceEndPoint for SNI-preserving address override

Background: TLS and SNI

When a client opens a TLS (HTTPS) connection, it performs a handshake before any application data is exchanged. Part of that handshake is the client announcing — in plaintext — which hostname it wants to talk to. This field is called Server Name Indication (SNI). The
server uses SNI to decide which TLS certificate to present, which is essential when many hostnames are served from the same IP address.

The key point: the hostname used for SNI is separate from the IP address the TCP connection is made to. DNS normally ties these together (you look up the hostname, get an IP, connect, and also use that hostname for SNI), but they are fundamentally independent.

Background: curl's --resolve

curl has a flag that exploits this separation:

curl --resolve polling.example.com:10943:127.0.0.1 https://polling.example.com:10943

This tells curl: "when you need to TCP-connect to polling.example.com:10943, use 127.0.0.1 instead of doing a real DNS lookup — but still send polling.example.com as the SNI hostname in the TLS handshake."

This is useful when you want to route traffic through a local tool (like a proxy or traffic shaper) while ensuring the remote server still sees the correct SNI and can respond with the right certificate.

The problem

Halibut derives both the TCP connect address and the TLS SNI hostname from ServiceEndPoint.BaseUri. There was no way to connect to a different address (e.g. a local Toxiproxy instance) while keeping the correct SNI hostname, without corrupting BaseUri to point at the
proxy — which misrepresents the real remote endpoint.

This matters in testing scenarios where Toxiproxy sits in front of a remote server to simulate network conditions (latency, disconnects, bandwidth limits). The TLS layer is still terminated at the real remote server, so SNI must match the real hostname.

This change

Adds a ForceResolveHost property to ServiceEndPoint:

var endpoint = new ServiceEndPoint(
"https://polling.example.com:10943", // real remote — used for SNI
remoteThumbprint,
halibutTimeoutsAndLimits);

endpoint.ForceResolveHost = new Uri("https://127.0.0.1:7778"); // connect here instead

When set, TcpConnectionFactory uses ForceResolveHost's host and port for the TCP connection, while the TLS SNI target remains BaseUri.Host. BaseUri continues to accurately represent the real remote endpoint for logging, identification, and certificate validation
purposes.

This is the Halibut equivalent of curl --resolve.

How to review this PR

Quality ✔️

Pre-requisites

  • I have read How we use GitHub Issues for help deciding when and where it's appropriate to make an issue.
  • I have considered informing or consulting the right people, according to the ownership map.
  • I have considered appropriate testing for my change.

@LukeButters LukeButters requested a review from a team as a code owner March 26, 2026 23:59
Copy link
Copy Markdown
Contributor

@rhysparry rhysparry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM

@LukeButters LukeButters enabled auto-merge (squash) March 29, 2026 23:04
@LukeButters LukeButters merged commit 4321751 into main Mar 29, 2026
17 checks passed
@LukeButters LukeButters deleted the luke/sni branch March 29, 2026 23:11
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.

2 participants