mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-30 15:07:04 +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 {
|
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
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user