Skip to content

internal/resolver: add target URI parsing with resolver validation#8876

Open
HueCodes wants to merge 4 commits into
grpc:masterfrom
HueCodes:validate-target-uri
Open

internal/resolver: add target URI parsing with resolver validation#8876
HueCodes wants to merge 4 commits into
grpc:masterfrom
HueCodes:validate-target-uri

Conversation

@HueCodes
Copy link
Copy Markdown

@HueCodes HueCodes commented Feb 2, 2026

Fixes #8747

Add resolver validation to ParseTarget in grpcutil. The function
parses a gRPC target URI string and verifies that a resolver is
registered for the parsed scheme.

Revert clientconn.go to url.Parse since it uses cc.getResolver()
for custom dial-option resolvers. Remove the redundant resolver.Get
check from rls/config.go.

RELEASE NOTES: n/a

Fixes grpc#8747

Add an internal API to validate gRPC target URI strings. The function
checks that the target parses as a valid RFC 3986 URI and that a
resolver is registered for its scheme.

RELEASE NOTES: n/a
@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 2, 2026

Codecov Report

❌ Patch coverage is 94.11765% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 83.05%. Comparing base (2abe1f0) to head (89ed8d1).
⚠️ Report is 66 commits behind head on master.

Files with missing lines Patch % Lines
balancer/rls/config.go 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #8876      +/-   ##
==========================================
- Coverage   83.17%   83.05%   -0.12%     
==========================================
  Files         414      412       -2     
  Lines       32751    32950     +199     
==========================================
+ Hits        27241    27367     +126     
- Misses       4091     4185      +94     
+ Partials     1419     1398      -21     
Files with missing lines Coverage Δ
clientconn.go 90.49% <100.00%> (-0.31%) ⬇️
internal/resolver/target.go 100.00% <100.00%> (ø)
balancer/rls/config.go 84.96% <50.00%> (-0.65%) ⬇️

... and 94 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@arjan-bal arjan-bal self-assigned this Feb 3, 2026
@arjan-bal arjan-bal self-requested a review February 3, 2026 06:27
@arjan-bal arjan-bal added this to the 1.80 Release milestone Feb 3, 2026
@arjan-bal arjan-bal added the Type: Feature New features or improvements in behavior label Feb 3, 2026
@arjan-bal
Copy link
Copy Markdown
Contributor

Hi @HueCodes, thanks for picking up this issue.

To ensure we have a reusable API, we should update existing call sites to use the new implementation. Specifically:

  1. ClientConn:

    grpc-go/clientconn.go

    Lines 1802 to 1827 in b7b1cce

    parsedTarget, err := parseTarget(cc.target)
    if err == nil {
    rb = cc.getResolver(parsedTarget.URL.Scheme)
    if rb != nil {
    cc.parsedTarget = parsedTarget
    cc.resolverBuilder = rb
    return nil
    }
    }
    // We are here because the user's dial target did not contain a scheme or
    // specified an unregistered scheme. We should fallback to the default
    // scheme, except when a custom dialer is specified in which case, we should
    // always use passthrough scheme. For either case, we need to respect any overridden
    // global defaults set by the user.
    defScheme := cc.dopts.defaultScheme
    if internal.UserSetDefaultScheme {
    defScheme = resolver.GetDefaultScheme()
    }
    canonicalTarget := defScheme + ":///" + cc.target
    parsedTarget, err = parseTarget(canonicalTarget)
    if err != nil {
    return err
    }
  2. Route Lookup Service:
    parsedTarget, err := url.Parse(lookupService)
    if err != nil {
    // url.Parse() fails if scheme is missing. Retry with default scheme.
    parsedTarget, err = url.Parse(resolver.GetDefaultScheme() + ":///" + lookupService)
    if err != nil {
    return nil, fmt.Errorf("rls: invalid target URI in lookup_service %s", lookupService)
    }
    }

Both of these locations share common behavior:

  1. If parsing fails, they attempt to append the default scheme and re-parse.
  2. They return a url.URL object.

To handle this, the new function would need to accept a default scheme param and return a url.Url as the result. I think we should design the API to handle these two operations internally. This would allow us to refactor the existing code to call the new API as well. @easwars, what do you think?

@arjan-bal arjan-bal assigned easwars and unassigned arjan-bal Feb 3, 2026
@HueCodes
Copy link
Copy Markdown
Author

HueCodes commented Feb 3, 2026

Thanks for the response! I will work on fixing this today when I am off work.

@easwars
Copy link
Copy Markdown
Contributor

easwars commented Feb 3, 2026

Agree with the comments by @arjan-bal.

Another place where we would have to use the new API is here:

if sc.serverURI == "" {

Currently, that code only verifies that the server_uri is not empty. But we need to ensure that it is a valid URI. I think this will cause a whole bunch of unit tests to fail because we simply pass a non-empty string for this value in a lot of tests. So, this could also be done as a follow-up if it ends up touching too many test files. Thanks.

@HueCodes
Copy link
Copy Markdown
Author

HueCodes commented Feb 4, 2026

Thanks for taking the time to review this. I appreciate it. I addressed the issues:

  • Added ParseTarget function that accepts a default scheme parameter and returns *url.URL
  • Updated clientconn.go and balancer/rls/config.go to use the new function
  • Left xds/bootstrap for a follow-up as suggested, since it may touch many test files

Let me know if there are any other changes you would like me to make.

Comment thread internal/grpcutil/target_test.go Outdated
Comment thread internal/grpcutil/target.go Outdated
@arjan-bal arjan-bal assigned HueCodes and unassigned arjan-bal Feb 5, 2026
@HueCodes HueCodes changed the title grpcutil: add ValidateTargetURI function grpcutil: add target URI parsing with resolver validation Feb 7, 2026
@eshitachandwani eshitachandwani assigned arjan-bal and unassigned HueCodes Feb 9, 2026
Comment thread internal/grpcutil/target.go Outdated
Comment thread internal/grpcutil/target.go Outdated
Comment thread clientconn.go Outdated
Comment thread clientconn.go Outdated
Comment thread internal/grpcutil/target.go Outdated
Comment thread internal/grpcutil/target_test.go Outdated
name string
target string
defaultScheme string
wantScheme string
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the scheme, I think we should also validate the host and the path (via Target.Endpoint()) since they're the most important components for gRPC. I've left a separate comment about returning a resolver.Target instead of a URL.

@arjan-bal arjan-bal assigned HueCodes and unassigned arjan-bal Feb 11, 2026
HueCodes added a commit to HueCodes/grpc-go that referenced this pull request Feb 13, 2026
Move ParseTarget to resolver package per PR grpc#8876 review feedback.
Add ParseTargetWithRegistry to support custom resolver registries.
Return resolver.Target instead of *url.URL for better API ergonomics.

Changes:
- Add resolver.ParseTarget using global resolver registry
- Add resolver.ParseTargetWithRegistry accepting custom registry function
- Return resolver.Target instead of *url.URL
- Use %v instead of %w in error messages per review
- Update balancer/rls/config.go to use resolver.ParseTarget
- Simplify clientconn.initParsedTargetAndResolverBuilder
- Remove internal/grpcutil/target.go and tests
Comment thread resolver/resolver.go Outdated
Comment thread resolver/resolver.go Outdated
Comment thread resolver/target_test.go Outdated
Comment thread resolver/target_test.go Outdated
Comment thread resolver/target_test.go Outdated
Comment thread resolver/target_test.go Outdated
Comment thread resolver/resolver.go
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't realize that adding the function here would make the ParseTarget public. The issue explicitly mentions that the API should be internal. Can you please move the function to the internal/resolver package?

// Package resolver provides internal resolver-related functionality.
package resolver

Apologies for the back and forth.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for taking the time to review my code. I am learning a ton as this is very helpful. I should be done tonight and will push the changes then.

@arjan-bal arjan-bal assigned HueCodes and unassigned arjan-bal Feb 18, 2026
@HueCodes HueCodes force-pushed the validate-target-uri branch from 1bfba71 to 48e43ac Compare February 20, 2026 05:46
Fixes grpc#8747

Add ParseTarget to internal/resolver. The function parses a gRPC target
string into a resolver.Target and verifies that a resolver is registered
for the parsed scheme using a caller-supplied lookup function.

Scheme lookup rules:
- Registered scheme (hierarchical or opaque form): accepted immediately.
- Unregistered scheme in hierarchical URI (scheme://...): rejected; the
  caller chose this scheme explicitly, so no silent fallback occurs.
- Opaque URI (e.g. host:port) or empty-scheme URI with an unregistered
  scheme: retried by prepending defaultScheme + ":///" if provided.

Accepting a builder func instead of calling resolver.Get directly lets
clientconn.go pass cc.getResolver, which also checks resolvers
registered via dial options.

Update clientconn.go and balancer/rls/config.go to use ParseTarget,
removing the duplicate parsing and resolver-lookup logic from both.

RELEASE NOTES: n/a
@HueCodes HueCodes force-pushed the validate-target-uri branch 2 times, most recently from 3a28eea to 623f036 Compare February 24, 2026 06:06
@mbissa mbissa modified the milestones: 1.80 Release, 1.81 Release Mar 6, 2026
@arjan-bal arjan-bal assigned arjan-bal and unassigned HueCodes Mar 17, 2026
@arjan-bal arjan-bal added Type: Internal Cleanup Refactors, etc and removed Type: Feature New features or improvements in behavior labels Mar 17, 2026
Copy link
Copy Markdown
Contributor

@arjan-bal arjan-bal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost looks good, some minor comments.

Comment thread clientconn.go
Comment on lines 1813 to 1816
defScheme := cc.dopts.defaultScheme
if internal.UserSetDefaultScheme {
defScheme = resolver.GetDefaultScheme()
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move the computation of the default scheme higher up and pass the default scheme to the first call to iresolver.ParseTarget? That should allow us to get rid of the second ParseTarget below.

Comment thread internal/resolver/target_test.go Outdated
errContain string
}{
{
name: "known scheme resolves",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Could you replace spaces with underscores (_) in the subtest names? The Go test harness automatically rewrites spaces, which makes it difficult to search the logs or run specific subtests via the test filter (-run flag) when debugging.

Comment thread internal/resolver/target.go Outdated
Comment on lines +50 to +54
if u.Opaque == "" {
// Unregistered scheme in hierarchical URI form (scheme://...): the
// caller explicitly chose this scheme; do not silently fall back.
return resolver.Target{}, fmt.Errorf("no resolver registered for scheme %q in target %q", u.Scheme, target)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this check isn't present in the current implementation. Is there a strong reason to add this? If no, I would suggest maintaining the existing behaviour.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strong reason, I removed it.

@arjan-bal arjan-bal assigned HueCodes and unassigned arjan-bal Mar 17, 2026
@arjan-bal arjan-bal changed the title grpcutil: add target URI parsing with resolver validation internal/resolver: add target URI parsing with resolver validation Mar 17, 2026
@easwars easwars assigned arjan-bal and unassigned HueCodes Mar 24, 2026
Comment thread internal/resolver/target.go Outdated
Comment on lines +49 to +53
// Hierarchical URI with an unregistered scheme: the caller explicitly
// chose this scheme, so do not silently fall back.
if err == nil && u.Scheme != "" && u.Opaque == "" {
return resolver.Target{}, fmt.Errorf("no resolver registered for scheme %q in target %q", u.Scheme, target)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Special handling for opaque URLs like this isn't present in the existing code. I assume this is required because the two places where the target is parsed behave differently:

  1. In clientconn.go, if the URL is parsed successfully but the builder for the requested scheme is not found, the default scheme is prepended, and the target is re-parsed.
  2. In rls/config.go, if the URL is parsed successfully but the builder for the requested scheme is not found, an error is returned directly. The default scheme is only used when either URL parsing fails, or it succeeds but the scheme is empty.

If we pass localhost:8080 to the RLS server parsing logic, it will parse it as an opaque URL, look for a resolver for localhost and fail. I suspect that RLS config parsing should be consistent across all gRPC implementations, as it comes from an xDS management server whereas gRPC channel target parsing differs slightly across them.

@easwars, can you comment on whether it makes sense to unify these two usages? If so, what is the expected behavior?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per the RLS design doc, the lookup_service field must be set and non-empty and must parse as a target URI. This to me means that it should follow the same set of rules that we use for a regular grpc target URI.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems weird to me that our existing implementation in the clientconn uses the default scheme when the target URI parses successfully, but the scheme is not found in the resolver registry. It looks like some kind of backwards compatible behavior that we had which probably made sense when our default scheme was passthrough. Now, the default scheme is dns with grpc.NewClient. I think we might have to revisit some of this, unfortunately.

@arjan-bal arjan-bal assigned easwars and unassigned arjan-bal Mar 24, 2026
Remove the opaque vs hierarchical URI distinction in ParseTarget.
The function now always falls back to the default scheme when the
parsed scheme is unregistered, matching existing clientconn behavior.

Consolidate clientconn.go to a single ParseTarget call by computing
the default scheme upfront and passing it as the defaultScheme
parameter.

Update RLS config test to reflect unified fallback behavior, where
unregistered schemes fall back to the default scheme instead of
returning an error.

RELEASE NOTES: n/a
@HueCodes HueCodes force-pushed the validate-target-uri branch from 383f46a to c2fdf5f Compare March 30, 2026 20:09
Remove internal/grpcutil/target.go and its test. The ValidateTargetURI
function was unused outside its own test and introduced a transitive
dependency from encoding/ through internal/grpcutil/ to resolver/ and
credentials/, pulling crypto/tls into lightweight packages.

Replace spaces with underscores in subtest names per review feedback.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add an internal API to validate a target URI

5 participants