mirror of
https://github.com/xjasonlyu/tun2socks.git
synced 2025-10-06 09:16:58 +08:00
Improve: use interface index for macos
This commit is contained in:
@@ -1,66 +0,0 @@
|
|||||||
package singledo
|
|
||||||
|
|
||||||
// Ref: github.com/Dreamacro/clash/common/singledo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type call struct {
|
|
||||||
wg sync.WaitGroup
|
|
||||||
val any
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Single struct {
|
|
||||||
mux sync.Mutex
|
|
||||||
last time.Time
|
|
||||||
wait time.Duration
|
|
||||||
call *call
|
|
||||||
result *Result
|
|
||||||
}
|
|
||||||
|
|
||||||
type Result struct {
|
|
||||||
Val any
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do single.Do likes sync.singleFlight
|
|
||||||
//lint:ignore ST1008 it likes sync.singleFlight
|
|
||||||
func (s *Single) Do(fn func() (any, error)) (v any, err error, shared bool) {
|
|
||||||
s.mux.Lock()
|
|
||||||
now := time.Now()
|
|
||||||
if now.Before(s.last.Add(s.wait)) {
|
|
||||||
s.mux.Unlock()
|
|
||||||
return s.result.Val, s.result.Err, true
|
|
||||||
}
|
|
||||||
|
|
||||||
if call := s.call; call != nil {
|
|
||||||
s.mux.Unlock()
|
|
||||||
call.wg.Wait()
|
|
||||||
return call.val, call.err, true
|
|
||||||
}
|
|
||||||
|
|
||||||
call := &call{}
|
|
||||||
call.wg.Add(1)
|
|
||||||
s.call = call
|
|
||||||
s.mux.Unlock()
|
|
||||||
call.val, call.err = fn()
|
|
||||||
call.wg.Done()
|
|
||||||
|
|
||||||
s.mux.Lock()
|
|
||||||
s.call = nil
|
|
||||||
s.result = &Result{call.val, call.err}
|
|
||||||
s.last = now
|
|
||||||
s.mux.Unlock()
|
|
||||||
return call.val, call.err, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Single) Reset() {
|
|
||||||
s.last = time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSingle(wait time.Duration) *Single {
|
|
||||||
return &Single{wait: wait}
|
|
||||||
}
|
|
@@ -1,69 +0,0 @@
|
|||||||
package singledo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBasic(t *testing.T) {
|
|
||||||
single := NewSingle(time.Millisecond * 30)
|
|
||||||
foo := 0
|
|
||||||
shardCount := atomic.NewInt32(0)
|
|
||||||
call := func() (any, error) {
|
|
||||||
foo++
|
|
||||||
time.Sleep(time.Millisecond * 5)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
const n = 5
|
|
||||||
wg.Add(n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
go func() {
|
|
||||||
_, _, shard := single.Do(call)
|
|
||||||
if shard {
|
|
||||||
shardCount.Inc()
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
assert.Equal(t, 1, foo)
|
|
||||||
assert.Equal(t, int32(4), shardCount.Load())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimer(t *testing.T) {
|
|
||||||
single := NewSingle(time.Millisecond * 30)
|
|
||||||
foo := 0
|
|
||||||
call := func() (any, error) {
|
|
||||||
foo++
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
single.Do(call)
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
_, _, shard := single.Do(call)
|
|
||||||
|
|
||||||
assert.Equal(t, 1, foo)
|
|
||||||
assert.True(t, shard)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReset(t *testing.T) {
|
|
||||||
single := NewSingle(time.Millisecond * 30)
|
|
||||||
foo := 0
|
|
||||||
call := func() (any, error) {
|
|
||||||
foo++
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
single.Do(call)
|
|
||||||
single.Reset()
|
|
||||||
single.Do(call)
|
|
||||||
|
|
||||||
assert.Equal(t, 2, foo)
|
|
||||||
}
|
|
@@ -9,8 +9,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DefaultInterfaceName = atomic.NewString("")
|
DefaultInterfaceName = atomic.NewString("")
|
||||||
DefaultRoutingMark = atomic.NewInt32(0)
|
DefaultInterfaceIndex = atomic.NewInt32(0)
|
||||||
|
DefaultRoutingMark = atomic.NewInt32(0)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
@@ -19,6 +20,11 @@ type Options struct {
|
|||||||
// from that particular interface are processed by the socket.
|
// from that particular interface are processed by the socket.
|
||||||
InterfaceName string
|
InterfaceName string
|
||||||
|
|
||||||
|
// InterfaceIndex is the index of interface/device to bind.
|
||||||
|
// It is almost the same as InterfaceName except it uses the
|
||||||
|
// index of the interface instead of the name.
|
||||||
|
InterfaceIndex int
|
||||||
|
|
||||||
// RoutingMark is the mark for each packet sent through this
|
// RoutingMark is the mark for each packet sent through this
|
||||||
// socket. Changing the mark can be used for mark-based routing
|
// socket. Changing the mark can be used for mark-based routing
|
||||||
// without netfilter or for packet filtering.
|
// without netfilter or for packet filtering.
|
||||||
@@ -27,8 +33,9 @@ type Options struct {
|
|||||||
|
|
||||||
func DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
func DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
return DialContextWithOptions(ctx, network, address, &Options{
|
return DialContextWithOptions(ctx, network, address, &Options{
|
||||||
InterfaceName: DefaultInterfaceName.Load(),
|
InterfaceName: DefaultInterfaceName.Load(),
|
||||||
RoutingMark: int(DefaultRoutingMark.Load()),
|
InterfaceIndex: int(DefaultInterfaceIndex.Load()),
|
||||||
|
RoutingMark: int(DefaultRoutingMark.Load()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +50,9 @@ func DialContextWithOptions(ctx context.Context, network, address string, opts *
|
|||||||
|
|
||||||
func ListenPacket(network, address string) (net.PacketConn, error) {
|
func ListenPacket(network, address string) (net.PacketConn, error) {
|
||||||
return ListenPacketWithOptions(network, address, &Options{
|
return ListenPacketWithOptions(network, address, &Options{
|
||||||
InterfaceName: DefaultInterfaceName.Load(),
|
InterfaceName: DefaultInterfaceName.Load(),
|
||||||
RoutingMark: int(DefaultRoutingMark.Load()),
|
InterfaceIndex: int(DefaultInterfaceIndex.Load()),
|
||||||
|
RoutingMark: int(DefaultRoutingMark.Load()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,25 +3,10 @@ package dialer
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/xjasonlyu/tun2socks/v2/common/singledo"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
var interfaces = singledo.NewSingle(30 * time.Second)
|
|
||||||
|
|
||||||
func resolveInterfaceByName(name string) (*net.Interface, error) {
|
|
||||||
value, err, _ := interfaces.Do(func() (any, error) {
|
|
||||||
return net.InterfaceByName(name)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return value.(*net.Interface), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) {
|
func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) {
|
||||||
if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) {
|
if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) {
|
||||||
return
|
return
|
||||||
@@ -29,24 +14,23 @@ func setSocketOptions(network, address string, c syscall.RawConn, opts *Options)
|
|||||||
|
|
||||||
var innerErr error
|
var innerErr error
|
||||||
err = c.Control(func(fd uintptr) {
|
err = c.Control(func(fd uintptr) {
|
||||||
// must be GlobalUnicast.
|
|
||||||
host, _, _ := net.SplitHostPort(address)
|
host, _, _ := net.SplitHostPort(address)
|
||||||
if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() {
|
if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.InterfaceName != "" {
|
if opts.InterfaceIndex == 0 && opts.InterfaceName != "" {
|
||||||
var iface *net.Interface
|
if iface, err := net.InterfaceByName(opts.InterfaceName); err == nil {
|
||||||
iface, innerErr = resolveInterfaceByName(opts.InterfaceName)
|
opts.InterfaceIndex = iface.Index
|
||||||
if innerErr != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.InterfaceIndex != 0 {
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp4", "udp4":
|
case "tcp4", "udp4":
|
||||||
innerErr = unix.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, iface.Index)
|
innerErr = unix.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, opts.InterfaceIndex)
|
||||||
case "tcp6", "udp6":
|
case "tcp6", "udp6":
|
||||||
innerErr = unix.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, iface.Index)
|
innerErr = unix.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, opts.InterfaceIndex)
|
||||||
}
|
}
|
||||||
if innerErr != nil {
|
if innerErr != nil {
|
||||||
return
|
return
|
||||||
|
@@ -14,12 +14,17 @@ func setSocketOptions(network, address string, c syscall.RawConn, opts *Options)
|
|||||||
|
|
||||||
var innerErr error
|
var innerErr error
|
||||||
err = c.Control(func(fd uintptr) {
|
err = c.Control(func(fd uintptr) {
|
||||||
// must be GlobalUnicast.
|
|
||||||
host, _, _ := net.SplitHostPort(address)
|
host, _, _ := net.SplitHostPort(address)
|
||||||
if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() {
|
if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.InterfaceName == "" && opts.InterfaceIndex != 0 {
|
||||||
|
if iface, err := net.InterfaceByIndex(opts.InterfaceIndex); err == nil {
|
||||||
|
opts.InterfaceName = iface.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if opts.InterfaceName != "" {
|
if opts.InterfaceName != "" {
|
||||||
if innerErr = unix.BindToDevice(int(fd), opts.InterfaceName); innerErr != nil {
|
if innerErr = unix.BindToDevice(int(fd), opts.InterfaceName); innerErr != nil {
|
||||||
return
|
return
|
||||||
|
@@ -3,6 +3,7 @@ package engine
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
|
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
|
||||||
@@ -97,7 +98,12 @@ func general(k *Key) error {
|
|||||||
log.SetLevel(level)
|
log.SetLevel(level)
|
||||||
|
|
||||||
if k.Interface != "" {
|
if k.Interface != "" {
|
||||||
dialer.DefaultInterfaceName.Store(k.Interface)
|
iface, err := net.InterfaceByName(k.Interface)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dialer.DefaultInterfaceName.Store(iface.Name)
|
||||||
|
dialer.DefaultInterfaceIndex.Store(int32(iface.Index))
|
||||||
log.Infof("[DIALER] bind to interface: %s", k.Interface)
|
log.Infof("[DIALER] bind to interface: %s", k.Interface)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user