Repository: grpc/grpc-go

Issue:
client transport: Handle 1xx HTTP status HEADERS frames correctly
Per the HTTP/2 spec, we should be ignoring leading HEADERS frames with a 1xx status code, unless END_STREAM is also set (in which case it would be an error).

Hints from issue comments:
@arjan-bal: This is where the client processes the headers: https://github.com/grpc/grpc-go/blob/9fa3267859958a7fa0141a8180102850f3d5842c/internal/transport/http2_client.go#L1436@hugehoo: hi, do you mind if i try on this issue?@vinothkumarr227: @arjan-bal kindly assign the issue to me@arjan-bal: Hi @hugehoo, @vinothkumarr227 had already started work on this issue, but the issue wasn't assigned to him. Apologies for the confusion.@hugehoo: @arjan-bal got it 👌

Solution patch:
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 00000000..deb08ca9
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,4 @@
+Thank you for your PR. Please read and follow
+https://github.com/grpc/grpc-go/blob/master/CONTRIBUTING.md, especially the
+"Guidelines for Pull Requests" section, and then delete this text before
+entering your PR description.
diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index 33b0c691..7440e36c 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -125,7 +125,7 @@ jobs:
           echo -e "\n-- Running Interop Test --"
           interop/interop_test.sh
           echo -e "\n-- Running xDS E2E Test --"
-          xds/internal/test/e2e/run.sh
+          internal/xds/test/e2e/run.sh
           echo -e "\n-- Running protoc-gen-go-grpc test --"
           ./scripts/vet-proto.sh -install
           cmd/protoc-gen-go-grpc/protoc-gen-go-grpc_test.sh
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1de0ce66..2079de7b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -33,17 +33,21 @@ guidelines, there may be valid reasons to do so, but it should be rare.
 
 ## Guidelines for Pull Requests
 
-How to get your contributions merged smoothly and quickly:
+Please read the following carefully to ensure your contributions can be merged
+smoothly and quickly.
+
+### PR Contents
 
 - Create **small PRs** that are narrowly focused on **addressing a single
   concern**. We often receive PRs that attempt to fix several things at the same
   time, and if one part of the PR has a problem, that will hold up the entire
   PR.
 
-- For **speculative changes**, consider opening an issue and discussing it
-  first. If you are suggesting a behavioral or API change, consider starting
-  with a [gRFC proposal](https://github.com/grpc/proposal). Many new features
-  that are not bug fixes will require cross-language agreement.
+- If your change does not address an **open issue** with an **agreed
+  resolution**, consider opening an issue and discussing it first. If you are
+  suggesting a behavioral or API change, consider starting with a [gRFC
+  proposal](https://github.com/grpc/proposal). Many new features that are not
+  bug fixes will require cross-language agreement.
 
 - If you want to fix **formatting or style**, consider whether your changes are
   an obvious improvement or might be considered a personal preference. If a
@@ -56,16 +60,6 @@ How to get your contributions merged smoothly and quickly:
   often written as "iff". Please do not make spelling correction changes unless
   you are certain they are misspellings.
 
-- Provide a good **PR description** as a record of **what** change is being made
-  and **why** it was made. Link to a GitHub issue if it exists.
-
-- Maintain a **clean commit history** and use **meaningful commit messages**.
-  PRs with messy commit histories are difficult to review and won't be merged.
-  Before sending your PR, ensure your changes are based on top of the latest
-  `upstream/master` commits, and avoid rebasing in the middle of a code review.
-  You should **never use `git push -f`** unless absolutely necessary during a
-  review, as it can interfere with GitHub's tracking of comments.
-
 - **All tests need to be passing** before your change can be merged. We
   recommend you run tests locally before creating your PR to catch breakages
   early on:
@@ -81,15 +75,80 @@ How to get your contributions merged smoothly and quickly:
   GitHub, which will trigger a GitHub Actions run that you can use to verify
   everything is passing.
 
-- If you are adding a new file, make sure it has the **copyright message**
+- Note that there are two GitHub actions checks that need not be green:
+
+  1. We test the freshness of the generated proto code we maintain via the
+     `vet-proto` check. If the source proto files are updated, but our repo is
+     not updated, an optional checker will fail. This will be fixed by our team
+     in a separate PR and will not prevent the merge of your PR.
+
+  2. We run a checker that will fail if there is any change in dependencies of
+     an exported package via the `dependencies` check. If new dependencies are
+     added that are not appropriate, we may not accept your PR (see below).
+
+- If you are adding a **new file**, make sure it has the **copyright message**
   template at the top as a comment. You can copy the message from an existing
   file and update the year.
 
 - The grpc package should only depend on standard Go packages and a small number
   of exceptions. **If your contribution introduces new dependencies**, you will
-  need a discussion with gRPC-Go maintainers. A GitHub action check will run on
-  every PR, and will flag any transitive dependency changes from any public
-  package.
+  need a discussion with gRPC-Go maintainers.
+
+### PR Descriptions
+
+- **PR titles** should start with the name of the component being addressed, or
+  the type of change. Examples: transport, client, server, round_robin, xds,
+  cleanup, deps.
+
+- Read and follow the **guidelines for PR titles and descriptions** here:
+  https://google.github.io/eng-practices/review/developer/cl-descriptions.html
+
+  *particularly* the sections "First Line" and "Body is Informative".
+
+  Note: your PR description will be used as the git commit message in a
+  squash-and-merge if your PR is approved. We may make changes to this as
+  necessary.
+
+- **Does this PR relate to an open issue?** On the first line, please use the
+  tag `Fixes #<issue>` to ensure the issue is closed when the PR is merged. Or
+  use `Updates #<issue>` if the PR is related to an open issue, but does not fix
+  it. Consider filing an issue if one does not already exist.
+
+- PR descriptions *must* conclude with **release notes** as follows:
+
+  ```
+  RELEASE NOTES:
+  * <component>: <summary>
+  ```
+
+  This need not match the PR title.
+
+  The summary must:
+
+  * be something that gRPC users will understand.
+
+  * clearly explain the feature being added, the issue being fixed, or the
+    behavior being changed, etc. If fixing a bug, be clear about how the bug
+    can be triggered by an end-user.
+
+  * begin with a capital letter and use complete sentences.
+
+  * be as short as possible to describe the change being made.
+
+  If a PR is *not* end-user visible -- e.g. a cleanup, testing change, or
+  GitHub-related, use `RELEASE NOTES: n/a`.
+
+### PR Process
+
+- Please **self-review** your code changes before sending your PR. This will
+  prevent simple, obvious errors from causing delays.
+
+- Maintain a **clean commit history** and use **meaningful commit messages**.
+  PRs with messy commit histories are difficult to review and won't be merged.
+  Before sending your PR, ensure your changes are based on top of the latest
+  `upstream/master` commits, and avoid rebasing in the middle of a code review.
+  You should **never use `git push -f`** unless absolutely necessary during a
+  review, as it can interfere with GitHub's tracking of comments.
 
 - Unless your PR is trivial, you should **expect reviewer comments** that you
   will need to address before merging. We'll label the PR as `Status: Requires
@@ -98,5 +157,3 @@ How to get your contributions merged smoothly and quickly:
   `stale`, and we will automatically close it after 7 days if we don't hear back
   from you. Please feel free to ping issues or bugs if you do not get a response
   within a week.
-
-- Exceptions to the rules can be made if there's a compelling reason to do so.
diff --git a/examples/features/csm_observability/client/Dockerfile b/examples/features/csm_observability/client/Dockerfile
index 0e974c13..6d46d079 100644
--- a/examples/features/csm_observability/client/Dockerfile
+++ b/examples/features/csm_observability/client/Dockerfile
@@ -15,7 +15,7 @@
 # Dockerfile for building the example client. To build the image, run the
 # following command from grpc-go directory:
 # docker build -t <TAG> -f examples/features/csm_observability/client/Dockerfile .
-FROM golang:1.23-alpine as build
+FROM golang:1.25-alpine as build
 
 RUN apk --no-cache add curl
 
diff --git a/examples/features/csm_observability/server/Dockerfile b/examples/features/csm_observability/server/Dockerfile
index 3ba99a7f..7ab8e2e8 100644
--- a/examples/features/csm_observability/server/Dockerfile
+++ b/examples/features/csm_observability/server/Dockerfile
@@ -16,7 +16,7 @@
 # following command from grpc-go directory:
 # docker build -t <TAG> -f examples/features/csm_observability/server/Dockerfile .
 
-FROM golang:1.23-alpine as build
+FROM golang:1.25-alpine as build
 RUN apk --no-cache add curl
 # Make a grpc-go directory and copy the repo into it.
 WORKDIR /go/src/grpc-go
diff --git a/gcp/observability/go.sum b/gcp/observability/go.sum
index b94f7cf9..6615a0b8 100644
--- a/gcp/observability/go.sum
+++ b/gcp/observability/go.sum
@@ -892,6 +892,7 @@ github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWH
 github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
 github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
+github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
 github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
@@ -918,9 +919,11 @@ github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaB
 github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
 github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34=
 github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI=
+github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
 github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
 github.com/envoyproxy/go-control-plane/envoy v1.32.2/go.mod h1:eR2SOX2IedqlPvmiKjUH7Wu//S602JKI7HPC/L3SRq8=
 github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE=
+github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
 github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
 github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
@@ -929,6 +932,7 @@ github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0+
 github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
 github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
 github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
+github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
 github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
 github.com/esiqveland/notify v0.11.0/go.mod h1:63UbVSaeJwF0LVJARHFuPgUAoM7o1BEvCZyknsuonBc=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@@ -1253,6 +1257,7 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
 github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
+github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
 github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
diff --git a/internal/envconfig/xds.go b/internal/envconfig/xds.go
index e8755155..b1f883bc 100644
--- a/internal/envconfig/xds.go
+++ b/internal/envconfig/xds.go
@@ -68,4 +68,10 @@ var (
 	// trust.  For more details, see:
 	// https://github.com/grpc/proposal/blob/master/A87-mtls-spiffe-support.md
 	XDSSPIFFEEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_MTLS_SPIFFE", false)
+
+	// XDSHTTPConnectEnabled is true if gRPC should parse custom Metadata
+	// configuring use of an HTTP CONNECT proxy via xDS from cluster resources.
+	// For more details, see:
+	// https://github.com/grpc/proposal/blob/master/A86-xds-http-connect.md
+	XDSHTTPConnectEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_HTTP_CONNECT", false)
 )
diff --git a/internal/transport/handler_server.go b/internal/transport/handler_server.go
index 3dea2357..d954a64c 100644
--- a/internal/transport/handler_server.go
+++ b/internal/transport/handler_server.go
@@ -277,11 +277,13 @@ func (ht *serverHandlerTransport) writeStatus(s *ServerStream, st *status.Status
 	if err == nil { // transport has not been closed
 		// Note: The trailer fields are compressed with hpack after this call returns.
 		// No WireLength field is set here.
+		s.hdrMu.Lock()
 		for _, sh := range ht.stats {
 			sh.HandleRPC(s.Context(), &stats.OutTrailer{
 				Trailer: s.trailer.Copy(),
 			})
 		}
+		s.hdrMu.Unlock()
 	}
 	ht.Close(errors.New("finished writing status"))
 	return err
diff --git a/internal/transport/http2_client.go b/internal/transport/http2_client.go
index 5467fe97..c31ed87b 100644
--- a/internal/transport/http2_client.go
+++ b/internal/transport/http2_client.go
@@ -1499,13 +1499,6 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
 		case "grpc-message":
 			grpcMessage = decodeGrpcMessage(hf.Value)
 		case ":status":
-			if hf.Value == "200" {
-				httpStatusErr = ""
-				statusCode := 200
-				httpStatusCode = &statusCode
-				break
-			}
-
 			c, err := strconv.ParseInt(hf.Value, 10, 32)
 			if err != nil {
 				se := status.New(codes.Internal, fmt.Sprintf("transport: malformed http-status: %v", err))
@@ -1513,7 +1506,19 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
 				return
 			}
 			statusCode := int(c)
+			if statusCode >= 100 && statusCode < 200 {
+				if endStream {
+					se := status.New(codes.Internal, fmt.Sprintf(
+						"protocol error: informational header with status code %d must not have END_STREAM set", statusCode))
+					t.closeStream(s, se.Err(), true, http2.ErrCodeProtocol, se, nil, endStream)
+				}
+				return
+			}
 			httpStatusCode = &statusCode
+			if statusCode == 200 {
+				httpStatusErr = ""
+				break
+			}
 
 			httpStatusErr = fmt.Sprintf(
 				"unexpected HTTP status code received from server: %d (%s)",
diff --git a/internal/transport/http2_server.go b/internal/transport/http2_server.go
index 9f725e15..83cee314 100644
--- a/internal/transport/http2_server.go
+++ b/internal/transport/http2_server.go
@@ -1353,10 +1353,10 @@ func (t *http2Server) closeStream(s *ServerStream, rst bool, rstCode http2.ErrCo
 	// called to interrupt the potential blocking on other goroutines.
 	s.cancel()
 
-	oldState := s.swapState(streamDone)
-	if oldState == streamDone {
-		return
-	}
+	// We can't return early even if the stream's state is "done" as the state
+	// might have been set by the `finishStream` method. Deleting the stream via
+	// `finishStream` can get blocked on flow control.
+	s.swapState(streamDone)
 	t.deleteStream(s, eosReceived)
 
 	t.controlBuf.put(&cleanupStream{
diff --git a/xds/internal/balancer/balancer.go b/internal/xds/balancer/balancer.go
similarity index 75%
rename from xds/internal/balancer/balancer.go
rename to internal/xds/balancer/balancer.go
index ff27af02..af3f999a 100644
--- a/xds/internal/balancer/balancer.go
+++ b/internal/xds/balancer/balancer.go
@@ -22,10 +22,10 @@ package balancer
 import (
 	_ "google.golang.org/grpc/balancer/leastrequest"                  // Register the least_request_experimental balancer
 	_ "google.golang.org/grpc/balancer/weightedtarget"                // Register the weighted_target balancer
-	_ "google.golang.org/grpc/xds/internal/balancer/cdsbalancer"      // Register the CDS balancer
-	_ "google.golang.org/grpc/xds/internal/balancer/clusterimpl"      // Register the xds_cluster_impl balancer
-	_ "google.golang.org/grpc/xds/internal/balancer/clustermanager"   // Register the xds_cluster_manager balancer
-	_ "google.golang.org/grpc/xds/internal/balancer/clusterresolver"  // Register the xds_cluster_resolver balancer
-	_ "google.golang.org/grpc/xds/internal/balancer/outlierdetection" // Register the outlier_detection balancer
-	_ "google.golang.org/grpc/xds/internal/balancer/priority"         // Register the priority balancer
+	_ "google.golang.org/grpc/internal/xds/balancer/cdsbalancer"      // Register the CDS balancer
+	_ "google.golang.org/grpc/internal/xds/balancer/clusterimpl"      // Register the xds_cluster_impl balancer
+	_ "google.golang.org/grpc/internal/xds/balancer/clustermanager"   // Register the xds_cluster_manager balancer
+	_ "google.golang.org/grpc/internal/xds/balancer/clusterresolver"  // Register the xds_cluster_resolver balancer
+	_ "google.golang.org/grpc/internal/xds/balancer/outlierdetection" // Register the outlier_detection balancer
+	_ "google.golang.org/grpc/internal/xds/balancer/priority"         // Register the priority balancer
 )
diff --git a/xds/internal/balancer/cdsbalancer/cdsbalancer.go b/internal/xds/balancer/cdsbalancer/cdsbalancer.go
similarity index 99%
rename from xds/internal/balancer/cdsbalancer/cdsbalancer.go
rename to internal/xds/balancer/cdsbalancer/cdsbalancer.go
index b413b8ac..0ad77b85 100644
--- a/xds/internal/balancer/cdsbalancer/cdsbalancer.go
+++ b/internal/xds/balancer/cdsbalancer/cdsbalancer.go
@@ -35,11 +35,11 @@ import (
 	"google.golang.org/grpc/internal/grpclog"
 	"google.golang.org/grpc/internal/grpcsync"
 	"google.golang.org/grpc/internal/pretty"
+	"google.golang.org/grpc/internal/xds/balancer/clusterresolver"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/serviceconfig"
-	"google.golang.org/grpc/xds/internal/balancer/clusterresolver"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
 )
 
 const (
diff --git a/xds/internal/balancer/cdsbalancer/cluster_watcher.go b/internal/xds/balancer/cdsbalancer/cluster_watcher.go
similarity index 97%
rename from xds/internal/balancer/cdsbalancer/cluster_watcher.go
rename to internal/xds/balancer/cdsbalancer/cluster_watcher.go
index a9adea0c..dd702b12 100644
--- a/xds/internal/balancer/cdsbalancer/cluster_watcher.go
+++ b/internal/xds/balancer/cdsbalancer/cluster_watcher.go
@@ -19,7 +19,7 @@ package cdsbalancer
 import (
 	"context"
 
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 )
 
 // clusterWatcher implements the xdsresource.ClusterWatcher interface, and is
diff --git a/xds/internal/balancer/cdsbalancer/logging.go b/internal/xds/balancer/cdsbalancer/logging.go
similarity index 100%
rename from xds/internal/balancer/cdsbalancer/logging.go
rename to internal/xds/balancer/cdsbalancer/logging.go
diff --git a/xds/internal/balancer/clusterimpl/clusterimpl.go b/internal/xds/balancer/clusterimpl/clusterimpl.go
similarity index 97%
rename from xds/internal/balancer/clusterimpl/clusterimpl.go
rename to internal/xds/balancer/clusterimpl/clusterimpl.go
index 006be4c4..ffe0a3db 100644
--- a/xds/internal/balancer/clusterimpl/clusterimpl.go
+++ b/internal/xds/balancer/clusterimpl/clusterimpl.go
@@ -38,15 +38,14 @@ import (
 	"google.golang.org/grpc/internal/balancer/gracefulswitch"
 	"google.golang.org/grpc/internal/grpclog"
 	"google.golang.org/grpc/internal/pretty"
-	"google.golang.org/grpc/internal/xds"
+	xdsinternal "google.golang.org/grpc/internal/xds"
+	"google.golang.org/grpc/internal/xds/balancer/loadstore"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/lrsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/serviceconfig"
-	xdsinternal "google.golang.org/grpc/xds/internal"
-	"google.golang.org/grpc/xds/internal/balancer/loadstore"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/lrsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient"
 )
 
 const (
@@ -436,7 +435,7 @@ func (b *clusterImplBalancer) NewSubConn(addrs []resolver.Address, opts balancer
 	clusterName := b.getClusterName()
 	newAddrs := make([]resolver.Address, len(addrs))
 	for i, addr := range addrs {
-		newAddrs[i] = xds.SetXDSHandshakeClusterName(addr, clusterName)
+		newAddrs[i] = xdsinternal.SetXDSHandshakeClusterName(addr, clusterName)
 	}
 	var sc balancer.SubConn
 	scw := &scWrapper{}
@@ -475,7 +474,7 @@ func (b *clusterImplBalancer) UpdateAddresses(sc balancer.SubConn, addrs []resol
 	newAddrs := make([]resolver.Address, len(addrs))
 	var lID clients.Locality
 	for i, addr := range addrs {
-		newAddrs[i] = xds.SetXDSHandshakeClusterName(addr, clusterName)
+		newAddrs[i] = xdsinternal.SetXDSHandshakeClusterName(addr, clusterName)
 		lID = xdsinternal.GetLocalityID(newAddrs[i])
 	}
 	if scw, ok := sc.(*scWrapper); ok {
diff --git a/xds/internal/balancer/clusterimpl/config.go b/internal/xds/balancer/clusterimpl/config.go
similarity index 100%
rename from xds/internal/balancer/clusterimpl/config.go
rename to internal/xds/balancer/clusterimpl/config.go
diff --git a/xds/internal/balancer/clusterimpl/logging.go b/internal/xds/balancer/clusterimpl/logging.go
similarity index 100%
rename from xds/internal/balancer/clusterimpl/logging.go
rename to internal/xds/balancer/clusterimpl/logging.go
diff --git a/xds/internal/balancer/clusterimpl/picker.go b/internal/xds/balancer/clusterimpl/picker.go
similarity index 95%
rename from xds/internal/balancer/clusterimpl/picker.go
rename to internal/xds/balancer/clusterimpl/picker.go
index 9ed16ffb..fab09fa0 100644
--- a/xds/internal/balancer/clusterimpl/picker.go
+++ b/internal/xds/balancer/clusterimpl/picker.go
@@ -27,10 +27,10 @@ import (
 	"google.golang.org/grpc/connectivity"
 	"google.golang.org/grpc/internal/stats"
 	"google.golang.org/grpc/internal/wrr"
+	xdsinternal "google.golang.org/grpc/internal/xds"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/xdsclient"
 	"google.golang.org/grpc/status"
-	"google.golang.org/grpc/xds/internal"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/xdsclient"
 )
 
 // NewRandomWRR is used when calculating drops. It's exported so that tests can
@@ -155,7 +155,7 @@ func (d *picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
 	}
 
 	if labels := telemetryLabels(info.Ctx); labels != nil {
-		labels["grpc.lb.locality"] = internal.LocalityString(lID)
+		labels["grpc.lb.locality"] = xdsinternal.LocalityString(lID)
 	}
 
 	if d.loadStore != nil {
diff --git a/xds/internal/balancer/clustermanager/balancerstateaggregator.go b/internal/xds/balancer/clustermanager/balancerstateaggregator.go
similarity index 100%
rename from xds/internal/balancer/clustermanager/balancerstateaggregator.go
rename to internal/xds/balancer/clustermanager/balancerstateaggregator.go
diff --git a/xds/internal/balancer/clustermanager/clustermanager.go b/internal/xds/balancer/clustermanager/clustermanager.go
similarity index 100%
rename from xds/internal/balancer/clustermanager/clustermanager.go
rename to internal/xds/balancer/clustermanager/clustermanager.go
diff --git a/xds/internal/balancer/clustermanager/config.go b/internal/xds/balancer/clustermanager/config.go
similarity index 100%
rename from xds/internal/balancer/clustermanager/config.go
rename to internal/xds/balancer/clustermanager/config.go
diff --git a/xds/internal/balancer/clustermanager/picker.go b/internal/xds/balancer/clustermanager/picker.go
similarity index 100%
rename from xds/internal/balancer/clustermanager/picker.go
rename to internal/xds/balancer/clustermanager/picker.go
diff --git a/xds/internal/balancer/clusterresolver/clusterresolver.go b/internal/xds/balancer/clusterresolver/clusterresolver.go
similarity index 98%
rename from xds/internal/balancer/clusterresolver/clusterresolver.go
rename to internal/xds/balancer/clusterresolver/clusterresolver.go
index f9ce5729..f5a30e1a 100644
--- a/xds/internal/balancer/clusterresolver/clusterresolver.go
+++ b/internal/xds/balancer/clusterresolver/clusterresolver.go
@@ -35,12 +35,12 @@ import (
 	"google.golang.org/grpc/internal/grpclog"
 	"google.golang.org/grpc/internal/grpcsync"
 	"google.golang.org/grpc/internal/pretty"
+	"google.golang.org/grpc/internal/xds/balancer/outlierdetection"
+	"google.golang.org/grpc/internal/xds/balancer/priority"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/serviceconfig"
-	"google.golang.org/grpc/xds/internal/balancer/outlierdetection"
-	"google.golang.org/grpc/xds/internal/balancer/priority"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
 )
 
 // Name is the name of the cluster_resolver balancer.
diff --git a/xds/internal/balancer/clusterresolver/config.go b/internal/xds/balancer/clusterresolver/config.go
similarity index 98%
rename from xds/internal/balancer/clusterresolver/config.go
rename to internal/xds/balancer/clusterresolver/config.go
index 7614b0fc..f3b4c6bf 100644
--- a/xds/internal/balancer/clusterresolver/config.go
+++ b/internal/xds/balancer/clusterresolver/config.go
@@ -23,9 +23,9 @@ import (
 	"fmt"
 
 	internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
+	"google.golang.org/grpc/internal/xds/balancer/outlierdetection"
 	"google.golang.org/grpc/internal/xds/bootstrap"
 	"google.golang.org/grpc/serviceconfig"
-	"google.golang.org/grpc/xds/internal/balancer/outlierdetection"
 )
 
 // DiscoveryMechanismType is the type of discovery mechanism.
diff --git a/xds/internal/balancer/clusterresolver/configbuilder.go b/internal/xds/balancer/clusterresolver/configbuilder.go
similarity index 96%
rename from xds/internal/balancer/clusterresolver/configbuilder.go
rename to internal/xds/balancer/clusterresolver/configbuilder.go
index 9c7fed86..3d8e0897 100644
--- a/xds/internal/balancer/clusterresolver/configbuilder.go
+++ b/internal/xds/balancer/clusterresolver/configbuilder.go
@@ -26,14 +26,14 @@ import (
 	"google.golang.org/grpc/internal/balancer/weight"
 	"google.golang.org/grpc/internal/hierarchy"
 	internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
+	xdsinternal "google.golang.org/grpc/internal/xds"
+	"google.golang.org/grpc/internal/xds/balancer/clusterimpl"
+	"google.golang.org/grpc/internal/xds/balancer/outlierdetection"
+	"google.golang.org/grpc/internal/xds/balancer/priority"
+	"google.golang.org/grpc/internal/xds/balancer/wrrlocality"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/resolver/ringhash"
-	"google.golang.org/grpc/xds/internal"
-	"google.golang.org/grpc/xds/internal/balancer/clusterimpl"
-	"google.golang.org/grpc/xds/internal/balancer/outlierdetection"
-	"google.golang.org/grpc/xds/internal/balancer/priority"
-	"google.golang.org/grpc/xds/internal/balancer/wrrlocality"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
 )
 
 const million = 1000000
@@ -257,7 +257,7 @@ func priorityLocalitiesToClusterImpl(localities []xdsresource.Locality, priority
 		if locality.Weight != 0 {
 			lw = locality.Weight
 		}
-		localityStr := internal.LocalityString(locality.ID)
+		localityStr := xdsinternal.LocalityString(locality.ID)
 		for _, endpoint := range locality.Endpoints {
 			// Filter out all "unhealthy" endpoints (unknown and healthy are
 			// both considered to be healthy:
@@ -270,7 +270,7 @@ func priorityLocalitiesToClusterImpl(localities []xdsresource.Locality, priority
 				resolverEndpoint.Addresses = append(resolverEndpoint.Addresses, resolver.Address{Addr: as})
 			}
 			resolverEndpoint = hierarchy.SetInEndpoint(resolverEndpoint, []string{priorityName, localityStr})
-			resolverEndpoint = internal.SetLocalityIDInEndpoint(resolverEndpoint, locality.ID)
+			resolverEndpoint = xdsinternal.SetLocalityIDInEndpoint(resolverEndpoint, locality.ID)
 			// "To provide the xds_wrr_locality load balancer information about
 			// locality weights received from EDS, the cluster resolver will
 			// populate a new locality weight attribute for each address The
diff --git a/xds/internal/balancer/clusterresolver/configbuilder_childname.go b/internal/xds/balancer/clusterresolver/configbuilder_childname.go
similarity index 96%
rename from xds/internal/balancer/clusterresolver/configbuilder_childname.go
rename to internal/xds/balancer/clusterresolver/configbuilder_childname.go
index bf4e3349..296ed740 100644
--- a/xds/internal/balancer/clusterresolver/configbuilder_childname.go
+++ b/internal/xds/balancer/clusterresolver/configbuilder_childname.go
@@ -20,8 +20,8 @@ package clusterresolver
 import (
 	"fmt"
 
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 )
 
 // nameGenerator generates a child name for a list of priorities (each priority
diff --git a/xds/internal/balancer/clusterresolver/logging.go b/internal/xds/balancer/clusterresolver/logging.go
similarity index 100%
rename from xds/internal/balancer/clusterresolver/logging.go
rename to internal/xds/balancer/clusterresolver/logging.go
diff --git a/xds/internal/balancer/clusterresolver/resource_resolver.go b/internal/xds/balancer/clusterresolver/resource_resolver.go
similarity index 99%
rename from xds/internal/balancer/clusterresolver/resource_resolver.go
rename to internal/xds/balancer/clusterresolver/resource_resolver.go
index c1a656c5..90e950c7 100644
--- a/xds/internal/balancer/clusterresolver/resource_resolver.go
+++ b/internal/xds/balancer/clusterresolver/resource_resolver.go
@@ -24,8 +24,8 @@ import (
 
 	"google.golang.org/grpc/internal/grpclog"
 	"google.golang.org/grpc/internal/grpcsync"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/grpc/resolver"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
 )
 
 // resourceUpdate is a combined update from all the resources, in the order of
diff --git a/xds/internal/balancer/clusterresolver/resource_resolver_dns.go b/internal/xds/balancer/clusterresolver/resource_resolver_dns.go
similarity index 100%
rename from xds/internal/balancer/clusterresolver/resource_resolver_dns.go
rename to internal/xds/balancer/clusterresolver/resource_resolver_dns.go
diff --git a/xds/internal/balancer/clusterresolver/resource_resolver_eds.go b/internal/xds/balancer/clusterresolver/resource_resolver_eds.go
similarity index 98%
rename from xds/internal/balancer/clusterresolver/resource_resolver_eds.go
rename to internal/xds/balancer/clusterresolver/resource_resolver_eds.go
index 043def95..18b517f1 100644
--- a/xds/internal/balancer/clusterresolver/resource_resolver_eds.go
+++ b/internal/xds/balancer/clusterresolver/resource_resolver_eds.go
@@ -23,7 +23,7 @@ import (
 
 	"google.golang.org/grpc/internal/grpclog"
 	"google.golang.org/grpc/internal/grpcsync"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 )
 
 type edsDiscoveryMechanism struct {
diff --git a/xds/internal/balancer/loadstore/load_store_wrapper.go b/internal/xds/balancer/loadstore/load_store_wrapper.go
similarity index 97%
rename from xds/internal/balancer/loadstore/load_store_wrapper.go
rename to internal/xds/balancer/loadstore/load_store_wrapper.go
index 48a2b7d1..89d5ad87 100644
--- a/xds/internal/balancer/loadstore/load_store_wrapper.go
+++ b/internal/xds/balancer/loadstore/load_store_wrapper.go
@@ -22,8 +22,8 @@ package loadstore
 import (
 	"sync"
 
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/lrsclient"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/lrsclient"
 )
 
 // NewWrapper creates a Wrapper.
diff --git a/xds/internal/balancer/outlierdetection/balancer.go b/internal/xds/balancer/outlierdetection/balancer.go
similarity index 100%
rename from xds/internal/balancer/outlierdetection/balancer.go
rename to internal/xds/balancer/outlierdetection/balancer.go
diff --git a/xds/internal/balancer/outlierdetection/callcounter.go b/internal/xds/balancer/outlierdetection/callcounter.go
similarity index 100%
rename from xds/internal/balancer/outlierdetection/callcounter.go
rename to internal/xds/balancer/outlierdetection/callcounter.go
diff --git a/xds/internal/balancer/outlierdetection/config.go b/internal/xds/balancer/outlierdetection/config.go
similarity index 100%
rename from xds/internal/balancer/outlierdetection/config.go
rename to internal/xds/balancer/outlierdetection/config.go
diff --git a/xds/internal/balancer/outlierdetection/logging.go b/internal/xds/balancer/outlierdetection/logging.go
similarity index 100%
rename from xds/internal/balancer/outlierdetection/logging.go
rename to internal/xds/balancer/outlierdetection/logging.go
diff --git a/xds/internal/balancer/outlierdetection/subconn_wrapper.go b/internal/xds/balancer/outlierdetection/subconn_wrapper.go
similarity index 100%
rename from xds/internal/balancer/outlierdetection/subconn_wrapper.go
rename to internal/xds/balancer/outlierdetection/subconn_wrapper.go
diff --git a/xds/internal/balancer/priority/balancer.go b/internal/xds/balancer/priority/balancer.go
similarity index 100%
rename from xds/internal/balancer/priority/balancer.go
rename to internal/xds/balancer/priority/balancer.go
diff --git a/xds/internal/balancer/priority/balancer_child.go b/internal/xds/balancer/priority/balancer_child.go
similarity index 100%
rename from xds/internal/balancer/priority/balancer_child.go
rename to internal/xds/balancer/priority/balancer_child.go
diff --git a/xds/internal/balancer/priority/balancer_priority.go b/internal/xds/balancer/priority/balancer_priority.go
similarity index 100%
rename from xds/internal/balancer/priority/balancer_priority.go
rename to internal/xds/balancer/priority/balancer_priority.go
diff --git a/xds/internal/balancer/priority/config.go b/internal/xds/balancer/priority/config.go
similarity index 100%
rename from xds/internal/balancer/priority/config.go
rename to internal/xds/balancer/priority/config.go
diff --git a/xds/internal/balancer/priority/ignore_resolve_now.go b/internal/xds/balancer/priority/ignore_resolve_now.go
similarity index 100%
rename from xds/internal/balancer/priority/ignore_resolve_now.go
rename to internal/xds/balancer/priority/ignore_resolve_now.go
diff --git a/xds/internal/balancer/priority/logging.go b/internal/xds/balancer/priority/logging.go
similarity index 100%
rename from xds/internal/balancer/priority/logging.go
rename to internal/xds/balancer/priority/logging.go
diff --git a/xds/internal/balancer/wrrlocality/balancer.go b/internal/xds/balancer/wrrlocality/balancer.go
similarity index 98%
rename from xds/internal/balancer/wrrlocality/balancer.go
rename to internal/xds/balancer/wrrlocality/balancer.go
index 0c99c108..b11aaa17 100644
--- a/xds/internal/balancer/wrrlocality/balancer.go
+++ b/internal/xds/balancer/wrrlocality/balancer.go
@@ -31,9 +31,9 @@ import (
 	"google.golang.org/grpc/balancer/weightedtarget"
 	"google.golang.org/grpc/internal/grpclog"
 	internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
+	xdsinternal "google.golang.org/grpc/internal/xds"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/serviceconfig"
-	"google.golang.org/grpc/xds/internal"
 )
 
 // Name is the name of wrr_locality balancer.
@@ -171,7 +171,7 @@ func (b *wrrLocalityBalancer) UpdateClientConnState(s balancer.ClientConnState)
 		// shouldn't happen though (this attribute that is set actually gets
 		// used to build localities in the first place), and thus don't error
 		// out, and just build a weighted target with undefined behavior.
-		locality := internal.LocalityString(internal.GetLocalityID(addr))
+		locality := xdsinternal.LocalityString(xdsinternal.GetLocalityID(addr))
 		ai, ok := getAddrInfo(addr)
 		if !ok {
 			return fmt.Errorf("xds_wrr_locality: missing locality weight information in address %q", addr)
diff --git a/xds/internal/balancer/wrrlocality/logging.go b/internal/xds/balancer/wrrlocality/logging.go
similarity index 100%
rename from xds/internal/balancer/wrrlocality/logging.go
rename to internal/xds/balancer/wrrlocality/logging.go
diff --git a/xds/internal/clients/config.go b/internal/xds/clients/config.go
similarity index 100%
rename from xds/internal/clients/config.go
rename to internal/xds/clients/config.go
diff --git a/xds/internal/clients/grpctransport/grpc_transport.go b/internal/xds/clients/grpctransport/grpc_transport.go
similarity index 99%
rename from xds/internal/clients/grpctransport/grpc_transport.go
rename to internal/xds/clients/grpctransport/grpc_transport.go
index 2659c301..841029dc 100644
--- a/xds/internal/clients/grpctransport/grpc_transport.go
+++ b/internal/xds/clients/grpctransport/grpc_transport.go
@@ -29,8 +29,8 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/grpclog"
+	"google.golang.org/grpc/internal/xds/clients"
 	"google.golang.org/grpc/keepalive"
-	"google.golang.org/grpc/xds/internal/clients"
 )
 
 var (
diff --git a/xds/internal/clients/internal/backoff/backoff.go b/internal/xds/clients/internal/backoff/backoff.go
similarity index 100%
rename from xds/internal/clients/internal/backoff/backoff.go
rename to internal/xds/clients/internal/backoff/backoff.go
diff --git a/xds/internal/clients/internal/buffer/unbounded.go b/internal/xds/clients/internal/buffer/unbounded.go
similarity index 100%
rename from xds/internal/clients/internal/buffer/unbounded.go
rename to internal/xds/clients/internal/buffer/unbounded.go
diff --git a/xds/internal/clients/internal/internal.go b/internal/xds/clients/internal/internal.go
similarity index 97%
rename from xds/internal/clients/internal/internal.go
rename to internal/xds/clients/internal/internal.go
index 371b4d19..60cf82b2 100644
--- a/xds/internal/clients/internal/internal.go
+++ b/internal/xds/clients/internal/internal.go
@@ -20,7 +20,7 @@
 package internal
 
 import (
-	"google.golang.org/grpc/xds/internal/clients"
+	"google.golang.org/grpc/internal/xds/clients"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/structpb"
 
diff --git a/xds/internal/clients/internal/pretty/pretty.go b/internal/xds/clients/internal/pretty/pretty.go
similarity index 100%
rename from xds/internal/clients/internal/pretty/pretty.go
rename to internal/xds/clients/internal/pretty/pretty.go
diff --git a/xds/internal/clients/internal/syncutil/callback_serializer.go b/internal/xds/clients/internal/syncutil/callback_serializer.go
similarity index 98%
rename from xds/internal/clients/internal/syncutil/callback_serializer.go
rename to internal/xds/clients/internal/syncutil/callback_serializer.go
index 87c921a4..baab133f 100644
--- a/xds/internal/clients/internal/syncutil/callback_serializer.go
+++ b/internal/xds/clients/internal/syncutil/callback_serializer.go
@@ -21,7 +21,7 @@ package syncutil
 import (
 	"context"
 
-	"google.golang.org/grpc/xds/internal/clients/internal/buffer"
+	"google.golang.org/grpc/internal/xds/clients/internal/buffer"
 )
 
 // CallbackSerializer provides a mechanism to schedule callbacks in a
diff --git a/xds/internal/clients/internal/syncutil/event.go b/internal/xds/clients/internal/syncutil/event.go
similarity index 100%
rename from xds/internal/clients/internal/syncutil/event.go
rename to internal/xds/clients/internal/syncutil/event.go
diff --git a/xds/internal/clients/lrsclient/internal/internal.go b/internal/xds/clients/lrsclient/internal/internal.go
similarity index 100%
rename from xds/internal/clients/lrsclient/internal/internal.go
rename to internal/xds/clients/lrsclient/internal/internal.go
diff --git a/xds/internal/clients/lrsclient/load_store.go b/internal/xds/clients/lrsclient/load_store.go
similarity index 99%
rename from xds/internal/clients/lrsclient/load_store.go
rename to internal/xds/clients/lrsclient/load_store.go
index 6b35fd97..fcc6247e 100644
--- a/xds/internal/clients/lrsclient/load_store.go
+++ b/internal/xds/clients/lrsclient/load_store.go
@@ -24,8 +24,8 @@ import (
 	"sync/atomic"
 	"time"
 
-	"google.golang.org/grpc/xds/internal/clients"
-	lrsclientinternal "google.golang.org/grpc/xds/internal/clients/lrsclient/internal"
+	"google.golang.org/grpc/internal/xds/clients"
+	lrsclientinternal "google.golang.org/grpc/internal/xds/clients/lrsclient/internal"
 )
 
 // A LoadStore aggregates loads for multiple clusters and services that are
diff --git a/xds/internal/clients/lrsclient/logging.go b/internal/xds/clients/lrsclient/logging.go
similarity index 100%
rename from xds/internal/clients/lrsclient/logging.go
rename to internal/xds/clients/lrsclient/logging.go
diff --git a/xds/internal/clients/lrsclient/lrs_stream.go b/internal/xds/clients/lrsclient/lrs_stream.go
similarity index 99%
rename from xds/internal/clients/lrsclient/lrs_stream.go
rename to internal/xds/clients/lrsclient/lrs_stream.go
index bb275bdb..9a95983b 100644
--- a/xds/internal/clients/lrsclient/lrs_stream.go
+++ b/internal/xds/clients/lrsclient/lrs_stream.go
@@ -27,7 +27,7 @@ import (
 	"google.golang.org/grpc/internal/backoff"
 	igrpclog "google.golang.org/grpc/internal/grpclog"
 	"google.golang.org/grpc/internal/pretty"
-	"google.golang.org/grpc/xds/internal/clients"
+	"google.golang.org/grpc/internal/xds/clients"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/durationpb"
 
diff --git a/xds/internal/clients/lrsclient/lrsclient.go b/internal/xds/clients/lrsclient/lrsclient.go
similarity index 94%
rename from xds/internal/clients/lrsclient/lrsclient.go
rename to internal/xds/clients/lrsclient/lrsclient.go
index af163e22..3e459495 100644
--- a/xds/internal/clients/lrsclient/lrsclient.go
+++ b/internal/xds/clients/lrsclient/lrsclient.go
@@ -30,9 +30,9 @@ import (
 
 	"google.golang.org/grpc/grpclog"
 	igrpclog "google.golang.org/grpc/internal/grpclog"
-	"google.golang.org/grpc/xds/internal/clients"
-	clientsinternal "google.golang.org/grpc/xds/internal/clients/internal"
-	"google.golang.org/grpc/xds/internal/clients/internal/backoff"
+	"google.golang.org/grpc/internal/xds/clients"
+	clientsinternal "google.golang.org/grpc/internal/xds/clients/internal"
+	"google.golang.org/grpc/internal/xds/clients/internal/backoff"
 )
 
 const (
@@ -61,10 +61,7 @@ type LRSClient struct {
 
 // New returns a new LRS Client configured with the provided config.
 func New(config Config) (*LRSClient, error) {
-	switch {
-	case config.Node.ID == "":
-		return nil, errors.New("lrsclient: node ID in node is empty")
-	case config.TransportBuilder == nil:
+	if config.TransportBuilder == nil {
 		return nil, errors.New("lrsclient: transport builder is nil")
 	}
 
diff --git a/xds/internal/clients/lrsclient/lrsconfig.go b/internal/xds/clients/lrsclient/lrsconfig.go
similarity index 95%
rename from xds/internal/clients/lrsclient/lrsconfig.go
rename to internal/xds/clients/lrsclient/lrsconfig.go
index c4862ff7..6dde1b46 100644
--- a/xds/internal/clients/lrsclient/lrsconfig.go
+++ b/internal/xds/clients/lrsclient/lrsconfig.go
@@ -19,7 +19,7 @@
 package lrsclient
 
 import (
-	"google.golang.org/grpc/xds/internal/clients"
+	"google.golang.org/grpc/internal/xds/clients"
 )
 
 // Config is used to configure an LRS client. After one has been passed to the
diff --git a/xds/internal/clients/transport_builder.go b/internal/xds/clients/transport_builder.go
similarity index 100%
rename from xds/internal/clients/transport_builder.go
rename to internal/xds/clients/transport_builder.go
diff --git a/xds/internal/clients/xdsclient/ads_stream.go b/internal/xds/clients/xdsclient/ads_stream.go
similarity index 97%
rename from xds/internal/clients/xdsclient/ads_stream.go
rename to internal/xds/clients/xdsclient/ads_stream.go
index 9f5e99ac..83f8a5df 100644
--- a/xds/internal/clients/xdsclient/ads_stream.go
+++ b/internal/xds/clients/xdsclient/ads_stream.go
@@ -27,11 +27,11 @@ import (
 
 	"google.golang.org/grpc/grpclog"
 	igrpclog "google.golang.org/grpc/internal/grpclog"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/internal/backoff"
-	"google.golang.org/grpc/xds/internal/clients/internal/buffer"
-	"google.golang.org/grpc/xds/internal/clients/internal/pretty"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/internal/backoff"
+	"google.golang.org/grpc/internal/xds/clients/internal/buffer"
+	"google.golang.org/grpc/internal/xds/clients/internal/pretty"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
 
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
@@ -71,7 +71,6 @@ type adsStreamEventHandler interface {
 	onStreamError(error)                           // Called when the ADS stream breaks.
 	onWatchExpiry(ResourceType, string)            // Called when the watch timer expires for a resource.
 	onResponse(response, func()) ([]string, error) // Called when a response is received on the ADS stream.
-	onRequest(typeURL string)                      // Called when a request is about to be sent on the ADS stream.
 }
 
 // state corresponding to a resource type.
@@ -445,11 +444,6 @@ func (s *adsStreamImpl) sendMessageLocked(stream clients.Stream, names []string,
 		}
 	}
 
-	// Call the event handler to remove unsubscribed cache entries. It is to
-	// ensure the cache entries are deleted even if discovery request fails. In
-	// case of failure when the stream restarts, nonce is reset anyways.
-	s.eventHandler.onRequest(url)
-
 	msg, err := proto.Marshal(req)
 	if err != nil {
 		s.logger.Warningf("Failed to marshal DiscoveryRequest: %v", err)
diff --git a/xds/internal/clients/xdsclient/authority.go b/internal/xds/clients/xdsclient/authority.go
similarity index 93%
rename from xds/internal/clients/xdsclient/authority.go
rename to internal/xds/clients/xdsclient/authority.go
index 35411d02..49a4480c 100644
--- a/xds/internal/clients/xdsclient/authority.go
+++ b/internal/xds/clients/xdsclient/authority.go
@@ -27,10 +27,10 @@ import (
 
 	"google.golang.org/grpc/grpclog"
 	igrpclog "google.golang.org/grpc/internal/grpclog"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/internal/syncutil"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/metrics"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/internal/syncutil"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/metrics"
 	"google.golang.org/protobuf/types/known/anypb"
 	"google.golang.org/protobuf/types/known/timestamppb"
 
@@ -294,9 +294,6 @@ func (a *authority) fallbackToServer(xc *xdsChannelWithConfig) bool {
 	// Subscribe to all existing resources from the new management server.
 	for typ, resources := range a.resources {
 		for name, state := range resources {
-			if len(state.watchers) == 0 {
-				continue
-			}
 			if a.logger.V(2) {
 				a.logger.Infof("Resubscribing to resource of type %q and name %q", typ.TypeName, name)
 			}
@@ -686,17 +683,6 @@ func (a *authority) watchResource(rType ResourceType, resourceName string, watch
 			}
 			resources[resourceName] = state
 			xdsChannel.channel.subscribe(rType, resourceName)
-		} else if len(state.watchers) == 0 {
-			if a.logger.V(2) {
-				a.logger.Infof("Re-watch for type %q, resource name %q before unsubscription", rType.TypeName, resourceName)
-			}
-			// Add the active channel to the resource's channel configs if not
-			// already present.
-			state.xdsChannelConfigs[xdsChannel] = true
-			// Ensure the resource is subscribed on the active channel. We do this
-			// even if resource is present in cache as re-watches  might occur
-			// after unsubscribes or channel changes.
-			xdsChannel.channel.subscribe(rType, resourceName)
 		}
 		// Always add the new watcher to the set of watchers.
 		state.watchers[watcher] = true
@@ -774,16 +760,32 @@ func (a *authority) unwatchResource(rType ResourceType, resourceName string, wat
 			}
 
 			// There are no more watchers for this resource. Unsubscribe this
-			// resource from all channels where it was subscribed to but do not
-			// delete the state associated with it in case the resource is
-			// re-requested later before un-subscription request is completed by
-			// the management server.
+			// resource from all channels where it was subscribed to and delete
+			// the state associated with it.
 			if a.logger.V(2) {
 				a.logger.Infof("Removing last watch for resource name %q", resourceName)
 			}
 			for xcc := range state.xdsChannelConfigs {
 				xcc.channel.unsubscribe(rType, resourceName)
 			}
+			delete(resources, resourceName)
+
+			// If there are no more watchers for this resource type, delete the
+			// resource type from the top-level map.
+			if len(resources) == 0 {
+				if a.logger.V(2) {
+					a.logger.Infof("Removing last watch for resource type %q", rType.TypeName)
+				}
+				delete(a.resources, rType)
+			}
+			// If there are no more watchers for any resource type, release the
+			// reference to the xdsChannels.
+			if len(a.resources) == 0 {
+				if a.logger.V(2) {
+					a.logger.Infof("Removing last watch for for any resource type, releasing reference to the xdsChannel")
+				}
+				a.closeXDSChannels()
+			}
 		}, func() { close(done) })
 		<-done
 	})
@@ -835,7 +837,7 @@ func (a *authority) closeXDSChannels() {
 func (a *authority) watcherExistsForUncachedResource() bool {
 	for _, resourceStates := range a.resources {
 		for _, state := range resourceStates {
-			if len(state.watchers) > 0 && state.md.Status == xdsresource.ServiceStatusRequested {
+			if state.md.Status == xdsresource.ServiceStatusRequested {
 				return true
 			}
 		}
@@ -867,9 +869,6 @@ func (a *authority) resourceConfig() []*v3statuspb.ClientConfig_GenericXdsConfig
 	for rType, resourceStates := range a.resources {
 		typeURL := rType.TypeURL
 		for name, state := range resourceStates {
-			if len(state.watchers) == 0 {
-				continue
-			}
 			var raw *anypb.Any
 			if state.cache != nil {
 				raw = &anypb.Any{TypeUrl: typeURL, Value: state.cache.Bytes()}
@@ -903,43 +902,6 @@ func (a *authority) close() {
 	}
 }
 
-// removeUnsubscribedCacheEntries iterates through all resources of the given type and
-// removes the state for resources that have no active watchers. This is called
-// after sending a discovery request to ensure that resources that were
-// unsubscribed (and thus have no watchers) are eventually removed from the
-// authority's cache.
-func (a *authority) removeUnsubscribedCacheEntries(rType ResourceType) {
-	a.xdsClientSerializer.TrySchedule(func(context.Context) {
-		resources := a.resources[rType]
-		if resources == nil {
-			return
-		}
-
-		for name, state := range resources {
-			if len(state.watchers) == 0 {
-				if a.logger.V(2) {
-					a.logger.Infof("Removing resource state for %q of type %q as it has no watchers", name, rType.TypeName)
-				}
-				delete(resources, name)
-			}
-		}
-
-		if len(resources) == 0 {
-			if a.logger.V(2) {
-				a.logger.Infof("Removing resource type %q from cache as it has no more resources", rType.TypeName)
-			}
-			delete(a.resources, rType)
-		}
-
-		if len(a.resources) == 0 {
-			if a.logger.V(2) {
-				a.logger.Infof("Removing last watch for any resource type, releasing reference to the xdsChannels")
-			}
-			a.closeXDSChannels()
-		}
-	})
-}
-
 func serviceStatusToProto(serviceStatus xdsresource.ServiceStatus) v3adminpb.ClientResourceStatus {
 	switch serviceStatus {
 	case xdsresource.ServiceStatusUnknown:
diff --git a/xds/internal/clients/xdsclient/channel.go b/internal/xds/clients/xdsclient/channel.go
similarity index 86%
rename from xds/internal/clients/xdsclient/channel.go
rename to internal/xds/clients/xdsclient/channel.go
index 3d65ce6b..e36e3622 100644
--- a/xds/internal/clients/xdsclient/channel.go
+++ b/internal/xds/clients/xdsclient/channel.go
@@ -26,11 +26,11 @@ import (
 
 	"google.golang.org/grpc/grpclog"
 	igrpclog "google.golang.org/grpc/internal/grpclog"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/internal"
-	"google.golang.org/grpc/xds/internal/clients/internal/backoff"
-	"google.golang.org/grpc/xds/internal/clients/internal/syncutil"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/internal"
+	"google.golang.org/grpc/internal/xds/clients/internal/backoff"
+	"google.golang.org/grpc/internal/xds/clients/internal/syncutil"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
 )
 
 const (
@@ -59,10 +59,6 @@ type xdsChannelEventHandler interface {
 	// adsResourceDoesNotExist is called when the xdsChannel determines that a
 	// requested ADS resource does not exist.
 	adsResourceDoesNotExist(ResourceType, string)
-
-	// adsResourceRemoveUnsubscribedCacheEntries is called when the xdsChannel
-	// needs to remove unsubscribed cache entries.
-	adsResourceRemoveUnsubscribedCacheEntries(ResourceType)
 }
 
 // xdsChannelOpts holds the options for creating a new xdsChannel.
@@ -140,32 +136,8 @@ type xdsChannel struct {
 }
 
 func (xc *xdsChannel) close() {
-	if xc.closed.HasFired() {
-		return
-	}
 	xc.closed.Fire()
-
-	// Get the resource types that this specific ADS stream was handling
-	// before stopping it.
-	//
-	// TODO: Revisit if we can avoid acquiring the lock of ads (another type).
-	xc.ads.mu.Lock()
-	typesHandledByStream := make([]ResourceType, 0, len(xc.ads.resourceTypeState))
-	for typ := range xc.ads.resourceTypeState {
-		typesHandledByStream = append(typesHandledByStream, typ)
-	}
-	xc.ads.mu.Unlock()
-
 	xc.ads.Stop()
-
-	// Schedule removeUnsubscribedCacheEntries for the types this stream was handling,
-	// on all authorities that were interested in this channel.
-	if _, ok := xc.eventHandler.(*channelState); ok {
-		for _, typ := range typesHandledByStream {
-			xc.eventHandler.adsResourceRemoveUnsubscribedCacheEntries(typ)
-		}
-	}
-
 	xc.transport.Close()
 	xc.logger.Infof("Shutdown")
 }
@@ -256,26 +228,6 @@ func (xc *xdsChannel) onResponse(resp response, onDone func()) ([]string, error)
 	return names, err
 }
 
-// onRequest invoked when a request is about to be sent on the ADS stream. It
-// removes the cache entries for the resource type that are no longer subscribed to.
-func (xc *xdsChannel) onRequest(typeURL string) {
-	if xc.closed.HasFired() {
-		if xc.logger.V(2) {
-			xc.logger.Infof("Received an update from the ADS stream on closed ADS stream")
-		}
-		return
-	}
-
-	// Lookup the resource parser based on the resource type.
-	rType, ok := xc.clientConfig.ResourceTypes[typeURL]
-	if !ok {
-		logger.Warningf("Resource type URL %q unknown in response from server", typeURL)
-		return
-	}
-
-	xc.eventHandler.adsResourceRemoveUnsubscribedCacheEntries(rType)
-}
-
 // decodeResponse decodes the resources in the given ADS response.
 //
 // The opts parameter provides configuration options for decoding the resources.
diff --git a/xds/internal/clients/xdsclient/clientimpl_watchers.go b/internal/xds/clients/xdsclient/clientimpl_watchers.go
similarity index 98%
rename from xds/internal/clients/xdsclient/clientimpl_watchers.go
rename to internal/xds/clients/xdsclient/clientimpl_watchers.go
index 913e313e..68b29295 100644
--- a/xds/internal/clients/xdsclient/clientimpl_watchers.go
+++ b/internal/xds/clients/xdsclient/clientimpl_watchers.go
@@ -21,7 +21,7 @@ package xdsclient
 import (
 	"fmt"
 
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
 )
 
 // wrappingWatcher is a wrapper around an xdsresource.ResourceWatcher that adds
diff --git a/xds/internal/clients/xdsclient/internal/internal.go b/internal/xds/clients/xdsclient/internal/internal.go
similarity index 86%
rename from xds/internal/clients/xdsclient/internal/internal.go
rename to internal/xds/clients/xdsclient/internal/internal.go
index 7adb6719..38d2473e 100644
--- a/xds/internal/clients/xdsclient/internal/internal.go
+++ b/internal/xds/clients/xdsclient/internal/internal.go
@@ -21,10 +21,6 @@ package internal
 import "time"
 
 var (
-	// WatchExpiryTimeout is the watch expiry timeout for xDS client. It can be
-	// overridden by tests to change the default watch expiry timeout.
-	WatchExpiryTimeout time.Duration
-
 	// StreamBackoff is the stream backoff for xDS client. It can be overridden
 	// by tests to change the default backoff strategy.
 	StreamBackoff func(int) time.Duration
diff --git a/xds/internal/clients/xdsclient/internal/xdsresource/ads_stream.go b/internal/xds/clients/xdsclient/internal/xdsresource/ads_stream.go
similarity index 100%
rename from xds/internal/clients/xdsclient/internal/xdsresource/ads_stream.go
rename to internal/xds/clients/xdsclient/internal/xdsresource/ads_stream.go
diff --git a/xds/internal/clients/xdsclient/internal/xdsresource/errors.go b/internal/xds/clients/xdsclient/internal/xdsresource/errors.go
similarity index 100%
rename from xds/internal/clients/xdsclient/internal/xdsresource/errors.go
rename to internal/xds/clients/xdsclient/internal/xdsresource/errors.go
diff --git a/xds/internal/clients/xdsclient/internal/xdsresource/name.go b/internal/xds/clients/xdsclient/internal/xdsresource/name.go
similarity index 100%
rename from xds/internal/clients/xdsclient/internal/xdsresource/name.go
rename to internal/xds/clients/xdsclient/internal/xdsresource/name.go
diff --git a/xds/internal/clients/xdsclient/internal/xdsresource/type.go b/internal/xds/clients/xdsclient/internal/xdsresource/type.go
similarity index 100%
rename from xds/internal/clients/xdsclient/internal/xdsresource/type.go
rename to internal/xds/clients/xdsclient/internal/xdsresource/type.go
diff --git a/xds/internal/clients/xdsclient/internal/xdsresource/version.go b/internal/xds/clients/xdsclient/internal/xdsresource/version.go
similarity index 100%
rename from xds/internal/clients/xdsclient/internal/xdsresource/version.go
rename to internal/xds/clients/xdsclient/internal/xdsresource/version.go
diff --git a/xds/internal/clients/xdsclient/logging.go b/internal/xds/clients/xdsclient/logging.go
similarity index 100%
rename from xds/internal/clients/xdsclient/logging.go
rename to internal/xds/clients/xdsclient/logging.go
diff --git a/xds/internal/clients/xdsclient/metrics/metrics.go b/internal/xds/clients/xdsclient/metrics/metrics.go
similarity index 100%
rename from xds/internal/clients/xdsclient/metrics/metrics.go
rename to internal/xds/clients/xdsclient/metrics/metrics.go
diff --git a/xds/internal/clients/xdsclient/resource_type.go b/internal/xds/clients/xdsclient/resource_type.go
similarity index 100%
rename from xds/internal/clients/xdsclient/resource_type.go
rename to internal/xds/clients/xdsclient/resource_type.go
diff --git a/xds/internal/clients/xdsclient/resource_watcher.go b/internal/xds/clients/xdsclient/resource_watcher.go
similarity index 100%
rename from xds/internal/clients/xdsclient/resource_watcher.go
rename to internal/xds/clients/xdsclient/resource_watcher.go
diff --git a/xds/internal/clients/xdsclient/xdsclient.go b/internal/xds/clients/xdsclient/xdsclient.go
similarity index 93%
rename from xds/internal/clients/xdsclient/xdsclient.go
rename to internal/xds/clients/xdsclient/xdsclient.go
index f111e62a..cc7d5c4e 100644
--- a/xds/internal/clients/xdsclient/xdsclient.go
+++ b/internal/xds/clients/xdsclient/xdsclient.go
@@ -39,13 +39,13 @@ import (
 	"time"
 
 	"google.golang.org/grpc/internal/grpclog"
-	"google.golang.org/grpc/xds/internal/clients"
-	clientsinternal "google.golang.org/grpc/xds/internal/clients/internal"
-	"google.golang.org/grpc/xds/internal/clients/internal/backoff"
-	"google.golang.org/grpc/xds/internal/clients/internal/syncutil"
-	xdsclientinternal "google.golang.org/grpc/xds/internal/clients/xdsclient/internal"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/metrics"
+	"google.golang.org/grpc/internal/xds/clients"
+	clientsinternal "google.golang.org/grpc/internal/xds/clients/internal"
+	"google.golang.org/grpc/internal/xds/clients/internal/backoff"
+	"google.golang.org/grpc/internal/xds/clients/internal/syncutil"
+	xdsclientinternal "google.golang.org/grpc/internal/xds/clients/xdsclient/internal"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/metrics"
 	"google.golang.org/protobuf/proto"
 
 	v3statuspb "github.com/envoyproxy/go-control-plane/envoy/service/status/v3"
@@ -61,7 +61,6 @@ var (
 )
 
 func init() {
-	xdsclientinternal.WatchExpiryTimeout = defaultWatchExpiryTimeout
 	xdsclientinternal.StreamBackoff = defaultExponentialBackoff
 	xdsclientinternal.ResourceWatchStateForTesting = resourceWatchStateForTesting
 }
@@ -108,7 +107,9 @@ func New(config Config) (*XDSClient, error) {
 	case config.Authorities == nil && config.Servers == nil:
 		return nil, errors.New("xdsclient: no servers or authorities specified")
 	}
-
+	if config.WatchExpiryTimeout == 0 {
+		config.WatchExpiryTimeout = defaultWatchExpiryTimeout
+	}
 	client, err := newClient(&config, name)
 	if err != nil {
 		return nil, err
@@ -116,12 +117,6 @@ func New(config Config) (*XDSClient, error) {
 	return client, nil
 }
 
-// SetWatchExpiryTimeoutForTesting override the default watch expiry timeout
-// with provided timeout value.
-func (c *XDSClient) SetWatchExpiryTimeoutForTesting(watchExpiryTimeout time.Duration) {
-	c.watchExpiryTimeout = watchExpiryTimeout
-}
-
 // newClient returns a new XDSClient with the given config.
 func newClient(config *Config, target string) (*XDSClient, error) {
 	ctx, cancel := context.WithCancel(context.Background())
@@ -130,7 +125,7 @@ func newClient(config *Config, target string) (*XDSClient, error) {
 		done:               syncutil.NewEvent(),
 		authorities:        make(map[string]*authority),
 		config:             config,
-		watchExpiryTimeout: xdsclientinternal.WatchExpiryTimeout,
+		watchExpiryTimeout: config.WatchExpiryTimeout,
 		backoff:            xdsclientinternal.StreamBackoff,
 		serializer:         syncutil.NewCallbackSerializer(ctx),
 		serializerClose:    cancel,
@@ -437,19 +432,6 @@ func (cs *channelState) adsResourceDoesNotExist(typ ResourceType, resourceName s
 	}
 }
 
-func (cs *channelState) adsResourceRemoveUnsubscribedCacheEntries(rType ResourceType) {
-	if cs.parent.done.HasFired() {
-		return
-	}
-
-	cs.parent.channelsMu.Lock()
-	defer cs.parent.channelsMu.Unlock()
-
-	for authority := range cs.interestedAuthorities {
-		authority.removeUnsubscribedCacheEntries(rType)
-	}
-}
-
 func resourceWatchStateForTesting(c *XDSClient, rType ResourceType, resourceName string) (xdsresource.ResourceWatchState, error) {
 	n := xdsresource.ParseName(resourceName)
 	a := c.getAuthorityForResource(n)
diff --git a/xds/internal/clients/xdsclient/xdsconfig.go b/internal/xds/clients/xdsclient/xdsconfig.go
similarity index 88%
rename from xds/internal/clients/xdsclient/xdsconfig.go
rename to internal/xds/clients/xdsclient/xdsconfig.go
index a276309d..9d376e50 100644
--- a/xds/internal/clients/xdsclient/xdsconfig.go
+++ b/internal/xds/clients/xdsclient/xdsconfig.go
@@ -19,7 +19,9 @@
 package xdsclient
 
 import (
-	"google.golang.org/grpc/xds/internal/clients"
+	"time"
+
+	"google.golang.org/grpc/internal/xds/clients"
 )
 
 // Config is used to configure an xDS client. After one has been passed to the
@@ -60,6 +62,13 @@ type Config struct {
 	// MetricsReporter is used to report registered metrics. If unset, no
 	// metrics will be reported.
 	MetricsReporter clients.MetricsReporter
+
+	// WatchExpiryTimeout is the duration after which a resource watch expires
+	// if the requested resource is not received from the management server.
+	// Most users will not need to set this. If zero, a default value of 15
+	// seconds is used as specified here:
+	// envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol#knowing-when-a-requested-resource-does-not-exist
+	WatchExpiryTimeout time.Duration
 }
 
 // ServerConfig contains configuration for an xDS management server.
diff --git a/xds/internal/clusterspecifier/cluster_specifier.go b/internal/xds/clusterspecifier/cluster_specifier.go
similarity index 100%
rename from xds/internal/clusterspecifier/cluster_specifier.go
rename to internal/xds/clusterspecifier/cluster_specifier.go
diff --git a/xds/internal/clusterspecifier/rls/rls.go b/internal/xds/clusterspecifier/rls/rls.go
similarity index 98%
rename from xds/internal/clusterspecifier/rls/rls.go
rename to internal/xds/clusterspecifier/rls/rls.go
index 50f58cd4..ce2c2253 100644
--- a/xds/internal/clusterspecifier/rls/rls.go
+++ b/internal/xds/clusterspecifier/rls/rls.go
@@ -26,7 +26,7 @@ import (
 	"google.golang.org/grpc/balancer"
 	"google.golang.org/grpc/internal"
 	rlspb "google.golang.org/grpc/internal/proto/grpc_lookup_v1"
-	"google.golang.org/grpc/xds/internal/clusterspecifier"
+	"google.golang.org/grpc/internal/xds/clusterspecifier"
 	"google.golang.org/protobuf/encoding/protojson"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
diff --git a/xds/internal/httpfilter/fault/fault.go b/internal/xds/httpfilter/fault/fault.go
similarity index 99%
rename from xds/internal/httpfilter/fault/fault.go
rename to internal/xds/httpfilter/fault/fault.go
index 0ffa9c82..3acd4aec 100644
--- a/xds/internal/httpfilter/fault/fault.go
+++ b/internal/xds/httpfilter/fault/fault.go
@@ -31,9 +31,9 @@ import (
 
 	"google.golang.org/grpc/codes"
 	iresolver "google.golang.org/grpc/internal/resolver"
+	"google.golang.org/grpc/internal/xds/httpfilter"
 	"google.golang.org/grpc/metadata"
 	"google.golang.org/grpc/status"
-	"google.golang.org/grpc/xds/internal/httpfilter"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 
diff --git a/xds/internal/httpfilter/httpfilter.go b/internal/xds/httpfilter/httpfilter.go
similarity index 100%
rename from xds/internal/httpfilter/httpfilter.go
rename to internal/xds/httpfilter/httpfilter.go
diff --git a/xds/internal/httpfilter/rbac/rbac.go b/internal/xds/httpfilter/rbac/rbac.go
similarity index 99%
rename from xds/internal/httpfilter/rbac/rbac.go
rename to internal/xds/httpfilter/rbac/rbac.go
index 260dad75..01550090 100644
--- a/xds/internal/httpfilter/rbac/rbac.go
+++ b/internal/xds/httpfilter/rbac/rbac.go
@@ -26,8 +26,8 @@ import (
 	"strings"
 
 	"google.golang.org/grpc/internal/resolver"
+	"google.golang.org/grpc/internal/xds/httpfilter"
 	"google.golang.org/grpc/internal/xds/rbac"
-	"google.golang.org/grpc/xds/internal/httpfilter"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 
diff --git a/xds/internal/httpfilter/router/router.go b/internal/xds/httpfilter/router/router.go
similarity index 98%
rename from xds/internal/httpfilter/router/router.go
rename to internal/xds/httpfilter/router/router.go
index a781523d..d3a498d6 100644
--- a/xds/internal/httpfilter/router/router.go
+++ b/internal/xds/httpfilter/router/router.go
@@ -23,7 +23,7 @@ import (
 	"fmt"
 
 	iresolver "google.golang.org/grpc/internal/resolver"
-	"google.golang.org/grpc/xds/internal/httpfilter"
+	"google.golang.org/grpc/internal/xds/httpfilter"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 
diff --git a/xds/internal/resolver/internal/internal.go b/internal/xds/resolver/internal/internal.go
similarity index 100%
rename from xds/internal/resolver/internal/internal.go
rename to internal/xds/resolver/internal/internal.go
diff --git a/xds/internal/resolver/logging.go b/internal/xds/resolver/logging.go
similarity index 100%
rename from xds/internal/resolver/logging.go
rename to internal/xds/resolver/logging.go
diff --git a/xds/internal/resolver/serviceconfig.go b/internal/xds/resolver/serviceconfig.go
similarity index 98%
rename from xds/internal/resolver/serviceconfig.go
rename to internal/xds/resolver/serviceconfig.go
index 40f038b8..2b4e5a05 100644
--- a/xds/internal/resolver/serviceconfig.go
+++ b/internal/xds/resolver/serviceconfig.go
@@ -35,11 +35,11 @@ import (
 	iringhash "google.golang.org/grpc/internal/ringhash"
 	"google.golang.org/grpc/internal/serviceconfig"
 	"google.golang.org/grpc/internal/wrr"
+	"google.golang.org/grpc/internal/xds/balancer/clustermanager"
+	"google.golang.org/grpc/internal/xds/httpfilter"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/grpc/metadata"
 	"google.golang.org/grpc/status"
-	"google.golang.org/grpc/xds/internal/balancer/clustermanager"
-	"google.golang.org/grpc/xds/internal/httpfilter"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
 )
 
 const (
diff --git a/xds/internal/resolver/watch_service.go b/internal/xds/resolver/watch_service.go
similarity index 98%
rename from xds/internal/resolver/watch_service.go
rename to internal/xds/resolver/watch_service.go
index e8d52d0e..44b885c4 100644
--- a/xds/internal/resolver/watch_service.go
+++ b/internal/xds/resolver/watch_service.go
@@ -21,7 +21,7 @@ package resolver
 import (
 	"context"
 
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 )
 
 type listenerWatcher struct {
diff --git a/xds/internal/resolver/xds_resolver.go b/internal/xds/resolver/xds_resolver.go
similarity index 99%
rename from xds/internal/resolver/xds_resolver.go
rename to internal/xds/resolver/xds_resolver.go
index a66719d0..70291f8c 100644
--- a/xds/internal/resolver/xds_resolver.go
+++ b/internal/xds/resolver/xds_resolver.go
@@ -33,10 +33,10 @@ import (
 	iresolver "google.golang.org/grpc/internal/resolver"
 	"google.golang.org/grpc/internal/wrr"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	rinternal "google.golang.org/grpc/internal/xds/resolver/internal"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/grpc/resolver"
-	rinternal "google.golang.org/grpc/xds/internal/resolver/internal"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
 )
 
 // Scheme is the xDS resolver's scheme.
diff --git a/xds/internal/server/conn_wrapper.go b/internal/xds/server/conn_wrapper.go
similarity index 99%
rename from xds/internal/server/conn_wrapper.go
rename to internal/xds/server/conn_wrapper.go
index 92d07e7f..840da7ca 100644
--- a/xds/internal/server/conn_wrapper.go
+++ b/internal/xds/server/conn_wrapper.go
@@ -28,7 +28,7 @@ import (
 	"google.golang.org/grpc/credentials/tls/certprovider"
 	xdsinternal "google.golang.org/grpc/internal/credentials/xds"
 	"google.golang.org/grpc/internal/transport"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 )
 
 // connWrapper is a thin wrapper around a net.Conn returned by Accept(). It
diff --git a/xds/internal/server/listener_wrapper.go b/internal/xds/server/listener_wrapper.go
similarity index 99%
rename from xds/internal/server/listener_wrapper.go
rename to internal/xds/server/listener_wrapper.go
index 2c32ace8..1f7da611 100644
--- a/xds/internal/server/listener_wrapper.go
+++ b/internal/xds/server/listener_wrapper.go
@@ -33,7 +33,7 @@ import (
 	internalgrpclog "google.golang.org/grpc/internal/grpclog"
 	"google.golang.org/grpc/internal/grpcsync"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 )
 
 var (
diff --git a/xds/internal/server/rds_handler.go b/internal/xds/server/rds_handler.go
similarity index 98%
rename from xds/internal/server/rds_handler.go
rename to internal/xds/server/rds_handler.go
index 4b8eb22d..bf78c37c 100644
--- a/xds/internal/server/rds_handler.go
+++ b/internal/xds/server/rds_handler.go
@@ -22,7 +22,7 @@ import (
 	"sync"
 
 	igrpclog "google.golang.org/grpc/internal/grpclog"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 )
 
 // rdsHandler handles any RDS queries that need to be started for a given server
diff --git a/internal/xds/xds.go b/internal/xds/xds.go
index 024c388b..b9a4ec90 100644
--- a/internal/xds/xds.go
+++ b/internal/xds/xds.go
@@ -14,12 +14,17 @@
  * limitations under the License.
  */
 
-// Package xds contains methods to Get/Set handshake cluster names. It is separated
-// out from the top level /internal package to avoid circular dependencies.
+// Package xds contains functions, structs, and utilities for working with
+// handshake cluster names, as well as shared components used by xds balancers
+// and resolvers. It is separated from the top-level /internal package to
+// avoid circular dependencies.
 package xds
 
 import (
+	"fmt"
+
 	"google.golang.org/grpc/attributes"
+	"google.golang.org/grpc/internal/xds/clients"
 	"google.golang.org/grpc/resolver"
 )
 
@@ -40,3 +45,60 @@ func GetXDSHandshakeClusterName(attr *attributes.Attributes) (string, bool) {
 	name, ok := v.(string)
 	return name, ok
 }
+
+// LocalityString generates a string representation of clients.Locality in the
+// format specified in gRFC A76.
+func LocalityString(l clients.Locality) string {
+	return fmt.Sprintf("{region=%q, zone=%q, sub_zone=%q}", l.Region, l.Zone, l.SubZone)
+}
+
+// IsLocalityEqual allows the values to be compared by Attributes.Equal.
+func IsLocalityEqual(l clients.Locality, o any) bool {
+	ol, ok := o.(clients.Locality)
+	if !ok {
+		return false
+	}
+	return l.Region == ol.Region && l.Zone == ol.Zone && l.SubZone == ol.SubZone
+}
+
+// LocalityFromString converts a string representation of clients.locality as
+// specified in gRFC A76, into a LocalityID struct.
+func LocalityFromString(s string) (ret clients.Locality, _ error) {
+	_, err := fmt.Sscanf(s, "{region=%q, zone=%q, sub_zone=%q}", &ret.Region, &ret.Zone, &ret.SubZone)
+	if err != nil {
+		return clients.Locality{}, fmt.Errorf("%s is not a well formatted locality ID, error: %v", s, err)
+	}
+	return ret, nil
+}
+
+type localityKeyType string
+
+const localityKey = localityKeyType("grpc.xds.internal.address.locality")
+
+// GetLocalityID returns the locality ID of addr.
+func GetLocalityID(addr resolver.Address) clients.Locality {
+	path, _ := addr.BalancerAttributes.Value(localityKey).(clients.Locality)
+	return path
+}
+
+// SetLocalityID sets locality ID in addr to l.
+func SetLocalityID(addr resolver.Address, l clients.Locality) resolver.Address {
+	addr.BalancerAttributes = addr.BalancerAttributes.WithValue(localityKey, l)
+	return addr
+}
+
+// SetLocalityIDInEndpoint sets locality ID in endpoint to l.
+func SetLocalityIDInEndpoint(endpoint resolver.Endpoint, l clients.Locality) resolver.Endpoint {
+	endpoint.Attributes = endpoint.Attributes.WithValue(localityKey, l)
+	return endpoint
+}
+
+// ResourceTypeMapForTesting maps TypeUrl to corresponding ResourceType.
+var ResourceTypeMapForTesting map[string]any
+
+// UnknownCSMLabels are TelemetryLabels emitted from CDS if CSM Telemetry Label
+// data is not present in the CDS Resource.
+var UnknownCSMLabels = map[string]string{
+	"csm.service_name":           "unknown",
+	"csm.service_namespace_name": "unknown",
+}
diff --git a/xds/internal/xdsclient/attributes.go b/internal/xds/xdsclient/attributes.go
similarity index 100%
rename from xds/internal/xdsclient/attributes.go
rename to internal/xds/xdsclient/attributes.go
diff --git a/xds/internal/xdsclient/client.go b/internal/xds/xdsclient/client.go
similarity index 95%
rename from xds/internal/xdsclient/client.go
rename to internal/xds/xdsclient/client.go
index 42f1a28f..51427316 100644
--- a/xds/internal/xdsclient/client.go
+++ b/internal/xds/xdsclient/client.go
@@ -25,8 +25,8 @@ import (
 
 	v3statuspb "github.com/envoyproxy/go-control-plane/envoy/service/status/v3"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/clients/lrsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients/lrsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 )
 
 // XDSClient is a full fledged gRPC client which queries a set of discovery APIs
diff --git a/xds/internal/xdsclient/clientimpl.go b/internal/xds/xdsclient/clientimpl.go
similarity index 87%
rename from xds/internal/xdsclient/clientimpl.go
rename to internal/xds/xdsclient/clientimpl.go
index f7610341..b1f79799 100644
--- a/xds/internal/xdsclient/clientimpl.go
+++ b/internal/xds/xdsclient/clientimpl.go
@@ -29,12 +29,12 @@ import (
 	"google.golang.org/grpc/internal/grpclog"
 	"google.golang.org/grpc/internal/xds/bootstrap"
 
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/lrsclient"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/metrics"
 	xdsbootstrap "google.golang.org/grpc/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/lrsclient"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/metrics"
 )
 
 const (
@@ -120,8 +120,8 @@ func (mr *metricsReporter) ReportMetric(metric any) {
 	}
 }
 
-func newClientImpl(config *bootstrap.Config, metricsRecorder estats.MetricsRecorder, target string) (*clientImpl, error) {
-	gConfig, err := buildXDSClientConfig(config, metricsRecorder, target)
+func newClientImpl(config *bootstrap.Config, metricsRecorder estats.MetricsRecorder, target string, watchExpiryTimeout time.Duration) (*clientImpl, error) {
+	gConfig, err := buildXDSClientConfig(config, metricsRecorder, target, watchExpiryTimeout)
 	if err != nil {
 		return nil, err
 	}
@@ -129,7 +129,21 @@ func newClientImpl(config *bootstrap.Config, metricsRecorder estats.MetricsRecor
 	if err != nil {
 		return nil, err
 	}
-	c := &clientImpl{XDSClient: client, xdsClientConfig: gConfig, bootstrapConfig: config, target: target, refCount: 1}
+	lrsC, err := lrsclient.New(lrsclient.Config{
+		Node:             gConfig.Node,
+		TransportBuilder: gConfig.TransportBuilder,
+	})
+	if err != nil {
+		return nil, err
+	}
+	c := &clientImpl{
+		XDSClient:       client,
+		xdsClientConfig: gConfig,
+		bootstrapConfig: config,
+		target:          target,
+		refCount:        1,
+		lrsClient:       lrsC,
+	}
 	c.logger = prefixLogger(c)
 	return c, nil
 }
@@ -149,7 +163,7 @@ func (c *clientImpl) decrRef() int32 {
 }
 
 // buildXDSClientConfig builds the xdsclient.Config from the bootstrap.Config.
-func buildXDSClientConfig(config *bootstrap.Config, metricsRecorder estats.MetricsRecorder, target string) (xdsclient.Config, error) {
+func buildXDSClientConfig(config *bootstrap.Config, metricsRecorder estats.MetricsRecorder, target string, watchExpiryTimeout time.Duration) (xdsclient.Config, error) {
 	grpcTransportConfigs := make(map[string]grpctransport.Config)
 	gServerCfgMap := make(map[xdsclient.ServerConfig]*bootstrap.ServerConfig)
 
@@ -204,12 +218,13 @@ func buildXDSClientConfig(config *bootstrap.Config, metricsRecorder estats.Metri
 	}
 
 	return xdsclient.Config{
-		Authorities:      gAuthorities,
-		Servers:          gServerCfgs,
-		Node:             gNode,
-		TransportBuilder: grpctransport.NewBuilder(grpcTransportConfigs),
-		ResourceTypes:    supportedResourceTypes(config, gServerCfgMap),
-		MetricsReporter:  &metricsReporter{recorder: metricsRecorder, target: target},
+		Authorities:        gAuthorities,
+		Servers:            gServerCfgs,
+		Node:               gNode,
+		TransportBuilder:   grpctransport.NewBuilder(grpcTransportConfigs),
+		ResourceTypes:      supportedResourceTypes(config, gServerCfgMap),
+		MetricsReporter:    &metricsReporter{recorder: metricsRecorder, target: target},
+		WatchExpiryTimeout: watchExpiryTimeout,
 	}, nil
 }
 
diff --git a/xds/internal/xdsclient/clientimpl_loadreport.go b/internal/xds/xdsclient/clientimpl_loadreport.go
similarity index 73%
rename from xds/internal/xdsclient/clientimpl_loadreport.go
rename to internal/xds/xdsclient/clientimpl_loadreport.go
index 39004ae7..ffd0c90b 100644
--- a/xds/internal/xdsclient/clientimpl_loadreport.go
+++ b/internal/xds/xdsclient/clientimpl_loadreport.go
@@ -22,9 +22,9 @@ import (
 	"sync"
 
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/lrsclient"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/lrsclient"
 )
 
 // ReportLoad starts a load reporting stream to the given server. All load
@@ -32,18 +32,6 @@ import (
 //
 // It returns a lrsclient.LoadStore for the user to report loads.
 func (c *clientImpl) ReportLoad(server *bootstrap.ServerConfig) (*lrsclient.LoadStore, func(context.Context)) {
-	if c.lrsClient == nil {
-		lrsC, err := lrsclient.New(lrsclient.Config{
-			Node:             c.xdsClientConfig.Node,
-			TransportBuilder: c.xdsClientConfig.TransportBuilder,
-		})
-		if err != nil {
-			c.logger.Warningf("Failed to create an lrs client to the management server to report load: %v", server, err)
-			return nil, func(context.Context) {}
-		}
-		c.lrsClient = lrsC
-	}
-
 	load, err := c.lrsClient.ReportLoad(clients.ServerIdentifier{
 		ServerURI: server.ServerURI(),
 		Extensions: grpctransport.ServerIdentifierExtension{
diff --git a/xds/internal/xdsclient/clientimpl_watchers.go b/internal/xds/xdsclient/clientimpl_watchers.go
similarity index 95%
rename from xds/internal/xdsclient/clientimpl_watchers.go
rename to internal/xds/xdsclient/clientimpl_watchers.go
index 29435993..398de1ed 100644
--- a/xds/internal/xdsclient/clientimpl_watchers.go
+++ b/internal/xds/xdsclient/clientimpl_watchers.go
@@ -18,7 +18,7 @@
 package xdsclient
 
 import (
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 )
 
 // WatchResource uses xDS to discover the resource associated with the provided
diff --git a/xds/internal/xdsclient/internal/internal.go b/internal/xds/xdsclient/internal/internal.go
similarity index 100%
rename from xds/internal/xdsclient/internal/internal.go
rename to internal/xds/xdsclient/internal/internal.go
diff --git a/xds/internal/xdsclient/logging.go b/internal/xds/xdsclient/logging.go
similarity index 100%
rename from xds/internal/xdsclient/logging.go
rename to internal/xds/xdsclient/logging.go
diff --git a/xds/internal/xdsclient/pool.go b/internal/xds/xdsclient/pool.go
similarity index 97%
rename from xds/internal/xdsclient/pool.go
rename to internal/xds/xdsclient/pool.go
index d1cc8476..eb0197e0 100644
--- a/xds/internal/xdsclient/pool.go
+++ b/internal/xds/xdsclient/pool.go
@@ -99,7 +99,7 @@ func NewPool(config *bootstrap.Config) *Pool {
 // expected to invoke once they are done using the client.  It is safe for the
 // caller to invoke this close function multiple times.
 func (p *Pool) NewClient(name string, metricsRecorder estats.MetricsRecorder) (XDSClient, func(), error) {
-	return p.newRefCounted(name, metricsRecorder)
+	return p.newRefCounted(name, metricsRecorder, defaultWatchExpiryTimeout)
 }
 
 // NewClientForTesting returns an xDS client configured with the provided
@@ -126,11 +126,10 @@ func (p *Pool) NewClientForTesting(opts OptionsForTesting) (XDSClient, func(), e
 	if opts.MetricsRecorder == nil {
 		opts.MetricsRecorder = istats.NewMetricsRecorderList(nil)
 	}
-	c, cancel, err := p.newRefCounted(opts.Name, opts.MetricsRecorder)
+	c, cancel, err := p.newRefCounted(opts.Name, opts.MetricsRecorder, opts.WatchExpiryTimeout)
 	if err != nil {
 		return nil, nil, err
 	}
-	c.SetWatchExpiryTimeoutForTesting(opts.WatchExpiryTimeout)
 	return c, cancel, nil
 }
 
@@ -252,7 +251,7 @@ func (p *Pool) clientRefCountedClose(name string) {
 // newRefCounted creates a new reference counted xDS client implementation for
 // name, if one does not exist already. If an xDS client for the given name
 // exists, it gets a reference to it and returns it.
-func (p *Pool) newRefCounted(name string, metricsRecorder estats.MetricsRecorder) (*clientImpl, func(), error) {
+func (p *Pool) newRefCounted(name string, metricsRecorder estats.MetricsRecorder, watchExpiryTimeout time.Duration) (*clientImpl, func(), error) {
 	p.mu.Lock()
 	defer p.mu.Unlock()
 
@@ -276,7 +275,7 @@ func (p *Pool) newRefCounted(name string, metricsRecorder estats.MetricsRecorder
 		return c, sync.OnceFunc(func() { p.clientRefCountedClose(name) }), nil
 	}
 
-	c, err := newClientImpl(config, metricsRecorder, name)
+	c, err := newClientImpl(config, metricsRecorder, name, watchExpiryTimeout)
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/xds/internal/xdsclient/requests_counter.go b/internal/xds/xdsclient/requests_counter.go
similarity index 100%
rename from xds/internal/xdsclient/requests_counter.go
rename to internal/xds/xdsclient/requests_counter.go
diff --git a/xds/internal/xdsclient/resource_types.go b/internal/xds/xdsclient/resource_types.go
similarity index 91%
rename from xds/internal/xdsclient/resource_types.go
rename to internal/xds/xdsclient/resource_types.go
index 2f76c83c..88451ab8 100644
--- a/xds/internal/xdsclient/resource_types.go
+++ b/internal/xds/xdsclient/resource_types.go
@@ -19,9 +19,9 @@ package xdsclient
 
 import (
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 )
 
 func supportedResourceTypes(config *bootstrap.Config, gServerCfgMap map[xdsclient.ServerConfig]*bootstrap.ServerConfig) map[string]xdsclient.ResourceType {
diff --git a/xds/internal/xdsclient/xdslbregistry/converter/converter.go b/internal/xds/xdsclient/xdslbregistry/converter/converter.go
similarity index 98%
rename from xds/internal/xdsclient/xdslbregistry/converter/converter.go
rename to internal/xds/xdsclient/xdslbregistry/converter/converter.go
index 959c6588..d38b9db8 100644
--- a/xds/internal/xdsclient/xdslbregistry/converter/converter.go
+++ b/internal/xds/xdsclient/xdslbregistry/converter/converter.go
@@ -35,8 +35,8 @@ import (
 	"google.golang.org/grpc/balancer/weightedroundrobin"
 	iringhash "google.golang.org/grpc/internal/ringhash"
 	internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
-	"google.golang.org/grpc/xds/internal/balancer/wrrlocality"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdslbregistry"
+	"google.golang.org/grpc/internal/xds/balancer/wrrlocality"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdslbregistry"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/structpb"
 
diff --git a/xds/internal/xdsclient/xdslbregistry/xdslbregistry.go b/internal/xds/xdsclient/xdslbregistry/xdslbregistry.go
similarity index 100%
rename from xds/internal/xdsclient/xdslbregistry/xdslbregistry.go
rename to internal/xds/xdsclient/xdslbregistry/xdslbregistry.go
diff --git a/xds/internal/xdsclient/xdsresource/cluster_resource_type.go b/internal/xds/xdsclient/xdsresource/cluster_resource_type.go
similarity index 97%
rename from xds/internal/xdsclient/xdsresource/cluster_resource_type.go
rename to internal/xds/xdsclient/xdsresource/cluster_resource_type.go
index de633508..2a6a08f9 100644
--- a/xds/internal/xdsclient/xdsresource/cluster_resource_type.go
+++ b/internal/xds/xdsclient/xdsresource/cluster_resource_type.go
@@ -20,8 +20,8 @@ package xdsresource
 import (
 	"google.golang.org/grpc/internal/pretty"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	xdsclient "google.golang.org/grpc/xds/internal/clients/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	xdsclient "google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 )
diff --git a/xds/internal/xdsclient/xdsresource/endpoints_resource_type.go b/internal/xds/xdsclient/xdsresource/endpoints_resource_type.go
similarity index 97%
rename from xds/internal/xdsclient/xdsresource/endpoints_resource_type.go
rename to internal/xds/xdsclient/xdsresource/endpoints_resource_type.go
index dd8a3b38..7ca45ec6 100644
--- a/xds/internal/xdsclient/xdsresource/endpoints_resource_type.go
+++ b/internal/xds/xdsclient/xdsresource/endpoints_resource_type.go
@@ -19,8 +19,8 @@ package xdsresource
 
 import (
 	"google.golang.org/grpc/internal/pretty"
-	xdsclient "google.golang.org/grpc/xds/internal/clients/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	xdsclient "google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 )
diff --git a/xds/internal/xdsclient/xdsresource/errors.go b/internal/xds/xdsclient/xdsresource/errors.go
similarity index 100%
rename from xds/internal/xdsclient/xdsresource/errors.go
rename to internal/xds/xdsclient/xdsresource/errors.go
diff --git a/xds/internal/xdsclient/xdsresource/filter_chain.go b/internal/xds/xdsclient/xdsresource/filter_chain.go
similarity index 99%
rename from xds/internal/xdsclient/xdsresource/filter_chain.go
rename to internal/xds/xdsclient/xdsresource/filter_chain.go
index 46bbcaf4..76ace109 100644
--- a/xds/internal/xdsclient/xdsresource/filter_chain.go
+++ b/internal/xds/xdsclient/xdsresource/filter_chain.go
@@ -25,9 +25,9 @@ import (
 
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/internal/resolver"
+	"google.golang.org/grpc/internal/xds/httpfilter"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/grpc/status"
-	"google.golang.org/grpc/xds/internal/httpfilter"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/proto"
 
 	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
diff --git a/xds/internal/xdsclient/xdsresource/listener_resource_type.go b/internal/xds/xdsclient/xdsresource/listener_resource_type.go
similarity index 98%
rename from xds/internal/xdsclient/xdsresource/listener_resource_type.go
rename to internal/xds/xdsclient/xdsresource/listener_resource_type.go
index 29ff8870..100a06f9 100644
--- a/xds/internal/xdsclient/xdsresource/listener_resource_type.go
+++ b/internal/xds/xdsclient/xdsresource/listener_resource_type.go
@@ -22,8 +22,8 @@ import (
 
 	"google.golang.org/grpc/internal/pretty"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	xdsclient "google.golang.org/grpc/xds/internal/clients/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	xdsclient "google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 )
diff --git a/xds/internal/xdsclient/xdsresource/logging.go b/internal/xds/xdsclient/xdsresource/logging.go
similarity index 100%
rename from xds/internal/xdsclient/xdsresource/logging.go
rename to internal/xds/xdsclient/xdsresource/logging.go
diff --git a/xds/internal/xdsclient/xdsresource/matcher.go b/internal/xds/xdsclient/xdsresource/matcher.go
similarity index 100%
rename from xds/internal/xdsclient/xdsresource/matcher.go
rename to internal/xds/xdsclient/xdsresource/matcher.go
diff --git a/xds/internal/xdsclient/xdsresource/matcher_path.go b/internal/xds/xdsclient/xdsresource/matcher_path.go
similarity index 100%
rename from xds/internal/xdsclient/xdsresource/matcher_path.go
rename to internal/xds/xdsclient/xdsresource/matcher_path.go
diff --git a/internal/xds/xdsclient/xdsresource/metadata.go b/internal/xds/xdsclient/xdsresource/metadata.go
new file mode 100644
index 00000000..b56c309d
--- /dev/null
+++ b/internal/xds/xdsclient/xdsresource/metadata.go
@@ -0,0 +1,93 @@
+/*
+ *
+ * Copyright 2025 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 xdsresource
+
+import (
+	"fmt"
+	"net/netip"
+
+	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
+	"google.golang.org/protobuf/types/known/anypb"
+)
+
+func init() {
+	registerMetadataConverter("type.googleapis.com/envoy.config.core.v3.Address", proxyAddressConvertor{})
+}
+
+var (
+	// metdataRegistry is a map from proto type to metadataConverter.
+	metdataRegistry = make(map[string]metadataConverter)
+)
+
+// metadataConverter converts xds metadata entries in
+// Metadata.typed_filter_metadata into an internal form with the fields relevant
+// to gRPC.
+type metadataConverter interface {
+	// convert parses the Any proto into a concrete struct.
+	convert(*anypb.Any) (any, error)
+}
+
+// registerMetadataConverter registers the converter to the map keyed on a proto
+// type_url. Must be called at init time. Not thread safe.
+func registerMetadataConverter(protoType string, c metadataConverter) {
+	metdataRegistry[protoType] = c
+}
+
+// metadataConverterForType retrieves a converter based on key given.
+func metadataConverterForType(typeURL string) metadataConverter {
+	return metdataRegistry[typeURL]
+}
+
+// StructMetadataValue stores the values in a google.protobuf.Struct from
+// FilterMetadata.
+type StructMetadataValue struct {
+	// Data stores the parsed JSON representation of a google.protobuf.Struct.
+	Data map[string]any
+}
+
+// ProxyAddressMetadataValue holds the address parsed from the
+// envoy.config.core.v3.Address proto message, as specified in gRFC A86.
+type ProxyAddressMetadataValue struct {
+	// Address stores the proxy address configured (A86). It will be in the form
+	// of host:port. It has to be either IPv6 or IPv4.
+	Address string
+}
+
+// proxyAddressConvertor implements the metadataConverter interface to handle
+// the conversion of envoy.config.core.v3.Address protobuf messages into an
+// internal representation.
+type proxyAddressConvertor struct{}
+
+func (proxyAddressConvertor) convert(anyProto *anypb.Any) (any, error) {
+	addressProto := &v3corepb.Address{}
+	if err := anyProto.UnmarshalTo(addressProto); err != nil {
+		return nil, fmt.Errorf("failed to unmarshal resource from Any proto: %v", err)
+	}
+	socketaddress := addressProto.GetSocketAddress()
+	if socketaddress == nil {
+		return nil, fmt.Errorf("no socket_address field in metadata")
+	}
+	if _, err := netip.ParseAddr(socketaddress.GetAddress()); err != nil {
+		return nil, fmt.Errorf("address field is not a valid IPv4 or IPv6 address: %q", socketaddress.GetAddress())
+	}
+	portvalue := socketaddress.GetPortValue()
+	if portvalue == 0 {
+		return nil, fmt.Errorf("port value not set in socket_address")
+	}
+	return ProxyAddressMetadataValue{Address: parseAddress(socketaddress)}, nil
+}
diff --git a/xds/internal/xdsclient/xdsresource/name.go b/internal/xds/xdsclient/xdsresource/name.go
similarity index 100%
rename from xds/internal/xdsclient/xdsresource/name.go
rename to internal/xds/xdsclient/xdsresource/name.go
diff --git a/xds/internal/xdsclient/xdsresource/resource_type.go b/internal/xds/xdsclient/xdsresource/resource_type.go
similarity index 98%
rename from xds/internal/xdsclient/xdsresource/resource_type.go
rename to internal/xds/xdsclient/xdsresource/resource_type.go
index 4ecf63a1..2c591312 100644
--- a/xds/internal/xdsclient/xdsresource/resource_type.go
+++ b/internal/xds/xdsclient/xdsresource/resource_type.go
@@ -27,10 +27,10 @@ package xdsresource
 import (
 	"fmt"
 
+	xdsinternal "google.golang.org/grpc/internal/xds"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	xdsinternal "google.golang.org/grpc/xds/internal"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/types/known/anypb"
 )
 
diff --git a/xds/internal/xdsclient/xdsresource/route_config_resource_type.go b/internal/xds/xdsclient/xdsresource/route_config_resource_type.go
similarity index 97%
rename from xds/internal/xdsclient/xdsresource/route_config_resource_type.go
rename to internal/xds/xdsclient/xdsresource/route_config_resource_type.go
index 344932e9..912dc1b7 100644
--- a/xds/internal/xdsclient/xdsresource/route_config_resource_type.go
+++ b/internal/xds/xdsclient/xdsresource/route_config_resource_type.go
@@ -19,8 +19,8 @@ package xdsresource
 
 import (
 	"google.golang.org/grpc/internal/pretty"
-	xdsclient "google.golang.org/grpc/xds/internal/clients/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	xdsclient "google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 )
diff --git a/xds/internal/xdsclient/xdsresource/type.go b/internal/xds/xdsclient/xdsresource/type.go
similarity index 98%
rename from xds/internal/xdsclient/xdsresource/type.go
rename to internal/xds/xdsclient/xdsresource/type.go
index 99420410..7f85c1da 100644
--- a/xds/internal/xdsclient/xdsresource/type.go
+++ b/internal/xds/xdsclient/xdsresource/type.go
@@ -21,7 +21,7 @@ import (
 	"time"
 
 	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 )
diff --git a/xds/internal/xdsclient/xdsresource/type_cds.go b/internal/xds/xdsclient/xdsresource/type_cds.go
similarity index 100%
rename from xds/internal/xdsclient/xdsresource/type_cds.go
rename to internal/xds/xdsclient/xdsresource/type_cds.go
diff --git a/xds/internal/xdsclient/xdsresource/type_eds.go b/internal/xds/xdsclient/xdsresource/type_eds.go
similarity index 95%
rename from xds/internal/xdsclient/xdsresource/type_eds.go
rename to internal/xds/xdsclient/xdsresource/type_eds.go
index 12294626..a8d568e1 100644
--- a/xds/internal/xdsclient/xdsresource/type_eds.go
+++ b/internal/xds/xdsclient/xdsresource/type_eds.go
@@ -18,7 +18,7 @@
 package xdsresource
 
 import (
-	"google.golang.org/grpc/xds/internal/clients"
+	"google.golang.org/grpc/internal/xds/clients"
 	"google.golang.org/protobuf/types/known/anypb"
 )
 
@@ -53,6 +53,7 @@ type Endpoint struct {
 	HealthStatus EndpointHealthStatus
 	Weight       uint32
 	HashKey      string
+	Metadata     map[string]any
 }
 
 // Locality contains information of a locality.
@@ -61,6 +62,7 @@ type Locality struct {
 	ID        clients.Locality
 	Priority  uint32
 	Weight    uint32
+	Metadata  map[string]any
 }
 
 // EndpointsUpdate contains an EDS update.
diff --git a/xds/internal/xdsclient/xdsresource/type_lds.go b/internal/xds/xdsclient/xdsresource/type_lds.go
similarity index 98%
rename from xds/internal/xdsclient/xdsresource/type_lds.go
rename to internal/xds/xdsclient/xdsresource/type_lds.go
index a71e38ea..a2e057b0 100644
--- a/xds/internal/xdsclient/xdsresource/type_lds.go
+++ b/internal/xds/xdsclient/xdsresource/type_lds.go
@@ -20,7 +20,7 @@ package xdsresource
 import (
 	"time"
 
-	"google.golang.org/grpc/xds/internal/httpfilter"
+	"google.golang.org/grpc/internal/xds/httpfilter"
 	"google.golang.org/protobuf/types/known/anypb"
 )
 
diff --git a/xds/internal/xdsclient/xdsresource/type_rds.go b/internal/xds/xdsclient/xdsresource/type_rds.go
similarity index 98%
rename from xds/internal/xdsclient/xdsresource/type_rds.go
rename to internal/xds/xdsclient/xdsresource/type_rds.go
index 42da3912..44bc2789 100644
--- a/xds/internal/xdsclient/xdsresource/type_rds.go
+++ b/internal/xds/xdsclient/xdsresource/type_rds.go
@@ -22,9 +22,9 @@ import (
 	"time"
 
 	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/internal/xds/clusterspecifier"
+	"google.golang.org/grpc/internal/xds/httpfilter"
 	"google.golang.org/grpc/internal/xds/matcher"
-	"google.golang.org/grpc/xds/internal/clusterspecifier"
-	"google.golang.org/grpc/xds/internal/httpfilter"
 	"google.golang.org/protobuf/types/known/anypb"
 )
 
diff --git a/xds/internal/xdsclient/xdsresource/unmarshal_cds.go b/internal/xds/xdsclient/xdsresource/unmarshal_cds.go
similarity index 99%
rename from xds/internal/xdsclient/xdsresource/unmarshal_cds.go
rename to internal/xds/xdsclient/xdsresource/unmarshal_cds.go
index 43247c5b..d7133c99 100644
--- a/xds/internal/xdsclient/xdsresource/unmarshal_cds.go
+++ b/internal/xds/xdsclient/xdsresource/unmarshal_cds.go
@@ -36,8 +36,8 @@ import (
 	iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
 	"google.golang.org/grpc/internal/xds/bootstrap"
 	"google.golang.org/grpc/internal/xds/matcher"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdslbregistry"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdslbregistry"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 	"google.golang.org/protobuf/types/known/structpb"
diff --git a/xds/internal/xdsclient/xdsresource/unmarshal_eds.go b/internal/xds/xdsclient/xdsresource/unmarshal_eds.go
similarity index 81%
rename from xds/internal/xdsclient/xdsresource/unmarshal_eds.go
rename to internal/xds/xdsclient/xdsresource/unmarshal_eds.go
index d56b42dd..f0e73acd 100644
--- a/xds/internal/xdsclient/xdsresource/unmarshal_eds.go
+++ b/internal/xds/xdsclient/xdsresource/unmarshal_eds.go
@@ -28,8 +28,8 @@ import (
 	v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3"
 	"google.golang.org/grpc/internal/envconfig"
 	"google.golang.org/grpc/internal/pretty"
-	"google.golang.org/grpc/xds/internal"
-	"google.golang.org/grpc/xds/internal/clients"
+	xdsinternal "google.golang.org/grpc/internal/xds"
+	"google.golang.org/grpc/internal/xds/clients"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 )
@@ -108,11 +108,16 @@ func parseEndpoints(lbEndpoints []*v3endpointpb.LbEndpoint, uniqueEndpointAddrs
 			}
 			uniqueEndpointAddrs[a] = true
 		}
+		endpointMetadata, err := validateAndConstructMetadata(lbEndpoint.GetMetadata())
+		if err != nil {
+			return nil, err
+		}
 		endpoints = append(endpoints, Endpoint{
 			HealthStatus: EndpointHealthStatus(lbEndpoint.GetHealthStatus()),
 			Addresses:    addrs,
 			Weight:       weight,
 			HashKey:      hashKey(lbEndpoint),
+			Metadata:     endpointMetadata,
 		})
 	}
 	return endpoints, nil
@@ -170,7 +175,7 @@ func parseEDSRespProto(m *v3endpointpb.ClusterLoadAssignment) (EndpointsUpdate,
 			Zone:    l.Zone,
 			SubZone: l.SubZone,
 		}
-		lidStr := internal.LocalityString(lid)
+		lidStr := xdsinternal.LocalityString(lid)
 
 		// "Since an xDS configuration can place a given locality under multiple
 		// priorities, it is possible to see locality weight attributes with
@@ -190,11 +195,17 @@ func parseEDSRespProto(m *v3endpointpb.ClusterLoadAssignment) (EndpointsUpdate,
 		if err != nil {
 			return EndpointsUpdate{}, err
 		}
+		localityMetadata, err := validateAndConstructMetadata(locality.GetMetadata())
+		if err != nil {
+			return EndpointsUpdate{}, err
+		}
+
 		ret.Localities = append(ret.Localities, Locality{
 			ID:        lid,
 			Endpoints: endpoints,
 			Weight:    weight,
 			Priority:  priority,
+			Metadata:  localityMetadata,
 		})
 	}
 	for i := 0; i < len(priorities); i++ {
@@ -204,3 +215,36 @@ func parseEDSRespProto(m *v3endpointpb.ClusterLoadAssignment) (EndpointsUpdate,
 	}
 	return ret, nil
 }
+
+func validateAndConstructMetadata(metadataProto *v3corepb.Metadata) (map[string]any, error) {
+	// TODO(easwars): Find a better place for the environment variable check
+	// once A83 is implemented.
+	if !envconfig.XDSHTTPConnectEnabled || metadataProto == nil {
+		return nil, nil
+	}
+	metadata := make(map[string]any)
+	// First go through TypedFilterMetadata.
+	for key, anyProto := range metadataProto.GetTypedFilterMetadata() {
+		converter := metadataConverterForType(anyProto.GetTypeUrl())
+		// Ignore types we don't have a converter for.
+		if converter == nil {
+			continue
+		}
+		val, err := converter.convert(anyProto)
+		if err != nil {
+			// If the converter fails, nack the whole resource.
+			return nil, fmt.Errorf("metadata conversion for key %q and type %q failed: %v", key, anyProto.GetTypeUrl(), err)
+		}
+		metadata[key] = val
+	}
+
+	// Process FilterMetadata for any keys not already handled.
+	for key, structProto := range metadataProto.GetFilterMetadata() {
+		// Skip keys already added from TyperFilterMetadata.
+		if metadata[key] != nil {
+			continue
+		}
+		metadata[key] = StructMetadataValue{Data: structProto.AsMap()}
+	}
+	return metadata, nil
+}
diff --git a/xds/internal/xdsclient/xdsresource/unmarshal_lds.go b/internal/xds/xdsclient/xdsresource/unmarshal_lds.go
similarity index 99%
rename from xds/internal/xdsclient/xdsresource/unmarshal_lds.go
rename to internal/xds/xdsclient/xdsresource/unmarshal_lds.go
index 475300ce..25c607b4 100644
--- a/xds/internal/xdsclient/xdsresource/unmarshal_lds.go
+++ b/internal/xds/xdsclient/xdsresource/unmarshal_lds.go
@@ -27,7 +27,7 @@ import (
 	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
 	v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
-	"google.golang.org/grpc/xds/internal/httpfilter"
+	"google.golang.org/grpc/internal/xds/httpfilter"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 )
diff --git a/xds/internal/xdsclient/xdsresource/unmarshal_rds.go b/internal/xds/xdsclient/xdsresource/unmarshal_rds.go
similarity index 99%
rename from xds/internal/xdsclient/xdsresource/unmarshal_rds.go
rename to internal/xds/xdsclient/xdsresource/unmarshal_rds.go
index db862514..beab03eb 100644
--- a/xds/internal/xdsclient/xdsresource/unmarshal_rds.go
+++ b/internal/xds/xdsclient/xdsresource/unmarshal_rds.go
@@ -25,8 +25,8 @@ import (
 	"time"
 
 	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/internal/xds/clusterspecifier"
 	"google.golang.org/grpc/internal/xds/matcher"
-	"google.golang.org/grpc/xds/internal/clusterspecifier"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 
diff --git a/xds/internal/xdsclient/xdsresource/version/version.go b/internal/xds/xdsclient/xdsresource/version/version.go
similarity index 100%
rename from xds/internal/xdsclient/xdsresource/version/version.go
rename to internal/xds/xdsclient/xdsresource/version/version.go
diff --git a/interop/observability/Dockerfile b/interop/observability/Dockerfile
index e4e3a3c7..32cfb810 100644
--- a/interop/observability/Dockerfile
+++ b/interop/observability/Dockerfile
@@ -17,7 +17,7 @@
 # Stage 1: Build the interop test client and server
 #
 
-FROM golang:1.23-bullseye as build
+FROM golang:1.24-bullseye as build
 
 WORKDIR /grpc-go
 COPY . .
@@ -36,7 +36,7 @@ RUN go build -o server/ server/server.go && \
 #   with the given parameters.
 #
 
-FROM golang:1.23-bullseye
+FROM golang:1.24-bullseye
 
 ENV GRPC_GO_LOG_SEVERITY_LEVEL info
 ENV GRPC_GO_LOG_VERBOSITY_LEVEL 2
diff --git a/interop/observability/go.sum b/interop/observability/go.sum
index 471bf15d..4c056078 100644
--- a/interop/observability/go.sum
+++ b/interop/observability/go.sum
@@ -919,9 +919,11 @@ github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaB
 github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
 github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34=
 github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI=
+github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
 github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
 github.com/envoyproxy/go-control-plane/envoy v1.32.2/go.mod h1:eR2SOX2IedqlPvmiKjUH7Wu//S602JKI7HPC/L3SRq8=
 github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE=
+github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
 github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
 github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
@@ -1255,6 +1257,7 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
 github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
+github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
 github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
diff --git a/interop/xds/client/Dockerfile b/interop/xds/client/Dockerfile
index 47c4bccd..f170ab3c 100644
--- a/interop/xds/client/Dockerfile
+++ b/interop/xds/client/Dockerfile
@@ -16,7 +16,7 @@
 # following command from grpc-go directory:
 # docker build -t <TAG> -f interop/xds/client/Dockerfile .
 
-FROM golang:1.23-alpine as build
+FROM golang:1.25-alpine as build
 
 # Make a grpc-go directory and copy the repo into it.
 WORKDIR /go/src/grpc-go
diff --git a/interop/xds/server/Dockerfile b/interop/xds/server/Dockerfile
index a43bbe49..08fc4976 100644
--- a/interop/xds/server/Dockerfile
+++ b/interop/xds/server/Dockerfile
@@ -16,7 +16,7 @@
 # following command from grpc-go directory:
 # docker build -t <TAG> -f interop/xds/server/Dockerfile .
 
-FROM golang:1.23-alpine as build
+FROM golang:1.25-alpine as build
 
 # Make a grpc-go directory and copy the repo into it.
 WORKDIR /go/src/grpc-go
diff --git a/stream.go b/stream.go
index d9bbd4c5..0a0af896 100644
--- a/stream.go
+++ b/stream.go
@@ -549,6 +549,8 @@ type clientStream struct {
 
 	sentLast bool // sent an end stream
 
+	receivedFirstMsg bool // set after the first message is received
+
 	methodConfig *MethodConfig
 
 	ctx context.Context // the application's context, wrapped by stats/tracing
@@ -1144,11 +1146,16 @@ func (a *csAttempt) recvMsg(m any, payInfo *payloadInfo) (err error) {
 			if statusErr := a.transportStream.Status().Err(); statusErr != nil {
 				return statusErr
 			}
+			// Received no msg and status OK for non-server streaming rpcs.
+			if !cs.desc.ServerStreams && !cs.receivedFirstMsg {
+				return status.Error(codes.Internal, "cardinality violation: received no response message from non-server-streaming RPC")
+			}
 			return io.EOF // indicates successful end of stream.
 		}
 
 		return toRPCErr(err)
 	}
+	cs.receivedFirstMsg = true
 	if a.trInfo != nil {
 		a.mu.Lock()
 		if a.trInfo.tr != nil {
@@ -1177,7 +1184,7 @@ func (a *csAttempt) recvMsg(m any, payInfo *payloadInfo) (err error) {
 	} else if err != nil {
 		return toRPCErr(err)
 	}
-	return status.Errorf(codes.Internal, "cardinality violation: expected <EOF> for non server-streaming RPCs, but received another message")
+	return status.Error(codes.Internal, "cardinality violation: expected <EOF> for non server-streaming RPCs, but received another message")
 }
 
 func (a *csAttempt) finish(err error) {
@@ -1359,6 +1366,7 @@ type addrConnStream struct {
 	transport        transport.ClientTransport
 	ctx              context.Context
 	sentLast         bool
+	receivedFirstMsg bool
 	desc             *StreamDesc
 	codec            baseCodec
 	sendCompressorV0 Compressor
@@ -1484,10 +1492,15 @@ func (as *addrConnStream) RecvMsg(m any) (err error) {
 			if statusErr := as.transportStream.Status().Err(); statusErr != nil {
 				return statusErr
 			}
+			// Received no msg and status OK for non-server streaming rpcs.
+			if !as.desc.ServerStreams && !as.receivedFirstMsg {
+				return status.Error(codes.Internal, "cardinality violation: received no response message from non-server-streaming RPC")
+			}
 			return io.EOF // indicates successful end of stream.
 		}
 		return toRPCErr(err)
 	}
+	as.receivedFirstMsg = true
 
 	if as.desc.ServerStreams {
 		// Subsequent messages should be received by subsequent RecvMsg calls.
@@ -1501,7 +1514,7 @@ func (as *addrConnStream) RecvMsg(m any) (err error) {
 	} else if err != nil {
 		return toRPCErr(err)
 	}
-	return status.Errorf(codes.Internal, "cardinality violation: expected <EOF> for non server-streaming RPCs, but received another message")
+	return status.Error(codes.Internal, "cardinality violation: expected <EOF> for non server-streaming RPCs, but received another message")
 }
 
 func (as *addrConnStream) finish(err error) {
diff --git a/xds/csds/csds.go b/xds/csds/csds.go
index 3d8398a7..603819d6 100644
--- a/xds/csds/csds.go
+++ b/xds/csds/csds.go
@@ -31,8 +31,8 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/grpclog"
 	internalgrpclog "google.golang.org/grpc/internal/grpclog"
+	"google.golang.org/grpc/internal/xds/xdsclient"
 	"google.golang.org/grpc/status"
-	"google.golang.org/grpc/xds/internal/xdsclient"
 
 	v3statusgrpc "github.com/envoyproxy/go-control-plane/envoy/service/status/v3"
 	v3statuspb "github.com/envoyproxy/go-control-plane/envoy/service/status/v3"
diff --git a/xds/googledirectpath/googlec2p.go b/xds/googledirectpath/googlec2p.go
index f75c022d..f1a02649 100644
--- a/xds/googledirectpath/googlec2p.go
+++ b/xds/googledirectpath/googlec2p.go
@@ -38,8 +38,8 @@ import (
 	"google.golang.org/grpc/internal/googlecloud"
 	internalgrpclog "google.golang.org/grpc/internal/grpclog"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient"
 	"google.golang.org/grpc/resolver"
-	"google.golang.org/grpc/xds/internal/xdsclient"
 
 	_ "google.golang.org/grpc/xds" // To register xds resolvers and balancers.
 )
diff --git a/xds/internal/internal.go b/xds/internal/internal.go
deleted file mode 100644
index 9e0b7931..00000000
--- a/xds/internal/internal.go
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- *
- * Copyright 2019 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 internal contains functions/structs shared by xds
-// balancers/resolvers.
-package internal
-
-import (
-	"fmt"
-
-	"google.golang.org/grpc/resolver"
-	"google.golang.org/grpc/xds/internal/clients"
-)
-
-// LocalityString generates a string representation of clients.Locality in the
-// format specified in gRFC A76.
-func LocalityString(l clients.Locality) string {
-	return fmt.Sprintf("{region=%q, zone=%q, sub_zone=%q}", l.Region, l.Zone, l.SubZone)
-}
-
-// IsLocalityEqual allows the values to be compared by Attributes.Equal.
-func IsLocalityEqual(l clients.Locality, o any) bool {
-	ol, ok := o.(clients.Locality)
-	if !ok {
-		return false
-	}
-	return l.Region == ol.Region && l.Zone == ol.Zone && l.SubZone == ol.SubZone
-}
-
-// LocalityFromString converts a string representation of clients.locality as
-// specified in gRFC A76, into a LocalityID struct.
-func LocalityFromString(s string) (ret clients.Locality, _ error) {
-	_, err := fmt.Sscanf(s, "{region=%q, zone=%q, sub_zone=%q}", &ret.Region, &ret.Zone, &ret.SubZone)
-	if err != nil {
-		return clients.Locality{}, fmt.Errorf("%s is not a well formatted locality ID, error: %v", s, err)
-	}
-	return ret, nil
-}
-
-type localityKeyType string
-
-const localityKey = localityKeyType("grpc.xds.internal.address.locality")
-
-// GetLocalityID returns the locality ID of addr.
-func GetLocalityID(addr resolver.Address) clients.Locality {
-	path, _ := addr.BalancerAttributes.Value(localityKey).(clients.Locality)
-	return path
-}
-
-// SetLocalityID sets locality ID in addr to l.
-func SetLocalityID(addr resolver.Address, l clients.Locality) resolver.Address {
-	addr.BalancerAttributes = addr.BalancerAttributes.WithValue(localityKey, l)
-	return addr
-}
-
-// SetLocalityIDInEndpoint sets locality ID in endpoint to l.
-func SetLocalityIDInEndpoint(endpoint resolver.Endpoint, l clients.Locality) resolver.Endpoint {
-	endpoint.Attributes = endpoint.Attributes.WithValue(localityKey, l)
-	return endpoint
-}
-
-// ResourceTypeMapForTesting maps TypeUrl to corresponding ResourceType.
-var ResourceTypeMapForTesting map[string]any
-
-// UnknownCSMLabels are TelemetryLabels emitted from CDS if CSM Telemetry Label
-// data is not present in the CDS Resource.
-var UnknownCSMLabels = map[string]string{
-	"csm.service_name":           "unknown",
-	"csm.service_namespace_name": "unknown",
-}
diff --git a/xds/server.go b/xds/server.go
index 5baf91de..d49396ce 100644
--- a/xds/server.go
+++ b/xds/server.go
@@ -35,11 +35,11 @@ import (
 	istats "google.golang.org/grpc/internal/stats"
 	"google.golang.org/grpc/internal/transport"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/server"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/grpc/metadata"
 	"google.golang.org/grpc/status"
-	"google.golang.org/grpc/xds/internal/server"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
 )
 
 const serverPrefix = "[xds-server %p] "
diff --git a/xds/server_options.go b/xds/server_options.go
index 4e3e3e9e..c72d93ec 100644
--- a/xds/server_options.go
+++ b/xds/server_options.go
@@ -24,7 +24,7 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/connectivity"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient"
 )
 
 type serverOptions struct {
diff --git a/xds/xds.go b/xds/xds.go
index 943d09f1..c6a1f25f 100644
--- a/xds/xds.go
+++ b/xds/xds.go
@@ -38,13 +38,13 @@ import (
 	"google.golang.org/grpc/xds/csds"
 
 	_ "google.golang.org/grpc/credentials/tls/certprovider/pemfile"           // Register the file watcher certificate provider plugin.
-	_ "google.golang.org/grpc/xds/internal/balancer"                          // Register the balancers.
-	_ "google.golang.org/grpc/xds/internal/clusterspecifier/rls"              // Register the RLS cluster specifier plugin. Note that this does not register the RLS LB policy.
-	_ "google.golang.org/grpc/xds/internal/httpfilter/fault"                  // Register the fault injection filter.
-	_ "google.golang.org/grpc/xds/internal/httpfilter/rbac"                   // Register the RBAC filter.
-	_ "google.golang.org/grpc/xds/internal/httpfilter/router"                 // Register the router filter.
-	_ "google.golang.org/grpc/xds/internal/resolver"                          // Register the xds_resolver.
-	_ "google.golang.org/grpc/xds/internal/xdsclient/xdslbregistry/converter" // Register the xDS LB Registry Converters.
+	_ "google.golang.org/grpc/internal/xds/balancer"                          // Register the balancers.
+	_ "google.golang.org/grpc/internal/xds/clusterspecifier/rls"              // Register the RLS cluster specifier plugin. Note that this does not register the RLS LB policy.
+	_ "google.golang.org/grpc/internal/xds/httpfilter/fault"                  // Register the fault injection filter.
+	_ "google.golang.org/grpc/internal/xds/httpfilter/rbac"                   // Register the RBAC filter.
+	_ "google.golang.org/grpc/internal/xds/httpfilter/router"                 // Register the router filter.
+	_ "google.golang.org/grpc/internal/xds/resolver"                          // Register the xds_resolver.
+	_ "google.golang.org/grpc/internal/xds/xdsclient/xdslbregistry/converter" // Register the xDS LB Registry Converters.
 
 	v3statusgrpc "github.com/envoyproxy/go-control-plane/envoy/service/status/v3"
 )



Test patch:
diff --git a/internal/transport/handler_server_test.go b/internal/transport/handler_server_test.go
index 91102283..e64af274 100644
--- a/internal/transport/handler_server_test.go
+++ b/internal/transport/handler_server_test.go
@@ -35,6 +35,7 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/mem"
 	"google.golang.org/grpc/metadata"
+	"google.golang.org/grpc/stats"
 	"google.golang.org/grpc/status"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/protoadapt"
@@ -246,7 +247,26 @@ type handleStreamTest struct {
 	ht    *serverHandlerTransport
 }
 
-func newHandleStreamTest(t *testing.T) *handleStreamTest {
+type mockStatsHandler struct {
+	rpcStatsCh chan stats.RPCStats
+}
+
+func (h *mockStatsHandler) TagRPC(ctx context.Context, _ *stats.RPCTagInfo) context.Context {
+	return ctx
+}
+
+func (h *mockStatsHandler) HandleRPC(_ context.Context, s stats.RPCStats) {
+	h.rpcStatsCh <- s
+}
+
+func (h *mockStatsHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context {
+	return ctx
+}
+
+func (h *mockStatsHandler) HandleConn(context.Context, stats.ConnStats) {
+}
+
+func newHandleStreamTest(t *testing.T, statsHandlers []stats.Handler) *handleStreamTest {
 	bodyr, bodyw := io.Pipe()
 	req := &http.Request{
 		ProtoMajor: 2,
@@ -260,7 +280,7 @@ func newHandleStreamTest(t *testing.T) *handleStreamTest {
 		Body: bodyr,
 	}
 	rw := newTestHandlerResponseWriter().(testHandlerResponseWriter)
-	ht, err := NewServerHandlerTransport(rw, req, nil, mem.DefaultBufferPool())
+	ht, err := NewServerHandlerTransport(rw, req, statsHandlers, mem.DefaultBufferPool())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -273,7 +293,7 @@ func newHandleStreamTest(t *testing.T) *handleStreamTest {
 }
 
 func (s) TestHandlerTransport_HandleStreams(t *testing.T) {
-	st := newHandleStreamTest(t)
+	st := newHandleStreamTest(t, nil)
 	handleStream := func(s *ServerStream) {
 		if want := "/service/foo.bar"; s.method != want {
 			t.Errorf("stream method = %q; want %q", s.method, want)
@@ -342,7 +362,7 @@ func (s) TestHandlerTransport_HandleStreams_InvalidArgument(t *testing.T) {
 }
 
 func handleStreamCloseBodyTest(t *testing.T, statusCode codes.Code, msg string) {
-	st := newHandleStreamTest(t)
+	st := newHandleStreamTest(t, nil)
 
 	handleStream := func(s *ServerStream) {
 		s.WriteStatus(status.New(statusCode, msg))
@@ -451,7 +471,7 @@ func (s) TestHandlerTransport_HandleStreams_WriteStatusWrite(t *testing.T) {
 }
 
 func testHandlerTransportHandleStreams(t *testing.T, handleStream func(st *handleStreamTest, s *ServerStream)) {
-	st := newHandleStreamTest(t)
+	st := newHandleStreamTest(t, nil)
 	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
 	t.Cleanup(cancel)
 	st.ht.HandleStreams(
@@ -483,7 +503,7 @@ func (s) TestHandlerTransport_HandleStreams_ErrDetails(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	hst := newHandleStreamTest(t)
+	hst := newHandleStreamTest(t, nil)
 	handleStream := func(s *ServerStream) {
 		s.WriteStatus(st)
 	}
@@ -506,11 +526,81 @@ func (s) TestHandlerTransport_HandleStreams_ErrDetails(t *testing.T) {
 	checkHeaderAndTrailer(t, hst.rw, wantHeader, wantTrailer)
 }
 
+// Tests the use of stats handlers and ensures there are no data races while
+// accessing trailers.
+func (s) TestHandlerTransport_HandleStreams_StatsHandlers(t *testing.T) {
+	errDetails := []protoadapt.MessageV1{
+		&epb.RetryInfo{
+			RetryDelay: &durationpb.Duration{Seconds: 60},
+		},
+		&epb.ResourceInfo{
+			ResourceType: "foo bar",
+			ResourceName: "service.foo.bar",
+			Owner:        "User",
+		},
+	}
+
+	statusCode := codes.ResourceExhausted
+	msg := "you are being throttled"
+	st, err := status.New(statusCode, msg).WithDetails(errDetails...)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	stBytes, err := proto.Marshal(st.Proto())
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Add mock stats handlers to exercise the stats handler code path.
+	statsHandler := &mockStatsHandler{
+		rpcStatsCh: make(chan stats.RPCStats, 2),
+	}
+	hst := newHandleStreamTest(t, []stats.Handler{statsHandler})
+	handleStream := func(s *ServerStream) {
+		if err := s.SendHeader(metadata.New(map[string]string{})); err != nil {
+			t.Error(err)
+		}
+		if err := s.SetTrailer(metadata.Pairs("custom-trailer", "Custom trailer value")); err != nil {
+			t.Error(err)
+		}
+		s.WriteStatus(st)
+	}
+	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
+	defer cancel()
+	hst.ht.HandleStreams(
+		ctx, func(s *ServerStream) { go handleStream(s) },
+	)
+	wantHeader := http.Header{
+		"Date":         nil,
+		"Content-Type": {"application/grpc"},
+		"Trailer":      {"Grpc-Status", "Grpc-Message", "Grpc-Status-Details-Bin"},
+	}
+	wantTrailer := http.Header{
+		"Grpc-Status":             {fmt.Sprint(uint32(statusCode))},
+		"Grpc-Message":            {encodeGrpcMessage(msg)},
+		"Grpc-Status-Details-Bin": {encodeBinHeader(stBytes)},
+		"Custom-Trailer":          []string{"Custom trailer value"},
+	}
+
+	checkHeaderAndTrailer(t, hst.rw, wantHeader, wantTrailer)
+	wantStatTypes := []stats.RPCStats{&stats.OutHeader{}, &stats.OutTrailer{}}
+	for _, wantType := range wantStatTypes {
+		select {
+		case <-ctx.Done():
+			t.Fatal("Context timed out waiting for statsHandler.HandleRPC() to be called.")
+		case s := <-statsHandler.rpcStatsCh:
+			if reflect.TypeOf(s) != reflect.TypeOf(wantType) {
+				t.Fatalf("Received RPCStats of type %T, want %T", s, wantType)
+			}
+		}
+	}
+}
+
 // TestHandlerTransport_Drain verifies that Drain() is not implemented
 // by `serverHandlerTransport`.
 func (s) TestHandlerTransport_Drain(t *testing.T) {
 	defer func() { recover() }()
-	st := newHandleStreamTest(t)
+	st := newHandleStreamTest(t, nil)
 	st.ht.Drain("whatever")
 	t.Errorf("serverHandlerTransport.Drain() should have panicked")
 }
diff --git a/internal/transport/transport_test.go b/internal/transport/transport_test.go
index b8f97c3c..d0c8a88d 100644
--- a/internal/transport/transport_test.go
+++ b/internal/transport/transport_test.go
@@ -3125,3 +3125,82 @@ func (s) TestServerSendsRSTAfterDeadlineToMisbehavedClient(t *testing.T) {
 		t.Fatalf("RST frame received earlier than expected by duration: %v", want-got)
 	}
 }
+
+// TestClientTransport_Handle1xxHeaders validates that 1xx HTTP status headers
+// are ignored and treated as a protocol error if END_STREAM is set.
+func (s) TestClientTransport_Handle1xxHeaders(t *testing.T) {
+	testStream := func() *ClientStream {
+		return &ClientStream{
+			Stream: &Stream{
+				buf: &recvBuffer{
+					c:  make(chan recvMsg),
+					mu: sync.Mutex{},
+				},
+			},
+			done:       make(chan struct{}),
+			headerChan: make(chan struct{}),
+		}
+	}
+
+	testClient := func(ts *ClientStream) *http2Client {
+		return &http2Client{
+			mu: sync.Mutex{},
+			activeStreams: map[uint32]*ClientStream{
+				0: ts,
+			},
+			controlBuf: newControlBuffer(make(<-chan struct{})),
+		}
+	}
+
+	for _, test := range []struct {
+		name            string
+		metaHeaderFrame *http2.MetaHeadersFrame
+		httpFlags       http2.Flags
+		wantStatus      *status.Status
+	}{
+		{
+			name: "1xx with END_STREAM is error",
+			metaHeaderFrame: &http2.MetaHeadersFrame{
+				Fields: []hpack.HeaderField{
+					{Name: ":status", Value: "100"},
+				},
+			},
+			httpFlags: http2.FlagHeadersEndStream,
+			wantStatus: status.New(
+				codes.Internal,
+				"protocol error: informational header with status code 100 must not have END_STREAM set",
+			),
+		},
+		{
+			name: "1xx without END_STREAM is ignored",
+			metaHeaderFrame: &http2.MetaHeadersFrame{
+				Fields: []hpack.HeaderField{
+					{Name: ":status", Value: "100"},
+				},
+			},
+			httpFlags:  0,
+			wantStatus: nil,
+		},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			ts := testStream()
+			s := testClient(ts)
+
+			test.metaHeaderFrame.HeadersFrame = &http2.HeadersFrame{
+				FrameHeader: http2.FrameHeader{
+					StreamID: 0,
+					Flags:    test.httpFlags,
+				},
+			}
+
+			s.operateHeaders(test.metaHeaderFrame)
+
+			got := ts.status
+			want := test.wantStatus
+
+			if got.Code() != want.Code() || got.Message() != want.Message() {
+				t.Fatalf("operateHeaders(%v); status = %v, want %v", test.metaHeaderFrame, got, want)
+			}
+		})
+	}
+}
diff --git a/xds/internal/balancer/cdsbalancer/aggregate_cluster_test.go b/internal/xds/balancer/cdsbalancer/aggregate_cluster_test.go
similarity index 96%
rename from xds/internal/balancer/cdsbalancer/aggregate_cluster_test.go
rename to internal/xds/balancer/cdsbalancer/aggregate_cluster_test.go
index 4db998b4..581f511b 100644
--- a/xds/internal/balancer/cdsbalancer/aggregate_cluster_test.go
+++ b/internal/xds/balancer/cdsbalancer/aggregate_cluster_test.go
@@ -31,11 +31,11 @@ import (
 	"google.golang.org/grpc/internal/stubserver"
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
+	xdsinternal "google.golang.org/grpc/internal/xds"
+	"google.golang.org/grpc/internal/xds/balancer/clusterresolver"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/grpc/serviceconfig"
 	"google.golang.org/grpc/status"
-	"google.golang.org/grpc/xds/internal"
-	"google.golang.org/grpc/xds/internal/balancer/clusterresolver"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
 
 	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
 	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
@@ -88,7 +88,7 @@ func (s) TestAggregateClusterSuccess_LeafNode(t *testing.T) {
 					Type:             clusterresolver.DiscoveryMechanismTypeEDS,
 					EDSServiceName:   serviceName,
 					OutlierDetection: json.RawMessage(`{}`),
-					TelemetryLabels:  internal.UnknownCSMLabels,
+					TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 				}},
 				XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`),
 			},
@@ -98,7 +98,7 @@ func (s) TestAggregateClusterSuccess_LeafNode(t *testing.T) {
 					Type:             clusterresolver.DiscoveryMechanismTypeEDS,
 					EDSServiceName:   serviceName + "-new",
 					OutlierDetection: json.RawMessage(`{}`),
-					TelemetryLabels:  internal.UnknownCSMLabels,
+					TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 				}},
 				XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`),
 			},
@@ -113,7 +113,7 @@ func (s) TestAggregateClusterSuccess_LeafNode(t *testing.T) {
 					Type:             clusterresolver.DiscoveryMechanismTypeLogicalDNS,
 					DNSHostname:      "dns_host:8080",
 					OutlierDetection: json.RawMessage(`{}`),
-					TelemetryLabels:  internal.UnknownCSMLabels,
+					TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 				}},
 				XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`),
 			},
@@ -123,7 +123,7 @@ func (s) TestAggregateClusterSuccess_LeafNode(t *testing.T) {
 					Type:             clusterresolver.DiscoveryMechanismTypeLogicalDNS,
 					DNSHostname:      "dns_host_new:8080",
 					OutlierDetection: json.RawMessage(`{}`),
-					TelemetryLabels:  internal.UnknownCSMLabels,
+					TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 				}},
 				XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`),
 			},
@@ -218,14 +218,14 @@ func (s) TestAggregateClusterSuccess_ThenUpdateChildClusters(t *testing.T) {
 				Type:             clusterresolver.DiscoveryMechanismTypeEDS,
 				EDSServiceName:   serviceName,
 				OutlierDetection: json.RawMessage(`{}`),
-				TelemetryLabels:  internal.UnknownCSMLabels,
+				TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 			},
 			{
 				Cluster:          dnsClusterName,
 				Type:             clusterresolver.DiscoveryMechanismTypeLogicalDNS,
 				DNSHostname:      fmt.Sprintf("%s:%d", dnsHostName, dnsPort),
 				OutlierDetection: json.RawMessage(`{}`),
-				TelemetryLabels:  internal.UnknownCSMLabels,
+				TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 			},
 		},
 		XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`),
@@ -256,14 +256,14 @@ func (s) TestAggregateClusterSuccess_ThenUpdateChildClusters(t *testing.T) {
 				Type:             clusterresolver.DiscoveryMechanismTypeEDS,
 				EDSServiceName:   serviceName,
 				OutlierDetection: json.RawMessage(`{}`),
-				TelemetryLabels:  internal.UnknownCSMLabels,
+				TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 			},
 			{
 				Cluster:          dnsClusterNameNew,
 				Type:             clusterresolver.DiscoveryMechanismTypeLogicalDNS,
 				DNSHostname:      fmt.Sprintf("%s:%d", dnsHostNameNew, dnsPort),
 				OutlierDetection: json.RawMessage(`{}`),
-				TelemetryLabels:  internal.UnknownCSMLabels,
+				TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 			},
 		},
 		XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`),
@@ -309,14 +309,14 @@ func (s) TestAggregateClusterSuccess_ThenChangeRootToEDS(t *testing.T) {
 				Type:             clusterresolver.DiscoveryMechanismTypeEDS,
 				EDSServiceName:   serviceName,
 				OutlierDetection: json.RawMessage(`{}`),
-				TelemetryLabels:  internal.UnknownCSMLabels,
+				TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 			},
 			{
 				Cluster:          dnsClusterName,
 				Type:             clusterresolver.DiscoveryMechanismTypeLogicalDNS,
 				DNSHostname:      fmt.Sprintf("%s:%d", dnsHostName, dnsPort),
 				OutlierDetection: json.RawMessage(`{}`),
-				TelemetryLabels:  internal.UnknownCSMLabels,
+				TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 			},
 		},
 		XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`),
@@ -342,7 +342,7 @@ func (s) TestAggregateClusterSuccess_ThenChangeRootToEDS(t *testing.T) {
 			Type:             clusterresolver.DiscoveryMechanismTypeEDS,
 			EDSServiceName:   serviceName,
 			OutlierDetection: json.RawMessage(`{}`),
-			TelemetryLabels:  internal.UnknownCSMLabels,
+			TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 		}},
 		XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`),
 	}
@@ -377,7 +377,7 @@ func (s) TestAggregatedClusterSuccess_SwitchBetweenLeafAndAggregate(t *testing.T
 			Type:             clusterresolver.DiscoveryMechanismTypeEDS,
 			EDSServiceName:   serviceName,
 			OutlierDetection: json.RawMessage(`{}`),
-			TelemetryLabels:  internal.UnknownCSMLabels,
+			TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 		}},
 		XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`),
 	}
@@ -406,14 +406,14 @@ func (s) TestAggregatedClusterSuccess_SwitchBetweenLeafAndAggregate(t *testing.T
 				Type:             clusterresolver.DiscoveryMechanismTypeEDS,
 				EDSServiceName:   serviceName,
 				OutlierDetection: json.RawMessage(`{}`),
-				TelemetryLabels:  internal.UnknownCSMLabels,
+				TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 			},
 			{
 				Cluster:          dnsClusterName,
 				Type:             clusterresolver.DiscoveryMechanismTypeLogicalDNS,
 				DNSHostname:      fmt.Sprintf("%s:%d", dnsHostName, dnsPort),
 				OutlierDetection: json.RawMessage(`{}`),
-				TelemetryLabels:  internal.UnknownCSMLabels,
+				TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 			},
 		},
 		XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`),
@@ -437,7 +437,7 @@ func (s) TestAggregatedClusterSuccess_SwitchBetweenLeafAndAggregate(t *testing.T
 			Type:             clusterresolver.DiscoveryMechanismTypeEDS,
 			EDSServiceName:   serviceName,
 			OutlierDetection: json.RawMessage(`{}`),
-			TelemetryLabels:  internal.UnknownCSMLabels,
+			TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 		}},
 		XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`),
 	}
@@ -590,7 +590,7 @@ func (s) TestAggregatedClusterSuccess_DiamondDependency(t *testing.T) {
 			Type:             clusterresolver.DiscoveryMechanismTypeEDS,
 			EDSServiceName:   serviceName,
 			OutlierDetection: json.RawMessage(`{}`),
-			TelemetryLabels:  internal.UnknownCSMLabels,
+			TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 		}},
 		XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`),
 	}
@@ -658,14 +658,14 @@ func (s) TestAggregatedClusterSuccess_IgnoreDups(t *testing.T) {
 				Type:             clusterresolver.DiscoveryMechanismTypeEDS,
 				EDSServiceName:   serviceName,
 				OutlierDetection: json.RawMessage(`{}`),
-				TelemetryLabels:  internal.UnknownCSMLabels,
+				TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 			},
 			{
 				Cluster:          clusterNameD,
 				Type:             clusterresolver.DiscoveryMechanismTypeEDS,
 				EDSServiceName:   serviceName,
 				OutlierDetection: json.RawMessage(`{}`),
-				TelemetryLabels:  internal.UnknownCSMLabels,
+				TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 			},
 		},
 		XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`),
@@ -748,7 +748,7 @@ func (s) TestAggregatedCluster_NodeChildOfItself(t *testing.T) {
 			Type:             clusterresolver.DiscoveryMechanismTypeEDS,
 			EDSServiceName:   serviceName,
 			OutlierDetection: json.RawMessage(`{}`),
-			TelemetryLabels:  internal.UnknownCSMLabels,
+			TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 		}},
 		XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`),
 	}
@@ -854,7 +854,7 @@ func (s) TestAggregatedCluster_CycleWithLeafNode(t *testing.T) {
 			Type:             clusterresolver.DiscoveryMechanismTypeEDS,
 			EDSServiceName:   serviceName,
 			OutlierDetection: json.RawMessage(`{}`),
-			TelemetryLabels:  internal.UnknownCSMLabels,
+			TelemetryLabels:  xdsinternal.UnknownCSMLabels,
 		}},
 		XDSLBPolicy: json.RawMessage(`[{"xds_wrr_locality_experimental": {"childPolicy": [{"round_robin": {}}]}}]`),
 	}
diff --git a/xds/internal/balancer/cdsbalancer/cdsbalancer_security_test.go b/internal/xds/balancer/cdsbalancer/cdsbalancer_security_test.go
similarity index 99%
rename from xds/internal/balancer/cdsbalancer/cdsbalancer_security_test.go
rename to internal/xds/balancer/cdsbalancer/cdsbalancer_security_test.go
index 2df4fbf5..401a16dc 100644
--- a/xds/internal/balancer/cdsbalancer/cdsbalancer_security_test.go
+++ b/internal/xds/balancer/cdsbalancer/cdsbalancer_security_test.go
@@ -45,12 +45,12 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient"
 	"google.golang.org/grpc/peer"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/resolver/manual"
 	"google.golang.org/grpc/serviceconfig"
 	"google.golang.org/grpc/testdata"
-	"google.golang.org/grpc/xds/internal/xdsclient"
 
 	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
 	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
diff --git a/xds/internal/balancer/cdsbalancer/cdsbalancer_test.go b/internal/xds/balancer/cdsbalancer/cdsbalancer_test.go
similarity index 99%
rename from xds/internal/balancer/cdsbalancer/cdsbalancer_test.go
rename to internal/xds/balancer/cdsbalancer/cdsbalancer_test.go
index ac232462..ca13655d 100644
--- a/xds/internal/balancer/cdsbalancer/cdsbalancer_test.go
+++ b/internal/xds/balancer/cdsbalancer/cdsbalancer_test.go
@@ -39,16 +39,16 @@ import (
 	"google.golang.org/grpc/internal/stubserver"
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
+	xdsinternal "google.golang.org/grpc/internal/xds"
+	"google.golang.org/grpc/internal/xds/balancer/clusterresolver"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/resolver/manual"
 	"google.golang.org/grpc/serviceconfig"
 	"google.golang.org/grpc/status"
-	xdsinternal "google.golang.org/grpc/xds/internal"
-	"google.golang.org/grpc/xds/internal/balancer/clusterresolver"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/types/known/durationpb"
 	"google.golang.org/protobuf/types/known/wrapperspb"
 
diff --git a/xds/internal/balancer/clusterimpl/balancer_test.go b/internal/xds/balancer/clusterimpl/balancer_test.go
similarity index 99%
rename from xds/internal/balancer/clusterimpl/balancer_test.go
rename to internal/xds/balancer/clusterimpl/balancer_test.go
index 454ed931..53f2debf 100644
--- a/xds/internal/balancer/clusterimpl/balancer_test.go
+++ b/internal/xds/balancer/clusterimpl/balancer_test.go
@@ -38,14 +38,13 @@ import (
 	"google.golang.org/grpc/internal/grpctest"
 	internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
 	"google.golang.org/grpc/internal/testutils"
-	"google.golang.org/grpc/internal/xds"
+	xdsinternal "google.golang.org/grpc/internal/xds"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/testutils/fakeclient"
+	"google.golang.org/grpc/internal/xds/xdsclient"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/serviceconfig"
-	xdsinternal "google.golang.org/grpc/xds/internal"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/testutils/fakeclient"
-	"google.golang.org/grpc/xds/internal/xdsclient"
 
 	v3orcapb "github.com/cncf/xds/go/xds/data/orca/v3"
 	"github.com/google/go-cmp/cmp"
@@ -729,7 +728,7 @@ func (s) TestClusterNameInAddressAttributes(t *testing.T) {
 	if got, want := addrs1[0].Addr, testBackendEndpoints[0].Addresses[0].Addr; got != want {
 		t.Fatalf("sc is created with addr %v, want %v", got, want)
 	}
-	cn, ok := xds.GetXDSHandshakeClusterName(addrs1[0].Attributes)
+	cn, ok := xdsinternal.GetXDSHandshakeClusterName(addrs1[0].Attributes)
 	if !ok || cn != testClusterName {
 		t.Fatalf("sc is created with addr with cluster name %v, %v, want cluster name %v", cn, ok, testClusterName)
 	}
@@ -760,7 +759,7 @@ func (s) TestClusterNameInAddressAttributes(t *testing.T) {
 		t.Fatalf("sc is created with addr %v, want %v", got, want)
 	}
 	// New addresses should have the new cluster name.
-	cn2, ok := xds.GetXDSHandshakeClusterName(addrs2[0].Attributes)
+	cn2, ok := xdsinternal.GetXDSHandshakeClusterName(addrs2[0].Attributes)
 	if !ok || cn2 != testClusterName2 {
 		t.Fatalf("sc is created with addr with cluster name %v, %v, want cluster name %v", cn2, ok, testClusterName2)
 	}
diff --git a/xds/internal/balancer/clusterimpl/config_test.go b/internal/xds/balancer/clusterimpl/config_test.go
similarity index 100%
rename from xds/internal/balancer/clusterimpl/config_test.go
rename to internal/xds/balancer/clusterimpl/config_test.go
diff --git a/xds/internal/balancer/clusterimpl/tests/balancer_test.go b/internal/xds/balancer/clusterimpl/tests/balancer_test.go
similarity index 100%
rename from xds/internal/balancer/clusterimpl/tests/balancer_test.go
rename to internal/xds/balancer/clusterimpl/tests/balancer_test.go
diff --git a/xds/internal/balancer/clustermanager/clustermanager_test.go b/internal/xds/balancer/clustermanager/clustermanager_test.go
similarity index 100%
rename from xds/internal/balancer/clustermanager/clustermanager_test.go
rename to internal/xds/balancer/clustermanager/clustermanager_test.go
diff --git a/xds/internal/balancer/clustermanager/config_test.go b/internal/xds/balancer/clustermanager/config_test.go
similarity index 98%
rename from xds/internal/balancer/clustermanager/config_test.go
rename to internal/xds/balancer/clustermanager/config_test.go
index ef496a2e..fd815dfa 100644
--- a/xds/internal/balancer/clustermanager/config_test.go
+++ b/internal/xds/balancer/clustermanager/config_test.go
@@ -25,7 +25,7 @@ import (
 	"google.golang.org/grpc/balancer"
 	_ "google.golang.org/grpc/balancer/weightedtarget"
 	internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
-	_ "google.golang.org/grpc/xds/internal/balancer/cdsbalancer"
+	_ "google.golang.org/grpc/internal/xds/balancer/cdsbalancer"
 )
 
 const (
diff --git a/xds/internal/balancer/clustermanager/e2e_test/clustermanager_test.go b/internal/xds/balancer/clustermanager/e2e_test/clustermanager_test.go
similarity index 100%
rename from xds/internal/balancer/clustermanager/e2e_test/clustermanager_test.go
rename to internal/xds/balancer/clustermanager/e2e_test/clustermanager_test.go
diff --git a/xds/internal/balancer/clusterresolver/clusterresolver_test.go b/internal/xds/balancer/clusterresolver/clusterresolver_test.go
similarity index 100%
rename from xds/internal/balancer/clusterresolver/clusterresolver_test.go
rename to internal/xds/balancer/clusterresolver/clusterresolver_test.go
diff --git a/xds/internal/balancer/clusterresolver/config_test.go b/internal/xds/balancer/clusterresolver/config_test.go
similarity index 99%
rename from xds/internal/balancer/clusterresolver/config_test.go
rename to internal/xds/balancer/clusterresolver/config_test.go
index 79c135a6..964359aa 100644
--- a/xds/internal/balancer/clusterresolver/config_test.go
+++ b/internal/xds/balancer/clusterresolver/config_test.go
@@ -30,8 +30,8 @@ import (
 	"google.golang.org/grpc/balancer/roundrobin"
 	iringhash "google.golang.org/grpc/internal/ringhash"
 	iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
+	"google.golang.org/grpc/internal/xds/balancer/outlierdetection"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/balancer/outlierdetection"
 )
 
 func TestDiscoveryMechanismTypeMarshalJSON(t *testing.T) {
diff --git a/xds/internal/balancer/clusterresolver/configbuilder_childname_test.go b/internal/xds/balancer/clusterresolver/configbuilder_childname_test.go
similarity index 96%
rename from xds/internal/balancer/clusterresolver/configbuilder_childname_test.go
rename to internal/xds/balancer/clusterresolver/configbuilder_childname_test.go
index f056b6bd..c5abcd8f 100644
--- a/xds/internal/balancer/clusterresolver/configbuilder_childname_test.go
+++ b/internal/xds/balancer/clusterresolver/configbuilder_childname_test.go
@@ -21,8 +21,8 @@ import (
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 )
 
 func Test_nameGenerator_generate(t *testing.T) {
diff --git a/xds/internal/balancer/clusterresolver/configbuilder_test.go b/internal/xds/balancer/clusterresolver/configbuilder_test.go
similarity index 97%
rename from xds/internal/balancer/clusterresolver/configbuilder_test.go
rename to internal/xds/balancer/clusterresolver/configbuilder_test.go
index 656596f9..571b7564 100644
--- a/xds/internal/balancer/clusterresolver/configbuilder_test.go
+++ b/internal/xds/balancer/clusterresolver/configbuilder_test.go
@@ -35,15 +35,15 @@ import (
 	"google.golang.org/grpc/internal/hierarchy"
 	iringhash "google.golang.org/grpc/internal/ringhash"
 	iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
+	xdsinternal "google.golang.org/grpc/internal/xds"
+	"google.golang.org/grpc/internal/xds/balancer/clusterimpl"
+	"google.golang.org/grpc/internal/xds/balancer/outlierdetection"
+	"google.golang.org/grpc/internal/xds/balancer/priority"
+	"google.golang.org/grpc/internal/xds/balancer/wrrlocality"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/grpc/resolver"
-	"google.golang.org/grpc/xds/internal"
-	"google.golang.org/grpc/xds/internal/balancer/clusterimpl"
-	"google.golang.org/grpc/xds/internal/balancer/outlierdetection"
-	"google.golang.org/grpc/xds/internal/balancer/priority"
-	"google.golang.org/grpc/xds/internal/balancer/wrrlocality"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
 )
 
 const (
@@ -642,8 +642,8 @@ func testEndpointWithAttrs(addrStrs []string, localityWeight, endpointWeight uin
 	}
 	path := []string{priority}
 	if lID != nil {
-		path = append(path, internal.LocalityString(*lID))
-		endpoint = internal.SetLocalityIDInEndpoint(endpoint, *lID)
+		path = append(path, xdsinternal.LocalityString(*lID))
+		endpoint = xdsinternal.SetLocalityIDInEndpoint(endpoint, *lID)
 	}
 	endpoint = hierarchy.SetInEndpoint(endpoint, path)
 	endpoint = wrrlocality.SetAddrInfoInEndpoint(endpoint, wrrlocality.AddrInfo{LocalityWeight: localityWeight})
diff --git a/xds/internal/balancer/clusterresolver/e2e_test/aggregate_cluster_test.go b/internal/xds/balancer/clusterresolver/e2e_test/aggregate_cluster_test.go
similarity index 99%
rename from xds/internal/balancer/clusterresolver/e2e_test/aggregate_cluster_test.go
rename to internal/xds/balancer/clusterresolver/e2e_test/aggregate_cluster_test.go
index 36d22abf..523b5259 100644
--- a/xds/internal/balancer/clusterresolver/e2e_test/aggregate_cluster_test.go
+++ b/internal/xds/balancer/clusterresolver/e2e_test/aggregate_cluster_test.go
@@ -37,13 +37,13 @@ import (
 	"google.golang.org/grpc/internal/testutils/pickfirst"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/grpc/peer"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/resolver/manual"
 	"google.golang.org/grpc/serviceconfig"
 	"google.golang.org/grpc/status"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/types/known/wrapperspb"
 
 	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
diff --git a/xds/internal/balancer/clusterresolver/e2e_test/balancer_test.go b/internal/xds/balancer/clusterresolver/e2e_test/balancer_test.go
similarity index 97%
rename from xds/internal/balancer/clusterresolver/e2e_test/balancer_test.go
rename to internal/xds/balancer/clusterresolver/e2e_test/balancer_test.go
index 8e7f3d1f..50801330 100644
--- a/xds/internal/balancer/clusterresolver/e2e_test/balancer_test.go
+++ b/internal/xds/balancer/clusterresolver/e2e_test/balancer_test.go
@@ -38,18 +38,18 @@ import (
 	"google.golang.org/grpc/internal/stubserver"
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
+	xdsinternal "google.golang.org/grpc/internal/xds"
+	"google.golang.org/grpc/internal/xds/balancer/clusterimpl"
+	"google.golang.org/grpc/internal/xds/balancer/outlierdetection"
+	"google.golang.org/grpc/internal/xds/balancer/priority"
+	"google.golang.org/grpc/internal/xds/balancer/wrrlocality"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/resolver/manual"
 	"google.golang.org/grpc/serviceconfig"
 	"google.golang.org/grpc/status"
-	xdsinternal "google.golang.org/grpc/xds/internal"
-	"google.golang.org/grpc/xds/internal/balancer/clusterimpl"
-	"google.golang.org/grpc/xds/internal/balancer/outlierdetection"
-	"google.golang.org/grpc/xds/internal/balancer/priority"
-	"google.golang.org/grpc/xds/internal/balancer/wrrlocality"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/types/known/durationpb"
 	"google.golang.org/protobuf/types/known/wrapperspb"
 
@@ -60,7 +60,7 @@ import (
 	testgrpc "google.golang.org/grpc/interop/grpc_testing"
 	testpb "google.golang.org/grpc/interop/grpc_testing"
 
-	_ "google.golang.org/grpc/xds/internal/balancer/cdsbalancer" // Register the "cds_experimental" LB policy.
+	_ "google.golang.org/grpc/internal/xds/balancer/cdsbalancer" // Register the "cds_experimental" LB policy.
 )
 
 // setupAndDial performs common setup across all tests
diff --git a/xds/internal/balancer/clusterresolver/e2e_test/eds_impl_test.go b/internal/xds/balancer/clusterresolver/e2e_test/eds_impl_test.go
similarity index 99%
rename from xds/internal/balancer/clusterresolver/e2e_test/eds_impl_test.go
rename to internal/xds/balancer/clusterresolver/e2e_test/eds_impl_test.go
index dfcb793d..477601ec 100644
--- a/xds/internal/balancer/clusterresolver/e2e_test/eds_impl_test.go
+++ b/internal/xds/balancer/clusterresolver/e2e_test/eds_impl_test.go
@@ -41,13 +41,13 @@ import (
 	rrutil "google.golang.org/grpc/internal/testutils/roundrobin"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/grpc/peer"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/resolver/manual"
 	"google.golang.org/grpc/serviceconfig"
 	"google.golang.org/grpc/status"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/types/known/wrapperspb"
 
 	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
@@ -57,8 +57,8 @@ import (
 	testgrpc "google.golang.org/grpc/interop/grpc_testing"
 	testpb "google.golang.org/grpc/interop/grpc_testing"
 
-	_ "google.golang.org/grpc/xds/internal/balancer/clusterresolver" // Register the "cluster_resolver_experimental" LB policy.
-	"google.golang.org/grpc/xds/internal/balancer/priority"
+	_ "google.golang.org/grpc/internal/xds/balancer/clusterresolver" // Register the "cluster_resolver_experimental" LB policy.
+	"google.golang.org/grpc/internal/xds/balancer/priority"
 )
 
 const (
diff --git a/xds/internal/balancer/outlierdetection/balancer_test.go b/internal/xds/balancer/outlierdetection/balancer_test.go
similarity index 99%
rename from xds/internal/balancer/outlierdetection/balancer_test.go
rename to internal/xds/balancer/outlierdetection/balancer_test.go
index 25b6a221..be3f03cd 100644
--- a/xds/internal/balancer/outlierdetection/balancer_test.go
+++ b/internal/xds/balancer/outlierdetection/balancer_test.go
@@ -46,12 +46,12 @@ import (
 	"google.golang.org/grpc/internal/stubserver"
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/roundrobin"
+	"google.golang.org/grpc/internal/xds/balancer/clusterimpl"
 	"google.golang.org/grpc/peer"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/resolver/manual"
 	"google.golang.org/grpc/serviceconfig"
 	"google.golang.org/grpc/status"
-	"google.golang.org/grpc/xds/internal/balancer/clusterimpl"
 
 	testgrpc "google.golang.org/grpc/interop/grpc_testing"
 	testpb "google.golang.org/grpc/interop/grpc_testing"
diff --git a/xds/internal/balancer/outlierdetection/callcounter_test.go b/internal/xds/balancer/outlierdetection/callcounter_test.go
similarity index 100%
rename from xds/internal/balancer/outlierdetection/callcounter_test.go
rename to internal/xds/balancer/outlierdetection/callcounter_test.go
diff --git a/xds/internal/balancer/outlierdetection/config_test.go b/internal/xds/balancer/outlierdetection/config_test.go
similarity index 100%
rename from xds/internal/balancer/outlierdetection/config_test.go
rename to internal/xds/balancer/outlierdetection/config_test.go
diff --git a/xds/internal/balancer/outlierdetection/e2e_test/outlierdetection_test.go b/internal/xds/balancer/outlierdetection/e2e_test/outlierdetection_test.go
similarity index 99%
rename from xds/internal/balancer/outlierdetection/e2e_test/outlierdetection_test.go
rename to internal/xds/balancer/outlierdetection/e2e_test/outlierdetection_test.go
index 161c364d..51a3013d 100644
--- a/xds/internal/balancer/outlierdetection/e2e_test/outlierdetection_test.go
+++ b/internal/xds/balancer/outlierdetection/e2e_test/outlierdetection_test.go
@@ -42,7 +42,7 @@ import (
 	testgrpc "google.golang.org/grpc/interop/grpc_testing"
 	testpb "google.golang.org/grpc/interop/grpc_testing"
 
-	_ "google.golang.org/grpc/xds/internal/balancer/outlierdetection" // To register helper functions which register/unregister Outlier Detection LB Policy.
+	_ "google.golang.org/grpc/internal/xds/balancer/outlierdetection" // To register helper functions which register/unregister Outlier Detection LB Policy.
 )
 
 var (
diff --git a/xds/internal/balancer/priority/balancer_test.go b/internal/xds/balancer/priority/balancer_test.go
similarity index 100%
rename from xds/internal/balancer/priority/balancer_test.go
rename to internal/xds/balancer/priority/balancer_test.go
diff --git a/xds/internal/balancer/priority/config_test.go b/internal/xds/balancer/priority/config_test.go
similarity index 100%
rename from xds/internal/balancer/priority/config_test.go
rename to internal/xds/balancer/priority/config_test.go
diff --git a/xds/internal/balancer/priority/ignore_resolve_now_test.go b/internal/xds/balancer/priority/ignore_resolve_now_test.go
similarity index 100%
rename from xds/internal/balancer/priority/ignore_resolve_now_test.go
rename to internal/xds/balancer/priority/ignore_resolve_now_test.go
diff --git a/xds/internal/balancer/wrrlocality/balancer_test.go b/internal/xds/balancer/wrrlocality/balancer_test.go
similarity index 97%
rename from xds/internal/balancer/wrrlocality/balancer_test.go
rename to internal/xds/balancer/wrrlocality/balancer_test.go
index 9c3a499e..b006a52b 100644
--- a/xds/internal/balancer/wrrlocality/balancer_test.go
+++ b/internal/xds/balancer/wrrlocality/balancer_test.go
@@ -34,10 +34,10 @@ import (
 	"google.golang.org/grpc/internal/grpctest"
 	internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
 	"google.golang.org/grpc/internal/testutils"
+	xdsinternal "google.golang.org/grpc/internal/xds"
+	"google.golang.org/grpc/internal/xds/clients"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/serviceconfig"
-	"google.golang.org/grpc/xds/internal"
-	"google.golang.org/grpc/xds/internal/clients"
 )
 
 const (
@@ -179,7 +179,7 @@ func (s) TestUpdateClientConnState(t *testing.T) {
 	addr1 := resolver.Address{
 		Addr: "locality-1",
 	}
-	addr1 = internal.SetLocalityID(addr1, clients.Locality{
+	addr1 = xdsinternal.SetLocalityID(addr1, clients.Locality{
 		Region:  "region-1",
 		Zone:    "zone-1",
 		SubZone: "subzone-1",
@@ -189,7 +189,7 @@ func (s) TestUpdateClientConnState(t *testing.T) {
 	addr2 := resolver.Address{
 		Addr: "locality-2",
 	}
-	addr2 = internal.SetLocalityID(addr2, clients.Locality{
+	addr2 = xdsinternal.SetLocalityID(addr2, clients.Locality{
 		Region:  "region-2",
 		Zone:    "zone-2",
 		SubZone: "subzone-2",
diff --git a/xds/internal/clients/grpctransport/examples_test.go b/internal/xds/clients/grpctransport/examples_test.go
similarity index 92%
rename from xds/internal/clients/grpctransport/examples_test.go
rename to internal/xds/clients/grpctransport/examples_test.go
index 57191d1f..09f96782 100644
--- a/xds/internal/clients/grpctransport/examples_test.go
+++ b/internal/xds/clients/grpctransport/examples_test.go
@@ -21,8 +21,8 @@ package grpctransport_test
 import (
 	"fmt"
 
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
 )
 
 // ExampleServerIdentifierExtension demonstrates how to create
diff --git a/xds/internal/clients/grpctransport/grpc_transport_ext_test.go b/internal/xds/clients/grpctransport/grpc_transport_ext_test.go
similarity index 98%
rename from xds/internal/clients/grpctransport/grpc_transport_ext_test.go
rename to internal/xds/clients/grpctransport/grpc_transport_ext_test.go
index 75ac5146..5c3050f8 100644
--- a/xds/internal/clients/grpctransport/grpc_transport_ext_test.go
+++ b/internal/xds/clients/grpctransport/grpc_transport_ext_test.go
@@ -28,10 +28,10 @@ import (
 	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/credentials/local"
 	"google.golang.org/grpc/internal/grpctest"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils/e2e"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils/e2e"
 )
 
 const (
diff --git a/xds/internal/clients/grpctransport/grpc_transport_test.go b/internal/xds/clients/grpctransport/grpc_transport_test.go
similarity index 99%
rename from xds/internal/clients/grpctransport/grpc_transport_test.go
rename to internal/xds/clients/grpctransport/grpc_transport_test.go
index 23e75257..e33e889e 100644
--- a/xds/internal/clients/grpctransport/grpc_transport_test.go
+++ b/internal/xds/clients/grpctransport/grpc_transport_test.go
@@ -31,7 +31,7 @@ import (
 	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/credentials/local"
 	"google.golang.org/grpc/internal/grpctest"
-	"google.golang.org/grpc/xds/internal/clients"
+	"google.golang.org/grpc/internal/xds/clients"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/testing/protocmp"
 
diff --git a/xds/internal/clients/internal/buffer/unbounded_test.go b/internal/xds/clients/internal/buffer/unbounded_test.go
similarity index 100%
rename from xds/internal/clients/internal/buffer/unbounded_test.go
rename to internal/xds/clients/internal/buffer/unbounded_test.go
diff --git a/xds/internal/clients/internal/internal_test.go b/internal/xds/clients/internal/internal_test.go
similarity index 99%
rename from xds/internal/clients/internal/internal_test.go
rename to internal/xds/clients/internal/internal_test.go
index 5b7889b7..99deb5c2 100644
--- a/xds/internal/clients/internal/internal_test.go
+++ b/internal/xds/clients/internal/internal_test.go
@@ -23,7 +23,7 @@ import (
 
 	"github.com/google/go-cmp/cmp"
 	"google.golang.org/grpc/internal/grpctest"
-	"google.golang.org/grpc/xds/internal/clients"
+	"google.golang.org/grpc/internal/xds/clients"
 	"google.golang.org/protobuf/testing/protocmp"
 	"google.golang.org/protobuf/types/known/structpb"
 
diff --git a/xds/internal/clients/internal/syncutil/callback_serializer_test.go b/internal/xds/clients/internal/syncutil/callback_serializer_test.go
similarity index 100%
rename from xds/internal/clients/internal/syncutil/callback_serializer_test.go
rename to internal/xds/clients/internal/syncutil/callback_serializer_test.go
diff --git a/xds/internal/clients/internal/syncutil/event_test.go b/internal/xds/clients/internal/syncutil/event_test.go
similarity index 100%
rename from xds/internal/clients/internal/syncutil/event_test.go
rename to internal/xds/clients/internal/syncutil/event_test.go
diff --git a/xds/internal/clients/internal/testutils/channel.go b/internal/xds/clients/internal/testutils/channel.go
similarity index 100%
rename from xds/internal/clients/internal/testutils/channel.go
rename to internal/xds/clients/internal/testutils/channel.go
diff --git a/xds/internal/clients/internal/testutils/e2e/clientresources.go b/internal/xds/clients/internal/testutils/e2e/clientresources.go
similarity index 100%
rename from xds/internal/clients/internal/testutils/e2e/clientresources.go
rename to internal/xds/clients/internal/testutils/e2e/clientresources.go
diff --git a/xds/internal/clients/internal/testutils/e2e/logging.go b/internal/xds/clients/internal/testutils/e2e/logging.go
similarity index 100%
rename from xds/internal/clients/internal/testutils/e2e/logging.go
rename to internal/xds/clients/internal/testutils/e2e/logging.go
diff --git a/xds/internal/clients/internal/testutils/e2e/server.go b/internal/xds/clients/internal/testutils/e2e/server.go
similarity index 99%
rename from xds/internal/clients/internal/testutils/e2e/server.go
rename to internal/xds/clients/internal/testutils/e2e/server.go
index f2903f53..3426bbc8 100644
--- a/xds/internal/clients/internal/testutils/e2e/server.go
+++ b/internal/xds/clients/internal/testutils/e2e/server.go
@@ -30,7 +30,7 @@ import (
 
 	"github.com/envoyproxy/go-control-plane/pkg/cache/types"
 	"google.golang.org/grpc"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils/fakeserver"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils/fakeserver"
 
 	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
 	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
diff --git a/xds/internal/clients/internal/testutils/fakeserver/server.go b/internal/xds/clients/internal/testutils/fakeserver/server.go
similarity index 99%
rename from xds/internal/clients/internal/testutils/fakeserver/server.go
rename to internal/xds/clients/internal/testutils/fakeserver/server.go
index 6aa66a89..8adb2e86 100644
--- a/xds/internal/clients/internal/testutils/fakeserver/server.go
+++ b/internal/xds/clients/internal/testutils/fakeserver/server.go
@@ -31,8 +31,8 @@ import (
 
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils"
 	"google.golang.org/grpc/status"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils"
 	"google.golang.org/protobuf/proto"
 
 	v3discoverygrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
diff --git a/xds/internal/clients/internal/testutils/marshal_any.go b/internal/xds/clients/internal/testutils/marshal_any.go
similarity index 100%
rename from xds/internal/clients/internal/testutils/marshal_any.go
rename to internal/xds/clients/internal/testutils/marshal_any.go
diff --git a/xds/internal/clients/internal/testutils/restartable_listener.go b/internal/xds/clients/internal/testutils/restartable_listener.go
similarity index 100%
rename from xds/internal/clients/internal/testutils/restartable_listener.go
rename to internal/xds/clients/internal/testutils/restartable_listener.go
diff --git a/xds/internal/clients/internal/testutils/wrappers.go b/internal/xds/clients/internal/testutils/wrappers.go
similarity index 100%
rename from xds/internal/clients/internal/testutils/wrappers.go
rename to internal/xds/clients/internal/testutils/wrappers.go
diff --git a/xds/internal/clients/lrsclient/load_store_test.go b/internal/xds/clients/lrsclient/load_store_test.go
similarity index 99%
rename from xds/internal/clients/lrsclient/load_store_test.go
rename to internal/xds/clients/lrsclient/load_store_test.go
index ecbc2ccf..abe1bc48 100644
--- a/xds/internal/clients/lrsclient/load_store_test.go
+++ b/internal/xds/clients/lrsclient/load_store_test.go
@@ -26,8 +26,8 @@ import (
 
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
-	"google.golang.org/grpc/xds/internal/clients"
-	lrsclientinternal "google.golang.org/grpc/xds/internal/clients/lrsclient/internal"
+	"google.golang.org/grpc/internal/xds/clients"
+	lrsclientinternal "google.golang.org/grpc/internal/xds/clients/lrsclient/internal"
 )
 
 var (
diff --git a/xds/internal/clients/lrsclient/loadreport_test.go b/internal/xds/clients/lrsclient/loadreport_test.go
similarity index 98%
rename from xds/internal/clients/lrsclient/loadreport_test.go
rename to internal/xds/clients/lrsclient/loadreport_test.go
index cdddb39f..b4d489ef 100644
--- a/xds/internal/clients/lrsclient/loadreport_test.go
+++ b/internal/xds/clients/lrsclient/loadreport_test.go
@@ -30,14 +30,14 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/internal/grpctest"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils/e2e"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils/fakeserver"
+	"google.golang.org/grpc/internal/xds/clients/lrsclient"
+	lrsclientinternal "google.golang.org/grpc/internal/xds/clients/lrsclient/internal"
 	"google.golang.org/grpc/status"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils/e2e"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils/fakeserver"
-	"google.golang.org/grpc/xds/internal/clients/lrsclient"
-	lrsclientinternal "google.golang.org/grpc/xds/internal/clients/lrsclient/internal"
 	"google.golang.org/protobuf/testing/protocmp"
 	"google.golang.org/protobuf/types/known/durationpb"
 
diff --git a/xds/internal/clients/xdsclient/channel_test.go b/internal/xds/clients/xdsclient/channel_test.go
similarity index 98%
rename from xds/internal/clients/xdsclient/channel_test.go
rename to internal/xds/clients/xdsclient/channel_test.go
index 50f52c17..153e7e82 100644
--- a/xds/internal/clients/xdsclient/channel_test.go
+++ b/internal/xds/clients/xdsclient/channel_test.go
@@ -30,12 +30,12 @@ import (
 	"github.com/google/go-cmp/cmp/cmpopts"
 	"github.com/google/uuid"
 	"google.golang.org/grpc/credentials/insecure"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils/e2e"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils/fakeserver"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils/e2e"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils/fakeserver"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
 	"google.golang.org/protobuf/testing/protocmp"
 	"google.golang.org/protobuf/types/known/anypb"
 
@@ -772,6 +772,3 @@ func (ta *testEventHandler) waitForResourceDoesNotExist(ctx context.Context) (Re
 	}
 	return typ, name, nil
 }
-
-func (*testEventHandler) adsResourceRemoveUnsubscribedCacheEntries(ResourceType) {
-}
diff --git a/xds/internal/clients/xdsclient/helpers_test.go b/internal/xds/clients/xdsclient/helpers_test.go
similarity index 98%
rename from xds/internal/clients/xdsclient/helpers_test.go
rename to internal/xds/clients/xdsclient/helpers_test.go
index efa025ef..6f5f8597 100644
--- a/xds/internal/clients/xdsclient/helpers_test.go
+++ b/internal/xds/clients/xdsclient/helpers_test.go
@@ -27,8 +27,8 @@ import (
 	"time"
 
 	"google.golang.org/grpc/internal/grpctest"
-	"google.golang.org/grpc/xds/internal/clients/internal/pretty"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients/internal/pretty"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 
diff --git a/xds/internal/clients/xdsclient/test/ads_stream_ack_nack_test.go b/internal/xds/clients/xdsclient/test/ads_stream_ack_nack_test.go
similarity index 98%
rename from xds/internal/clients/xdsclient/test/ads_stream_ack_nack_test.go
rename to internal/xds/clients/xdsclient/test/ads_stream_ack_nack_test.go
index 467365d2..75639b4c 100644
--- a/xds/internal/clients/xdsclient/test/ads_stream_ack_nack_test.go
+++ b/internal/xds/clients/xdsclient/test/ads_stream_ack_nack_test.go
@@ -28,12 +28,12 @@ import (
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/uuid"
 	"google.golang.org/grpc/credentials/insecure"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils/e2e"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils/e2e"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/testing/protocmp"
 
diff --git a/xds/internal/clients/xdsclient/test/ads_stream_backoff_test.go b/internal/xds/clients/xdsclient/test/ads_stream_backoff_test.go
similarity index 97%
rename from xds/internal/clients/xdsclient/test/ads_stream_backoff_test.go
rename to internal/xds/clients/xdsclient/test/ads_stream_backoff_test.go
index ad5ed26f..6dc2ac51 100644
--- a/xds/internal/clients/xdsclient/test/ads_stream_backoff_test.go
+++ b/internal/xds/clients/xdsclient/test/ads_stream_backoff_test.go
@@ -27,13 +27,13 @@ import (
 	"time"
 
 	"google.golang.org/grpc/credentials/insecure"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils/e2e"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient"
-	xdsclientinternal "google.golang.org/grpc/xds/internal/clients/xdsclient/internal"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils/e2e"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient"
+	xdsclientinternal "google.golang.org/grpc/internal/xds/clients/xdsclient/internal"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/testing/protocmp"
 
 	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
diff --git a/xds/internal/clients/xdsclient/test/ads_stream_flow_control_test.go b/internal/xds/clients/xdsclient/test/ads_stream_flow_control_test.go
similarity index 98%
rename from xds/internal/clients/xdsclient/test/ads_stream_flow_control_test.go
rename to internal/xds/clients/xdsclient/test/ads_stream_flow_control_test.go
index fe53c76a..9c417a14 100644
--- a/xds/internal/clients/xdsclient/test/ads_stream_flow_control_test.go
+++ b/internal/xds/clients/xdsclient/test/ads_stream_flow_control_test.go
@@ -33,10 +33,10 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 )
 
 // blockingListenerWatcher implements xdsresource.ListenerWatcher. It writes to
diff --git a/xds/internal/clients/xdsclient/test/ads_stream_restart_test.go b/internal/xds/clients/xdsclient/test/ads_stream_restart_test.go
similarity index 96%
rename from xds/internal/clients/xdsclient/test/ads_stream_restart_test.go
rename to internal/xds/clients/xdsclient/test/ads_stream_restart_test.go
index 9d8b876d..b205ac2d 100644
--- a/xds/internal/clients/xdsclient/test/ads_stream_restart_test.go
+++ b/internal/xds/clients/xdsclient/test/ads_stream_restart_test.go
@@ -26,10 +26,10 @@ import (
 	"github.com/google/uuid"
 	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 
 	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
 	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
diff --git a/xds/internal/clients/xdsclient/test/ads_stream_watch_test.go b/internal/xds/clients/xdsclient/test/ads_stream_watch_test.go
similarity index 83%
rename from xds/internal/clients/xdsclient/test/ads_stream_watch_test.go
rename to internal/xds/clients/xdsclient/test/ads_stream_watch_test.go
index 178da6c5..40ca8eaa 100644
--- a/xds/internal/clients/xdsclient/test/ads_stream_watch_test.go
+++ b/internal/xds/clients/xdsclient/test/ads_stream_watch_test.go
@@ -28,11 +28,12 @@ import (
 	"github.com/google/uuid"
 	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient"
-	xdsclientinternal "google.golang.org/grpc/xds/internal/clients/xdsclient/internal"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient"
+	xdsclientinternal "google.golang.org/grpc/internal/xds/clients/xdsclient/internal"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
 
 	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 )
@@ -175,9 +176,34 @@ func (s) TestADS_WatchState_TimerFires(t *testing.T) {
 	// short resource expiry timeout.
 	nodeID := uuid.New().String()
 	configs := map[string]grpctransport.Config{"insecure": {Credentials: insecure.NewBundle()}}
-	overrideWatchExpiryTimeout(t, defaultTestWatchExpiryTimeout)
-	client := createXDSClient(t, mgmtServer.Address, nodeID, grpctransport.NewBuilder(configs))
-
+	resourceTypes := map[string]xdsclient.ResourceType{xdsresource.V3ListenerURL: listenerType}
+	si := clients.ServerIdentifier{
+		ServerURI:  mgmtServer.Address,
+		Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"},
+	}
+
+	xdsClientConfig := xdsclient.Config{
+		Servers:          []xdsclient.ServerConfig{{ServerIdentifier: si}},
+		Node:             clients.Node{ID: nodeID, UserAgentName: "user-agent", UserAgentVersion: "0.0.0.0"},
+		TransportBuilder: grpctransport.NewBuilder(configs),
+		ResourceTypes:    resourceTypes,
+		// Xdstp resource names used in this test do not specify an
+		// authority. These will end up looking up an entry with the
+		// empty key in the authorities map. Having an entry with an
+		// empty key and empty configuration, results in these
+		// resources also using the top-level configuration.
+		Authorities: map[string]xdsclient.Authority{
+			"": {XDSServers: []xdsclient.ServerConfig{}},
+		},
+		WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
+	}
+
+	// Create an xDS client with the above config.
+	client, err := xdsclient.New(xdsClientConfig)
+	if err != nil {
+		t.Fatalf("Failed to create xDS client: %v", err)
+	}
+	t.Cleanup(func() { client.Close() })
 	// Create a watch for the first listener resource and verify that the timer
 	// is running and the watch state is `requested`.
 	const listenerName = "listener"
diff --git a/xds/internal/clients/xdsclient/test/authority_test.go b/internal/xds/clients/xdsclient/test/authority_test.go
similarity index 97%
rename from xds/internal/clients/xdsclient/test/authority_test.go
rename to internal/xds/clients/xdsclient/test/authority_test.go
index 0256c5bb..9941b989 100644
--- a/xds/internal/clients/xdsclient/test/authority_test.go
+++ b/internal/xds/clients/xdsclient/test/authority_test.go
@@ -25,12 +25,12 @@ import (
 
 	"github.com/google/uuid"
 	"google.golang.org/grpc/credentials/insecure"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils/e2e"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils/e2e"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
 
 	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 )
@@ -105,10 +105,10 @@ func setupForAuthorityTests(ctx context.Context, t *testing.T) (*testutils.Liste
 			testAuthority2: {XDSServers: []xdsclient.ServerConfig{}},
 			testAuthority3: {XDSServers: []xdsclient.ServerConfig{{ServerIdentifier: siNonDefault}}},
 		},
+		WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
 	}
 
 	// Create an xDS client with the above config.
-	overrideWatchExpiryTimeout(t, defaultTestWatchExpiryTimeout)
 	client, err := xdsclient.New(xdsClientConfig)
 	if err != nil {
 		t.Fatalf("Failed to create xDS client: %v", err)
diff --git a/xds/internal/clients/xdsclient/test/dump_test.go b/internal/xds/clients/xdsclient/test/dump_test.go
similarity index 97%
rename from xds/internal/clients/xdsclient/test/dump_test.go
rename to internal/xds/clients/xdsclient/test/dump_test.go
index 49f09453..900e91e2 100644
--- a/xds/internal/clients/xdsclient/test/dump_test.go
+++ b/internal/xds/clients/xdsclient/test/dump_test.go
@@ -29,13 +29,13 @@ import (
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/uuid"
 	"google.golang.org/grpc/credentials/insecure"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/internal/pretty"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils/e2e"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/internal/pretty"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils/e2e"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/testing/protocmp"
 	"google.golang.org/protobuf/types/known/anypb"
diff --git a/xds/internal/clients/xdsclient/test/helpers_test.go b/internal/xds/clients/xdsclient/test/helpers_test.go
similarity index 97%
rename from xds/internal/clients/xdsclient/test/helpers_test.go
rename to internal/xds/clients/xdsclient/test/helpers_test.go
index 6e7086fb..ae7d5cea 100644
--- a/xds/internal/clients/xdsclient/test/helpers_test.go
+++ b/internal/xds/clients/xdsclient/test/helpers_test.go
@@ -29,10 +29,10 @@ import (
 	"time"
 
 	"google.golang.org/grpc/internal/grpctest"
-	"google.golang.org/grpc/xds/internal/clients/internal/pretty"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients/internal/pretty"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 
diff --git a/xds/internal/clients/xdsclient/test/lds_watchers_test.go b/internal/xds/clients/xdsclient/test/lds_watchers_test.go
similarity index 97%
rename from xds/internal/clients/xdsclient/test/lds_watchers_test.go
rename to internal/xds/clients/xdsclient/test/lds_watchers_test.go
index e31d3457..ae478392 100644
--- a/xds/internal/clients/xdsclient/test/lds_watchers_test.go
+++ b/internal/xds/clients/xdsclient/test/lds_watchers_test.go
@@ -29,14 +29,13 @@ import (
 	"github.com/google/go-cmp/cmp/cmpopts"
 	"github.com/google/uuid"
 	"google.golang.org/grpc/credentials/insecure"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/internal/syncutil"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils/e2e"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient"
-	xdsclientinternal "google.golang.org/grpc/xds/internal/clients/xdsclient/internal"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/internal/syncutil"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils/e2e"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
 
 	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"
@@ -115,12 +114,6 @@ func badListenerResource(t *testing.T, name string) *v3listenerpb.Listener {
 	}
 }
 
-func overrideWatchExpiryTimeout(t *testing.T, watchExpiryTimeout time.Duration) {
-	originalWatchExpiryTimeout := xdsclientinternal.WatchExpiryTimeout
-	xdsclientinternal.WatchExpiryTimeout = watchExpiryTimeout
-	t.Cleanup(func() { xdsclientinternal.WatchExpiryTimeout = originalWatchExpiryTimeout })
-}
-
 // verifyNoListenerUpdate verifies that no listener update is received on the
 // provided update channel, and returns an error if an update is received.
 //
@@ -726,11 +719,10 @@ func (s) TestLDSWatch_ExpiryTimerFiresBeforeResponse(t *testing.T) {
 		Node:             clients.Node{ID: nodeID},
 		TransportBuilder: grpctransport.NewBuilder(configs),
 		ResourceTypes:    resourceTypes,
+		// Override the default watch expiry timeout.
+		WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
 	}
 
-	// Create an xDS client with the above config and override the default
-	// watch expiry timeout.
-	overrideWatchExpiryTimeout(t, defaultTestWatchExpiryTimeout)
 	client, err := xdsclient.New(xdsClientConfig)
 	if err != nil {
 		t.Fatalf("Failed to create xDS client: %v", err)
@@ -777,11 +769,11 @@ func (s) TestLDSWatch_ValidResponseCancelsExpiryTimerBehavior(t *testing.T) {
 		Node:             clients.Node{ID: nodeID},
 		TransportBuilder: grpctransport.NewBuilder(configs),
 		ResourceTypes:    resourceTypes,
+		// Override the default watch expiry timeout.
+		WatchExpiryTimeout: defaultTestWatchExpiryTimeout,
 	}
 
-	// Create an xDS client with the above config and override the default
-	// watch expiry timeout.
-	overrideWatchExpiryTimeout(t, defaultTestWatchExpiryTimeout)
+	// Create an xDS client with the above config.
 	client, err := xdsclient.New(xdsClientConfig)
 	if err != nil {
 		t.Fatalf("Failed to create xDS client: %v", err)
diff --git a/xds/internal/clients/xdsclient/test/metrics_test.go b/internal/xds/clients/xdsclient/test/metrics_test.go
similarity index 97%
rename from xds/internal/clients/xdsclient/test/metrics_test.go
rename to internal/xds/clients/xdsclient/test/metrics_test.go
index b49693bc..8332cc57 100644
--- a/xds/internal/clients/xdsclient/test/metrics_test.go
+++ b/internal/xds/clients/xdsclient/test/metrics_test.go
@@ -27,12 +27,12 @@ import (
 	"github.com/google/uuid"
 	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/internal/testutils"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils/e2e"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/metrics"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils/e2e"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/metrics"
 
 	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
diff --git a/xds/internal/clients/xdsclient/test/misc_watchers_test.go b/internal/xds/clients/xdsclient/test/misc_watchers_test.go
similarity index 75%
rename from xds/internal/clients/xdsclient/test/misc_watchers_test.go
rename to internal/xds/clients/xdsclient/test/misc_watchers_test.go
index 824d54be..e611c224 100644
--- a/xds/internal/clients/xdsclient/test/misc_watchers_test.go
+++ b/internal/xds/clients/xdsclient/test/misc_watchers_test.go
@@ -27,13 +27,13 @@ import (
 
 	"github.com/google/uuid"
 	"google.golang.org/grpc/credentials/insecure"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils/e2e"
-	"google.golang.org/grpc/xds/internal/clients/internal/testutils/fakeserver"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils/e2e"
+	"google.golang.org/grpc/internal/xds/clients/internal/testutils/fakeserver"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
 	"google.golang.org/protobuf/types/known/anypb"
 
 	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
@@ -527,139 +527,3 @@ func (s) TestWatchErrorsContainNodeID_ChannelCreationFailure(t *testing.T) {
 		}
 	}
 }
-
-// TestUnsubscribeAndResubscribe tests the scenario where the client is busy
-// processing a response (simulating a pending ACK at a higher level by holding
-// the onDone callback from watchers). During this busy state, a resource is
-// unsubscribed and then immediately resubscribed which causes the
-// unsubscription and new subscription requests to be buffered due to flow
-// control.
-//
-// The test verifies the following:
-//   - The resubscribed resource is served from the cache.
-//   - No "resource does not exist" error is generated for the resubscribed
-//     resource.
-func (s) TestRaceUnsubscribeResubscribe(t *testing.T) {
-	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
-	defer cancel()
-
-	mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{})
-	nodeID := uuid.New().String()
-
-	resourceTypes := map[string]xdsclient.ResourceType{xdsresource.V3ListenerURL: listenerType}
-	si := clients.ServerIdentifier{
-		ServerURI:  mgmtServer.Address,
-		Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"},
-	}
-
-	configs := map[string]grpctransport.Config{"insecure": {Credentials: insecure.NewBundle()}}
-	xdsClientConfig := xdsclient.Config{
-		Servers:          []xdsclient.ServerConfig{{ServerIdentifier: si}},
-		Node:             clients.Node{ID: nodeID},
-		TransportBuilder: grpctransport.NewBuilder(configs),
-		ResourceTypes:    resourceTypes,
-		// Xdstp resource names used in this test do not specify an
-		// authority. These will end up looking up an entry with the
-		// empty key in the authorities map. Having an entry with an
-		// empty key and empty configuration, results in these
-		// resources also using the top-level configuration.
-		Authorities: map[string]xdsclient.Authority{
-			"": {XDSServers: []xdsclient.ServerConfig{}},
-		},
-	}
-
-	// Create an xDS client with the above config.
-	client, err := xdsclient.New(xdsClientConfig)
-	if err != nil {
-		t.Fatalf("Failed to create xDS client: %v", err)
-	}
-	defer client.Close()
-
-	const ldsResourceName1 = "test-listener-resource1"
-	const ldsResourceName2 = "test-listener-resource2"
-	const rdsName1 = "test-route-configuration-resource1"
-	const rdsName2 = "test-route-configuration-resource2"
-	listenerResource1 := e2e.DefaultClientListener(ldsResourceName1, rdsName1)
-	listenerResource2 := e2e.DefaultClientListener(ldsResourceName2, rdsName2)
-
-	// Watch ldsResourceName1 with a regular watcher to ensure it's in cache
-	// and ACKed.
-	watcherInitial := newListenerWatcher()
-	cancelInitial := client.WatchResource(xdsresource.V3ListenerURL, ldsResourceName1, watcherInitial)
-	if err := mgmtServer.Update(ctx, e2e.UpdateOptions{NodeID: nodeID, Listeners: []*v3listenerpb.Listener{listenerResource1}, SkipValidation: true}); err != nil {
-		t.Fatalf("mgmtServer.Update() for %s failed: %v", ldsResourceName1, err)
-	}
-	if err := verifyListenerUpdate(ctx, watcherInitial.updateCh, listenerUpdateErrTuple{update: listenerUpdate{RouteConfigName: rdsName1}}); err != nil {
-		t.Fatalf("watcherR1Initial did not receive update for %s: %v", ldsResourceName1, err)
-	}
-	cancelInitial()
-
-	// Watch ldsResourceName1 and ldsResourceName2 using blocking watchers.
-	// - Server sends {ldsResourceName1, ldsResourceName2}.
-	// - Watchers for both resources get the update but we HOLD on to their
-	//   onDone callbacks.
-	blockingWatcherR1 := newBLockingListenerWatcher()
-	cancelR1 := client.WatchResource(xdsresource.V3ListenerURL, ldsResourceName1, blockingWatcherR1)
-	// defer cancelR1 later to create the race
-
-	blockingWatcherR2 := newBLockingListenerWatcher()
-	cancelR2 := client.WatchResource(xdsresource.V3ListenerURL, ldsResourceName2, blockingWatcherR2)
-	defer cancelR2()
-
-	// Configure the listener resources on the management server.
-	resources := e2e.UpdateOptions{
-		NodeID:         nodeID,
-		Listeners:      []*v3listenerpb.Listener{listenerResource1, listenerResource2},
-		SkipValidation: true}
-	if err := mgmtServer.Update(ctx, resources); err != nil {
-		t.Fatalf("mgmtServer.Update() for %s and %s failed: %v", ldsResourceName1, ldsResourceName2, err)
-	}
-
-	var onDoneR1, onDoneR2 func()
-	select {
-	case <-blockingWatcherR1.updateCh:
-		onDoneR1 = <-blockingWatcherR1.doneNotifierCh
-	case <-ctx.Done():
-		t.Fatalf("Timeout waiting for update for %s on blockingWatcherR1: %v", ldsResourceName1, ctx.Err())
-	}
-	select {
-	case <-blockingWatcherR2.updateCh:
-		onDoneR2 = <-blockingWatcherR2.doneNotifierCh
-	case <-ctx.Done():
-		t.Fatalf("Timeout waiting for update for %s on blockingWatcherR2: %v", ldsResourceName2, ctx.Err())
-	}
-
-	// At this point, ACK for {listenerResource1,listenerResource2} has been
-	// sent by the client but s.fc.pending.Load() is true because onDoneR1 and
-	// onDoneR2 are held.
-	//
-	// Unsubscribe listenerResource1. This request should be buffered by
-	// adsStreamImpl because s.fc.pending.Load() is true.
-	cancelR1()
-
-	// Resubscribe listenerResource1 with a new regular watcher, which should
-	// be served from cache.
-	watcherR1New := newListenerWatcher()
-	cancelR1New := client.WatchResource(xdsresource.V3ListenerURL, ldsResourceName1, watcherR1New)
-	defer cancelR1New()
-
-	if err := verifyListenerUpdate(ctx, watcherR1New.updateCh, listenerUpdateErrTuple{update: listenerUpdate{RouteConfigName: rdsName1}}); err != nil {
-		t.Fatalf("watcherR1New did not receive cached update for %s: %v", ldsResourceName1, err)
-	}
-
-	// Release the onDone callbacks.
-	if onDoneR1 != nil { // onDoneR1 might be nil if cancelR1() completed very fast.
-		onDoneR1()
-	}
-	onDoneR2()
-
-	// Verify watcherR1New does not get a "resource does not exist" error.
-	sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout*10) // Slightly longer to catch delayed errors
-	defer sCancel()
-	if err := verifyNoListenerUpdate(sCtx, watcherR1New.resourceErrCh); err != nil {
-		t.Fatalf("watcherR1New received unexpected resource error for %s: %v", ldsResourceName1, err)
-	}
-	if err := verifyNoListenerUpdate(sCtx, watcherR1New.ambientErrCh); err != nil {
-		t.Fatalf("watcherR1New received unexpected ambient error for %s: %v", ldsResourceName1, err)
-	}
-}
diff --git a/xds/internal/clients/xdsclient/xdsclient_test.go b/internal/xds/clients/xdsclient/xdsclient_test.go
similarity index 95%
rename from xds/internal/clients/xdsclient/xdsclient_test.go
rename to internal/xds/clients/xdsclient/xdsclient_test.go
index 073362d0..d1664382 100644
--- a/xds/internal/clients/xdsclient/xdsclient_test.go
+++ b/internal/xds/clients/xdsclient/xdsclient_test.go
@@ -23,9 +23,9 @@ import (
 	"testing"
 
 	"google.golang.org/grpc/credentials/insecure"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient/internal/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient/internal/xdsresource"
 )
 
 func (s) TestXDSClient_New(t *testing.T) {
diff --git a/xds/internal/clusterspecifier/rls/rls_test.go b/internal/xds/clusterspecifier/rls/rls_test.go
similarity index 97%
rename from xds/internal/clusterspecifier/rls/rls_test.go
rename to internal/xds/clusterspecifier/rls/rls_test.go
index 8f09ac15..70be4b88 100644
--- a/xds/internal/clusterspecifier/rls/rls_test.go
+++ b/internal/xds/clusterspecifier/rls/rls_test.go
@@ -27,12 +27,12 @@ import (
 	"google.golang.org/grpc/internal/grpctest"
 	rlspb "google.golang.org/grpc/internal/proto/grpc_lookup_v1"
 	"google.golang.org/grpc/internal/testutils"
-	"google.golang.org/grpc/xds/internal/clusterspecifier"
+	"google.golang.org/grpc/internal/xds/clusterspecifier"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/durationpb"
 
 	_ "google.golang.org/grpc/balancer/rls"                      // Register the RLS LB policy.
-	_ "google.golang.org/grpc/xds/internal/balancer/cdsbalancer" // Register the CDS LB policy.
+	_ "google.golang.org/grpc/internal/xds/balancer/cdsbalancer" // Register the CDS LB policy.
 )
 
 func init() {
diff --git a/xds/internal/httpfilter/fault/fault_test.go b/internal/xds/httpfilter/fault/fault_test.go
similarity index 99%
rename from xds/internal/httpfilter/fault/fault_test.go
rename to internal/xds/httpfilter/fault/fault_test.go
index 27377ccd..2612094b 100644
--- a/xds/internal/httpfilter/fault/fault_test.go
+++ b/internal/xds/httpfilter/fault/fault_test.go
@@ -55,9 +55,9 @@ import (
 	testgrpc "google.golang.org/grpc/interop/grpc_testing"
 	testpb "google.golang.org/grpc/interop/grpc_testing"
 
-	_ "google.golang.org/grpc/xds/internal/balancer"          // Register the balancers.
-	_ "google.golang.org/grpc/xds/internal/httpfilter/router" // Register the router filter.
-	_ "google.golang.org/grpc/xds/internal/resolver"          // Register the xds_resolver.
+	_ "google.golang.org/grpc/internal/xds/balancer"          // Register the balancers.
+	_ "google.golang.org/grpc/internal/xds/httpfilter/router" // Register the router filter.
+	_ "google.golang.org/grpc/internal/xds/resolver"          // Register the xds_resolver.
 )
 
 const defaultTestTimeout = 10 * time.Second
diff --git a/xds/internal/resolver/cluster_specifier_plugin_test.go b/internal/xds/resolver/cluster_specifier_plugin_test.go
similarity index 98%
rename from xds/internal/resolver/cluster_specifier_plugin_test.go
rename to internal/xds/resolver/cluster_specifier_plugin_test.go
index 5fc9e08a..d9a3fcec 100644
--- a/xds/internal/resolver/cluster_specifier_plugin_test.go
+++ b/internal/xds/resolver/cluster_specifier_plugin_test.go
@@ -29,10 +29,10 @@ import (
 	iresolver "google.golang.org/grpc/internal/resolver"
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
+	"google.golang.org/grpc/internal/xds/balancer/clustermanager"
+	"google.golang.org/grpc/internal/xds/clusterspecifier"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/serviceconfig"
-	"google.golang.org/grpc/xds/internal/balancer/clustermanager"
-	"google.golang.org/grpc/xds/internal/clusterspecifier"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 	"google.golang.org/protobuf/types/known/wrapperspb"
diff --git a/xds/internal/resolver/helpers_test.go b/internal/xds/resolver/helpers_test.go
similarity index 88%
rename from xds/internal/resolver/helpers_test.go
rename to internal/xds/resolver/helpers_test.go
index cdb9511b..243bbfd9 100644
--- a/xds/internal/resolver/helpers_test.go
+++ b/internal/xds/resolver/helpers_test.go
@@ -34,12 +34,14 @@ import (
 	iresolver "google.golang.org/grpc/internal/resolver"
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
+	xdsresolver "google.golang.org/grpc/internal/xds/resolver"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/serviceconfig"
 	"google.golang.org/grpc/status"
-	xdsresolver "google.golang.org/grpc/xds/internal/resolver"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
 
+	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
+	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
 	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
 	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
@@ -60,11 +62,17 @@ const (
 	defaultTestServiceName     = "service-name"
 	defaultTestRouteConfigName = "route-config-name"
 	defaultTestClusterName     = "cluster-name"
+	defaultTestEndpointName    = "endpoint-name"
+	defaultTestHostname        = "test-host"
 )
 
-// This is the expected service config when using default listener and route
-// configuration resources from the e2e package using the above resource names.
-var wantDefaultServiceConfig = fmt.Sprintf(`{
+var defaultTestPort = []uint32{8080}
+
+// wantServiceConfig returns a JSON representation of a service config with
+// xds_cluster_manager_experimental LB policy with a child policy of
+// cds_experimental for the provided cluster name.
+func wantServiceConfig(clusterName string) string {
+	return fmt.Sprintf(`{
    "loadBalancingConfig": [{
 	 "xds_cluster_manager_experimental": {
 	   "children": {
@@ -78,7 +86,8 @@ var wantDefaultServiceConfig = fmt.Sprintf(`{
 	   }
 	 }
    }]
- }`, defaultTestClusterName, defaultTestClusterName)
+ }`, clusterName, clusterName)
+}
 
 // buildResolverForTarget builds an xDS resolver for the given target. If
 // the bootstrap contents are provided, it build the xDS resolver using them
@@ -283,6 +292,21 @@ func configureResourcesOnManagementServer(ctx context.Context, t *testing.T, mgm
 	}
 }
 
+// Updates all the listener, route, cluster and endpoint configuration resources
+// on the given management server.
+func configureAllResourcesOnManagementServer(ctx context.Context, t *testing.T, mgmtServer *e2e.ManagementServer, nodeID string, listeners []*v3listenerpb.Listener, routes []*v3routepb.RouteConfiguration, clusters []*v3clusterpb.Cluster, endpoints []*v3endpointpb.ClusterLoadAssignment) {
+	resources := e2e.UpdateOptions{
+		NodeID:    nodeID,
+		Listeners: listeners,
+		Routes:    routes,
+		Clusters:  clusters,
+		Endpoints: endpoints,
+	}
+	if err := mgmtServer.Update(ctx, resources); err != nil {
+		t.Fatal(err)
+	}
+}
+
 // waitForResourceNames waits for the wantNames to be pushed on to namesCh.
 // Fails the test by calling t.Fatal if the context expires before that.
 func waitForResourceNames(ctx context.Context, t *testing.T, namesCh chan []string, wantNames []string) {
diff --git a/xds/internal/resolver/serviceconfig_test.go b/internal/xds/resolver/serviceconfig_test.go
similarity index 97%
rename from xds/internal/resolver/serviceconfig_test.go
rename to internal/xds/resolver/serviceconfig_test.go
index 003076de..3570ff2e 100644
--- a/xds/internal/resolver/serviceconfig_test.go
+++ b/internal/xds/resolver/serviceconfig_test.go
@@ -30,9 +30,9 @@ import (
 	"google.golang.org/grpc/internal/grpcutil"
 	iresolver "google.golang.org/grpc/internal/resolver"
 	"google.golang.org/grpc/internal/testutils"
+	_ "google.golang.org/grpc/internal/xds/balancer/cdsbalancer" // To parse LB config
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/grpc/metadata"
-	_ "google.golang.org/grpc/xds/internal/balancer/cdsbalancer" // To parse LB config
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
 )
 
 var defaultTestTimeout = 10 * time.Second
diff --git a/xds/internal/resolver/watch_service_test.go b/internal/xds/resolver/watch_service_test.go
similarity index 73%
rename from xds/internal/resolver/watch_service_test.go
rename to internal/xds/resolver/watch_service_test.go
index 7df159c6..1c68eba3 100644
--- a/xds/internal/resolver/watch_service_test.go
+++ b/internal/xds/resolver/watch_service_test.go
@@ -49,28 +49,35 @@ func (s) TestServiceWatch_ListenerPointsToNewRouteConfiguration(t *testing.T) {
 	mgmtServer, lisCh, routeCfgCh, bc := setupManagementServerForTest(t, nodeID)
 
 	// Configure resources on the management server.
-	listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
-	routes := []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(defaultTestRouteConfigName, defaultTestServiceName, defaultTestClusterName)}
-	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
+	resources := e2e.DefaultClientResources(e2e.ResourceParams{
+		DialTarget: defaultTestServiceName,
+		NodeID:     nodeID,
+		Host:       defaultTestHostname,
+		Port:       defaultTestPort[0],
+		SecLevel:   e2e.SecurityLevelNone,
+	})
+	if err := mgmtServer.Update(ctx, resources); err != nil {
+		t.Fatal(err)
+	}
 
 	stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
 
 	// Verify initial update from the resolver.
 	waitForResourceNames(ctx, t, lisCh, []string{defaultTestServiceName})
-	waitForResourceNames(ctx, t, routeCfgCh, []string{defaultTestRouteConfigName})
-	verifyUpdateFromResolver(ctx, t, stateCh, wantDefaultServiceConfig)
+	waitForResourceNames(ctx, t, routeCfgCh, []string{resources.Routes[0].Name})
+	verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(resources.Clusters[0].Name))
 
 	// Update the listener resource to point to a new route configuration name.
 	// Leave the old route configuration resource unchanged.
 	newTestRouteConfigName := defaultTestRouteConfigName + "-new"
-	listeners = []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, newTestRouteConfigName)}
-	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
+	resources.Listeners = []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, newTestRouteConfigName)}
+	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, resources.Listeners, resources.Routes)
 
 	// Verify that the new route configuration resource is requested.
 	waitForResourceNames(ctx, t, routeCfgCh, []string{newTestRouteConfigName})
 
 	// Update the old route configuration resource by adding a new route.
-	routes[0].VirtualHosts[0].Routes = append(routes[0].VirtualHosts[0].Routes, &v3routepb.Route{
+	resources.Routes[0].VirtualHosts[0].Routes = append(resources.Routes[0].VirtualHosts[0].Routes, &v3routepb.Route{
 		Match: &v3routepb.RouteMatch{
 			PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/foo/bar"},
 			CaseSensitive: &wrapperspb.BoolValue{Value: false},
@@ -81,17 +88,17 @@ func (s) TestServiceWatch_ListenerPointsToNewRouteConfiguration(t *testing.T) {
 			},
 		},
 	})
-	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
+	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, resources.Listeners, resources.Routes)
 
 	// Wait for no update from the resolver.
 	verifyNoUpdateFromResolver(ctx, t, stateCh)
 
 	// Update the management server with the new route configuration resource.
-	routes = append(routes, e2e.DefaultRouteConfig(newTestRouteConfigName, defaultTestServiceName, defaultTestClusterName))
-	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
+	resources.Routes = append(resources.Routes, e2e.DefaultRouteConfig(newTestRouteConfigName, defaultTestServiceName, resources.Clusters[0].Name))
+	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, resources.Listeners, resources.Routes)
 
 	// Ensure update from the resolver.
-	verifyUpdateFromResolver(ctx, t, stateCh, wantDefaultServiceConfig)
+	verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(resources.Clusters[0].Name))
 }
 
 // Tests the case where the listener resource changes to contain an inline route
@@ -106,22 +113,28 @@ func (s) TestServiceWatch_ListenerPointsToInlineRouteConfiguration(t *testing.T)
 	mgmtServer, lisCh, routeCfgCh, bc := setupManagementServerForTest(t, nodeID)
 
 	// Configure resources on the management server.
-	listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
-	routes := []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(defaultTestRouteConfigName, defaultTestServiceName, defaultTestClusterName)}
-	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
-
+	resources := e2e.DefaultClientResources(e2e.ResourceParams{
+		DialTarget: defaultTestServiceName,
+		NodeID:     nodeID,
+		Host:       defaultTestHostname,
+		Port:       defaultTestPort[0],
+		SecLevel:   e2e.SecurityLevelNone,
+	})
+	if err := mgmtServer.Update(ctx, resources); err != nil {
+		t.Fatal(err)
+	}
 	stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
 
 	// Verify initial update from the resolver.
 	waitForResourceNames(ctx, t, lisCh, []string{defaultTestServiceName})
-	waitForResourceNames(ctx, t, routeCfgCh, []string{defaultTestRouteConfigName})
-	verifyUpdateFromResolver(ctx, t, stateCh, wantDefaultServiceConfig)
+	waitForResourceNames(ctx, t, routeCfgCh, []string{resources.Routes[0].Name})
+	verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(resources.Clusters[0].Name))
 
 	// Update listener to contain an inline route configuration.
 	hcm := testutils.MarshalAny(t, &v3httppb.HttpConnectionManager{
 		RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
 			RouteConfig: &v3routepb.RouteConfiguration{
-				Name: defaultTestRouteConfigName,
+				Name: resources.Routes[0].Name,
 				VirtualHosts: []*v3routepb.VirtualHost{{
 					Domains: []string{defaultTestServiceName},
 					Routes: []*v3routepb.Route{{
@@ -130,7 +143,7 @@ func (s) TestServiceWatch_ListenerPointsToInlineRouteConfiguration(t *testing.T)
 						},
 						Action: &v3routepb.Route_Route{
 							Route: &v3routepb.RouteAction{
-								ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: defaultTestClusterName},
+								ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: resources.Clusters[0].Name},
 							},
 						},
 					}},
@@ -139,7 +152,7 @@ func (s) TestServiceWatch_ListenerPointsToInlineRouteConfiguration(t *testing.T)
 		},
 		HttpFilters: []*v3httppb.HttpFilter{e2e.HTTPFilter("router", &v3routerpb.Router{})},
 	})
-	listeners = []*v3listenerpb.Listener{{
+	resources.Listeners = []*v3listenerpb.Listener{{
 		Name:        defaultTestServiceName,
 		ApiListener: &v3listenerpb.ApiListener{ApiListener: hcm},
 		FilterChains: []*v3listenerpb.FilterChain{{
@@ -150,19 +163,19 @@ func (s) TestServiceWatch_ListenerPointsToInlineRouteConfiguration(t *testing.T)
 			}},
 		}},
 	}}
-	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, nil)
+	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, resources.Listeners, nil)
 
 	// Verify that the old route configuration is not requested anymore.
 	waitForResourceNames(ctx, t, routeCfgCh, []string{})
-	verifyUpdateFromResolver(ctx, t, stateCh, wantDefaultServiceConfig)
+	verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(resources.Clusters[0].Name))
 
 	// Update listener back to contain a route configuration name.
-	listeners = []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
-	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
+	resources.Listeners = []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, resources.Routes[0].Name)}
+	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, resources.Listeners, resources.Routes)
 
 	// Verify that that route configuration resource is requested.
-	waitForResourceNames(ctx, t, routeCfgCh, []string{defaultTestRouteConfigName})
+	waitForResourceNames(ctx, t, routeCfgCh, []string{resources.Routes[0].Name})
 
 	// Verify that appropriate SC is pushed on the channel.
-	verifyUpdateFromResolver(ctx, t, stateCh, wantDefaultServiceConfig)
+	verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(resources.Clusters[0].Name))
 }
diff --git a/xds/internal/resolver/xds_resolver_test.go b/internal/xds/resolver/xds_resolver_test.go
similarity index 90%
rename from xds/internal/resolver/xds_resolver_test.go
rename to internal/xds/resolver/xds_resolver_test.go
index 87b6b8b4..fb436556 100644
--- a/xds/internal/resolver/xds_resolver_test.go
+++ b/internal/xds/resolver/xds_resolver_test.go
@@ -38,15 +38,15 @@ import (
 	iringhash "google.golang.org/grpc/internal/ringhash"
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
+	"google.golang.org/grpc/internal/xds/balancer/clustermanager"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/httpfilter"
+	rinternal "google.golang.org/grpc/internal/xds/resolver/internal"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/grpc/metadata"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/serviceconfig"
-	"google.golang.org/grpc/xds/internal/balancer/clustermanager"
-	"google.golang.org/grpc/xds/internal/httpfilter"
-	rinternal "google.golang.org/grpc/xds/internal/resolver/internal"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 	"google.golang.org/protobuf/types/known/durationpb"
@@ -54,15 +54,17 @@ import (
 	"google.golang.org/protobuf/types/known/wrapperspb"
 
 	v3xdsxdstypepb "github.com/cncf/xds/go/xds/type/v3"
+	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
 	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
+	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
 	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
 	v3routerpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
 	v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
 	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
 
-	_ "google.golang.org/grpc/xds/internal/balancer/cdsbalancer" // Register the cds LB policy
-	_ "google.golang.org/grpc/xds/internal/httpfilter/router"    // Register the router filter
+	_ "google.golang.org/grpc/internal/xds/balancer/cdsbalancer" // Register the cds LB policy
+	_ "google.golang.org/grpc/internal/xds/httpfilter/router"    // Register the router filter
 )
 
 // Tests the case where xDS client creation is expected to fail because the
@@ -350,7 +352,7 @@ func (s) TestResolverBadServiceUpdate_NACKedWithCache(t *testing.T) {
 	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
 
 	// Expect a good update from the resolver.
-	cs := verifyUpdateFromResolver(ctx, t, stateCh, wantDefaultServiceConfig)
+	cs := verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(defaultTestClusterName))
 
 	// "Make an RPC" by invoking the config selector.
 	_, err := cs.SelectConfig(iresolver.RPCInfo{Context: ctx, Method: "/service/method"})
@@ -400,6 +402,8 @@ func (s) TestResolverGoodServiceUpdate(t *testing.T) {
 	for _, tt := range []struct {
 		name              string
 		routeConfig       *v3routepb.RouteConfiguration
+		clusterConfig     []*v3clusterpb.Cluster
+		endpointConfig    []*v3endpointpb.ClusterLoadAssignment
 		wantServiceConfig string
 		wantClusters      map[string]bool
 	}{
@@ -411,7 +415,9 @@ func (s) TestResolverGoodServiceUpdate(t *testing.T) {
 				ClusterSpecifierType: e2e.RouteConfigClusterSpecifierTypeCluster,
 				ClusterName:          defaultTestClusterName,
 			}),
-			wantServiceConfig: wantDefaultServiceConfig,
+			clusterConfig:     []*v3clusterpb.Cluster{e2e.DefaultCluster(defaultTestClusterName, defaultTestEndpointName, e2e.SecurityLevelNone)},
+			endpointConfig:    []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(defaultTestEndpointName, defaultTestHostname, defaultTestPort)},
+			wantServiceConfig: wantServiceConfig(defaultTestClusterName),
 			wantClusters:      map[string]bool{fmt.Sprintf("cluster:%s", defaultTestClusterName): true},
 		},
 		{
@@ -422,6 +428,12 @@ func (s) TestResolverGoodServiceUpdate(t *testing.T) {
 				ClusterSpecifierType: e2e.RouteConfigClusterSpecifierTypeWeightedCluster,
 				WeightedClusters:     map[string]int{"cluster_1": 75, "cluster_2": 25},
 			}),
+			clusterConfig: []*v3clusterpb.Cluster{
+				e2e.DefaultCluster("cluster_1", "endpoint_1", e2e.SecurityLevelNone),
+				e2e.DefaultCluster("cluster_2", "endpoint_2", e2e.SecurityLevelNone)},
+			endpointConfig: []*v3endpointpb.ClusterLoadAssignment{
+				e2e.DefaultEndpoint("endpoint_1", defaultTestHostname, defaultTestPort),
+				e2e.DefaultEndpoint("endpoint_2", defaultTestHostname, defaultTestPort)},
 			// This update contains the cluster from the previous update as well
 			// as this update, as the previous config selector still references
 			// the old cluster when the new one is pushed.
@@ -460,7 +472,7 @@ func (s) TestResolverGoodServiceUpdate(t *testing.T) {
 			// route configuration resource, as specified by the test case.
 			listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
 			routes := []*v3routepb.RouteConfiguration{tt.routeConfig}
-			configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
+			configureAllResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes, tt.clusterConfig, tt.endpointConfig)
 
 			stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
 
@@ -527,7 +539,9 @@ func (s) TestResolverRequestHash(t *testing.T) {
 			}},
 		}},
 	}}
-	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
+	cluster := []*v3clusterpb.Cluster{e2e.DefaultCluster(defaultTestClusterName, defaultTestEndpointName, e2e.SecurityLevelNone)}
+	endpoints := []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(defaultTestEndpointName, defaultTestHostname, defaultTestPort)}
+	configureAllResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes, cluster, endpoints)
 
 	// Build the resolver and read the config selector out of it.
 	stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
@@ -564,14 +578,19 @@ func (s) TestResolverRemovedWithRPCs(t *testing.T) {
 	mgmtServer, _, _, bc := setupManagementServerForTest(t, nodeID)
 
 	// Configure resources on the management server.
-	listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
-	routes := []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(defaultTestRouteConfigName, defaultTestServiceName, defaultTestClusterName)}
-	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
+	resources := e2e.DefaultClientResources(e2e.ResourceParams{
+		DialTarget: defaultTestServiceName,
+		NodeID:     nodeID,
+		Host:       defaultTestHostname,
+		Port:       defaultTestPort[0],
+		SecLevel:   e2e.SecurityLevelNone,
+	})
+	mgmtServer.Update(ctx, resources)
 
 	stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
 
 	// Read the update pushed by the resolver to the ClientConn.
-	cs := verifyUpdateFromResolver(ctx, t, stateCh, wantDefaultServiceConfig)
+	cs := verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(resources.Clusters[0].Name))
 
 	res, err := cs.SelectConfig(iresolver.RPCInfo{Context: ctx, Method: "/service/method"})
 	if err != nil {
@@ -588,7 +607,7 @@ func (s) TestResolverRemovedWithRPCs(t *testing.T) {
 	// not produce an empty service config at this point. Instead it will retain
 	// the cluster to which the RPC is ongoing in the service config, but will
 	// return an erroring config selector which will fail new RPCs.
-	cs = verifyUpdateFromResolver(ctx, t, stateCh, wantDefaultServiceConfig)
+	cs = verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(resources.Clusters[0].Name))
 	_, err = cs.SelectConfig(iresolver.RPCInfo{Context: ctx, Method: "/service/method"})
 	if err := verifyResolverError(err, codes.Unavailable, "has been removed", nodeID); err != nil {
 		t.Fatal(err)
@@ -628,14 +647,14 @@ waitForStateUpdate:
 		sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout)
 		defer sCancel()
 
-		configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
+		configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, resources.Listeners, resources.Routes)
 
 		select {
 		case state = <-stateCh:
 			if err := state.ServiceConfig.Err; err != nil {
 				t.Fatalf("Received error in service config: %v", state.ServiceConfig.Err)
 			}
-			wantSCParsed := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(wantDefaultServiceConfig)
+			wantSCParsed := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(wantServiceConfig(resources.Clusters[0].Name))
 			if !internal.EqualServiceConfigForTesting(state.ServiceConfig.Config, wantSCParsed.Config) {
 				t.Fatalf("Got service config:\n%s \nWant service config:\n%s", cmp.Diff(nil, state.ServiceConfig.Config), cmp.Diff(nil, wantSCParsed.Config))
 			}
@@ -666,14 +685,18 @@ func (s) TestResolverRemovedResource(t *testing.T) {
 	mgmtServer, _, _, bc := setupManagementServerForTest(t, nodeID)
 
 	// Configure resources on the management server.
-	listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
-	routes := []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(defaultTestRouteConfigName, defaultTestServiceName, defaultTestClusterName)}
-	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
-
+	resources := e2e.DefaultClientResources(e2e.ResourceParams{
+		DialTarget: defaultTestServiceName,
+		NodeID:     nodeID,
+		Host:       defaultTestHostname,
+		Port:       defaultTestPort[0],
+		SecLevel:   e2e.SecurityLevelNone,
+	})
+	mgmtServer.Update(ctx, resources)
 	stateCh, errCh, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
 
 	// Read the update pushed by the resolver to the ClientConn.
-	cs := verifyUpdateFromResolver(ctx, t, stateCh, wantDefaultServiceConfig)
+	cs := verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(resources.Clusters[0].Name))
 
 	// "Make an RPC" by invoking the config selector.
 	res, err := cs.SelectConfig(iresolver.RPCInfo{Context: ctx, Method: "/service/method"})
@@ -693,7 +716,7 @@ func (s) TestResolverRemovedResource(t *testing.T) {
 
 	// The channel should receive the existing service config with the original
 	// cluster but with an erroring config selector.
-	cs = verifyUpdateFromResolver(ctx, t, stateCh, wantDefaultServiceConfig)
+	cs = verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(resources.Clusters[0].Name))
 
 	// "Make another RPC" by invoking the config selector.
 	_, err = cs.SelectConfig(iresolver.RPCInfo{Context: ctx, Method: "/service/method"})
@@ -821,7 +844,17 @@ func (s) TestResolverMaxStreamDuration(t *testing.T) {
 			},
 		}},
 	}}
-	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
+	cluster := []*v3clusterpb.Cluster{
+		e2e.DefaultCluster("A", "endpoint_A", e2e.SecurityLevelNone),
+		e2e.DefaultCluster("B", "endpoint_B", e2e.SecurityLevelNone),
+		e2e.DefaultCluster("C", "endpoint_C", e2e.SecurityLevelNone),
+	}
+	endpoints := []*v3endpointpb.ClusterLoadAssignment{
+		e2e.DefaultEndpoint("endpoint_A", defaultTestHostname, defaultTestPort),
+		e2e.DefaultEndpoint("endpoint_B", defaultTestHostname, defaultTestPort),
+		e2e.DefaultEndpoint("endpoint_C", defaultTestHostname, defaultTestPort),
+	}
+	configureAllResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes, cluster, endpoints)
 
 	// Read the update pushed by the resolver to the ClientConn.
 	cs := verifyUpdateFromResolver(ctx, t, stateCh, "")
@@ -873,33 +906,43 @@ func (s) TestResolverDelayedOnCommitted(t *testing.T) {
 	mgmtServer, _, _, bc := setupManagementServerForTest(t, nodeID)
 
 	// Configure resources on the management server.
-	listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(defaultTestServiceName, defaultTestRouteConfigName)}
-	routes := []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(defaultTestRouteConfigName, defaultTestServiceName, defaultTestClusterName)}
-	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
+	resources := e2e.DefaultClientResources(e2e.ResourceParams{
+		DialTarget: defaultTestServiceName,
+		NodeID:     nodeID,
+		Host:       defaultTestHostname,
+		Port:       defaultTestPort[0],
+		SecLevel:   e2e.SecurityLevelNone,
+	})
+	mgmtServer.Update(ctx, resources)
 
 	stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
 
 	// Read the update pushed by the resolver to the ClientConn.
-	cs := verifyUpdateFromResolver(ctx, t, stateCh, wantDefaultServiceConfig)
+	cs := verifyUpdateFromResolver(ctx, t, stateCh, wantServiceConfig(resources.Clusters[0].Name))
 
 	// Make an RPC, but do not commit it yet.
 	resOld, err := cs.SelectConfig(iresolver.RPCInfo{Context: ctx, Method: "/service/method"})
 	if err != nil {
 		t.Fatalf("cs.SelectConfig(): %v", err)
 	}
-	wantClusterName := fmt.Sprintf("cluster:%s", defaultTestClusterName)
+	wantClusterName := fmt.Sprintf("cluster:%s", resources.Clusters[0].Name)
 	if cluster := clustermanager.GetPickedClusterForTesting(resOld.Context); cluster != wantClusterName {
 		t.Fatalf("Picked cluster is %q, want %q", cluster, wantClusterName)
 	}
 
 	// Delay resOld.OnCommitted(). As long as there are pending RPCs to removed
 	// clusters, they still appear in the service config.
-
+	oldClusterName := resources.Clusters[0].Name
 	// Update the route configuration resource on the management server to
 	// return a new cluster.
 	newClusterName := "new-" + defaultTestClusterName
-	routes = []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(defaultTestRouteConfigName, defaultTestServiceName, newClusterName)}
-	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
+	newEndpointName := "new-" + defaultTestEndpointName
+	resources.Routes = []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(resources.Routes[0].Name, defaultTestServiceName, newClusterName)}
+	resources.Clusters = []*v3clusterpb.Cluster{e2e.DefaultCluster(newClusterName, newEndpointName, e2e.SecurityLevelNone)}
+	resources.Endpoints = []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(newEndpointName, defaultTestHostname, defaultTestPort)}
+	if err := mgmtServer.Update(ctx, resources); err != nil {
+		t.Fatal(err)
+	}
 
 	// Read the update pushed by the resolver to the ClientConn and ensure the
 	// old cluster is present in the service config. Also ensure that the newly
@@ -932,7 +975,7 @@ func (s) TestResolverDelayedOnCommitted(t *testing.T) {
 		  }
 		}
 	  ]
-}`, defaultTestClusterName, defaultTestClusterName, newClusterName, newClusterName)
+}`, oldClusterName, oldClusterName, newClusterName, newClusterName)
 	cs = verifyUpdateFromResolver(ctx, t, stateCh, wantSC)
 
 	resNew, err := cs.SelectConfig(iresolver.RPCInfo{Context: ctx, Method: "/service/method"})
@@ -1050,7 +1093,15 @@ func (s) TestResolverWRR(t *testing.T) {
 		ClusterSpecifierType: e2e.RouteConfigClusterSpecifierTypeWeightedCluster,
 		WeightedClusters:     map[string]int{"A": 75, "B": 25},
 	})}
-	configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes)
+	clusters := []*v3clusterpb.Cluster{
+		e2e.DefaultCluster("A", "endpoint_A", e2e.SecurityLevelNone),
+		e2e.DefaultCluster("B", "endpoint_B", e2e.SecurityLevelNone),
+	}
+	endpoints := []*v3endpointpb.ClusterLoadAssignment{
+		e2e.DefaultEndpoint("endpoint_A", defaultTestHostname, defaultTestPort),
+		e2e.DefaultEndpoint("endpoint_B", defaultTestHostname, defaultTestPort),
+	}
+	configureAllResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, listeners, routes, clusters, endpoints)
 
 	// Read the update pushed by the resolver to the ClientConn.
 	cs := verifyUpdateFromResolver(ctx, t, stateCh, "")
@@ -1481,9 +1532,17 @@ func (s) TestXDSResolverHTTPFilters(t *testing.T) {
 			// Build an xDS resolver.
 			stateCh, _, _ := buildResolverForTarget(t, resolver.Target{URL: *testutils.MustParseURL("xds:///" + defaultTestServiceName)}, bc)
 
+			cluster := []*v3clusterpb.Cluster{
+				e2e.DefaultCluster("A", "endpoint_A", e2e.SecurityLevelNone),
+				e2e.DefaultCluster("B", "endpoint_B", e2e.SecurityLevelNone),
+			}
+			endpoints := []*v3endpointpb.ClusterLoadAssignment{
+				e2e.DefaultEndpoint("endpoint_A", defaultTestHostname, defaultTestPort),
+				e2e.DefaultEndpoint("endpoint_B", defaultTestHostname, defaultTestPort),
+			}
 			// Update the management server with a listener resource that
 			// contains an inline route configuration.
-			configureResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, []*v3listenerpb.Listener{tc.listener}, nil)
+			configureAllResourcesOnManagementServer(ctx, t, mgmtServer, nodeID, []*v3listenerpb.Listener{tc.listener}, nil, cluster, endpoints)
 
 			// Ensure that the resolver pushes a state update to the channel.
 			cs := verifyUpdateFromResolver(ctx, t, stateCh, "")
diff --git a/xds/internal/server/rds_handler_test.go b/internal/xds/server/rds_handler_test.go
similarity index 98%
rename from xds/internal/server/rds_handler_test.go
rename to internal/xds/server/rds_handler_test.go
index 886cf535..429ccaa1 100644
--- a/xds/internal/server/rds_handler_test.go
+++ b/internal/xds/server/rds_handler_test.go
@@ -30,8 +30,8 @@ import (
 	"google.golang.org/grpc/internal/grpctest"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 
 	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
 	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
diff --git a/xds/internal/test/e2e/README.md b/internal/xds/test/e2e/README.md
similarity index 100%
rename from xds/internal/test/e2e/README.md
rename to internal/xds/test/e2e/README.md
diff --git a/xds/internal/test/e2e/controlplane.go b/internal/xds/test/e2e/controlplane.go
similarity index 100%
rename from xds/internal/test/e2e/controlplane.go
rename to internal/xds/test/e2e/controlplane.go
diff --git a/xds/internal/test/e2e/e2e.go b/internal/xds/test/e2e/e2e.go
similarity index 100%
rename from xds/internal/test/e2e/e2e.go
rename to internal/xds/test/e2e/e2e.go
diff --git a/xds/internal/test/e2e/e2e_test.go b/internal/xds/test/e2e/e2e_test.go
similarity index 100%
rename from xds/internal/test/e2e/e2e_test.go
rename to internal/xds/test/e2e/e2e_test.go
diff --git a/xds/internal/test/e2e/e2e_utils.go b/internal/xds/test/e2e/e2e_utils.go
similarity index 100%
rename from xds/internal/test/e2e/e2e_utils.go
rename to internal/xds/test/e2e/e2e_utils.go
diff --git a/xds/internal/test/e2e/run.sh b/internal/xds/test/e2e/run.sh
similarity index 100%
rename from xds/internal/test/e2e/run.sh
rename to internal/xds/test/e2e/run.sh
diff --git a/xds/internal/testutils/balancer_test.go b/internal/xds/testutils/balancer_test.go
similarity index 100%
rename from xds/internal/testutils/balancer_test.go
rename to internal/xds/testutils/balancer_test.go
diff --git a/xds/internal/testutils/fakeclient/client.go b/internal/xds/testutils/fakeclient/client.go
similarity index 96%
rename from xds/internal/testutils/fakeclient/client.go
rename to internal/xds/testutils/fakeclient/client.go
index bfcbf5ae..fa465139 100644
--- a/xds/internal/testutils/fakeclient/client.go
+++ b/internal/xds/testutils/fakeclient/client.go
@@ -24,9 +24,9 @@ import (
 
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/lrsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/lrsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient"
 )
 
 // Client is a fake implementation of an xds client. It exposes a bunch of
diff --git a/xds/internal/testutils/resource_watcher.go b/internal/xds/testutils/resource_watcher.go
similarity index 97%
rename from xds/internal/testutils/resource_watcher.go
rename to internal/xds/testutils/resource_watcher.go
index e8f7830a..9feb8d2a 100644
--- a/xds/internal/testutils/resource_watcher.go
+++ b/internal/xds/testutils/resource_watcher.go
@@ -18,7 +18,7 @@
 
 package testutils
 
-import "google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+import "google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 
 // TestResourceWatcher implements the xdsresource.ResourceWatcher interface,
 // used to receive updates on watches registered with the xDS client, when using
diff --git a/xds/internal/testutils/testutils.go b/internal/xds/testutils/testutils.go
similarity index 92%
rename from xds/internal/testutils/testutils.go
rename to internal/xds/testutils/testutils.go
index 15049b6a..d806dec1 100644
--- a/xds/internal/testutils/testutils.go
+++ b/internal/xds/testutils/testutils.go
@@ -19,8 +19,8 @@
 package testutils
 
 import (
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 )
 
 // BuildResourceName returns the resource name in the format of an xdstp://
diff --git a/xds/internal/internal_test.go b/internal/xds/xds_test.go
similarity index 97%
rename from xds/internal/internal_test.go
rename to internal/xds/xds_test.go
index 2f6893b5..659be120 100644
--- a/xds/internal/internal_test.go
+++ b/internal/xds/xds_test.go
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package internal
+package xds
 
 import (
 	"reflect"
@@ -26,7 +26,7 @@ import (
 	corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
 	"github.com/google/go-cmp/cmp"
 	"google.golang.org/grpc/internal/grpctest"
-	"google.golang.org/grpc/xds/internal/clients"
+	"google.golang.org/grpc/internal/xds/clients"
 )
 
 const ignorePrefix = "XXX_"
diff --git a/xds/internal/xdsclient/client_refcounted_test.go b/internal/xds/xdsclient/client_refcounted_test.go
similarity index 100%
rename from xds/internal/xdsclient/client_refcounted_test.go
rename to internal/xds/xdsclient/client_refcounted_test.go
diff --git a/xds/internal/xdsclient/client_test.go b/internal/xds/xdsclient/client_test.go
similarity index 100%
rename from xds/internal/xdsclient/client_test.go
rename to internal/xds/xdsclient/client_test.go
diff --git a/xds/internal/xdsclient/clientimpl_test.go b/internal/xds/xdsclient/clientimpl_test.go
similarity index 97%
rename from xds/internal/xdsclient/clientimpl_test.go
rename to internal/xds/xdsclient/clientimpl_test.go
index fbfc24a0..d2fc8f7f 100644
--- a/xds/internal/xdsclient/clientimpl_test.go
+++ b/internal/xds/xdsclient/clientimpl_test.go
@@ -31,11 +31,11 @@ import (
 	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/internal/testutils/stats"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/clients/grpctransport"
-	"google.golang.org/grpc/xds/internal/clients/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/clients/grpctransport"
+	"google.golang.org/grpc/internal/xds/clients/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/testing/protocmp"
 )
 
@@ -208,7 +208,7 @@ func (s) TestBuildXDSClientConfig_Success(t *testing.T) {
 			if err != nil {
 				t.Fatalf("Failed to create bootstrap config: %v", err)
 			}
-			gotCfg, err := buildXDSClientConfig(bootstrapConfig, stats.NewTestMetricsRecorder(), testTargetName)
+			gotCfg, err := buildXDSClientConfig(bootstrapConfig, stats.NewTestMetricsRecorder(), testTargetName, 0)
 			if err != nil {
 				t.Fatalf("Failed to build XDSClientConfig: %v", err)
 			}
diff --git a/xds/internal/xdsclient/metrics_test.go b/internal/xds/xdsclient/metrics_test.go
similarity index 99%
rename from xds/internal/xdsclient/metrics_test.go
rename to internal/xds/xdsclient/metrics_test.go
index f876aa1a..26a38aea 100644
--- a/xds/internal/xdsclient/metrics_test.go
+++ b/internal/xds/xdsclient/metrics_test.go
@@ -30,12 +30,12 @@ import (
 	"google.golang.org/grpc/internal/testutils/stats"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 
 	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
 
-	_ "google.golang.org/grpc/xds/internal/httpfilter/router" // Register the router filter.
+	_ "google.golang.org/grpc/internal/xds/httpfilter/router" // Register the router filter.
 )
 
 type noopListenerWatcher struct{}
diff --git a/xds/internal/xdsclient/pool/pool_ext_test.go b/internal/xds/xdsclient/pool/pool_ext_test.go
similarity index 99%
rename from xds/internal/xdsclient/pool/pool_ext_test.go
rename to internal/xds/xdsclient/pool/pool_ext_test.go
index 6bd00b15..2150a270 100644
--- a/xds/internal/xdsclient/pool/pool_ext_test.go
+++ b/internal/xds/xdsclient/pool/pool_ext_test.go
@@ -34,8 +34,8 @@ import (
 	"google.golang.org/grpc/internal/testutils/stats"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient"
 	_ "google.golang.org/grpc/xds"
-	"google.golang.org/grpc/xds/internal/xdsclient"
 
 	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
 	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
diff --git a/xds/internal/xdsclient/requests_counter_test.go b/internal/xds/xdsclient/requests_counter_test.go
similarity index 100%
rename from xds/internal/xdsclient/requests_counter_test.go
rename to internal/xds/xdsclient/requests_counter_test.go
diff --git a/xds/internal/xdsclient/tests/ads_stream_ack_nack_test.go b/internal/xds/xdsclient/tests/ads_stream_ack_nack_test.go
similarity index 99%
rename from xds/internal/xdsclient/tests/ads_stream_ack_nack_test.go
rename to internal/xds/xdsclient/tests/ads_stream_ack_nack_test.go
index 09e9b39e..29ddae55 100644
--- a/xds/internal/xdsclient/tests/ads_stream_ack_nack_test.go
+++ b/internal/xds/xdsclient/tests/ads_stream_ack_nack_test.go
@@ -31,8 +31,8 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/testing/protocmp"
 
diff --git a/xds/internal/xdsclient/tests/ads_stream_restart_test.go b/internal/xds/xdsclient/tests/ads_stream_restart_test.go
similarity index 97%
rename from xds/internal/xdsclient/tests/ads_stream_restart_test.go
rename to internal/xds/xdsclient/tests/ads_stream_restart_test.go
index a53f96fb..58fad802 100644
--- a/xds/internal/xdsclient/tests/ads_stream_restart_test.go
+++ b/internal/xds/xdsclient/tests/ads_stream_restart_test.go
@@ -30,9 +30,9 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 
 	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
 	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
diff --git a/xds/internal/xdsclient/tests/authority_test.go b/internal/xds/xdsclient/tests/authority_test.go
similarity index 98%
rename from xds/internal/xdsclient/tests/authority_test.go
rename to internal/xds/xdsclient/tests/authority_test.go
index 187fb4d1..fbea7cb8 100644
--- a/xds/internal/xdsclient/tests/authority_test.go
+++ b/internal/xds/xdsclient/tests/authority_test.go
@@ -30,9 +30,9 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	xdstestutils "google.golang.org/grpc/xds/internal/testutils"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	xdstestutils "google.golang.org/grpc/internal/xds/testutils"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 
 	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
 )
diff --git a/xds/internal/xdsclient/tests/cds_watchers_test.go b/internal/xds/xdsclient/tests/cds_watchers_test.go
similarity index 99%
rename from xds/internal/xdsclient/tests/cds_watchers_test.go
rename to internal/xds/xdsclient/tests/cds_watchers_test.go
index 3df630e5..e6368340 100644
--- a/xds/internal/xdsclient/tests/cds_watchers_test.go
+++ b/internal/xds/xdsclient/tests/cds_watchers_test.go
@@ -34,8 +34,8 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 
 	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
 	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
diff --git a/xds/internal/xdsclient/tests/client_custom_dialopts_test.go b/internal/xds/xdsclient/tests/client_custom_dialopts_test.go
similarity index 98%
rename from xds/internal/xdsclient/tests/client_custom_dialopts_test.go
rename to internal/xds/xdsclient/tests/client_custom_dialopts_test.go
index 0f65b69c..39deb124 100644
--- a/xds/internal/xdsclient/tests/client_custom_dialopts_test.go
+++ b/internal/xds/xdsclient/tests/client_custom_dialopts_test.go
@@ -34,11 +34,11 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	internalbootstrap "google.golang.org/grpc/internal/xds/bootstrap"
+	xci "google.golang.org/grpc/internal/xds/xdsclient/internal"
 	testgrpc "google.golang.org/grpc/interop/grpc_testing"
 	testpb "google.golang.org/grpc/interop/grpc_testing"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/xds/bootstrap"
-	xci "google.golang.org/grpc/xds/internal/xdsclient/internal"
 )
 
 // nopDialOption is a no-op grpc.DialOption with a name.
diff --git a/xds/internal/xdsclient/tests/dump_test.go b/internal/xds/xdsclient/tests/dump_test.go
similarity index 99%
rename from xds/internal/xdsclient/tests/dump_test.go
rename to internal/xds/xdsclient/tests/dump_test.go
index 117c810b..a476c29b 100644
--- a/xds/internal/xdsclient/tests/dump_test.go
+++ b/internal/xds/xdsclient/tests/dump_test.go
@@ -34,8 +34,8 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/protobuf/testing/protocmp"
 	"google.golang.org/protobuf/types/known/anypb"
 
diff --git a/xds/internal/xdsclient/tests/eds_watchers_test.go b/internal/xds/xdsclient/tests/eds_watchers_test.go
similarity index 99%
rename from xds/internal/xdsclient/tests/eds_watchers_test.go
rename to internal/xds/xdsclient/tests/eds_watchers_test.go
index d551562c..a76c5864 100644
--- a/xds/internal/xdsclient/tests/eds_watchers_test.go
+++ b/internal/xds/xdsclient/tests/eds_watchers_test.go
@@ -33,9 +33,9 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/protobuf/types/known/wrapperspb"
 
 	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
diff --git a/xds/internal/xdsclient/tests/fallback_test.go b/internal/xds/xdsclient/tests/fallback_test.go
similarity index 99%
rename from xds/internal/xdsclient/tests/fallback_test.go
rename to internal/xds/xdsclient/tests/fallback_test.go
index 5767a76a..d4e2d807 100644
--- a/xds/internal/xdsclient/tests/fallback_test.go
+++ b/internal/xds/xdsclient/tests/fallback_test.go
@@ -34,11 +34,11 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/grpc/peer"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/status"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
 
 	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
 	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
diff --git a/xds/internal/xdsclient/tests/federation_watchers_test.go b/internal/xds/xdsclient/tests/federation_watchers_test.go
similarity index 98%
rename from xds/internal/xdsclient/tests/federation_watchers_test.go
rename to internal/xds/xdsclient/tests/federation_watchers_test.go
index 7f9babde..1bf0dc55 100644
--- a/xds/internal/xdsclient/tests/federation_watchers_test.go
+++ b/internal/xds/xdsclient/tests/federation_watchers_test.go
@@ -26,9 +26,9 @@ import (
 	"github.com/google/uuid"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 
 	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
 	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
diff --git a/xds/internal/xdsclient/tests/helpers_test.go b/internal/xds/xdsclient/tests/helpers_test.go
similarity index 100%
rename from xds/internal/xdsclient/tests/helpers_test.go
rename to internal/xds/xdsclient/tests/helpers_test.go
diff --git a/xds/internal/xdsclient/tests/lds_watchers_test.go b/internal/xds/xdsclient/tests/lds_watchers_test.go
similarity index 99%
rename from xds/internal/xdsclient/tests/lds_watchers_test.go
rename to internal/xds/xdsclient/tests/lds_watchers_test.go
index ff40de28..87714863 100644
--- a/xds/internal/xdsclient/tests/lds_watchers_test.go
+++ b/internal/xds/xdsclient/tests/lds_watchers_test.go
@@ -34,16 +34,16 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 
 	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 	v3routerpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
 	v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
 	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
 
+	_ "google.golang.org/grpc/internal/xds/httpfilter/router" // Register the router filter.
 	_ "google.golang.org/grpc/xds"                            // To ensure internal.NewXDSResolverWithConfigForTesting is set.
-	_ "google.golang.org/grpc/xds/internal/httpfilter/router" // Register the router filter.
 )
 
 type noopListenerWatcher struct{}
diff --git a/xds/internal/xdsclient/tests/loadreport_test.go b/internal/xds/xdsclient/tests/loadreport_test.go
similarity index 81%
rename from xds/internal/xdsclient/tests/loadreport_test.go
rename to internal/xds/xdsclient/tests/loadreport_test.go
index a93b5bc1..95a9354d 100644
--- a/xds/internal/xdsclient/tests/loadreport_test.go
+++ b/internal/xds/xdsclient/tests/loadreport_test.go
@@ -23,6 +23,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"net"
+	"sync"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -30,17 +31,24 @@ import (
 	"github.com/google/uuid"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/credentials/insecure"
+	"google.golang.org/grpc/internal"
+	"google.golang.org/grpc/internal/stubserver"
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/testutils/xds/fakeserver"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/status"
-	"google.golang.org/grpc/xds/internal/clients"
 	"google.golang.org/protobuf/testing/protocmp"
 
 	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
 	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
 	v3lrspb "github.com/envoyproxy/go-control-plane/envoy/service/load_stats/v3"
+	testgrpc "google.golang.org/grpc/interop/grpc_testing"
+	testpb "google.golang.org/grpc/interop/grpc_testing"
 	"google.golang.org/protobuf/types/known/durationpb"
 )
 
@@ -437,3 +445,111 @@ func (s) TestReportLoad_StreamCreation(t *testing.T) {
 	defer sCancel3()
 	cancel3(sCtx3)
 }
+
+// TestConcurrentReportLoad verifies that the client can safely handle concurrent
+// requests to initiate load reporting streams. It launches multiple goroutines
+// that all call client.ReportLoad simultaneously.
+func (s) TestConcurrentReportLoad(t *testing.T) {
+	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
+	defer cancel()
+
+	mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{SupportLoadReportingService: true})
+	nodeID := uuid.New().String()
+	bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
+	client := createXDSClient(t, bc)
+
+	serverConfig, err := bootstrap.ServerConfigForTesting(bootstrap.ServerConfigTestingOptions{URI: mgmtServer.Address})
+	if err != nil {
+		t.Fatalf("Failed to create server config for testing: %v", err)
+	}
+
+	// Call ReportLoad() concurrently from multiple go routines.
+	var wg sync.WaitGroup
+	const numGoroutines = 10
+	wg.Add(numGoroutines)
+	for range numGoroutines {
+		go func() {
+			defer wg.Done()
+			_, cancelStore := client.ReportLoad(serverConfig)
+			defer cancelStore(ctx)
+		}()
+	}
+	wg.Wait()
+}
+
+// TestConcurrentChannels verifies that we can create multiple gRPC channels
+// concurrently with a shared XDSClient, each of which will create a new LRS
+// stream without any race.
+func (s) TestConcurrentChannels(t *testing.T) {
+	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
+	defer cancel()
+
+	mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{AllowResourceSubset: true, SupportLoadReportingService: true})
+
+	nodeID := uuid.New().String()
+	bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)
+
+	if internal.NewXDSResolverWithPoolForTesting == nil {
+		t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil")
+	}
+
+	config, err := bootstrap.NewConfigFromContents(bc)
+	if err != nil {
+		t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err)
+	}
+	pool := xdsclient.NewPool(config)
+
+	resolverBuilder := internal.NewXDSResolverWithPoolForTesting.(func(*xdsclient.Pool) (resolver.Builder, error))
+	xdsResolver, err := resolverBuilder(pool)
+	if err != nil {
+		t.Fatalf("Failed to create xDS resolver for testing: %v", err)
+	}
+
+	server := stubserver.StartTestService(t, nil)
+	defer server.Stop()
+
+	// Configure the management server with resources that enable LRS.
+	const serviceName = "my-service-e2e-lrs-test"
+	resources := e2e.DefaultClientResources(e2e.ResourceParams{
+		DialTarget: serviceName,
+		NodeID:     nodeID,
+		Host:       "localhost",
+		Port:       testutils.ParsePort(t, server.Address),
+		SecLevel:   e2e.SecurityLevelNone,
+	})
+	resources.Clusters[0].LrsServer = &v3corepb.ConfigSource{
+		ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
+			Self: &v3corepb.SelfConfigSource{},
+		},
+	}
+	if err := mgmtServer.Update(ctx, resources); err != nil {
+		t.Fatal(err)
+	}
+
+	var wg sync.WaitGroup
+	const (
+		numGoroutines = 10
+		numRPCs       = 10
+	)
+	for range numGoroutines {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			for range numRPCs {
+				cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(xdsResolver))
+				if err != nil {
+					t.Errorf("grpc.NewClient() failed: %v", err)
+					return
+				}
+				defer cc.Close()
+
+				testClient := testgrpc.NewTestServiceClient(cc)
+				if _, err := testClient.EmptyCall(ctx, &testpb.Empty{}); err != nil {
+					t.Errorf("EmptyCall() failed: %v", err)
+					return
+				}
+			}
+		}()
+	}
+	wg.Wait()
+}
diff --git a/xds/internal/xdsclient/tests/rds_watchers_test.go b/internal/xds/xdsclient/tests/rds_watchers_test.go
similarity index 99%
rename from xds/internal/xdsclient/tests/rds_watchers_test.go
rename to internal/xds/xdsclient/tests/rds_watchers_test.go
index a650bd7c..3ec333ad 100644
--- a/xds/internal/xdsclient/tests/rds_watchers_test.go
+++ b/internal/xds/xdsclient/tests/rds_watchers_test.go
@@ -33,8 +33,8 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/protobuf/types/known/wrapperspb"
 
 	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
diff --git a/xds/internal/xdsclient/tests/resource_update_test.go b/internal/xds/xdsclient/tests/resource_update_test.go
similarity index 99%
rename from xds/internal/xdsclient/tests/resource_update_test.go
rename to internal/xds/xdsclient/tests/resource_update_test.go
index 25109408..90517a90 100644
--- a/xds/internal/xdsclient/tests/resource_update_test.go
+++ b/internal/xds/xdsclient/tests/resource_update_test.go
@@ -33,9 +33,9 @@ import (
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/testutils/xds/fakeserver"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/testing/protocmp"
 	"google.golang.org/protobuf/types/known/anypb"
@@ -51,7 +51,7 @@ import (
 	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
 	v3statuspb "github.com/envoyproxy/go-control-plane/envoy/service/status/v3"
 
-	_ "google.golang.org/grpc/xds/internal/httpfilter/router" // Register the router filter.
+	_ "google.golang.org/grpc/internal/xds/httpfilter/router" // Register the router filter.
 )
 
 // startFakeManagementServer starts a fake xDS management server and registers a
diff --git a/xds/internal/xdsclient/xdsclient_test.go b/internal/xds/xdsclient/xdsclient_test.go
similarity index 100%
rename from xds/internal/xdsclient/xdsclient_test.go
rename to internal/xds/xdsclient/xdsclient_test.go
diff --git a/xds/internal/xdsclient/xdslbregistry/xdslbregistry_test.go b/internal/xds/xdsclient/xdslbregistry/xdslbregistry_test.go
similarity index 99%
rename from xds/internal/xdsclient/xdslbregistry/xdslbregistry_test.go
rename to internal/xds/xdsclient/xdslbregistry/xdslbregistry_test.go
index 5760e632..d23f4678 100644
--- a/xds/internal/xdsclient/xdslbregistry/xdslbregistry_test.go
+++ b/internal/xds/xdsclient/xdslbregistry/xdslbregistry_test.go
@@ -31,9 +31,9 @@ import (
 	"google.golang.org/grpc/internal/pretty"
 	internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
 	"google.golang.org/grpc/internal/testutils"
+	"google.golang.org/grpc/internal/xds/balancer/wrrlocality"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdslbregistry"
 	_ "google.golang.org/grpc/xds" // Register the xDS LB Registry Converters.
-	"google.golang.org/grpc/xds/internal/balancer/wrrlocality"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdslbregistry"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 	"google.golang.org/protobuf/types/known/structpb"
diff --git a/xds/internal/xdsclient/xdsresource/filter_chain_test.go b/internal/xds/xdsclient/xdsresource/filter_chain_test.go
similarity index 99%
rename from xds/internal/xdsclient/xdsresource/filter_chain_test.go
rename to internal/xds/xdsclient/xdsresource/filter_chain_test.go
index 67b37dff..a6dcacec 100644
--- a/xds/internal/xdsclient/xdsresource/filter_chain_test.go
+++ b/internal/xds/xdsclient/xdsresource/filter_chain_test.go
@@ -43,9 +43,9 @@ import (
 	iresolver "google.golang.org/grpc/internal/resolver"
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
-	"google.golang.org/grpc/xds/internal/httpfilter"
-	"google.golang.org/grpc/xds/internal/httpfilter/router"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/httpfilter"
+	"google.golang.org/grpc/internal/xds/httpfilter/router"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 )
 
 const (
diff --git a/xds/internal/xdsclient/xdsresource/matcher_path_test.go b/internal/xds/xdsclient/xdsresource/matcher_path_test.go
similarity index 100%
rename from xds/internal/xdsclient/xdsresource/matcher_path_test.go
rename to internal/xds/xdsclient/xdsresource/matcher_path_test.go
diff --git a/xds/internal/xdsclient/xdsresource/matcher_test.go b/internal/xds/xdsclient/xdsresource/matcher_test.go
similarity index 100%
rename from xds/internal/xdsclient/xdsresource/matcher_test.go
rename to internal/xds/xdsclient/xdsresource/matcher_test.go
diff --git a/internal/xds/xdsclient/xdsresource/metadata_test.go b/internal/xds/xdsclient/xdsresource/metadata_test.go
new file mode 100644
index 00000000..28de8b51
--- /dev/null
+++ b/internal/xds/xdsclient/xdsresource/metadata_test.go
@@ -0,0 +1,199 @@
+/*
+ *
+ * Copyright 2025 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 xdsresource
+
+import (
+	"testing"
+
+	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
+	"github.com/google/go-cmp/cmp"
+	"google.golang.org/grpc/internal/testutils"
+)
+
+const proxyAddressTypeURL = "type.googleapis.com/envoy.config.core.v3.Address"
+
+func (s) TestProxyAddressConverterSuccess(t *testing.T) {
+	converter := metadataConverterForType(proxyAddressTypeURL)
+	if converter == nil {
+		t.Fatalf("Converter for %q not found in registry", proxyAddressTypeURL)
+	}
+	tests := []struct {
+		name string
+		addr *v3corepb.Address
+		want ProxyAddressMetadataValue
+	}{
+		{
+			name: "valid IPv4 address and port",
+			addr: &v3corepb.Address{
+				Address: &v3corepb.Address_SocketAddress{
+					SocketAddress: &v3corepb.SocketAddress{
+						Address: "192.168.1.1",
+						PortSpecifier: &v3corepb.SocketAddress_PortValue{
+							PortValue: 8080,
+						},
+					},
+				},
+			},
+			want: ProxyAddressMetadataValue{
+				Address: "192.168.1.1:8080",
+			},
+		},
+		{
+			name: "valid full IPv6 address and port",
+			addr: &v3corepb.Address{
+				Address: &v3corepb.Address_SocketAddress{
+					SocketAddress: &v3corepb.SocketAddress{
+						Address: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+						PortSpecifier: &v3corepb.SocketAddress_PortValue{
+							PortValue: 9090,
+						},
+					},
+				},
+			},
+			want: ProxyAddressMetadataValue{
+				Address: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:9090",
+			},
+		},
+		{
+			name: "valid shortened IPv6 address",
+			addr: &v3corepb.Address{
+				Address: &v3corepb.Address_SocketAddress{
+					SocketAddress: &v3corepb.SocketAddress{
+						Address: "2001:db8::1",
+						PortSpecifier: &v3corepb.SocketAddress_PortValue{
+							PortValue: 9090,
+						},
+					},
+				},
+			},
+			want: ProxyAddressMetadataValue{
+				Address: "[2001:db8::1]:9090",
+			},
+		},
+		{
+			name: "valid link-local IPv6 address",
+			addr: &v3corepb.Address{
+				Address: &v3corepb.Address_SocketAddress{
+					SocketAddress: &v3corepb.SocketAddress{
+						Address: "fe80::1ff:fe23:4567:890a",
+						PortSpecifier: &v3corepb.SocketAddress_PortValue{
+							PortValue: 8888,
+						},
+					},
+				},
+			},
+			want: ProxyAddressMetadataValue{
+				Address: "[fe80::1ff:fe23:4567:890a]:8888",
+			},
+		},
+		{
+			name: "valid IPv4-mapped IPv6 address",
+			addr: &v3corepb.Address{
+				Address: &v3corepb.Address_SocketAddress{
+					SocketAddress: &v3corepb.SocketAddress{
+						Address: "::ffff:192.0.2.128",
+						PortSpecifier: &v3corepb.SocketAddress_PortValue{
+							PortValue: 1234,
+						},
+					},
+				},
+			},
+			want: ProxyAddressMetadataValue{
+				Address: "[::ffff:192.0.2.128]:1234",
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			anyProto := testutils.MarshalAny(t, tt.addr)
+			got, err := converter.convert(anyProto)
+			if err != nil {
+				t.Fatalf("convert() failed with error: %v", err)
+			}
+			if diff := cmp.Diff(tt.want, got, cmp.AllowUnexported(ProxyAddressMetadataValue{})); diff != "" {
+				t.Errorf("convert() returned unexpected value (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
+
+func (s) TestProxyAddressConverterFailure(t *testing.T) {
+	converter := metadataConverterForType(proxyAddressTypeURL)
+	if converter == nil {
+		t.Fatalf("Converter for %q not found in registry", proxyAddressTypeURL)
+	}
+	tests := []struct {
+		name    string
+		addr    *v3corepb.Address
+		wantErr string
+	}{
+		{
+			name: "invalid address",
+			addr: &v3corepb.Address{
+				Address: &v3corepb.Address_SocketAddress{
+					SocketAddress: &v3corepb.SocketAddress{
+						Address: "invalid-ip",
+					},
+				},
+			},
+			wantErr: "address field is not a valid IPv4 or IPv6 address: \"invalid-ip\"",
+		},
+		{
+			name: "missing socket_address",
+			addr: &v3corepb.Address{
+				// No SocketAddress field set.
+			},
+			wantErr: "no socket_address field in metadata",
+		},
+		{
+			name: "address is not a socket address",
+			addr: &v3corepb.Address{
+				Address: &v3corepb.Address_EnvoyInternalAddress{
+					EnvoyInternalAddress: &v3corepb.EnvoyInternalAddress{
+						AddressNameSpecifier: &v3corepb.EnvoyInternalAddress_ServerListenerName{
+							ServerListenerName: "some-internal-listener",
+						},
+					},
+				},
+			},
+			wantErr: "no socket_address field in metadata",
+		},
+		{
+			name: "port value not set",
+			addr: &v3corepb.Address{
+				Address: &v3corepb.Address_SocketAddress{
+					SocketAddress: &v3corepb.SocketAddress{
+						Address:       "127.0.0.1",
+						PortSpecifier: nil,
+					},
+				},
+			},
+			wantErr: "port value not set in socket_address",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			anyProto := testutils.MarshalAny(t, tt.addr)
+			_, err := converter.convert(anyProto)
+			if err == nil || err.Error() != tt.wantErr {
+				t.Errorf("convert() got error = %v, wantErr = %q", err, tt.wantErr)
+			}
+		})
+	}
+}
diff --git a/xds/internal/xdsclient/xdsresource/name_test.go b/internal/xds/xdsclient/xdsresource/name_test.go
similarity index 100%
rename from xds/internal/xdsclient/xdsresource/name_test.go
rename to internal/xds/xdsclient/xdsresource/name_test.go
diff --git a/xds/internal/xdsclient/xdsresource/test_utils_test.go b/internal/xds/xdsclient/xdsresource/test_utils_test.go
similarity index 100%
rename from xds/internal/xdsclient/xdsresource/test_utils_test.go
rename to internal/xds/xdsclient/xdsresource/test_utils_test.go
diff --git a/xds/internal/xdsclient/xdsresource/tests/unmarshal_cds_test.go b/internal/xds/xdsclient/xdsresource/tests/unmarshal_cds_test.go
similarity index 95%
rename from xds/internal/xdsclient/xdsresource/tests/unmarshal_cds_test.go
rename to internal/xds/xdsclient/xdsresource/tests/unmarshal_cds_test.go
index c9eaef5a..c25ddf62 100644
--- a/xds/internal/xdsclient/xdsresource/tests/unmarshal_cds_test.go
+++ b/internal/xds/xdsclient/xdsresource/tests/unmarshal_cds_test.go
@@ -32,11 +32,11 @@ import (
 	iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
+	xdsinternal "google.golang.org/grpc/internal/xds"
+	"google.golang.org/grpc/internal/xds/balancer/wrrlocality"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/grpc/serviceconfig"
-	"google.golang.org/grpc/xds/internal"
-	"google.golang.org/grpc/xds/internal/balancer/wrrlocality"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 	"google.golang.org/protobuf/types/known/structpb"
@@ -142,7 +142,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
 				ClusterName:     clusterName,
 				ClusterType:     xdsresource.ClusterTypeLogicalDNS,
 				DNSHostName:     "dns_host:8080",
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 			wantLBConfig: &iserviceconfig.BalancerConfig{
 				Name: wrrlocality.Name,
@@ -171,7 +171,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
 				ClusterName:             clusterName,
 				ClusterType:             xdsresource.ClusterTypeAggregate,
 				PrioritizedClusterNames: []string{"a", "b", "c"},
-				TelemetryLabels:         internal.UnknownCSMLabels,
+				TelemetryLabels:         xdsinternal.UnknownCSMLabels,
 			},
 			wantLBConfig: &iserviceconfig.BalancerConfig{
 				Name: wrrlocality.Name,
@@ -187,7 +187,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
 			cluster: e2e.DefaultCluster(clusterName, "", e2e.SecurityLevelNone),
 			wantUpdate: xdsresource.ClusterUpdate{
 				ClusterName:     clusterName,
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 			wantLBConfig: &iserviceconfig.BalancerConfig{
 				Name: wrrlocality.Name,
@@ -204,7 +204,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
 			wantUpdate: xdsresource.ClusterUpdate{
 				ClusterName:     clusterName,
 				EDSServiceName:  serviceName,
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 			wantLBConfig: &iserviceconfig.BalancerConfig{
 				Name: wrrlocality.Name,
@@ -227,7 +227,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
 				ClusterName:     clusterName,
 				EDSServiceName:  serviceName,
 				LRSServerConfig: serverCfg,
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 			wantLBConfig: &iserviceconfig.BalancerConfig{
 				Name: wrrlocality.Name,
@@ -266,7 +266,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
 				EDSServiceName:  serviceName,
 				LRSServerConfig: serverCfg,
 				MaxRequests:     func() *uint32 { i := uint32(512); return &i }(),
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 			wantLBConfig: &iserviceconfig.BalancerConfig{
 				Name: wrrlocality.Name,
@@ -287,7 +287,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
 			wantUpdate: xdsresource.ClusterUpdate{
 				ClusterName:     clusterName,
 				EDSServiceName:  serviceName,
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 			wantLBConfig: &iserviceconfig.BalancerConfig{
 				Name: "ring_hash_experimental",
@@ -315,7 +315,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
 			wantUpdate: xdsresource.ClusterUpdate{
 				ClusterName:     clusterName,
 				EDSServiceName:  serviceName,
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 			wantLBConfig: &iserviceconfig.BalancerConfig{
 				Name: "least_request_experimental",
@@ -340,7 +340,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
 			wantUpdate: xdsresource.ClusterUpdate{
 				ClusterName:     clusterName,
 				EDSServiceName:  serviceName,
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 			wantLBConfig: &iserviceconfig.BalancerConfig{
 				Name: "ring_hash_experimental",
@@ -373,7 +373,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
 			wantUpdate: xdsresource.ClusterUpdate{
 				ClusterName:     clusterName,
 				EDSServiceName:  serviceName,
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 			wantLBConfig: &iserviceconfig.BalancerConfig{
 				Name: "least_request_experimental",
@@ -412,7 +412,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
 			wantUpdate: xdsresource.ClusterUpdate{
 				ClusterName:     clusterName,
 				EDSServiceName:  serviceName,
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 			wantLBConfig: &iserviceconfig.BalancerConfig{
 				Name: "ring_hash_experimental",
@@ -448,7 +448,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
 			wantUpdate: xdsresource.ClusterUpdate{
 				ClusterName:     clusterName,
 				EDSServiceName:  serviceName,
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 			wantLBConfig: &iserviceconfig.BalancerConfig{
 				Name: wrrlocality.Name,
@@ -488,7 +488,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
 			wantUpdate: xdsresource.ClusterUpdate{
 				ClusterName:     clusterName,
 				EDSServiceName:  serviceName,
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 			wantLBConfig: &iserviceconfig.BalancerConfig{
 				Name: wrrlocality.Name,
@@ -537,7 +537,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
 			wantUpdate: xdsresource.ClusterUpdate{
 				ClusterName:     clusterName,
 				EDSServiceName:  serviceName,
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 			wantLBConfig: &iserviceconfig.BalancerConfig{
 				Name: "ring_hash_experimental",
diff --git a/xds/internal/xdsclient/xdsresource/unmarshal_cds_test.go b/internal/xds/xdsclient/xdsresource/unmarshal_cds_test.go
similarity index 98%
rename from xds/internal/xdsclient/xdsresource/unmarshal_cds_test.go
rename to internal/xds/xdsclient/xdsresource/unmarshal_cds_test.go
index 513241e0..0fb70d63 100644
--- a/xds/internal/xdsclient/xdsresource/unmarshal_cds_test.go
+++ b/internal/xds/xdsclient/xdsresource/unmarshal_cds_test.go
@@ -28,10 +28,10 @@ import (
 	"google.golang.org/grpc/internal/envconfig"
 	"google.golang.org/grpc/internal/pretty"
 	"google.golang.org/grpc/internal/testutils"
+	xdsinternal "google.golang.org/grpc/internal/xds"
 	"google.golang.org/grpc/internal/xds/bootstrap"
 	"google.golang.org/grpc/internal/xds/matcher"
-	"google.golang.org/grpc/xds/internal"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 
 	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
 	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
@@ -935,7 +935,7 @@ func (s) TestValidateClusterWithSecurityConfig(t *testing.T) {
 					RootInstanceName: rootPluginInstance,
 					RootCertName:     rootCertName,
 				},
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 		},
 		{
@@ -977,7 +977,7 @@ func (s) TestValidateClusterWithSecurityConfig(t *testing.T) {
 					RootInstanceName: rootPluginInstance,
 					RootCertName:     rootCertName,
 				},
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 		},
 		{
@@ -1023,7 +1023,7 @@ func (s) TestValidateClusterWithSecurityConfig(t *testing.T) {
 					IdentityInstanceName: identityPluginInstance,
 					IdentityCertName:     identityCertName,
 				},
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 		},
 		{
@@ -1076,7 +1076,7 @@ func (s) TestValidateClusterWithSecurityConfig(t *testing.T) {
 					IdentityInstanceName: identityPluginInstance,
 					IdentityCertName:     identityCertName,
 				},
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 		},
 		{
@@ -1121,7 +1121,7 @@ func (s) TestValidateClusterWithSecurityConfig(t *testing.T) {
 					IdentityInstanceName: identityPluginInstance,
 					IdentityCertName:     identityCertName,
 				},
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 		},
 		{
@@ -1223,7 +1223,7 @@ func (s) TestValidateClusterWithSecurityConfig(t *testing.T) {
 						matcher.StringMatcherForTesting(nil, nil, nil, newStringP(sanContains), nil, false),
 					},
 				},
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 		},
 		{
@@ -1290,7 +1290,7 @@ func (s) TestValidateClusterWithSecurityConfig(t *testing.T) {
 						matcher.StringMatcherForTesting(nil, nil, nil, newStringP(sanContains), nil, false),
 					},
 				},
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 		},
 		{
@@ -1354,7 +1354,7 @@ func (s) TestValidateClusterWithSecurityConfig(t *testing.T) {
 						matcher.StringMatcherForTesting(nil, nil, nil, newStringP(sanContains), nil, false),
 					},
 				},
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 		},
 	}
@@ -1553,7 +1553,7 @@ func (s) TestUnmarshalCluster(t *testing.T) {
 				EDSServiceName:  v3Service,
 				LRSServerConfig: serverCfg,
 				Raw:             v3ClusterAny,
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 		},
 		{
@@ -1566,7 +1566,7 @@ func (s) TestUnmarshalCluster(t *testing.T) {
 				EDSServiceName:  v3Service,
 				LRSServerConfig: serverCfg,
 				Raw:             v3ClusterAny,
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 		},
 		{
@@ -1579,7 +1579,7 @@ func (s) TestUnmarshalCluster(t *testing.T) {
 				EDSServiceName:  v3Service,
 				LRSServerConfig: serverCfg,
 				Raw:             v3ClusterAnyWithEDSConfigSourceSelf,
-				TelemetryLabels: internal.UnknownCSMLabels,
+				TelemetryLabels: xdsinternal.UnknownCSMLabels,
 			},
 		},
 		{
diff --git a/xds/internal/xdsclient/xdsresource/unmarshal_eds_test.go b/internal/xds/xdsclient/xdsresource/unmarshal_eds_test.go
similarity index 69%
rename from xds/internal/xdsclient/xdsresource/unmarshal_eds_test.go
rename to internal/xds/xdsclient/xdsresource/unmarshal_eds_test.go
index f38f6961..bad0798f 100644
--- a/xds/internal/xdsclient/xdsresource/unmarshal_eds_test.go
+++ b/internal/xds/xdsclient/xdsresource/unmarshal_eds_test.go
@@ -32,8 +32,8 @@ import (
 	"google.golang.org/grpc/internal/envconfig"
 	"google.golang.org/grpc/internal/pretty"
 	"google.golang.org/grpc/internal/testutils"
-	"google.golang.org/grpc/xds/internal/clients"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/clients"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 	"google.golang.org/protobuf/types/known/structpb"
@@ -41,12 +41,124 @@ import (
 )
 
 func (s) TestEDSParseRespProto(t *testing.T) {
+	testutils.SetEnvConfig(t, &envconfig.XDSHTTPConnectEnabled, true)
 	tests := []struct {
 		name    string
 		m       *v3endpointpb.ClusterLoadAssignment
 		want    EndpointsUpdate
 		wantErr bool
 	}{
+		{
+			name: "with endpoint metadata from typed",
+			m: func() *v3endpointpb.ClusterLoadAssignment {
+				clab0 := newClaBuilder("test", nil)
+				clab0.addLocality("locality-1", 1, 0, []endpointOpts{{
+					addrWithPort: "addr1:314",
+					metadata: &v3corepb.Metadata{
+						TypedFilterMetadata: map[string]*anypb.Any{
+							"typed.key": testutils.MarshalAny(t, &v3corepb.Address{
+								Address: &v3corepb.Address_SocketAddress{
+									SocketAddress: &v3corepb.SocketAddress{
+										Address: "1.2.3.4",
+										PortSpecifier: &v3corepb.SocketAddress_PortValue{
+											PortValue: 1111,
+										}},
+								},
+							}),
+						},
+						FilterMetadata: map[string]*structpb.Struct{
+							"some.key": {Fields: map[string]*structpb.Value{
+								"field": structpb.NewStringValue("untyped-value")}},
+						},
+					},
+				}}, nil)
+				return clab0.Build()
+			}(),
+			want: EndpointsUpdate{
+				Localities: []Locality{
+					{
+						Endpoints: []Endpoint{{
+							Addresses:    []string{"addr1:314"},
+							HealthStatus: EndpointHealthStatusUnknown,
+							Weight:       1,
+							Metadata: map[string]any{
+								"typed.key": ProxyAddressMetadataValue{
+									Address: "1.2.3.4:1111",
+								},
+								"some.key": StructMetadataValue{
+									Data: map[string]any{"field": "untyped-value"},
+								},
+							},
+						}},
+						ID:       clients.Locality{SubZone: "locality-1"},
+						Priority: 0,
+						Weight:   1,
+					},
+				},
+			},
+		},
+
+		{
+			name: "with endpoint metadata from filtered",
+			m: func() *v3endpointpb.ClusterLoadAssignment {
+				clab0 := newClaBuilder("test", nil)
+				clab0.addLocality("locality-1", 1, 0, []endpointOpts{{
+					addrWithPort: "addr1:314",
+					metadata: &v3corepb.Metadata{
+						FilterMetadata: map[string]*structpb.Struct{
+							"test-key": {},
+						},
+					},
+				}}, nil)
+				return clab0.Build()
+			}(),
+			want: EndpointsUpdate{
+				Localities: []Locality{
+					{
+						Endpoints: []Endpoint{{
+							Addresses:    []string{"addr1:314"},
+							HealthStatus: EndpointHealthStatusUnknown,
+							Weight:       1,
+							Metadata: map[string]any{
+								"test-key": StructMetadataValue{Data: map[string]any{}},
+							},
+						}},
+						ID:       clients.Locality{SubZone: "locality-1"},
+						Priority: 0,
+						Weight:   1,
+					},
+				},
+			},
+		},
+		{
+			name: "with locality metadata from filtered",
+			m: func() *v3endpointpb.ClusterLoadAssignment {
+				clab0 := newClaBuilder("test", nil)
+				clab0.addLocality("locality-1", 1, 0, []endpointOpts{{addrWithPort: "addr1:314"}}, &addLocalityOptions{
+					Metadata: &v3corepb.Metadata{
+						FilterMetadata: map[string]*structpb.Struct{"test-key": {}},
+					},
+				})
+				return clab0.Build()
+			}(),
+			want: EndpointsUpdate{
+				Localities: []Locality{
+					{
+						Endpoints: []Endpoint{{
+							Addresses:    []string{"addr1:314"},
+							HealthStatus: EndpointHealthStatusUnknown,
+							Weight:       1,
+						}},
+						ID:       clients.Locality{SubZone: "locality-1"},
+						Priority: 0,
+						Weight:   1,
+						Metadata: map[string]any{
+							"test-key": StructMetadataValue{Data: map[string]any{}},
+						},
+					},
+				},
+			},
+		},
 		{
 			name: "missing-priority",
 			m: func() *v3endpointpb.ClusterLoadAssignment {
@@ -214,8 +326,7 @@ func (s) TestEDSParseRespProto(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			got, err := parseEDSRespProto(tt.m)
 			if (err != nil) != tt.wantErr {
-				t.Errorf("parseEDSRespProto() error = %v, wantErr %v", err, tt.wantErr)
-				return
+				t.Fatalf("parseEDSRespProto() error = %v, wantErr %v", err, tt.wantErr)
 			}
 			if d := cmp.Diff(got, tt.want, cmpopts.EquateEmpty()); d != "" {
 				t.Errorf("parseEDSRespProto() got = %v, want %v, diff: %v", got, tt.want, d)
@@ -325,8 +436,7 @@ func (s) TestEDSParseRespProtoAdditionalAddrs(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			got, err := parseEDSRespProto(tt.m)
 			if (err != nil) != tt.wantErr {
-				t.Errorf("parseEDSRespProto() error = %v, wantErr %v", err, tt.wantErr)
-				return
+				t.Fatalf("parseEDSRespProto() error = %v, wantErr %v", err, tt.wantErr)
 			}
 			if d := cmp.Diff(got, tt.want, cmpopts.EquateEmpty()); d != "" {
 				t.Errorf("parseEDSRespProto() got = %v, want %v, diff: %v", got, tt.want, d)
@@ -465,6 +575,7 @@ func (s) TestUnmarshalEndpointHashKey(t *testing.T) {
 }
 
 func (s) TestUnmarshalEndpoints(t *testing.T) {
+	testutils.SetEnvConfig(t, &envconfig.XDSHTTPConnectEnabled, true)
 	var v3EndpointsAny = testutils.MarshalAny(t, func() *v3endpointpb.ClusterLoadAssignment {
 		clab0 := newClaBuilder("test", nil)
 		clab0.addLocality("locality-1", 1, 1, []endpointOpts{{addrWithPort: "addr1:314"}}, &addLocalityOptions{
@@ -588,6 +699,174 @@ func (s) TestUnmarshalEndpoints(t *testing.T) {
 	}
 }
 
+func (s) TestValidateAndConstructMetadataWithXDSHTTPConnectEnabled(t *testing.T) {
+	testutils.SetEnvConfig(t, &envconfig.XDSHTTPConnectEnabled, true)
+	tests := []struct {
+		name          string
+		metadataProto *v3corepb.Metadata
+		want          map[string]any
+		wantErr       bool
+	}{
+		{
+			name: "typed filter metadata over filter metadata-xDSHTTPConnect-enabled",
+			metadataProto: &v3corepb.Metadata{
+				TypedFilterMetadata: map[string]*anypb.Any{
+					"some.key": testutils.MarshalAny(t, &v3corepb.Address{
+						Address: &v3corepb.Address_SocketAddress{
+							SocketAddress: &v3corepb.SocketAddress{
+								Address: "1.2.3.4",
+								PortSpecifier: &v3corepb.SocketAddress_PortValue{
+									PortValue: 1111,
+								},
+							},
+						},
+					}),
+				},
+				FilterMetadata: map[string]*structpb.Struct{
+					"some.key": {Fields: map[string]*structpb.Value{
+						"field": structpb.NewStringValue("untyped-value")}},
+				},
+			},
+
+			want: map[string]any{
+				"some.key": ProxyAddressMetadataValue{Address: "1.2.3.4:1111"},
+			},
+		},
+		{
+			name: "success-case-xDSHTTPConnect-enabled",
+			metadataProto: &v3corepb.Metadata{
+				TypedFilterMetadata: map[string]*anypb.Any{
+					"envoy.http11_proxy_transport_socket.proxy_address": testutils.MarshalAny(t, &v3corepb.Address{
+						Address: &v3corepb.Address_SocketAddress{
+							SocketAddress: &v3corepb.SocketAddress{
+								Address: "1.2.3.4",
+								PortSpecifier: &v3corepb.SocketAddress_PortValue{
+									PortValue: 8080,
+								},
+							},
+						},
+					}),
+				},
+				FilterMetadata: map[string]*structpb.Struct{
+					"untyped-key": {Fields: map[string]*structpb.Value{"field": structpb.NewStringValue("value")}},
+				},
+			},
+			want: map[string]any{
+				"envoy.http11_proxy_transport_socket.proxy_address": ProxyAddressMetadataValue{Address: "1.2.3.4:8080"},
+				"untyped-key": StructMetadataValue{Data: map[string]any{"field": string("value")}},
+			},
+		},
+		{
+			name: "failure-case-converter-error-xDSHTTPConnect-enabled",
+			metadataProto: &v3corepb.Metadata{
+				TypedFilterMetadata: map[string]*anypb.Any{
+					"envoy.http11_proxy_transport_socket.proxy_address": testutils.MarshalAny(t, &v3corepb.Address{
+						Address: &v3corepb.Address_SocketAddress{
+							SocketAddress: &v3corepb.SocketAddress{
+								Address: "invalid",
+							},
+						},
+					}),
+				},
+			},
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := validateAndConstructMetadata(tt.metadataProto)
+			if (err != nil) != tt.wantErr {
+				t.Fatalf("validateAndConstructMetadata() error = %v, wantErr %v", err, tt.wantErr)
+			}
+			if diff := cmp.Diff(tt.want, got, cmp.AllowUnexported(ProxyAddressMetadataValue{})); diff != "" {
+				t.Errorf("validateAndConstructMetadata() returned unexpected diff (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
+
+func (s) TestValidateAndConstructMetadataWithXDSHTTPConnectDisabled(t *testing.T) {
+	tests := []struct {
+		name          string
+		metadataProto *v3corepb.Metadata
+		want          map[string]any
+		wantErr       bool
+	}{
+		{
+			name: "typed-filter-metadata-over-filter-metadata-disabled",
+			metadataProto: &v3corepb.Metadata{
+				TypedFilterMetadata: map[string]*anypb.Any{
+					"some.key": testutils.MarshalAny(t, &v3corepb.Address{
+						Address: &v3corepb.Address_SocketAddress{
+							SocketAddress: &v3corepb.SocketAddress{
+								Address: "1.2.3.4",
+								PortSpecifier: &v3corepb.SocketAddress_PortValue{
+									PortValue: 1111,
+								},
+							},
+						},
+					}),
+				},
+				FilterMetadata: map[string]*structpb.Struct{
+					"some.key": {Fields: map[string]*structpb.Value{
+						"field": structpb.NewStringValue("untyped-value")}},
+				},
+			},
+			want: nil,
+		},
+		{
+			name: "success-case-metadata-disabled",
+			metadataProto: &v3corepb.Metadata{
+				TypedFilterMetadata: map[string]*anypb.Any{
+					"envoy.http11_proxy_transport_socket.proxy_address": testutils.MarshalAny(t, &v3corepb.Address{
+						Address: &v3corepb.Address_SocketAddress{
+							SocketAddress: &v3corepb.SocketAddress{
+								Address: "1.2.3.4",
+								PortSpecifier: &v3corepb.SocketAddress_PortValue{
+									PortValue: 8080,
+								},
+							},
+						},
+					}),
+				},
+				FilterMetadata: map[string]*structpb.Struct{
+					"untyped-key": {Fields: map[string]*structpb.Value{
+						"field": structpb.NewStringValue("value")}},
+				},
+			},
+			want: nil,
+		},
+		{
+			name: "failure-case-converter-error-metadata-disabled",
+			metadataProto: &v3corepb.Metadata{
+				TypedFilterMetadata: map[string]*anypb.Any{
+					"envoy.http11_proxy_transport_socket.proxy_address": testutils.MarshalAny(t, &v3corepb.Address{
+						Address: &v3corepb.Address_SocketAddress{
+							SocketAddress: &v3corepb.SocketAddress{
+								Address: "invalid",
+							},
+						},
+					}),
+				},
+			},
+			want: nil,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := validateAndConstructMetadata(tt.metadataProto)
+			if (err != nil) != tt.wantErr {
+				t.Fatalf("validateAndConstructMetadata() error = %v, wantErr %v", err, tt.wantErr)
+			}
+			if diff := cmp.Diff(tt.want, got, cmp.AllowUnexported(ProxyAddressMetadataValue{})); diff != "" {
+				t.Errorf("validateAndConstructMetadata() returned unexpected diff (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
+
 // claBuilder builds a ClusterLoadAssignment, aka EDS
 // response.
 type claBuilder struct {
@@ -619,13 +898,15 @@ func newClaBuilder(clusterName string, dropPercents []uint32) *claBuilder {
 
 // addLocalityOptions contains options when adding locality to the builder.
 type addLocalityOptions struct {
-	Health []v3corepb.HealthStatus
-	Weight []uint32
+	Health   []v3corepb.HealthStatus
+	Weight   []uint32
+	Metadata *v3corepb.Metadata
 }
 
 type endpointOpts struct {
 	addrWithPort            string
 	additionalAddrWithPorts []string
+	metadata                *v3corepb.Metadata
 }
 
 func addressFromStr(addrWithPort string) *v3corepb.Address {
@@ -666,6 +947,7 @@ func (clab *claBuilder) addLocality(subzone string, weight uint32, priority uint
 					AdditionalAddresses: additionalAddrs,
 				},
 			},
+			Metadata: e.metadata,
 		}
 		if opts != nil {
 			if i < len(opts.Health) {
@@ -687,12 +969,16 @@ func (clab *claBuilder) addLocality(subzone string, weight uint32, priority uint
 		}
 	}
 
-	clab.v.Endpoints = append(clab.v.Endpoints, &v3endpointpb.LocalityLbEndpoints{
+	locality := &v3endpointpb.LocalityLbEndpoints{
 		Locality:            localityID,
 		LbEndpoints:         lbEndPoints,
 		LoadBalancingWeight: &wrapperspb.UInt32Value{Value: weight},
 		Priority:            priority,
-	})
+	}
+	if opts != nil {
+		locality.Metadata = opts.Metadata
+	}
+	clab.v.Endpoints = append(clab.v.Endpoints, locality)
 }
 
 // Build builds ClusterLoadAssignment.
diff --git a/xds/internal/xdsclient/xdsresource/unmarshal_lds_test.go b/internal/xds/xdsclient/xdsresource/unmarshal_lds_test.go
similarity index 99%
rename from xds/internal/xdsclient/xdsresource/unmarshal_lds_test.go
rename to internal/xds/xdsclient/xdsresource/unmarshal_lds_test.go
index 900afdd3..b652c722 100644
--- a/xds/internal/xdsclient/xdsresource/unmarshal_lds_test.go
+++ b/internal/xds/xdsclient/xdsresource/unmarshal_lds_test.go
@@ -28,8 +28,8 @@ import (
 	"google.golang.org/grpc/internal/pretty"
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
-	"google.golang.org/grpc/xds/internal/httpfilter"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/httpfilter"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 	"google.golang.org/protobuf/types/known/durationpb"
@@ -48,8 +48,8 @@ import (
 	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
 	v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
 
-	_ "google.golang.org/grpc/xds/internal/httpfilter/rbac"   // Register the RBAC HTTP filter.
-	_ "google.golang.org/grpc/xds/internal/httpfilter/router" // Register the router filter.
+	_ "google.golang.org/grpc/internal/xds/httpfilter/rbac"   // Register the RBAC HTTP filter.
+	_ "google.golang.org/grpc/internal/xds/httpfilter/router" // Register the router filter.
 )
 
 func (s) TestUnmarshalListener_ClientSide(t *testing.T) {
diff --git a/xds/internal/xdsclient/xdsresource/unmarshal_rds_test.go b/internal/xds/xdsclient/xdsresource/unmarshal_rds_test.go
similarity index 99%
rename from xds/internal/xdsclient/xdsresource/unmarshal_rds_test.go
rename to internal/xds/xdsclient/xdsresource/unmarshal_rds_test.go
index c545ba45..622ef7df 100644
--- a/xds/internal/xdsclient/xdsresource/unmarshal_rds_test.go
+++ b/internal/xds/xdsclient/xdsresource/unmarshal_rds_test.go
@@ -31,10 +31,10 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/internal/pretty"
 	"google.golang.org/grpc/internal/testutils"
+	"google.golang.org/grpc/internal/xds/clusterspecifier"
+	"google.golang.org/grpc/internal/xds/httpfilter"
 	"google.golang.org/grpc/internal/xds/matcher"
-	"google.golang.org/grpc/xds/internal/clusterspecifier"
-	"google.golang.org/grpc/xds/internal/httpfilter"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
 	"google.golang.org/protobuf/types/known/durationpb"
diff --git a/test/end2end_test.go b/test/end2end_test.go
index b2f50399..9157c525 100644
--- a/test/end2end_test.go
+++ b/test/end2end_test.go
@@ -3590,9 +3590,6 @@ func testClientStreamingError(t *testing.T, e env) {
 // Tests that a client receives a cardinality violation error for client-streaming
 // RPCs if the server doesn't send a message before returning status OK.
 func (s) TestClientStreamingCardinalityViolation_ServerHandlerMissingSendAndClose(t *testing.T) {
-	// TODO : https://github.com/grpc/grpc-go/issues/8119 - remove `t.Skip()`
-	// after this is fixed.
-	t.Skip()
 	ss := &stubserver.StubServer{
 		StreamingInputCallF: func(_ testgrpc.TestService_StreamingInputCallServer) error {
 			// Returning status OK without sending a response message.This is a
@@ -3741,6 +3738,165 @@ func (s) TestClientStreaming_ReturnErrorAfterSendAndClose(t *testing.T) {
 	}
 }
 
+// Tests that client receives a cardinality violation error for unary
+// RPCs if the server doesn't send a message before returning status OK.
+func (s) TestUnaryRPC_ServerSendsOnlyTrailersWithOK(t *testing.T) {
+	lis, err := testutils.LocalTCPListener()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer lis.Close()
+
+	ss := grpc.UnknownServiceHandler(func(any, grpc.ServerStream) error {
+		return nil
+	})
+
+	s := grpc.NewServer(ss)
+	go s.Serve(lis)
+	defer s.Stop()
+
+	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
+	defer cancel()
+	cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
+	if err != nil {
+		t.Fatalf("grpc.NewClient(%q) failed unexpectedly: %v", lis.Addr(), err)
+	}
+	defer cc.Close()
+
+	client := testgrpc.NewTestServiceClient(cc)
+	if _, err = client.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) != codes.Internal {
+		t.Errorf("stream.RecvMsg() = %v, want error %v", status.Code(err), codes.Internal)
+	}
+}
+
+// Tests the behavior for unary RPC when client calls RecvMsg() twice.
+// Second call to RecvMsg should fail with io.EOF.
+func (s) TestUnaryRPC_ClientCallRecvMsgTwice(t *testing.T) {
+	e := tcpTLSEnv
+	te := newTest(t, e)
+	defer te.tearDown()
+
+	te.startServer(&testServer{security: e.security})
+
+	cc := te.clientConn()
+	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
+	defer cancel()
+
+	desc := &grpc.StreamDesc{
+		StreamName:    "UnaryCall",
+		ServerStreams: false,
+		ClientStreams: false,
+	}
+	stream, err := cc.NewStream(ctx, desc, "/grpc.testing.TestService/UnaryCall")
+	if err != nil {
+		t.Fatalf("cc.NewStream() failed unexpectedly: %v", err)
+	}
+
+	if err := stream.SendMsg(&testpb.SimpleRequest{}); err != nil {
+		t.Fatalf("stream.SendMsg(_) = %v, want <nil>", err)
+	}
+
+	resp := &testpb.SimpleResponse{}
+	if err := stream.RecvMsg(resp); err != nil {
+		t.Fatalf("stream.RecvMsg() = %v , want <nil>", err)
+	}
+
+	if err = stream.RecvMsg(resp); err != io.EOF {
+		t.Errorf("stream.RecvMsg() = %v, want error %v", err, io.EOF)
+	}
+}
+
+// Tests the behavior for unary RPC when server calls SendMsg() twice.
+// Client should fail with cardinality violation error.
+func (s) TestUnaryRPC_ServerCallSendMsgTwice(t *testing.T) {
+	lis, err := testutils.LocalTCPListener()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer lis.Close()
+
+	s := grpc.NewServer()
+	serviceDesc := grpc.ServiceDesc{
+		ServiceName: "grpc.testing.TestService",
+		HandlerType: (*any)(nil),
+		Methods:     []grpc.MethodDesc{},
+		Streams: []grpc.StreamDesc{
+			{
+				StreamName: "UnaryCall",
+				Handler: func(_ any, stream grpc.ServerStream) error {
+					if err := stream.RecvMsg(&testpb.Empty{}); err != nil {
+						t.Errorf("stream.RecvMsg() = %v, want <nil>", err)
+					}
+
+					if err = stream.SendMsg(&testpb.Empty{}); err != nil {
+						t.Errorf("stream.SendMsg() = %v, want <nil>", err)
+					}
+
+					if err = stream.SendMsg(&testpb.Empty{}); err != nil {
+						t.Errorf("stream.SendMsg() = %v, want <nil>", err)
+					}
+					return nil
+				},
+				ClientStreams: false,
+				ServerStreams: false,
+			},
+		},
+	}
+	s.RegisterService(&serviceDesc, &testServer{})
+	go s.Serve(lis)
+	defer s.Stop()
+
+	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
+	defer cancel()
+	cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
+	if err != nil {
+		t.Fatalf("grpc.NewClient(%q) failed unexpectedly: %v", lis.Addr(), err)
+	}
+	defer cc.Close()
+
+	client := testgrpc.NewTestServiceClient(cc)
+	if _, err = client.UnaryCall(ctx, &testpb.SimpleRequest{}); status.Code(err) != codes.Internal {
+		t.Errorf("stream.RecvMsg() = %v, want error %v", status.Code(err), codes.Internal)
+	}
+}
+
+// Tests the behavior for client-streaming RPC when client calls RecvMsg() twice.
+// Second call to RecvMsg should fail with io.EOF.
+func (s) TestClientStreaming_ClientCallRecvMsgTwice(t *testing.T) {
+	ss := stubserver.StubServer{
+		StreamingInputCallF: func(stream testgrpc.TestService_StreamingInputCallServer) error {
+			if err := stream.SendAndClose(&testpb.StreamingInputCallResponse{}); err != nil {
+				t.Errorf("stream.SendAndClose(_) = %v, want <nil>", err)
+			}
+			return nil
+		},
+	}
+	if err := ss.Start(nil); err != nil {
+		t.Fatal("Error starting server:", err)
+	}
+	defer ss.Stop()
+
+	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
+	defer cancel()
+	stream, err := ss.Client.StreamingInputCall(ctx)
+	if err != nil {
+		t.Fatalf(".StreamingInputCall(_) = _, %v, want <nil>", err)
+	}
+	if err := stream.Send(&testpb.StreamingInputCallRequest{}); err != nil {
+		t.Fatalf("stream.Send(_) = %v, want <nil>", err)
+	}
+	if err := stream.CloseSend(); err != nil {
+		t.Fatalf("stream.CloseSend() = %v, want <nil>", err)
+	}
+	resp := new(testpb.StreamingInputCallResponse)
+	if err := stream.RecvMsg(resp); err != nil {
+		t.Fatalf("stream.RecvMsg() = %v , want <nil>", err)
+	}
+	if err = stream.RecvMsg(resp); err != io.EOF {
+		t.Errorf("stream.RecvMsg() = %v, want error %v", err, io.EOF)
+	}
+}
+
 // Tests the behavior for server-side streaming when client calls SendMsg twice.
 // Second call to SendMsg should fail with Internal error and result in closing
 // the connection with a RST_STREAM.
@@ -3802,7 +3958,6 @@ func (s) TestServerStreaming_ClientCallSendMsgTwice(t *testing.T) {
 	<-handlerDone
 }
 
-// TODO(i/7286) : Add tests to check server-side behavior for Unary RPC.
 // Tests the behavior for unary RPC when client calls SendMsg twice. Second call
 // to SendMsg should fail with Internal error.
 func (s) TestUnaryRPC_ClientCallSendMsgTwice(t *testing.T) {
@@ -3981,7 +4136,7 @@ func (s) TestServerStreaming_ClientSendsZeroRequests(t *testing.T) {
 }
 
 // Tests that a client receives a cardinality violation error for client-streaming
-// RPCs if the server call SendMsg multiple times.
+// RPCs if the server calls SendMsg() multiple times.
 func (s) TestClientStreaming_ServerHandlerSendMsgAfterSendMsg(t *testing.T) {
 	ss := stubserver.StubServer{
 		StreamingInputCallF: func(stream testgrpc.TestService_StreamingInputCallServer) error {
diff --git a/test/kokoro/xds.sh b/test/kokoro/xds.sh
index daac0d68..886146c0 100755
--- a/test/kokoro/xds.sh
+++ b/test/kokoro/xds.sh
@@ -9,7 +9,7 @@ export GOPATH="${HOME}/gopath"
 pushd grpc-go/interop/xds/client
 # Install a version of Go supported by gRPC for the new features, e.g.
 # errors.Is()
-gofilename=go1.23.0.linux-amd64.tar.gz
+gofilename=go1.25.0.linux-amd64.tar.gz
 curl --retry 3 -O -L "https://go.dev/dl/${gofilename}"
 sudo tar -C /usr/local -xf "${gofilename}"
 sudo ln -s /usr/local/go/bin/go /usr/bin/go
diff --git a/test/transport_test.go b/test/transport_test.go
index 10e9ab57..3f0a8260 100644
--- a/test/transport_test.go
+++ b/test/transport_test.go
@@ -229,3 +229,76 @@ func (s) TestRSTDuringMessageRead(t *testing.T) {
 		t.Fatalf("client.EmptyCall() returned %v; want status with code %v", err, codes.Canceled)
 	}
 }
+
+// Test verifies that a client-side cancellation correctly frees up resources on
+// the server. The test setup is designed to simulate a scenario where a server
+// is blocked from sending a large message due to a full client-side flow
+// control window. The client-side cancellation of this blocked RPC then frees
+// up the max concurrent streams quota on the server, allowing a new RPC to be
+// created successfully.
+func (s) TestCancelWhileServerWaitingForFlowControl(t *testing.T) {
+	serverDoneCh := make(chan struct{}, 2)
+	const flowControlWindowSize = 65535
+	ss := &stubserver.StubServer{
+		StreamingOutputCallF: func(_ *testpb.StreamingOutputCallRequest, stream testgrpc.TestService_StreamingOutputCallServer) error {
+			// Send a large message to exhaust the client's flow control window.
+			stream.Send(&testpb.StreamingOutputCallResponse{
+				Payload: &testpb.Payload{
+					Body: make([]byte, flowControlWindowSize+1),
+				},
+			})
+			serverDoneCh <- struct{}{}
+			return nil
+		},
+	}
+
+	// Create a server that allows only 1 stream at a time.
+	ss = stubserver.StartTestService(t, ss, grpc.MaxConcurrentStreams(1))
+	defer ss.Stop()
+	// Use a static flow control window.
+	if err := ss.StartClient(grpc.WithStaticStreamWindowSize(flowControlWindowSize)); err != nil {
+		t.Fatalf("Error while start test service client: %v", err)
+	}
+	client := ss.Client
+	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
+	defer cancel()
+
+	streamCtx, streamCancel := context.WithCancel(ctx)
+	defer streamCancel()
+
+	if _, err := client.StreamingOutputCall(streamCtx, &testpb.StreamingOutputCallRequest{}); err != nil {
+		t.Fatalf("Failed to create server streaming RPC: %v", err)
+	}
+
+	// Wait for the server handler to return. This should cause the trailers to
+	// be buffered on the server, waiting for flow control quota to first send
+	// the data frame.
+	select {
+	case <-ctx.Done():
+		t.Fatal("Context timed out waiting for server handler to return.")
+	case <-serverDoneCh:
+	}
+
+	// Attempt to create a stream. It should fail since the previous stream is
+	// still blocked.
+	shortCtx, shortCancel := context.WithTimeout(ctx, defaultTestShortTimeout)
+	defer shortCancel()
+	_, err := client.StreamingOutputCall(shortCtx, &testpb.StreamingOutputCallRequest{})
+	if status.Code(err) != codes.DeadlineExceeded {
+		t.Fatalf("Server stream creation returned error with unexpected status code: %v, want code: %v", err, codes.DeadlineExceeded)
+	}
+
+	// Cancel the RPC, this should free up concurrent stream quota on the
+	// server.
+	streamCancel()
+
+	// Attempt to create another stream.
+	stream, err := client.StreamingOutputCall(ctx, &testpb.StreamingOutputCallRequest{})
+	if err != nil {
+		t.Fatalf("Failed to create server streaming RPC: %v", err)
+	}
+	_, err = stream.Recv()
+	if err != nil {
+		t.Fatalf("Failed to read from the stream: %v", err)
+	}
+}
diff --git a/xds/csds/csds_e2e_test.go b/xds/csds/csds_e2e_test.go
index a9654b20..d09deeb2 100644
--- a/xds/csds/csds_e2e_test.go
+++ b/xds/csds/csds_e2e_test.go
@@ -36,9 +36,9 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
 	"google.golang.org/grpc/xds/csds"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
 	"google.golang.org/protobuf/encoding/prototext"
 	"google.golang.org/protobuf/testing/protocmp"
 	"google.golang.org/protobuf/types/known/anypb"
@@ -52,7 +52,7 @@ import (
 	v3statuspb "github.com/envoyproxy/go-control-plane/envoy/service/status/v3"
 	v3statuspbgrpc "github.com/envoyproxy/go-control-plane/envoy/service/status/v3"
 
-	_ "google.golang.org/grpc/xds/internal/httpfilter/router" // Register the router filter
+	_ "google.golang.org/grpc/internal/xds/httpfilter/router" // Register the router filter
 )
 
 const defaultTestTimeout = 5 * time.Second
diff --git a/xds/googledirectpath/googlec2p_test.go b/xds/googledirectpath/googlec2p_test.go
index 51f3fa4a..adb5c3a3 100644
--- a/xds/googledirectpath/googlec2p_test.go
+++ b/xds/googledirectpath/googlec2p_test.go
@@ -32,10 +32,10 @@ import (
 	"google.golang.org/grpc/internal/envconfig"
 	"google.golang.org/grpc/internal/grpctest"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient"
 	testgrpc "google.golang.org/grpc/interop/grpc_testing"
 	testpb "google.golang.org/grpc/interop/grpc_testing"
 	"google.golang.org/grpc/resolver"
-	"google.golang.org/grpc/xds/internal/xdsclient"
 )
 
 const defaultTestTimeout = 5 * time.Second
diff --git a/xds/server_ext_test.go b/xds/server_ext_test.go
index 381b93eb..7e97a901 100644
--- a/xds/server_ext_test.go
+++ b/xds/server_ext_test.go
@@ -40,10 +40,10 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient"
 	"google.golang.org/grpc/peer"
 	"google.golang.org/grpc/status"
 	"google.golang.org/grpc/xds"
-	"google.golang.org/grpc/xds/internal/xdsclient"
 
 	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
diff --git a/xds/server_resource_ext_test.go b/xds/server_resource_ext_test.go
index b396eb9f..29e68fb2 100644
--- a/xds/server_resource_ext_test.go
+++ b/xds/server_resource_ext_test.go
@@ -36,9 +36,9 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 	"google.golang.org/grpc/xds"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
 	"google.golang.org/protobuf/types/known/wrapperspb"
 
 	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
diff --git a/xds/server_security_ext_test.go b/xds/server_security_ext_test.go
index 5436c2ad..fbc21e55 100644
--- a/xds/server_security_ext_test.go
+++ b/xds/server_security_ext_test.go
@@ -35,8 +35,8 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient"
 	"google.golang.org/grpc/xds"
-	"google.golang.org/grpc/xds/internal/xdsclient"
 	"google.golang.org/protobuf/types/known/wrapperspb"
 
 	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
diff --git a/xds/server_serving_mode_ext_test.go b/xds/server_serving_mode_ext_test.go
index ce4f65f1..678bf380 100644
--- a/xds/server_serving_mode_ext_test.go
+++ b/xds/server_serving_mode_ext_test.go
@@ -33,9 +33,9 @@ import (
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/testutils/xds/e2e/setup"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient"
 	"google.golang.org/grpc/status"
 	"google.golang.org/grpc/xds"
-	"google.golang.org/grpc/xds/internal/xdsclient"
 
 	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
diff --git a/xds/server_test.go b/xds/server_test.go
index 921b8864..df631410 100644
--- a/xds/server_test.go
+++ b/xds/server_test.go
@@ -42,13 +42,13 @@ import (
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/xds/bootstrap"
-	"google.golang.org/grpc/xds/internal/xdsclient"
-	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
+	"google.golang.org/grpc/internal/xds/xdsclient"
+	"google.golang.org/grpc/internal/xds/xdsclient/xdsresource/version"
 
 	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
 
-	_ "google.golang.org/grpc/xds/internal/httpfilter/router" // Register the router filter
+	_ "google.golang.org/grpc/internal/xds/httpfilter/router" // Register the router filter
 )
 
 const (
diff --git a/xds/test/eds_resource_missing_test.go b/xds/test/eds_resource_missing_test.go
index ce334b2c..24727054 100644
--- a/xds/test/eds_resource_missing_test.go
+++ b/xds/test/eds_resource_missing_test.go
@@ -34,9 +34,9 @@ import (
 	"google.golang.org/grpc/internal/testutils/xds/e2e"
 	"google.golang.org/grpc/internal/testutils/xds/e2e/setup"
 	"google.golang.org/grpc/internal/xds/bootstrap"
+	"google.golang.org/grpc/internal/xds/xdsclient"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/status"
-	"google.golang.org/grpc/xds/internal/xdsclient"
 
 	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
 	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"