Compare commits

..

7 Commits

Author SHA1 Message Date
世界
7bd004f141 Skip strict routing on Windows versions below 10 2026-01-17 18:38:29 +08:00
世界
d44e0c68d4 Fix darwin batch read not exit on stop 2026-01-15 02:03:38 +08:00
世界
bc23daa800 Fix TUN interface restart fails with existing addresses 2026-01-04 23:24:54 +08:00
世界
e6c219a61e Fix race condition in ReadPacket 2025-12-17 19:45:13 +08:00
世界
ddc824fb9c redirect: Fix compatibility with /product/bin/su 2025-10-21 21:26:31 +08:00
世界
e229d7041e Fix race codes 2025-09-12 18:02:37 +08:00
世界
e2503223dc Prevent panic when wintun dll fails to load 2025-09-09 16:35:02 +08:00
6 changed files with 65 additions and 12 deletions

View File

@@ -39,6 +39,10 @@ func closeAdapter(wintun *Adapter) {
// deterministically. If it is set to nil, the GUID is chosen by the system at random,
// and hence a new NLA entry is created for each new adapter.
func CreateAdapter(name string, tunnelType string, requestedGUID *windows.GUID) (wintun *Adapter, err error) {
err = procWintunCloseAdapter.Find()
if err != nil {
return
}
var name16 *uint16
name16, err = windows.UTF16PtrFromString(name)
if err != nil {

View File

@@ -69,6 +69,7 @@ func (r *autoRedirect) Start() error {
r.androidSu = true
for _, suPath := range []string{
"su",
"/product/bin/su",
"/system/bin/su",
} {
r.suPath, err = exec.LookPath(suPath)

View File

@@ -11,6 +11,7 @@ import (
)
type TCPNat struct {
timeout time.Duration
portIndex uint16
portAccess sync.RWMutex
addrAccess sync.RWMutex
@@ -19,6 +20,7 @@ type TCPNat struct {
}
type TCPSession struct {
sync.Mutex
Source netip.AddrPort
Destination netip.AddrPort
LastActive time.Time
@@ -26,38 +28,41 @@ type TCPSession struct {
func NewNat(ctx context.Context, timeout time.Duration) *TCPNat {
natMap := &TCPNat{
timeout: timeout,
portIndex: 10000,
addrMap: make(map[netip.AddrPort]uint16),
portMap: make(map[uint16]*TCPSession),
}
go natMap.loopCheckTimeout(ctx, timeout)
go natMap.loopCheckTimeout(ctx)
return natMap
}
func (n *TCPNat) loopCheckTimeout(ctx context.Context, timeout time.Duration) {
ticker := time.NewTicker(timeout)
func (n *TCPNat) loopCheckTimeout(ctx context.Context) {
ticker := time.NewTicker(n.timeout)
defer ticker.Stop()
for {
select {
case <-ticker.C:
n.checkTimeout(timeout)
n.checkTimeout()
case <-ctx.Done():
return
}
}
}
func (n *TCPNat) checkTimeout(timeout time.Duration) {
func (n *TCPNat) checkTimeout() {
now := time.Now()
n.portAccess.Lock()
defer n.portAccess.Unlock()
n.addrAccess.Lock()
defer n.addrAccess.Unlock()
for natPort, session := range n.portMap {
if now.Sub(session.LastActive) > timeout {
session.Lock()
if now.Sub(session.LastActive) > n.timeout {
delete(n.addrMap, session.Source)
delete(n.portMap, natPort)
}
session.Unlock()
}
}
@@ -66,7 +71,11 @@ func (n *TCPNat) LookupBack(port uint16) *TCPSession {
session := n.portMap[port]
n.portAccess.RUnlock()
if session != nil {
session.LastActive = time.Now()
session.Lock()
if time.Since(session.LastActive) > time.Second {
session.LastActive = time.Now()
}
session.Unlock()
}
return session
}

View File

@@ -152,7 +152,10 @@ func (t *NativeTun) Start() error {
func (t *NativeTun) Close() error {
defer flushDNSCache()
return E.Errors(t.unsetRoutes(), t.tunFile.Close())
t.stopFd.Stop()
err := E.Errors(t.unsetRoutes(), t.tunFile.Close())
t.stopFd.Close()
return err
}
func (t *NativeTun) Read(p []byte) (n int, err error) {
@@ -347,6 +350,9 @@ func (t *NativeTun) BatchRead() ([]*buf.Buffer, error) {
t.buffers = t.buffers[:0]
return nil, errno
}
if n < 0 {
return nil, os.ErrClosed
}
if n < 1 {
return nil, nil
}

View File

@@ -130,7 +130,7 @@ func (t *NativeTun) configure(tunLink netlink.Link) error {
for _, address := range t.options.Inet4Address {
addr4, _ := netlink.ParseAddr(address.String())
err = netlink.AddrAdd(tunLink, addr4)
if err != nil {
if err != nil && !errors.Is(err, unix.EEXIST) {
return err
}
}
@@ -139,7 +139,7 @@ func (t *NativeTun) configure(tunLink netlink.Link) error {
for _, address := range t.options.Inet6Address {
addr6, _ := netlink.ParseAddr(address.String())
err = netlink.AddrAdd(tunLink, addr6)
if err != nil {
if err != nil && !errors.Is(err, unix.EEXIST) {
return err
}
}
@@ -315,6 +315,7 @@ func (t *NativeTun) Close() error {
if t.interfaceCallback != nil {
t.options.InterfaceMonitor.UnregisterCallback(t.interfaceCallback)
}
t.unsetAddresses()
return E.Errors(t.unsetRoute(), t.unsetRules(), common.Close(common.PtrOrNil(t.tunFile)))
}
@@ -1003,6 +1004,24 @@ func (t *NativeTun) unsetRules() error {
return nil
}
func (t *NativeTun) unsetAddresses() {
if t.options.FileDescriptor > 0 {
return
}
tunLink, err := netlink.LinkByName(t.options.Name)
if err != nil {
return
}
for _, address := range t.options.Inet4Address {
addr, _ := netlink.ParseAddr(address.String())
_ = netlink.AddrDel(tunLink, addr)
}
for _, address := range t.options.Inet6Address {
addr, _ := netlink.ParseAddr(address.String())
_ = netlink.AddrDel(tunLink, addr)
}
}
func (t *NativeTun) resetRules() error {
t.unsetRules()
return t.setRules()

View File

@@ -181,6 +181,13 @@ func (t *NativeTun) Start() error {
return err
}
if t.options.StrictRoute {
major, _, _ := windows.RtlGetNtVersionNumbers()
if major < 10 {
if t.options.Logger != nil {
t.options.Logger.Warn("strict routing is not supported on Windows versions below 10")
}
return nil
}
var engine uintptr
session := &winsys.FWPM_SESSION0{Flags: winsys.FWPM_SESSION_FLAG_DYNAMIC}
err := winsys.FwpmEngineOpen0(nil, winsys.RPC_C_AUTHN_DEFAULT, nil, session, unsafe.Pointer(&engine))
@@ -395,15 +402,16 @@ retry:
func (t *NativeTun) ReadPacket() ([]byte, func(), error) {
t.running.Add(1)
defer t.running.Done()
retry:
if t.close.Load() == 1 {
t.running.Done()
return nil, nil, os.ErrClosed
}
start := nanotime()
shouldSpin := t.rate.current.Load() >= spinloopRateThreshold && uint64(start-t.rate.nextStartTime.Load()) <= rateMeasurementGranularity*2
for {
if t.close.Load() == 1 {
t.running.Done()
return nil, nil, os.ErrClosed
}
packet, err := t.session.ReceivePacket()
@@ -411,7 +419,10 @@ retry:
case nil:
packetSize := len(packet)
t.rate.update(uint64(packetSize))
return packet, func() { t.session.ReleaseReceivePacket(packet) }, nil
return packet, func() {
t.session.ReleaseReceivePacket(packet)
t.running.Done()
}, nil
case windows.ERROR_NO_MORE_ITEMS:
if !shouldSpin || uint64(nanotime()-start) >= spinloopDuration {
windows.WaitForSingleObject(t.readWait, windows.INFINITE)
@@ -420,10 +431,13 @@ retry:
procyield(1)
continue
case windows.ERROR_HANDLE_EOF:
t.running.Done()
return nil, nil, os.ErrClosed
case windows.ERROR_INVALID_DATA:
t.running.Done()
return nil, nil, errors.New("send ring corrupt")
}
t.running.Done()
return nil, nil, fmt.Errorf("read failed: %w", err)
}
}