google.golang.org/grpc@v1.72.2/xds/internal/xdsclient/xdsresource/matcher.go (about)

     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  package xdsresource
    19  
    20  import (
    21  	"fmt"
    22  	rand "math/rand/v2"
    23  	"strings"
    24  
    25  	"google.golang.org/grpc/internal/grpcutil"
    26  	iresolver "google.golang.org/grpc/internal/resolver"
    27  	"google.golang.org/grpc/internal/xds/matcher"
    28  	"google.golang.org/grpc/metadata"
    29  )
    30  
    31  // RouteToMatcher converts a route to a Matcher to match incoming RPC's against.
    32  //
    33  // Only expected to be called on a Route that passed validation checks by the
    34  // xDS client.
    35  func RouteToMatcher(r *Route) *CompositeMatcher {
    36  	var pm pathMatcher
    37  	switch {
    38  	case r.Regex != nil:
    39  		pm = newPathRegexMatcher(r.Regex)
    40  	case r.Path != nil:
    41  		pm = newPathExactMatcher(*r.Path, r.CaseInsensitive)
    42  	case r.Prefix != nil:
    43  		pm = newPathPrefixMatcher(*r.Prefix, r.CaseInsensitive)
    44  	default:
    45  		panic("illegal route: missing path_matcher")
    46  	}
    47  
    48  	headerMatchers := make([]matcher.HeaderMatcher, 0, len(r.Headers))
    49  	for _, h := range r.Headers {
    50  		var matcherT matcher.HeaderMatcher
    51  		invert := h.InvertMatch != nil && *h.InvertMatch
    52  		switch {
    53  		case h.ExactMatch != nil && *h.ExactMatch != "":
    54  			matcherT = matcher.NewHeaderExactMatcher(h.Name, *h.ExactMatch, invert)
    55  		case h.RegexMatch != nil:
    56  			matcherT = matcher.NewHeaderRegexMatcher(h.Name, h.RegexMatch, invert)
    57  		case h.PrefixMatch != nil && *h.PrefixMatch != "":
    58  			matcherT = matcher.NewHeaderPrefixMatcher(h.Name, *h.PrefixMatch, invert)
    59  		case h.SuffixMatch != nil && *h.SuffixMatch != "":
    60  			matcherT = matcher.NewHeaderSuffixMatcher(h.Name, *h.SuffixMatch, invert)
    61  		case h.RangeMatch != nil:
    62  			matcherT = matcher.NewHeaderRangeMatcher(h.Name, h.RangeMatch.Start, h.RangeMatch.End, invert)
    63  		case h.PresentMatch != nil:
    64  			matcherT = matcher.NewHeaderPresentMatcher(h.Name, *h.PresentMatch, invert)
    65  		case h.StringMatch != nil:
    66  			matcherT = matcher.NewHeaderStringMatcher(h.Name, *h.StringMatch, invert)
    67  		default:
    68  			panic("illegal route: missing header_match_specifier")
    69  		}
    70  		headerMatchers = append(headerMatchers, matcherT)
    71  	}
    72  
    73  	var fractionMatcher *fractionMatcher
    74  	if r.Fraction != nil {
    75  		fractionMatcher = newFractionMatcher(*r.Fraction)
    76  	}
    77  	return newCompositeMatcher(pm, headerMatchers, fractionMatcher)
    78  }
    79  
    80  // CompositeMatcher is a matcher that holds onto many matchers and aggregates
    81  // the matching results.
    82  type CompositeMatcher struct {
    83  	pm  pathMatcher
    84  	hms []matcher.HeaderMatcher
    85  	fm  *fractionMatcher
    86  }
    87  
    88  func newCompositeMatcher(pm pathMatcher, hms []matcher.HeaderMatcher, fm *fractionMatcher) *CompositeMatcher {
    89  	return &CompositeMatcher{pm: pm, hms: hms, fm: fm}
    90  }
    91  
    92  // Match returns true if all matchers return true.
    93  func (a *CompositeMatcher) Match(info iresolver.RPCInfo) bool {
    94  	if a.pm != nil && !a.pm.match(info.Method) {
    95  		return false
    96  	}
    97  
    98  	// Call headerMatchers even if md is nil, because routes may match
    99  	// non-presence of some headers.
   100  	var md metadata.MD
   101  	if info.Context != nil {
   102  		md, _ = metadata.FromOutgoingContext(info.Context)
   103  		if extraMD, ok := grpcutil.ExtraMetadata(info.Context); ok {
   104  			md = metadata.Join(md, extraMD)
   105  			// Remove all binary headers. They are hard to match with. May need
   106  			// to add back if asked by users.
   107  			for k := range md {
   108  				if strings.HasSuffix(k, "-bin") {
   109  					delete(md, k)
   110  				}
   111  			}
   112  		}
   113  	}
   114  	for _, m := range a.hms {
   115  		if !m.Match(md) {
   116  			return false
   117  		}
   118  	}
   119  
   120  	if a.fm != nil && !a.fm.match() {
   121  		return false
   122  	}
   123  	return true
   124  }
   125  
   126  func (a *CompositeMatcher) String() string {
   127  	var ret string
   128  	if a.pm != nil {
   129  		ret += a.pm.String()
   130  	}
   131  	for _, m := range a.hms {
   132  		ret += m.String()
   133  	}
   134  	if a.fm != nil {
   135  		ret += a.fm.String()
   136  	}
   137  	return ret
   138  }
   139  
   140  type fractionMatcher struct {
   141  	fraction int64 // real fraction is fraction/1,000,000.
   142  }
   143  
   144  func newFractionMatcher(fraction uint32) *fractionMatcher {
   145  	return &fractionMatcher{fraction: int64(fraction)}
   146  }
   147  
   148  // RandInt64n overwrites rand for control in tests.
   149  var RandInt64n = rand.Int64N
   150  
   151  func (fm *fractionMatcher) match() bool {
   152  	t := RandInt64n(1000000)
   153  	return t <= fm.fraction
   154  }
   155  
   156  func (fm *fractionMatcher) String() string {
   157  	return fmt.Sprintf("fraction:%v", fm.fraction)
   158  }
   159  
   160  type domainMatchType int
   161  
   162  const (
   163  	domainMatchTypeInvalid domainMatchType = iota
   164  	domainMatchTypeUniversal
   165  	domainMatchTypePrefix
   166  	domainMatchTypeSuffix
   167  	domainMatchTypeExact
   168  )
   169  
   170  // Exact > Suffix > Prefix > Universal > Invalid.
   171  func (t domainMatchType) betterThan(b domainMatchType) bool {
   172  	return t > b
   173  }
   174  
   175  func matchTypeForDomain(d string) domainMatchType {
   176  	if d == "" {
   177  		return domainMatchTypeInvalid
   178  	}
   179  	if d == "*" {
   180  		return domainMatchTypeUniversal
   181  	}
   182  	if strings.HasPrefix(d, "*") {
   183  		return domainMatchTypeSuffix
   184  	}
   185  	if strings.HasSuffix(d, "*") {
   186  		return domainMatchTypePrefix
   187  	}
   188  	if strings.Contains(d, "*") {
   189  		return domainMatchTypeInvalid
   190  	}
   191  	return domainMatchTypeExact
   192  }
   193  
   194  func match(domain, host string) (domainMatchType, bool) {
   195  	switch typ := matchTypeForDomain(domain); typ {
   196  	case domainMatchTypeInvalid:
   197  		return typ, false
   198  	case domainMatchTypeUniversal:
   199  		return typ, true
   200  	case domainMatchTypePrefix:
   201  		// abc.*
   202  		return typ, strings.HasPrefix(host, strings.TrimSuffix(domain, "*"))
   203  	case domainMatchTypeSuffix:
   204  		// *.123
   205  		return typ, strings.HasSuffix(host, strings.TrimPrefix(domain, "*"))
   206  	case domainMatchTypeExact:
   207  		return typ, domain == host
   208  	default:
   209  		return domainMatchTypeInvalid, false
   210  	}
   211  }
   212  
   213  // FindBestMatchingVirtualHost returns the virtual host whose domains field best
   214  // matches host
   215  //
   216  //	The domains field support 4 different matching pattern types:
   217  //
   218  //	- Exact match
   219  //	- Suffix match (e.g. “*ABC”)
   220  //	- Prefix match (e.g. “ABC*)
   221  //	- Universal match (e.g. “*”)
   222  //
   223  //	The best match is defined as:
   224  //	- A match is better if it’s matching pattern type is better.
   225  //	  * Exact match > suffix match > prefix match > universal match.
   226  //
   227  //	- If two matches are of the same pattern type, the longer match is
   228  //	  better.
   229  //	  * This is to compare the length of the matching pattern, e.g. “*ABCDE” >
   230  //	    “*ABC”
   231  func FindBestMatchingVirtualHost(host string, vHosts []*VirtualHost) *VirtualHost { // Maybe move this crap to client
   232  	var (
   233  		matchVh   *VirtualHost
   234  		matchType = domainMatchTypeInvalid
   235  		matchLen  int
   236  	)
   237  	for _, vh := range vHosts {
   238  		for _, domain := range vh.Domains {
   239  			typ, matched := match(domain, host)
   240  			if typ == domainMatchTypeInvalid {
   241  				// The rds response is invalid.
   242  				return nil
   243  			}
   244  			if matchType.betterThan(typ) || matchType == typ && matchLen >= len(domain) || !matched {
   245  				// The previous match has better type, or the previous match has
   246  				// better length, or this domain isn't a match.
   247  				continue
   248  			}
   249  			matchVh = vh
   250  			matchType = typ
   251  			matchLen = len(domain)
   252  		}
   253  	}
   254  	return matchVh
   255  }
   256  
   257  // FindBestMatchingVirtualHostServer returns the virtual host whose domains field best
   258  // matches authority.
   259  func FindBestMatchingVirtualHostServer(authority string, vHosts []VirtualHostWithInterceptors) *VirtualHostWithInterceptors {
   260  	var (
   261  		matchVh   *VirtualHostWithInterceptors
   262  		matchType = domainMatchTypeInvalid
   263  		matchLen  int
   264  	)
   265  	for _, vh := range vHosts {
   266  		for _, domain := range vh.Domains {
   267  			typ, matched := match(domain, authority)
   268  			if typ == domainMatchTypeInvalid {
   269  				// The rds response is invalid.
   270  				return nil
   271  			}
   272  			if matchType.betterThan(typ) || matchType == typ && matchLen >= len(domain) || !matched {
   273  				// The previous match has better type, or the previous match has
   274  				// better length, or this domain isn't a match.
   275  				continue
   276  			}
   277  			matchVh = &vh
   278  			matchType = typ
   279  			matchLen = len(domain)
   280  		}
   281  	}
   282  	return matchVh
   283  }