mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-29 04:26:42 +00:00
Merge commit 'cd4ce973e9f6ef3a7acf9a7030927b4143f9ea47' into finalrules
This commit is contained in:
+1
-1
@@ -122,7 +122,7 @@ func NewReader(reader io.Reader) Reader {
|
||||
}
|
||||
|
||||
_, isFile := reader.(*os.File)
|
||||
if !isFile && useReadv {
|
||||
if !isFile && useReadV() {
|
||||
if sc, ok := reader.(syscall.Conn); ok {
|
||||
rawConn, err := sc.SyscallConn()
|
||||
if err != nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ package buf
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
@@ -143,13 +144,24 @@ func (r *ReadVReader) ReadMultiBuffer() (MultiBuffer, error) {
|
||||
return mb, nil
|
||||
}
|
||||
|
||||
var useReadv bool
|
||||
var useReadv atomic.Bool
|
||||
|
||||
func init() {
|
||||
func useReadV() bool {
|
||||
return useReadv.Load()
|
||||
}
|
||||
|
||||
func reloadEnvSettings() error {
|
||||
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
|
||||
value := platform.NewEnvFlag(platform.UseReadV).GetValue(func() string { return defaultFlagValue })
|
||||
enabled := false
|
||||
switch value {
|
||||
case defaultFlagValue, "auto", "enable":
|
||||
useReadv = true
|
||||
enabled = true
|
||||
}
|
||||
useReadv.Store(enabled)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
platform.RegisterEnvReload(reloadEnvSettings)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ import (
|
||||
"github.com/xtls/xray-core/features/stats"
|
||||
)
|
||||
|
||||
const useReadv = false
|
||||
func useReadV() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn, counter stats.Counter) Reader {
|
||||
panic("not implemented")
|
||||
|
||||
@@ -0,0 +1,350 @@
|
||||
//go:build darwin && !ios
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"net/netip"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
darwinProcPIDListFDs = 1
|
||||
darwinProcPIDFDSocketInfo = 3
|
||||
darwinProcFDTypeSocket = 2
|
||||
darwinProcFDInfoSize = 8
|
||||
darwinSocketFDInfoSize = 792
|
||||
darwinSocketFDInfoPSIOff = 24
|
||||
darwinSocketInfoProtoOff = darwinSocketFDInfoPSIOff + 156
|
||||
darwinSocketInfoFamilyOff = darwinSocketFDInfoPSIOff + 160
|
||||
darwinSocketInfoKindOff = darwinSocketFDInfoPSIOff + 232
|
||||
darwinSocketInfoInSockOff = darwinSocketFDInfoPSIOff + 240
|
||||
darwinInSockInfoFPortOff = darwinSocketInfoInSockOff
|
||||
darwinInSockInfoLPortOff = darwinSocketInfoInSockOff + 4
|
||||
darwinInSockInfoVFlagOff = darwinSocketInfoInSockOff + 24
|
||||
darwinInSockInfoFAddrOff = darwinSocketInfoInSockOff + 32
|
||||
darwinInSockInfoLAddrOff = darwinSocketInfoInSockOff + 48
|
||||
darwinInSockInfoSize = 80
|
||||
darwinInSockInfoIPv4 = 0x1
|
||||
darwinInSockInfoIPv6 = 0x2
|
||||
darwinSockInfoIN = 1
|
||||
darwinSockInfoTCP = 2
|
||||
)
|
||||
|
||||
type darwinSocketMatchLevel int
|
||||
|
||||
const (
|
||||
darwinSocketNoMatch darwinSocketMatchLevel = iota
|
||||
darwinSocketPortMatch
|
||||
darwinSocketRemoteMatch
|
||||
darwinSocketLocalMatch
|
||||
darwinSocketExactMatch
|
||||
)
|
||||
|
||||
func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort uint16) (PID int, Name string, AbsolutePath string, err error) {
|
||||
isLocal, err := IsLocal(net.ParseIP(srcIP))
|
||||
if err != nil {
|
||||
return 0, "", "", errors.New("failed to determine if address is local: ", err)
|
||||
}
|
||||
if !isLocal {
|
||||
return 0, "", "", ErrNotLocal
|
||||
}
|
||||
if network != "tcp" && network != "udp" {
|
||||
panic("Unsupported network type for process lookup.")
|
||||
}
|
||||
|
||||
srcAddr, err := netip.ParseAddr(srcIP)
|
||||
if err != nil {
|
||||
return 0, "", "", errors.New("invalid source IP address: ", srcIP)
|
||||
}
|
||||
srcAddr = srcAddr.Unmap()
|
||||
|
||||
var dstAddr netip.Addr
|
||||
hasDstAddr := false
|
||||
if destIP != "" && destPort != 0 {
|
||||
dstAddr, err = netip.ParseAddr(destIP)
|
||||
if err != nil {
|
||||
return 0, "", "", errors.New("invalid destination IP address: ", destIP)
|
||||
}
|
||||
dstAddr = dstAddr.Unmap()
|
||||
hasDstAddr = true
|
||||
}
|
||||
|
||||
processes, err := unix.SysctlKinfoProcSlice("kern.proc.all")
|
||||
if err != nil {
|
||||
return 0, "", "", errors.New("failed to list processes").Base(err)
|
||||
}
|
||||
|
||||
var bestPID int32
|
||||
bestLevel := darwinSocketNoMatch
|
||||
ambiguousBest := false
|
||||
|
||||
for _, process := range processes {
|
||||
pid := process.Proc.P_pid
|
||||
if pid <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
matchLevel, err := darwinProcessSocketMatchLevel(pid, network, srcAddr, srcPort, dstAddr, destPort, hasDstAddr)
|
||||
if err != nil || matchLevel == darwinSocketNoMatch {
|
||||
continue
|
||||
}
|
||||
if matchLevel == darwinSocketExactMatch {
|
||||
bestPID = pid
|
||||
bestLevel = matchLevel
|
||||
ambiguousBest = false
|
||||
break
|
||||
}
|
||||
if matchLevel > bestLevel {
|
||||
bestPID = pid
|
||||
bestLevel = matchLevel
|
||||
ambiguousBest = false
|
||||
continue
|
||||
}
|
||||
if matchLevel == bestLevel {
|
||||
ambiguousBest = true
|
||||
}
|
||||
}
|
||||
|
||||
if bestLevel == darwinSocketNoMatch {
|
||||
return 0, "", "", errors.New("process not found for ", network, " connection from ", srcIP, ":", srcPort, " to ", destIP, ":", destPort)
|
||||
}
|
||||
if ambiguousBest {
|
||||
return 0, "", "", errors.New("ambiguous process match for ", network, " connection from ", srcIP, ":", srcPort, " to ", destIP, ":", destPort)
|
||||
}
|
||||
|
||||
absPath, err := darwinProcessPath(bestPID)
|
||||
if err != nil {
|
||||
return 0, "", "", errors.New("could not get process path for PID ", bestPID, ": ", err)
|
||||
}
|
||||
|
||||
absPath = filepath.ToSlash(absPath)
|
||||
return int(bestPID), filepath.Base(absPath), absPath, nil
|
||||
}
|
||||
|
||||
func darwinProcessSocketMatchLevel(pid int32, network string, srcAddr netip.Addr, srcPort uint16, dstAddr netip.Addr, dstPort uint16, hasDstAddr bool) (darwinSocketMatchLevel, error) {
|
||||
fds, err := darwinProcessFDs(pid)
|
||||
if err != nil {
|
||||
return darwinSocketNoMatch, err
|
||||
}
|
||||
|
||||
bestLevel := darwinSocketNoMatch
|
||||
info := make([]byte, darwinSocketFDInfoSize)
|
||||
for fd := 0; fd+darwinProcFDInfoSize <= len(fds); fd += darwinProcFDInfoSize {
|
||||
fdNumber := int32(darwinReadNativeUint32(fds[fd : fd+4]))
|
||||
fdType := darwinReadNativeUint32(fds[fd+4 : fd+8])
|
||||
if fdType != darwinProcFDTypeSocket {
|
||||
continue
|
||||
}
|
||||
|
||||
n, err := darwinProcPIDFDInfo(pid, fdNumber, darwinProcPIDFDSocketInfo, info)
|
||||
if err != nil || n < darwinSocketInfoInSockOff+darwinInSockInfoSize {
|
||||
continue
|
||||
}
|
||||
level := darwinSocketInfoMatchLevel(info[:n], network, srcAddr, srcPort, dstAddr, dstPort, hasDstAddr)
|
||||
if level == darwinSocketExactMatch {
|
||||
return level, nil
|
||||
}
|
||||
if level > bestLevel {
|
||||
bestLevel = level
|
||||
}
|
||||
}
|
||||
|
||||
return bestLevel, nil
|
||||
}
|
||||
|
||||
func darwinProcessFDs(pid int32) ([]byte, error) {
|
||||
n, err := darwinProcPIDInfo(pid, darwinProcPIDListFDs, 0, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf := make([]byte, n)
|
||||
n, err = darwinProcPIDInfo(pid, darwinProcPIDListFDs, 0, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf[:n], nil
|
||||
}
|
||||
|
||||
func darwinSocketInfoMatchLevel(info []byte, network string, srcAddr netip.Addr, srcPort uint16, dstAddr netip.Addr, dstPort uint16, hasDstAddr bool) darwinSocketMatchLevel {
|
||||
protocol := int(darwinReadNativeUint32(info[darwinSocketInfoProtoOff : darwinSocketInfoProtoOff+4]))
|
||||
family := int(darwinReadNativeUint32(info[darwinSocketInfoFamilyOff : darwinSocketInfoFamilyOff+4]))
|
||||
kind := int(darwinReadNativeUint32(info[darwinSocketInfoKindOff : darwinSocketInfoKindOff+4]))
|
||||
|
||||
switch network {
|
||||
case "tcp":
|
||||
if protocol != unix.IPPROTO_TCP || kind != darwinSockInfoTCP {
|
||||
return darwinSocketNoMatch
|
||||
}
|
||||
case "udp":
|
||||
if protocol != unix.IPPROTO_UDP || kind != darwinSockInfoIN {
|
||||
return darwinSocketNoMatch
|
||||
}
|
||||
default:
|
||||
return darwinSocketNoMatch
|
||||
}
|
||||
|
||||
vflag := info[darwinInSockInfoVFlagOff]
|
||||
if srcAddr.Is4() {
|
||||
// Dual-stack sockets expose IPv4-mapped connections as AF_INET6
|
||||
// while marking the endpoint as IPv4 in ini_vflag.
|
||||
if (family != unix.AF_INET && family != unix.AF_INET6) || vflag&darwinInSockInfoIPv4 == 0 {
|
||||
return darwinSocketNoMatch
|
||||
}
|
||||
} else {
|
||||
if family != unix.AF_INET6 || vflag&darwinInSockInfoIPv6 == 0 {
|
||||
return darwinSocketNoMatch
|
||||
}
|
||||
}
|
||||
|
||||
localPort := int32(darwinReadNativeUint32(info[darwinInSockInfoLPortOff : darwinInSockInfoLPortOff+4]))
|
||||
if !darwinPortMatches(localPort, srcPort) {
|
||||
return darwinSocketNoMatch
|
||||
}
|
||||
localAddrMatches := darwinAddrMatchesOrUnspecified(info[darwinInSockInfoLAddrOff:darwinInSockInfoLAddrOff+16], srcAddr)
|
||||
|
||||
foreignAddrRaw := info[darwinInSockInfoFAddrOff : darwinInSockInfoFAddrOff+16]
|
||||
foreignPort := int32(darwinReadNativeUint32(info[darwinInSockInfoFPortOff : darwinInSockInfoFPortOff+4]))
|
||||
|
||||
if !hasDstAddr {
|
||||
if localAddrMatches {
|
||||
return darwinSocketExactMatch
|
||||
}
|
||||
return darwinSocketNoMatch
|
||||
}
|
||||
|
||||
remoteMatches := darwinPortMatches(foreignPort, dstPort) && darwinAddrMatches(foreignAddrRaw, dstAddr)
|
||||
if network == "udp" && darwinEndpointIsZero(foreignAddrRaw, foreignPort) && localAddrMatches {
|
||||
return darwinSocketExactMatch
|
||||
}
|
||||
switch {
|
||||
case localAddrMatches && remoteMatches:
|
||||
return darwinSocketExactMatch
|
||||
case localAddrMatches:
|
||||
return darwinSocketLocalMatch
|
||||
case remoteMatches:
|
||||
return darwinSocketRemoteMatch
|
||||
default:
|
||||
return darwinSocketPortMatch
|
||||
}
|
||||
}
|
||||
|
||||
func darwinPortMatches(value int32, port uint16) bool {
|
||||
raw := uint16(value)
|
||||
return raw == port || darwinNtohs(raw) == port
|
||||
}
|
||||
|
||||
func darwinNtohs(value uint16) uint16 {
|
||||
return value<<8 | value>>8
|
||||
}
|
||||
|
||||
func darwinAddrMatches(raw []byte, addr netip.Addr) bool {
|
||||
if addr.Is4() {
|
||||
ip := addr.As4()
|
||||
return bytes.Equal(raw[12:16], ip[:])
|
||||
}
|
||||
ip := addr.As16()
|
||||
return bytes.Equal(raw, ip[:])
|
||||
}
|
||||
|
||||
func darwinAddrMatchesOrUnspecified(raw []byte, addr netip.Addr) bool {
|
||||
if darwinAddrMatches(raw, addr) {
|
||||
return true
|
||||
}
|
||||
if addr.Is4() {
|
||||
return darwinBytesAreZero(raw[12:16])
|
||||
}
|
||||
return darwinBytesAreZero(raw)
|
||||
}
|
||||
|
||||
func darwinEndpointIsZero(rawAddr []byte, port int32) bool {
|
||||
return uint32(port) == 0 && darwinBytesAreZero(rawAddr)
|
||||
}
|
||||
|
||||
func darwinBytesAreZero(raw []byte) bool {
|
||||
for _, value := range raw {
|
||||
if value != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func darwinReadNativeUint32(b []byte) uint32 {
|
||||
return *(*uint32)(unsafe.Pointer(&b[0]))
|
||||
}
|
||||
|
||||
func darwinProcessPath(pid int32) (string, error) {
|
||||
buf := make([]byte, unix.PathMax)
|
||||
n, err := darwinProcPIDPath(pid, buf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if n <= 0 {
|
||||
return "", errors.New("empty process path")
|
||||
}
|
||||
return strings.TrimRight(string(buf[:n]), "\x00"), nil
|
||||
}
|
||||
|
||||
func darwinProcPIDInfo(pid int32, flavor int, arg uint64, buf []byte) (int, error) {
|
||||
var ptr unsafe.Pointer
|
||||
if len(buf) > 0 {
|
||||
ptr = unsafe.Pointer(&buf[0])
|
||||
}
|
||||
|
||||
r0, _, errno := syscall_syscall6(libc_proc_pidinfo_trampoline_addr, uintptr(pid), uintptr(flavor), uintptr(arg), uintptr(ptr), uintptr(len(buf)), 0)
|
||||
if errno != 0 {
|
||||
return 0, errno
|
||||
}
|
||||
return int(r0), nil
|
||||
}
|
||||
|
||||
func darwinProcPIDFDInfo(pid int32, fd int32, flavor int, buf []byte) (int, error) {
|
||||
var ptr unsafe.Pointer
|
||||
if len(buf) > 0 {
|
||||
ptr = unsafe.Pointer(&buf[0])
|
||||
}
|
||||
|
||||
r0, _, errno := syscall_syscall6(libc_proc_pidfdinfo_trampoline_addr, uintptr(pid), uintptr(fd), uintptr(flavor), uintptr(ptr), uintptr(len(buf)), 0)
|
||||
if errno != 0 {
|
||||
return 0, errno
|
||||
}
|
||||
return int(r0), nil
|
||||
}
|
||||
|
||||
func darwinProcPIDPath(pid int32, buf []byte) (int, error) {
|
||||
r0, _, errno := syscall_syscall6(libc_proc_pidpath_trampoline_addr, uintptr(pid), uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), 0, 0, 0)
|
||||
if errno != 0 {
|
||||
return 0, errno
|
||||
}
|
||||
return int(r0), nil
|
||||
}
|
||||
|
||||
var libc_proc_pidinfo_trampoline_addr uintptr
|
||||
|
||||
//go:cgo_import_dynamic libc_proc_pidinfo proc_pidinfo "/usr/lib/libproc.dylib"
|
||||
|
||||
var libc_proc_pidfdinfo_trampoline_addr uintptr
|
||||
|
||||
//go:cgo_import_dynamic libc_proc_pidfdinfo proc_pidfdinfo "/usr/lib/libproc.dylib"
|
||||
|
||||
var libc_proc_pidpath_trampoline_addr uintptr
|
||||
|
||||
//go:cgo_import_dynamic libc_proc_pidpath proc_pidpath "/usr/lib/libproc.dylib"
|
||||
|
||||
// Implemented in the runtime package (runtime/sys_darwin.go).
|
||||
func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
|
||||
|
||||
//go:linkname syscall_syscall6 syscall.syscall6
|
||||
@@ -0,0 +1,18 @@
|
||||
//go:build darwin && !ios
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
TEXT libc_proc_pidinfo_trampoline<>(SB),NOSPLIT,$0-0
|
||||
JMP libc_proc_pidinfo(SB)
|
||||
GLOBL ·libc_proc_pidinfo_trampoline_addr(SB), RODATA, $8
|
||||
DATA ·libc_proc_pidinfo_trampoline_addr(SB)/8, $libc_proc_pidinfo_trampoline<>(SB)
|
||||
|
||||
TEXT libc_proc_pidfdinfo_trampoline<>(SB),NOSPLIT,$0-0
|
||||
JMP libc_proc_pidfdinfo(SB)
|
||||
GLOBL ·libc_proc_pidfdinfo_trampoline_addr(SB), RODATA, $8
|
||||
DATA ·libc_proc_pidfdinfo_trampoline_addr(SB)/8, $libc_proc_pidfdinfo_trampoline<>(SB)
|
||||
|
||||
TEXT libc_proc_pidpath_trampoline<>(SB),NOSPLIT,$0-0
|
||||
JMP libc_proc_pidpath(SB)
|
||||
GLOBL ·libc_proc_pidpath_trampoline_addr(SB), RODATA, $8
|
||||
DATA ·libc_proc_pidpath_trampoline_addr(SB)/8, $libc_proc_pidpath_trampoline<>(SB)
|
||||
@@ -0,0 +1,356 @@
|
||||
//go:build darwin && !ios
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
stdnet "net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func TestFindProcessDarwinTCP(t *testing.T) {
|
||||
listener, err := stdnet.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
accepted := make(chan stdnet.Conn, 1)
|
||||
go func() {
|
||||
conn, err := listener.Accept()
|
||||
if err == nil {
|
||||
accepted <- conn
|
||||
return
|
||||
}
|
||||
close(accepted)
|
||||
}()
|
||||
|
||||
conn, err := stdnet.Dial("tcp", listener.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
serverConn := <-accepted
|
||||
if serverConn == nil {
|
||||
t.Fatal("server did not accept tcp connection")
|
||||
}
|
||||
defer serverConn.Close()
|
||||
|
||||
local := conn.LocalAddr().(*stdnet.TCPAddr)
|
||||
remote := conn.RemoteAddr().(*stdnet.TCPAddr)
|
||||
|
||||
pid, name, path, err := FindProcess("tcp", local.IP.String(), uint16(local.Port), remote.IP.String(), uint16(remote.Port))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertCurrentProcess(t, pid, name, path)
|
||||
}
|
||||
|
||||
func TestFindProcessDarwinTCPIPv4Mapped(t *testing.T) {
|
||||
listener, err := stdnet.Listen("tcp4", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
accepted := make(chan stdnet.Conn, 1)
|
||||
go func() {
|
||||
conn, err := listener.Accept()
|
||||
if err == nil {
|
||||
accepted <- conn
|
||||
return
|
||||
}
|
||||
close(accepted)
|
||||
}()
|
||||
|
||||
listenerAddr := listener.Addr().(*stdnet.TCPAddr)
|
||||
fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_STREAM, unix.IPPROTO_TCP)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer unix.Close(fd)
|
||||
|
||||
mappedAddr := [16]byte{10: 0xff, 11: 0xff, 12: 127, 15: 1}
|
||||
if err := unix.Connect(fd, &unix.SockaddrInet6{
|
||||
Port: listenerAddr.Port,
|
||||
Addr: mappedAddr,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
serverConn := <-accepted
|
||||
if serverConn == nil {
|
||||
t.Fatal("server did not accept tcp connection")
|
||||
}
|
||||
defer serverConn.Close()
|
||||
|
||||
local, err := unix.Getsockname(fd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
localPort := local.(*unix.SockaddrInet6).Port
|
||||
|
||||
pid, name, path, err := FindProcess("tcp", "127.0.0.1", uint16(localPort), "127.0.0.1", uint16(listenerAddr.Port))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertCurrentProcess(t, pid, name, path)
|
||||
}
|
||||
|
||||
func TestFindProcessDarwinUDP(t *testing.T) {
|
||||
conn, err := stdnet.ListenUDP("udp", &stdnet.UDPAddr{IP: stdnet.ParseIP("127.0.0.1")})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
local := conn.LocalAddr().(*stdnet.UDPAddr)
|
||||
|
||||
pid, name, path, err := FindProcess("udp", local.IP.String(), uint16(local.Port), "", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertCurrentProcess(t, pid, name, path)
|
||||
}
|
||||
|
||||
func TestFindProcessDarwinNonLocal(t *testing.T) {
|
||||
_, _, _, err := FindProcess("tcp", "203.0.113.1", 80, "", 0)
|
||||
if err != ErrNotLocal {
|
||||
t.Fatalf("expected ErrNotLocal, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindProcessDarwinUnsupportedNetwork(t *testing.T) {
|
||||
defer func() {
|
||||
if recover() == nil {
|
||||
t.Fatal("expected panic")
|
||||
}
|
||||
}()
|
||||
|
||||
_, _, _, _ = FindProcess("icmp", "127.0.0.1", 0, "", 0)
|
||||
}
|
||||
|
||||
func assertCurrentProcess(t *testing.T, pid int, name string, path string) {
|
||||
t.Helper()
|
||||
|
||||
if pid != os.Getpid() {
|
||||
t.Fatalf("expected pid %d, got %d (%s, %s)", os.Getpid(), pid, name, path)
|
||||
}
|
||||
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if path == "" || name == "" {
|
||||
t.Fatalf("expected process path and name, got name=%q path=%q", name, path)
|
||||
}
|
||||
if sameFile(executable, path) {
|
||||
return
|
||||
}
|
||||
t.Fatalf("expected executable %q, got %q", executable, path)
|
||||
}
|
||||
|
||||
func sameFile(left string, right string) bool {
|
||||
leftInfo, leftErr := os.Stat(left)
|
||||
rightInfo, rightErr := os.Stat(right)
|
||||
if leftErr != nil || rightErr != nil {
|
||||
return false
|
||||
}
|
||||
return os.SameFile(leftInfo, rightInfo)
|
||||
}
|
||||
|
||||
func TestFindProcessDarwinTCPWithoutDestination(t *testing.T) {
|
||||
listener, err := stdnet.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
accepted := make(chan stdnet.Conn, 1)
|
||||
go func() {
|
||||
conn, err := listener.Accept()
|
||||
if err == nil {
|
||||
accepted <- conn
|
||||
return
|
||||
}
|
||||
close(accepted)
|
||||
}()
|
||||
|
||||
conn, err := stdnet.DialTimeout("tcp", listener.Addr().String(), time.Second)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
serverConn := <-accepted
|
||||
if serverConn == nil {
|
||||
t.Fatal("server did not accept tcp connection")
|
||||
}
|
||||
defer serverConn.Close()
|
||||
|
||||
local := conn.LocalAddr().(*stdnet.TCPAddr)
|
||||
pid, name, path, err := FindProcess("tcp", local.IP.String(), uint16(local.Port), "", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertCurrentProcess(t, pid, name, path)
|
||||
}
|
||||
|
||||
func TestFindProcessDarwinTCPWithDifferentDestination(t *testing.T) {
|
||||
listener, err := stdnet.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
accepted := make(chan stdnet.Conn, 1)
|
||||
go func() {
|
||||
conn, err := listener.Accept()
|
||||
if err == nil {
|
||||
accepted <- conn
|
||||
return
|
||||
}
|
||||
close(accepted)
|
||||
}()
|
||||
|
||||
conn, err := stdnet.DialTimeout("tcp", listener.Addr().String(), time.Second)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
serverConn := <-accepted
|
||||
if serverConn == nil {
|
||||
t.Fatal("server did not accept tcp connection")
|
||||
}
|
||||
defer serverConn.Close()
|
||||
|
||||
local := conn.LocalAddr().(*stdnet.TCPAddr)
|
||||
pid, name, path, err := FindProcess("tcp", local.IP.String(), uint16(local.Port), "203.0.113.10", 443)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertCurrentProcess(t, pid, name, path)
|
||||
}
|
||||
|
||||
func TestDarwinSocketInfoMatchLevelFallbacks(t *testing.T) {
|
||||
src := netip.MustParseAddr("198.18.0.2")
|
||||
dst := netip.MustParseAddr("203.0.113.10")
|
||||
otherLocal := netip.MustParseAddr("192.168.1.10")
|
||||
otherRemote := netip.MustParseAddr("198.51.100.10")
|
||||
unspecifiedLocal := netip.MustParseAddr("0.0.0.0")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
local netip.Addr
|
||||
remote netip.Addr
|
||||
hasDst bool
|
||||
wantLevel darwinSocketMatchLevel
|
||||
}{
|
||||
{
|
||||
name: "exact",
|
||||
local: src,
|
||||
remote: dst,
|
||||
hasDst: true,
|
||||
wantLevel: darwinSocketExactMatch,
|
||||
},
|
||||
{
|
||||
name: "unspecified local with matching remote",
|
||||
local: unspecifiedLocal,
|
||||
remote: dst,
|
||||
hasDst: true,
|
||||
wantLevel: darwinSocketExactMatch,
|
||||
},
|
||||
{
|
||||
name: "unspecified local without destination",
|
||||
local: unspecifiedLocal,
|
||||
remote: otherRemote,
|
||||
hasDst: false,
|
||||
wantLevel: darwinSocketExactMatch,
|
||||
},
|
||||
{
|
||||
name: "local match with different remote",
|
||||
local: src,
|
||||
remote: otherRemote,
|
||||
hasDst: true,
|
||||
wantLevel: darwinSocketLocalMatch,
|
||||
},
|
||||
{
|
||||
name: "remote match with different local",
|
||||
local: otherLocal,
|
||||
remote: dst,
|
||||
hasDst: true,
|
||||
wantLevel: darwinSocketRemoteMatch,
|
||||
},
|
||||
{
|
||||
name: "port only with destination",
|
||||
local: otherLocal,
|
||||
remote: otherRemote,
|
||||
hasDst: true,
|
||||
wantLevel: darwinSocketPortMatch,
|
||||
},
|
||||
{
|
||||
name: "different local without destination",
|
||||
local: otherLocal,
|
||||
remote: otherRemote,
|
||||
hasDst: false,
|
||||
wantLevel: darwinSocketNoMatch,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
info := newDarwinSocketInfo("tcp", test.local, 12345, test.remote, 443)
|
||||
level := darwinSocketInfoMatchLevel(info, "tcp", src, 12345, dst, 443, test.hasDst)
|
||||
if level != test.wantLevel {
|
||||
t.Fatalf("unexpected match level: got %d, want %d", level, test.wantLevel)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDarwinSocketInfoMatchLevelIPv4Mapped(t *testing.T) {
|
||||
src := netip.MustParseAddr("127.0.0.1")
|
||||
dst := netip.MustParseAddr("203.0.113.10")
|
||||
info := newDarwinSocketInfo("tcp", src, 12345, dst, 443)
|
||||
writeDarwinNativeUint32(info, darwinSocketInfoFamilyOff, uint32(unix.AF_INET6))
|
||||
|
||||
level := darwinSocketInfoMatchLevel(info, "tcp", src, 12345, dst, 443, true)
|
||||
if level != darwinSocketExactMatch {
|
||||
t.Fatalf("unexpected match level: got %d, want %d", level, darwinSocketExactMatch)
|
||||
}
|
||||
}
|
||||
|
||||
func newDarwinSocketInfo(network string, local netip.Addr, localPort uint16, remote netip.Addr, remotePort uint16) []byte {
|
||||
info := make([]byte, darwinSocketFDInfoSize)
|
||||
switch network {
|
||||
case "tcp":
|
||||
writeDarwinNativeUint32(info, darwinSocketInfoProtoOff, uint32(unix.IPPROTO_TCP))
|
||||
writeDarwinNativeUint32(info, darwinSocketInfoKindOff, uint32(darwinSockInfoTCP))
|
||||
case "udp":
|
||||
writeDarwinNativeUint32(info, darwinSocketInfoProtoOff, uint32(unix.IPPROTO_UDP))
|
||||
writeDarwinNativeUint32(info, darwinSocketInfoKindOff, uint32(darwinSockInfoIN))
|
||||
}
|
||||
writeDarwinNativeUint32(info, darwinSocketInfoFamilyOff, uint32(unix.AF_INET))
|
||||
info[darwinInSockInfoVFlagOff] = darwinInSockInfoIPv4
|
||||
writeDarwinNativeUint32(info, darwinInSockInfoLPortOff, uint32(localPort))
|
||||
writeDarwinNativeUint32(info, darwinInSockInfoFPortOff, uint32(remotePort))
|
||||
copyDarwinIPv4(info[darwinInSockInfoLAddrOff:darwinInSockInfoLAddrOff+16], local)
|
||||
copyDarwinIPv4(info[darwinInSockInfoFAddrOff:darwinInSockInfoFAddrOff+16], remote)
|
||||
return info
|
||||
}
|
||||
|
||||
func writeDarwinNativeUint32(b []byte, offset int, value uint32) {
|
||||
*(*uint32)(unsafe.Pointer(&b[offset])) = value
|
||||
}
|
||||
|
||||
func copyDarwinIPv4(dst []byte, addr netip.Addr) {
|
||||
ip := addr.As4()
|
||||
copy(dst[12:16], ip[:])
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//go:build ios
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
|
||||
func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort uint16) (int, string, string, error) {
|
||||
return 0, "", "", errors.New("process lookup is not supported on this platform")
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !windows && !linux && !android
|
||||
//go:build !windows && !linux && !android && !darwin
|
||||
|
||||
package net
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var envReloadRegistry = struct {
|
||||
sync.RWMutex
|
||||
handlers []func() error
|
||||
}{}
|
||||
|
||||
// RegisterEnvReload registers an environment reload handler and runs it once
|
||||
// immediately so package defaults keep the same behavior as init-time reads.
|
||||
func RegisterEnvReload(handler func() error) {
|
||||
if handler == nil {
|
||||
return
|
||||
}
|
||||
envReloadRegistry.Lock()
|
||||
envReloadRegistry.handlers = append(envReloadRegistry.handlers, handler)
|
||||
envReloadRegistry.Unlock()
|
||||
if err := handler(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReloadEnvSettings refreshes all registered environment-backed package state.
|
||||
func ReloadEnvSettings() error {
|
||||
envReloadRegistry.RLock()
|
||||
handlers := append([]func() error{}, envReloadRegistry.handlers...)
|
||||
envReloadRegistry.RUnlock()
|
||||
|
||||
var errs []error
|
||||
for _, handler := range handlers {
|
||||
if err := handler(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
@@ -3,11 +3,8 @@ package bittorrent
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
)
|
||||
|
||||
type SniffHeader struct{}
|
||||
@@ -39,50 +36,44 @@ func SniffUTP(b []byte) (*SniffHeader, error) {
|
||||
return nil, common.ErrNoClue
|
||||
}
|
||||
|
||||
buffer := buf.FromBytes(b)
|
||||
|
||||
var typeAndVersion uint8
|
||||
|
||||
if binary.Read(buffer, binary.BigEndian, &typeAndVersion) != nil {
|
||||
return nil, common.ErrNoClue
|
||||
} else if b[0]>>4&0xF > 4 || b[0]&0xF != 1 {
|
||||
// type 4 (ST_SYN), version 1
|
||||
if b[0] != 0x41 {
|
||||
return nil, errNotBittorrent
|
||||
}
|
||||
|
||||
var extension uint8
|
||||
|
||||
if binary.Read(buffer, binary.BigEndian, &extension) != nil {
|
||||
return nil, common.ErrNoClue
|
||||
} else if extension != 0 && extension != 1 {
|
||||
// timestamp_difference is always 0 in new connections
|
||||
if binary.BigEndian.Uint32(b[8:12]) != 0 {
|
||||
return nil, errNotBittorrent
|
||||
}
|
||||
|
||||
// Walk the extension chain. Selective ack (1) and extension bits (2)
|
||||
extension, offset := b[1], 20
|
||||
for extension != 0 {
|
||||
if extension != 1 {
|
||||
if len(b) < offset+2 {
|
||||
return nil, errNotBittorrent
|
||||
}
|
||||
if binary.Read(buffer, binary.BigEndian, &extension) != nil {
|
||||
return nil, common.ErrNoClue
|
||||
length := int(b[offset+1])
|
||||
switch extension {
|
||||
case 1: // selective ack
|
||||
if length < 4 || length%4 != 0 {
|
||||
return nil, errNotBittorrent
|
||||
}
|
||||
case 2: // extension bits: fixed 8 bytes, sent in ST_SYN by µTorrent
|
||||
if length != 8 {
|
||||
return nil, errNotBittorrent
|
||||
}
|
||||
default:
|
||||
return nil, errNotBittorrent
|
||||
}
|
||||
|
||||
var length uint8
|
||||
if err := binary.Read(buffer, binary.BigEndian, &length); err != nil {
|
||||
return nil, common.ErrNoClue
|
||||
}
|
||||
if common.Error2(buffer.ReadBytes(int32(length))) != nil {
|
||||
return nil, common.ErrNoClue
|
||||
if len(b) < offset+2+length {
|
||||
return nil, errNotBittorrent
|
||||
}
|
||||
extension = b[offset]
|
||||
offset += 2 + length
|
||||
}
|
||||
|
||||
if common.Error2(buffer.ReadBytes(2)) != nil {
|
||||
return nil, common.ErrNoClue
|
||||
}
|
||||
|
||||
var timestamp uint32
|
||||
if err := binary.Read(buffer, binary.BigEndian, ×tamp); err != nil {
|
||||
return nil, common.ErrNoClue
|
||||
}
|
||||
if math.Abs(float64(time.Now().UnixMicro()-int64(timestamp))) > float64(24*time.Hour) {
|
||||
// extensions should consume all ST_SYN payload
|
||||
if len(b) != offset {
|
||||
return nil, errNotBittorrent
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package bittorrent
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
)
|
||||
|
||||
// utpPacket builds the fixed 20-byte header defined by BEP 29.
|
||||
func utpPacket(packetType, extension byte, tsDiff uint32, payload ...byte) []byte {
|
||||
b := make([]byte, 20)
|
||||
b[0] = packetType<<4 | 1
|
||||
b[1] = extension
|
||||
binary.BigEndian.PutUint16(b[2:4], 0x4a3f) // connection_id, random
|
||||
binary.BigEndian.PutUint32(b[4:8], 0x8c3a91d2) // timestamp_microseconds, sender's clock
|
||||
binary.BigEndian.PutUint32(b[8:12], tsDiff)
|
||||
binary.BigEndian.PutUint32(b[12:16], 0x00100000) // wnd_size
|
||||
binary.BigEndian.PutUint16(b[16:18], 0x71ee) // seq_nr, random in libutp/libtorrent
|
||||
binary.BigEndian.PutUint16(b[18:20], 0x0000) // ack_nr
|
||||
return append(b, payload...)
|
||||
}
|
||||
|
||||
func TestSniffUTP(t *testing.T) {
|
||||
selectiveAck := []byte{0, 4, 0xff, 0x00, 0xff, 0x00}
|
||||
wrongVersion := utpPacket(4, 0, 0)
|
||||
wrongVersion[0] = 4<<4 | 2
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
err error
|
||||
}{
|
||||
{"syn", utpPacket(4, 0, 0), nil},
|
||||
{"syn with selective ack", append(utpPacket(4, 1, 0), selectiveAck...), nil},
|
||||
{"syn with extension bits", append(utpPacket(4, 2, 0), 0, 8, 1, 2, 3, 4, 5, 6, 7, 8), nil},
|
||||
{"extension bits with wrong length", append(utpPacket(4, 2, 0), 0, 4, 1, 2, 3, 4), errNotBittorrent},
|
||||
{"syn with nonzero timestamp_difference", utpPacket(4, 0, 0x1234), errNotBittorrent},
|
||||
{"syn with trailing payload", utpPacket(4, 0, 0, 'x'), errNotBittorrent},
|
||||
// txid 0x4100, no EDNS0: the worst case colliding with the uTP header
|
||||
{"dns query", []byte{
|
||||
0x41, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 'a', 0x02, 'c', 'o', 0x00, 0x00, 0x01, 0x00, 0x01,
|
||||
}, errNotBittorrent},
|
||||
{"established connection packets", utpPacket(0, 0, 0x5678, 'x', 'y', 'z'), errNotBittorrent},
|
||||
{"state", utpPacket(2, 0, 0x5678), errNotBittorrent},
|
||||
{"fin", utpPacket(1, 0, 0x5678), errNotBittorrent},
|
||||
{"wrong version", wrongVersion, errNotBittorrent},
|
||||
{"unknown packet type", utpPacket(5, 0, 0), errNotBittorrent},
|
||||
{"unknown extension", utpPacket(4, 2, 0), errNotBittorrent},
|
||||
{"extension chain past the datagram", utpPacket(4, 1, 0, 0, 8, 0xff), errNotBittorrent},
|
||||
{"selective ack not in multiples of 4", append(utpPacket(4, 1, 0), 0, 3, 0xff, 0x00, 0xff), errNotBittorrent},
|
||||
{"shorter than the header", utpPacket(4, 0, 0)[:19], common.ErrNoClue},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
h, err := SniffUTP(c.payload)
|
||||
if err != c.err {
|
||||
t.Fatalf("expected error %v, got %v", c.err, err)
|
||||
}
|
||||
if err == nil && h == nil {
|
||||
t.Fatal("expected a sniff header, got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,10 @@
|
||||
package quic
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/cipher"
|
||||
_ "crypto/tls"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
type CipherSuiteTLS13 struct {
|
||||
ID uint16
|
||||
KeyLen int
|
||||
AEAD func(key, fixedNonce []byte) cipher.AEAD
|
||||
Hash crypto.Hash
|
||||
}
|
||||
|
||||
//go:linkname AEADAESGCMTLS13 crypto/tls.aeadAESGCMTLS13
|
||||
func AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD
|
||||
|
||||
@@ -3,7 +3,6 @@ package quic
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
@@ -28,22 +27,43 @@ func (s SniffHeader) Domain() string {
|
||||
return s.domain
|
||||
}
|
||||
|
||||
const (
|
||||
versionDraft29 uint32 = 0xff00001d
|
||||
version1 uint32 = 0x1
|
||||
var (
|
||||
errNotQUIC = errors.New("not quic")
|
||||
errNotQUICInitial = errors.New("not initial packet")
|
||||
)
|
||||
|
||||
type quicVersionSpec struct {
|
||||
ver uint32
|
||||
typeInitial byte
|
||||
initialSalt []byte
|
||||
labelPrefix string
|
||||
}
|
||||
|
||||
var (
|
||||
quicSaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
|
||||
quicSalt = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
|
||||
initialSuite = &CipherSuiteTLS13{
|
||||
ID: tls.TLS_AES_128_GCM_SHA256,
|
||||
KeyLen: 16,
|
||||
AEAD: AEADAESGCMTLS13,
|
||||
Hash: crypto.SHA256,
|
||||
quicDraft29 = quicVersionSpec{
|
||||
ver: 0xff00001d,
|
||||
typeInitial: 0b00,
|
||||
initialSalt: []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99},
|
||||
labelPrefix: "quic",
|
||||
}
|
||||
quicV1 = quicVersionSpec{
|
||||
ver: 0x1,
|
||||
typeInitial: 0b00,
|
||||
initialSalt: []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a},
|
||||
labelPrefix: "quic",
|
||||
}
|
||||
quicV2 = quicVersionSpec{
|
||||
ver: 0x6b3343cf,
|
||||
typeInitial: 0b01,
|
||||
initialSalt: []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9},
|
||||
labelPrefix: "quicv2",
|
||||
}
|
||||
|
||||
quicVersionSpecMap = map[uint32]*quicVersionSpec{
|
||||
quicDraft29.ver: &quicDraft29,
|
||||
quicV1.ver: &quicV1,
|
||||
quicV2.ver: &quicV2,
|
||||
}
|
||||
errNotQuic = errors.New("not quic")
|
||||
errNotQuicInitial = errors.New("not initial packet")
|
||||
)
|
||||
|
||||
func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
@@ -63,60 +83,61 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
buffer := buf.FromBytes(b)
|
||||
typeByte, err := buffer.ReadByte()
|
||||
if err != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
|
||||
isLongHeader := typeByte&0x80 > 0
|
||||
if !isLongHeader || typeByte&0x40 == 0 {
|
||||
return nil, errNotQuicInitial
|
||||
return nil, errNotQUICInitial
|
||||
}
|
||||
|
||||
vb, err := buffer.ReadBytes(4)
|
||||
if err != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
|
||||
versionNumber := binary.BigEndian.Uint32(vb)
|
||||
if versionNumber != 0 && typeByte&0x40 == 0 {
|
||||
return nil, errNotQuic
|
||||
} else if versionNumber != versionDraft29 && versionNumber != version1 {
|
||||
return nil, errNotQuic
|
||||
var s *quicVersionSpec
|
||||
if v, ok := quicVersionSpecMap[versionNumber]; ok {
|
||||
s = v
|
||||
} else {
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
|
||||
packetType := (typeByte & 0x30) >> 4
|
||||
isQuicInitial := packetType == 0x0
|
||||
|
||||
var destConnID []byte
|
||||
if l, err := buffer.ReadByte(); err != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
} else if destConnID, err = buffer.ReadBytes(int32(l)); err != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
|
||||
if l, err := buffer.ReadByte(); err != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
} else if common.Error2(buffer.ReadBytes(int32(l))) != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
|
||||
if isQuicInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
|
||||
tokenLen, err := readShortQuicVarint(buffer)
|
||||
packetType := (typeByte & 0x30) >> 4
|
||||
isQUICInitial := packetType == s.typeInitial
|
||||
|
||||
if isQUICInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
|
||||
tokenLen, err := readShortQUICVarint(buffer)
|
||||
if err != nil || tokenLen > int32(len(b)) {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
|
||||
if _, err = buffer.ReadBytes(tokenLen); err != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
}
|
||||
|
||||
packetLen, err := readShortQuicVarint(buffer)
|
||||
packetLen, err := readShortQUICVarint(buffer)
|
||||
if err != nil {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
// packetLen is impossible to be shorter than this
|
||||
if packetLen < 4 {
|
||||
return nil, errNotQuic
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
|
||||
hdrLen := len(b) - int(buffer.Len())
|
||||
@@ -125,25 +146,23 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
}
|
||||
|
||||
restPayload := b[hdrLen+int(packetLen):]
|
||||
if !isQuicInitial { // Skip this packet if it's not initial packet
|
||||
if !isQUICInitial { // Skip this packet if it's not initial packet
|
||||
b = restPayload
|
||||
continue
|
||||
}
|
||||
|
||||
var salt []byte
|
||||
if versionNumber == version1 {
|
||||
salt = quicSalt
|
||||
} else {
|
||||
salt = quicSaltOld
|
||||
}
|
||||
salt := s.initialSalt
|
||||
label := s.labelPrefix
|
||||
initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)
|
||||
secret := hkdfExpandLabel(crypto.SHA256, initialSecret, []byte{}, "client in", crypto.SHA256.Size())
|
||||
hpKey := hkdfExpandLabel(initialSuite.Hash, secret, []byte{}, "quic hp", initialSuite.KeyLen)
|
||||
secret := hkdfExpandLabel(initialSecret, "client in", crypto.SHA256.Size())
|
||||
hpKey := hkdfExpandLabel(secret, label+" hp", 16)
|
||||
block, err := aes.NewCipher(hpKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(b) < hdrLen+4+block.BlockSize() {
|
||||
return nil, errNotQUIC
|
||||
}
|
||||
cache.Clear()
|
||||
mask := cache.Extend(int32(block.BlockSize()))
|
||||
block.Encrypt(mask, b[hdrLen+4:hdrLen+4+len(mask)])
|
||||
@@ -153,8 +172,8 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
b[hdrLen+i] ^= mask[i+1]
|
||||
}
|
||||
|
||||
key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16)
|
||||
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
|
||||
key := hkdfExpandLabel(secret, label+" key", 16)
|
||||
iv := hkdfExpandLabel(secret, label+" iv", 12)
|
||||
cipher := AEADAESGCMTLS13(key, iv)
|
||||
|
||||
nonce := cache.Extend(int32(cipher.NonceSize()))
|
||||
@@ -179,44 +198,44 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
case 0x00: // PADDING frame
|
||||
case 0x01: // PING frame
|
||||
case 0x02, 0x03: // ACK frame
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Largest Acknowledged
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Largest Acknowledged
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Delay
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Delay
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
ackRangeCount, err := readShortQuicVarint(buffer) // Field: ACK Range Count
|
||||
ackRangeCount, err := readShortQUICVarint(buffer) // Field: ACK Range Count
|
||||
if err != nil {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: First ACK Range
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: First ACK Range
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Range -> Gap
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Range -> Gap
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Range -> ACK Range Length
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Range -> ACK Range Length
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
}
|
||||
if frameType == 0x03 {
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
}
|
||||
case 0x06: // CRYPTO frame, we will use this frame
|
||||
offset, err := readShortQuicVarint(buffer) // Field: Offset
|
||||
offset, err := readShortQUICVarint(buffer) // Field: Offset
|
||||
if err != nil {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
length, err := readShortQuicVarint(buffer) // Field: Length
|
||||
length, err := readShortQUICVarint(buffer) // Field: Length
|
||||
if err != nil || length > buffer.Len() {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -232,13 +251,13 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Error Code
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Error Code
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Frame Type
|
||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Frame Type
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
length, err := readShortQuicVarint(buffer) // Field: Reason Phrase Length
|
||||
length, err := readShortQUICVarint(buffer) // Field: Reason Phrase Length
|
||||
if err != nil {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
@@ -248,7 +267,7 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
default:
|
||||
// Only above frame types are permitted in initial packet.
|
||||
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
|
||||
return nil, errNotQuicInitial
|
||||
return nil, errNotQUICInitial
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,35 +285,33 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||
return nil, protocol.ErrProtoNeedMoreData
|
||||
}
|
||||
|
||||
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {
|
||||
b := make([]byte, 3, 3+6+len(label)+1+len(context))
|
||||
binary.BigEndian.PutUint16(b, uint16(length))
|
||||
b[2] = uint8(6 + len(label))
|
||||
b = append(b, []byte("tls13 ")...)
|
||||
b = append(b, []byte(label)...)
|
||||
b = b[:3+6+len(label)+1]
|
||||
b[3+6+len(label)] = uint8(len(context))
|
||||
b = append(b, context...)
|
||||
func hkdfExpandLabel(secret []byte, label string, length int) []byte {
|
||||
b := make([]byte, 0, 2+1+6+len(label)+1)
|
||||
b = binary.BigEndian.AppendUint16(b, uint16(length))
|
||||
b = append(b, byte(6+len(label)))
|
||||
b = append(b, "tls13 "...)
|
||||
b = append(b, label...)
|
||||
b = append(b, 0) // context
|
||||
|
||||
out := make([]byte, length)
|
||||
n, err := hkdf.Expand(hash.New, secret, b).Read(out)
|
||||
n, err := hkdf.Expand(crypto.SHA256.New, secret, b).Read(out)
|
||||
if err != nil || n != length {
|
||||
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// readShortQuicVarint wraps quicvarint.Read with a max limit for length related fields.
|
||||
// readShortQUICVarint wraps quicvarint.Read with a max limit for length related fields.
|
||||
// we only handle QUIC Initial so these numbers should not exceed 65535
|
||||
// returns int32 to reduce type conversion
|
||||
func readShortQuicVarint(reader io.ByteReader) (int32, error) {
|
||||
func readShortQUICVarint(reader io.ByteReader) (int32, error) {
|
||||
v, err := quicvarint.Read(reader)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if v > 65535 {
|
||||
// not used(
|
||||
return 0, errNotQuicInitial
|
||||
return 0, errNotQUICInitial
|
||||
}
|
||||
return int32(v), nil
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -207,6 +207,7 @@ func getConfig() string {
|
||||
"tag": "XHTTP_IN",
|
||||
"streamSettings": {
|
||||
"network": "xhttp",
|
||||
"security": "tls",
|
||||
"xhttpSettings": {
|
||||
"host": "bing.com",
|
||||
"path": "/xhttp_client_upload",
|
||||
|
||||
+33
-19
@@ -8,7 +8,7 @@ import (
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
@@ -27,25 +27,39 @@ var AddrParser = protocol.NewAddressParser(
|
||||
)
|
||||
|
||||
var (
|
||||
Show bool
|
||||
BaseKey []byte
|
||||
Show atomic.Bool
|
||||
baseKey atomic.Value
|
||||
)
|
||||
|
||||
func init() {
|
||||
if strings.ToLower(platform.NewEnvFlag(platform.XUDPLog).GetValue(func() string { return "" })) == "true" {
|
||||
Show = true
|
||||
func reloadEnvSettings() error {
|
||||
Show.Store(strings.ToLower(platform.NewEnvFlag(platform.XUDPLog).GetValue(func() string { return "" })) == "true")
|
||||
raw := platform.NewEnvFlag(platform.XUDPBaseKey).GetValue(func() string { return "" })
|
||||
if raw == "" {
|
||||
ensureBaseKey()
|
||||
return nil
|
||||
}
|
||||
BaseKey = make([]byte, 32)
|
||||
rand.Read(BaseKey)
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond) // this is not nice, but need to give some time for Android to setup ENV
|
||||
if raw := platform.NewEnvFlag(platform.XUDPBaseKey).GetValue(func() string { return "" }); raw != "" {
|
||||
if BaseKey, _ = base64.RawURLEncoding.DecodeString(raw); len(BaseKey) == 32 {
|
||||
return
|
||||
}
|
||||
panic(platform.XUDPBaseKey + ": invalid value (BaseKey must be 32 bytes): " + raw + " len " + strconv.Itoa(len(BaseKey)))
|
||||
}
|
||||
}()
|
||||
key, _ := base64.RawURLEncoding.DecodeString(raw)
|
||||
if len(key) != 32 {
|
||||
return errors.New(platform.XUDPBaseKey + ": invalid value (BaseKey must be 32 bytes): " + raw + " len " + strconv.Itoa(len(key)))
|
||||
}
|
||||
baseKey.Store(append([]byte(nil), key...))
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureBaseKey() []byte {
|
||||
if key := baseKey.Load(); key != nil {
|
||||
return key.([]byte)
|
||||
}
|
||||
key := make([]byte, 32)
|
||||
if _, err := rand.Read(key); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
baseKey.Store(key)
|
||||
return key
|
||||
}
|
||||
|
||||
func init() {
|
||||
platform.RegisterEnvReload(reloadEnvSettings)
|
||||
}
|
||||
|
||||
func GetGlobalID(ctx context.Context) (globalID [8]byte) {
|
||||
@@ -54,10 +68,10 @@ func GetGlobalID(ctx context.Context) (globalID [8]byte) {
|
||||
}
|
||||
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.Network == net.Network_UDP &&
|
||||
(inbound.Name == "dokodemo-door" || inbound.Name == "socks" || inbound.Name == "shadowsocks" || inbound.Name == "tun") {
|
||||
h := blake3.New(8, BaseKey)
|
||||
h := blake3.New(8, ensureBaseKey())
|
||||
h.Write([]byte(inbound.Source.String()))
|
||||
copy(globalID[:], h.Sum(nil))
|
||||
if Show {
|
||||
if Show.Load() {
|
||||
errors.LogInfo(ctx, fmt.Sprintf("XUDP inbound.Source.String(): %v\tglobalID: %v\n", inbound.Source.String(), globalID))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user