mic~ proxy for JA4-TLS fingerprinting evasion |
| git clone https://github.com/djnnvx/mic.git |
| log | files | refs | archive | README | LICENSE |
proxy.go (3580B)
1 package proxy 2 3 import ( 4 "bufio" 5 "crypto/x509" 6 "errors" 7 "fmt" 8 "io" 9 "log" 10 "net" 11 "sync" 12 "time" 13 14 utls "github.com/bogdanfinn/utls" 15 "github.com/djnnvx/mic/fingerprint" 16 ) 17 18 const ( 19 dialTimeout = 10 * time.Second 20 acceptRetryDelay = 5 * time.Millisecond 21 acceptMaxRetryDelay = 1 * time.Second 22 ) 23 24 type Proxy struct { 25 ListenAddr string 26 BackendAddr string // server-front: backend host:port 27 CAPool *x509.CertPool 28 Fingerprint *fingerprint.TLSFingerprint 29 LocalCA *LocalCA // client-front: MitM CA for TLS interception 30 Handler Handler 31 } 32 33 type Handler func(conn net.Conn, p *Proxy) 34 35 func (p *Proxy) dialTarget(host string) (*utls.UConn, error) { 36 tcpConn, err := net.DialTimeout("tcp", host, dialTimeout) 37 if err != nil { 38 return nil, err 39 } 40 41 helloID := utls.HelloRandomized 42 if p.Fingerprint != nil { 43 helloID = p.Fingerprint.ClientHelloID() 44 } 45 46 serverName, _, err := net.SplitHostPort(host) 47 if err != nil { 48 tcpConn.Close() 49 return nil, fmt.Errorf("dialTarget: parse host %q: %w", host, err) 50 } 51 52 cfg := &utls.Config{ServerName: serverName} 53 if p.CAPool != nil { 54 cfg.RootCAs = p.CAPool 55 } 56 57 uconn := utls.UClient(tcpConn, cfg, helloID, false, false, false) 58 if err := uconn.Handshake(); err != nil { 59 uconn.Close() 60 return nil, err 61 } 62 return uconn, nil 63 } 64 65 // bufferedConn reads from a bufio.Reader that may already hold bytes read ahead 66 // from the conn (e.g. by http.ReadRequest). Writes and Close go to the conn. 67 type bufferedConn struct { 68 r *bufio.Reader 69 net.Conn 70 } 71 72 func (b *bufferedConn) Read(p []byte) (int, error) { return b.r.Read(p) } 73 74 func (b *bufferedConn) CloseWrite() error { 75 if hc, ok := b.Conn.(halfCloser); ok { 76 return hc.CloseWrite() 77 } 78 return errNoHalfClose 79 } 80 81 var errNoHalfClose = errors.New("proxy: connection does not support half-close") 82 83 // halfCloser is implemented by *net.TCPConn, *tls.Conn and *utls.UConn. 84 type halfCloser interface { 85 CloseWrite() error 86 } 87 88 // closeWrite shuts down only the write side so the peer still receives the data 89 // already sent. 90 func closeWrite(c io.Closer) { 91 if hc, ok := c.(halfCloser); ok && hc.CloseWrite() == nil { 92 return 93 } 94 c.Close() 95 } 96 97 // pipe copies both directions. A finished direction only half-closes its write 98 // side: a client that shut its write end after sending a request must still 99 // receive the response. 100 func pipe(a, b io.ReadWriteCloser) { 101 var wg sync.WaitGroup 102 wg.Add(2) 103 104 go func() { 105 defer wg.Done() 106 io.Copy(b, a) 107 closeWrite(b) 108 }() 109 110 go func() { 111 defer wg.Done() 112 io.Copy(a, b) 113 closeWrite(a) 114 }() 115 116 wg.Wait() 117 a.Close() 118 b.Close() 119 } 120 121 func (p *Proxy) Run() error { 122 if p.Handler == nil { 123 return fmt.Errorf("proxy: Handler is required") 124 } 125 ln, err := net.Listen("tcp", p.ListenAddr) 126 if err != nil { 127 return err 128 } 129 defer ln.Close() 130 131 log.Printf("[+] Starting proxy on %s", p.ListenAddr) 132 return p.Serve(ln) 133 } 134 135 // Serve accepts connections until the listener is closed. Transient accept 136 // errors are retried with exponential backoff, mirroring net/http.Server.Serve. 137 func (p *Proxy) Serve(ln net.Listener) error { 138 if p.Handler == nil { 139 return fmt.Errorf("proxy: Handler is required") 140 } 141 142 var delay time.Duration 143 for { 144 conn, err := ln.Accept() 145 if err != nil { 146 if errors.Is(err, net.ErrClosed) { 147 return err 148 } 149 if delay == 0 { 150 delay = acceptRetryDelay 151 } else { 152 delay *= 2 153 } 154 if delay > acceptMaxRetryDelay { 155 delay = acceptMaxRetryDelay 156 } 157 log.Printf("Failed to accept connection: %v; retrying in %v", err, delay) 158 time.Sleep(delay) 159 continue 160 } 161 delay = 0 162 go p.Handler(conn, p) 163 } 164 }