The guard refused to start an AmneziaWG endpoint whose detour chain reached a WireGuard one, and refused silently: not an error, just started=false, after which every dial failed with "WireGuard is not ready yet". A selector hook went further and suspended an already-working node the moment its group switched to a WireGuard member. It existed because AmneziaWG inside WireGuard hung the kernel on Android. We do not ship Android, upstream dropped the guard once the cause was gone, and the cure landed here yesterday — the ClientBind reserved-gate plus the submodule pin that carries its twin. So the tree held both the cure and the prohibition on using it, and the configuration simply did not come up while looking like a node that "just does not work". Also takes the two fixes that belong with it. ClientBind.conn was read on a lock-free fast path and written under a mutex; upstream found that race with the same end-to-end test we wrote yesterday, so we had taken one half of a pair again. And the outer WireGuard UDP socket forced DF, unlike direct, hysteria and tuic — with encapsulation the datagram regularly exceeds the path MTU and the kernel drops it instead of fragmenting, a symptom indistinguishable from the bug we spent yesterday on. The race needed its own test: the existing e2e run did not flag it under -race even at -count=15. Eight goroutines over both connect branches reproduce it deterministically, naming the lock-free read and the guarded write. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
74 lines
2.3 KiB
Go
74 lines
2.3 KiB
Go
package adapter
|
|
|
|
import (
|
|
"context"
|
|
"net/netip"
|
|
"time"
|
|
|
|
"github.com/sagernet/sing-box/log"
|
|
"github.com/sagernet/sing-box/option"
|
|
"github.com/sagernet/sing-tun"
|
|
N "github.com/sagernet/sing/common/network"
|
|
)
|
|
|
|
// Note: for proxy protocols, outbound creates early connections by default.
|
|
|
|
type Outbound interface {
|
|
Type() string
|
|
Tag() string
|
|
Network() []string
|
|
Dependencies() []string
|
|
N.Dialer
|
|
}
|
|
|
|
type OutboundWithPreferredRoutes interface {
|
|
Outbound
|
|
PreferredDomain(metadata *InboundContext, domain string) bool
|
|
PreferredAddress(metadata *InboundContext, address netip.Addr) bool
|
|
}
|
|
|
|
type FlowOutbound interface {
|
|
Outbound
|
|
tun.Port
|
|
PreMatchFlow(network string, destination netip.Addr) PreMatchAction
|
|
}
|
|
|
|
type OutboundRegistry interface {
|
|
option.OutboundOptionsRegistry
|
|
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
|
}
|
|
|
|
type OutboundManager interface {
|
|
Lifecycle
|
|
Outbounds() []Outbound
|
|
Outbound(tag string) (Outbound, bool)
|
|
Default() Outbound
|
|
Remove(tag string) error
|
|
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) error
|
|
}
|
|
|
|
// lx:begin idle-suspend
|
|
// IdleSuspendable is implemented by a WG/AWG endpoint so the router's idle tick
|
|
// (SPEC 020) can suspend it when it is idle and unreachable, without importing
|
|
// protocol/wireguard. SuspendIfIdle brings the device Down (freeing its
|
|
// recv-worker bufsArrs — the dominant GC-scan holder) only on the live→asleep
|
|
// transition; the next dial through the endpoint wakes it lazily.
|
|
type IdleSuspendable interface {
|
|
Tag() string
|
|
SuspendIfIdle(reachable bool, threshold time.Duration)
|
|
}
|
|
|
|
// ReachabilityInvalidator is implemented by the Router. SPEC 020 reachability is
|
|
// recomputed only on events that change the active routing tree — a selector
|
|
// switch, a urltest auto-switch / pool rebuild, or a config reload — not on every
|
|
// idle tick. Those event points pull this out of the context
|
|
// (service.FromContext[adapter.ReachabilityInvalidator]) and mark the cache
|
|
// dirty; the next idle tick recomputes lazily. Kept as its own narrow interface
|
|
// so protocol/group calls it without importing route and without widening the
|
|
// large adapter.Router interface.
|
|
type ReachabilityInvalidator interface {
|
|
InvalidateReachability()
|
|
}
|
|
|
|
// lx:end idle-suspend
|