-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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
net/http: support way to modify Request context data from net.Conn etc #20956
Comments
There's kinda a way for you to do it already. It's either gross or clever, but I'll leave that judgement up to you: The net/http server set the func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr()) Note that you control what Related issue #16220 was about letting people adjust the base context but it fizzled out due to lack of real need. Perhaps we could have an optional That would require some thought and design. I'll repurpose this bug about that if you don't mind. /cc @tombergan |
#18997 is going to add that support. But I don't think that well help in this example, because the hook doesn't get the |
@tombergan, the desire to get at "the" conn never goes away, so I think it's time, with sufficiently scary names and docs. For instance, for TLS connections, do we give them both the raw connection and the TLS wrapper conn? With what names? // RawEncryptedConn is the bottommost net.Conn, under the TLS connection if any.
// It's usually of type *net.TCPConn, unless you have a weird setup.
// You must not assume you can read or write from it, since it's likely wrapped in TLS,
// and you don't know which HTTP protocol you're speaking. Oh, you also shouldn't
// Close it if you don't want to break multiple requests in flight. We make no promises
// about what will happen with this if you Read, Write, or Close it, and we reserve
// the right to break your code if you do so. This is provided so you can get at things
// you need to get at. We trust you to make good life decisions.
// Enjoy.
RawEncryptedConn net.Conn Or something. |
@bradfitz My suggestion would be to call it RawConn or something, so as to not make it specifically about encrypted connections. Besides that, I do like your comment :) How can we get this ball rolling again? |
I just got back to work today after some months of paternity leave. Not coincidentally, today is also the Go 1.10 feature freeze date, so this will need to be pumped to Go 1.11. |
@Freeaqingme if you are interested/available, would you like to spin up a CL to get the ball rolling? The usage and tests will give folks a palatable point to start with. /cc @meirf @rs just in case y'all need this functionality too or are interested. |
@Freeaqingme, was the hacky workaround I suggested in #20956 (comment) workable? That's not to say we can't do more here, if somebody wants to pick it up. |
This kinda fell off my radar swamped by other projects. Though there shouldn't be a reason for your suggestion not to work. I'll see if I have some this weekend to gobble up a CL with a method to more conveniently retrieve the underlying conn (which is not necessarily encrypted but can still get a very scary name) |
@bradfitz package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io"
"math/big"
"net"
"net/http"
"net/url"
)
func generateTLSConfig() *tls.Config {
key, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
panic(err)
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate(rand.Reader, &template,
&template, &key.PublicKey, key)
if err != nil {
panic(err)
}
keyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key)},
)
certPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certDER,
})
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
panic(err)
}
return &tls.Config{
Certificates: []tls.Certificate{tlsCert},
GetConfigForClient: storeClientSNI,
}
}
var (
tlsCfg = generateTLSConfig()
)
func storeClientSNI(chi *tls.ClientHelloInfo) (*tls.Config, error) {
conn := chi.Conn.(*hackedInnerConn)
conn.sni = chi.ServerName
return nil, nil
}
type hackedListener struct {
net.Listener
}
type hackedInnerConn struct {
net.Conn
sni string
}
type hackedOuterConn struct {
net.Conn
hic *hackedInnerConn
}
type hackedAddr struct {
net.Addr
hoc *hackedOuterConn
}
func (hc hackedOuterConn) LocalAddr() net.Addr {
return hackedAddr{
hc.Conn.LocalAddr(),
&hc,
}
}
func (hln hackedListener) Accept() (net.Conn, error) {
conn, err := hln.Listener.Accept()
if err != nil {
panic(err)
}
hoc := hackedOuterConn{}
hic := hackedInnerConn{
conn,
"",
}
hoc.Conn = tls.Server(&hic, tlsCfg)
hoc.hic = &hic
return hoc, nil
}
func startServer(addr string, handler http.Handler) {
netAddr, err := url.Parse(addr)
if err != nil {
panic(err)
}
server := &http.Server{
Handler: handler,
}
ln, err := net.Listen("tcp", netAddr.Host)
if err != nil {
panic(err)
}
hackedLn := hackedListener{
ln,
}
err = server.Serve(hackedLn)
if err != nil {
panic(err)
}
}
func mustWrite(w io.Writer, p []byte) {
_, err := w.Write(p)
if err != nil {
panic(err.Error())
}
}
func main() {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
haddr := r.Context().Value(http.LocalAddrContextKey).(hackedAddr)
mustWrite(w, []byte(haddr.hoc.hic.sni+"\r\n"))
mustWrite(w, []byte(r.Host+"\r\n"))
})
startServer("https://127.0.0.1:4443", handler)
} It can be tested via a curl command: |
If the But there is a question: |
As of Go 1.13, this exists: https://golang.org/pkg/net/http/#Server.ConnContext (see #30694) I'm pretty sure that solves these issues so closing this bug, but let me know if not. |
Hi,
I've got a connection that implements the net.Conn interface. It's a custom implementation that implements the proxyprotocol v2. Part of the protocol is that the header, besides original remote address can contain additional metadata about its original form (e.g. if ssl was used, what ciphers, etc. But also things like network namespace).
I'm using the standard net/http tooling, and it provides no direct access to the underlying net.conn. That's been discussed in multiple issues, so don't want to spark that discussion yet again. I am however looking for a way to access this metadata.
The only option I'm aware of that currently exists is to Hijack() the response. That's suboptimal, however, because the reason I'm using net/http is so I don't have to implement HTTP myself :) (and there's no way to Unhijack() the connection again).
Unless there already is a workaround available that I'm not aware of, please do consider this a feature request for a way of transferring data from the underlying net.Conn to the http.Request object.
Thanks,
Dolf
//cc @bradfitz
The text was updated successfully, but these errors were encountered: