mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-07-13 23:28:46 +00:00
294 lines
7.2 KiB
Go
294 lines
7.2 KiB
Go
//go:build darwin
|
|
|
|
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 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 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[:])
|
|
}
|