Light Controller IPv6 Support
Problem
The Rust controller-rs service is the control-plane endpoint for runtime
registration, discovery, MCP admin traffic, and portal event streams. In
dual-stack deployments, clients can resolve the controller hostname to either
IPv4 or IPv6. The controller listener and its TLS configuration must therefore
support IPv6 without changing the existing IPv4 deployment defaults.
The important distinction is between socket addresses and service metadata:
- the controller listener uses a socket address such as
0.0.0.0:8438or[::]:8438; - registered runtime instances publish a host address string plus a separate
port, such as
fdd0:0:0:1::3and8443.
Current Behavior
controller-rs already stores its bind address as a Rust SocketAddr.
The default remains IPv4 wildcard binding:
0.0.0.0:8438
The listener address can be overridden with CONTROLLER_ADDR.
For IPv6, the value must use bracketed socket-address syntax:
CONTROLLER_ADDR='[::]:8438'
This value is used directly by both server modes:
- HTTPS/WSS mode uses
axum_server::bind(settings.listen_addr). - HTTP/WS mode uses
tokio::net::TcpListener::bind(settings.listen_addr).
There is no string concatenation of IP and port in the controller listener
path, so the common IPv6 failure form :::8438 is avoided.
Goals
- Support IPv4, IPv6, and dual-stack controller listener binding.
- Keep the current default as IPv4 wildcard binding.
- Keep the existing
CONTROLLER_ADDRconfiguration contract. - Accept runtime registration metadata that uses IPv4 literals, IPv6 literals, or DNS hostnames.
- Reject unspecified runtime registration addresses such as
0.0.0.0and::, because those are bind addresses, not reachable service addresses.
Non-Goals
- Do not enable IPv6 by default.
- Do not change controller WebSocket paths or JSON-RPC contracts.
- Do not change the runtime registration metadata shape.
- Do not add client-side IPv4 fallback in this change.
Listener Configuration
Default IPv4 listener:
CONTROLLER_ADDR='0.0.0.0:8438'
IPv6 wildcard listener:
CONTROLLER_ADDR='[::]:8438'
Specific IPv6 interface:
CONTROLLER_ADDR='[fdd0:0:0:1::10]:8438'
Specific IPv4 interface:
CONTROLLER_ADDR='172.16.1.10:8438'
The brackets are required only because CONTROLLER_ADDR is a full socket
address. They separate the IPv6 literal from the port.
TLS Configuration
The controller starts with TLS enabled by default. IPv6 listener support does not remove the normal TLS hostname requirements.
For production, provide a certificate whose Subject Alternative Name covers the DNS name or IP address clients use to reach the controller:
CONTROLLER_TLS_CERT_PATH=/config/server.pem
CONTROLLER_TLS_KEY_PATH=/config/server.key
CONTROLLER_TLS_TRUST_CERT_PATH=/config/ca.pem
For generated local self-signed certificates, include any IPv6 literal that clients will use:
CONTROLLER_TLS_SERVER_NAME=localhost
CONTROLLER_TLS_ALT_NAMES='localhost,127.0.0.1,::1'
If clients connect by DNS name, prefer adding that DNS name to the certificate SAN and keep clients using the name instead of a raw IP literal.
Runtime Registration Metadata
Runtime services connect outbound to Light Controller and send
service/register metadata. The registration address is not a socket address;
the address and port are separate fields.
IPv6 registration metadata should use the raw IPv6 literal:
{
"serviceId": "com.networknt.light-gateway-1.0.0",
"protocol": "https",
"address": "fdd0:0:0:1::3",
"port": 8443
}
Do not use brackets in the address field:
{
"address": "[fdd0:0:0:1::3]"
}
Brackets are only used when constructing URL authorities or socket addresses.
The controller validates registration addresses as IP literals or DNS hostnames
and rejects unspecified bind addresses such as 0.0.0.0 and ::.
Discovery Behavior
Discovery returns the registered address and port separately. Downstream
clients, such as light-gateway, are responsible for building a reachable
upstream authority. For IPv6 literals, clients must bracket the address when
they construct a URL or host:port authority:
address = fdd0:0:0:1::3
port = 8443
target = [fdd0:0:0:1::3]:8443
Deployment Guidance
Only configure the controller with CONTROLLER_ADDR='[::]:8438' when the host,
container network, Kubernetes Service, and ingress path are intended to accept
IPv6 traffic.
In a dual-stack environment, verify all of these:
- the controller process is listening on IPv6;
- DNS returns the expected address family;
- TLS SANs cover the hostname or IP clients use;
- runtime services can open outbound WebSocket connections to the controller;
- registered service metadata publishes reachable addresses, not wildcard bind addresses.
Verification
From a peer in the same network:
getent ahosts <controller-host>
curl -k -g https://[<controller-ipv6>]:8438/health
curl -k -v https://<controller-host>:8438/health
For WebSocket clients, verify the same address family through the real endpoint:
wss://<controller-host>:8438/ws/microservice
wss://<controller-host>:8438/ws/discovery
wss://<controller-host>:8438/ctrl/mcp
If a TLS client connects by IPv6 literal and fails certificate validation, check the certificate SANs before changing the controller listener.