-
Notifications
You must be signed in to change notification settings - Fork 55
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
feat(transport): TLS StreamDialer & Dynamic HTTP CONNECT #117
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Suggested PR title: "feat(transport): TLS StreamDialer"
To add to this PR: reuse https://pkg.go.dev/crypto/tls#ClientSessionCache |
Hold on a bit. I want to move TLS to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! The code looks good, added some comments to double check.
|
||
var _ transport.StreamConn = (*streamConn)(nil) | ||
|
||
func (c streamConn) CloseRead() error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to override CloseWrite
to close both connections as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe tls.Conn already does that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but it won't call innerConn.CloseWrite()
, just wanna make sure this is expected.
... and does not call CloseWrite on the underlying connection ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you are right. I added the call to close the inner conn
@@ -48,9 +75,16 @@ func (h *connectHandler) ServeHTTP(proxyResp http.ResponseWriter, proxyReq *http | |||
} | |||
|
|||
// Dial the target. | |||
targetConn, err := h.dialer.Dial(proxyReq.Context(), proxyReq.Host) | |||
transportConfig := proxyReq.Header.Get("Transport") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we prefix the header name with sth like X-Outline-Transport
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That goes against the official current best practice: https://www.rfc-editor.org/rfc/rfc6648
We could consider Outline-Transport
though. It's longer, more things to type when I write command lines, but it could make it clear it's our header, in case we add multiple ones.
Co-authored-by: J. Yi <[email protected]>
FYI, I fixed the flaky test, because it was blocking my PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, I think we just need to double confirm the CloseWrite
behavior is expected (replied in the comment above).
This PR introduces the TLS transport. It also adds a way to dynamically specify a transport in the local proxy HTTP CONNECT.
This will help us explore domain-fronting, DNS-over-TLS and SOCKS5-over-TLS, and address SNI-based censorship.
Protocols over TLS
This works for DNS-over-TLS:
Given a SOCKS5 server
[host:port]
, you can do SOCKS5-over-TLS with:Domain Fronting
This now works for domain-fronting:
The example below shows the the bypass censorship of RFE/RL in a network Russia.
Running a local proxy (
KEY
is a config for a server in Russia):Fetching the page via the proxy in Russia and domain-fronting:
Note that I had to tell Curl to connect to
e4887.dscb.akamaiedge.net
in order to bypass DNS-based blocking. That forces the need to specify thecertName=www.rferl.org
in the transport, otherwise is tries to use the dialed address (e4887.dscb.akamaiedge.net
).This is not ideal. In the future I'd like to have a transport config that will map the domain to resolve and connect to. That way the user can keep dialing the intended destination. An example with a fictitious
fixed
dialer that always dials the same address could look like:We may want to introduce matchers based on host:port, do a allow a dialer to be used across endpoints. An example where we map
www.rferl.org
toe4887.dscb.akamaiedge.net
, and only changes the SNI for TLS connection could look like:Dynamic HTTP CONNECT
With the HTTP CONNECT proxy, we can now use the
Transport
header to specify a transport config that wraps the base dialer. So a tunnel request could look like:or
This allows the client to dynamically change the transport on a per-connection basis, so the transport selection logic can live in Flutter, for example. This also mitigates the issue of the transport config not working for every endpoint.
Below is the domain-fronting example above, modified to use dynamic transports.
Proxy (just connects to the Russian proxy, no bypass logic):
Request with domain-fronting: