WireGuard outbound: Keep the device when its setup fails (#7151)

https://github.com/XTLS/Xray-core/pull/7151#issuecomment-6186150131

Fixes https://github.com/XTLS/Xray-core/pull/7151#issuecomment-6190189547

---------

Co-authored-by: LjhAUMEM <llnu14702@gmail.com>
This commit is contained in:
T3ST3ST3R0N
2026-10-16 09:50:47 +00:00
committed by GitHub
co-authored by LjhAUMEM
parent da2248c516
commit 0f3d884786
3 changed files with 177 additions and 164 deletions
+11 -14
View File
@@ -16,8 +16,9 @@ import (
) )
type bind struct { type bind struct {
ctx context.Context
resolveFunc func(host string) (net.IP, error) resolveFunc func(host string) (net.IP, error)
listenFunc func() (net.PacketConn, error) listenFunc func(ctx context.Context) (net.PacketConn, error)
downFunc func() error downFunc func() error
reserved []byte reserved []byte
@@ -26,6 +27,12 @@ type bind struct {
mu sync.Mutex mu sync.Mutex
} }
func (b *bind) SetCtx(ctx context.Context) {
b.mu.Lock()
defer b.mu.Unlock()
b.ctx = ctx
}
func (b *bind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) { func (b *bind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
@@ -34,7 +41,7 @@ func (b *bind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err
return nil, 0, conn.ErrBindAlreadyOpen return nil, 0, conn.ErrBindAlreadyOpen
} }
c, err := b.listenFunc() c, err := b.listenFunc(b.ctx)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@@ -52,12 +59,9 @@ func (b *bind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err
case <-ch: case <-ch:
default: default:
errors.LogErrorInner(context.Background(), err, "unexpected closed") errors.LogErrorInner(context.Background(), err, "unexpected closed")
b.mu.Lock() if b.downFunc != nil {
downFunc := b.downFunc
b.mu.Unlock()
if downFunc != nil {
go func() { go func() {
common.Must(downFunc()) common.Must(b.downFunc())
}() }()
} }
} }
@@ -79,13 +83,6 @@ func (b *bind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err
}, uint16(c.LocalAddr().(*net.UDPAddr).Port), nil }, uint16(c.LocalAddr().(*net.UDPAddr).Port), nil
} }
// setDownFunc sets downFunc after the device is created, since the device may already be using the bind.
func (b *bind) setDownFunc(f func() error) {
b.mu.Lock()
defer b.mu.Unlock()
b.downFunc = f
}
func (b *bind) Close() error { func (b *bind) Close() error {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
+165 -149
View File
@@ -7,6 +7,7 @@ import (
"reflect" "reflect"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/tun"
@@ -31,18 +32,14 @@ import (
) )
type Handler struct { type Handler struct {
conf *DeviceConfig p policy.Manager
policyManager policy.Manager
dns dns.Client
streamSettings *internet.MemoryStreamConfig canup *atomic.Bool
uplinkCounter stats.Counter tnet *Net
downlinkCounter stats.Counter bind *bind
dev *device.Device
tun tun.Device ipc string
tnet *Net mu sync.Mutex
dev *device.Device
mu sync.Mutex
} }
func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) { func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
@@ -124,17 +121,86 @@ func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
return nil, err return nil, err
} }
blockup := newblockup(tun)
bind := &bind{}
logger := &device.Logger{
Verbosef: func(format string, args ...any) {
log.Record(&log.GeneralMessage{
Severity: log.Severity_Debug,
Content: fmt.Sprintf(format, args...),
})
},
Errorf: func(format string, args ...any) {
log.Record(&log.GeneralMessage{
Severity: log.Severity_Error,
Content: fmt.Sprintf(format, args...),
})
},
}
dev := device.NewDevice(blockup, bind, logger)
bind.ctx = context.Background()
bind.resolveFunc = MakeResolveLocal(d, streamSettings)
bind.listenFunc = func(ctx context.Context) (net.PacketConn, error) {
dest, err := net.ParseDestination("udp:" + conf.Peers[0].Endpoint)
if err != nil {
return nil, err
}
var pktConn net.PacketConn
if streamSettings.FinalMask != nil {
conn, err := streamSettings.FinalMask.DialUDP(ctx, dest)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
}
pktConn = conn.(*net.PacketConnWrapper).PacketConn
} else {
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
}
switch c := conn.(type) {
case *net.PacketConnWrapper:
pktConn = c.PacketConn
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default:
panic(reflect.TypeOf(c))
}
}
if uplinkCounter != nil || downlinkCounter != nil {
pktConn = &PacketCounterConnection{
PacketConn: pktConn,
ReadCounter: downlinkCounter,
WriteCounter: uplinkCounter,
}
}
return pktConn, nil
}
bind.downFunc = dev.Down
bind.reserved = conf.Reserved
var cfg strings.Builder
cfg.WriteString("private_key=" + conf.SecretKey + "\n")
for _, peer := range conf.Peers {
cfg.WriteString("public_key=" + peer.PublicKey + "\n")
if peer.PreSharedKey != "" {
cfg.WriteString("preshared_key=" + peer.PreSharedKey + "\n")
}
cfg.WriteString("endpoint=" + peer.Endpoint + "\n")
for _, ip := range peer.AllowedIps {
cfg.WriteString("allowed_ip=" + ip + "\n")
}
if peer.KeepAlive != "" {
cfg.WriteString("persistent_keepalive_interval=" + peer.KeepAlive + "\n")
}
}
return &Handler{ return &Handler{
conf: conf, p: p,
policyManager: p,
dns: d,
streamSettings: streamSettings, canup: blockup.canup,
uplinkCounter: uplinkCounter, tnet: tnet,
downlinkCounter: downlinkCounter, bind: bind,
dev: dev,
tun: tun, ipc: cfg.String(),
tnet: tnet,
}, nil }, nil
} }
@@ -149,7 +215,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
ob.CanSpliceCopy = 3 ob.CanSpliceCopy = 3
dialer.SetOutboundGateway(ctx, ob) dialer.SetOutboundGateway(ctx, ob)
if err := h.init(ctx); err != nil { if err := h.up(ctx); err != nil {
return err return err
} }
@@ -159,7 +225,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
newCtx, newCancel = context.WithCancel(context.Background()) newCtx, newCancel = context.WithCancel(context.Background())
} }
sessionPolicy := h.policyManager.ForLevel(0) sessionPolicy := h.p.ForLevel(0)
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, func() { timer := signal.CancelAfterInactivity(ctx, func() {
cancel() cancel()
@@ -234,150 +300,72 @@ func (h *Handler) Close() (err error) {
if h.dev != nil { if h.dev != nil {
h.dev.Close() h.dev.Close()
h.dev = nil h.dev = nil
h.tun = nil
} else if h.tun != nil {
h.tun.Close()
h.tun = nil
} }
return nil return nil
} }
func (h *Handler) init(ctx context.Context) error { func (h *Handler) up(ctx context.Context) error {
h.mu.Lock() h.mu.Lock()
defer h.mu.Unlock() defer h.mu.Unlock()
if h.tun == nil { if h.dev == nil {
return errors.New("closed") return errors.New("closed")
} }
if h.dev != nil { h.bind.SetCtx(ctx)
return h.dev.Up() if !h.canup.Load() {
h.canup.Store(true)
} }
resolveFunc := h.resolveLocal if len(h.ipc) > 0 {
listenFunc := func() (net.PacketConn, error) { if err := h.dev.IpcSet(h.ipc); err != nil {
dest, err := net.ParseDestination("udp:" + h.conf.Peers[0].Endpoint) return err
}
h.ipc = ""
}
return h.dev.Up()
}
func MakeResolveLocal(d dns.Client, streamSettings *internet.MemoryStreamConfig) func(host string) (net.IP, error) {
return func(host string) (net.IP, error) {
if ip := net.ParseIP(host); ip != nil {
return ip, nil
}
ips, _, err := d.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
if err != nil { if err != nil {
return nil, err return nil, err
} }
var pktConn net.PacketConn got := ips
if h.streamSettings.FinalMask != nil { if streamSettings.SocketSettings != nil {
conn, err := h.streamSettings.FinalMask.DialUDP(ctx, dest) var got4, got6 []net.IP
if err != nil { for _, ip := range ips {
return nil, errors.New("failed to dial to dest").Base(err) if ip.To4() != nil {
got4 = append(got4, ip)
} else {
got6 = append(got6, ip)
}
} }
pktConn = conn.(*net.PacketConnWrapper).PacketConn switch streamSettings.SocketSettings.DomainStrategy {
} else { case internet.DomainStrategy_AS_IS, internet.DomainStrategy_USE_IP, internet.DomainStrategy_FORCE_IP:
conn, err := internet.DialSystem(ctx, dest, h.streamSettings.SocketSettings) got = ips
if err != nil { case internet.DomainStrategy_USE_IP4, internet.DomainStrategy_FORCE_IP4:
return nil, errors.New("failed to dial to dest").Base(err)
}
switch c := conn.(type) {
case *net.PacketConnWrapper:
pktConn = c.PacketConn
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default:
panic(reflect.TypeOf(c))
}
}
if h.uplinkCounter != nil || h.downlinkCounter != nil {
pktConn = &PacketCounterConnection{
PacketConn: pktConn,
ReadCounter: h.downlinkCounter,
WriteCounter: h.uplinkCounter,
}
}
return pktConn, nil
}
// device.NewDevice may use the bind right away (Up -> BindUpdate -> Open),
// so everything it reads must be set before creating the device.
bind := &bind{
resolveFunc: resolveFunc,
listenFunc: listenFunc,
reserved: h.conf.Reserved,
}
logger := &device.Logger{
Verbosef: func(format string, args ...any) {
log.Record(&log.GeneralMessage{
Severity: log.Severity_Debug,
Content: fmt.Sprintf(format, args...),
})
},
Errorf: func(format string, args ...any) {
log.Record(&log.GeneralMessage{
Severity: log.Severity_Error,
Content: fmt.Sprintf(format, args...),
})
},
}
dev := device.NewDevice(h.tun, bind, logger)
bind.setDownFunc(dev.Down)
var cfg strings.Builder
cfg.WriteString("private_key=" + h.conf.SecretKey + "\n")
for _, peer := range h.conf.Peers {
cfg.WriteString("public_key=" + peer.PublicKey + "\n")
if peer.PreSharedKey != "" {
cfg.WriteString("preshared_key=" + peer.PreSharedKey + "\n")
}
cfg.WriteString("endpoint=" + peer.Endpoint + "\n")
for _, ip := range peer.AllowedIps {
cfg.WriteString("allowed_ip=" + ip + "\n")
}
if peer.KeepAlive != "" {
cfg.WriteString("persistent_keepalive_interval=" + peer.KeepAlive + "\n")
}
}
err := dev.IpcSet(cfg.String())
if err != nil {
return err
}
err = dev.Up()
if err != nil {
return err
}
h.dev = dev
return nil
}
func (h *Handler) resolveLocal(host string) (net.IP, error) {
if ip := net.ParseIP(host); ip != nil {
return ip, nil
}
ips, _, err := h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
if err != nil {
return nil, err
}
got := ips
if h.streamSettings.SocketSettings != nil {
var got4, got6 []net.IP
for _, ip := range ips {
if ip.To4() != nil {
got4 = append(got4, ip)
} else {
got6 = append(got6, ip)
}
}
switch h.streamSettings.SocketSettings.DomainStrategy {
case internet.DomainStrategy_AS_IS, internet.DomainStrategy_USE_IP, internet.DomainStrategy_FORCE_IP:
got = ips
case internet.DomainStrategy_USE_IP4, internet.DomainStrategy_FORCE_IP4:
got = got4
case internet.DomainStrategy_USE_IP6, internet.DomainStrategy_FORCE_IP6:
got = got6
case internet.DomainStrategy_USE_IP46, internet.DomainStrategy_FORCE_IP46:
got = got4
if len(got) == 0 {
got = got6
}
case internet.DomainStrategy_USE_IP64, internet.DomainStrategy_FORCE_IP64:
got = got6
if len(got) == 0 {
got = got4 got = got4
case internet.DomainStrategy_USE_IP6, internet.DomainStrategy_FORCE_IP6:
got = got6
case internet.DomainStrategy_USE_IP46, internet.DomainStrategy_FORCE_IP46:
got = got4
if len(got) == 0 {
got = got6
}
case internet.DomainStrategy_USE_IP64, internet.DomainStrategy_FORCE_IP64:
got = got6
if len(got) == 0 {
got = got4
}
}
if len(got) == 0 {
return nil, dns.ErrEmptyResponse
} }
} }
if len(got) == 0 { return got[dice.Roll(len(got))], nil
return nil, dns.ErrEmptyResponse
}
} }
return got[dice.Roll(len(got))], nil
} }
type UDPConnClient struct { type UDPConnClient struct {
@@ -510,3 +498,31 @@ func (c *cache) Cache(host string, saddr []string, ttl uint32) {
deadline: time.Now().Add(time.Second * time.Duration(ttl)), deadline: time.Now().Add(time.Second * time.Duration(ttl)),
} }
} }
type blockup struct {
tun.Device
ch chan tun.Event
canup *atomic.Bool
}
func newblockup(t tun.Device) *blockup {
ch := make(chan tun.Event, cap(t.Events()))
canup := &atomic.Bool{}
go func() {
for e := range t.Events() {
if e&tun.EventUp != 0 && !canup.Load() {
continue
}
select {
case ch <- e:
default:
}
}
close(ch)
}()
return &blockup{Device: t, ch: ch, canup: canup}
}
func (b *blockup) Events() <-chan tun.Event {
return b.ch
}
+1 -1
View File
@@ -255,7 +255,7 @@ func (s *Server) Start() error {
if s.src.Address.Family().IsDomain() { if s.src.Address.Family().IsDomain() {
return errors.New("address is domain") return errors.New("address is domain")
} }
listenFunc := func() (net.PacketConn, error) { listenFunc := func(context.Context) (net.PacketConn, error) {
var pktConn net.PacketConn var pktConn net.PacketConn
var err error var err error
if s.streamSettings.FinalMask != nil { if s.streamSettings.FinalMask != nil {