diff --git a/transport/internet/sockopt_freebsd.go b/transport/internet/sockopt_freebsd.go new file mode 100644 index 00000000..f99d37a9 --- /dev/null +++ b/transport/internet/sockopt_freebsd.go @@ -0,0 +1,218 @@ +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 + 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 { + if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { + return newError("failed to set resuse_addr").Base(err).AtWarning() + } + + if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, soReUsePortLB, 1); err != nil { + return newError("failed to set resuse_port").Base(err).AtWarning() + } + + 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) +} diff --git a/transport/internet/sockopt_other.go b/transport/internet/sockopt_other.go index eca55cc3..31d39deb 100644 --- a/transport/internet/sockopt_other.go +++ b/transport/internet/sockopt_other.go @@ -1,4 +1,4 @@ -// +build js dragonfly freebsd netbsd openbsd +// +build js dragonfly netbsd openbsd package internet diff --git a/transport/internet/tcp/sockopt_freebsd.go b/transport/internet/tcp/sockopt_freebsd.go new file mode 100644 index 00000000..58f3dcd0 --- /dev/null +++ b/transport/internet/tcp/sockopt_freebsd.go @@ -0,0 +1,24 @@ +// +build freebsd +// +build !confonly + +package tcp + +import ( + "v2ray.com/core/common/net" + "v2ray.com/core/transport/internet" +) + +// GetOriginalDestination from tcp conn +func GetOriginalDestination(conn internet.Connection) (net.Destination, error) { + la := conn.LocalAddr() + ra := conn.RemoteAddr() + ip, port, err := internet.OriginalDst(la, ra) + if err != nil { + return net.Destination{}, newError("failed to get destination").Base(err) + } + dest := net.TCPDestination(net.IPAddress(ip), net.Port(port)) + if !dest.IsValid() { + return net.Destination{}, newError("failed to parse destination.") + } + return dest, nil +} diff --git a/transport/internet/tcp/sockopt_other.go b/transport/internet/tcp/sockopt_other.go index d7b31851..2d4d4708 100644 --- a/transport/internet/tcp/sockopt_other.go +++ b/transport/internet/tcp/sockopt_other.go @@ -1,4 +1,4 @@ -// +build !linux +// +build !linux,!freebsd // +build !confonly package tcp diff --git a/transport/internet/udp/hub_freebsd.go b/transport/internet/udp/hub_freebsd.go new file mode 100644 index 00000000..35d33464 --- /dev/null +++ b/transport/internet/udp/hub_freebsd.go @@ -0,0 +1,37 @@ +// +build freebsd + +package udp + +import ( + "bytes" + "encoding/gob" + "io" + + "v2ray.com/core/common/net" + "v2ray.com/core/transport/internet" +) + +// RetrieveOriginalDest from stored laddr, caddr +func RetrieveOriginalDest(oob []byte) net.Destination { + dec := gob.NewDecoder(bytes.NewBuffer(oob)) + var la, ra net.UDPAddr + dec.Decode(&la) + dec.Decode(&ra) + ip, port, err := internet.OriginalDst(&la, &ra) + if err != nil { + return net.Destination{} + } + return net.UDPDestination(net.IPAddress(ip), net.Port(port)) +} + +// ReadUDPMsg stores laddr, caddr for later use +func ReadUDPMsg(conn *net.UDPConn, payload []byte, oob []byte) (int, int, int, *net.UDPAddr, error) { + nBytes, addr, err := conn.ReadFromUDP(payload) + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + enc.Encode(conn.LocalAddr().(*net.UDPAddr)) + enc.Encode(addr) + var reader io.Reader = &buf + noob, _ := reader.Read(oob) + return nBytes, noob, 0, addr, err +} diff --git a/transport/internet/udp/hub_other.go b/transport/internet/udp/hub_other.go index 8ce8cc0e..d4ab30e8 100644 --- a/transport/internet/udp/hub_other.go +++ b/transport/internet/udp/hub_other.go @@ -1,4 +1,4 @@ -// +build !linux +// +build !linux,!freebsd package udp