1/*
2 *
3 * Copyright 2020 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19package xdsclient
20
21import (
22	"fmt"
23	"strings"
24	"testing"
25	"time"
26
27	v1typepb "github.com/cncf/udpa/go/udpa/type/v1"
28	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
29	"github.com/golang/protobuf/proto"
30	spb "github.com/golang/protobuf/ptypes/struct"
31	"github.com/google/go-cmp/cmp"
32	"github.com/google/go-cmp/cmp/cmpopts"
33	"google.golang.org/protobuf/types/known/durationpb"
34
35	"google.golang.org/grpc/internal/testutils"
36	"google.golang.org/grpc/internal/xds/env"
37	"google.golang.org/grpc/xds/internal/httpfilter"
38	_ "google.golang.org/grpc/xds/internal/httpfilter/router"
39	"google.golang.org/grpc/xds/internal/testutils/e2e"
40	"google.golang.org/grpc/xds/internal/version"
41
42	v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
43	v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
44	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
45	v2httppb "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2"
46	v2listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v2"
47	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
48	v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
49	v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
50	anypb "github.com/golang/protobuf/ptypes/any"
51	wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
52)
53
54func (s) TestUnmarshalListener_ClientSide(t *testing.T) {
55	const (
56		v2LDSTarget       = "lds.target.good:2222"
57		v3LDSTarget       = "lds.target.good:3333"
58		v2RouteConfigName = "v2RouteConfig"
59		v3RouteConfigName = "v3RouteConfig"
60		routeName         = "routeName"
61		testVersion       = "test-version-lds-client"
62	)
63
64	var (
65		v2Lis = testutils.MarshalAny(&v2xdspb.Listener{
66			Name: v2LDSTarget,
67			ApiListener: &v2listenerpb.ApiListener{
68				ApiListener: testutils.MarshalAny(&v2httppb.HttpConnectionManager{
69					RouteSpecifier: &v2httppb.HttpConnectionManager_Rds{
70						Rds: &v2httppb.Rds{
71							ConfigSource: &v2corepb.ConfigSource{
72								ConfigSourceSpecifier: &v2corepb.ConfigSource_Ads{Ads: &v2corepb.AggregatedConfigSource{}},
73							},
74							RouteConfigName: v2RouteConfigName,
75						},
76					},
77				}),
78			},
79		})
80		customFilter = &v3httppb.HttpFilter{
81			Name:       "customFilter",
82			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: customFilterConfig},
83		}
84		typedStructFilter = &v3httppb.HttpFilter{
85			Name:       "customFilter",
86			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: wrappedCustomFilterTypedStructConfig},
87		}
88		customOptionalFilter = &v3httppb.HttpFilter{
89			Name:       "customFilter",
90			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: customFilterConfig},
91			IsOptional: true,
92		}
93		customFilter2 = &v3httppb.HttpFilter{
94			Name:       "customFilter2",
95			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: customFilterConfig},
96		}
97		errFilter = &v3httppb.HttpFilter{
98			Name:       "errFilter",
99			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: errFilterConfig},
100		}
101		errOptionalFilter = &v3httppb.HttpFilter{
102			Name:       "errFilter",
103			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: errFilterConfig},
104			IsOptional: true,
105		}
106		clientOnlyCustomFilter = &v3httppb.HttpFilter{
107			Name:       "clientOnlyCustomFilter",
108			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: clientOnlyCustomFilterConfig},
109		}
110		serverOnlyCustomFilter = &v3httppb.HttpFilter{
111			Name:       "serverOnlyCustomFilter",
112			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: serverOnlyCustomFilterConfig},
113		}
114		serverOnlyOptionalCustomFilter = &v3httppb.HttpFilter{
115			Name:       "serverOnlyOptionalCustomFilter",
116			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: serverOnlyCustomFilterConfig},
117			IsOptional: true,
118		}
119		unknownFilter = &v3httppb.HttpFilter{
120			Name:       "unknownFilter",
121			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: unknownFilterConfig},
122		}
123		unknownOptionalFilter = &v3httppb.HttpFilter{
124			Name:       "unknownFilter",
125			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: unknownFilterConfig},
126			IsOptional: true,
127		}
128		v3LisWithInlineRoute = testutils.MarshalAny(&v3listenerpb.Listener{
129			Name: v3LDSTarget,
130			ApiListener: &v3listenerpb.ApiListener{
131				ApiListener: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
132					RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
133						RouteConfig: &v3routepb.RouteConfiguration{
134							Name: routeName,
135							VirtualHosts: []*v3routepb.VirtualHost{{
136								Domains: []string{v3LDSTarget},
137								Routes: []*v3routepb.Route{{
138									Match: &v3routepb.RouteMatch{
139										PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
140									},
141									Action: &v3routepb.Route_Route{
142										Route: &v3routepb.RouteAction{
143											ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName},
144										}}}}}}},
145					},
146					HttpFilters: []*v3httppb.HttpFilter{emptyRouterFilter},
147					CommonHttpProtocolOptions: &v3corepb.HttpProtocolOptions{
148						MaxStreamDuration: durationpb.New(time.Second),
149					},
150				}),
151			},
152		})
153		v3LisWithFilters = func(fs ...*v3httppb.HttpFilter) *anypb.Any {
154			fs = append(fs, emptyRouterFilter)
155			return testutils.MarshalAny(&v3listenerpb.Listener{
156				Name: v3LDSTarget,
157				ApiListener: &v3listenerpb.ApiListener{
158					ApiListener: testutils.MarshalAny(
159						&v3httppb.HttpConnectionManager{
160							RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
161								Rds: &v3httppb.Rds{
162									ConfigSource: &v3corepb.ConfigSource{
163										ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}},
164									},
165									RouteConfigName: v3RouteConfigName,
166								},
167							},
168							CommonHttpProtocolOptions: &v3corepb.HttpProtocolOptions{
169								MaxStreamDuration: durationpb.New(time.Second),
170							},
171							HttpFilters: fs,
172						}),
173				},
174			})
175		}
176		errMD = UpdateMetadata{
177			Status:  ServiceStatusNACKed,
178			Version: testVersion,
179			ErrState: &UpdateErrorMetadata{
180				Version: testVersion,
181				Err:     cmpopts.AnyError,
182			},
183		}
184	)
185
186	tests := []struct {
187		name       string
188		resources  []*anypb.Any
189		wantUpdate map[string]ListenerUpdateErrTuple
190		wantMD     UpdateMetadata
191		wantErr    bool
192	}{
193		{
194			name:      "non-listener resource",
195			resources: []*anypb.Any{{TypeUrl: version.V3HTTPConnManagerURL}},
196			wantMD:    errMD,
197			wantErr:   true,
198		},
199		{
200			name: "badly marshaled listener resource",
201			resources: []*anypb.Any{
202				{
203					TypeUrl: version.V3ListenerURL,
204					Value: func() []byte {
205						lis := &v3listenerpb.Listener{
206							Name: v3LDSTarget,
207							ApiListener: &v3listenerpb.ApiListener{
208								ApiListener: &anypb.Any{
209									TypeUrl: version.V3HTTPConnManagerURL,
210									Value:   []byte{1, 2, 3, 4},
211								},
212							},
213						}
214						mLis, _ := proto.Marshal(lis)
215						return mLis
216					}(),
217				},
218			},
219			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
220			wantMD:     errMD,
221			wantErr:    true,
222		},
223		{
224			name: "wrong type in apiListener",
225			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
226				Name: v3LDSTarget,
227				ApiListener: &v3listenerpb.ApiListener{
228					ApiListener: testutils.MarshalAny(&v2xdspb.Listener{}),
229				},
230			})},
231			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
232			wantMD:     errMD,
233			wantErr:    true,
234		},
235		{
236			name: "empty httpConnMgr in apiListener",
237			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
238				Name: v3LDSTarget,
239				ApiListener: &v3listenerpb.ApiListener{
240					ApiListener: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
241						RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
242							Rds: &v3httppb.Rds{},
243						},
244					}),
245				},
246			})},
247			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
248			wantMD:     errMD,
249			wantErr:    true,
250		},
251		{
252			name: "scopedRoutes routeConfig in apiListener",
253			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
254				Name: v3LDSTarget,
255				ApiListener: &v3listenerpb.ApiListener{
256					ApiListener: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
257						RouteSpecifier: &v3httppb.HttpConnectionManager_ScopedRoutes{},
258					}),
259				},
260			})},
261			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
262			wantMD:     errMD,
263			wantErr:    true,
264		},
265		{
266			name: "rds.ConfigSource in apiListener is not ADS",
267			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
268				Name: v3LDSTarget,
269				ApiListener: &v3listenerpb.ApiListener{
270					ApiListener: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
271						RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
272							Rds: &v3httppb.Rds{
273								ConfigSource: &v3corepb.ConfigSource{
274									ConfigSourceSpecifier: &v3corepb.ConfigSource_Path{
275										Path: "/some/path",
276									},
277								},
278								RouteConfigName: v3RouteConfigName,
279							},
280						},
281					}),
282				},
283			})},
284			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
285			wantMD:     errMD,
286			wantErr:    true,
287		},
288		{
289			name: "empty resource list",
290			wantMD: UpdateMetadata{
291				Status:  ServiceStatusACKed,
292				Version: testVersion,
293			},
294		},
295		{
296			name:      "v3 with no filters",
297			resources: []*anypb.Any{v3LisWithFilters()},
298			wantUpdate: map[string]ListenerUpdateErrTuple{
299				v3LDSTarget: {Update: ListenerUpdate{RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second, HTTPFilters: routerFilterList, Raw: v3LisWithFilters()}},
300			},
301			wantMD: UpdateMetadata{
302				Status:  ServiceStatusACKed,
303				Version: testVersion,
304			},
305		},
306		{
307			name: "v3 no terminal filter",
308			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
309				Name: v3LDSTarget,
310				ApiListener: &v3listenerpb.ApiListener{
311					ApiListener: testutils.MarshalAny(
312						&v3httppb.HttpConnectionManager{
313							RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
314								Rds: &v3httppb.Rds{
315									ConfigSource: &v3corepb.ConfigSource{
316										ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}},
317									},
318									RouteConfigName: v3RouteConfigName,
319								},
320							},
321							CommonHttpProtocolOptions: &v3corepb.HttpProtocolOptions{
322								MaxStreamDuration: durationpb.New(time.Second),
323							},
324						}),
325				},
326			})},
327			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
328			wantMD:     errMD,
329			wantErr:    true,
330		},
331		{
332			name:      "v3 with custom filter",
333			resources: []*anypb.Any{v3LisWithFilters(customFilter)},
334			wantUpdate: map[string]ListenerUpdateErrTuple{
335				v3LDSTarget: {Update: ListenerUpdate{
336					RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second,
337					HTTPFilters: []HTTPFilter{
338						{
339							Name:   "customFilter",
340							Filter: httpFilter{},
341							Config: filterConfig{Cfg: customFilterConfig},
342						},
343						routerFilter,
344					},
345					Raw: v3LisWithFilters(customFilter),
346				}},
347			},
348			wantMD: UpdateMetadata{
349				Status:  ServiceStatusACKed,
350				Version: testVersion,
351			},
352		},
353		{
354			name:      "v3 with custom filter in typed struct",
355			resources: []*anypb.Any{v3LisWithFilters(typedStructFilter)},
356			wantUpdate: map[string]ListenerUpdateErrTuple{
357				v3LDSTarget: {Update: ListenerUpdate{
358					RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second,
359					HTTPFilters: []HTTPFilter{
360						{
361							Name:   "customFilter",
362							Filter: httpFilter{},
363							Config: filterConfig{Cfg: customFilterTypedStructConfig},
364						},
365						routerFilter,
366					},
367					Raw: v3LisWithFilters(typedStructFilter),
368				}},
369			},
370			wantMD: UpdateMetadata{
371				Status:  ServiceStatusACKed,
372				Version: testVersion,
373			},
374		},
375		{
376			name:      "v3 with optional custom filter",
377			resources: []*anypb.Any{v3LisWithFilters(customOptionalFilter)},
378			wantUpdate: map[string]ListenerUpdateErrTuple{
379				v3LDSTarget: {Update: ListenerUpdate{
380					RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second,
381					HTTPFilters: []HTTPFilter{
382						{
383							Name:   "customFilter",
384							Filter: httpFilter{},
385							Config: filterConfig{Cfg: customFilterConfig},
386						},
387						routerFilter,
388					},
389					Raw: v3LisWithFilters(customOptionalFilter),
390				}},
391			},
392			wantMD: UpdateMetadata{
393				Status:  ServiceStatusACKed,
394				Version: testVersion,
395			},
396		},
397		{
398			name:       "v3 with two filters with same name",
399			resources:  []*anypb.Any{v3LisWithFilters(customFilter, customFilter)},
400			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
401			wantMD:     errMD,
402			wantErr:    true,
403		},
404		{
405			name:      "v3 with two filters - same type different name",
406			resources: []*anypb.Any{v3LisWithFilters(customFilter, customFilter2)},
407			wantUpdate: map[string]ListenerUpdateErrTuple{
408				v3LDSTarget: {Update: ListenerUpdate{
409					RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second,
410					HTTPFilters: []HTTPFilter{{
411						Name:   "customFilter",
412						Filter: httpFilter{},
413						Config: filterConfig{Cfg: customFilterConfig},
414					}, {
415						Name:   "customFilter2",
416						Filter: httpFilter{},
417						Config: filterConfig{Cfg: customFilterConfig},
418					},
419						routerFilter,
420					},
421					Raw: v3LisWithFilters(customFilter, customFilter2),
422				}},
423			},
424			wantMD: UpdateMetadata{
425				Status:  ServiceStatusACKed,
426				Version: testVersion,
427			},
428		},
429		{
430			name:       "v3 with server-only filter",
431			resources:  []*anypb.Any{v3LisWithFilters(serverOnlyCustomFilter)},
432			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
433			wantMD:     errMD,
434			wantErr:    true,
435		},
436		{
437			name:      "v3 with optional server-only filter",
438			resources: []*anypb.Any{v3LisWithFilters(serverOnlyOptionalCustomFilter)},
439			wantUpdate: map[string]ListenerUpdateErrTuple{
440				v3LDSTarget: {Update: ListenerUpdate{
441					RouteConfigName:   v3RouteConfigName,
442					MaxStreamDuration: time.Second,
443					Raw:               v3LisWithFilters(serverOnlyOptionalCustomFilter),
444					HTTPFilters:       routerFilterList,
445				}},
446			},
447			wantMD: UpdateMetadata{
448				Status:  ServiceStatusACKed,
449				Version: testVersion,
450			},
451		},
452		{
453			name:      "v3 with client-only filter",
454			resources: []*anypb.Any{v3LisWithFilters(clientOnlyCustomFilter)},
455			wantUpdate: map[string]ListenerUpdateErrTuple{
456				v3LDSTarget: {Update: ListenerUpdate{
457					RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second,
458					HTTPFilters: []HTTPFilter{
459						{
460							Name:   "clientOnlyCustomFilter",
461							Filter: clientOnlyHTTPFilter{},
462							Config: filterConfig{Cfg: clientOnlyCustomFilterConfig},
463						},
464						routerFilter},
465					Raw: v3LisWithFilters(clientOnlyCustomFilter),
466				}},
467			},
468			wantMD: UpdateMetadata{
469				Status:  ServiceStatusACKed,
470				Version: testVersion,
471			},
472		},
473		{
474			name:       "v3 with err filter",
475			resources:  []*anypb.Any{v3LisWithFilters(errFilter)},
476			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
477			wantMD:     errMD,
478			wantErr:    true,
479		},
480		{
481			name:       "v3 with optional err filter",
482			resources:  []*anypb.Any{v3LisWithFilters(errOptionalFilter)},
483			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
484			wantMD:     errMD,
485			wantErr:    true,
486		},
487		{
488			name:       "v3 with unknown filter",
489			resources:  []*anypb.Any{v3LisWithFilters(unknownFilter)},
490			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
491			wantMD:     errMD,
492			wantErr:    true,
493		},
494		{
495			name:      "v3 with unknown filter (optional)",
496			resources: []*anypb.Any{v3LisWithFilters(unknownOptionalFilter)},
497			wantUpdate: map[string]ListenerUpdateErrTuple{
498				v3LDSTarget: {Update: ListenerUpdate{
499					RouteConfigName:   v3RouteConfigName,
500					MaxStreamDuration: time.Second,
501					HTTPFilters:       routerFilterList,
502					Raw:               v3LisWithFilters(unknownOptionalFilter),
503				}},
504			},
505			wantMD: UpdateMetadata{
506				Status:  ServiceStatusACKed,
507				Version: testVersion,
508			},
509		},
510		{
511			name:      "v2 listener resource",
512			resources: []*anypb.Any{v2Lis},
513			wantUpdate: map[string]ListenerUpdateErrTuple{
514				v2LDSTarget: {Update: ListenerUpdate{RouteConfigName: v2RouteConfigName, Raw: v2Lis}},
515			},
516			wantMD: UpdateMetadata{
517				Status:  ServiceStatusACKed,
518				Version: testVersion,
519			},
520		},
521		{
522			name:      "v3 listener resource",
523			resources: []*anypb.Any{v3LisWithFilters()},
524			wantUpdate: map[string]ListenerUpdateErrTuple{
525				v3LDSTarget: {Update: ListenerUpdate{RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second, HTTPFilters: routerFilterList, Raw: v3LisWithFilters()}},
526			},
527			wantMD: UpdateMetadata{
528				Status:  ServiceStatusACKed,
529				Version: testVersion,
530			},
531		},
532		{
533			name:      "v3 listener with inline route configuration",
534			resources: []*anypb.Any{v3LisWithInlineRoute},
535			wantUpdate: map[string]ListenerUpdateErrTuple{
536				v3LDSTarget: {Update: ListenerUpdate{
537					InlineRouteConfig: &RouteConfigUpdate{
538						VirtualHosts: []*VirtualHost{{
539							Domains: []string{v3LDSTarget},
540							Routes:  []*Route{{Prefix: newStringP("/"), WeightedClusters: map[string]WeightedCluster{clusterName: {Weight: 1}}, RouteAction: RouteActionRoute}},
541						}}},
542					MaxStreamDuration: time.Second,
543					Raw:               v3LisWithInlineRoute,
544					HTTPFilters:       routerFilterList,
545				}},
546			},
547			wantMD: UpdateMetadata{
548				Status:  ServiceStatusACKed,
549				Version: testVersion,
550			},
551		},
552		{
553			name:      "multiple listener resources",
554			resources: []*anypb.Any{v2Lis, v3LisWithFilters()},
555			wantUpdate: map[string]ListenerUpdateErrTuple{
556				v2LDSTarget: {Update: ListenerUpdate{RouteConfigName: v2RouteConfigName, Raw: v2Lis}},
557				v3LDSTarget: {Update: ListenerUpdate{RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second, Raw: v3LisWithFilters(), HTTPFilters: routerFilterList}},
558			},
559			wantMD: UpdateMetadata{
560				Status:  ServiceStatusACKed,
561				Version: testVersion,
562			},
563		},
564		{
565			// To test that unmarshal keeps processing on errors.
566			name: "good and bad listener resources",
567			resources: []*anypb.Any{
568				v2Lis,
569				testutils.MarshalAny(&v3listenerpb.Listener{
570					Name: "bad",
571					ApiListener: &v3listenerpb.ApiListener{
572						ApiListener: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
573							RouteSpecifier: &v3httppb.HttpConnectionManager_ScopedRoutes{},
574						}),
575					}}),
576				v3LisWithFilters(),
577			},
578			wantUpdate: map[string]ListenerUpdateErrTuple{
579				v2LDSTarget: {Update: ListenerUpdate{RouteConfigName: v2RouteConfigName, Raw: v2Lis}},
580				v3LDSTarget: {Update: ListenerUpdate{RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second, Raw: v3LisWithFilters(), HTTPFilters: routerFilterList}},
581				"bad":       {Err: cmpopts.AnyError},
582			},
583			wantMD:  errMD,
584			wantErr: true,
585		},
586	}
587
588	for _, test := range tests {
589		t.Run(test.name, func(t *testing.T) {
590			update, md, err := UnmarshalListener(testVersion, test.resources, nil)
591			if (err != nil) != test.wantErr {
592				t.Fatalf("UnmarshalListener(), got err: %v, wantErr: %v", err, test.wantErr)
593			}
594			if diff := cmp.Diff(update, test.wantUpdate, cmpOpts); diff != "" {
595				t.Errorf("got unexpected update, diff (-got +want): %v", diff)
596			}
597			if diff := cmp.Diff(md, test.wantMD, cmpOptsIgnoreDetails); diff != "" {
598				t.Errorf("got unexpected metadata, diff (-got +want): %v", diff)
599			}
600		})
601	}
602}
603
604func (s) TestUnmarshalListener_ServerSide(t *testing.T) {
605	oldRBAC := env.RBACSupport
606	env.RBACSupport = true
607	defer func() {
608		env.RBACSupport = oldRBAC
609	}()
610	const (
611		v3LDSTarget = "grpc/server?xds.resource.listening_address=0.0.0.0:9999"
612		testVersion = "test-version-lds-server"
613	)
614
615	var (
616		serverOnlyCustomFilter = &v3httppb.HttpFilter{
617			Name:       "serverOnlyCustomFilter",
618			ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: serverOnlyCustomFilterConfig},
619		}
620		routeConfig = &v3routepb.RouteConfiguration{
621			Name: "routeName",
622			VirtualHosts: []*v3routepb.VirtualHost{{
623				Domains: []string{"lds.target.good:3333"},
624				Routes: []*v3routepb.Route{{
625					Match: &v3routepb.RouteMatch{
626						PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
627					},
628					Action: &v3routepb.Route_NonForwardingAction{},
629				}}}}}
630		inlineRouteConfig = &RouteConfigUpdate{
631			VirtualHosts: []*VirtualHost{{
632				Domains: []string{"lds.target.good:3333"},
633				Routes:  []*Route{{Prefix: newStringP("/"), RouteAction: RouteActionNonForwardingAction}},
634			}}}
635		emptyValidNetworkFilters = []*v3listenerpb.Filter{
636			{
637				Name: "filter-1",
638				ConfigType: &v3listenerpb.Filter_TypedConfig{
639					TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
640						RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
641							RouteConfig: routeConfig,
642						},
643						HttpFilters: []*v3httppb.HttpFilter{e2e.RouterHTTPFilter},
644					}),
645				},
646			},
647		}
648		localSocketAddress = &v3corepb.Address{
649			Address: &v3corepb.Address_SocketAddress{
650				SocketAddress: &v3corepb.SocketAddress{
651					Address: "0.0.0.0",
652					PortSpecifier: &v3corepb.SocketAddress_PortValue{
653						PortValue: 9999,
654					},
655				},
656			},
657		}
658		listenerEmptyTransportSocket = testutils.MarshalAny(&v3listenerpb.Listener{
659			Name:    v3LDSTarget,
660			Address: localSocketAddress,
661			FilterChains: []*v3listenerpb.FilterChain{
662				{
663					Name:    "filter-chain-1",
664					Filters: emptyValidNetworkFilters,
665				},
666			},
667		})
668		listenerNoValidationContextDeprecatedFields = testutils.MarshalAny(&v3listenerpb.Listener{
669			Name:    v3LDSTarget,
670			Address: localSocketAddress,
671			FilterChains: []*v3listenerpb.FilterChain{
672				{
673					Name:    "filter-chain-1",
674					Filters: emptyValidNetworkFilters,
675					TransportSocket: &v3corepb.TransportSocket{
676						Name: "envoy.transport_sockets.tls",
677						ConfigType: &v3corepb.TransportSocket_TypedConfig{
678							TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
679								CommonTlsContext: &v3tlspb.CommonTlsContext{
680									TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
681										InstanceName:    "identityPluginInstance",
682										CertificateName: "identityCertName",
683									},
684								},
685							}),
686						},
687					},
688				},
689			},
690			DefaultFilterChain: &v3listenerpb.FilterChain{
691				Name:    "default-filter-chain-1",
692				Filters: emptyValidNetworkFilters,
693				TransportSocket: &v3corepb.TransportSocket{
694					Name: "envoy.transport_sockets.tls",
695					ConfigType: &v3corepb.TransportSocket_TypedConfig{
696						TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
697							CommonTlsContext: &v3tlspb.CommonTlsContext{
698								TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
699									InstanceName:    "defaultIdentityPluginInstance",
700									CertificateName: "defaultIdentityCertName",
701								},
702							},
703						}),
704					},
705				},
706			},
707		})
708		listenerNoValidationContextNewFields = testutils.MarshalAny(&v3listenerpb.Listener{
709			Name:    v3LDSTarget,
710			Address: localSocketAddress,
711			FilterChains: []*v3listenerpb.FilterChain{
712				{
713					Name:    "filter-chain-1",
714					Filters: emptyValidNetworkFilters,
715					TransportSocket: &v3corepb.TransportSocket{
716						Name: "envoy.transport_sockets.tls",
717						ConfigType: &v3corepb.TransportSocket_TypedConfig{
718							TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
719								CommonTlsContext: &v3tlspb.CommonTlsContext{
720									TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
721										InstanceName:    "identityPluginInstance",
722										CertificateName: "identityCertName",
723									},
724								},
725							}),
726						},
727					},
728				},
729			},
730			DefaultFilterChain: &v3listenerpb.FilterChain{
731				Name:    "default-filter-chain-1",
732				Filters: emptyValidNetworkFilters,
733				TransportSocket: &v3corepb.TransportSocket{
734					Name: "envoy.transport_sockets.tls",
735					ConfigType: &v3corepb.TransportSocket_TypedConfig{
736						TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
737							CommonTlsContext: &v3tlspb.CommonTlsContext{
738								TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
739									InstanceName:    "defaultIdentityPluginInstance",
740									CertificateName: "defaultIdentityCertName",
741								},
742							},
743						}),
744					},
745				},
746			},
747		})
748		listenerWithValidationContextDeprecatedFields = testutils.MarshalAny(&v3listenerpb.Listener{
749			Name:    v3LDSTarget,
750			Address: localSocketAddress,
751			FilterChains: []*v3listenerpb.FilterChain{
752				{
753					Name:    "filter-chain-1",
754					Filters: emptyValidNetworkFilters,
755					TransportSocket: &v3corepb.TransportSocket{
756						Name: "envoy.transport_sockets.tls",
757						ConfigType: &v3corepb.TransportSocket_TypedConfig{
758							TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
759								RequireClientCertificate: &wrapperspb.BoolValue{Value: true},
760								CommonTlsContext: &v3tlspb.CommonTlsContext{
761									TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
762										InstanceName:    "identityPluginInstance",
763										CertificateName: "identityCertName",
764									},
765									ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
766										ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
767											InstanceName:    "rootPluginInstance",
768											CertificateName: "rootCertName",
769										},
770									},
771								},
772							}),
773						},
774					},
775				},
776			},
777			DefaultFilterChain: &v3listenerpb.FilterChain{
778				Name:    "default-filter-chain-1",
779				Filters: emptyValidNetworkFilters,
780				TransportSocket: &v3corepb.TransportSocket{
781					Name: "envoy.transport_sockets.tls",
782					ConfigType: &v3corepb.TransportSocket_TypedConfig{
783						TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
784							RequireClientCertificate: &wrapperspb.BoolValue{Value: true},
785							CommonTlsContext: &v3tlspb.CommonTlsContext{
786								TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
787									InstanceName:    "defaultIdentityPluginInstance",
788									CertificateName: "defaultIdentityCertName",
789								},
790								ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
791									ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
792										InstanceName:    "defaultRootPluginInstance",
793										CertificateName: "defaultRootCertName",
794									},
795								},
796							},
797						}),
798					},
799				},
800			},
801		})
802		listenerWithValidationContextNewFields = testutils.MarshalAny(&v3listenerpb.Listener{
803			Name:    v3LDSTarget,
804			Address: localSocketAddress,
805			FilterChains: []*v3listenerpb.FilterChain{
806				{
807					Name:    "filter-chain-1",
808					Filters: emptyValidNetworkFilters,
809					TransportSocket: &v3corepb.TransportSocket{
810						Name: "envoy.transport_sockets.tls",
811						ConfigType: &v3corepb.TransportSocket_TypedConfig{
812							TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
813								RequireClientCertificate: &wrapperspb.BoolValue{Value: true},
814								CommonTlsContext: &v3tlspb.CommonTlsContext{
815									TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
816										InstanceName:    "identityPluginInstance",
817										CertificateName: "identityCertName",
818									},
819									ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
820										ValidationContext: &v3tlspb.CertificateValidationContext{
821											CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
822												InstanceName:    "rootPluginInstance",
823												CertificateName: "rootCertName",
824											},
825										},
826									},
827								},
828							}),
829						},
830					},
831				},
832			},
833			DefaultFilterChain: &v3listenerpb.FilterChain{
834				Name:    "default-filter-chain-1",
835				Filters: emptyValidNetworkFilters,
836				TransportSocket: &v3corepb.TransportSocket{
837					Name: "envoy.transport_sockets.tls",
838					ConfigType: &v3corepb.TransportSocket_TypedConfig{
839						TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
840							RequireClientCertificate: &wrapperspb.BoolValue{Value: true},
841							CommonTlsContext: &v3tlspb.CommonTlsContext{
842								TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
843									InstanceName:    "defaultIdentityPluginInstance",
844									CertificateName: "defaultIdentityCertName",
845								},
846								ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
847									CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
848										DefaultValidationContext: &v3tlspb.CertificateValidationContext{
849											CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
850												InstanceName:    "defaultRootPluginInstance",
851												CertificateName: "defaultRootCertName",
852											},
853										},
854									},
855								},
856							},
857						}),
858					},
859				},
860			},
861		})
862		errMD = UpdateMetadata{
863			Status:  ServiceStatusNACKed,
864			Version: testVersion,
865			ErrState: &UpdateErrorMetadata{
866				Version: testVersion,
867				Err:     cmpopts.AnyError,
868			},
869		}
870	)
871
872	tests := []struct {
873		name       string
874		resources  []*anypb.Any
875		wantUpdate map[string]ListenerUpdateErrTuple
876		wantMD     UpdateMetadata
877		wantErr    string
878	}{
879		{
880			name: "non-empty listener filters",
881			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
882				Name: v3LDSTarget,
883				ListenerFilters: []*v3listenerpb.ListenerFilter{
884					{Name: "listener-filter-1"},
885				},
886			})},
887			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
888			wantMD:     errMD,
889			wantErr:    "unsupported field 'listener_filters'",
890		},
891		{
892			name: "use_original_dst is set",
893			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
894				Name:           v3LDSTarget,
895				UseOriginalDst: &wrapperspb.BoolValue{Value: true},
896			})},
897			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
898			wantMD:     errMD,
899			wantErr:    "unsupported field 'use_original_dst'",
900		},
901		{
902			name:       "no address field",
903			resources:  []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{Name: v3LDSTarget})},
904			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
905			wantMD:     errMD,
906			wantErr:    "no address field in LDS response",
907		},
908		{
909			name: "no socket address field",
910			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
911				Name:    v3LDSTarget,
912				Address: &v3corepb.Address{},
913			})},
914			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
915			wantMD:     errMD,
916			wantErr:    "no socket_address field in LDS response",
917		},
918		{
919			name: "no filter chains and no default filter chain",
920			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
921				Name:    v3LDSTarget,
922				Address: localSocketAddress,
923				FilterChains: []*v3listenerpb.FilterChain{
924					{
925						FilterChainMatch: &v3listenerpb.FilterChainMatch{DestinationPort: &wrapperspb.UInt32Value{Value: 666}},
926						Filters:          emptyValidNetworkFilters,
927					},
928				},
929			})},
930			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
931			wantMD:     errMD,
932			wantErr:    "no supported filter chains and no default filter chain",
933		},
934		{
935			name: "missing http connection manager network filter",
936			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
937				Name:    v3LDSTarget,
938				Address: localSocketAddress,
939				FilterChains: []*v3listenerpb.FilterChain{
940					{
941						Name: "filter-chain-1",
942					},
943				},
944			})},
945			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
946			wantMD:     errMD,
947			wantErr:    "missing HttpConnectionManager filter",
948		},
949		{
950			name: "missing filter name in http filter",
951			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
952				Name:    v3LDSTarget,
953				Address: localSocketAddress,
954				FilterChains: []*v3listenerpb.FilterChain{
955					{
956						Name: "filter-chain-1",
957						Filters: []*v3listenerpb.Filter{
958							{
959								ConfigType: &v3listenerpb.Filter_TypedConfig{
960									TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{}),
961								},
962							},
963						},
964					},
965				},
966			})},
967			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
968			wantMD:     errMD,
969			wantErr:    "missing name field in filter",
970		},
971		{
972			name: "duplicate filter names in http filter",
973			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
974				Name:    v3LDSTarget,
975				Address: localSocketAddress,
976				FilterChains: []*v3listenerpb.FilterChain{
977					{
978						Name: "filter-chain-1",
979						Filters: []*v3listenerpb.Filter{
980							{
981								Name: "name",
982								ConfigType: &v3listenerpb.Filter_TypedConfig{
983									TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
984										RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
985											RouteConfig: routeConfig,
986										},
987										HttpFilters: []*v3httppb.HttpFilter{emptyRouterFilter},
988									}),
989								},
990							},
991							{
992								Name: "name",
993								ConfigType: &v3listenerpb.Filter_TypedConfig{
994									TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
995										RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
996											RouteConfig: routeConfig,
997										},
998										HttpFilters: []*v3httppb.HttpFilter{emptyRouterFilter},
999									}),
1000								},
1001							},
1002						},
1003					},
1004				},
1005			})},
1006			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1007			wantMD:     errMD,
1008			wantErr:    "duplicate filter name",
1009		},
1010		{
1011			name: "no terminal filter",
1012			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1013				Name:    v3LDSTarget,
1014				Address: localSocketAddress,
1015				FilterChains: []*v3listenerpb.FilterChain{
1016					{
1017						Name: "filter-chain-1",
1018						Filters: []*v3listenerpb.Filter{
1019							{
1020								Name: "name",
1021								ConfigType: &v3listenerpb.Filter_TypedConfig{
1022									TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
1023										RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
1024											RouteConfig: routeConfig,
1025										},
1026									}),
1027								},
1028							},
1029						},
1030					},
1031				},
1032			})},
1033			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1034			wantMD:     errMD,
1035			wantErr:    "http filters list is empty",
1036		},
1037		{
1038			name: "terminal filter not last",
1039			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1040				Name:    v3LDSTarget,
1041				Address: localSocketAddress,
1042				FilterChains: []*v3listenerpb.FilterChain{
1043					{
1044						Name: "filter-chain-1",
1045						Filters: []*v3listenerpb.Filter{
1046							{
1047								Name: "name",
1048								ConfigType: &v3listenerpb.Filter_TypedConfig{
1049									TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
1050										RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
1051											RouteConfig: routeConfig,
1052										},
1053										HttpFilters: []*v3httppb.HttpFilter{emptyRouterFilter, serverOnlyCustomFilter},
1054									}),
1055								},
1056							},
1057						},
1058					},
1059				},
1060			})},
1061			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1062			wantMD:     errMD,
1063			wantErr:    "is a terminal filter but it is not last in the filter chain",
1064		},
1065		{
1066			name: "last not terminal filter",
1067			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1068				Name:    v3LDSTarget,
1069				Address: localSocketAddress,
1070				FilterChains: []*v3listenerpb.FilterChain{
1071					{
1072						Name: "filter-chain-1",
1073						Filters: []*v3listenerpb.Filter{
1074							{
1075								Name: "name",
1076								ConfigType: &v3listenerpb.Filter_TypedConfig{
1077									TypedConfig: testutils.MarshalAny(&v3httppb.HttpConnectionManager{
1078										RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
1079											RouteConfig: routeConfig,
1080										},
1081										HttpFilters: []*v3httppb.HttpFilter{serverOnlyCustomFilter},
1082									}),
1083								},
1084							},
1085						},
1086					},
1087				},
1088			})},
1089			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1090			wantMD:     errMD,
1091			wantErr:    "is not a terminal filter",
1092		},
1093		{
1094			name: "unsupported oneof in typed config of http filter",
1095			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1096				Name:    v3LDSTarget,
1097				Address: localSocketAddress,
1098				FilterChains: []*v3listenerpb.FilterChain{
1099					{
1100						Name: "filter-chain-1",
1101						Filters: []*v3listenerpb.Filter{
1102							{
1103								Name:       "name",
1104								ConfigType: &v3listenerpb.Filter_ConfigDiscovery{},
1105							},
1106						},
1107					},
1108				},
1109			})},
1110			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1111			wantMD:     errMD,
1112			wantErr:    "unsupported config_type",
1113		},
1114		{
1115			name: "overlapping filter chain match criteria",
1116			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1117				Name:    v3LDSTarget,
1118				Address: localSocketAddress,
1119				FilterChains: []*v3listenerpb.FilterChain{
1120					{
1121						FilterChainMatch: &v3listenerpb.FilterChainMatch{SourcePorts: []uint32{1, 2, 3, 4, 5}},
1122						Filters:          emptyValidNetworkFilters,
1123					},
1124					{
1125						FilterChainMatch: &v3listenerpb.FilterChainMatch{},
1126						Filters:          emptyValidNetworkFilters,
1127					},
1128					{
1129						FilterChainMatch: &v3listenerpb.FilterChainMatch{SourcePorts: []uint32{5, 6, 7}},
1130						Filters:          emptyValidNetworkFilters,
1131					},
1132				},
1133			})},
1134			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1135			wantMD:     errMD,
1136			wantErr:    "multiple filter chains with overlapping matching rules are defined",
1137		},
1138		{
1139			name: "unsupported network filter",
1140			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1141				Name:    v3LDSTarget,
1142				Address: localSocketAddress,
1143				FilterChains: []*v3listenerpb.FilterChain{
1144					{
1145						Name: "filter-chain-1",
1146						Filters: []*v3listenerpb.Filter{
1147							{
1148								Name: "name",
1149								ConfigType: &v3listenerpb.Filter_TypedConfig{
1150									TypedConfig: testutils.MarshalAny(&v3httppb.LocalReplyConfig{}),
1151								},
1152							},
1153						},
1154					},
1155				},
1156			})},
1157			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1158			wantMD:     errMD,
1159			wantErr:    "unsupported network filter",
1160		},
1161		{
1162			name: "badly marshaled network filter",
1163			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1164				Name:    v3LDSTarget,
1165				Address: localSocketAddress,
1166				FilterChains: []*v3listenerpb.FilterChain{
1167					{
1168						Name: "filter-chain-1",
1169						Filters: []*v3listenerpb.Filter{
1170							{
1171								Name: "name",
1172								ConfigType: &v3listenerpb.Filter_TypedConfig{
1173									TypedConfig: &anypb.Any{
1174										TypeUrl: version.V3HTTPConnManagerURL,
1175										Value:   []byte{1, 2, 3, 4},
1176									},
1177								},
1178							},
1179						},
1180					},
1181				},
1182			})},
1183			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1184			wantMD:     errMD,
1185			wantErr:    "failed unmarshaling of network filter",
1186		},
1187		{
1188			name: "unexpected transport socket name",
1189			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1190				Name:    v3LDSTarget,
1191				Address: localSocketAddress,
1192				FilterChains: []*v3listenerpb.FilterChain{
1193					{
1194						Name:    "filter-chain-1",
1195						Filters: emptyValidNetworkFilters,
1196						TransportSocket: &v3corepb.TransportSocket{
1197							Name: "unsupported-transport-socket-name",
1198						},
1199					},
1200				},
1201			})},
1202			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1203			wantMD:     errMD,
1204			wantErr:    "transport_socket field has unexpected name",
1205		},
1206		{
1207			name: "unexpected transport socket typedConfig URL",
1208			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1209				Name:    v3LDSTarget,
1210				Address: localSocketAddress,
1211				FilterChains: []*v3listenerpb.FilterChain{
1212					{
1213						Name:    "filter-chain-1",
1214						Filters: emptyValidNetworkFilters,
1215						TransportSocket: &v3corepb.TransportSocket{
1216							Name: "envoy.transport_sockets.tls",
1217							ConfigType: &v3corepb.TransportSocket_TypedConfig{
1218								TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{}),
1219							},
1220						},
1221					},
1222				},
1223			})},
1224			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1225			wantMD:     errMD,
1226			wantErr:    "transport_socket field has unexpected typeURL",
1227		},
1228		{
1229			name: "badly marshaled transport socket",
1230			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1231				Name:    v3LDSTarget,
1232				Address: localSocketAddress,
1233				FilterChains: []*v3listenerpb.FilterChain{
1234					{
1235						Name:    "filter-chain-1",
1236						Filters: emptyValidNetworkFilters,
1237						TransportSocket: &v3corepb.TransportSocket{
1238							Name: "envoy.transport_sockets.tls",
1239							ConfigType: &v3corepb.TransportSocket_TypedConfig{
1240								TypedConfig: &anypb.Any{
1241									TypeUrl: version.V3DownstreamTLSContextURL,
1242									Value:   []byte{1, 2, 3, 4},
1243								},
1244							},
1245						},
1246					},
1247				},
1248			})},
1249			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1250			wantMD:     errMD,
1251			wantErr:    "failed to unmarshal DownstreamTlsContext in LDS response",
1252		},
1253		{
1254			name: "missing CommonTlsContext",
1255			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1256				Name:    v3LDSTarget,
1257				Address: localSocketAddress,
1258				FilterChains: []*v3listenerpb.FilterChain{
1259					{
1260						Name:    "filter-chain-1",
1261						Filters: emptyValidNetworkFilters,
1262						TransportSocket: &v3corepb.TransportSocket{
1263							Name: "envoy.transport_sockets.tls",
1264							ConfigType: &v3corepb.TransportSocket_TypedConfig{
1265								TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{}),
1266							},
1267						},
1268					},
1269				},
1270			})},
1271			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1272			wantMD:     errMD,
1273			wantErr:    "DownstreamTlsContext in LDS response does not contain a CommonTlsContext",
1274		},
1275		{
1276			name: "unsupported validation context in transport socket",
1277			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1278				Name:    v3LDSTarget,
1279				Address: localSocketAddress,
1280				FilterChains: []*v3listenerpb.FilterChain{
1281					{
1282						Name:    "filter-chain-1",
1283						Filters: emptyValidNetworkFilters,
1284						TransportSocket: &v3corepb.TransportSocket{
1285							Name: "envoy.transport_sockets.tls",
1286							ConfigType: &v3corepb.TransportSocket_TypedConfig{
1287								TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
1288									CommonTlsContext: &v3tlspb.CommonTlsContext{
1289										ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextSdsSecretConfig{
1290											ValidationContextSdsSecretConfig: &v3tlspb.SdsSecretConfig{
1291												Name: "foo-sds-secret",
1292											},
1293										},
1294									},
1295								}),
1296							},
1297						},
1298					},
1299				},
1300			})},
1301			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1302			wantMD:     errMD,
1303			wantErr:    "validation context contains unexpected type",
1304		},
1305		{
1306			name:      "empty transport socket",
1307			resources: []*anypb.Any{listenerEmptyTransportSocket},
1308			wantUpdate: map[string]ListenerUpdateErrTuple{
1309				v3LDSTarget: {Update: ListenerUpdate{
1310					InboundListenerCfg: &InboundListenerConfig{
1311						Address: "0.0.0.0",
1312						Port:    "9999",
1313						FilterChains: &FilterChainManager{
1314							dstPrefixMap: map[string]*destPrefixEntry{
1315								unspecifiedPrefixMapKey: {
1316									srcTypeArr: [3]*sourcePrefixes{
1317										{
1318											srcPrefixMap: map[string]*sourcePrefixEntry{
1319												unspecifiedPrefixMapKey: {
1320													srcPortMap: map[int]*FilterChain{
1321														0: {
1322															InlineRouteConfig: inlineRouteConfig,
1323															HTTPFilters:       routerFilterList,
1324														},
1325													},
1326												},
1327											},
1328										},
1329									},
1330								},
1331							},
1332						},
1333					},
1334					Raw: listenerEmptyTransportSocket,
1335				}},
1336			},
1337			wantMD: UpdateMetadata{
1338				Status:  ServiceStatusACKed,
1339				Version: testVersion,
1340			},
1341		},
1342		{
1343			name: "no identity and root certificate providers using deprecated fields",
1344			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1345				Name:    v3LDSTarget,
1346				Address: localSocketAddress,
1347				FilterChains: []*v3listenerpb.FilterChain{
1348					{
1349						Name:    "filter-chain-1",
1350						Filters: emptyValidNetworkFilters,
1351						TransportSocket: &v3corepb.TransportSocket{
1352							Name: "envoy.transport_sockets.tls",
1353							ConfigType: &v3corepb.TransportSocket_TypedConfig{
1354								TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
1355									RequireClientCertificate: &wrapperspb.BoolValue{Value: true},
1356									CommonTlsContext: &v3tlspb.CommonTlsContext{
1357										TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
1358											InstanceName:    "identityPluginInstance",
1359											CertificateName: "identityCertName",
1360										},
1361									},
1362								}),
1363							},
1364						},
1365					},
1366				},
1367			})},
1368			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1369			wantMD:     errMD,
1370			wantErr:    "security configuration on the server-side does not contain root certificate provider instance name, but require_client_cert field is set",
1371		},
1372		{
1373			name: "no identity and root certificate providers using new fields",
1374			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1375				Name:    v3LDSTarget,
1376				Address: localSocketAddress,
1377				FilterChains: []*v3listenerpb.FilterChain{
1378					{
1379						Name:    "filter-chain-1",
1380						Filters: emptyValidNetworkFilters,
1381						TransportSocket: &v3corepb.TransportSocket{
1382							Name: "envoy.transport_sockets.tls",
1383							ConfigType: &v3corepb.TransportSocket_TypedConfig{
1384								TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
1385									RequireClientCertificate: &wrapperspb.BoolValue{Value: true},
1386									CommonTlsContext: &v3tlspb.CommonTlsContext{
1387										TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
1388											InstanceName:    "identityPluginInstance",
1389											CertificateName: "identityCertName",
1390										},
1391									},
1392								}),
1393							},
1394						},
1395					},
1396				},
1397			})},
1398			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1399			wantMD:     errMD,
1400			wantErr:    "security configuration on the server-side does not contain root certificate provider instance name, but require_client_cert field is set",
1401		},
1402		{
1403			name: "no identity certificate provider with require_client_cert",
1404			resources: []*anypb.Any{testutils.MarshalAny(&v3listenerpb.Listener{
1405				Name:    v3LDSTarget,
1406				Address: localSocketAddress,
1407				FilterChains: []*v3listenerpb.FilterChain{
1408					{
1409						Name:    "filter-chain-1",
1410						Filters: emptyValidNetworkFilters,
1411						TransportSocket: &v3corepb.TransportSocket{
1412							Name: "envoy.transport_sockets.tls",
1413							ConfigType: &v3corepb.TransportSocket_TypedConfig{
1414								TypedConfig: testutils.MarshalAny(&v3tlspb.DownstreamTlsContext{
1415									CommonTlsContext: &v3tlspb.CommonTlsContext{},
1416								}),
1417							},
1418						},
1419					},
1420				},
1421			})},
1422			wantUpdate: map[string]ListenerUpdateErrTuple{v3LDSTarget: {Err: cmpopts.AnyError}},
1423			wantMD:     errMD,
1424			wantErr:    "security configuration on the server-side does not contain identity certificate provider instance name",
1425		},
1426		{
1427			name:      "happy case with no validation context using deprecated fields",
1428			resources: []*anypb.Any{listenerNoValidationContextDeprecatedFields},
1429			wantUpdate: map[string]ListenerUpdateErrTuple{
1430				v3LDSTarget: {Update: ListenerUpdate{
1431					InboundListenerCfg: &InboundListenerConfig{
1432						Address: "0.0.0.0",
1433						Port:    "9999",
1434						FilterChains: &FilterChainManager{
1435							dstPrefixMap: map[string]*destPrefixEntry{
1436								unspecifiedPrefixMapKey: {
1437									srcTypeArr: [3]*sourcePrefixes{
1438										{
1439											srcPrefixMap: map[string]*sourcePrefixEntry{
1440												unspecifiedPrefixMapKey: {
1441													srcPortMap: map[int]*FilterChain{
1442														0: {
1443															SecurityCfg: &SecurityConfig{
1444																IdentityInstanceName: "identityPluginInstance",
1445																IdentityCertName:     "identityCertName",
1446															},
1447															InlineRouteConfig: inlineRouteConfig,
1448															HTTPFilters:       routerFilterList,
1449														},
1450													},
1451												},
1452											},
1453										},
1454									},
1455								},
1456							},
1457							def: &FilterChain{
1458								SecurityCfg: &SecurityConfig{
1459									IdentityInstanceName: "defaultIdentityPluginInstance",
1460									IdentityCertName:     "defaultIdentityCertName",
1461								},
1462								InlineRouteConfig: inlineRouteConfig,
1463								HTTPFilters:       routerFilterList,
1464							},
1465						},
1466					},
1467					Raw: listenerNoValidationContextDeprecatedFields,
1468				}},
1469			},
1470			wantMD: UpdateMetadata{
1471				Status:  ServiceStatusACKed,
1472				Version: testVersion,
1473			},
1474		},
1475		{
1476			name:      "happy case with no validation context using new fields",
1477			resources: []*anypb.Any{listenerNoValidationContextNewFields},
1478			wantUpdate: map[string]ListenerUpdateErrTuple{
1479				v3LDSTarget: {Update: ListenerUpdate{
1480					InboundListenerCfg: &InboundListenerConfig{
1481						Address: "0.0.0.0",
1482						Port:    "9999",
1483						FilterChains: &FilterChainManager{
1484							dstPrefixMap: map[string]*destPrefixEntry{
1485								unspecifiedPrefixMapKey: {
1486									srcTypeArr: [3]*sourcePrefixes{
1487										{
1488											srcPrefixMap: map[string]*sourcePrefixEntry{
1489												unspecifiedPrefixMapKey: {
1490													srcPortMap: map[int]*FilterChain{
1491														0: {
1492															SecurityCfg: &SecurityConfig{
1493																IdentityInstanceName: "identityPluginInstance",
1494																IdentityCertName:     "identityCertName",
1495															},
1496															InlineRouteConfig: inlineRouteConfig,
1497															HTTPFilters:       routerFilterList,
1498														},
1499													},
1500												},
1501											},
1502										},
1503									},
1504								},
1505							},
1506							def: &FilterChain{
1507								SecurityCfg: &SecurityConfig{
1508									IdentityInstanceName: "defaultIdentityPluginInstance",
1509									IdentityCertName:     "defaultIdentityCertName",
1510								},
1511								InlineRouteConfig: inlineRouteConfig,
1512								HTTPFilters:       routerFilterList,
1513							},
1514						},
1515					},
1516					Raw: listenerNoValidationContextNewFields,
1517				}},
1518			},
1519			wantMD: UpdateMetadata{
1520				Status:  ServiceStatusACKed,
1521				Version: testVersion,
1522			},
1523		},
1524		{
1525			name:      "happy case with validation context provider instance with deprecated fields",
1526			resources: []*anypb.Any{listenerWithValidationContextDeprecatedFields},
1527			wantUpdate: map[string]ListenerUpdateErrTuple{
1528				v3LDSTarget: {Update: ListenerUpdate{
1529					InboundListenerCfg: &InboundListenerConfig{
1530						Address: "0.0.0.0",
1531						Port:    "9999",
1532						FilterChains: &FilterChainManager{
1533							dstPrefixMap: map[string]*destPrefixEntry{
1534								unspecifiedPrefixMapKey: {
1535									srcTypeArr: [3]*sourcePrefixes{
1536										{
1537											srcPrefixMap: map[string]*sourcePrefixEntry{
1538												unspecifiedPrefixMapKey: {
1539													srcPortMap: map[int]*FilterChain{
1540														0: {
1541															SecurityCfg: &SecurityConfig{
1542																RootInstanceName:     "rootPluginInstance",
1543																RootCertName:         "rootCertName",
1544																IdentityInstanceName: "identityPluginInstance",
1545																IdentityCertName:     "identityCertName",
1546																RequireClientCert:    true,
1547															},
1548															InlineRouteConfig: inlineRouteConfig,
1549															HTTPFilters:       routerFilterList,
1550														},
1551													},
1552												},
1553											},
1554										},
1555									},
1556								},
1557							},
1558							def: &FilterChain{
1559								SecurityCfg: &SecurityConfig{
1560									RootInstanceName:     "defaultRootPluginInstance",
1561									RootCertName:         "defaultRootCertName",
1562									IdentityInstanceName: "defaultIdentityPluginInstance",
1563									IdentityCertName:     "defaultIdentityCertName",
1564									RequireClientCert:    true,
1565								},
1566								InlineRouteConfig: inlineRouteConfig,
1567								HTTPFilters:       routerFilterList,
1568							},
1569						},
1570					},
1571					Raw: listenerWithValidationContextDeprecatedFields,
1572				}},
1573			},
1574			wantMD: UpdateMetadata{
1575				Status:  ServiceStatusACKed,
1576				Version: testVersion,
1577			},
1578		},
1579		{
1580			name:      "happy case with validation context provider instance with new fields",
1581			resources: []*anypb.Any{listenerWithValidationContextNewFields},
1582			wantUpdate: map[string]ListenerUpdateErrTuple{
1583				v3LDSTarget: {Update: ListenerUpdate{
1584					InboundListenerCfg: &InboundListenerConfig{
1585						Address: "0.0.0.0",
1586						Port:    "9999",
1587						FilterChains: &FilterChainManager{
1588							dstPrefixMap: map[string]*destPrefixEntry{
1589								unspecifiedPrefixMapKey: {
1590									srcTypeArr: [3]*sourcePrefixes{
1591										{
1592											srcPrefixMap: map[string]*sourcePrefixEntry{
1593												unspecifiedPrefixMapKey: {
1594													srcPortMap: map[int]*FilterChain{
1595														0: {
1596															SecurityCfg: &SecurityConfig{
1597																RootInstanceName:     "rootPluginInstance",
1598																RootCertName:         "rootCertName",
1599																IdentityInstanceName: "identityPluginInstance",
1600																IdentityCertName:     "identityCertName",
1601																RequireClientCert:    true,
1602															},
1603															InlineRouteConfig: inlineRouteConfig,
1604															HTTPFilters:       routerFilterList,
1605														},
1606													},
1607												},
1608											},
1609										},
1610									},
1611								},
1612							},
1613							def: &FilterChain{
1614								SecurityCfg: &SecurityConfig{
1615									RootInstanceName:     "defaultRootPluginInstance",
1616									RootCertName:         "defaultRootCertName",
1617									IdentityInstanceName: "defaultIdentityPluginInstance",
1618									IdentityCertName:     "defaultIdentityCertName",
1619									RequireClientCert:    true,
1620								},
1621								InlineRouteConfig: inlineRouteConfig,
1622								HTTPFilters:       routerFilterList,
1623							},
1624						},
1625					},
1626					Raw: listenerWithValidationContextNewFields,
1627				}},
1628			},
1629			wantMD: UpdateMetadata{
1630				Status:  ServiceStatusACKed,
1631				Version: testVersion,
1632			},
1633		},
1634	}
1635
1636	for _, test := range tests {
1637		t.Run(test.name, func(t *testing.T) {
1638			gotUpdate, md, err := UnmarshalListener(testVersion, test.resources, nil)
1639			if (err != nil) != (test.wantErr != "") {
1640				t.Fatalf("UnmarshalListener(), got err: %v, wantErr: %v", err, test.wantErr)
1641			}
1642			if err != nil && !strings.Contains(err.Error(), test.wantErr) {
1643				t.Fatalf("UnmarshalListener() = %v wantErr: %q", err, test.wantErr)
1644			}
1645			if diff := cmp.Diff(gotUpdate, test.wantUpdate, cmpOpts); diff != "" {
1646				t.Errorf("got unexpected update, diff (-got +want): %v", diff)
1647			}
1648			if diff := cmp.Diff(md, test.wantMD, cmpOptsIgnoreDetails); diff != "" {
1649				t.Errorf("got unexpected metadata, diff (-got +want): %v", diff)
1650			}
1651		})
1652	}
1653}
1654
1655type filterConfig struct {
1656	httpfilter.FilterConfig
1657	Cfg      proto.Message
1658	Override proto.Message
1659}
1660
1661// httpFilter allows testing the http filter registry and parsing functionality.
1662type httpFilter struct {
1663	httpfilter.ClientInterceptorBuilder
1664	httpfilter.ServerInterceptorBuilder
1665}
1666
1667func (httpFilter) TypeURLs() []string { return []string{"custom.filter"} }
1668
1669func (httpFilter) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) {
1670	return filterConfig{Cfg: cfg}, nil
1671}
1672
1673func (httpFilter) ParseFilterConfigOverride(override proto.Message) (httpfilter.FilterConfig, error) {
1674	return filterConfig{Override: override}, nil
1675}
1676
1677func (httpFilter) IsTerminal() bool {
1678	return false
1679}
1680
1681// errHTTPFilter returns errors no matter what is passed to ParseFilterConfig.
1682type errHTTPFilter struct {
1683	httpfilter.ClientInterceptorBuilder
1684}
1685
1686func (errHTTPFilter) TypeURLs() []string { return []string{"err.custom.filter"} }
1687
1688func (errHTTPFilter) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) {
1689	return nil, fmt.Errorf("error from ParseFilterConfig")
1690}
1691
1692func (errHTTPFilter) ParseFilterConfigOverride(override proto.Message) (httpfilter.FilterConfig, error) {
1693	return nil, fmt.Errorf("error from ParseFilterConfigOverride")
1694}
1695
1696func (errHTTPFilter) IsTerminal() bool {
1697	return false
1698}
1699
1700func init() {
1701	httpfilter.Register(httpFilter{})
1702	httpfilter.Register(errHTTPFilter{})
1703	httpfilter.Register(serverOnlyHTTPFilter{})
1704	httpfilter.Register(clientOnlyHTTPFilter{})
1705}
1706
1707// serverOnlyHTTPFilter does not implement ClientInterceptorBuilder
1708type serverOnlyHTTPFilter struct {
1709	httpfilter.ServerInterceptorBuilder
1710}
1711
1712func (serverOnlyHTTPFilter) TypeURLs() []string { return []string{"serverOnly.custom.filter"} }
1713
1714func (serverOnlyHTTPFilter) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) {
1715	return filterConfig{Cfg: cfg}, nil
1716}
1717
1718func (serverOnlyHTTPFilter) ParseFilterConfigOverride(override proto.Message) (httpfilter.FilterConfig, error) {
1719	return filterConfig{Override: override}, nil
1720}
1721
1722func (serverOnlyHTTPFilter) IsTerminal() bool {
1723	return false
1724}
1725
1726// clientOnlyHTTPFilter does not implement ServerInterceptorBuilder
1727type clientOnlyHTTPFilter struct {
1728	httpfilter.ClientInterceptorBuilder
1729}
1730
1731func (clientOnlyHTTPFilter) TypeURLs() []string { return []string{"clientOnly.custom.filter"} }
1732
1733func (clientOnlyHTTPFilter) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) {
1734	return filterConfig{Cfg: cfg}, nil
1735}
1736
1737func (clientOnlyHTTPFilter) ParseFilterConfigOverride(override proto.Message) (httpfilter.FilterConfig, error) {
1738	return filterConfig{Override: override}, nil
1739}
1740
1741func (clientOnlyHTTPFilter) IsTerminal() bool {
1742	return false
1743}
1744
1745var customFilterConfig = &anypb.Any{
1746	TypeUrl: "custom.filter",
1747	Value:   []byte{1, 2, 3},
1748}
1749
1750var errFilterConfig = &anypb.Any{
1751	TypeUrl: "err.custom.filter",
1752	Value:   []byte{1, 2, 3},
1753}
1754
1755var serverOnlyCustomFilterConfig = &anypb.Any{
1756	TypeUrl: "serverOnly.custom.filter",
1757	Value:   []byte{1, 2, 3},
1758}
1759
1760var clientOnlyCustomFilterConfig = &anypb.Any{
1761	TypeUrl: "clientOnly.custom.filter",
1762	Value:   []byte{1, 2, 3},
1763}
1764
1765var customFilterTypedStructConfig = &v1typepb.TypedStruct{
1766	TypeUrl: "custom.filter",
1767	Value: &spb.Struct{
1768		Fields: map[string]*spb.Value{
1769			"foo": {Kind: &spb.Value_StringValue{StringValue: "bar"}},
1770		},
1771	},
1772}
1773var wrappedCustomFilterTypedStructConfig *anypb.Any
1774
1775func init() {
1776	wrappedCustomFilterTypedStructConfig = testutils.MarshalAny(customFilterTypedStructConfig)
1777}
1778
1779var unknownFilterConfig = &anypb.Any{
1780	TypeUrl: "unknown.custom.filter",
1781	Value:   []byte{1, 2, 3},
1782}
1783
1784func wrappedOptionalFilter(name string) *anypb.Any {
1785	return testutils.MarshalAny(&v3routepb.FilterConfig{
1786		IsOptional: true,
1787		Config: &anypb.Any{
1788			TypeUrl: name,
1789			Value:   []byte{1, 2, 3},
1790		},
1791	})
1792}
1793