github.com/v2fly/v2ray-core/v4@v4.45.2/app/router/command/command_test.go (about) 1 package command_test 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 "github.com/golang/mock/gomock" 9 "github.com/google/go-cmp/cmp" 10 "github.com/google/go-cmp/cmp/cmpopts" 11 "google.golang.org/grpc" 12 "google.golang.org/grpc/test/bufconn" 13 14 "github.com/v2fly/v2ray-core/v4/app/router" 15 . "github.com/v2fly/v2ray-core/v4/app/router/command" 16 "github.com/v2fly/v2ray-core/v4/app/stats" 17 "github.com/v2fly/v2ray-core/v4/common" 18 "github.com/v2fly/v2ray-core/v4/common/net" 19 "github.com/v2fly/v2ray-core/v4/features/routing" 20 "github.com/v2fly/v2ray-core/v4/testing/mocks" 21 ) 22 23 func TestServiceSubscribeRoutingStats(t *testing.T) { 24 c := stats.NewChannel(&stats.ChannelConfig{ 25 SubscriberLimit: 1, 26 BufferSize: 0, 27 Blocking: true, 28 }) 29 common.Must(c.Start()) 30 defer c.Close() 31 32 lis := bufconn.Listen(1024 * 1024) 33 bufDialer := func(context.Context, string) (net.Conn, error) { 34 return lis.Dial() 35 } 36 37 testCases := []*RoutingContext{ 38 {InboundTag: "in", OutboundTag: "out"}, 39 {TargetIPs: [][]byte{{1, 2, 3, 4}}, TargetPort: 8080, OutboundTag: "out"}, 40 {TargetDomain: "example.com", TargetPort: 443, OutboundTag: "out"}, 41 {SourcePort: 9999, TargetPort: 9999, OutboundTag: "out"}, 42 {Network: net.Network_UDP, OutboundGroupTags: []string{"outergroup", "innergroup"}, OutboundTag: "out"}, 43 {Protocol: "bittorrent", OutboundTag: "blocked"}, 44 {User: "example@v2fly.org", OutboundTag: "out"}, 45 {SourceIPs: [][]byte{{127, 0, 0, 1}}, Attributes: map[string]string{"attr": "value"}, OutboundTag: "out"}, 46 } 47 errCh := make(chan error) 48 nextPub := make(chan struct{}) 49 50 // Server goroutine 51 go func() { 52 server := grpc.NewServer() 53 RegisterRoutingServiceServer(server, NewRoutingServer(nil, c)) 54 errCh <- server.Serve(lis) 55 }() 56 57 // Publisher goroutine 58 go func() { 59 publishTestCases := func() error { 60 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 61 defer cancel() 62 for { // Wait until there's one subscriber in routing stats channel 63 if len(c.Subscribers()) > 0 { 64 break 65 } 66 if ctx.Err() != nil { 67 return ctx.Err() 68 } 69 } 70 for _, tc := range testCases { 71 c.Publish(context.Background(), AsRoutingRoute(tc)) 72 time.Sleep(time.Millisecond) 73 } 74 return nil 75 } 76 77 if err := publishTestCases(); err != nil { 78 errCh <- err 79 } 80 81 // Wait for next round of publishing 82 <-nextPub 83 84 if err := publishTestCases(); err != nil { 85 errCh <- err 86 } 87 }() 88 89 // Client goroutine 90 go func() { 91 defer lis.Close() 92 conn, err := grpc.DialContext(context.Background(), "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure()) 93 if err != nil { 94 errCh <- err 95 return 96 } 97 defer conn.Close() 98 client := NewRoutingServiceClient(conn) 99 100 // Test retrieving all fields 101 testRetrievingAllFields := func() error { 102 streamCtx, streamClose := context.WithCancel(context.Background()) 103 104 // Test the unsubscription of stream works well 105 defer func() { 106 streamClose() 107 timeOutCtx, timeout := context.WithTimeout(context.Background(), time.Second) 108 defer timeout() 109 for { // Wait until there's no subscriber in routing stats channel 110 if len(c.Subscribers()) == 0 { 111 break 112 } 113 if timeOutCtx.Err() != nil { 114 t.Error("unexpected subscribers not decreased in channel", timeOutCtx.Err()) 115 } 116 } 117 }() 118 119 stream, err := client.SubscribeRoutingStats(streamCtx, &SubscribeRoutingStatsRequest{}) 120 if err != nil { 121 return err 122 } 123 124 for _, tc := range testCases { 125 msg, err := stream.Recv() 126 if err != nil { 127 return err 128 } 129 if r := cmp.Diff(msg, tc, cmpopts.IgnoreUnexported(RoutingContext{})); r != "" { 130 t.Error(r) 131 } 132 } 133 134 // Test that double subscription will fail 135 errStream, err := client.SubscribeRoutingStats(context.Background(), &SubscribeRoutingStatsRequest{ 136 FieldSelectors: []string{"ip", "port", "domain", "outbound"}, 137 }) 138 if err != nil { 139 return err 140 } 141 if _, err := errStream.Recv(); err == nil { 142 t.Error("unexpected successful subscription") 143 } 144 145 return nil 146 } 147 148 // Test retrieving only a subset of fields 149 testRetrievingSubsetOfFields := func() error { 150 streamCtx, streamClose := context.WithCancel(context.Background()) 151 defer streamClose() 152 stream, err := client.SubscribeRoutingStats(streamCtx, &SubscribeRoutingStatsRequest{ 153 FieldSelectors: []string{"ip", "port", "domain", "outbound"}, 154 }) 155 if err != nil { 156 return err 157 } 158 159 // Send nextPub signal to start next round of publishing 160 close(nextPub) 161 162 for _, tc := range testCases { 163 msg, err := stream.Recv() 164 if err != nil { 165 return err 166 } 167 stat := &RoutingContext{ // Only a subset of stats is retrieved 168 SourceIPs: tc.SourceIPs, 169 TargetIPs: tc.TargetIPs, 170 SourcePort: tc.SourcePort, 171 TargetPort: tc.TargetPort, 172 TargetDomain: tc.TargetDomain, 173 OutboundGroupTags: tc.OutboundGroupTags, 174 OutboundTag: tc.OutboundTag, 175 } 176 if r := cmp.Diff(msg, stat, cmpopts.IgnoreUnexported(RoutingContext{})); r != "" { 177 t.Error(r) 178 } 179 } 180 181 return nil 182 } 183 184 if err := testRetrievingAllFields(); err != nil { 185 errCh <- err 186 } 187 if err := testRetrievingSubsetOfFields(); err != nil { 188 errCh <- err 189 } 190 errCh <- nil // Client passed all tests successfully 191 }() 192 193 // Wait for goroutines to complete 194 select { 195 case <-time.After(2 * time.Second): 196 t.Fatal("Test timeout after 2s") 197 case err := <-errCh: 198 if err != nil { 199 t.Fatal(err) 200 } 201 } 202 } 203 204 func TestSerivceTestRoute(t *testing.T) { 205 c := stats.NewChannel(&stats.ChannelConfig{ 206 SubscriberLimit: 1, 207 BufferSize: 16, 208 Blocking: true, 209 }) 210 common.Must(c.Start()) 211 defer c.Close() 212 213 r := new(router.Router) 214 mockCtl := gomock.NewController(t) 215 defer mockCtl.Finish() 216 common.Must(r.Init(context.TODO(), &router.Config{ 217 Rule: []*router.RoutingRule{ 218 { 219 InboundTag: []string{"in"}, 220 TargetTag: &router.RoutingRule_Tag{Tag: "out"}, 221 }, 222 { 223 Protocol: []string{"bittorrent"}, 224 TargetTag: &router.RoutingRule_Tag{Tag: "blocked"}, 225 }, 226 { 227 PortList: &net.PortList{Range: []*net.PortRange{{From: 8080, To: 8080}}}, 228 TargetTag: &router.RoutingRule_Tag{Tag: "out"}, 229 }, 230 { 231 SourcePortList: &net.PortList{Range: []*net.PortRange{{From: 9999, To: 9999}}}, 232 TargetTag: &router.RoutingRule_Tag{Tag: "out"}, 233 }, 234 { 235 Domain: []*router.Domain{{Type: router.Domain_Domain, Value: "com"}}, 236 TargetTag: &router.RoutingRule_Tag{Tag: "out"}, 237 }, 238 { 239 SourceGeoip: []*router.GeoIP{{CountryCode: "private", Cidr: []*router.CIDR{{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}}, 240 TargetTag: &router.RoutingRule_Tag{Tag: "out"}, 241 }, 242 { 243 UserEmail: []string{"example@v2fly.org"}, 244 TargetTag: &router.RoutingRule_Tag{Tag: "out"}, 245 }, 246 { 247 Networks: []net.Network{net.Network_UDP, net.Network_TCP}, 248 TargetTag: &router.RoutingRule_Tag{Tag: "out"}, 249 }, 250 }, 251 }, mocks.NewDNSClient(mockCtl), mocks.NewOutboundManager(mockCtl))) 252 253 lis := bufconn.Listen(1024 * 1024) 254 bufDialer := func(context.Context, string) (net.Conn, error) { 255 return lis.Dial() 256 } 257 258 errCh := make(chan error) 259 260 // Server goroutine 261 go func() { 262 server := grpc.NewServer() 263 RegisterRoutingServiceServer(server, NewRoutingServer(r, c)) 264 errCh <- server.Serve(lis) 265 }() 266 267 // Client goroutine 268 go func() { 269 defer lis.Close() 270 conn, err := grpc.DialContext(context.Background(), "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure()) 271 if err != nil { 272 errCh <- err 273 } 274 defer conn.Close() 275 client := NewRoutingServiceClient(conn) 276 277 testCases := []*RoutingContext{ 278 {InboundTag: "in", OutboundTag: "out"}, 279 {TargetIPs: [][]byte{{1, 2, 3, 4}}, TargetPort: 8080, OutboundTag: "out"}, 280 {TargetDomain: "example.com", TargetPort: 443, OutboundTag: "out"}, 281 {SourcePort: 9999, TargetPort: 9999, OutboundTag: "out"}, 282 {Network: net.Network_UDP, Protocol: "bittorrent", OutboundTag: "blocked"}, 283 {User: "example@v2fly.org", OutboundTag: "out"}, 284 {SourceIPs: [][]byte{{127, 0, 0, 1}}, Attributes: map[string]string{"attr": "value"}, OutboundTag: "out"}, 285 } 286 287 // Test simple TestRoute 288 testSimple := func() error { 289 for _, tc := range testCases { 290 route, err := client.TestRoute(context.Background(), &TestRouteRequest{RoutingContext: tc}) 291 if err != nil { 292 return err 293 } 294 if r := cmp.Diff(route, tc, cmpopts.IgnoreUnexported(RoutingContext{})); r != "" { 295 t.Error(r) 296 } 297 } 298 return nil 299 } 300 301 // Test TestRoute with special options 302 testOptions := func() error { 303 sub, err := c.Subscribe() 304 if err != nil { 305 return err 306 } 307 for _, tc := range testCases { 308 route, err := client.TestRoute(context.Background(), &TestRouteRequest{ 309 RoutingContext: tc, 310 FieldSelectors: []string{"ip", "port", "domain", "outbound"}, 311 PublishResult: true, 312 }) 313 if err != nil { 314 return err 315 } 316 stat := &RoutingContext{ // Only a subset of stats is retrieved 317 SourceIPs: tc.SourceIPs, 318 TargetIPs: tc.TargetIPs, 319 SourcePort: tc.SourcePort, 320 TargetPort: tc.TargetPort, 321 TargetDomain: tc.TargetDomain, 322 OutboundGroupTags: tc.OutboundGroupTags, 323 OutboundTag: tc.OutboundTag, 324 } 325 if r := cmp.Diff(route, stat, cmpopts.IgnoreUnexported(RoutingContext{})); r != "" { 326 t.Error(r) 327 } 328 select { // Check that routing result has been published to statistics channel 329 case msg, received := <-sub: 330 if route, ok := msg.(routing.Route); received && ok { 331 if r := cmp.Diff(AsProtobufMessage(nil)(route), tc, cmpopts.IgnoreUnexported(RoutingContext{})); r != "" { 332 t.Error(r) 333 } 334 } else { 335 t.Error("unexpected failure in receiving published routing result for testcase", tc) 336 } 337 case <-time.After(100 * time.Millisecond): 338 t.Error("unexpected failure in receiving published routing result", tc) 339 } 340 } 341 return nil 342 } 343 344 if err := testSimple(); err != nil { 345 errCh <- err 346 } 347 if err := testOptions(); err != nil { 348 errCh <- err 349 } 350 errCh <- nil // Client passed all tests successfully 351 }() 352 353 // Wait for goroutines to complete 354 select { 355 case <-time.After(2 * time.Second): 356 t.Fatal("Test timeout after 2s") 357 case err := <-errCh: 358 if err != nil { 359 t.Fatal(err) 360 } 361 } 362 }