mirror of https://github.com/XTLS/Xray-core
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
6.2 KiB
230 lines
6.2 KiB
package internet |
|
|
|
import ( |
|
"encoding/binary" |
|
"net" |
|
"os" |
|
"syscall" |
|
"unsafe" |
|
|
|
"golang.org/x/sys/unix" |
|
) |
|
|
|
const ( |
|
sysPFINOUT = 0x0 |
|
sysPFIN = 0x1 |
|
sysPFOUT = 0x2 |
|
sysPFFWD = 0x3 |
|
sysDIOCNATLOOK = 0xc04c4417 |
|
) |
|
|
|
type pfiocNatlook struct { |
|
Saddr [16]byte /* pf_addr */ |
|
Daddr [16]byte /* pf_addr */ |
|
Rsaddr [16]byte /* pf_addr */ |
|
Rdaddr [16]byte /* pf_addr */ |
|
Sport uint16 |
|
Dport uint16 |
|
Rsport uint16 |
|
Rdport uint16 |
|
Af uint8 |
|
Proto uint8 |
|
Direction uint8 |
|
Pad [1]byte |
|
} |
|
|
|
const ( |
|
sizeofPfiocNatlook = 0x4c |
|
soReUsePort = 0x00000200 |
|
soReUsePortLB = 0x00010000 |
|
) |
|
|
|
func ioctl(s uintptr, ioc int, b []byte) error { |
|
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, s, uintptr(ioc), uintptr(unsafe.Pointer(&b[0]))); errno != 0 { |
|
return error(errno) |
|
} |
|
return nil |
|
} |
|
func (nl *pfiocNatlook) rdPort() int { |
|
return int(binary.BigEndian.Uint16((*[2]byte)(unsafe.Pointer(&nl.Rdport))[:])) |
|
} |
|
|
|
func (nl *pfiocNatlook) setPort(remote, local int) { |
|
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&nl.Sport))[:], uint16(remote)) |
|
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&nl.Dport))[:], uint16(local)) |
|
} |
|
|
|
// OriginalDst uses ioctl to read original destination from /dev/pf |
|
func OriginalDst(la, ra net.Addr) (net.IP, int, error) { |
|
f, err := os.Open("/dev/pf") |
|
if err != nil { |
|
return net.IP{}, -1, newError("failed to open device /dev/pf").Base(err) |
|
} |
|
defer f.Close() |
|
fd := f.Fd() |
|
b := make([]byte, sizeofPfiocNatlook) |
|
nl := (*pfiocNatlook)(unsafe.Pointer(&b[0])) |
|
var raIP, laIP net.IP |
|
var raPort, laPort int |
|
switch la.(type) { |
|
case *net.TCPAddr: |
|
raIP = ra.(*net.TCPAddr).IP |
|
laIP = la.(*net.TCPAddr).IP |
|
raPort = ra.(*net.TCPAddr).Port |
|
laPort = la.(*net.TCPAddr).Port |
|
nl.Proto = syscall.IPPROTO_TCP |
|
case *net.UDPAddr: |
|
raIP = ra.(*net.UDPAddr).IP |
|
laIP = la.(*net.UDPAddr).IP |
|
raPort = ra.(*net.UDPAddr).Port |
|
laPort = la.(*net.UDPAddr).Port |
|
nl.Proto = syscall.IPPROTO_UDP |
|
} |
|
if raIP.To4() != nil { |
|
if laIP.IsUnspecified() { |
|
laIP = net.ParseIP("127.0.0.1") |
|
} |
|
copy(nl.Saddr[:net.IPv4len], raIP.To4()) |
|
copy(nl.Daddr[:net.IPv4len], laIP.To4()) |
|
nl.Af = syscall.AF_INET |
|
} |
|
if raIP.To16() != nil && raIP.To4() == nil { |
|
if laIP.IsUnspecified() { |
|
laIP = net.ParseIP("::1") |
|
} |
|
copy(nl.Saddr[:], raIP) |
|
copy(nl.Daddr[:], laIP) |
|
nl.Af = syscall.AF_INET6 |
|
} |
|
nl.setPort(raPort, laPort) |
|
ioc := uintptr(sysDIOCNATLOOK) |
|
for _, dir := range []byte{sysPFOUT, sysPFIN} { |
|
nl.Direction = dir |
|
err = ioctl(fd, int(ioc), b) |
|
if err == nil || err != syscall.ENOENT { |
|
break |
|
} |
|
} |
|
if err != nil { |
|
return net.IP{}, -1, os.NewSyscallError("ioctl", err) |
|
} |
|
|
|
odPort := nl.rdPort() |
|
var odIP net.IP |
|
switch nl.Af { |
|
case syscall.AF_INET: |
|
odIP = make(net.IP, net.IPv4len) |
|
copy(odIP, nl.Rdaddr[:net.IPv4len]) |
|
case syscall.AF_INET6: |
|
odIP = make(net.IP, net.IPv6len) |
|
copy(odIP, nl.Rdaddr[:]) |
|
} |
|
return odIP, odPort, nil |
|
} |
|
|
|
func applyOutboundSocketOptions(network string, address string, fd uintptr, config *SocketConfig) error { |
|
if config.Mark != 0 { |
|
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_USER_COOKIE, int(config.Mark)); err != nil { |
|
return newError("failed to set SO_USER_COOKIE").Base(err) |
|
} |
|
} |
|
|
|
if isTCPSocket(network) { |
|
switch config.Tfo { |
|
case SocketConfig_Enable: |
|
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_FASTOPEN, 1); err != nil { |
|
return newError("failed to set TCP_FASTOPEN_CONNECT=1").Base(err) |
|
} |
|
case SocketConfig_Disable: |
|
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_FASTOPEN, 0); err != nil { |
|
return newError("failed to set TCP_FASTOPEN_CONNECT=0").Base(err) |
|
} |
|
} |
|
} |
|
|
|
if config.Tproxy.IsEnabled() { |
|
ip, _, _ := net.SplitHostPort(address) |
|
if net.ParseIP(ip).To4() != nil { |
|
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BINDANY, 1); err != nil { |
|
return newError("failed to set outbound IP_BINDANY").Base(err) |
|
} |
|
} else { |
|
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BINDANY, 1); err != nil { |
|
return newError("failed to set outbound IPV6_BINDANY").Base(err) |
|
} |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) error { |
|
if config.Mark != 0 { |
|
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_USER_COOKIE, int(config.Mark)); err != nil { |
|
return newError("failed to set SO_USER_COOKIE").Base(err) |
|
} |
|
} |
|
if isTCPSocket(network) { |
|
switch config.Tfo { |
|
case SocketConfig_Enable: |
|
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_FASTOPEN, 1); err != nil { |
|
return newError("failed to set TCP_FASTOPEN=1").Base(err) |
|
} |
|
case SocketConfig_Disable: |
|
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_FASTOPEN, 0); err != nil { |
|
return newError("failed to set TCP_FASTOPEN=0").Base(err) |
|
} |
|
} |
|
} |
|
|
|
if config.Tproxy.IsEnabled() { |
|
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BINDANY, 1); err != nil { |
|
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BINDANY, 1); err != nil { |
|
return newError("failed to set inbound IP_BINDANY").Base(err) |
|
} |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func bindAddr(fd uintptr, ip []byte, port uint32) error { |
|
setReuseAddr(fd) |
|
setReusePort(fd) |
|
|
|
var sockaddr syscall.Sockaddr |
|
|
|
switch len(ip) { |
|
case net.IPv4len: |
|
a4 := &syscall.SockaddrInet4{ |
|
Port: int(port), |
|
} |
|
copy(a4.Addr[:], ip) |
|
sockaddr = a4 |
|
case net.IPv6len: |
|
a6 := &syscall.SockaddrInet6{ |
|
Port: int(port), |
|
} |
|
copy(a6.Addr[:], ip) |
|
sockaddr = a6 |
|
default: |
|
return newError("unexpected length of ip") |
|
} |
|
|
|
return syscall.Bind(int(fd), sockaddr) |
|
} |
|
|
|
func setReuseAddr(fd uintptr) error { |
|
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { |
|
return newError("failed to set SO_REUSEADDR").Base(err).AtWarning() |
|
} |
|
return nil |
|
} |
|
|
|
func setReusePort(fd uintptr) error { |
|
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, soReUsePortLB, 1); err != nil { |
|
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, soReUsePort, 1); err != nil { |
|
return newError("failed to set SO_REUSEPORT").Base(err).AtWarning() |
|
} |
|
} |
|
return nil |
|
}
|
|
|