Skip to content

Commit 1482471

Browse files
committed
Add redirect and tproxy controls
1 parent cb5ca6e commit 1482471

File tree

6 files changed

+205
-0
lines changed

6 files changed

+205
-0
lines changed

common/control/interface.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package control
33
import (
44
"syscall"
55

6+
"github.com/sagernet/sing/common"
67
E "github.com/sagernet/sing/common/exceptions"
78
)
89

@@ -30,10 +31,29 @@ func Conn(conn syscall.Conn, block func(fd uintptr) error) error {
3031
return Raw(rawConn, block)
3132
}
3233

34+
func Conn0[T any](conn syscall.Conn, block func(fd uintptr) (T, error)) (T, error) {
35+
rawConn, err := conn.SyscallConn()
36+
if err != nil {
37+
return common.DefaultValue[T](), err
38+
}
39+
return Raw0[T](rawConn, block)
40+
}
41+
3342
func Raw(rawConn syscall.RawConn, block func(fd uintptr) error) error {
3443
var innerErr error
3544
err := rawConn.Control(func(fd uintptr) {
3645
innerErr = block(fd)
3746
})
3847
return E.Errors(innerErr, err)
3948
}
49+
50+
func Raw0[T any](rawConn syscall.RawConn, block func(fd uintptr) (T, error)) (T, error) {
51+
var (
52+
value T
53+
innerErr error
54+
)
55+
err := rawConn.Control(func(fd uintptr) {
56+
value, innerErr = block(fd)
57+
})
58+
return value, E.Errors(innerErr, err)
59+
}

common/control/redirect_darwin.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package control
2+
3+
import (
4+
"encoding/binary"
5+
"net"
6+
"net/netip"
7+
"syscall"
8+
"unsafe"
9+
10+
M "github.com/sagernet/sing/common/metadata"
11+
12+
"golang.org/x/sys/unix"
13+
)
14+
15+
const (
16+
PF_OUT = 0x2
17+
DIOCNATLOOK = 0xc0544417
18+
)
19+
20+
func GetOriginalDestination(conn net.Conn) (netip.AddrPort, error) {
21+
pfFd, err := syscall.Open("/dev/pf", 0, syscall.O_RDONLY)
22+
if err != nil {
23+
return netip.AddrPort{}, err
24+
}
25+
defer syscall.Close(pfFd)
26+
nl := struct {
27+
saddr, daddr, rsaddr, rdaddr [16]byte
28+
sxport, dxport, rsxport, rdxport [4]byte
29+
af, proto, protoVariant, direction uint8
30+
}{
31+
af: syscall.AF_INET,
32+
proto: syscall.IPPROTO_TCP,
33+
direction: PF_OUT,
34+
}
35+
localAddr := M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
36+
removeAddr := M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
37+
if localAddr.IsIPv4() {
38+
copy(nl.saddr[:net.IPv4len], removeAddr.Addr.AsSlice())
39+
copy(nl.daddr[:net.IPv4len], localAddr.Addr.AsSlice())
40+
nl.af = syscall.AF_INET
41+
} else {
42+
copy(nl.saddr[:], removeAddr.Addr.AsSlice())
43+
copy(nl.daddr[:], localAddr.Addr.AsSlice())
44+
nl.af = syscall.AF_INET6
45+
}
46+
binary.BigEndian.PutUint16(nl.sxport[:], removeAddr.Port)
47+
binary.BigEndian.PutUint16(nl.dxport[:], localAddr.Port)
48+
if _, _, errno := unix.Syscall(syscall.SYS_IOCTL, uintptr(pfFd), DIOCNATLOOK, uintptr(unsafe.Pointer(&nl))); errno != 0 {
49+
return netip.AddrPort{}, errno
50+
}
51+
var address netip.Addr
52+
if nl.af == unix.AF_INET {
53+
address = M.AddrFromIP(nl.rdaddr[:net.IPv4len])
54+
} else {
55+
address = netip.AddrFrom16(nl.rdaddr)
56+
}
57+
return netip.AddrPortFrom(address, binary.BigEndian.Uint16(nl.rdxport[:])), nil
58+
}

common/control/redirect_linux.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package control
2+
3+
import (
4+
"encoding/binary"
5+
"net"
6+
"net/netip"
7+
"os"
8+
"syscall"
9+
10+
"github.com/sagernet/sing/common"
11+
M "github.com/sagernet/sing/common/metadata"
12+
13+
"golang.org/x/sys/unix"
14+
)
15+
16+
func GetOriginalDestination(conn net.Conn) (netip.AddrPort, error) {
17+
syscallConn, loaded := common.Cast[syscall.Conn](conn)
18+
if !loaded {
19+
return netip.AddrPort{}, os.ErrInvalid
20+
}
21+
return Conn0[netip.AddrPort](syscallConn, func(fd uintptr) (netip.AddrPort, error) {
22+
if M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap().IsIPv4() {
23+
raw, err := unix.GetsockoptIPv6Mreq(int(fd), unix.IPPROTO_IP, unix.SO_ORIGINAL_DST)
24+
if err != nil {
25+
return netip.AddrPort{}, err
26+
}
27+
return netip.AddrPortFrom(M.AddrFromIP(raw.Multiaddr[4:8]), uint16(raw.Multiaddr[2])<<8+uint16(raw.Multiaddr[3])), nil
28+
} else {
29+
raw, err := unix.GetsockoptIPv6MTUInfo(int(fd), unix.IPPROTO_IPV6, unix.SO_ORIGINAL_DST)
30+
if err != nil {
31+
return netip.AddrPort{}, err
32+
}
33+
var port [2]byte
34+
binary.BigEndian.PutUint16(port[:], raw.Addr.Port)
35+
return netip.AddrPortFrom(M.AddrFromIP(raw.Addr.Addr[:]), binary.LittleEndian.Uint16(port[:])), nil
36+
}
37+
})
38+
}

common/control/redirect_other.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//go:build !linux && !darwin
2+
3+
package control
4+
5+
import (
6+
"net"
7+
"net/netip"
8+
"os"
9+
)
10+
11+
func GetOriginalDestination(conn net.Conn) (netip.AddrPort, error) {
12+
return netip.AddrPort{}, os.ErrInvalid
13+
}

common/control/tproxy_linux.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package control
2+
3+
import (
4+
"encoding/binary"
5+
"net/netip"
6+
"syscall"
7+
8+
E "github.com/sagernet/sing/common/exceptions"
9+
M "github.com/sagernet/sing/common/metadata"
10+
11+
"golang.org/x/sys/unix"
12+
)
13+
14+
func TProxy(fd uintptr, family int) error {
15+
err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
16+
if err == nil {
17+
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
18+
}
19+
if err == nil && family == unix.AF_INET6 {
20+
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
21+
}
22+
if err == nil {
23+
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
24+
}
25+
if err == nil && family == unix.AF_INET6 {
26+
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)
27+
}
28+
return err
29+
}
30+
31+
func TProxyWriteBack() Func {
32+
return func(network, address string, conn syscall.RawConn) error {
33+
return Raw(conn, func(fd uintptr) error {
34+
if M.ParseSocksaddr(address).Addr.Is6() {
35+
return syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
36+
} else {
37+
return syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
38+
}
39+
})
40+
}
41+
}
42+
43+
func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
44+
controlMessages, err := unix.ParseSocketControlMessage(oob)
45+
if err != nil {
46+
return netip.AddrPort{}, err
47+
}
48+
for _, message := range controlMessages {
49+
if message.Header.Level == unix.SOL_IP && message.Header.Type == unix.IP_RECVORIGDSTADDR {
50+
return netip.AddrPortFrom(M.AddrFromIP(message.Data[4:8]), binary.BigEndian.Uint16(message.Data[2:4])), nil
51+
} else if message.Header.Level == unix.SOL_IPV6 && message.Header.Type == unix.IPV6_RECVORIGDSTADDR {
52+
return netip.AddrPortFrom(M.AddrFromIP(message.Data[8:24]), binary.BigEndian.Uint16(message.Data[2:4])), nil
53+
}
54+
}
55+
return netip.AddrPort{}, E.New("not found")
56+
}

common/control/tproxy_other.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//go:build !linux
2+
3+
package control
4+
5+
import (
6+
"net/netip"
7+
"os"
8+
)
9+
10+
func TProxy(fd uintptr, isIPv6 bool) error {
11+
return os.ErrInvalid
12+
}
13+
14+
func TProxyWriteBack() Func {
15+
return nil
16+
}
17+
18+
func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) {
19+
return netip.AddrPort{}, os.ErrInvalid
20+
}

0 commit comments

Comments
 (0)