mirror of
https://github.com/XTLS/Xray-core.git
synced 2024-08-21 12:16:26 +00:00
* Add TCP keep alive idle setting * Add TCP keep alive idle setting: auto generated * Add TCP keep alive support in Linux * Add TCP keep alive support in MacOS, FreeBSD * Add TCP keep alive support in Windows * fix bug introduced in adding tcp keep alive adjustment * embed macOS const to avoid platform inconsistency * embed macOS const to avoid platform inconsistency(again) * add TCP Keep Alive support in config * use sys/unix instead of syscall Suggestion from: https://github.com/v2fly/v2ray-core/pull/1395#issuecomment-974761647 * use sys/unix instead of syscall Suggestion from: https://github.com/v2fly/v2ray-core/pull/1395#issuecomment-974761647 * Separate TcpKeepAliveIdle and TcpKeepAliveInterval check logic * Disable tcp keepAlive when TcpKeepAliveIdle < 0 and TcpKeepAliveInterval <= 0 Co-authored-by: xqzr <34030394+xqzr@users.noreply.github.com> Co-authored-by: ValdikSS <iam@valdikss.org.ru> Co-authored-by: Shelikhoo <xiaokangwang@outlook.com> Co-authored-by: xqzr <34030394+xqzr@users.noreply.github.com>
265 lines
7.7 KiB
Go
265 lines
7.7 KiB
Go
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) {
|
|
tfo := config.ParseTFOValue()
|
|
if tfo > 0 {
|
|
tfo = 1
|
|
}
|
|
if tfo >= 0 {
|
|
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_FASTOPEN, tfo); err != nil {
|
|
return newError("failed to set TCP_FASTOPEN_CONNECT=", tfo).Base(err)
|
|
}
|
|
}
|
|
if config.TcpKeepAliveIdle > 0 || config.TcpKeepAliveInterval > 0 {
|
|
if config.TcpKeepAliveIdle > 0 {
|
|
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, int(config.TcpKeepAliveIdle)); err != nil {
|
|
return newError("failed to set TCP_KEEPIDLE", err)
|
|
}
|
|
}
|
|
if config.TcpKeepAliveInterval > 0 {
|
|
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, int(config.TcpKeepAliveInterval)); err != nil {
|
|
return newError("failed to set TCP_KEEPINTVL", err)
|
|
}
|
|
}
|
|
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil {
|
|
return newError("failed to set SO_KEEPALIVE", err)
|
|
}
|
|
} else if config.TcpKeepAliveInterval < 0 || config.TcpKeepAliveIdle < 0 {
|
|
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 0); err != nil {
|
|
return newError("failed to unset SO_KEEPALIVE", 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) {
|
|
tfo := config.ParseTFOValue()
|
|
if tfo >= 0 {
|
|
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_FASTOPEN, tfo); err != nil {
|
|
return newError("failed to set TCP_FASTOPEN=", tfo).Base(err)
|
|
}
|
|
}
|
|
if config.TcpKeepAliveIdle > 0 || config.TcpKeepAliveInterval > 0 {
|
|
if config.TcpKeepAliveIdle > 0 {
|
|
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, int(config.TcpKeepAliveIdle)); err != nil {
|
|
return newError("failed to set TCP_KEEPIDLE", err)
|
|
}
|
|
}
|
|
if config.TcpKeepAliveInterval > 0 {
|
|
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, int(config.TcpKeepAliveInterval)); err != nil {
|
|
return newError("failed to set TCP_KEEPINTVL", err)
|
|
}
|
|
}
|
|
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil {
|
|
return newError("failed to set SO_KEEPALIVE", err)
|
|
}
|
|
} else if config.TcpKeepAliveInterval < 0 || config.TcpKeepAliveIdle < 0 {
|
|
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 0); err != nil {
|
|
return newError("failed to unset SO_KEEPALIVE", 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
|
|
}
|