diff --git a/common/control/bind.go b/common/control/bind.go new file mode 100644 index 0000000..0510848 --- /dev/null +++ b/common/control/bind.go @@ -0,0 +1,43 @@ +package control + +import ( + "net" + + E "github.com/sagernet/sing/common/exceptions" +) + +type BindManager interface { + IndexByName(name string) (int, error) + Update() error +} + +type simpleBindManager struct { + interfaceIndexByName map[string]int +} + +func (m *simpleBindManager) IndexByName(name string) (int, error) { + if index, loaded := m.interfaceIndexByName[name]; loaded { + return index, nil + } + err := m.Update() + if err != nil { + return 0, err + } + if index, loaded := m.interfaceIndexByName[name]; loaded { + return index, nil + } + return 0, E.New("interface ", name, " not found") +} + +func (m *simpleBindManager) Update() error { + interfaces, err := net.Interfaces() + if err != nil { + return err + } + interfaceIndexByName := make(map[string]int) + for _, iface := range interfaces { + interfaceIndexByName[iface.Name] = iface.Index + } + m.interfaceIndexByName = interfaceIndexByName + return nil +} diff --git a/common/control/bind_linux.go b/common/control/bind_linux.go index 883acca..6c7dd4e 100644 --- a/common/control/bind_linux.go +++ b/common/control/bind_linux.go @@ -6,7 +6,11 @@ import ( E "github.com/sagernet/sing/common/exceptions" ) -func BindToInterface(interfaceName string) Func { +func NewBindManager() BindManager { + return nil +} + +func BindToInterface(manager BindManager, interfaceName string) Func { return func(network, address string, conn syscall.RawConn) error { var innerErr error err := conn.Control(func(fd uintptr) { @@ -15,3 +19,21 @@ func BindToInterface(interfaceName string) Func { return E.Errors(innerErr, err) } } + +func BindToInterfaceFunc(manager BindManager, interfaceNameFunc func() string) Func { + return func(network, address string, conn syscall.RawConn) error { + interfaceName := interfaceNameFunc() + if interfaceName == "" { + return nil + } + var innerErr error + err := conn.Control(func(fd uintptr) { + innerErr = syscall.BindToDevice(int(fd), interfaceName) + }) + return E.Errors(innerErr, err) + } +} + +func BindToInterfaceIndexFunc(interfaceIndexFunc func() int) Func { + return nil +} diff --git a/common/control/bind_other.go b/common/control/bind_other.go index b8f4ab5..5ac89dd 100644 --- a/common/control/bind_other.go +++ b/common/control/bind_other.go @@ -1,7 +1,19 @@ -//go:build !linux +//go:build !linux && !windows package control -func BindToInterface(interfaceName string) Func { +func NewBindManager() BindManager { + return nil +} + +func BindToInterface(manager BindManager, interfaceName string) Func { + return nil +} + +func BindToInterfaceFunc(manager BindManager, interfaceNameFunc func() string) Func { + return nil +} + +func BindToInterfaceIndexFunc(interfaceIndexFunc func() int) Func { return nil } diff --git a/common/control/bind_windows.go b/common/control/bind_windows.go new file mode 100644 index 0000000..4242110 --- /dev/null +++ b/common/control/bind_windows.go @@ -0,0 +1,98 @@ +package control + +import ( + "encoding/binary" + "net" + "net/netip" + "syscall" + "unsafe" + + E "github.com/sagernet/sing/common/exceptions" +) + +const ( + IP_UNICAST_IF = 31 + IPV6_UNICAST_IF = 31 +) + +func NewBindManager() BindManager { + return &simpleBindManager{ + interfaceIndexByName: make(map[string]int), + } +} + +func bind4(handle syscall.Handle, ifaceIdx int) error { + var bytes [4]byte + binary.BigEndian.PutUint32(bytes[:], uint32(ifaceIdx)) + idx := *(*uint32)(unsafe.Pointer(&bytes[0])) + return syscall.SetsockoptInt(handle, syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx)) +} + +func bind6(handle syscall.Handle, ifaceIdx int) error { + return syscall.SetsockoptInt(handle, syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, ifaceIdx) +} + +func bindInterfaceIndex(network string, address string, conn syscall.RawConn, interfaceIndex int) error { + ipStr, _, err := net.SplitHostPort(address) + if err == nil { + if ip, err := netip.ParseAddr(ipStr); err == nil && !ip.IsGlobalUnicast() { + return err + } + } + var innerErr error + err = conn.Control(func(fd uintptr) { + handle := syscall.Handle(fd) + // handle ip empty, e.g. net.Listen("udp", ":0") + if ipStr == "" { + innerErr = bind4(handle, interfaceIndex) + if innerErr != nil { + return + } + // try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6 + bind6(handle, interfaceIndex) + return + } + + switch network { + case "tcp4", "udp4", "ip4": + innerErr = bind4(handle, interfaceIndex) + case "tcp6", "udp6": + innerErr = bind6(handle, interfaceIndex) + } + }) + return E.Errors(innerErr, err) +} + +func BindToInterface(manager BindManager, interfaceName string) Func { + return func(network, address string, conn syscall.RawConn) error { + index, err := manager.IndexByName(interfaceName) + if err != nil { + return err + } + return bindInterfaceIndex(network, address, conn, index) + } +} + +func BindToInterfaceFunc(manager BindManager, interfaceNameFunc func() string) Func { + return func(network, address string, conn syscall.RawConn) error { + interfaceName := interfaceNameFunc() + if interfaceName == "" { + return nil + } + index, err := manager.IndexByName(interfaceName) + if err != nil { + return err + } + return bindInterfaceIndex(network, address, conn, index) + } +} + +func BindToInterfaceIndexFunc(interfaceIndexFunc func() int) Func { + return func(network, address string, conn syscall.RawConn) error { + index := interfaceIndexFunc() + if index == -1 { + return nil + } + return bindInterfaceIndex(network, address, conn, index) + } +} diff --git a/common/control/reuse_other.go b/common/control/reuse_other.go index b0731d6..69c5be4 100644 --- a/common/control/reuse_other.go +++ b/common/control/reuse_other.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build !linux && !windows package control diff --git a/common/control/reuse_windows.go b/common/control/reuse_windows.go new file mode 100644 index 0000000..6b1132c --- /dev/null +++ b/common/control/reuse_windows.go @@ -0,0 +1,17 @@ +package control + +import ( + "syscall" + + E "github.com/sagernet/sing/common/exceptions" +) + +func ReuseAddr() Func { + return func(network, address string, conn syscall.RawConn) error { + var innerErr error + err := conn.Control(func(fd uintptr) { + innerErr = syscall.SetsockoptInt(syscall.Handle(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + }) + return E.Errors(innerErr, err) + } +} diff --git a/format.go b/format.go index 6840452..7b30b9a 100644 --- a/format.go +++ b/format.go @@ -1,7 +1,7 @@ package box //go:generate go install -v mvdan.cc/gofumpt@latest -//go:generate go install -v github.com/daixiang0/gci@latest +//go:generate go install -v github.com/daixiang0/gci@v0.4.0 //go:generate gofumpt -l -w . //go:generate gofmt -s -w . //go:generate gci write -s "standard,prefix(github.com/sagernet/),default" .