Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions cmd/galactic-router/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ import (
grpchealth "google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"

cloudv1alpha1 "go.datum.net/cloud/api/v1alpha1"
"go.datum.net/galactic/internal/config"
"go.datum.net/galactic/internal/controller"
"go.datum.net/galactic/internal/hash"
Expand All @@ -35,6 +38,7 @@ import (
"go.datum.net/galactic/internal/reconcile"
galacticruntime "go.datum.net/galactic/internal/runtime"
"go.datum.net/galactic/internal/runtime/gobgp"
"go.datum.net/galactic/internal/serviceroute"
networkwebhook "go.datum.net/galactic/internal/webhook"
bgpv1alpha1 "go.datum.net/network/api/v1alpha1"
)
Expand Down Expand Up @@ -80,6 +84,7 @@ func runCmd(cfg *config.RouterConfig) error {
scheme := runtime.NewScheme()
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(bgpv1alpha1.AddToScheme(scheme))
utilruntime.Must(cloudv1alpha1.AddToScheme(scheme))

mgrOptions := ctrl.Options{
Scheme: scheme,
Expand All @@ -102,6 +107,8 @@ func runCmd(cfg *config.RouterConfig) error {
if err != nil {
return fmt.Errorf("create manager: %w", err)
}
serviceRouteMetrics := serviceroute.NewMetrics()
serviceRouteMetrics.MustRegister(ctrlmetrics.Registry)

// ctx carries a cause so an ordinary signal-triggered shutdown can be told
// apart from the health server's own Serve failure below. See the cause
Expand Down Expand Up @@ -264,6 +271,20 @@ func runCmd(cfg *config.RouterConfig) error {
return fmt.Errorf("setup ServiceVIPBinding controller: %w", err)
}

// Register the node-local platform service route controller. It resolves
// ServiceRoutePolicy against Cloud VPCAttachment.status.node and programs
// only this node's local routes.
if err := (&controller.ServiceRoutePolicyReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
NodeName: nodeName,
Programmer: serviceroute.LinuxRouteProgrammer{},
Metrics: serviceRouteMetrics,
Applied: make(map[types.NamespacedName]map[types.NamespacedName]serviceroute.RouteIntent),
}).SetupWithManager(mgr); err != nil {
return fmt.Errorf("setup ServiceRoutePolicy controller: %w", err)
}

// Register GC controller for cleaning up orphaned BGP CRDs and VRFs.
gcRec := &controller.GCReconciler{
Client: mgr.GetClient(),
Expand Down
9 changes: 9 additions & 0 deletions config/galactic-router/rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ rules:
resources:
- servicevipbindings
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: ["network.datumapis.com"]
resources:
- serviceendpoints
- serviceroutepolicies
verbs: ["get", "list", "watch"]
- apiGroups: ["network.datumapis.com"]
resources:
- bgprouters/status
Expand All @@ -42,6 +47,10 @@ rules:
- bgpvrfinstances/status
- servicevipbindings/status
verbs: ["get", "update", "patch"]
- apiGroups: ["cloud.datumapis.com"]
resources:
- vpcattachments
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources:
- secrets
Expand Down
138 changes: 109 additions & 29 deletions docs/enhancements/dns64/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# Shared DNS64 service

Status: Proposed. This design has not passed end-to-end Galactic validation.
Status: Prototype. Galactic implements same-node service routing. Production
DNS64/NAT64 packet-path validation is not complete. The prototype is not
production-ready.

## Summary

Provide a managed DNS64 service for IPv6-only Compute instances. PowerDNS
Recursor resolves public names and synthesizes AAAA records from A records
when a name has no usable native AAAA record. Galactic's NAT64 gateway
translates connections to IPv4.
This design proposes a managed DNS64 service for IPv6-only Compute instances.
PowerDNS Recursor resolves public names and synthesizes AAAA records from
A records when a name has no usable native AAAA record. Galactic's NAT64
gateway translates connections to IPv4.

The initial service uses one shared `/96` NAT64 prefix per serving fabric and a
shared regional resolver fleet. The planning range is 10,000–100,000 tenants;
Expand Down Expand Up @@ -44,7 +46,7 @@ Galactic routing and NAT64 path.
- Make IPv4 literals work or create connectivity to unreachable destinations.
- Preserve established connections when a NAT64 gateway loses session state.

## High-level architectural proposal
## Proposed architecture

Use PowerDNS Recursor as the short-term recursive DNS64 engine. Its native
`recursor.dns64_prefix` setting supplies the shared `/96` prefix and performs
Expand All @@ -63,16 +65,14 @@ may reach it.

[Container diagram source](containers.puml).

The container diagram is the component and relationship reference. The
resolver service and PowerDNS Recursor are shared regionally; the NAT64 path is
separate from the DNS query path.
The diagrams describe the proposed product architecture.

### Endpoint access and resource controls

The resolver VIP is a managed network service. Galactic authorizes the workload
attachment and routes only eligible network traffic to the endpoint. The
attachment and resolver service must reject spoofed sources and unauthorized
queries.
The proposed resolver VIP is a managed network service. Galactic selects
eligible workload attachments for routing. The attachment and resolver service
must reject spoofed sources and unauthorized queries. The current route
programmer does not enforce the policy's protocol and port restrictions.

The first milestone does not require a resolver process or cache per tenant.
Bound shard memory, connections, outstanding queries, and query rates. Apply
Expand All @@ -96,10 +96,82 @@ DNS does not select a NAT64 gateway, the synthesized address uses the shared
prefix, and application traffic returns through the same translation path.

The [CNI route implementation][cni-routes] and [NAT64 gateway design][nat64-design]
define the existing networking side of this flow. Resolver endpoint delivery
remains a proposed integration.

### Shared prefix and routing contract
define the egress side of this flow. The service-route prototype supplies the
local resolver route; end-to-end DNS64 integration remains planned.

### Current service-route prototype

The [Network API][service-route-api] defines two internal resources:

- `ServiceEndpoint` publishes a stable service address, protocol, and port.
Its `attachmentRef` identifies the Cloud API `VPCAttachment` that hosts the
service. The current Galactic implementation requires this reference.
- `ServiceRoutePolicy` references an endpoint in the same namespace and selects
consumer attachments by label. The network control plane must assign labels
that grant service access.

These resources support shared services without a customer-facing DNS64 API
or a Kubernetes route resource for every attachment or node. Policy status
holds durable conditions, not attachment counts. The current reconciler does
not publish those conditions.

[Galactic's service-route controller][service-route-programming] runs inside
`galactic-router`. It watches both resources and Cloud `VPCAttachment` changes,
including `VPCAttachment.status.node`, the authoritative placement field.
Router RBAC grants read and watch access to these APIs. Each router reconciles
only attachments assigned to its node. The service attachment must also be on
that node.

For each selected consumer, Galactic uses attachment status to resolve the
consumer subnet, both VPCs, and their host interfaces. On Linux, it programs
bidirectional routes in the VPC virtual routing and forwarding (VRF) tables:

- The consumer VPC table routes the endpoint address to the service interface.
- The service VPC table routes the consumer subnet to the consumer interface.

Galactic also programs matching eBPF pass-through route-map entries in both
VRFs. These entries keep service traffic on the local Linux routing path
instead of sending it through an egress gateway. NAT64 translation, NAT64
routes, and default-route programming remain outside this controller.

### Route cleanup and rollback

After it successfully computes the desired routes, the controller removes the
policy's previously tracked routes and pass-through entries before applying
the replacement. This removes stale routes when selection or attachment
placement changes. Policy deletion also triggers cleanup of tracked routes.

Updates are not atomic. If the return route fails to install, the programmer
attempts to remove the forward route. If the service-side pass-through entry
fails, it attempts to remove the consumer-side entry, but leaves the Linux
routes installed. Rollback does not restore the previous policy state.

Cleanup relies on in-memory tracking. Restarting the controller loses that
tracking, and failed updates can leave partial state. Endpoint lookup or route
planning errors leave existing routes in place; deleting an endpoint alone
does not withdraw them. Recovery from these cases remains a prototype limit.

### Current routing observability

[Service-route metrics][service-route-observability] use the existing
`galactic-router` Prometheus endpoint. They report reconciliation outcomes and
duration, route apply and remove outcomes, and the number of locally tracked
route intents. Labels use only bounded result and operation values. Resource
identities stay out of metric labels to keep cardinality bounded.

Structured logs include policy and node context, endpoint context where
available, compiled intent counts, and errors from lookup, planning,
programming, or cleanup. The route count reflects controller tracking; it does
not prove resolver health or packet delivery.

### Planned DNS64 readiness and prefix contract

DNS64 readiness requires agreement between the resolver VIP, the Galactic
route to that VIP, and the shared NAT64 prefix. The VIP supplied to the instance
must match the routed endpoint. The resolver's synthesis prefix must match
the prefix routed to the assigned NAT64 gateway. A reachable resolver alone
does not establish DNS64 readiness. The service-route controller does not
implement this readiness check.

Use one Datum-managed `/96` prefix shared across the fabric. Read it from
[`EgressShard.status.nat64Prefix`][egress-status]; do not allocate prefixes in
Expand All @@ -116,7 +188,7 @@ reason. Native IPv6 resolution can continue. Coordinate prefix changes with
route retention for cached synthesized answers; clients can retain earlier
answers until their TTL expires.

### Synthesis and DNS security
### Planned synthesis and DNS security

Follow [RFC 6147][dns64-rfc]. Synthesize only for an AAAA query when eligible
A records exist and no usable native AAAA record exists. Preserve NXDOMAIN,
Expand All @@ -134,20 +206,20 @@ resolution. Apply ACLs, response-rate limiting, maximum response sizes, and
resource limits to reduce cache-poisoning, amplification, and denial-of-
service risk.

### Configuration and rollout
### Planned configuration and rollout

The DNS service controller distributes versioned fleet configuration to the
assigned resolver shards. The initial configuration contains the shared
The proposed DNS service controller distributes versioned fleet configuration
to the assigned resolver shards. The initial configuration contains the shared
prefix, public upstream policy, ACLs, synthesis exclusions, resource limits,
and observability settings. It does not manage tenant zones or tenant DNS
records.

Validate each revision before activation and retain the previous revision for
rollback. Drain a replica before upgrade, verify health and synthesis, then
return it to service. Admit a shard only after its configuration, resolver
health, and NAT64 prefix agree with the active revision.
return it to service. Admit a shard only after its configuration and resolver
health pass the DNS64 readiness checks.

### Availability, capacity, and operations
### Planned availability and capacity

Place resolver replicas across failure domains within each region. Keep enough
spare capacity to lose a replica. Use a stable IPv6 VIP with health-aware
Expand All @@ -161,16 +233,21 @@ synthesis rate, cache hit rate, resolver resource use, and NAT64 connection
success. Bound metric cardinality and query-log retention. Add tenant-level
accounting only when quotas or billing require it.

## Implementation boundary
## Limitations and validation

The current implementation supports same-node consumer and service attachments
only. Cross-node service routing is future work. Health-based endpoint
withdrawal is not implemented. Route programming requires Linux.

The existing prototype exercised dnsdist, BIND, and Jool in a controlled lab.
That implementation is not the short-term production design. The new prototype
That implementation is not the proposed production design. The DNS64 prototype
must exercise PowerDNS Recursor with the shared prefix and the intended
Galactic/NAT64 path. Jool remains a test fixture only.

The Galactic compatibility profile validates saved configuration offline; it
does not prove the Galactic packet path. Controlled upstreams and same-host
replicas do not establish internet-resolution performance or failure-domain
Production DNS64/NAT64 packet-path validation is not complete. The Galactic
compatibility profile validates saved configuration offline; it does not prove
the Galactic packet path. Controlled upstreams and same-host replicas do not
establish internet-resolution performance or failure-domain
availability.

Galactic source reviewed on September 16, 2026, supports TCP and UDP. It lacks
Expand Down Expand Up @@ -213,3 +290,6 @@ plantuml -tsvg docs/enhancements/dns64/containers.puml docs/enhancements/dns64/r
[nat64-constraints]: https://github.com/datum-cloud/enhancements/blob/a778a6c3104b60b0e7634af4f3e5d2e3ae303549/enhancements/networking/nat64-gateway-for-vpc-networks.md#L184-L198
[dns64-rfc]: https://www.rfc-editor.org/rfc/rfc6147.html
[nat64-rfc]: https://www.rfc-editor.org/rfc/rfc6146.html
[service-route-api]: https://github.com/datum-cloud/network/pull/27
[service-route-programming]: https://github.com/datum-cloud/galactic/pull/634
[service-route-observability]: https://github.com/datum-cloud/galactic/pull/635
39 changes: 24 additions & 15 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module go.datum.net/galactic

go 1.26.0
go 1.26.4

require (
github.com/cilium/ebpf v0.22.0
Expand All @@ -19,7 +19,8 @@ require (
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.21.0
github.com/vishvananda/netlink v1.3.2-0.20260831221819-dcee5577542a
go.datum.net/network v0.1.0
go.datum.net/cloud v0.1.1-0.20260918213150-260f797bbc5c
go.datum.net/network v0.1.1-0.20260930210653-3a8d02541780
golang.org/x/sys v0.48.0
golang.org/x/term v0.46.0
google.golang.org/grpc v1.84.0
Expand All @@ -40,23 +41,32 @@ require (
github.com/eapache/queue v1.1.0 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fsnotify/fsnotify v1.10.1 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gaissmai/bart v0.26.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-openapi/jsonpointer v0.23.1 // indirect
github.com/go-openapi/jsonreference v0.21.6 // indirect
github.com/go-openapi/swag v0.26.0 // indirect
github.com/go-openapi/swag/cmdutils v0.26.0 // indirect
github.com/go-openapi/swag/conv v0.26.0 // indirect
github.com/go-openapi/swag/fileutils v0.26.0 // indirect
github.com/go-openapi/swag/jsonname v0.26.0 // indirect
github.com/go-openapi/swag/jsonutils v0.26.0 // indirect
github.com/go-openapi/swag/loading v0.26.0 // indirect
github.com/go-openapi/swag/mangling v0.26.0 // indirect
github.com/go-openapi/swag/netutils v0.26.0 // indirect
github.com/go-openapi/swag/stringutils v0.26.0 // indirect
github.com/go-openapi/swag/typeutils v0.26.0 // indirect
github.com/go-openapi/swag/yamlutils v0.26.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/gnostic-models v0.7.1 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/k-sone/critbitgo v1.4.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/socket v0.6.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
Expand Down Expand Up @@ -91,19 +101,18 @@ require (
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/text v0.41.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.48.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
google.golang.org/protobuf v1.36.12 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/tools v0.7.0 // indirect
k8s.io/apiextensions-apiserver v0.36.0 // indirect
k8s.io/apiextensions-apiserver v0.36.1 // indirect
k8s.io/klog/v2 v2.140.0 // indirect
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
k8s.io/kube-openapi v0.0.0-20260330154417-16be699c7b31 // indirect
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/knftables v0.0.18 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
Expand Down
Loading
Loading