Browse Source

support range list in routing rule

Darien Raymond 6 years ago
parent
commit
95583b5031

+ 3 - 3
app/router/condition.go

@@ -177,12 +177,12 @@ func (m *MultiGeoIPMatcher) Apply(ctx context.Context) bool {
 }
 
 type PortMatcher struct {
-	port net.PortRange
+	port net.MemoryPortList
 }
 
-func NewPortMatcher(portRange net.PortRange) *PortMatcher {
+func NewPortMatcher(list *net.PortList) *PortMatcher {
 	return &PortMatcher{
-		port: portRange,
+		port: net.PortListFromProto(list),
 	}
 }
 

+ 28 - 0
app/router/condition_test.go

@@ -237,6 +237,34 @@ func TestRoutingRule(t *testing.T) {
 				},
 			},
 		},
+		{
+			rule: &RoutingRule{
+				PortList: &net.PortList{
+					Range: []*net.PortRange{
+						{From: 443, To: 443},
+						{From: 1000, To: 1100},
+					},
+				},
+			},
+			test: []ruleTest{
+				{
+					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 443)}),
+					output: true,
+				},
+				{
+					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 1100)}),
+					output: true,
+				},
+				{
+					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 1005)}),
+					output: true,
+				},
+				{
+					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 53)}),
+					output: false,
+				},
+			},
+		},
 	}
 
 	for _, test := range cases {

+ 5 - 2
app/router/config.go

@@ -5,6 +5,7 @@ package router
 import (
 	"context"
 
+	net "v2ray.com/core/common/net"
 	"v2ray.com/core/features/outbound"
 )
 
@@ -83,8 +84,10 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
 		conds.Add(NewInboundTagMatcher(rr.InboundTag))
 	}
 
-	if rr.PortRange != nil {
-		conds.Add(NewPortMatcher(*rr.PortRange))
+	if rr.PortList != nil {
+		conds.Add(NewPortMatcher(rr.PortList))
+	} else if rr.PortRange != nil {
+		conds.Add(NewPortMatcher(&net.PortList{Range: []*net.PortRange{rr.PortRange}}))
 	}
 
 	if len(rr.Networks) > 0 {

+ 72 - 59
app/router/config.pb.go

@@ -466,13 +466,17 @@ type RoutingRule struct {
 	// List of domains for target domain matching.
 	Domain []*Domain `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
 	// List of CIDRs for target IP address matching.
-	// The list must be sorted beforehand.
+	// Deprecated. Use geoip below.
 	Cidr []*CIDR `protobuf:"bytes,3,rep,name=cidr,proto3" json:"cidr,omitempty"` // Deprecated: Do not use.
 	// List of GeoIPs for target IP address matching. If this entry exists, the cidr above will have no effect.
 	// GeoIP fields with the same country code are supposed to contain exactly same content. They will be merged during runtime.
 	// For customized GeoIPs, please leave country code empty.
-	Geoip     []*GeoIP       `protobuf:"bytes,10,rep,name=geoip,proto3" json:"geoip,omitempty"`
-	PortRange *net.PortRange `protobuf:"bytes,4,opt,name=port_range,json=portRange,proto3" json:"port_range,omitempty"`
+	Geoip []*GeoIP `protobuf:"bytes,10,rep,name=geoip,proto3" json:"geoip,omitempty"`
+	// A range of port [from, to]. If the destination port is in this range, this rule takes effect.
+	// Deprecated. Use port_list.
+	PortRange *net.PortRange `protobuf:"bytes,4,opt,name=port_range,json=portRange,proto3" json:"port_range,omitempty"` // Deprecated: Do not use.
+	// List of ports.
+	PortList *net.PortList `protobuf:"bytes,14,opt,name=port_list,json=portList,proto3" json:"port_list,omitempty"`
 	// List of networks. Deprecated. Use networks.
 	NetworkList *net.NetworkList `protobuf:"bytes,5,opt,name=network_list,json=networkList,proto3" json:"network_list,omitempty"` // Deprecated: Do not use.
 	// List of networks for matching.
@@ -573,6 +577,7 @@ func (m *RoutingRule) GetGeoip() []*GeoIP {
 	return nil
 }
 
+// Deprecated: Do not use.
 func (m *RoutingRule) GetPortRange() *net.PortRange {
 	if m != nil {
 		return m.PortRange
@@ -580,6 +585,13 @@ func (m *RoutingRule) GetPortRange() *net.PortRange {
 	return nil
 }
 
+func (m *RoutingRule) GetPortList() *net.PortList {
+	if m != nil {
+		return m.PortList
+	}
+	return nil
+}
+
 // Deprecated: Do not use.
 func (m *RoutingRule) GetNetworkList() *net.NetworkList {
 	if m != nil {
@@ -761,61 +773,62 @@ func init() {
 }
 
 var fileDescriptor_6b1608360690c5fc = []byte{
-	// 881 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0xef, 0x6e, 0x1b, 0x45,
-	0x10, 0xcf, 0xdd, 0xd9, 0xae, 0x6f, 0xce, 0x31, 0xc7, 0x8a, 0xa2, 0x23, 0x10, 0x6a, 0x4e, 0x85,
-	0x5a, 0x02, 0x9d, 0x25, 0x17, 0xf8, 0x80, 0x84, 0x42, 0xe2, 0x94, 0xc4, 0x02, 0x42, 0xb4, 0x69,
-	0xfb, 0x01, 0x3e, 0x58, 0xe7, 0xf3, 0xe6, 0x58, 0xf5, 0xbc, 0xbb, 0xda, 0xdb, 0x2b, 0xf5, 0xc3,
-	0xf0, 0x02, 0x48, 0x3c, 0x03, 0xaf, 0x86, 0xf6, 0x8f, 0xe3, 0x18, 0xea, 0x12, 0xf5, 0xdb, 0xee,
-	0xcc, 0x6f, 0x66, 0x7f, 0xfb, 0x9b, 0x99, 0x5d, 0xf8, 0xec, 0xe5, 0x58, 0xe6, 0xab, 0xac, 0xe0,
-	0xcb, 0x51, 0xc1, 0x25, 0x19, 0xe5, 0x42, 0x8c, 0x24, 0x6f, 0x14, 0x91, 0xa3, 0x82, 0xb3, 0x6b,
-	0x5a, 0x66, 0x42, 0x72, 0xc5, 0xd1, 0xfd, 0x35, 0x4e, 0x92, 0x2c, 0x17, 0x22, 0xb3, 0x98, 0x83,
-	0x87, 0xff, 0x0a, 0x2f, 0xf8, 0x72, 0xc9, 0xd9, 0x88, 0x11, 0x35, 0x12, 0x5c, 0x2a, 0x1b, 0x7c,
-	0xf0, 0x68, 0x37, 0x8a, 0x11, 0xf5, 0x3b, 0x97, 0x2f, 0x2c, 0x30, 0xfd, 0xdb, 0x87, 0xce, 0x29,
-	0x5f, 0xe6, 0x94, 0xa1, 0xaf, 0xa1, 0xa5, 0x56, 0x82, 0x24, 0xde, 0xc0, 0x1b, 0xf6, 0xc7, 0x69,
-	0xf6, 0xda, 0xf3, 0x33, 0x0b, 0xce, 0x9e, 0xae, 0x04, 0xc1, 0x06, 0x8f, 0xde, 0x83, 0xf6, 0xcb,
-	0xbc, 0x6a, 0x48, 0xe2, 0x0f, 0xbc, 0x61, 0x88, 0xed, 0x06, 0x3d, 0x81, 0x30, 0x57, 0x4a, 0xd2,
-	0x79, 0xa3, 0x48, 0x12, 0x0c, 0x82, 0x61, 0x34, 0x7e, 0xf4, 0xe6, 0x94, 0xc7, 0x6b, 0x38, 0xde,
-	0x44, 0x1e, 0x54, 0x10, 0xde, 0xd8, 0x51, 0x0c, 0xc1, 0x0b, 0xb2, 0x32, 0x04, 0x43, 0xac, 0x97,
-	0xe8, 0x01, 0xc0, 0x9c, 0xf3, 0x6a, 0xb6, 0x21, 0xd0, 0x3d, 0xdf, 0xc3, 0xa1, 0xb6, 0x3d, 0x37,
-	0x34, 0x0e, 0x21, 0xa4, 0x4c, 0x39, 0x7f, 0x30, 0xf0, 0x86, 0xc1, 0xf9, 0x1e, 0xee, 0x52, 0xa6,
-	0x8c, 0xfb, 0x64, 0x1f, 0x22, 0x7d, 0x87, 0x85, 0x05, 0xa4, 0x63, 0x68, 0xe9, 0x8b, 0xa1, 0x10,
-	0xda, 0x97, 0x55, 0x4e, 0x59, 0xbc, 0xa7, 0x97, 0x98, 0x94, 0xe4, 0x55, 0xec, 0x21, 0x58, 0x4b,
-	0x15, 0xfb, 0xa8, 0x0b, 0xad, 0xef, 0x9b, 0xaa, 0x8a, 0x83, 0x34, 0x83, 0xd6, 0x64, 0x7a, 0x8a,
-	0x51, 0x1f, 0x7c, 0x2a, 0x0c, 0xb7, 0x1e, 0xf6, 0xa9, 0x40, 0xef, 0x43, 0x47, 0x48, 0x72, 0x4d,
-	0x5f, 0x19, 0x5a, 0xfb, 0xd8, 0xed, 0xd2, 0x5f, 0xa1, 0x7d, 0x46, 0xf8, 0xf4, 0x12, 0x7d, 0x02,
-	0xbd, 0x82, 0x37, 0x4c, 0xc9, 0xd5, 0xac, 0xe0, 0x0b, 0xe2, 0xae, 0x15, 0x39, 0xdb, 0x84, 0x2f,
-	0x08, 0x1a, 0x41, 0xab, 0xa0, 0x0b, 0x99, 0xf8, 0x46, 0xbf, 0x0f, 0x77, 0xe8, 0xa7, 0x8f, 0xc7,
-	0x06, 0x98, 0x1e, 0x41, 0x68, 0x92, 0xff, 0x48, 0x6b, 0x85, 0xc6, 0xd0, 0x26, 0x3a, 0x55, 0xe2,
-	0x99, 0xf0, 0x8f, 0x76, 0x84, 0x9b, 0x00, 0x6c, 0xa1, 0x69, 0x01, 0xf7, 0xce, 0x08, 0xbf, 0xa2,
-	0x8a, 0xdc, 0x85, 0xdf, 0x57, 0xd0, 0x59, 0x18, 0x45, 0x1c, 0xc3, 0xc3, 0x37, 0x56, 0x18, 0x3b,
-	0x70, 0x3a, 0x81, 0xc8, 0x1d, 0x62, 0x78, 0x7e, 0xb9, 0xcd, 0xf3, 0xe3, 0xdd, 0x3c, 0x75, 0xc8,
-	0x9a, 0xe9, 0x1f, 0x6d, 0x88, 0x30, 0x6f, 0x14, 0x65, 0x25, 0x6e, 0x2a, 0x82, 0x10, 0x04, 0x2a,
-	0x2f, 0x2d, 0xcb, 0xf3, 0x3d, 0xac, 0x37, 0xe8, 0x53, 0xd8, 0x9f, 0xe7, 0x55, 0xce, 0x0a, 0xca,
-	0xca, 0x99, 0xf6, 0xf6, 0x9c, 0xb7, 0x77, 0x63, 0x7e, 0x9a, 0x97, 0x6f, 0x79, 0x0d, 0xf4, 0xd8,
-	0x55, 0x27, 0xf8, 0xdf, 0xea, 0x9c, 0xf8, 0x89, 0x67, 0x2b, 0xa4, 0x8b, 0x52, 0x12, 0x4e, 0x45,
-	0x02, 0x77, 0x29, 0x8a, 0x81, 0xa2, 0x23, 0x00, 0x3d, 0xdb, 0x33, 0x99, 0xb3, 0x92, 0x24, 0xad,
-	0x81, 0x37, 0x8c, 0xc6, 0x83, 0xdb, 0x81, 0x76, 0xbc, 0x33, 0x46, 0x54, 0x76, 0xc9, 0xa5, 0xc2,
-	0x1a, 0x87, 0x43, 0xb1, 0x5e, 0xa2, 0x29, 0xf4, 0xdc, 0xd8, 0xcf, 0x2a, 0x5a, 0xab, 0xa4, 0x6d,
-	0x52, 0xa4, 0x3b, 0x52, 0x5c, 0x58, 0xa8, 0xae, 0x8d, 0x21, 0x1e, 0xb1, 0x8d, 0x01, 0x7d, 0x03,
-	0x5d, 0xb7, 0xad, 0x93, 0xfd, 0x41, 0x30, 0xec, 0x6f, 0xd7, 0xeb, 0xbf, 0x69, 0xf0, 0x0d, 0x1e,
-	0x7d, 0x07, 0x51, 0xcd, 0x1b, 0x59, 0x90, 0x99, 0xd1, 0xad, 0x73, 0x37, 0xdd, 0xc0, 0xc6, 0x4c,
-	0xb4, 0x7a, 0x47, 0xd0, 0x73, 0x19, 0xac, 0x88, 0xd1, 0x1d, 0x44, 0x74, 0x67, 0x9e, 0x19, 0x29,
-	0x0f, 0x01, 0x9a, 0x9a, 0xc8, 0x19, 0x59, 0xe6, 0xb4, 0x4a, 0xee, 0x0d, 0x82, 0x61, 0x88, 0x43,
-	0x6d, 0x79, 0xa2, 0x0d, 0xe8, 0x01, 0x44, 0x94, 0xcd, 0x79, 0xc3, 0x16, 0xa6, 0x5d, 0xba, 0xc6,
-	0x0f, 0xce, 0xa4, 0x5b, 0xe5, 0x00, 0xba, 0xe6, 0xe1, 0x2c, 0x78, 0x95, 0x84, 0xc6, 0x7b, 0xb3,
-	0x3f, 0xe9, 0x01, 0xa8, 0x5c, 0x96, 0x44, 0xe9, 0xd8, 0xf4, 0x02, 0xf6, 0x4f, 0xd6, 0x4d, 0x66,
-	0x1a, 0x34, 0xbe, 0xd5, 0xa0, 0xb6, 0x3d, 0x3f, 0x87, 0x77, 0x79, 0xa3, 0xec, 0x71, 0x35, 0xa9,
-	0x48, 0xa1, 0xb8, 0x9d, 0xf5, 0x10, 0xc7, 0x6b, 0xc7, 0x95, 0xb3, 0xa7, 0x7f, 0xf9, 0xd0, 0x99,
-	0x98, 0x0f, 0x02, 0x3d, 0x83, 0x77, 0x6c, 0x0b, 0xce, 0x6a, 0x25, 0x73, 0x45, 0xca, 0x95, 0x7b,
-	0xb4, 0xbf, 0xd8, 0xa5, 0xa5, 0xfd, 0x58, 0x6c, 0xff, 0x5e, 0xb9, 0x18, 0xdc, 0x5f, 0x6c, 0xed,
-	0xf5, 0x07, 0x20, 0x9b, 0x8a, 0xb8, 0x21, 0xd8, 0xf5, 0x01, 0xdc, 0x9a, 0x39, 0x6c, 0xf0, 0xe8,
-	0x07, 0xe8, 0x6f, 0xa6, 0xcc, 0x64, 0xb0, 0x13, 0xf1, 0x70, 0x47, 0x86, 0x2d, 0x59, 0xf0, 0x66,
-	0x42, 0xf5, 0x36, 0x3d, 0x83, 0xfe, 0x36, 0x4d, 0xfd, 0xd4, 0x1e, 0xd7, 0xd3, 0xda, 0xbe, 0xc5,
-	0xcf, 0x6a, 0x32, 0x15, 0xb1, 0x87, 0x62, 0xe8, 0x4d, 0xc5, 0xf4, 0xfa, 0x82, 0xb3, 0x9f, 0x72,
-	0x55, 0xfc, 0x16, 0xfb, 0xa8, 0x0f, 0x30, 0x15, 0x3f, 0xb3, 0x53, 0xb2, 0xcc, 0xd9, 0x22, 0x0e,
-	0x4e, 0xbe, 0x85, 0x0f, 0x0a, 0xbe, 0x7c, 0x3d, 0x85, 0x4b, 0xef, 0x97, 0x8e, 0x5d, 0xfd, 0xe9,
-	0xdf, 0x7f, 0x3e, 0xc6, 0xf9, 0x2a, 0x9b, 0x68, 0xc4, 0xb1, 0x10, 0xe6, 0x7e, 0x44, 0xce, 0x3b,
-	0xa6, 0xac, 0x8f, 0xff, 0x09, 0x00, 0x00, 0xff, 0xff, 0x9b, 0x8e, 0x85, 0x22, 0xaf, 0x07, 0x00,
+	// 897 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0xdd, 0x6e, 0xe3, 0x44,
+	0x14, 0xae, 0xed, 0x24, 0x1b, 0x1f, 0x27, 0xc1, 0x8c, 0x58, 0x64, 0x0a, 0xa5, 0xc1, 0x5a, 0xd8,
+	0x48, 0x20, 0x47, 0xca, 0x02, 0x17, 0x08, 0xb4, 0x34, 0xe9, 0xd2, 0x46, 0x40, 0xa9, 0xa6, 0xbb,
+	0x7b, 0x01, 0x17, 0x91, 0xe3, 0x4c, 0x8d, 0xb5, 0xce, 0xcc, 0x68, 0x3c, 0x5e, 0x36, 0xaf, 0x84,
+	0xc4, 0x33, 0xf0, 0x28, 0xbc, 0x0a, 0x9a, 0x9f, 0x34, 0x0d, 0x34, 0x25, 0xe2, 0x6e, 0xce, 0x39,
+	0xdf, 0x39, 0xf3, 0xcd, 0xf9, 0x1b, 0xf8, 0xe4, 0xf5, 0x48, 0xa4, 0xab, 0x24, 0x63, 0xcb, 0x61,
+	0xc6, 0x04, 0x19, 0xa6, 0x9c, 0x0f, 0x05, 0xab, 0x25, 0x11, 0xc3, 0x8c, 0xd1, 0xeb, 0x22, 0x4f,
+	0xb8, 0x60, 0x92, 0xa1, 0x87, 0x6b, 0x9c, 0x20, 0x49, 0xca, 0x79, 0x62, 0x30, 0x87, 0x8f, 0xfe,
+	0xe1, 0x9e, 0xb1, 0xe5, 0x92, 0xd1, 0x21, 0x25, 0x72, 0xc8, 0x99, 0x90, 0xc6, 0xf9, 0xf0, 0xf1,
+	0x6e, 0x14, 0x25, 0xf2, 0x37, 0x26, 0x5e, 0x19, 0x60, 0xfc, 0xa7, 0x0b, 0xad, 0x53, 0xb6, 0x4c,
+	0x0b, 0x8a, 0xbe, 0x84, 0x86, 0x5c, 0x71, 0x12, 0x39, 0x7d, 0x67, 0xd0, 0x1b, 0xc5, 0xc9, 0x9d,
+	0xf7, 0x27, 0x06, 0x9c, 0x3c, 0x5f, 0x71, 0x82, 0x35, 0x1e, 0xbd, 0x03, 0xcd, 0xd7, 0x69, 0x59,
+	0x93, 0xc8, 0xed, 0x3b, 0x03, 0x1f, 0x1b, 0x01, 0x3d, 0x03, 0x3f, 0x95, 0x52, 0x14, 0xf3, 0x5a,
+	0x92, 0xc8, 0xeb, 0x7b, 0x83, 0x60, 0xf4, 0xf8, 0xfe, 0x90, 0x27, 0x6b, 0x38, 0xde, 0x78, 0x1e,
+	0x96, 0xe0, 0xdf, 0xe8, 0x51, 0x08, 0xde, 0x2b, 0xb2, 0xd2, 0x04, 0x7d, 0xac, 0x8e, 0xe8, 0x18,
+	0x60, 0xce, 0x58, 0x39, 0xdb, 0x10, 0x68, 0x9f, 0x1f, 0x60, 0x5f, 0xe9, 0x5e, 0x6a, 0x1a, 0x47,
+	0xe0, 0x17, 0x54, 0x5a, 0xbb, 0xd7, 0x77, 0x06, 0xde, 0xf9, 0x01, 0x6e, 0x17, 0x54, 0x6a, 0xf3,
+	0xb8, 0x0b, 0x81, 0x7a, 0xc3, 0xc2, 0x00, 0xe2, 0x11, 0x34, 0xd4, 0xc3, 0x90, 0x0f, 0xcd, 0xcb,
+	0x32, 0x2d, 0x68, 0x78, 0xa0, 0x8e, 0x98, 0xe4, 0xe4, 0x4d, 0xe8, 0x20, 0x58, 0xa7, 0x2a, 0x74,
+	0x51, 0x1b, 0x1a, 0xdf, 0xd5, 0x65, 0x19, 0x7a, 0x71, 0x02, 0x8d, 0xc9, 0xf4, 0x14, 0xa3, 0x1e,
+	0xb8, 0x05, 0xd7, 0xdc, 0x3a, 0xd8, 0x2d, 0x38, 0x7a, 0x17, 0x5a, 0x5c, 0x90, 0xeb, 0xe2, 0x8d,
+	0xa6, 0xd5, 0xc5, 0x56, 0x8a, 0x7f, 0x81, 0xe6, 0x19, 0x61, 0xd3, 0x4b, 0xf4, 0x11, 0x74, 0x32,
+	0x56, 0x53, 0x29, 0x56, 0xb3, 0x8c, 0x2d, 0x88, 0x7d, 0x56, 0x60, 0x75, 0x13, 0xb6, 0x20, 0x68,
+	0x08, 0x8d, 0xac, 0x58, 0x88, 0xc8, 0xd5, 0xf9, 0x7b, 0x7f, 0x47, 0xfe, 0xd4, 0xf5, 0x58, 0x03,
+	0xe3, 0xa7, 0xe0, 0xeb, 0xe0, 0x3f, 0x14, 0x95, 0x44, 0x23, 0x68, 0x12, 0x15, 0x2a, 0x72, 0xb4,
+	0xfb, 0x07, 0x3b, 0xdc, 0xb5, 0x03, 0x36, 0xd0, 0x38, 0x83, 0x07, 0x67, 0x84, 0x5d, 0x15, 0x92,
+	0xec, 0xc3, 0xef, 0x0b, 0x68, 0x2d, 0x74, 0x46, 0x2c, 0xc3, 0xa3, 0x7b, 0x2b, 0x8c, 0x2d, 0x38,
+	0x9e, 0x40, 0x60, 0x2f, 0xd1, 0x3c, 0x3f, 0xdf, 0xe6, 0xf9, 0xe1, 0x6e, 0x9e, 0xca, 0x65, 0xcd,
+	0xf4, 0xaf, 0x26, 0x04, 0x98, 0xd5, 0xb2, 0xa0, 0x39, 0xae, 0x4b, 0x82, 0x10, 0x78, 0x32, 0xcd,
+	0x0d, 0xcb, 0xf3, 0x03, 0xac, 0x04, 0xf4, 0x31, 0x74, 0xe7, 0x69, 0x99, 0xd2, 0xac, 0xa0, 0xf9,
+	0x4c, 0x59, 0x3b, 0xd6, 0xda, 0xb9, 0x51, 0x3f, 0x4f, 0xf3, 0xff, 0xf9, 0x0c, 0xf4, 0xc4, 0x56,
+	0xc7, 0xfb, 0xcf, 0xea, 0x8c, 0xdd, 0xc8, 0x31, 0x15, 0x52, 0x45, 0xc9, 0x09, 0x2b, 0x78, 0x04,
+	0xfb, 0x14, 0x45, 0x43, 0xd1, 0x04, 0x40, 0xcd, 0xf6, 0x4c, 0xa4, 0x34, 0x27, 0x51, 0xa3, 0xef,
+	0x0c, 0x82, 0x51, 0xff, 0xb6, 0xa3, 0x19, 0xef, 0x84, 0x12, 0x99, 0x5c, 0x32, 0x21, 0xb1, 0xc2,
+	0xe9, 0x3b, 0x7d, 0xbe, 0x16, 0xd1, 0xd7, 0xa0, 0x85, 0x59, 0x59, 0x54, 0x32, 0xea, 0xe9, 0x18,
+	0xc7, 0xf7, 0xc4, 0x50, 0x95, 0xc1, 0x6d, 0x6e, 0x4f, 0x68, 0x0a, 0x1d, 0xbb, 0x38, 0x4c, 0x80,
+	0xa6, 0x0e, 0x10, 0xef, 0x08, 0x70, 0x61, 0xa0, 0xca, 0x53, 0xd3, 0x08, 0xe8, 0x46, 0x81, 0xbe,
+	0x82, 0xb6, 0x15, 0xab, 0xa8, 0xdb, 0xf7, 0x06, 0xbd, 0xed, 0x8a, 0xff, 0x3b, 0x0c, 0xbe, 0xc1,
+	0xa3, 0x6f, 0x21, 0xa8, 0x58, 0x2d, 0x32, 0x32, 0xd3, 0x99, 0x6f, 0xed, 0x97, 0x79, 0x30, 0x3e,
+	0x13, 0x95, 0xff, 0xa7, 0xd0, 0xb1, 0x11, 0x4c, 0x19, 0x82, 0x3d, 0xca, 0x60, 0xef, 0x3c, 0xd3,
+	0xc5, 0x38, 0x02, 0xa8, 0x2b, 0x22, 0x66, 0x64, 0x99, 0x16, 0x65, 0xf4, 0xa0, 0xef, 0x0d, 0x7c,
+	0xec, 0x2b, 0xcd, 0x33, 0xa5, 0x40, 0xc7, 0x10, 0x14, 0x74, 0xce, 0x6a, 0xba, 0xd0, 0x0d, 0xd7,
+	0xd6, 0x76, 0xb0, 0x2a, 0xd5, 0x6c, 0x87, 0xd0, 0xd6, 0xab, 0x37, 0x63, 0x65, 0xe4, 0x6b, 0xeb,
+	0x8d, 0x3c, 0xee, 0x00, 0xc8, 0x54, 0xe4, 0x44, 0x2a, 0xdf, 0xf8, 0x02, 0xba, 0xe3, 0x75, 0x9b,
+	0xea, 0x16, 0x0f, 0x6f, 0xb5, 0xb8, 0x69, 0xf0, 0x4f, 0xe1, 0x6d, 0x56, 0x4b, 0x73, 0x5d, 0x45,
+	0x4a, 0x92, 0x49, 0x66, 0xb6, 0x85, 0x8f, 0xc3, 0xb5, 0xe1, 0xca, 0xea, 0xe3, 0x3f, 0x5c, 0x68,
+	0x4d, 0xf4, 0x17, 0x83, 0x5e, 0xc0, 0x5b, 0xa6, 0x89, 0x67, 0x95, 0x14, 0xa9, 0x24, 0xf9, 0xca,
+	0xae, 0xfd, 0xcf, 0x76, 0xe5, 0xd2, 0x7c, 0x4d, 0x66, 0x02, 0xae, 0xac, 0x0f, 0xee, 0x2d, 0xb6,
+	0x64, 0xf5, 0x85, 0x88, 0xba, 0x24, 0x76, 0x8c, 0x76, 0x7d, 0x21, 0xb7, 0xa6, 0x16, 0x6b, 0x3c,
+	0xfa, 0x1e, 0x7a, 0x9b, 0x39, 0xd5, 0x11, 0xcc, 0x4c, 0x3d, 0xda, 0x11, 0x61, 0x2b, 0x2d, 0x78,
+	0x33, 0xe3, 0x4a, 0x8c, 0xcf, 0xa0, 0xb7, 0x4d, 0x53, 0x2d, 0xeb, 0x93, 0x6a, 0x5a, 0x99, 0x6d,
+	0xfe, 0xa2, 0x22, 0x53, 0x1e, 0x3a, 0x28, 0x84, 0xce, 0x94, 0x4f, 0xaf, 0x2f, 0x18, 0xfd, 0x31,
+	0x95, 0xd9, 0xaf, 0xa1, 0x8b, 0x7a, 0x00, 0x53, 0xfe, 0x13, 0x3d, 0x25, 0xcb, 0x94, 0x2e, 0x42,
+	0x6f, 0xfc, 0x0d, 0xbc, 0x97, 0xb1, 0xe5, 0xdd, 0x14, 0x2e, 0x9d, 0x9f, 0x5b, 0xe6, 0xf4, 0xbb,
+	0xfb, 0xf0, 0xe5, 0x08, 0xa7, 0xab, 0x64, 0xa2, 0x10, 0x27, 0x9c, 0xeb, 0xf7, 0x11, 0x31, 0x6f,
+	0xe9, 0xb2, 0x3e, 0xf9, 0x3b, 0x00, 0x00, 0xff, 0xff, 0xb2, 0x28, 0x0d, 0x41, 0xf1, 0x07, 0x00,
 	0x00,
 }

+ 7 - 2
app/router/config.proto

@@ -83,7 +83,7 @@ message RoutingRule {
   repeated Domain domain = 2;
 
   // List of CIDRs for target IP address matching.
-  // The list must be sorted beforehand.
+  // Deprecated. Use geoip below.
   repeated CIDR cidr = 3 [deprecated = true];
   
   // List of GeoIPs for target IP address matching. If this entry exists, the cidr above will have no effect.
@@ -91,7 +91,12 @@ message RoutingRule {
   // For customized GeoIPs, please leave country code empty.
   repeated GeoIP geoip = 10;
 
-  v2ray.core.common.net.PortRange port_range = 4;
+  // A range of port [from, to]. If the destination port is in this range, this rule takes effect.
+  // Deprecated. Use port_list.
+  v2ray.core.common.net.PortRange port_range = 4 [deprecated = true];
+
+  // List of ports.
+  v2ray.core.common.net.PortList port_list = 14;
 
   // List of networks. Deprecated. Use networks.
   v2ray.core.common.net.NetworkList network_list = 5 [deprecated = true];

+ 28 - 0
common/net/port.go

@@ -65,3 +65,31 @@ func SinglePortRange(p Port) *PortRange {
 		To:   uint32(p),
 	}
 }
+
+type MemoryPortRange struct {
+	From Port
+	To   Port
+}
+
+func (r MemoryPortRange) Contains(port Port) bool {
+	return r.From <= port && port <= r.To
+}
+
+type MemoryPortList []MemoryPortRange
+
+func PortListFromProto(l *PortList) MemoryPortList {
+	mpl := make(MemoryPortList, 0, len(l.Range))
+	for _, r := range l.Range {
+		mpl = append(mpl, MemoryPortRange{From: Port(r.From), To: Port(r.To)})
+	}
+	return mpl
+}
+
+func (mpl MemoryPortList) Contains(port Port) bool {
+	for _, pr := range mpl {
+		if pr.Contains(port) {
+			return true
+		}
+	}
+	return false
+}

+ 48 - 5
common/net/port.pb.go

@@ -67,8 +67,49 @@ func (m *PortRange) GetTo() uint32 {
 	return 0
 }
 
+// PortList is a list of ports.
+type PortList struct {
+	Range                []*PortRange `protobuf:"bytes,1,rep,name=range,proto3" json:"range,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
+	XXX_unrecognized     []byte       `json:"-"`
+	XXX_sizecache        int32        `json:"-"`
+}
+
+func (m *PortList) Reset()         { *m = PortList{} }
+func (m *PortList) String() string { return proto.CompactTextString(m) }
+func (*PortList) ProtoMessage()    {}
+func (*PortList) Descriptor() ([]byte, []int) {
+	return fileDescriptor_166067e37a39f913, []int{1}
+}
+
+func (m *PortList) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_PortList.Unmarshal(m, b)
+}
+func (m *PortList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_PortList.Marshal(b, m, deterministic)
+}
+func (m *PortList) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_PortList.Merge(m, src)
+}
+func (m *PortList) XXX_Size() int {
+	return xxx_messageInfo_PortList.Size(m)
+}
+func (m *PortList) XXX_DiscardUnknown() {
+	xxx_messageInfo_PortList.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PortList proto.InternalMessageInfo
+
+func (m *PortList) GetRange() []*PortRange {
+	if m != nil {
+		return m.Range
+	}
+	return nil
+}
+
 func init() {
 	proto.RegisterType((*PortRange)(nil), "v2ray.core.common.net.PortRange")
+	proto.RegisterType((*PortList)(nil), "v2ray.core.common.net.PortList")
 }
 
 func init() {
@@ -76,15 +117,17 @@ func init() {
 }
 
 var fileDescriptor_166067e37a39f913 = []byte{
-	// 158 bytes of a gzipped FileDescriptorProto
+	// 192 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x29, 0x33, 0x2a, 0x4a,
 	0xac, 0xd4, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0xce, 0xcf, 0xcd, 0xcd,
 	0xcf, 0xd3, 0xcf, 0x4b, 0x2d, 0xd1, 0x2f, 0xc8, 0x2f, 0x2a, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9,
 	0x17, 0x12, 0x85, 0xa9, 0x2a, 0x4a, 0xd5, 0x83, 0xa8, 0xd0, 0xcb, 0x4b, 0x2d, 0x51, 0xd2, 0xe7,
 	0xe2, 0x0c, 0xc8, 0x2f, 0x2a, 0x09, 0x4a, 0xcc, 0x4b, 0x4f, 0x15, 0x12, 0xe2, 0x62, 0x71, 0x2b,
 	0xca, 0xcf, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0d, 0x02, 0xb3, 0x85, 0xf8, 0xb8, 0x98, 0x42,
-	0xf2, 0x25, 0x98, 0xc0, 0x22, 0x4c, 0x21, 0xf9, 0x4e, 0x56, 0x5c, 0x92, 0xc9, 0xf9, 0xb9, 0x7a,
-	0x58, 0x4d, 0x0b, 0x60, 0x8c, 0x62, 0xce, 0x4b, 0x2d, 0x59, 0xc5, 0x24, 0x1a, 0x66, 0x14, 0x94,
-	0x58, 0xa9, 0xe7, 0x0c, 0x92, 0x76, 0x86, 0x48, 0xfb, 0xa5, 0x96, 0x24, 0xb1, 0x81, 0x9d, 0x62,
-	0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x1a, 0x0f, 0x6a, 0xc0, 0xb2, 0x00, 0x00, 0x00,
+	0xf2, 0x25, 0x98, 0xc0, 0x22, 0x4c, 0x21, 0xf9, 0x4a, 0x4e, 0x5c, 0x1c, 0x20, 0x0d, 0x3e, 0x99,
+	0xc5, 0x25, 0x42, 0x66, 0x5c, 0xac, 0x45, 0x20, 0x8d, 0x12, 0x8c, 0x0a, 0xcc, 0x1a, 0xdc, 0x46,
+	0x0a, 0x7a, 0x58, 0xed, 0xd0, 0x83, 0x5b, 0x10, 0x04, 0x51, 0xee, 0x64, 0xc5, 0x25, 0x99, 0x9c,
+	0x9f, 0x8b, 0x5d, 0x75, 0x00, 0x63, 0x14, 0x73, 0x5e, 0x6a, 0xc9, 0x2a, 0x26, 0xd1, 0x30, 0xa3,
+	0xa0, 0xc4, 0x4a, 0x3d, 0x67, 0x90, 0xb4, 0x33, 0x44, 0xda, 0x2f, 0xb5, 0x24, 0x89, 0x0d, 0xec,
+	0x1d, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xba, 0xd0, 0x7b, 0xfa, 0xf6, 0x00, 0x00, 0x00,
 }

+ 5 - 0
common/net/port.proto

@@ -13,3 +13,8 @@ message PortRange {
   // The port that this range ends with (inclusive).
   uint32 To = 2;
 }
+
+// PortList is a list of ports.
+message PortList {
+  repeated PortRange range = 1;
+}

+ 52 - 8
infra/conf/common.go

@@ -111,12 +111,7 @@ func parseIntPort(data []byte) (net.Port, error) {
 	return net.PortFromInt(intPort)
 }
 
-func parseStringPort(data []byte) (net.Port, net.Port, error) {
-	var s string
-	err := json.Unmarshal(data, &s)
-	if err != nil {
-		return net.Port(0), net.Port(0), err
-	}
+func parseStringPort(s string) (net.Port, net.Port, error) {
 	if strings.HasPrefix(s, "env:") {
 		s = s[4:]
 		s = os.Getenv(s)
@@ -124,7 +119,7 @@ func parseStringPort(data []byte) (net.Port, net.Port, error) {
 
 	pair := strings.SplitN(s, "-", 2)
 	if len(pair) == 0 {
-		return net.Port(0), net.Port(0), newError("Config: Invalid port range: ", s)
+		return net.Port(0), net.Port(0), newError("invalid port range: ", s)
 	}
 	if len(pair) == 1 {
 		port, err := net.PortFromString(pair[0])
@@ -142,6 +137,15 @@ func parseStringPort(data []byte) (net.Port, net.Port, error) {
 	return fromPort, toPort, nil
 }
 
+func parseJSONStringPort(data []byte) (net.Port, net.Port, error) {
+	var s string
+	err := json.Unmarshal(data, &s)
+	if err != nil {
+		return net.Port(0), net.Port(0), err
+	}
+	return parseStringPort(s)
+}
+
 type PortRange struct {
 	From uint32
 	To   uint32
@@ -163,7 +167,7 @@ func (v *PortRange) UnmarshalJSON(data []byte) error {
 		return nil
 	}
 
-	from, to, err := parseStringPort(data)
+	from, to, err := parseJSONStringPort(data)
 	if err == nil {
 		v.From = uint32(from)
 		v.To = uint32(to)
@@ -176,6 +180,46 @@ func (v *PortRange) UnmarshalJSON(data []byte) error {
 	return newError("invalid port range: ", string(data))
 }
 
+type PortList struct {
+	Range []PortRange
+}
+
+func (list *PortList) Build() *net.PortList {
+	portList := new(net.PortList)
+	for _, r := range list.Range {
+		portList.Range = append(portList.Range, r.Build())
+	}
+	return portList
+}
+
+// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
+func (list *PortList) UnmarshalJSON(data []byte) error {
+	var listStr string
+	if err := json.Unmarshal(data, &listStr); err != nil {
+		return newError("invalid port list: ", string(data)).Base(err)
+	}
+	rangelist := strings.Split(listStr, ",")
+	for _, rangeStr := range rangelist {
+		trimmed := strings.TrimSpace(rangeStr)
+		if len(trimmed) > 0 {
+			if strings.Contains(trimmed, "-") {
+				from, to, err := parseStringPort(trimmed)
+				if err != nil {
+					return newError("invalid port range: ", trimmed).Base(err)
+				}
+				list.Range = append(list.Range, PortRange{From: uint32(from), To: uint32(to)})
+			} else {
+				port, err := parseIntPort([]byte(trimmed))
+				if err != nil {
+					return newError("invalid port: ", trimmed).Base(err)
+				}
+				list.Range = append(list.Range, PortRange{From: uint32(port), To: uint32(port)})
+			}
+		}
+	}
+	return nil
+}
+
 type User struct {
 	EmailString string `json:"email"`
 	LevelByte   byte   `json:"level"`

+ 2 - 2
infra/conf/router.go

@@ -354,7 +354,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
 		RouterRule
 		Domain     *StringList  `json:"domain"`
 		IP         *StringList  `json:"ip"`
-		Port       *PortRange   `json:"port"`
+		Port       *PortList    `json:"port"`
 		Network    *NetworkList `json:"network"`
 		SourceIP   *StringList  `json:"source"`
 		User       *StringList  `json:"user"`
@@ -399,7 +399,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
 	}
 
 	if rawFieldRule.Port != nil {
-		rule.PortRange = rawFieldRule.Port.Build()
+		rule.PortList = rawFieldRule.Port.Build()
 	}
 
 	if rawFieldRule.Network != nil {

+ 17 - 0
infra/conf/router_test.go

@@ -7,6 +7,7 @@ import (
 	"github.com/golang/protobuf/proto"
 
 	"v2ray.com/core/app/router"
+	"v2ray.com/core/common/net"
 	. "v2ray.com/core/infra/conf"
 )
 
@@ -43,6 +44,10 @@ func TestRouterConfig(t *testing.T) {
 								"::1/128"
 							],
 							"outboundTag": "test"
+						},{
+							"type": "field",
+							"port": "53, 443, 1000-2000",
+							"outboundTag": "test"
 						}
 					]
 				},
@@ -97,6 +102,18 @@ func TestRouterConfig(t *testing.T) {
 							Tag: "test",
 						},
 					},
+					{
+						PortList: &net.PortList{
+							Range: []*net.PortRange{
+								{From: 53, To: 53},
+								{From: 443, To: 443},
+								{From: 1000, To: 2000},
+							},
+						},
+						TargetTag: &router.RoutingRule_Tag{
+							Tag: "test",
+						},
+					},
 				},
 			},
 		},