diff --git a/common/control/interface.go b/common/control/interface.go index f778a4b..01f07b4 100644 --- a/common/control/interface.go +++ b/common/control/interface.go @@ -3,6 +3,7 @@ package control import ( "syscall" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" ) @@ -30,6 +31,14 @@ func Conn(conn syscall.Conn, block func(fd uintptr) error) error { return Raw(rawConn, block) } +func Conn0[T any](conn syscall.Conn, block func(fd uintptr) (T, error)) (T, error) { + rawConn, err := conn.SyscallConn() + if err != nil { + return common.DefaultValue[T](), err + } + return Raw0[T](rawConn, block) +} + func Raw(rawConn syscall.RawConn, block func(fd uintptr) error) error { var innerErr error err := rawConn.Control(func(fd uintptr) { @@ -37,3 +46,14 @@ func Raw(rawConn syscall.RawConn, block func(fd uintptr) error) error { }) return E.Errors(innerErr, err) } + +func Raw0[T any](rawConn syscall.RawConn, block func(fd uintptr) (T, error)) (T, error) { + var ( + value T + innerErr error + ) + err := rawConn.Control(func(fd uintptr) { + value, innerErr = block(fd) + }) + return value, E.Errors(innerErr, err) +} diff --git a/common/control/redirect_darwin.go b/common/control/redirect_darwin.go new file mode 100644 index 0000000..50db3d8 --- /dev/null +++ b/common/control/redirect_darwin.go @@ -0,0 +1,58 @@ +package control + +import ( + "encoding/binary" + "net" + "net/netip" + "syscall" + "unsafe" + + M "github.com/sagernet/sing/common/metadata" + + "golang.org/x/sys/unix" +) + +const ( + PF_OUT = 0x2 + DIOCNATLOOK = 0xc0544417 +) + +func GetOriginalDestination(conn net.Conn) (netip.AddrPort, error) { + pfFd, err := syscall.Open("/dev/pf", 0, syscall.O_RDONLY) + if err != nil { + return netip.AddrPort{}, err + } + defer syscall.Close(pfFd) + nl := struct { + saddr, daddr, rsaddr, rdaddr [16]byte + sxport, dxport, rsxport, rdxport [4]byte + af, proto, protoVariant, direction uint8 + }{ + af: syscall.AF_INET, + proto: syscall.IPPROTO_TCP, + direction: PF_OUT, + } + localAddr := M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() + removeAddr := M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap() + if localAddr.IsIPv4() { + copy(nl.saddr[:net.IPv4len], removeAddr.Addr.AsSlice()) + copy(nl.daddr[:net.IPv4len], localAddr.Addr.AsSlice()) + nl.af = syscall.AF_INET + } else { + copy(nl.saddr[:], removeAddr.Addr.AsSlice()) + copy(nl.daddr[:], localAddr.Addr.AsSlice()) + nl.af = syscall.AF_INET6 + } + binary.BigEndian.PutUint16(nl.sxport[:], removeAddr.Port) + binary.BigEndian.PutUint16(nl.dxport[:], localAddr.Port) + if _, _, errno := unix.Syscall(syscall.SYS_IOCTL, uintptr(pfFd), DIOCNATLOOK, uintptr(unsafe.Pointer(&nl))); errno != 0 { + return netip.AddrPort{}, errno + } + var address netip.Addr + if nl.af == unix.AF_INET { + address = M.AddrFromIP(nl.rdaddr[:net.IPv4len]) + } else { + address = netip.AddrFrom16(nl.rdaddr) + } + return netip.AddrPortFrom(address, binary.BigEndian.Uint16(nl.rdxport[:])), nil +} diff --git a/common/control/redirect_linux.go b/common/control/redirect_linux.go new file mode 100644 index 0000000..82ab233 --- /dev/null +++ b/common/control/redirect_linux.go @@ -0,0 +1,38 @@ +package control + +import ( + "encoding/binary" + "net" + "net/netip" + "os" + "syscall" + + "github.com/sagernet/sing/common" + M "github.com/sagernet/sing/common/metadata" + + "golang.org/x/sys/unix" +) + +func GetOriginalDestination(conn net.Conn) (netip.AddrPort, error) { + syscallConn, loaded := common.Cast[syscall.Conn](conn) + if !loaded { + return netip.AddrPort{}, os.ErrInvalid + } + return Conn0[netip.AddrPort](syscallConn, func(fd uintptr) (netip.AddrPort, error) { + if M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap().IsIPv4() { + raw, err := unix.GetsockoptIPv6Mreq(int(fd), unix.IPPROTO_IP, unix.SO_ORIGINAL_DST) + if err != nil { + return netip.AddrPort{}, err + } + return netip.AddrPortFrom(M.AddrFromIP(raw.Multiaddr[4:8]), uint16(raw.Multiaddr[2])<<8+uint16(raw.Multiaddr[3])), nil + } else { + raw, err := unix.GetsockoptIPv6MTUInfo(int(fd), unix.IPPROTO_IPV6, unix.SO_ORIGINAL_DST) + if err != nil { + return netip.AddrPort{}, err + } + var port [2]byte + binary.BigEndian.PutUint16(port[:], raw.Addr.Port) + return netip.AddrPortFrom(M.AddrFromIP(raw.Addr.Addr[:]), binary.LittleEndian.Uint16(port[:])), nil + } + }) +} diff --git a/common/control/redirect_other.go b/common/control/redirect_other.go new file mode 100644 index 0000000..b0f3297 --- /dev/null +++ b/common/control/redirect_other.go @@ -0,0 +1,13 @@ +//go:build !linux && !darwin + +package control + +import ( + "net" + "net/netip" + "os" +) + +func GetOriginalDestination(conn net.Conn) (netip.AddrPort, error) { + return netip.AddrPort{}, os.ErrInvalid +} diff --git a/common/control/tproxy_linux.go b/common/control/tproxy_linux.go new file mode 100644 index 0000000..b296b98 --- /dev/null +++ b/common/control/tproxy_linux.go @@ -0,0 +1,56 @@ +package control + +import ( + "encoding/binary" + "net/netip" + "syscall" + + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + + "golang.org/x/sys/unix" +) + +func TProxy(fd uintptr, family int) error { + err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + if err == nil { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) + } + if err == nil && family == unix.AF_INET6 { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1) + } + if err == nil { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1) + } + if err == nil && family == unix.AF_INET6 { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1) + } + return err +} + +func TProxyWriteBack() Func { + return func(network, address string, conn syscall.RawConn) error { + return Raw(conn, func(fd uintptr) error { + if M.ParseSocksaddr(address).Addr.Is6() { + return syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1) + } else { + return syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) + } + }) + } +} + +func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) { + controlMessages, err := unix.ParseSocketControlMessage(oob) + if err != nil { + return netip.AddrPort{}, err + } + for _, message := range controlMessages { + if message.Header.Level == unix.SOL_IP && message.Header.Type == unix.IP_RECVORIGDSTADDR { + return netip.AddrPortFrom(M.AddrFromIP(message.Data[4:8]), binary.BigEndian.Uint16(message.Data[2:4])), nil + } else if message.Header.Level == unix.SOL_IPV6 && message.Header.Type == unix.IPV6_RECVORIGDSTADDR { + return netip.AddrPortFrom(M.AddrFromIP(message.Data[8:24]), binary.BigEndian.Uint16(message.Data[2:4])), nil + } + } + return netip.AddrPort{}, E.New("not found") +} diff --git a/common/control/tproxy_other.go b/common/control/tproxy_other.go new file mode 100644 index 0000000..cad1808 --- /dev/null +++ b/common/control/tproxy_other.go @@ -0,0 +1,20 @@ +//go:build !linux + +package control + +import ( + "net/netip" + "os" +) + +func TProxy(fd uintptr, isIPv6 bool) error { + return os.ErrInvalid +} + +func TProxyWriteBack() Func { + return nil +} + +func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) { + return netip.AddrPort{}, os.ErrInvalid +}