blob: e59e2386cc1bf8f3091ab222f0c47469bee5ce1e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
*
* Copyright 2021 gRPC authors.
*
*/
package resource
import (
"errors"
"fmt"
"net"
)
import (
dubbogoLogger "github.com/dubbogo/gost/log/logger"
v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
)
import (
"dubbo.apache.org/dubbo-go/v3/xds/client/resource/version"
"dubbo.apache.org/dubbo-go/v3/xds/httpfilter"
"dubbo.apache.org/dubbo-go/v3/xds/utils/envconfig"
"dubbo.apache.org/dubbo-go/v3/xds/utils/resolver"
)
const (
// Used as the map key for unspecified prefixes. The actual value of this
// key is immaterial.
unspecifiedPrefixMapKey = "unspecified"
// An unspecified destination or source prefix should be considered a less
// specific match than a wildcard prefix, `0.0.0.0/0` or `::/0`. Also, an
// unspecified prefix should match most v4 and v6 addresses compared to the
// wildcard prefixes which match only a specific network (v4 or v6).
//
// We use these constants when looking up the most specific prefix match. A
// wildcard prefix will match 0 bits, and to make sure that a wildcard
// prefix is considered a more specific match than an unspecified prefix, we
// use a value of -1 for the latter.
noPrefixMatch = -2
unspecifiedPrefixMatch = -1
)
// FilterChain captures information from within a FilterChain message in a
// Listener resource.
type FilterChain struct {
// SecurityCfg contains transport socket security configuration.
SecurityCfg *SecurityConfig
// HTTPFilters represent the HTTP Filters that comprise this FilterChain.
HTTPFilters []HTTPFilter
// RouteConfigName is the route configuration name for this FilterChain.
//
// Exactly one of RouteConfigName and InlineRouteConfig is set.
RouteConfigName string
// InlineRouteConfig is the inline route configuration (RDS response)
// returned for this filter chain.
//
// Exactly one of RouteConfigName and InlineRouteConfig is set.
InlineRouteConfig *RouteConfigUpdate
}
// VirtualHostWithInterceptors captures information present in a VirtualHost
// update, and also contains routes with instantiated HTTP Filters.
type VirtualHostWithInterceptors struct {
// Domains are the domain names which map to this Virtual Host. On the
// server side, this will be dictated by the :authority header of the
// incoming RPC.
Domains []string
// Routes are the Routes for this Virtual Host.
Routes []RouteWithInterceptors
}
// RouteWithInterceptors captures information in a Route, and contains
// a usable matcher and also instantiated HTTP Filters.
type RouteWithInterceptors struct {
// M is the matcher used to match to this route.
M *CompositeMatcher
// ActionType is the type of routing action to initiate once matched to.
ActionType RouteActionType
// Interceptors are interceptors instantiated for this route. These will be
// constructed from a combination of the top level configuration and any
// HTTP Filter overrides present in Virtual Host or Route.
Interceptors []resolver.ServerInterceptor
}
// ConstructUsableRouteConfiguration takes Route Configuration and converts it
// into matchable route configuration, with instantiated HTTP Filters per route.
func (f *FilterChain) ConstructUsableRouteConfiguration(config RouteConfigUpdate) ([]VirtualHostWithInterceptors, error) {
vhs := make([]VirtualHostWithInterceptors, len(config.VirtualHosts))
for _, vh := range config.VirtualHosts {
vhwi, err := f.convertVirtualHost(vh)
if err != nil {
return nil, fmt.Errorf("virtual host construction: %v", err)
}
vhs = append(vhs, vhwi)
}
return vhs, nil
}
func (f *FilterChain) convertVirtualHost(virtualHost *VirtualHost) (VirtualHostWithInterceptors, error) {
rs := make([]RouteWithInterceptors, len(virtualHost.Routes))
for i, r := range virtualHost.Routes {
var err error
rs[i].ActionType = r.ActionType
rs[i].M, err = RouteToMatcher(r)
if err != nil {
return VirtualHostWithInterceptors{}, fmt.Errorf("matcher construction: %v", err)
}
for _, filter := range f.HTTPFilters {
// Route is highest priority on server side, as there is no concept
// of an upstream cluster on server side.
override := r.HTTPFilterConfigOverride[filter.Name]
if override == nil {
// Virtual Host is second priority.
override = virtualHost.HTTPFilterConfigOverride[filter.Name]
}
sb, ok := filter.Filter.(httpfilter.ServerInterceptorBuilder)
if !ok {
// Should not happen if it passed xdsClient validation.
return VirtualHostWithInterceptors{}, fmt.Errorf("filter does not support use in server")
}
si, err := sb.BuildServerInterceptor(filter.Config, override)
if err != nil {
return VirtualHostWithInterceptors{}, fmt.Errorf("filter construction: %v", err)
}
if si != nil {
rs[i].Interceptors = append(rs[i].Interceptors, si)
}
}
}
return VirtualHostWithInterceptors{Domains: virtualHost.Domains, Routes: rs}, nil
}
// SourceType specifies the connection source IP match type.
type SourceType int
const (
// SourceTypeAny matches connection attempts from any source.
SourceTypeAny SourceType = iota
// SourceTypeSameOrLoopback matches connection attempts from the same host.
SourceTypeSameOrLoopback
// SourceTypeExternal matches connection attempts from a different host.
SourceTypeExternal
)
// FilterChainManager contains all the match criteria specified through all
// filter chains in a single Listener resource. It also contains the default
// filter chain specified in the Listener resource. It provides two important
// pieces of functionality:
// 1. Validate the filter chains in an incoming Listener resource to make sure
// that there aren't filter chains which contain the same match criteria.
// 2. As part of performing the above validation, it builds an internal data
// structure which will if used to look up the matching filter chain at
// connection time.
//
// The logic specified in the documentation around the xDS FilterChainMatch
// proto mentions 8 criteria to match on.
// The following order applies:
//
// 1. Destination port.
// 2. Destination IP address.
// 3. Server name (e.g. SNI for TLS protocol),
// 4. Transport protocol.
// 5. Application protocols (e.g. ALPN for TLS protocol).
// 6. Source type (e.g. any, local or external network).
// 7. Source IP address.
// 8. Source port.
type FilterChainManager struct {
logger dubbogoLogger.Logger
// Destination prefix is the first match criteria that we support.
// Therefore, this multi-stage map is indexed on destination prefixes
// specified in the match criteria.
// Unspecified destination prefix matches end up as a wildcard entry here
// with a key of 0.0.0.0/0.
dstPrefixMap map[string]*destPrefixEntry
// At connection time, we do not have the actual destination prefix to match
// on. We only have the real destination address of the incoming connection.
// This means that we cannot use the above map at connection time. This list
// contains the map entries from the above map that we can use at connection
// time to find matching destination prefixes in O(n) time.
//
// TODO: Implement LC-trie to support logarithmic time lookups. If that
// involves too much time/effort, sort this slice based on the netmask size.
dstPrefixes []*destPrefixEntry
def *FilterChain // Default filter chain, if specified.
// RouteConfigNames are the route configuration names which need to be
// dynamically queried for RDS Configuration for any FilterChains which
// specify to load RDS Configuration dynamically.
RouteConfigNames map[string]bool
}
// destPrefixEntry is the value type of the map indexed on destination prefixes.
type destPrefixEntry struct {
// The actual destination prefix. Set to nil for unspecified prefixes.
net *net.IPNet
// We need to keep track of the transport protocols seen as part of the
// config validation (and internal structure building) phase. The only two
// values that we support are empty string and "raw_buffer", with the latter
// taking preference. Once we have seen one filter chain with "raw_buffer",
// we can drop everything other filter chain with an empty transport
// protocol.
rawBufferSeen bool
// For each specified source type in the filter chain match criteria, this
// array points to the set of specified source prefixes.
// Unspecified source type matches end up as a wildcard entry here with an
// index of 0, which actually represents the source type `ANY`.
srcTypeArr sourceTypesArray
}
// An array for the fixed number of source types that we have.
type sourceTypesArray [3]*sourcePrefixes
// sourcePrefixes contains source prefix related information specified in the
// match criteria. These are pointed to by the array of source types.
type sourcePrefixes struct {
// These are very similar to the 'dstPrefixMap' and 'dstPrefixes' field of
// FilterChainManager. Go there for more info.
srcPrefixMap map[string]*sourcePrefixEntry
srcPrefixes []*sourcePrefixEntry
}
// sourcePrefixEntry contains match criteria per source prefix.
type sourcePrefixEntry struct {
// The actual destination prefix. Set to nil for unspecified prefixes.
net *net.IPNet
// Mapping from source ports specified in the match criteria to the actual
// filter chain. Unspecified source port matches en up as a wildcard entry
// here with a key of 0.
srcPortMap map[int]*FilterChain
}
// NewFilterChainManager parses the received Listener resource and builds a
// FilterChainManager. Returns a non-nil error on validation failures.
//
// This function is only exported so that tests outside of this package can
// create a FilterChainManager.
func NewFilterChainManager(lis *v3listenerpb.Listener, logger dubbogoLogger.Logger) (*FilterChainManager, error) {
// Parse all the filter chains and build the internal data structures.
fci := &FilterChainManager{
logger: logger,
dstPrefixMap: make(map[string]*destPrefixEntry),
RouteConfigNames: make(map[string]bool),
}
if err := fci.addFilterChains(lis.GetFilterChains()); err != nil {
return nil, err
}
// Build the source and dest prefix slices used by Lookup().
fcSeen := false
for _, dstPrefix := range fci.dstPrefixMap {
fci.dstPrefixes = append(fci.dstPrefixes, dstPrefix)
for _, st := range dstPrefix.srcTypeArr {
if st == nil {
continue
}
for _, srcPrefix := range st.srcPrefixMap {
st.srcPrefixes = append(st.srcPrefixes, srcPrefix)
for _, fc := range srcPrefix.srcPortMap {
if fc != nil {
fcSeen = true
}
}
}
}
}
// Retrieve the default filter chain. The match criteria specified on the
// default filter chain is never used. The default filter chain simply gets
// used when none of the other filter chains match.
var def *FilterChain
if dfc := lis.GetDefaultFilterChain(); dfc != nil {
var err error
if def, err = fci.filterChainFromProto(dfc); err != nil {
return nil, err
}
}
fci.def = def
// If there are no supported filter chains and no default filter chain, we
// fail here. This will call the Listener resource to be NACK'ed.
if !fcSeen && fci.def == nil {
return nil, fmt.Errorf("no supported filter chains and no default filter chain")
}
return fci, nil
}
// addFilterChains parses the filter chains in fcs and adds the required
// internal data structures corresponding to the match criteria.
func (fci *FilterChainManager) addFilterChains(fcs []*v3listenerpb.FilterChain) error {
for _, fc := range fcs {
fcm := fc.GetFilterChainMatch()
if fcm.GetDestinationPort().GetValue() != 0 {
// Destination port is the first match criteria and we do not
// support filter chains which contains this match criteria.
fci.logger.Warnf("Dropping filter chain %+v since it contains unsupported destination_port match field", fc)
continue
}
// Build the internal representation of the filter chain match fields.
if err := fci.addFilterChainsForDestPrefixes(fc); err != nil {
return err
}
}
return nil
}
func (fci *FilterChainManager) addFilterChainsForDestPrefixes(fc *v3listenerpb.FilterChain) error {
ranges := fc.GetFilterChainMatch().GetPrefixRanges()
dstPrefixes := make([]*net.IPNet, 0, len(ranges))
for _, pr := range ranges {
cidr := fmt.Sprintf("%s/%d", pr.GetAddressPrefix(), pr.GetPrefixLen().GetValue())
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
return fmt.Errorf("failed to parse destination prefix range: %+v", pr)
}
dstPrefixes = append(dstPrefixes, ipnet)
}
if len(dstPrefixes) == 0 {
// Use the unspecified entry when destination prefix is unspecified, and
// set the `net` field to nil.
if fci.dstPrefixMap[unspecifiedPrefixMapKey] == nil {
fci.dstPrefixMap[unspecifiedPrefixMapKey] = &destPrefixEntry{}
}
return fci.addFilterChainsForServerNames(fci.dstPrefixMap[unspecifiedPrefixMapKey], fc)
}
for _, prefix := range dstPrefixes {
p := prefix.String()
if fci.dstPrefixMap[p] == nil {
fci.dstPrefixMap[p] = &destPrefixEntry{net: prefix}
}
if err := fci.addFilterChainsForServerNames(fci.dstPrefixMap[p], fc); err != nil {
return err
}
}
return nil
}
func (fci *FilterChainManager) addFilterChainsForServerNames(dstEntry *destPrefixEntry, fc *v3listenerpb.FilterChain) error {
// Filter chains specifying server names in their match criteria always fail
// a match at connection time. So, these filter chains can be dropped now.
if len(fc.GetFilterChainMatch().GetServerNames()) != 0 {
fci.logger.Warnf("Dropping filter chain %+v since it contains unsupported server_names match field", fc)
return nil
}
return fci.addFilterChainsForTransportProtocols(dstEntry, fc)
}
func (fci *FilterChainManager) addFilterChainsForTransportProtocols(dstEntry *destPrefixEntry, fc *v3listenerpb.FilterChain) error {
tp := fc.GetFilterChainMatch().GetTransportProtocol()
switch {
case tp != "" && tp != "raw_buffer":
// Only allow filter chains with transport protocol set to empty string
// or "raw_buffer".
fci.logger.Warnf("Dropping filter chain %+v since it contains unsupported value for transport_protocols match field", fc)
return nil
case tp == "" && dstEntry.rawBufferSeen:
// If we have already seen filter chains with transport protocol set to
// "raw_buffer", we can drop filter chains with transport protocol set
// to empty string, since the former takes precedence.
fci.logger.Warnf("Dropping filter chain %+v since it contains unsupported value for transport_protocols match field", fc)
return nil
case tp != "" && !dstEntry.rawBufferSeen:
// This is the first "raw_buffer" that we are seeing. Set the bit and
// reset the source types array which might contain entries for filter
// chains with transport protocol set to empty string.
dstEntry.rawBufferSeen = true
dstEntry.srcTypeArr = sourceTypesArray{}
}
return fci.addFilterChainsForApplicationProtocols(dstEntry, fc)
}
func (fci *FilterChainManager) addFilterChainsForApplicationProtocols(dstEntry *destPrefixEntry, fc *v3listenerpb.FilterChain) error {
if len(fc.GetFilterChainMatch().GetApplicationProtocols()) != 0 {
fci.logger.Warnf("Dropping filter chain %+v since it contains unsupported application_protocols match field", fc)
return nil
}
return fci.addFilterChainsForSourceType(dstEntry, fc)
}
// addFilterChainsForSourceType adds source types to the internal data
// structures and delegates control to addFilterChainsForSourcePrefixes to
// continue building the internal data structure.
func (fci *FilterChainManager) addFilterChainsForSourceType(dstEntry *destPrefixEntry, fc *v3listenerpb.FilterChain) error {
var srcType SourceType
switch st := fc.GetFilterChainMatch().GetSourceType(); st {
case v3listenerpb.FilterChainMatch_ANY:
srcType = SourceTypeAny
case v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK:
srcType = SourceTypeSameOrLoopback
case v3listenerpb.FilterChainMatch_EXTERNAL:
srcType = SourceTypeExternal
default:
return fmt.Errorf("unsupported source type: %v", st)
}
st := int(srcType)
if dstEntry.srcTypeArr[st] == nil {
dstEntry.srcTypeArr[st] = &sourcePrefixes{srcPrefixMap: make(map[string]*sourcePrefixEntry)}
}
return fci.addFilterChainsForSourcePrefixes(dstEntry.srcTypeArr[st].srcPrefixMap, fc)
}
// addFilterChainsForSourcePrefixes adds source prefixes to the internal data
// structures and delegates control to addFilterChainsForSourcePorts to continue
// building the internal data structure.
func (fci *FilterChainManager) addFilterChainsForSourcePrefixes(srcPrefixMap map[string]*sourcePrefixEntry, fc *v3listenerpb.FilterChain) error {
ranges := fc.GetFilterChainMatch().GetSourcePrefixRanges()
srcPrefixes := make([]*net.IPNet, 0, len(ranges))
for _, pr := range fc.GetFilterChainMatch().GetSourcePrefixRanges() {
cidr := fmt.Sprintf("%s/%d", pr.GetAddressPrefix(), pr.GetPrefixLen().GetValue())
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
return fmt.Errorf("failed to parse source prefix range: %+v", pr)
}
srcPrefixes = append(srcPrefixes, ipnet)
}
if len(srcPrefixes) == 0 {
// Use the unspecified entry when destination prefix is unspecified, and
// set the `net` field to nil.
if srcPrefixMap[unspecifiedPrefixMapKey] == nil {
srcPrefixMap[unspecifiedPrefixMapKey] = &sourcePrefixEntry{
srcPortMap: make(map[int]*FilterChain),
}
}
return fci.addFilterChainsForSourcePorts(srcPrefixMap[unspecifiedPrefixMapKey], fc)
}
for _, prefix := range srcPrefixes {
p := prefix.String()
if srcPrefixMap[p] == nil {
srcPrefixMap[p] = &sourcePrefixEntry{
net: prefix,
srcPortMap: make(map[int]*FilterChain),
}
}
if err := fci.addFilterChainsForSourcePorts(srcPrefixMap[p], fc); err != nil {
return err
}
}
return nil
}
// addFilterChainsForSourcePorts adds source ports to the internal data
// structures and completes the process of building the internal data structure.
// It is here that we determine if there are multiple filter chains with
// overlapping matching rules.
func (fci *FilterChainManager) addFilterChainsForSourcePorts(srcEntry *sourcePrefixEntry, fcProto *v3listenerpb.FilterChain) error {
ports := fcProto.GetFilterChainMatch().GetSourcePorts()
srcPorts := make([]int, 0, len(ports))
for _, port := range ports {
srcPorts = append(srcPorts, int(port))
}
fc, err := fci.filterChainFromProto(fcProto)
if err != nil {
return err
}
if len(srcPorts) == 0 {
// Use the wildcard port '0', when source ports are unspecified.
if curFC := srcEntry.srcPortMap[0]; curFC != nil {
return errors.New("multiple filter chains with overlapping matching rules are defined")
}
srcEntry.srcPortMap[0] = fc
return nil
}
for _, port := range srcPorts {
if curFC := srcEntry.srcPortMap[port]; curFC != nil {
return errors.New("multiple filter chains with overlapping matching rules are defined")
}
srcEntry.srcPortMap[port] = fc
}
return nil
}
// filterChainFromProto extracts the relevant information from the FilterChain
// proto and stores it in our internal representation. It also persists any
// RouteNames which need to be queried dynamically via RDS.
func (fci *FilterChainManager) filterChainFromProto(fc *v3listenerpb.FilterChain) (*FilterChain, error) {
filterChain, err := processNetworkFilters(fc.GetFilters())
if err != nil {
return nil, err
}
// These route names will be dynamically queried via RDS in the wrapped
// listener, which receives the LDS response, if specified for the filter
// chain.
if filterChain.RouteConfigName != "" {
fci.RouteConfigNames[filterChain.RouteConfigName] = true
}
// If the transport_socket field is not specified, it means that the control
// plane has not sent us any security config. This is fine and the server
// will use the fallback credentials configured as part of the
// xdsCredentials.
ts := fc.GetTransportSocket()
if ts == nil {
return filterChain, nil
}
if name := ts.GetName(); name != transportSocketName {
return nil, fmt.Errorf("transport_socket field has unexpected name: %s", name)
}
any := ts.GetTypedConfig()
if any == nil || any.TypeUrl != version.V3DownstreamTLSContextURL {
return nil, fmt.Errorf("transport_socket field has unexpected typeURL: %s", any.TypeUrl)
}
downstreamCtx := &v3tlspb.DownstreamTlsContext{}
if err = proto.Unmarshal(any.GetValue(), downstreamCtx); err != nil {
return nil, fmt.Errorf("failed to unmarshal DownstreamTlsContext in LDS response: %v", err)
}
if downstreamCtx.GetRequireSni().GetValue() {
return nil, fmt.Errorf("require_sni field set to true in DownstreamTlsContext message: %v", downstreamCtx)
}
if downstreamCtx.GetOcspStaplePolicy() != v3tlspb.DownstreamTlsContext_LENIENT_STAPLING {
return nil, fmt.Errorf("ocsp_staple_policy field set to unsupported value in DownstreamTlsContext message: %v", downstreamCtx)
}
// The following fields from `DownstreamTlsContext` are ignore:
// - disable_stateless_session_resumption
// - session_ticket_keys
// - session_ticket_keys_sds_secret_config
// - session_timeout
if downstreamCtx.GetCommonTlsContext() == nil {
return nil, errors.New("DownstreamTlsContext in LDS response does not contain a CommonTlsContext")
}
sc, err := securityConfigFromCommonTLSContext(downstreamCtx.GetCommonTlsContext(), true)
if err != nil {
return nil, err
}
if sc == nil {
// sc == nil is a valid case where the control plane has not sent us any
// security configuration. xDS creds will use fallback creds.
return filterChain, nil
}
sc.RequireClientCert = downstreamCtx.GetRequireClientCertificate().GetValue()
if sc.RequireClientCert && sc.RootInstanceName == "" {
return nil, errors.New("security configuration on the server-side does not contain root certificate provider instance name, but require_client_cert field is set")
}
filterChain.SecurityCfg = sc
return filterChain, nil
}
// Validate takes a function to validate the FilterChains in this manager.
func (fci *FilterChainManager) Validate(f func(fc *FilterChain) error) error {
for _, dst := range fci.dstPrefixMap {
for _, srcType := range dst.srcTypeArr {
if srcType == nil {
continue
}
for _, src := range srcType.srcPrefixMap {
for _, fc := range src.srcPortMap {
if err := f(fc); err != nil {
return err
}
}
}
}
}
return f(fci.def)
}
func processNetworkFilters(filters []*v3listenerpb.Filter) (*FilterChain, error) {
filterChain := &FilterChain{}
seenNames := make(map[string]bool, len(filters))
seenHCM := false
for _, filter := range filters {
name := filter.GetName()
if name == "" {
return nil, fmt.Errorf("network filters {%+v} is missing name field in filter: {%+v}", filters, filter)
}
if seenNames[name] {
return nil, fmt.Errorf("network filters {%+v} has duplicate filter name %q", filters, name)
}
seenNames[name] = true
// Network filters have a oneof field named `config_type` where we
// only support `TypedConfig` variant.
switch typ := filter.GetConfigType().(type) {
case *v3listenerpb.Filter_TypedConfig:
// The typed_config field has an `anypb.Any` proto which could
// directly contain the serialized bytes of the actual filter
// configuration, or it could be encoded as a `TypedStruct`.
// TODO: Add support for `TypedStruct`.
tc := filter.GetTypedConfig()
// The only network filter that we currently support is the v3
// HttpConnectionManager. So, we can directly check the type_url
// and unmarshal the config.
// TODO: Implement a registry of supported network filters (like
// we have for HTTP filters), when we have to support network
// filters other than HttpConnectionManager.
if tc.GetTypeUrl() != version.V3HTTPConnManagerURL {
return nil, fmt.Errorf("network filters {%+v} has unsupported network filter %q in filter {%+v}", filters, tc.GetTypeUrl(), filter)
}
hcm := &v3httppb.HttpConnectionManager{}
if err := ptypes.UnmarshalAny(tc, hcm); err != nil {
return nil, fmt.Errorf("network filters {%+v} failed unmarshaling of network filter {%+v}: %v", filters, filter, err)
}
// "Any filters after HttpConnectionManager should be ignored during
// connection processing but still be considered for validity.
// HTTPConnectionManager must have valid http_filters." - A36
filters, err := processHTTPFilters(hcm.GetHttpFilters(), true)
if err != nil {
return nil, fmt.Errorf("network filters {%+v} had invalid server side HTTP Filters {%+v}: %v", filters, hcm.GetHttpFilters(), err)
}
if !seenHCM {
// Validate for RBAC in only the HCM that will be used, since this isn't a logical validation failure,
// it's simply a validation to support RBAC HTTP Filter.
// "HttpConnectionManager.xff_num_trusted_hops must be unset or zero and
// HttpConnectionManager.original_ip_detection_extensions must be empty. If
// either field has an incorrect value, the Listener must be NACKed." - A41
if hcm.XffNumTrustedHops != 0 {
return nil, fmt.Errorf("xff_num_trusted_hops must be unset or zero %+v", hcm)
}
if len(hcm.OriginalIpDetectionExtensions) != 0 {
return nil, fmt.Errorf("original_ip_detection_extensions must be empty %+v", hcm)
}
// TODO: Implement terminal filter logic, as per A36.
filterChain.HTTPFilters = filters
seenHCM = true
if !envconfig.XDSRBAC {
continue
}
switch hcm.RouteSpecifier.(type) {
case *v3httppb.HttpConnectionManager_Rds:
if hcm.GetRds().GetConfigSource().GetAds() == nil {
return nil, fmt.Errorf("ConfigSource is not ADS: %+v", hcm)
}
name := hcm.GetRds().GetRouteConfigName()
if name == "" {
return nil, fmt.Errorf("empty route_config_name: %+v", hcm)
}
filterChain.RouteConfigName = name
case *v3httppb.HttpConnectionManager_RouteConfig:
// "RouteConfiguration validation logic inherits all
// previous validations made for client-side usage as RDS
// does not distinguish between client-side and
// server-side." - A36
// Can specify v3 here, as will never get to this function
// if v2.
routeU, err := generateRDSUpdateFromRouteConfiguration(hcm.GetRouteConfig(), nil, false)
if err != nil {
return nil, fmt.Errorf("failed to parse inline RDS resp: %v", err)
}
filterChain.InlineRouteConfig = &routeU
case nil:
return nil, fmt.Errorf("no RouteSpecifier: %+v", hcm)
default:
return nil, fmt.Errorf("unsupported type %T for RouteSpecifier", hcm.RouteSpecifier)
}
}
default:
return nil, fmt.Errorf("network filters {%+v} has unsupported config_type %T in filter %s", filters, typ, filter.GetName())
}
}
if !seenHCM {
return nil, fmt.Errorf("network filters {%+v} missing HttpConnectionManager filter", filters)
}
return filterChain, nil
}
// FilterChainLookupParams wraps parameters to be passed to Lookup.
type FilterChainLookupParams struct {
// IsUnspecified indicates whether the server is listening on a wildcard
// address, "0.0.0.0" for IPv4 and "::" for IPv6. Only when this is set to
// true, do we consider the destination prefixes specified in the filter
// chain match criteria.
IsUnspecifiedListener bool
// DestAddr is the local address of an incoming connection.
DestAddr net.IP
// SourceAddr is the remote address of an incoming connection.
SourceAddr net.IP
// SourcePort is the remote port of an incoming connection.
SourcePort int
}
// Lookup returns the most specific matching filter chain to be used for an
// incoming connection on the server side.
//
// Returns a non-nil error if no matching filter chain could be found or
// multiple matching filter chains were found, and in both cases, the incoming
// connection must be dropped.
func (fci *FilterChainManager) Lookup(params FilterChainLookupParams) (*FilterChain, error) {
dstPrefixes := filterByDestinationPrefixes(fci.dstPrefixes, params.IsUnspecifiedListener, params.DestAddr)
if len(dstPrefixes) == 0 {
if fci.def != nil {
return fci.def, nil
}
return nil, fmt.Errorf("no matching filter chain based on destination prefix match for %+v", params)
}
srcType := SourceTypeExternal
if params.SourceAddr.Equal(params.DestAddr) || params.SourceAddr.IsLoopback() {
srcType = SourceTypeSameOrLoopback
}
srcPrefixes := filterBySourceType(dstPrefixes, srcType)
if len(srcPrefixes) == 0 {
if fci.def != nil {
return fci.def, nil
}
return nil, fmt.Errorf("no matching filter chain based on source type match for %+v", params)
}
srcPrefixEntry, err := filterBySourcePrefixes(srcPrefixes, params.SourceAddr)
if err != nil {
return nil, err
}
if fc := filterBySourcePorts(srcPrefixEntry, params.SourcePort); fc != nil {
return fc, nil
}
if fci.def != nil {
return fci.def, nil
}
return nil, fmt.Errorf("no matching filter chain after all match criteria for %+v", params)
}
// filterByDestinationPrefixes is the first stage of the filter chain
// matching algorithm. It takes the complete set of configured filter chain
// matchers and returns the most specific matchers based on the destination
// prefix match criteria (the prefixes which match the most number of bits).
func filterByDestinationPrefixes(dstPrefixes []*destPrefixEntry, isUnspecified bool, dstAddr net.IP) []*destPrefixEntry {
if !isUnspecified {
// Destination prefix matchers are considered only when the listener is
// bound to the wildcard address.
return dstPrefixes
}
var matchingDstPrefixes []*destPrefixEntry
maxSubnetMatch := noPrefixMatch
for _, prefix := range dstPrefixes {
if prefix.net != nil && !prefix.net.Contains(dstAddr) {
// Skip prefixes which don't match.
continue
}
// For unspecified prefixes, since we do not store a real net.IPNet
// inside prefix, we do not perform a match. Instead we simply set
// the matchSize to -1, which is less than the matchSize (0) for a
// wildcard prefix.
matchSize := unspecifiedPrefixMatch
if prefix.net != nil {
matchSize, _ = prefix.net.Mask.Size()
}
if matchSize < maxSubnetMatch {
continue
}
if matchSize > maxSubnetMatch {
maxSubnetMatch = matchSize
matchingDstPrefixes = make([]*destPrefixEntry, 0, 1)
}
matchingDstPrefixes = append(matchingDstPrefixes, prefix)
}
return matchingDstPrefixes
}
// filterBySourceType is the second stage of the matching algorithm. It
// trims the filter chains based on the most specific source type match.
func filterBySourceType(dstPrefixes []*destPrefixEntry, srcType SourceType) []*sourcePrefixes {
var (
srcPrefixes []*sourcePrefixes
bestSrcTypeMatch int
)
for _, prefix := range dstPrefixes {
var (
srcPrefix *sourcePrefixes
match int
)
switch srcType {
case SourceTypeExternal:
match = int(SourceTypeExternal)
srcPrefix = prefix.srcTypeArr[match]
case SourceTypeSameOrLoopback:
match = int(SourceTypeSameOrLoopback)
srcPrefix = prefix.srcTypeArr[match]
}
if srcPrefix == nil {
match = int(SourceTypeAny)
srcPrefix = prefix.srcTypeArr[match]
}
if match < bestSrcTypeMatch {
continue
}
if match > bestSrcTypeMatch {
bestSrcTypeMatch = match
srcPrefixes = make([]*sourcePrefixes, 0)
}
if srcPrefix != nil {
// The source type array always has 3 entries, but these could be
// nil if the appropriate source type match was not specified.
srcPrefixes = append(srcPrefixes, srcPrefix)
}
}
return srcPrefixes
}
// filterBySourcePrefixes is the third stage of the filter chain matching
// algorithm. It trims the filter chains based on the source prefix. At most one
// filter chain with the most specific match progress to the next stage.
func filterBySourcePrefixes(srcPrefixes []*sourcePrefixes, srcAddr net.IP) (*sourcePrefixEntry, error) {
var matchingSrcPrefixes []*sourcePrefixEntry
maxSubnetMatch := noPrefixMatch
for _, sp := range srcPrefixes {
for _, prefix := range sp.srcPrefixes {
if prefix.net != nil && !prefix.net.Contains(srcAddr) {
// Skip prefixes which don't match.
continue
}
// For unspecified prefixes, since we do not store a real net.IPNet
// inside prefix, we do not perform a match. Instead we simply set
// the matchSize to -1, which is less than the matchSize (0) for a
// wildcard prefix.
matchSize := unspecifiedPrefixMatch
if prefix.net != nil {
matchSize, _ = prefix.net.Mask.Size()
}
if matchSize < maxSubnetMatch {
continue
}
if matchSize > maxSubnetMatch {
maxSubnetMatch = matchSize
matchingSrcPrefixes = make([]*sourcePrefixEntry, 0, 1)
}
matchingSrcPrefixes = append(matchingSrcPrefixes, prefix)
}
}
if len(matchingSrcPrefixes) == 0 {
// Finding no match is not an error condition. The caller will end up
// using the default filter chain if one was configured.
return nil, nil
}
// We expect at most a single matching source prefix entry at this point. If
// we have multiple entries here, and some of their source port matchers had
// wildcard entries, we could be left with more than one matching filter
// chain and hence would have been flagged as an invalid configuration at
// config validation time.
if len(matchingSrcPrefixes) != 1 {
return nil, errors.New("multiple matching filter chains")
}
return matchingSrcPrefixes[0], nil
}
// filterBySourcePorts is the last stage of the filter chain matching
// algorithm. It trims the filter chains based on the source ports.
func filterBySourcePorts(spe *sourcePrefixEntry, srcPort int) *FilterChain {
if spe == nil {
return nil
}
// A match could be a wildcard match (this happens when the match
// criteria does not specify source ports) or a specific port match (this
// happens when the match criteria specifies a set of ports and the source
// port of the incoming connection matches one of the specified ports). The
// latter is considered to be a more specific match.
if fc := spe.srcPortMap[srcPort]; fc != nil {
return fc
}
if fc := spe.srcPortMap[0]; fc != nil {
return fc
}
return nil
}