diff --git a/common/process/searcher.go b/common/process/searcher.go index 64305237a..7a882fc98 100644 --- a/common/process/searcher.go +++ b/common/process/searcher.go @@ -14,6 +14,7 @@ import ( type Searcher interface { FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) + ResetCache() Close() error } diff --git a/common/process/searcher_android.go b/common/process/searcher_android.go index e63477448..6b845eb52 100644 --- a/common/process/searcher_android.go +++ b/common/process/searcher_android.go @@ -23,6 +23,9 @@ func NewSearcher(config Config) (Searcher, error) { return &androidSearcher{config.PackageManager}, nil } +func (s *androidSearcher) ResetCache() { +} + func (s *androidSearcher) Close() error { return nil } diff --git a/common/process/searcher_darwin.go b/common/process/searcher_darwin.go index 1b5c0dd6c..0ef7c1c92 100644 --- a/common/process/searcher_darwin.go +++ b/common/process/searcher_darwin.go @@ -20,6 +20,10 @@ func NewSearcher(_ Config) (Searcher, error) { return &darwinSearcher{}, nil } +func (d *darwinSearcher) ResetCache() { + sharedDarwinConnectionFinder.resetCache() +} + func (d *darwinSearcher) Close() error { return nil } diff --git a/common/process/searcher_darwin_shared.go b/common/process/searcher_darwin_shared.go index 0557ae676..4cb3dde14 100644 --- a/common/process/searcher_darwin_shared.go +++ b/common/process/searcher_darwin_shared.go @@ -119,6 +119,12 @@ func (f *darwinConnectionFinder) find(network string, source netip.AddrPort, des return nil, ErrNotFound } +func (f *darwinConnectionFinder) resetCache() { + f.access.Lock() + defer f.access.Unlock() + clear(f.snapshots) +} + func (f *darwinConnectionFinder) loadSnapshot(network string, forceRefresh bool) (darwinSnapshot, bool, error) { f.access.Lock() defer f.access.Unlock() diff --git a/common/process/searcher_linux.go b/common/process/searcher_linux.go index 9b1a91603..1d94ea72d 100644 --- a/common/process/searcher_linux.go +++ b/common/process/searcher_linux.go @@ -35,6 +35,10 @@ func NewSearcher(config Config) (Searcher, error) { return searcher, nil } +func (s *linuxSearcher) ResetCache() { + s.processPathCache.cache.Purge() +} + func (s *linuxSearcher) Close() error { var errs []error for _, conn := range s.diagConns { diff --git a/common/process/searcher_windows.go b/common/process/searcher_windows.go index 39695355b..f01176570 100644 --- a/common/process/searcher_windows.go +++ b/common/process/searcher_windows.go @@ -28,6 +28,9 @@ func initWin32API() error { return winiphlpapi.LoadExtendedTable() } +func (s *windowsSearcher) ResetCache() { +} + func (s *windowsSearcher) Close() error { return nil } diff --git a/dns/transport/https.go b/dns/transport/https.go index 5baa782fc..05e9f8c5f 100644 --- a/dns/transport/https.go +++ b/dns/transport/https.go @@ -10,6 +10,7 @@ import ( "net/url" "strconv" "sync" + "sync/atomic" "time" "github.com/sagernet/sing-box/adapter" @@ -45,6 +46,8 @@ type HTTPSTransport struct { dialer N.Dialer destination *url.URL headers http.Header + serverAddr M.Socksaddr + fallback *atomic.Bool transportAccess sync.Mutex transport *HTTPSTransportWrapper transportResetAt time.Time @@ -123,13 +126,20 @@ func NewHTTPSRaw( if tlsConfig != nil { dialer = tls.NewDialer(dialer, tlsConfig) } + fallback := new(atomic.Bool) + if destination.Scheme == "http" { + // plain HTTP DoH used by Tailscale + fallback.Store(true) + } return &HTTPSTransport{ TransportAdapter: adapter, logger: logger, dialer: dialer, destination: destination, headers: headers, - transport: NewHTTPSTransportWrapper(dialer, serverAddr, destination), + serverAddr: serverAddr, + fallback: fallback, + transport: NewHTTPSTransportWrapper(dialer, serverAddr, fallback), } } @@ -148,8 +158,14 @@ func (t *HTTPSTransport) Close() error { func (t *HTTPSTransport) Reset() { t.transportAccess.Lock() defer t.transportAccess.Unlock() - t.transport.CloseIdleConnections() - t.transport = t.transport.Clone() + t.resetTransportLocked() +} + +func (t *HTTPSTransport) resetTransportLocked() { + oldTransport := t.transport + t.transport = NewHTTPSTransportWrapper(t.dialer, t.serverAddr, t.fallback) + t.transportResetAt = time.Now() + oldTransport.Close() } func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { @@ -162,9 +178,7 @@ func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS if t.transportResetAt.After(startAt) { return nil, err } - t.transport.CloseIdleConnections() - t.transport = t.transport.Clone() - t.transportResetAt = time.Now() + t.resetTransportLocked() } return nil, err } diff --git a/dns/transport/https_transport.go b/dns/transport/https_transport.go index c823718aa..c41655354 100644 --- a/dns/transport/https_transport.go +++ b/dns/transport/https_transport.go @@ -5,7 +5,7 @@ import ( "errors" "net" "net/http" - "net/url" + "sync" "sync/atomic" "github.com/sagernet/sing-box/common/tls" @@ -22,42 +22,50 @@ type HTTPSTransportWrapper struct { http2Transport *http2.Transport httpTransport *http.Transport fallback *atomic.Bool + connAccess sync.Mutex + connections map[*httpsTrackedConn]struct{} + closed bool } -func NewHTTPSTransportWrapper(dialer N.Dialer, serverAddr M.Socksaddr, destination *url.URL) *HTTPSTransportWrapper { - var fallback atomic.Bool - if destination.Scheme == "http" { - // plain HTTP DoH used by Tailscale - fallback.Store(true) +func NewHTTPSTransportWrapper(dialer N.Dialer, serverAddr M.Socksaddr, fallback *atomic.Bool) *HTTPSTransportWrapper { + wrapper := &HTTPSTransportWrapper{ + fallback: fallback, + connections: make(map[*httpsTrackedConn]struct{}), } - return &HTTPSTransportWrapper{ - http2Transport: &http2.Transport{ - DialTLSContext: func(ctx context.Context, _, _ string, _ *tls.STDConfig) (net.Conn, error) { - resultConn, err := dialer.DialContext(ctx, N.NetworkTCP, serverAddr) - if err != nil { - return nil, err + wrapper.http2Transport = &http2.Transport{ + DialTLSContext: func(ctx context.Context, _, _ string, _ *tls.STDConfig) (net.Conn, error) { + resultConn, err := dialer.DialContext(ctx, N.NetworkTCP, serverAddr) + if err != nil { + return nil, err + } + if tlsConn, isTLSConn := resultConn.(tls.Conn); isTLSConn { + state := tlsConn.ConnectionState() + if state.NegotiatedProtocol != http2.NextProtoTLS { + tlsConn.Close() + fallback.Store(true) + return nil, errFallback } - if tlsConn, isTLSConn := resultConn.(tls.Conn); isTLSConn { - state := tlsConn.ConnectionState() - if state.NegotiatedProtocol != http2.NextProtoTLS { - tlsConn.Close() - fallback.Store(true) - return nil, errFallback - } - } - return resultConn, nil - }, + } + return wrapper.trackConn(resultConn) }, - httpTransport: &http.Transport{ - DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) { - return dialer.DialContext(ctx, N.NetworkTCP, serverAddr) - }, - DialTLSContext: func(ctx context.Context, _, _ string) (net.Conn, error) { - return dialer.DialContext(ctx, N.NetworkTCP, serverAddr) - }, - }, - fallback: &fallback, } + wrapper.httpTransport = &http.Transport{ + DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) { + resultConn, err := dialer.DialContext(ctx, N.NetworkTCP, serverAddr) + if err != nil { + return nil, err + } + return wrapper.trackConn(resultConn) + }, + DialTLSContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + resultConn, err := dialer.DialContext(ctx, N.NetworkTCP, serverAddr) + if err != nil { + return nil, err + } + return wrapper.trackConn(resultConn) + }, + } + return wrapper } func (h *HTTPSTransportWrapper) RoundTrip(request *http.Request) (*http.Response, error) { @@ -74,17 +82,47 @@ func (h *HTTPSTransportWrapper) RoundTrip(request *http.Request) (*http.Response return response, nil } -func (h *HTTPSTransportWrapper) CloseIdleConnections() { +func (h *HTTPSTransportWrapper) trackConn(conn net.Conn) (net.Conn, error) { + trackedConn := &httpsTrackedConn{Conn: conn, wrapper: h} + h.connAccess.Lock() + if h.closed { + h.connAccess.Unlock() + conn.Close() + return nil, net.ErrClosed + } + h.connections[trackedConn] = struct{}{} + h.connAccess.Unlock() + return trackedConn, nil +} + +func (h *HTTPSTransportWrapper) Close() { + h.connAccess.Lock() + if h.closed { + h.connAccess.Unlock() + return + } + h.closed = true + connections := make([]*httpsTrackedConn, 0, len(h.connections)) + for trackedConn := range h.connections { + connections = append(connections, trackedConn) + } + h.connections = nil + h.connAccess.Unlock() + for _, trackedConn := range connections { + trackedConn.Conn.Close() + } h.http2Transport.CloseIdleConnections() h.httpTransport.CloseIdleConnections() } -func (h *HTTPSTransportWrapper) Clone() *HTTPSTransportWrapper { - return &HTTPSTransportWrapper{ - httpTransport: h.httpTransport, - http2Transport: &http2.Transport{ - DialTLSContext: h.http2Transport.DialTLSContext, - }, - fallback: h.fallback, - } +type httpsTrackedConn struct { + net.Conn + wrapper *HTTPSTransportWrapper +} + +func (c *httpsTrackedConn) Close() error { + c.wrapper.connAccess.Lock() + delete(c.wrapper.connections, c) + c.wrapper.connAccess.Unlock() + return c.Conn.Close() } diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index d0dca79b3..ddb868356 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -148,6 +148,9 @@ func (t *Transport) Reset() { } } t.system.reset() + if t.resolved != nil { + t.resolved.Reset() + } if t.dhcpTransport != nil { t.dhcpTransport.Reset() } diff --git a/dns/transport/local/local_resolved.go b/dns/transport/local/local_resolved.go index 451ee3650..13b2a4345 100644 --- a/dns/transport/local/local_resolved.go +++ b/dns/transport/local/local_resolved.go @@ -9,6 +9,7 @@ import ( type ResolvedResolver interface { Start() error Close() error + Reset() Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) ExchangeAsync(ctx context.Context, message *mDNS.Msg, callback func(response *mDNS.Msg, err error)) } diff --git a/dns/transport/local/local_resolved_linux.go b/dns/transport/local/local_resolved_linux.go index b55213b4c..93f24c617 100644 --- a/dns/transport/local/local_resolved_linux.go +++ b/dns/transport/local/local_resolved_linux.go @@ -134,6 +134,19 @@ func (t *DBusResolvedResolver) Close() error { return closeErr } +func (t *DBusResolvedResolver) Reset() { + serverSet := t.savedServerSet.Load() + if serverSet == nil { + return + } + for _, server := range serverSet.servers { + server.primaryTransport.Reset() + if server.fallbackTransport != nil { + server.fallbackTransport.Reset() + } + } +} + func (t *DBusResolvedResolver) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { serverSet := t.savedServerSet.Load() if serverSet == nil { diff --git a/go.mod b/go.mod index 9e0f5fd6c..924e6f39f 100644 --- a/go.mod +++ b/go.mod @@ -46,14 +46,14 @@ require ( github.com/sagernet/sing v0.8.12-0.20260717153536-4f1ed45a99a5 github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 github.com/sagernet/sing-mux v0.3.5 - github.com/sagernet/sing-openconnect v0.0.0-20260718163953-a1c7815e4f04 - github.com/sagernet/sing-openvpn v0.0.0-20260718163953-26ecbeb6352c + github.com/sagernet/sing-openconnect v0.0.0-20260719094202-dc28b269c7ce + github.com/sagernet/sing-openvpn v0.0.0-20260719094204-c57d60b5c5a4 github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1 - github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb - github.com/sagernet/sing-tun v0.8.12-0.20260719050255-7e7bd198288c + github.com/sagernet/sing-snell v0.0.0-20260719094200-c43fbee0e839 + github.com/sagernet/sing-tun v0.8.12-0.20260719094150-557ca930fccd github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 diff --git a/go.sum b/go.sum index d1793bf96..f02ed2434 100644 --- a/go.sum +++ b/go.sum @@ -285,10 +285,10 @@ github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 h1:3y6 github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3/go.mod h1:XEqEDYRCAYLaoPjZ1ifVWJg5iWAJHL2gOAXe/PM28Cg= github.com/sagernet/sing-mux v0.3.5 h1:RHnhVEc+SFqkrK4xMygYjDwwLhzp2Bj3lztSukONfhI= github.com/sagernet/sing-mux v0.3.5/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-openconnect v0.0.0-20260718163953-a1c7815e4f04 h1:HIb3Tu19qqH5fD5xnyyHb6zJaETIsnXamb/hWTtxil8= -github.com/sagernet/sing-openconnect v0.0.0-20260718163953-a1c7815e4f04/go.mod h1:EIzh5HtImfQJxPKXFwS9lyMnmMy4aCQCx7ntQ4u41Gs= -github.com/sagernet/sing-openvpn v0.0.0-20260718163953-26ecbeb6352c h1:EhwLZF3IUyDj4uZ7vkUZAI7GymXCeOCwiseuOTsFjp8= -github.com/sagernet/sing-openvpn v0.0.0-20260718163953-26ecbeb6352c/go.mod h1:CmTGnS5ijVSqFQV1dTq4WvFLUoz7bk9xasBPsX8NcYo= +github.com/sagernet/sing-openconnect v0.0.0-20260719094202-dc28b269c7ce h1:uPyEKbqEyGaJoKxQtiS+T9ZTrioL0Vl5Ko3i6iTpV2Y= +github.com/sagernet/sing-openconnect v0.0.0-20260719094202-dc28b269c7ce/go.mod h1:EIzh5HtImfQJxPKXFwS9lyMnmMy4aCQCx7ntQ4u41Gs= +github.com/sagernet/sing-openvpn v0.0.0-20260719094204-c57d60b5c5a4 h1:3H1pOsE5IRzr2U+oUTabPrvryj7xg1CEmOnMMwsQSQA= +github.com/sagernet/sing-openvpn v0.0.0-20260719094204-c57d60b5c5a4/go.mod h1:CmTGnS5ijVSqFQV1dTq4WvFLUoz7bk9xasBPsX8NcYo= github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc h1:zdc0fj4JdAdgAmQIoh7ZF+B/wPTEF2X75lYDqTmvlaw= github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc/go.mod h1:9k+dzGsWMttUGldBzq3dU792YHXzW6NgfbOGltnXq+0= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= @@ -297,10 +297,10 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1 h1:ZiHZdnEnP+YS73NMsxiZmIFCwNd0M4k7PkGCKNXhbaM= github.com/sagernet/sing-shadowtls v0.2.1/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb h1:VvU2/PZqP5tbKTDq0BxkhRO8ZnKI4UJzziakgBiP2Qg= -github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb/go.mod h1:PcwzX/Xvqky0EP3kGt8OCjYb3R1pydenPHNQZcPZmXY= -github.com/sagernet/sing-tun v0.8.12-0.20260719050255-7e7bd198288c h1:jS5eWD9PyXhHY89DueRDgenfcrfpyPOuuZIoNfnBmOs= -github.com/sagernet/sing-tun v0.8.12-0.20260719050255-7e7bd198288c/go.mod h1:F/gRq5VX1WN/OZtsvbN2JjXXuNl2ATJglHMSk1/iN9U= +github.com/sagernet/sing-snell v0.0.0-20260719094200-c43fbee0e839 h1:YL0oCb55moImUGvjhhVEYODGMo5i9dAf+RpmMFPCq9w= +github.com/sagernet/sing-snell v0.0.0-20260719094200-c43fbee0e839/go.mod h1:PcwzX/Xvqky0EP3kGt8OCjYb3R1pydenPHNQZcPZmXY= +github.com/sagernet/sing-tun v0.8.12-0.20260719094150-557ca930fccd h1:tH79/IieRjLx5DiVu3NpXc1hir0xL6Avmmist0aFHks= +github.com/sagernet/sing-tun v0.8.12-0.20260719094150-557ca930fccd/go.mod h1:F/gRq5VX1WN/OZtsvbN2JjXXuNl2ATJglHMSk1/iN9U= github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb h1:KEMbfexD4DvrQGYWwx6r+AwH9Veh8z6cnBZmtCS2G+0= github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb/go.mod h1:D4CnJX3MNAAANhbQUxfIRgBdnvlTEaV7h6ojedcs+pw= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= diff --git a/protocol/direct/inbound.go b/protocol/direct/inbound.go index fcb4671d8..42135dc16 100644 --- a/protocol/direct/inbound.go +++ b/protocol/direct/inbound.go @@ -77,6 +77,10 @@ func (i *Inbound) Start(stage adapter.StartStage) error { return i.listener.Start() } +func (i *Inbound) InterfaceUpdated() { + i.udpNat.Purge() +} + func (i *Inbound) Close() error { return i.listener.Close() } diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index fdf45988b..9edf38323 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -29,10 +29,11 @@ func RegisterOutbound(registry *outbound.Registry) { } var ( - _ N.ParallelDialer = (*Outbound)(nil) - _ dialer.ParallelNetworkDialer = (*Outbound)(nil) - _ dialer.DirectDialer = (*Outbound)(nil) - _ adapter.FlowOutbound = (*Outbound)(nil) + _ N.ParallelDialer = (*Outbound)(nil) + _ dialer.ParallelNetworkDialer = (*Outbound)(nil) + _ dialer.DirectDialer = (*Outbound)(nil) + _ adapter.FlowOutbound = (*Outbound)(nil) + _ adapter.InterfaceUpdateListener = (*Outbound)(nil) ) type Outbound struct { @@ -88,30 +89,43 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL func (h *Outbound) Start(stage adapter.StartStage) error { switch stage { case adapter.StartStatePostStart, adapter.StartStateStarted: - h.fetchMyAddresses() + if len(h.myAddresses.Load()) == 0 { + h.fetchMyAddresses() + } } return nil } func (h *Outbound) fetchMyAddresses() { - if len(h.myAddresses.Load()) > 0 { - return - } myInterfaceNames := h.network.InterfaceMonitor().MyInterfaces() if len(myInterfaceNames) == 0 { return } - var myAddresses []netip.Prefix + var ( + myAddresses []netip.Prefix + found bool + ) for _, myInterfaceName := range myInterfaceNames { myInterface, err := h.network.InterfaceFinder().ByName(myInterfaceName) if err != nil { continue } + found = true myAddresses = append(myAddresses, myInterface.Addresses...) } + if !found { + return + } h.myAddresses.Store(myAddresses) } +func (h *Outbound) InterfaceUpdated() { + h.fetchMyAddresses() + if h.icmpPort != nil { + h.icmpPort.Close() + } +} + func (h *Outbound) isMyLoopbackAddress(addresses ...netip.Addr) bool { for _, prefix := range h.myAddresses.Load() { for _, address := range addresses { diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index 69e310370..d97235b78 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -28,7 +28,10 @@ func RegisterURLTest(registry *outbound.Registry) { outbound.Register[option.URLTestOutboundOptions](registry, C.TypeURLTest, NewURLTest) } -var _ adapter.OutboundGroup = (*URLTest)(nil) +var ( + _ adapter.OutboundGroup = (*URLTest)(nil) + _ adapter.InterfaceUpdateListener = (*URLTest)(nil) +) type URLTest struct { outbound.Adapter @@ -114,6 +117,17 @@ func (s *URLTest) CheckOutbounds() { s.group.CheckOutbounds(true) } +func (s *URLTest) InterfaceUpdated() { + group := s.group + if group == nil { + return + } + if group.pause.IsDevicePaused() || group.pause.IsNetworkPaused() { + return + } + go group.CheckOutbounds(true) +} + func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { s.group.Touch() var outbound adapter.Outbound diff --git a/protocol/openconnect/client.go b/protocol/openconnect/client.go index dd78d72de..6622adf63 100644 --- a/protocol/openconnect/client.go +++ b/protocol/openconnect/client.go @@ -34,6 +34,7 @@ import ( var ( _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) _ adapter.FlowOutbound = (*Endpoint)(nil) + _ adapter.InterfaceUpdateListener = (*Endpoint)(nil) _ dialer.PacketDialerWithDestination = (*Endpoint)(nil) _ tun.Port = (*Endpoint)(nil) ) @@ -396,6 +397,10 @@ func (e *Endpoint) Close() error { return err } +func (e *Endpoint) InterfaceUpdated() { + e.client.RestartSession() +} + func (e *Endpoint) PreMatchFlow(network string, destination netip.Addr) adapter.PreMatchAction { return adapter.PreMatchFlow } diff --git a/protocol/openvpn/client.go b/protocol/openvpn/client.go index 7fb0dd091..339e6b106 100644 --- a/protocol/openvpn/client.go +++ b/protocol/openvpn/client.go @@ -32,6 +32,7 @@ import ( var ( _ adapter.OutboundWithPreferredRoutes = (*ClientEndpoint)(nil) _ adapter.FlowOutbound = (*ClientEndpoint)(nil) + _ adapter.InterfaceUpdateListener = (*ClientEndpoint)(nil) _ dialer.PacketDialerWithDestination = (*ClientEndpoint)(nil) _ tun.Port = (*ClientEndpoint)(nil) ) @@ -453,6 +454,10 @@ func (c *ClientEndpoint) Close() error { return err } +func (c *ClientEndpoint) InterfaceUpdated() { + c.client.RestartSession() +} + func (c *ClientEndpoint) PreMatchFlow(network string, destination netip.Addr) adapter.PreMatchAction { return adapter.PreMatchFlow } diff --git a/protocol/redirect/tproxy.go b/protocol/redirect/tproxy.go index 48fae0f23..6ce7330c8 100644 --- a/protocol/redirect/tproxy.go +++ b/protocol/redirect/tproxy.go @@ -85,6 +85,10 @@ func (t *TProxy) Start(stage adapter.StartStage) error { return err } +func (t *TProxy) InterfaceUpdated() { + t.udpNat.Purge() +} + func (t *TProxy) Close() error { _ = t.udpNat.Close() return t.listener.Close() diff --git a/protocol/snell/outbound.go b/protocol/snell/outbound.go index 74543f8f5..c24a92c02 100644 --- a/protocol/snell/outbound.go +++ b/protocol/snell/outbound.go @@ -32,9 +32,12 @@ type Outbound struct { serverAddr M.Socksaddr } +var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) + type snellClient interface { snellprotocol.Method DialContext(ctx context.Context, destination M.Socksaddr) (net.Conn, error) + Reset() Close() error } @@ -136,6 +139,10 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return packetConn, nil } +func (h *Outbound) InterfaceUpdated() { + h.client.Reset() +} + func (h *Outbound) Close() error { return h.client.Close() } diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 91f766034..0924e44fe 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -68,6 +68,7 @@ import ( var ( _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) + _ adapter.InterfaceUpdateListener = (*Endpoint)(nil) _ dialer.PacketDialerWithDestination = (*Endpoint)(nil) _ tun.Port = (*Endpoint)(nil) ) @@ -696,6 +697,16 @@ func (t *Endpoint) Close() error { return err } +func (t *Endpoint) InterfaceUpdated() { + if !t.started.Load() { + return + } + netMon, loaded := t.server.Sys().NetMon.GetOK() + if loaded && netMon != nil { + netMon.InjectEvent() + } +} + func (t *Endpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch network { case N.NetworkTCP: diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 739ceaa27..19178d4a4 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -507,6 +507,13 @@ func (t *Inbound) updateRouteAddressSet(it adapter.RuleSet) { t.routeExcludeAddressSet = nil } +func (t *Inbound) InterfaceUpdated() { + tunStack := t.tunStack + if tunStack != nil { + tunStack.ResetNetwork() + } +} + func (t *Inbound) Close() error { return common.Close( t.tunStack, diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index d3e8c1a41..14e3ecb53 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -26,6 +26,7 @@ import ( var ( _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) + _ adapter.InterfaceUpdateListener = (*Endpoint)(nil) _ dialer.PacketDialerWithDestination = (*Endpoint)(nil) ) @@ -159,6 +160,16 @@ func (w *Endpoint) Close() error { return w.endpoint.Close() } +func (w *Endpoint) InterfaceUpdated() { + if !w.started.Load() { + return + } + err := w.endpoint.BindUpdate() + if err != nil { + w.logger.Error(E.Cause(err, "update bind")) + } +} + func (w *Endpoint) PreMatchFlow(network string, destination netip.Addr) adapter.PreMatchAction { return adapter.PreMatchFlow } diff --git a/route/platform_searcher.go b/route/platform_searcher.go index 20fbda3f3..718385f4e 100644 --- a/route/platform_searcher.go +++ b/route/platform_searcher.go @@ -44,6 +44,9 @@ func (s *platformSearcher) FindProcessInfo(ctx context.Context, network string, return s.platform.FindConnectionOwner(request) } +func (s *platformSearcher) ResetCache() { +} + func (s *platformSearcher) Close() error { return nil } diff --git a/route/router.go b/route/router.go index 2359e5c3e..682111d43 100644 --- a/route/router.go +++ b/route/router.go @@ -288,4 +288,10 @@ func (r *Router) NeighborResolver() adapter.NeighborResolver { func (r *Router) ResetNetwork() { r.httpClientManager.ResetNetwork() r.dns.ResetNetwork() + if r.processCache != nil { + r.processCache.Purge() + } + if r.processSearcher != nil { + r.processSearcher.ResetCache() + } } diff --git a/transport/wireguard/endpoint.go b/transport/wireguard/endpoint.go index c21db3235..9761783cf 100644 --- a/transport/wireguard/endpoint.go +++ b/transport/wireguard/endpoint.go @@ -254,6 +254,13 @@ func (e *Endpoint) Lookup(address netip.Addr) *device.Peer { return e.allowedIPs.Lookup(address.AsSlice()) } +func (e *Endpoint) BindUpdate() error { + if e.device == nil { + return nil + } + return e.device.BindUpdate() +} + func (e *Endpoint) onPauseUpdated(event int) { switch event { case pause.EventDevicePaused, pause.EventNetworkPause: