Przeglądaj źródła

Routing config: Add `processName` (#5489)

风扇滑翔翼 5 miesięcy temu
rodzic
commit
7265b5ac3f

+ 39 - 0
app/router/condition.go

@@ -2,6 +2,7 @@ package router
 
 import (
 	"context"
+	"os"
 	"regexp"
 	"strings"
 
@@ -305,3 +306,41 @@ func (m *AttributeMatcher) Apply(ctx routing.Context) bool {
 	}
 	return m.Match(attributes)
 }
+
+type ProcessNameMatcher struct {
+	names []string
+}
+
+func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
+	srcPort := ctx.GetSourcePort().String()
+	srcIP := ctx.GetSourceIPs()[0].String()
+	var network string
+	switch ctx.GetNetwork() {
+	case net.Network_TCP:
+		network = "tcp"
+	case net.Network_UDP:
+		network = "udp"
+	default:
+		return false
+	}
+	src, err := net.ParseDestination(strings.Join([]string{network, srcIP, srcPort}, ":"))
+	if err != nil {
+		return false
+	}
+	pid, name, 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() {
+			return true
+		}
+		if n == name {
+			return true
+		}
+	}
+	return false
+}

+ 8 - 0
app/router/config.go

@@ -106,6 +106,14 @@ 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 conds.Len() == 0 {
 		return nil, errors.New("this rule has no effective fields").AtWarning()
 	}

+ 71 - 61
app/router/config.pb.go

@@ -490,6 +490,7 @@ type RoutingRule struct {
 	LocalGeoip     []*GeoIP          `protobuf:"bytes,17,rep,name=local_geoip,json=localGeoip,proto3" json:"local_geoip,omitempty"`
 	LocalPortList  *net.PortList     `protobuf:"bytes,18,opt,name=local_port_list,json=localPortList,proto3" json:"local_port_list,omitempty"`
 	VlessRouteList *net.PortList     `protobuf:"bytes,20,opt,name=vless_route_list,json=vlessRouteList,proto3" json:"vless_route_list,omitempty"`
+	ProcessName    []string          `protobuf:"bytes,21,rep,name=process_name,json=processName,proto3" json:"process_name,omitempty"`
 }
 
 func (x *RoutingRule) Reset() {
@@ -641,6 +642,13 @@ func (x *RoutingRule) GetVlessRouteList() *net.PortList {
 	return nil
 }
 
+func (x *RoutingRule) GetProcessName() []string {
+	if x != nil {
+		return x.ProcessName
+	}
+	return nil
+}
+
 type isRoutingRule_TargetTag interface {
 	isRoutingRule_TargetTag()
 }
@@ -1081,7 +1089,7 @@ var file_app_router_config_proto_rawDesc = []byte{
 	0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x65, 0x6e, 0x74,
 	0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
 	0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69,
-	0x74, 0x65, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xe8, 0x06, 0x0a, 0x0b, 0x52, 0x6f,
+	0x74, 0x65, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x8b, 0x07, 0x0a, 0x0b, 0x52, 0x6f,
 	0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x74, 0x61, 0x67,
 	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x25, 0x0a,
 	0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x0c,
@@ -1131,66 +1139,68 @@ var file_app_router_config_proto_rawDesc = []byte{
 	0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78,
 	0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50,
 	0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x0e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x52, 0x6f,
-	0x75, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69,
-	0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
-	0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05,
-	0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c,
-	0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,
-	0x5f, 0x74, 0x61, 0x67, 0x22, 0xdc, 0x01, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69,
-	0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62,
-	0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20,
-	0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x6c,
-	0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
-	0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
-	0x79, 0x12, 0x4d, 0x0a, 0x11, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x5f, 0x73, 0x65,
-	0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78,
-	0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61,
-	0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x10,
-	0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
-	0x12, 0x21, 0x0a, 0x0c, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x61, 0x67,
-	0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b,
-	0x54, 0x61, 0x67, 0x22, 0x54, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x57,
-	0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x12, 0x14, 0x0a,
-	0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61,
-	0x74, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01,
-	0x28, 0x02, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xc0, 0x01, 0x0a, 0x17, 0x53, 0x74,
-	0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x4c, 0x6f, 0x61, 0x64, 0x43,
-	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x0a, 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02,
-	0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e,
-	0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x57,
-	0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1c, 0x0a, 0x09,
-	0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52,
-	0x09, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78,
-	0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x65, 0x78,
-	0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x54, 0x54,
-	0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x54, 0x54, 0x12, 0x1c,
-	0x0a, 0x09, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28,
-	0x02, 0x52, 0x09, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x90, 0x02, 0x0a,
-	0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4f, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69,
-	0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
-	0x32, 0x26, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74,
-	0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
-	0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
-	0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x30, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65,
-	0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
-	0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67,
-	0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x45, 0x0a, 0x0e, 0x62, 0x61,
-	0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03,
-	0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f,
-	0x75, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75,
-	0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c,
-	0x65, 0x22, 0x3c, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74,
-	0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x10, 0x0a,
-	0x0c, 0x49, 0x70, 0x49, 0x66, 0x4e, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x10, 0x02, 0x12,
-	0x0e, 0x0a, 0x0a, 0x49, 0x70, 0x4f, 0x6e, 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x10, 0x03, 0x42,
-	0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e,
-	0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
-	0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63,
-	0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0xaa, 0x02,
-	0x0f, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72,
-	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x75, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x63, 0x65,
+	0x73, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x15, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x70,
+	0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74,
+	0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
+	0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
+	0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
+	0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72,
+	0x67, 0x65, 0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0xdc, 0x01, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61,
+	0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x6f,
+	0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72,
+	0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64,
+	0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x74, 0x72, 0x61,
+	0x74, 0x65, 0x67, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x61,
+	0x74, 0x65, 0x67, 0x79, 0x12, 0x4d, 0x0a, 0x11, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
+	0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65,
+	0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
+	0x65, 0x52, 0x10, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69,
+	0x6e, 0x67, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f,
+	0x74, 0x61, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x61, 0x6c, 0x6c, 0x62,
+	0x61, 0x63, 0x6b, 0x54, 0x61, 0x67, 0x22, 0x54, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
+	0x67, 0x79, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x65,
+	0x78, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70,
+	0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
+	0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xc0, 0x01, 0x0a,
+	0x17, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x4c, 0x6f,
+	0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x0a, 0x05, 0x63, 0x6f, 0x73, 0x74,
+	0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61,
+	0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
+	0x67, 0x79, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x12,
+	0x1c, 0x0a, 0x09, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03,
+	0x28, 0x03, 0x52, 0x09, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x1a, 0x0a,
+	0x08, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52,
+	0x08, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x61, 0x78,
+	0x52, 0x54, 0x54, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x54,
+	0x54, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06,
+	0x20, 0x01, 0x28, 0x02, 0x52, 0x09, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x22,
+	0x90, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4f, 0x0a, 0x0f, 0x64, 0x6f,
+	0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72,
+	0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d,
+	0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d,
+	0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x30, 0x0a, 0x04, 0x72,
+	0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74,
+	0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x45, 0x0a,
+	0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18,
+	0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
+	0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e,
+	0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67,
+	0x52, 0x75, 0x6c, 0x65, 0x22, 0x3c, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74,
+	0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00,
+	0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, 0x4e, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68,
+	0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70, 0x4f, 0x6e, 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64,
+	0x10, 0x03, 0x42, 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61,
+	0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x24, 0x67, 0x69, 0x74,
+	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61,
+	0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65,
+	0x72, 0xaa, 0x02, 0x0f, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75,
+	0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (

+ 1 - 0
app/router/config.proto

@@ -113,6 +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;
 }
 
 message BalancingRule {

+ 178 - 0
common/net/find_process_linux.go

@@ -0,0 +1,178 @@
+//go:build linux
+
+package net
+
+import (
+	"bufio"
+	"encoding/hex"
+	"fmt"
+	"os"
+	"strconv"
+	"strings"
+
+	"github.com/xtls/xray-core/common/errors"
+)
+
+func FindProcess(dest Destination) (int, string, error) {
+	isLocal, err := IsLocal(dest.Address.IP())
+	if err != nil {
+		return 0, "", errors.New("failed to determine if address is local: ", err)
+	}
+	if !isLocal {
+		return 0, "", ErrNotLocal
+	}
+	if dest.Network != Network_TCP && dest.Network != Network_UDP {
+		panic("Unsupported network type for process lookup.")
+	}
+	// the core should never has a domain as source(?
+	if dest.Address.Family() == AddressFamilyDomain {
+		panic("Domain addresses are not supported for process lookup.")
+	}
+	var procFile string
+
+	switch dest.Network {
+	case Network_TCP:
+		if dest.Address.Family() == AddressFamilyIPv4 {
+			procFile = "/proc/net/tcp"
+		}
+		if dest.Address.Family() == AddressFamilyIPv6 {
+			procFile = "/proc/net/tcp6"
+		}
+	case Network_UDP:
+		if dest.Address.Family() == AddressFamilyIPv4 {
+			procFile = "/proc/net/udp"
+		}
+		if dest.Address.Family() == AddressFamilyIPv6 {
+			procFile = "/proc/net/udp6"
+		}
+	default:
+		panic("Unsupported network type for process lookup.")
+	}
+
+	targetHexAddr, err := formatLittleEndianString(dest.Address, dest.Port)
+	if err != nil {
+		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)
+	}
+	if inode == "" {
+		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)
+	}
+	if pidStr == "" {
+		return 0, "", errors.New("no process found for inode ", inode)
+	}
+
+	procName, err := getProcessName(pidStr)
+	if err != nil {
+		return 0, "", fmt.Errorf("could not get process name for PID %s: %w", pidStr, err)
+	}
+
+	pid, err := strconv.Atoi(pidStr)
+	if err != nil {
+		return 0, "", errors.New("failed to parse PID: ", err)
+	}
+
+	return pid, procName, nil
+}
+
+func formatLittleEndianString(addr Address, port Port) (string, error) {
+	ip := addr.IP()
+	var ipBytes []byte
+	if addr.Family() == AddressFamilyIPv4 {
+		ipBytes = ip.To4()
+	} else {
+		ipBytes = ip.To16()
+	}
+	if ipBytes == nil {
+		return "", errors.New("invalid IP format for ", addr.Family(), ": ", ip)
+	}
+
+	for i, j := 0, len(ipBytes)-1; i < j; i, j = i+1, j-1 {
+		ipBytes[i], ipBytes[j] = ipBytes[j], ipBytes[i]
+	}
+	portHex := fmt.Sprintf("%04X", uint16(port))
+	ipHex := strings.ToUpper(hex.EncodeToString(ipBytes))
+	return fmt.Sprintf("%s:%s", ipHex, portHex), nil
+}
+
+func findInodeInFile(filePath, targetHexAddr string) (string, error) {
+	file, err := os.Open(filePath)
+	if err != nil {
+		return "", err
+	}
+	defer file.Close()
+
+	scanner := bufio.NewScanner(file)
+
+	for scanner.Scan() {
+		line := scanner.Text()
+		fields := strings.Fields(line)
+
+		if len(fields) < 10 {
+			continue
+		}
+
+		localAddress := fields[1]
+		if localAddress == targetHexAddr {
+			inode := fields[9]
+			return inode, nil
+		}
+	}
+
+	return "", scanner.Err()
+}
+
+func findPidByInode(inode string) (string, error) {
+	procDir, err := os.ReadDir("/proc")
+	if err != nil {
+		return "", err
+	}
+
+	targetLink := "socket:[" + inode + "]"
+
+	for _, entry := range procDir {
+		if !entry.IsDir() {
+			continue
+		}
+		pid := entry.Name()
+		if _, err := strconv.Atoi(pid); err != nil {
+			continue
+		}
+
+		fdPath := fmt.Sprintf("/proc/%s/fd", pid)
+		fdDir, err := os.ReadDir(fdPath)
+		if err != nil {
+			continue
+		}
+
+		for _, fdEntry := range fdDir {
+			linkPath := fmt.Sprintf("%s/%s", fdPath, fdEntry.Name())
+			linkTarget, err := os.Readlink(linkPath)
+			if err != nil {
+				continue
+			}
+			if linkTarget == targetLink {
+				return pid, nil
+			}
+		}
+	}
+	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
+}

+ 11 - 0
common/net/find_process_others.go

@@ -0,0 +1,11 @@
+//go:build !windows && !linux
+
+package net
+
+import (
+	"github.com/xtls/xray-core/common/errors"
+)
+
+func FindProcess(dest Destination) (int, string, error) {
+	return 0, "", errors.New("process lookup is not supported on this platform")
+}

+ 243 - 0
common/net/find_process_windows.go

@@ -0,0 +1,243 @@
+//go:build windows
+
+package net
+
+import (
+	"net/netip"
+	"strings"
+	"sync"
+	"syscall"
+	"unsafe"
+
+	"golang.org/x/sys/windows"
+
+	"github.com/xtls/xray-core/common/errors"
+)
+
+const (
+	tcpTableFunc    = "GetExtendedTcpTable"
+	tcpTablePidConn = 4
+	udpTableFunc    = "GetExtendedUdpTable"
+	udpTablePid     = 1
+)
+
+var (
+	getExTCPTable uintptr
+	getExUDPTable uintptr
+
+	once    sync.Once
+	initErr error
+)
+
+func initWin32API() error {
+	h, err := windows.LoadLibrary("iphlpapi.dll")
+	if err != nil {
+		return errors.New("LoadLibrary iphlpapi.dll failed").Base(err)
+	}
+
+	getExTCPTable, err = windows.GetProcAddress(h, tcpTableFunc)
+	if err != nil {
+		return errors.New("GetProcAddress of ", tcpTableFunc, " failed").Base(err)
+	}
+
+	getExUDPTable, err = windows.GetProcAddress(h, udpTableFunc)
+	if err != nil {
+		return errors.New("GetProcAddress of ", udpTableFunc, " failed").Base(err)
+	}
+
+	return nil
+}
+
+func FindProcess(dest Destination) (int, string, error) {
+	once.Do(func() {
+		initErr = initWin32API()
+	})
+	if initErr != nil {
+		return 0, "", initErr
+	}
+	isLocal, err := IsLocal(dest.Address.IP())
+	if err != nil {
+		return 0, "", errors.New("failed to determine if address is local: ", err)
+	}
+	if !isLocal {
+		return 0, "", ErrNotLocal
+	}
+	if dest.Network != Network_TCP && dest.Network != Network_UDP {
+		panic("Unsupported network type for process lookup.")
+	}
+	// the core should never has a domain as source(?
+	if dest.Address.Family() == AddressFamilyDomain {
+		panic("Domain addresses are not supported for process lookup.")
+	}
+	var class int
+	var fn uintptr
+	switch dest.Network {
+	case Network_TCP:
+		fn = getExTCPTable
+		class = tcpTablePidConn
+	case Network_UDP:
+		fn = getExUDPTable
+		class = udpTablePid
+	default:
+		panic("Unsupported network type for process lookup.")
+	}
+	ip := dest.Address.IP()
+	port := int(dest.Port)
+
+	addr, ok := netip.AddrFromSlice(ip)
+	if !ok {
+		return 0, "", errors.New("invalid IP address")
+	}
+	addr = addr.Unmap()
+
+	family := windows.AF_INET
+	if addr.Is6() {
+		family = windows.AF_INET6
+	}
+
+	buf, err := getTransportTable(fn, family, class)
+	if err != nil {
+		return 0, "", err
+	}
+
+	s := newSearcher(dest.Network, dest.Address.Family())
+
+	pid, err := s.Search(buf, addr, uint16(port))
+	if err != nil {
+		return 0, "", err
+	}
+	name, err := getExecPathFromPID(pid)
+	// drop .exe
+	name = strings.TrimSuffix(name, ".exe")
+	return int(pid), name, err
+}
+
+type searcher struct {
+	itemSize int
+	port     int
+	ip       int
+	ipSize   int
+	pid      int
+	tcpState int
+}
+
+func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error) {
+	n := int(readNativeUint32(b[:4]))
+	itemSize := s.itemSize
+	for i := range n {
+		row := b[4+itemSize*i : 4+itemSize*(i+1)]
+
+		if s.tcpState >= 0 {
+			tcpState := readNativeUint32(row[s.tcpState : s.tcpState+4])
+			// MIB_TCP_STATE_ESTAB, only check established connections for TCP
+			if tcpState != 5 {
+				continue
+			}
+		}
+
+		// according to MSDN, only the lower 16 bits of dwLocalPort are used and the port number is in network endian.
+		// this field can be illustrated as follows depends on different machine endianess:
+		//     little endian: [ MSB LSB  0   0  ]   interpret as native uint32 is ((LSB<<8)|MSB)
+		//       big  endian: [  0   0  MSB LSB ]   interpret as native uint32 is ((MSB<<8)|LSB)
+		// so we need an syscall.Ntohs on the lower 16 bits after read the port as native uint32
+		srcPort := syscall.Ntohs(uint16(readNativeUint32(row[s.port : s.port+4])))
+		if srcPort != port {
+			continue
+		}
+
+		srcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize])
+		srcIP = srcIP.Unmap()
+		// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
+		if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
+			continue
+		}
+
+		pid := readNativeUint32(row[s.pid : s.pid+4])
+		return pid, nil
+	}
+	return 0, errors.New("not found")
+}
+
+func newSearcher(network Network, family AddressFamily) *searcher {
+	var itemSize, port, ip, ipSize, pid int
+	tcpState := -1
+	switch network {
+	case Network_TCP:
+		if family == AddressFamilyIPv4 {
+			// struct MIB_TCPROW_OWNER_PID
+			itemSize, port, ip, ipSize, pid, tcpState = 24, 8, 4, 4, 20, 0
+		}
+		if family == AddressFamilyIPv6 {
+			// struct MIB_TCP6ROW_OWNER_PID
+			itemSize, port, ip, ipSize, pid, tcpState = 56, 20, 0, 16, 52, 48
+		}
+	case Network_UDP:
+		if family == AddressFamilyIPv4 {
+			// struct MIB_UDPROW_OWNER_PID
+			itemSize, port, ip, ipSize, pid = 12, 4, 0, 4, 8
+		}
+		if family == AddressFamilyIPv6 {
+			// struct MIB_UDP6ROW_OWNER_PID
+			itemSize, port, ip, ipSize, pid = 28, 20, 0, 16, 24
+		}
+	}
+
+	return &searcher{
+		itemSize: itemSize,
+		port:     port,
+		ip:       ip,
+		ipSize:   ipSize,
+		pid:      pid,
+		tcpState: tcpState,
+	}
+}
+
+func getTransportTable(fn uintptr, family int, class int) ([]byte, error) {
+	for size, buf := uint32(8), make([]byte, 8); ; {
+		ptr := unsafe.Pointer(&buf[0])
+		err, _, _ := syscall.Syscall6(fn, 6, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
+
+		switch err {
+		case 0:
+			return buf, nil
+		case uintptr(syscall.ERROR_INSUFFICIENT_BUFFER):
+			buf = make([]byte, size)
+		default:
+			return nil, errors.New("syscall error: ", int(err))
+		}
+	}
+}
+
+func readNativeUint32(b []byte) uint32 {
+	return *(*uint32)(unsafe.Pointer(&b[0]))
+}
+
+func getExecPathFromPID(pid uint32) (string, error) {
+	// kernel process starts with a colon in order to distinguish with normal processes
+	switch pid {
+	case 0:
+		// reserved pid for system idle process
+		return ":System Idle Process", nil
+	case 4:
+		// reserved pid for windows kernel image
+		return ":System", nil
+	}
+	h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
+	if err != nil {
+		return "", err
+	}
+	defer windows.CloseHandle(h)
+
+	buf := make([]uint16, syscall.MAX_LONG_PATH)
+	size := uint32(len(buf))
+	err = windows.QueryFullProcessImageName(h, 0, &buf[0], &size)
+	if err != nil {
+		return "", err
+	}
+	// full path will like: C:\Windows\System32\curl.exe
+	// we only need the executable name
+	fullPathName := syscall.UTF16ToString(buf[:size])
+	nameSplit := strings.Split(fullPathName, "\\")
+	name := nameSplit[len(nameSplit)-1]
+	return name, nil
+}

+ 41 - 1
common/net/net.go

@@ -1,7 +1,13 @@
 // Package net is a drop-in replacement to Golang's net package, with some more functionalities.
 package net // import "github.com/xtls/xray-core/common/net"
 
-import "time"
+import (
+	"net"
+	"sync/atomic"
+	"time"
+
+	"github.com/xtls/xray-core/common/errors"
+)
 
 // defines the maximum time an idle TCP session can survive in the tunnel, so
 // it should be consistent across HTTP versions and with other transports.
@@ -12,3 +18,37 @@ const QuicgoH3KeepAlivePeriod = 10 * time.Second
 
 // consistent with chrome
 const ChromeH2KeepAlivePeriod = 45 * time.Second
+
+var ErrNotLocal = errors.New("the source address is not from local machine.")
+
+type localIPCahceEntry struct {
+	addrs      []net.Addr
+	lastUpdate time.Time
+}
+
+var localIPCahce = atomic.Pointer[localIPCahceEntry]{}
+
+func IsLocal(ip net.IP) (bool, error) {
+	var addrs []net.Addr
+	if entry := localIPCahce.Load(); entry == nil || time.Since(entry.lastUpdate) > time.Minute {
+		var err error
+		addrs, err = net.InterfaceAddrs()
+		if err != nil {
+			return false, err
+		}
+		localIPCahce.Store(&localIPCahceEntry{
+			addrs:      addrs,
+			lastUpdate: time.Now(),
+		})
+	} else {
+		addrs = entry.addrs
+	}
+	for _, addr := range addrs {
+		if ipnet, ok := addr.(*net.IPNet); ok {
+			if ipnet.IP.Equal(ip) {
+				return true, nil
+			}
+		}
+	}
+	return false, nil
+}

+ 22 - 15
infra/conf/router.go

@@ -520,21 +520,22 @@ func ToCidrList(ips StringList) ([]*router.GeoIP, error) {
 func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
 	type RawFieldRule struct {
 		RouterRule
-		Domain     *StringList       `json:"domain"`
-		Domains    *StringList       `json:"domains"`
-		IP         *StringList       `json:"ip"`
-		Port       *PortList         `json:"port"`
-		Network    *NetworkList      `json:"network"`
-		SourceIP   *StringList       `json:"sourceIP"`
-		Source     *StringList       `json:"source"`
-		SourcePort *PortList         `json:"sourcePort"`
-		User       *StringList       `json:"user"`
-		VlessRoute *PortList         `json:"vlessRoute"`
-		InboundTag *StringList       `json:"inboundTag"`
-		Protocols  *StringList       `json:"protocol"`
-		Attributes map[string]string `json:"attrs"`
-		LocalIP    *StringList       `json:"localIP"`
-		LocalPort  *PortList         `json:"localPort"`
+		Domain      *StringList       `json:"domain"`
+		Domains     *StringList       `json:"domains"`
+		IP          *StringList       `json:"ip"`
+		Port        *PortList         `json:"port"`
+		Network     *NetworkList      `json:"network"`
+		SourceIP    *StringList       `json:"sourceIP"`
+		Source      *StringList       `json:"source"`
+		SourcePort  *PortList         `json:"sourcePort"`
+		User        *StringList       `json:"user"`
+		VlessRoute  *PortList         `json:"vlessRoute"`
+		InboundTag  *StringList       `json:"inboundTag"`
+		Protocols   *StringList       `json:"protocol"`
+		Attributes  map[string]string `json:"attrs"`
+		LocalIP     *StringList       `json:"localIP"`
+		LocalPort   *PortList         `json:"localPort"`
+		ProcessName *StringList       `json:"processName"`
 	}
 	rawFieldRule := new(RawFieldRule)
 	err := json.Unmarshal(msg, rawFieldRule)
@@ -647,6 +648,12 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
 		rule.Attributes = rawFieldRule.Attributes
 	}
 
+	if rawFieldRule.ProcessName != nil {
+		for _, s := range *rawFieldRule.ProcessName {
+			rule.ProcessName = append(rule.ProcessName, s)
+		}
+	}
+
 	return rule, nil
 }