Feature: use native syscall to bind interface on Linux and macOS
This commit is contained in:
parent
2321e9139d
commit
50b3d497f6
6 changed files with 205 additions and 135 deletions
104
component/dialer/bind.go
Normal file
104
component/dialer/bind.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errPlatformNotSupport = errors.New("unsupport platform")
|
||||||
|
)
|
||||||
|
|
||||||
|
func lookupTCPAddr(ip net.IP, addrs []net.Addr) (*net.TCPAddr, error) {
|
||||||
|
ipv4 := ip.To4() != nil
|
||||||
|
|
||||||
|
for _, elm := range addrs {
|
||||||
|
addr, ok := elm.(*net.IPNet)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addrV4 := addr.IP.To4() != nil
|
||||||
|
|
||||||
|
if addrV4 && ipv4 {
|
||||||
|
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
|
||||||
|
} else if !addrV4 && !ipv4 {
|
||||||
|
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrAddrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) {
|
||||||
|
ipv4 := ip.To4() != nil
|
||||||
|
|
||||||
|
for _, elm := range addrs {
|
||||||
|
addr, ok := elm.(*net.IPNet)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addrV4 := addr.IP.To4() != nil
|
||||||
|
|
||||||
|
if addrV4 && ipv4 {
|
||||||
|
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
|
||||||
|
} else if !addrV4 && !ipv4 {
|
||||||
|
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrAddrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name string) error {
|
||||||
|
iface, err := net.InterfaceByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp4", "tcp6":
|
||||||
|
if addr, err := lookupTCPAddr(ip, addrs); err == nil {
|
||||||
|
dialer.LocalAddr = addr
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
if addr, err := lookupUDPAddr(ip, addrs); err == nil {
|
||||||
|
dialer.LocalAddr = addr
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fallbackBindToListenConfig(name string) (string, error) {
|
||||||
|
iface, err := net.InterfaceByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, elm := range addrs {
|
||||||
|
addr, ok := elm.(*net.IPNet)
|
||||||
|
if !ok || addr.IP.To4() == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.JoinHostPort(addr.IP.String(), "0"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", ErrAddrNotFound
|
||||||
|
}
|
46
component/dialer/bind_darwin.go
Normal file
46
component/dialer/bind_darwin.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
||||||
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer.Control = func(network, address string, c syscall.RawConn) error {
|
||||||
|
return c.Control(func(fd uintptr) {
|
||||||
|
switch network {
|
||||||
|
case "tcp4", "udp4":
|
||||||
|
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, iface.Index)
|
||||||
|
case "tcp6", "udp6":
|
||||||
|
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, iface.Index)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
||||||
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lc.Control = func(network, address string, c syscall.RawConn) error {
|
||||||
|
return c.Control(func(fd uintptr) {
|
||||||
|
switch network {
|
||||||
|
case "tcp4", "udp4":
|
||||||
|
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, iface.Index)
|
||||||
|
case "tcp6", "udp6":
|
||||||
|
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, iface.Index)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
26
component/dialer/bind_linux.go
Normal file
26
component/dialer/bind_linux.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
||||||
|
dialer.Control = func(network, address string, c syscall.RawConn) error {
|
||||||
|
return c.Control(func(fd uintptr) {
|
||||||
|
syscall.BindToDevice(int(fd), ifaceName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
||||||
|
lc.Control = func(network, address string, c syscall.RawConn) error {
|
||||||
|
return c.Control(func(fd uintptr) {
|
||||||
|
syscall.BindToDevice(int(fd), ifaceName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
13
component/dialer/bind_others.go
Normal file
13
component/dialer/bind_others.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// +build !linux,!darwin
|
||||||
|
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
|
||||||
|
return errNotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
|
||||||
|
return errNotSupport
|
||||||
|
}
|
|
@ -19,17 +19,6 @@ func Dialer() (*net.Dialer, error) {
|
||||||
return dialer, nil
|
return dialer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListenConfig() (*net.ListenConfig, error) {
|
|
||||||
cfg := &net.ListenConfig{}
|
|
||||||
if ListenConfigHook != nil {
|
|
||||||
if err := ListenConfigHook(cfg); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Dial(network, address string) (net.Conn, error) {
|
func Dial(network, address string) (net.Conn, error) {
|
||||||
return DialContext(context.Background(), network, address)
|
return DialContext(context.Background(), network, address)
|
||||||
}
|
}
|
||||||
|
@ -73,19 +62,16 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListenPacket(network, address string) (net.PacketConn, error) {
|
func ListenPacket(network, address string) (net.PacketConn, error) {
|
||||||
lc, err := ListenConfig()
|
cfg := &net.ListenConfig{}
|
||||||
if err != nil {
|
if ListenPacketHook != nil {
|
||||||
return nil, err
|
var err error
|
||||||
}
|
address, err = ListenPacketHook(cfg, address)
|
||||||
|
|
||||||
if ListenPacketHook != nil && address == "" {
|
|
||||||
ip, err := ListenPacketHook()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
address = net.JoinHostPort(ip.String(), "0")
|
|
||||||
}
|
}
|
||||||
return lc.ListenPacket(context.Background(), network, address)
|
|
||||||
|
return cfg.ListenPacket(context.Background(), network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dualStackDialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
func dualStackDialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
|
|
@ -3,20 +3,15 @@ package dialer
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DialerHookFunc = func(dialer *net.Dialer) error
|
type DialerHookFunc = func(dialer *net.Dialer) error
|
||||||
type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) error
|
type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) error
|
||||||
type ListenConfigHookFunc = func(*net.ListenConfig) error
|
type ListenPacketHookFunc = func(lc *net.ListenConfig, address string) (string, error)
|
||||||
type ListenPacketHookFunc = func() (net.IP, error)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DialerHook DialerHookFunc
|
DialerHook DialerHookFunc
|
||||||
DialHook DialHookFunc
|
DialHook DialHookFunc
|
||||||
ListenConfigHook ListenConfigHookFunc
|
|
||||||
ListenPacketHook ListenPacketHookFunc
|
ListenPacketHook ListenPacketHookFunc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,124 +20,24 @@ var (
|
||||||
ErrNetworkNotSupport = errors.New("network not support")
|
ErrNetworkNotSupport = errors.New("network not support")
|
||||||
)
|
)
|
||||||
|
|
||||||
func lookupTCPAddr(ip net.IP, addrs []net.Addr) (*net.TCPAddr, error) {
|
|
||||||
ipv4 := ip.To4() != nil
|
|
||||||
|
|
||||||
for _, elm := range addrs {
|
|
||||||
addr, ok := elm.(*net.IPNet)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
addrV4 := addr.IP.To4() != nil
|
|
||||||
|
|
||||||
if addrV4 && ipv4 {
|
|
||||||
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
|
|
||||||
} else if !addrV4 && !ipv4 {
|
|
||||||
return &net.TCPAddr{IP: addr.IP, Port: 0}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrAddrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) {
|
|
||||||
ipv4 := ip.To4() != nil
|
|
||||||
|
|
||||||
for _, elm := range addrs {
|
|
||||||
addr, ok := elm.(*net.IPNet)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
addrV4 := addr.IP.To4() != nil
|
|
||||||
|
|
||||||
if addrV4 && ipv4 {
|
|
||||||
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
|
|
||||||
} else if !addrV4 && !ipv4 {
|
|
||||||
return &net.UDPAddr{IP: addr.IP, Port: 0}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrAddrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func ListenPacketWithInterface(name string) ListenPacketHookFunc {
|
func ListenPacketWithInterface(name string) ListenPacketHookFunc {
|
||||||
single := singledo.NewSingle(5 * time.Second)
|
return func(lc *net.ListenConfig, address string) (string, error) {
|
||||||
|
err := bindIfaceToListenConfig(lc, name)
|
||||||
return func() (net.IP, error) {
|
if err == errPlatformNotSupport {
|
||||||
elm, err, _ := single.Do(func() (interface{}, error) {
|
address, err = fallbackBindToListenConfig(name)
|
||||||
iface, err := net.InterfaceByName(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs, err := iface.Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return addrs, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addrs := elm.([]net.Addr)
|
return address, err
|
||||||
|
|
||||||
for _, elm := range addrs {
|
|
||||||
addr, ok := elm.(*net.IPNet)
|
|
||||||
if !ok || addr.IP.To4() == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return addr.IP, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrAddrNotFound
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func DialerWithInterface(name string) DialHookFunc {
|
func DialerWithInterface(name string) DialHookFunc {
|
||||||
single := singledo.NewSingle(5 * time.Second)
|
|
||||||
|
|
||||||
return func(dialer *net.Dialer, network string, ip net.IP) error {
|
return func(dialer *net.Dialer, network string, ip net.IP) error {
|
||||||
elm, err, _ := single.Do(func() (interface{}, error) {
|
err := bindIfaceToDialer(dialer, name)
|
||||||
iface, err := net.InterfaceByName(name)
|
if err == errPlatformNotSupport {
|
||||||
if err != nil {
|
err = fallbackBindToDialer(dialer, network, ip, name)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs, err := iface.Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return addrs, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addrs := elm.([]net.Addr)
|
return err
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
if addr, err := lookupTCPAddr(ip, addrs); err == nil {
|
|
||||||
dialer.LocalAddr = addr
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
if addr, err := lookupUDPAddr(ip, addrs); err == nil {
|
|
||||||
dialer.LocalAddr = addr
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue