mic.git


mic

~ proxy for JA4-TLS fingerprinting evasion
git clone https://github.com/djnnvx/mic.git
log | files | refs | archive | README | LICENSE

commit 90c475b670f7eb8d9a6a55758d5ff87932fe2c36
parent 2896ba9819f94149d962e60c4e6dda742ad2722e
Author: djnn <[email protected]>
Date: Sun, 17 May 2026 18:44:45 +0900

fix(net): ipv6-safe host parsing)

Diffstat:
Mproxy/https.go | 26++++++++++++++++----------
Aproxy/https_test.go | 46++++++++++++++++++++++++++++++++++++++++++++++
Mproxy/proxy.go | 8++++----

3 files changed, 66 insertions(+), 14 deletions(-)

diff --git a/proxy/https.go b/proxy/https.go
@@ -6,9 +6,23 @@ import (
 	"log"
 	"net"
 	"net/http"
-	"strings"
 )
 
+// parseAuthority extracts the server name and a dial-ready "host:port" from
+// a CONNECT request's authority. If the input lacks an explicit port (rare —
+// CONNECT requires one), 443 is assumed. IPv6 literals in brackets are
+// handled (e.g. "[::1]:443" → "::1", "[::1]:443").
+func parseAuthority(authority string) (server, hostPort string) {
+	if h, _, err := net.SplitHostPort(authority); err == nil {
+		return h, authority
+	}
+	server = authority
+	if n := len(server); n >= 2 && server[0] == '[' && server[n-1] == ']' {
+		server = server[1 : n-1]
+	}
+	return server, net.JoinHostPort(server, "443")
+}
+
 func HttpsHandler(clientConn net.Conn, p *Proxy) {
 	defer clientConn.Close()
 
@@ -24,10 +38,7 @@ func HttpsHandler(clientConn net.Conn, p *Proxy) {
 		return
 	}
 
-	host := req.URL.Host
-	if !strings.Contains(host, ":") {
-		host = host + ":443"
-	}
+	serverName, host := parseAuthority(req.URL.Host)
 
 	log.Printf("Connecting to target: %s", host)
 	targetConn, err := p.dialTarget(host)
@@ -40,11 +51,6 @@ func HttpsHandler(clientConn net.Conn, p *Proxy) {
 	clientConn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
 
 	if p.LocalCA != nil {
-		serverName := host
-		if idx := strings.LastIndex(host, ":"); idx != -1 {
-			serverName = host[:idx]
-		}
-
 		cert, err := p.LocalCA.issueCert(serverName)
 		if err != nil {
 			log.Printf("Failed to issue cert for %s: %v", serverName, err)
diff --git a/proxy/https_test.go b/proxy/https_test.go
@@ -0,0 +1,46 @@
+package proxy
+
+import "testing"
+
+func TestParseAuthority(t *testing.T) {
+	cases := []struct {
+		name         string
+		in           string
+		wantServer   string
+		wantHostPort string
+	}{
+		{"hostname with explicit port", "example.com:443", "example.com", "example.com:443"},
+		{"hostname with non-443 port", "localhost:3000", "localhost", "localhost:3000"},
+		{"hostname with high port", "api.internal:8443", "api.internal", "api.internal:8443"},
+		{"bare hostname defaults to 443", "example.com", "example.com", "example.com:443"},
+		{"bare localhost defaults to 443", "localhost", "localhost", "localhost:443"},
+
+		{"subdomain with port", "www.example.com:443", "www.example.com", "www.example.com:443"},
+		{"subdomain with non-443 port", "api.example.com:8080", "api.example.com", "api.example.com:8080"},
+		{"deep subdomain with port", "a.b.c.example.com:9000", "a.b.c.example.com", "a.b.c.example.com:9000"},
+		{"bare subdomain defaults to 443", "www.example.com", "www.example.com", "www.example.com:443"},
+		{"bare deep subdomain defaults to 443", "a.b.c.example.com", "a.b.c.example.com", "a.b.c.example.com:443"},
+		{"hyphenated hostname with port", "my-host-1.example.com:8443", "my-host-1.example.com", "my-host-1.example.com:8443"},
+
+		{"IPv4 with explicit port", "127.0.0.1:8080", "127.0.0.1", "127.0.0.1:8080"},
+		{"IPv4 with 443", "10.0.0.1:443", "10.0.0.1", "10.0.0.1:443"},
+		{"bare IPv4 defaults to 443", "10.0.0.1", "10.0.0.1", "10.0.0.1:443"},
+
+		{"IPv6 bracketed with port", "[::1]:443", "::1", "[::1]:443"},
+		{"IPv6 bracketed with non-443 port", "[fe80::1]:8080", "fe80::1", "[fe80::1]:8080"},
+		{"IPv6 bracketed without port", "[::1]", "::1", "[::1]:443"},
+		{"IPv6 full address bracketed with port", "[2001:db8::1]:9000", "2001:db8::1", "[2001:db8::1]:9000"},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			gotServer, gotHostPort := parseAuthority(tc.in)
+			if gotServer != tc.wantServer {
+				t.Errorf("server: got %q, want %q", gotServer, tc.wantServer)
+			}
+			if gotHostPort != tc.wantHostPort {
+				t.Errorf("hostPort: got %q, want %q", gotHostPort, tc.wantHostPort)
+			}
+		})
+	}
+}
diff --git a/proxy/proxy.go b/proxy/proxy.go
@@ -6,7 +6,6 @@ import (
 	"io"
 	"log"
 	"net"
-	"strings"
 	"sync"
 
 	utls "github.com/bogdanfinn/utls"
@@ -37,9 +36,10 @@ func (p *Proxy) dialTarget(host string) (*utls.UConn, error) {
 		helloID = p.Fingerprint.ClientHelloID()
 	}
 
-	serverName := host
-	if idx := strings.LastIndex(host, ":"); idx != -1 {
-		serverName = host[:idx]
+	serverName, _, err := net.SplitHostPort(host)
+	if err != nil {
+		tcpConn.Close()
+		return nil, fmt.Errorf("dialTarget: parse host %q: %w", host, err)
 	}
 
 	cfg := &utls.Config{ServerName: serverName}