diff --git a/cmd/probe/main.go b/cmd/probe/main.go
@@ -5,7 +5,6 @@ package main
import (
"bufio"
"context"
- "crypto/tls"
"encoding/json"
"fmt"
"net"
@@ -74,19 +73,16 @@ func probe(id utls.ClientHelloID) (string, error) {
}
defer uconn.Close()
- proto := uconn.ConnectionState().NegotiatedProtocol
-
- if proto == "h2" {
- // Use http2.Transport with the already-dialed connection.
- h2t := &http2.Transport{
- DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
- return dialUTLS(ctx, id)
- },
+ if uconn.ConnectionState().NegotiatedProtocol == "h2" {
+ // Reuse the already-established uconn rather than letting the Transport
+ // dial again — the first handshake is the one that produced the JA4 we
+ // want to measure.
+ cc, err := (&http2.Transport{}).NewClientConn(uconn)
+ if err != nil {
+ return "", err
}
- // Discard the first connection — the Transport dials its own.
- uconn.Close()
- client := &http.Client{Transport: h2t}
- resp, err := client.Get("https://tlsinfo.me/json")
+ req, _ := http.NewRequest("GET", "https://tlsinfo.me/json", nil)
+ resp, err := cc.RoundTrip(req)
if err != nil {
return "", err
}
@@ -94,7 +90,6 @@ func probe(id utls.ClientHelloID) (string, error) {
return decodeJA4(resp.Body)
}
- // http/1.1 path: reuse the already-established connection.
req, _ := http.NewRequest("GET", "/json", nil)
req.Host = "tlsinfo.me"
req.Header.Set("Connection", "close")
diff --git a/proxy/https.go b/proxy/https.go
@@ -77,9 +77,9 @@ func HttpsHandler(clientConn net.Conn, p *Proxy) {
defer tlsClient.Close()
log.Printf("MitM tunnel established for %s (proto: %s)", serverName, tlsClient.ConnectionState().NegotiatedProtocol)
- pipe(tlsClient, tlsClient, targetConn)
+ pipe(tlsClient, targetConn)
return
}
- pipe(clientConn, reader, targetConn)
+ pipe(&bufferedConn{r: reader, Conn: clientConn}, targetConn)
}
diff --git a/proxy/proxy.go b/proxy/proxy.go
@@ -1,6 +1,7 @@
package proxy
import (
+ "bufio"
"crypto/x509"
"fmt"
"io"
@@ -55,22 +56,32 @@ func (p *Proxy) dialTarget(host string) (*utls.UConn, error) {
return uconn, nil
}
-// pipe copies bidirectionally between client (reading from r) and target.
-// When either direction finishes it closes that side so the other goroutine unblocks.
-func pipe(client io.ReadWriteCloser, r io.Reader, target io.ReadWriteCloser) {
+// bufferedConn pairs a net.Conn with a bufio.Reader that may already contain
+// data read ahead from the underlying conn (e.g. after http.ReadRequest).
+// Reads come from the buffer; writes and Close go to the conn.
+type bufferedConn struct {
+ r *bufio.Reader
+ net.Conn
+}
+
+func (b *bufferedConn) Read(p []byte) (int, error) { return b.r.Read(p) }
+
+// pipe copies bidirectionally between a and b. When either direction finishes
+// it closes that side so the other goroutine unblocks.
+func pipe(a, b io.ReadWriteCloser) {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
- io.Copy(target, r)
- target.Close()
+ io.Copy(b, a)
+ b.Close()
}()
go func() {
defer wg.Done()
- io.Copy(client, target)
- client.Close()
+ io.Copy(a, b)
+ a.Close()
}()
wg.Wait()
diff --git a/proxy/serverfront.go b/proxy/serverfront.go
@@ -1,7 +1,6 @@
package proxy
import (
- "bufio"
"crypto/tls"
"fmt"
"log"
@@ -38,6 +37,6 @@ func ServerFrontHandler(certFile, keyFile string) (Handler, error) {
}
defer targetConn.Close()
- pipe(tlsConn, bufio.NewReader(tlsConn), targetConn)
+ pipe(tlsConn, targetConn)
}, nil
}
| | | |