Files
upstream-grpc-go/internal/xds/server/routing.go
upstream-mirror e241b33f26
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 20:40:15 +05:00

193 lines
5.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
*
* Copyright 2026 gRPC authors.
*
* Licensed 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.
*
*/
package server
import (
"errors"
"strings"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/internal/transport"
"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
"google.golang.org/grpc/metadata"
)
// RouteAndProcess routes the incoming RPC to a configured route in the route
// table and also processes the RPC by running the incoming RPC through any HTTP
// Filters configured.
func RouteAndProcess(ss grpc.ServerStream) (grpc.ServerStream, error) {
ctx := ss.Context()
conn := transport.GetConnection(ctx)
cw, ok := conn.(*connWrapper)
if !ok {
return nil, errors.New("missing virtual hosts in incoming context")
}
rc := cw.urc.Load()
// Error out at routing l7 level with a status code UNAVAILABLE, represents
// an nack before usable route configuration or resource not found for RDS
// or error combining LDS + RDS (Shouldn't happen).
if rc.err != nil {
if logger.V(2) {
logger.Infof("RPC on connection with xDS Configuration error: %v", rc.err)
}
return nil, rc.statusErrWithNodeID(codes.Unavailable, "error from xDS configuration for matched route configuration: %v", rc.err)
}
mn, ok := grpc.Method(ctx)
if !ok {
return nil, rc.statusErrWithNodeID(codes.Internal, "missing method name in incoming context")
}
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, rc.statusErrWithNodeID(codes.Internal, "missing metadata in incoming context")
}
// A41 added logic to the core grpc implementation to guarantee that once the
// RPC gets to this point, there will be a single, unambiguous authority
// present in the header map. But add a defensive check to ensure authority
// header is present.
authority := md.Get(":authority")
if len(authority) == 0 {
return nil, rc.statusErrWithNodeID(codes.Internal, "no :authority header present")
}
vh := findBestMatchingVirtualHostServer(authority[0], rc.vhs)
if vh == nil {
return nil, rc.statusErrWithNodeID(codes.Unavailable, "the incoming RPC did not match a configured Virtual Host")
}
var rwi *routeWithInterceptors
for _, r := range vh.routes {
if r.matcher.Match(mn, md) {
// "NonForwardingAction is expected for all Routes used on
// server-side; a route with an inappropriate action causes RPCs
// matching that route to fail with UNAVAILABLE." - A36
if r.actionType != xdsresource.RouteActionNonForwardingAction {
return nil, rc.statusErrWithNodeID(codes.Unavailable, "the incoming RPC matched to a route that was not of action type non forwarding")
}
rwi = &r
break
}
}
if rwi == nil {
return nil, rc.statusErrWithNodeID(codes.Unavailable, "the incoming RPC did not match a configured Route")
}
if rwi.interceptor != nil {
return rwi.interceptor.InterceptRPC(ss)
}
return ss, nil
}
// findBestMatchingVirtualHostServer returns the virtual host whose domains field best
// matches host
//
// The domains field support 4 different matching pattern types:
//
// - Exact match
// - Suffix match (e.g. “*ABC”)
// - Prefix match (e.g. “ABC*)
// - Universal match (e.g. “*”)
//
// The best match is defined as:
// - A match is better if it’s matching pattern type is better.
// * Exact match > suffix match > prefix match > universal match.
//
// - If two matches are of the same pattern type, the longer match is
// better.
// * This is to compare the length of the matching pattern, e.g. “*ABCDE” >
// “*ABC”
func findBestMatchingVirtualHostServer(authority string, vHosts []virtualHostWithInterceptors) *virtualHostWithInterceptors {
var (
matchVh *virtualHostWithInterceptors
matchType = domainMatchTypeInvalid
matchLen int
)
for _, vh := range vHosts {
for _, domain := range vh.domains {
typ, matched := match(domain, authority)
if typ == domainMatchTypeInvalid {
// The rds response is invalid.
return nil
}
if matchType.betterThan(typ) || matchType == typ && matchLen >= len(domain) || !matched {
// The previous match has better type, or the previous match has
// better length, or this domain isn't a match.
continue
}
matchVh = &vh
matchType = typ
matchLen = len(domain)
}
}
return matchVh
}
type domainMatchType int
const (
domainMatchTypeInvalid domainMatchType = iota
domainMatchTypeUniversal
domainMatchTypePrefix
domainMatchTypeSuffix
domainMatchTypeExact
)
// Exact > Suffix > Prefix > Universal > Invalid.
func (t domainMatchType) betterThan(b domainMatchType) bool {
return t > b
}
func matchTypeForDomain(d string) domainMatchType {
if d == "" {
return domainMatchTypeInvalid
}
if d == "*" {
return domainMatchTypeUniversal
}
if strings.HasPrefix(d, "*") {
return domainMatchTypeSuffix
}
if strings.HasSuffix(d, "*") {
return domainMatchTypePrefix
}
if strings.Contains(d, "*") {
return domainMatchTypeInvalid
}
return domainMatchTypeExact
}
func match(domain, host string) (domainMatchType, bool) {
switch typ := matchTypeForDomain(domain); typ {
case domainMatchTypeInvalid:
return typ, false
case domainMatchTypeUniversal:
return typ, true
case domainMatchTypePrefix:
// abc.*
return typ, strings.HasPrefix(host, strings.TrimSuffix(domain, "*"))
case domainMatchTypeSuffix:
// *.123
return typ, strings.HasSuffix(host, strings.TrimPrefix(domain, "*"))
case domainMatchTypeExact:
return typ, domain == host
default:
return domainMatchTypeInvalid, false
}
}