diff --git a/adapter/outbound/mitm.go b/adapter/outbound/mitm.go index 53b99ff7..e1afe441 100644 --- a/adapter/outbound/mitm.go +++ b/adapter/outbound/mitm.go @@ -3,6 +3,8 @@ package outbound import ( "context" "errors" + "net" + "time" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/trie" @@ -12,50 +14,50 @@ import ( var ( errIgnored = errors.New("not match in mitm host lists") httpProxyClient = NewHttp(HttpOption{}) - - MiddlemanRewriteHosts *trie.DomainTrie[bool] + rewriteHosts *trie.DomainTrie[bool] ) type Mitm struct { *Base - serverAddr string + serverAddr *net.TCPAddr } // DialContext implements C.ProxyAdapter -func (m *Mitm) DialContext(ctx context.Context, metadata *C.Metadata, _ ...dialer.Option) (C.Conn, error) { - if MiddlemanRewriteHosts == nil { +func (m *Mitm) DialContext(_ context.Context, metadata *C.Metadata, _ ...dialer.Option) (C.Conn, error) { + if (rewriteHosts == nil || rewriteHosts.Search(metadata.String()) == nil) && metadata.DstPort != "80" { return nil, errIgnored } - if MiddlemanRewriteHosts.Search(metadata.String()) == nil && metadata.DstPort != "80" { - return nil, errIgnored + c, err := net.DialTCP("tcp", nil, m.serverAddr) + if err != nil { + return nil, err } + _ = c.SetKeepAlive(true) + _ = c.SetKeepAlivePeriod(60 * time.Second) + metadata.Type = C.MITM - c, err := dialer.DialContext(ctx, "tcp", m.serverAddr, []dialer.Option{dialer.WithInterface(""), dialer.WithRoutingMark(0), dialer.WithDirect()}...) + hc, err := httpProxyClient.StreamConn(c, metadata) if err != nil { + _ = c.Close() return nil, err } - tcpKeepAlive(c) - - defer safeConnClose(c, err) - - c, err = httpProxyClient.StreamConn(c, metadata) - if err != nil { - return nil, err - } - - return NewConn(c, m), nil + return NewConn(hc, m), nil } func NewMitm(serverAddr string) *Mitm { + tcpAddr, _ := net.ResolveTCPAddr("tcp", serverAddr) return &Mitm{ Base: &Base{ name: "Mitm", tp: C.Mitm, }, - serverAddr: serverAddr, + serverAddr: tcpAddr, } } + +func UpdateRewriteHosts(hosts *trie.DomainTrie[bool]) { + rewriteHosts = hosts +} diff --git a/adapter/outbound/reject.go b/adapter/outbound/reject.go index f4752104..3ab11453 100644 --- a/adapter/outbound/reject.go +++ b/adapter/outbound/reject.go @@ -6,16 +6,43 @@ import ( "net" "time" + "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" ) +const ( + rejectCountLimit = 50 + rejectDelay = time.Second * 35 +) + +var rejectCounter = cache.NewLRUCache[string, int](cache.WithAge[string, int](15), cache.WithStale[string, int](false), cache.WithSize[string, int](512)) + type Reject struct { *Base } // DialContext implements C.ProxyAdapter func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { + key := metadata.RemoteAddress() + + count, existed := rejectCounter.Get(key) + if !existed { + count = 0 + } + + count = count + 1 + + rejectCounter.Set(key, count) + + if count > rejectCountLimit { + c, _ := net.Pipe() + + _ = c.SetDeadline(time.Now().Add(rejectDelay)) + + return NewConn(c, r), nil + } + return NewConn(&nopConn{}, r), nil } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index de7530d8..c8f7a2da 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -333,7 +333,7 @@ func updateIPTables(cfg *config.Config) { } func updateMitm(mitm *config.Mitm) { - outbound.MiddlemanRewriteHosts = mitm.Hosts + outbound.UpdateRewriteHosts(mitm.Hosts) tunnel.UpdateRewrites(mitm.Rules) } diff --git a/listener/listener.go b/listener/listener.go index b7396c68..67424cc7 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -347,11 +347,11 @@ func ReCreateTun(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan tunAddressPrefix = lastTunAddressPrefix } - if !hasTunConfigChange(tunConf, tunAddressPrefix) { - return - } - if tunStackListener != nil { + if !hasTunConfigChange(tunConf, tunAddressPrefix) { + return + } + _ = tunStackListener.Close() tunStackListener = nil lastTunConf = nil @@ -388,9 +388,9 @@ func ReCreateMitm(port int, tcpIn chan<- C.ConnContext) { if mitmListener.RawAddress() == addr { return } - tunnel.MitmOutbound = nil _ = mitmListener.Close() mitmListener = nil + tunnel.SetMitmOutbound(nil) } if portIsZero(addr) { @@ -442,7 +442,7 @@ func ReCreateMitm(port int, tcpIn chan<- C.ConnContext) { return } - tunnel.MitmOutbound = outbound.NewMitm(mitmListener.Address()) + tunnel.SetMitmOutbound(outbound.NewMitm(mitmListener.Address())) log.Infoln("Mitm proxy listening at: %s", mitmListener.Address()) } diff --git a/rewrite/parser_test.go b/rewrite/parser_test.go index 58d1149a..b067c56e 100644 --- a/rewrite/parser_test.go +++ b/rewrite/parser_test.go @@ -1,12 +1,6 @@ package rewrites import ( - "bytes" - "fmt" - "image" - "image/color" - "image/draw" - "image/png" "regexp" "testing" @@ -41,16 +35,3 @@ func TestParseRewrite(t *testing.T) { assert.Equal(t, c2.RuleRegx(), regexp.MustCompile(`(\r\n)User-Agent:.+(\r\n)`)) assert.Equal(t, c2.RulePayload(), "$1User-Agent: Fuck-Who$2") } - -func Test1PxPNG(t *testing.T) { - m := image.NewRGBA(image.Rect(0, 0, 1, 1)) - - draw.Draw(m, m.Bounds(), &image.Uniform{C: color.Transparent}, image.Point{}, draw.Src) - - buf := &bytes.Buffer{} - - assert.Nil(t, png.Encode(buf, m)) - - fmt.Printf("len: %d\n", buf.Len()) - fmt.Printf("% #x\n", buf.Bytes()) -} diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index a16b5232..c9ac58c7 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -38,8 +38,8 @@ var ( // default timeout for UDP session udpTimeout = 60 * time.Second - // MitmOutbound mitm proxy adapter - MitmOutbound C.ProxyAdapter + // mitmOutbound mitm proxy adapter + mitmOutbound C.ProxyAdapter ) func init() { @@ -96,6 +96,11 @@ func SetMode(m TunnelMode) { mode = m } +// SetMitmOutbound set the MITM outbound +func SetMitmOutbound(outbound C.ProxyAdapter) { + mitmOutbound = outbound +} + // Rewrites return all rewrites func Rewrites() C.RewriteRule { return rewrites @@ -302,9 +307,11 @@ func handleTCPConn(connCtx C.ConnContext) { ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout) defer cancel() - if MitmOutbound != nil && metadata.Type != C.MITM { - if remoteConn, err1 := MitmOutbound.DialContext(ctx, metadata); err1 == nil { + + if mitmOutbound != nil && metadata.Type != C.MITM { + if remoteConn, err1 := mitmOutbound.DialContext(ctx, metadata); err1 == nil { remoteConn = statistic.NewSniffing(remoteConn, metadata, nil) + defer func(remoteConn C.Conn) { _ = remoteConn.Close() }(remoteConn) @@ -329,7 +336,11 @@ func handleTCPConn(connCtx C.ConnContext) { } return } - remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule) + + if remoteConn.Chains().Last() != "REJECT" { + remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule) + } + defer func(remoteConn C.Conn) { _ = remoteConn.Close() }(remoteConn)