Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 59 additions & 5 deletions app/router/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package router
import (
"context"
"os"
"path/filepath"
"regexp"
"slices"
"strings"

"github.com/xtls/xray-core/common/errors"
Expand Down Expand Up @@ -308,7 +310,51 @@ func (m *AttributeMatcher) Apply(ctx routing.Context) bool {
}

type ProcessNameMatcher struct {
names []string
ProcessNames []string
AbsPaths []string
Folders []string
MatchXraySelf bool
}

func NewProcessNameMatcher(names []string) *ProcessNameMatcher {
processNames := []string{}
folders := []string{}
absPaths := []string{}
matchXraySelf := false
for _, name := range names {
if name == "self/" {
matchXraySelf = true
continue
}
// replace xray/ with self executable path
if name == "xray/" {
xrayPath, err := os.Executable()
if err != nil {
errors.LogError(context.Background(), "Failed to get xray executable path: ", err)
continue
}
name = xrayPath
}
name := filepath.ToSlash(name)
// /usr/bin/
if strings.HasSuffix(name, "/") {
folders = append(folders, name)
continue
}
// /usr/bin/curl
if strings.Contains(name, "/") {
absPaths = append(absPaths, name)
continue
}
// curl.exe or curl
processNames = append(processNames, strings.TrimSuffix(name, ".exe"))
}
return &ProcessNameMatcher{
ProcessNames: processNames,
AbsPaths: absPaths,
Folders: folders,
MatchXraySelf: matchXraySelf,
}
}

func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
Expand All @@ -327,18 +373,26 @@ func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
if err != nil {
return false
}
pid, name, err := net.FindProcess(src)
pid, name, absPath, err := net.FindProcess(src)
if err != nil {
if err != net.ErrNotLocal {
errors.LogError(context.Background(), "Unables to find local process name: ", err)
}
return false
}
for _, n := range m.names {
if name == "/self" && pid == os.Getpid() {
if m.MatchXraySelf {
if pid == os.Getpid() {
return true
}
if n == name {
}
if slices.Contains(m.ProcessNames, name) {
return true
}
if slices.Contains(m.AbsPaths, absPath) {
return true
}
for _, f := range m.Folders {
if strings.HasPrefix(absPath, f) {
return true
}
}
Expand Down
8 changes: 2 additions & 6 deletions app/router/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,8 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
conds.Add(matcher)
}

if len(rr.ProcessName) > 0 {
refinedNames := make([]string, 0, len(rr.ProcessName))
for _, name := range rr.ProcessName {
refinedNames = append(refinedNames, strings.TrimSuffix(name, ".exe"))
}
conds.Add(&ProcessNameMatcher{refinedNames})
if len(rr.Process) > 0 {
conds.Add(NewProcessNameMatcher(rr.Process))
}

if conds.Len() == 0 {
Expand Down
132 changes: 66 additions & 66 deletions app/router/config.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/router/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ message RoutingRule {
xray.common.net.PortList local_port_list = 18;

xray.common.net.PortList vless_route_list = 20;
repeated string process_name = 21;
repeated string process = 21;
}

message BalancingRule {
Expand Down
38 changes: 18 additions & 20 deletions common/net/find_process_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import (
"github.com/xtls/xray-core/common/errors"
)

func FindProcess(dest Destination) (int, string, error) {
func FindProcess(dest Destination) (PID int, Name string, AbsolutePath string, err error) {
isLocal, err := IsLocal(dest.Address.IP())
if err != nil {
return 0, "", errors.New("failed to determine if address is local: ", err)
return 0, "", "", errors.New("failed to determine if address is local: ", err)
}
if !isLocal {
return 0, "", ErrNotLocal
return 0, "", "", ErrNotLocal
}
if dest.Network != Network_TCP && dest.Network != Network_UDP {
panic("Unsupported network type for process lookup.")
Expand Down Expand Up @@ -51,36 +51,39 @@ func FindProcess(dest Destination) (int, string, error) {

targetHexAddr, err := formatLittleEndianString(dest.Address, dest.Port)
if err != nil {
return 0, "", errors.New("failed to format address: ", err)
return 0, "", "", errors.New("failed to format address: ", err)
}

inode, err := findInodeInFile(procFile, targetHexAddr)
if err != nil {
return 0, "", errors.New("could not search in ", procFile).Base(err)
return 0, "", "", errors.New("could not search in ", procFile).Base(err)
}
if inode == "" {
return 0, "", errors.New("connection for ", dest.Address, ":", dest.Port, " not found in ", procFile)
return 0, "", "", errors.New("connection for ", dest.Address, ":", dest.Port, " not found in ", procFile)
}

pidStr, err := findPidByInode(inode)
if err != nil {
return 0, "", errors.New("could not find PID for inode ", inode, ": ", err)
return 0, "", "", errors.New("could not find PID for inode ", inode, ": ", err)
}
if pidStr == "" {
return 0, "", errors.New("no process found for inode ", inode)
return 0, "", "", errors.New("no process found for inode ", inode)
}

procName, err := getProcessName(pidStr)
absPath, err := getAbsPath(pidStr)
if err != nil {
return 0, "", fmt.Errorf("could not get process name for PID %s: %w", pidStr, err)
return 0, "", "", errors.New("could not get process name for PID ", pidStr, ":", err)
}

nameSplit := strings.Split(absPath, "/")
procName := nameSplit[len(nameSplit)-1]

pid, err := strconv.Atoi(pidStr)
if err != nil {
return 0, "", errors.New("failed to parse PID: ", err)
return 0, "", "", errors.New("failed to parse PID: ", err)
}

return pid, procName, nil
return pid, procName, absPath, nil
}

func formatLittleEndianString(addr Address, port Port) (string, error) {
Expand Down Expand Up @@ -167,12 +170,7 @@ func findPidByInode(inode string) (string, error) {
return "", nil
}

func getProcessName(pid string) (string, error) {
path := fmt.Sprintf("/proc/%s/comm", pid)
content, err := os.ReadFile(path)
if err != nil {
return "", err
}
// remove trailing \n
return strings.TrimSpace(string(content)), nil
func getAbsPath(pid string) (string, error) {
path := fmt.Sprintf("/proc/%s/exe", pid)
return os.Readlink(path)
}
Loading