mirror of
https://github.com/SagerNet/sing.git
synced 2025-04-05 21:07:41 +03:00
Add redirect and tproxy controls
This commit is contained in:
parent
589c7eb4df
commit
aca2a85545
6 changed files with 205 additions and 0 deletions
|
@ -3,6 +3,7 @@ package control
|
||||||
import (
|
import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
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)
|
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 {
|
func Raw(rawConn syscall.RawConn, block func(fd uintptr) error) error {
|
||||||
var innerErr error
|
var innerErr error
|
||||||
err := rawConn.Control(func(fd uintptr) {
|
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)
|
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)
|
||||||
|
}
|
||||||
|
|
58
common/control/redirect_darwin.go
Normal file
58
common/control/redirect_darwin.go
Normal file
|
@ -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
|
||||||
|
}
|
38
common/control/redirect_linux.go
Normal file
38
common/control/redirect_linux.go
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
13
common/control/redirect_other.go
Normal file
13
common/control/redirect_other.go
Normal file
|
@ -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
|
||||||
|
}
|
56
common/control/tproxy_linux.go
Normal file
56
common/control/tproxy_linux.go
Normal file
|
@ -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")
|
||||||
|
}
|
20
common/control/tproxy_other.go
Normal file
20
common/control/tproxy_other.go
Normal file
|
@ -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
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue