mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-16 12:34:01 +00:00
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:
+11
-14
@@ -16,8 +16,9 @@ import (
|
||||
)
|
||||
|
||||
type bind struct {
|
||||
ctx context.Context
|
||||
resolveFunc func(host string) (net.IP, error)
|
||||
listenFunc func() (net.PacketConn, error)
|
||||
listenFunc func(ctx context.Context) (net.PacketConn, error)
|
||||
downFunc func() error
|
||||
reserved []byte
|
||||
|
||||
@@ -26,6 +27,12 @@ type bind struct {
|
||||
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) {
|
||||
b.mu.Lock()
|
||||
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
|
||||
}
|
||||
|
||||
c, err := b.listenFunc()
|
||||
c, err := b.listenFunc(b.ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -52,12 +59,9 @@ func (b *bind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err
|
||||
case <-ch:
|
||||
default:
|
||||
errors.LogErrorInner(context.Background(), err, "unexpected closed")
|
||||
b.mu.Lock()
|
||||
downFunc := b.downFunc
|
||||
b.mu.Unlock()
|
||||
if downFunc != nil {
|
||||
if b.downFunc != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
+165
-149
@@ -7,6 +7,7 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
@@ -31,18 +32,14 @@ import (
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
conf *DeviceConfig
|
||||
policyManager policy.Manager
|
||||
dns dns.Client
|
||||
p policy.Manager
|
||||
|
||||
streamSettings *internet.MemoryStreamConfig
|
||||
uplinkCounter stats.Counter
|
||||
downlinkCounter stats.Counter
|
||||
|
||||
tun tun.Device
|
||||
tnet *Net
|
||||
dev *device.Device
|
||||
mu sync.Mutex
|
||||
canup *atomic.Bool
|
||||
tnet *Net
|
||||
bind *bind
|
||||
dev *device.Device
|
||||
ipc string
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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{
|
||||
conf: conf,
|
||||
policyManager: p,
|
||||
dns: d,
|
||||
p: p,
|
||||
|
||||
streamSettings: streamSettings,
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
|
||||
tun: tun,
|
||||
tnet: tnet,
|
||||
canup: blockup.canup,
|
||||
tnet: tnet,
|
||||
bind: bind,
|
||||
dev: dev,
|
||||
ipc: cfg.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -149,7 +215,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
ob.CanSpliceCopy = 3
|
||||
dialer.SetOutboundGateway(ctx, ob)
|
||||
|
||||
if err := h.init(ctx); err != nil {
|
||||
if err := h.up(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -159,7 +225,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
newCtx, newCancel = context.WithCancel(context.Background())
|
||||
}
|
||||
|
||||
sessionPolicy := h.policyManager.ForLevel(0)
|
||||
sessionPolicy := h.p.ForLevel(0)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
timer := signal.CancelAfterInactivity(ctx, func() {
|
||||
cancel()
|
||||
@@ -234,150 +300,72 @@ func (h *Handler) Close() (err error) {
|
||||
if h.dev != nil {
|
||||
h.dev.Close()
|
||||
h.dev = nil
|
||||
h.tun = nil
|
||||
} else if h.tun != nil {
|
||||
h.tun.Close()
|
||||
h.tun = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) init(ctx context.Context) error {
|
||||
func (h *Handler) up(ctx context.Context) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if h.tun == nil {
|
||||
if h.dev == nil {
|
||||
return errors.New("closed")
|
||||
}
|
||||
if h.dev != nil {
|
||||
return h.dev.Up()
|
||||
h.bind.SetCtx(ctx)
|
||||
if !h.canup.Load() {
|
||||
h.canup.Store(true)
|
||||
}
|
||||
resolveFunc := h.resolveLocal
|
||||
listenFunc := func() (net.PacketConn, error) {
|
||||
dest, err := net.ParseDestination("udp:" + h.conf.Peers[0].Endpoint)
|
||||
if len(h.ipc) > 0 {
|
||||
if err := h.dev.IpcSet(h.ipc); err != nil {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
var pktConn net.PacketConn
|
||||
if h.streamSettings.FinalMask != nil {
|
||||
conn, err := h.streamSettings.FinalMask.DialUDP(ctx, dest)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to dial to dest").Base(err)
|
||||
got := ips
|
||||
if 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)
|
||||
}
|
||||
}
|
||||
pktConn = conn.(*net.PacketConnWrapper).PacketConn
|
||||
} else {
|
||||
conn, err := internet.DialSystem(ctx, dest, h.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 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 {
|
||||
switch 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
|
||||
}
|
||||
}
|
||||
if len(got) == 0 {
|
||||
return nil, dns.ErrEmptyResponse
|
||||
}
|
||||
}
|
||||
if len(got) == 0 {
|
||||
return nil, dns.ErrEmptyResponse
|
||||
}
|
||||
return got[dice.Roll(len(got))], nil
|
||||
}
|
||||
return got[dice.Roll(len(got))], nil
|
||||
}
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ func (s *Server) Start() error {
|
||||
if s.src.Address.Family().IsDomain() {
|
||||
return errors.New("address is domain")
|
||||
}
|
||||
listenFunc := func() (net.PacketConn, error) {
|
||||
listenFunc := func(context.Context) (net.PacketConn, error) {
|
||||
var pktConn net.PacketConn
|
||||
var err error
|
||||
if s.streamSettings.FinalMask != nil {
|
||||
|
||||
Reference in New Issue
Block a user