diff --git a/app/router/condition.go b/app/router/condition.go index d21487a20371..8d9027fe39e4 100644 --- a/app/router/condition.go +++ b/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 +} diff --git a/app/router/config.go b/app/router/config.go index 9f6a4844408e..dca625839ba4 100644 --- a/app/router/config.go +++ b/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() } diff --git a/app/router/config.pb.go b/app/router/config.pb.go index 25f679b01868..c5803f56d762 100644 --- a/app/router/config.pb.go +++ b/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 ( diff --git a/app/router/config.proto b/app/router/config.proto index f26bccb4daea..222f395ded6d 100644 --- a/app/router/config.proto +++ b/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 { diff --git a/common/net/find_process_linux.go b/common/net/find_process_linux.go new file mode 100644 index 000000000000..80080b709b96 --- /dev/null +++ b/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 +} diff --git a/common/net/find_process_others.go b/common/net/find_process_others.go new file mode 100644 index 000000000000..472d4f8573f0 --- /dev/null +++ b/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") +} diff --git a/common/net/find_process_windows.go b/common/net/find_process_windows.go new file mode 100644 index 000000000000..fe2232548fa5 --- /dev/null +++ b/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 +} diff --git a/common/net/net.go b/common/net/net.go index 92431c421f5c..115ede7de121 100644 --- a/common/net/net.go +++ b/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 +} diff --git a/infra/conf/router.go b/infra/conf/router.go index 6e09038bc364..38158b780eff 100644 --- a/infra/conf/router.go +++ b/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 }