111 lines
3.7 KiB
Go
111 lines
3.7 KiB
Go
package grpc
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"strings"
|
|
|
|
"google.golang.org/grpc/resolver"
|
|
)
|
|
|
|
// staticBuilder implements the `resolver.Builder` interface.
|
|
type staticBuilder struct{}
|
|
|
|
// newStaticBuilder creates a `staticBuilder` used to construct static DNS
|
|
// resolvers.
|
|
func newStaticBuilder() resolver.Builder {
|
|
return &staticBuilder{}
|
|
}
|
|
|
|
// Build implements the `resolver.Builder` interface and is usually called by
|
|
// the gRPC dialer. It takes a target containing a comma separated list of
|
|
// IPv4/6 addresses and a `resolver.ClientConn` and returns a `staticResolver`
|
|
// which implements the `resolver.Resolver` interface.
|
|
func (sb *staticBuilder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) {
|
|
var resolverAddrs []resolver.Address
|
|
for _, address := range strings.Split(target.Endpoint(), ",") {
|
|
parsedAddress, err := parseResolverIPAddress(address)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resolverAddrs = append(resolverAddrs, *parsedAddress)
|
|
}
|
|
r, err := newStaticResolver(cc, resolverAddrs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
// Scheme returns the scheme that `staticBuilder` will be registered for, for
|
|
// example: `static:///`.
|
|
func (sb *staticBuilder) Scheme() string {
|
|
return "static"
|
|
}
|
|
|
|
// staticResolver is used to wrap an inner `resolver.ClientConn` and implements
|
|
// the `resolver.Resolver` interface.
|
|
type staticResolver struct {
|
|
cc resolver.ClientConn
|
|
}
|
|
|
|
// newStaticResolver takes a `resolver.ClientConn` and a list of
|
|
// `resolver.Addresses`. It updates the state of the `resolver.ClientConn` with
|
|
// the provided addresses and returns a `staticResolver` which wraps the
|
|
// `resolver.ClientConn` and implements the `resolver.Resolver` interface.
|
|
func newStaticResolver(cc resolver.ClientConn, resolverAddrs []resolver.Address) (resolver.Resolver, error) {
|
|
err := cc.UpdateState(resolver.State{Addresses: resolverAddrs})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &staticResolver{cc: cc}, nil
|
|
}
|
|
|
|
// ResolveNow is a no-op necessary for `staticResolver` to implement the
|
|
// `resolver.Resolver` interface. This resolver is constructed once by
|
|
// staticBuilder.Build and the state of the inner `resolver.ClientConn` is never
|
|
// updated.
|
|
func (sr *staticResolver) ResolveNow(_ resolver.ResolveNowOptions) {}
|
|
|
|
// Close is a no-op necessary for `staticResolver` to implement the
|
|
// `resolver.Resolver` interface.
|
|
func (sr *staticResolver) Close() {}
|
|
|
|
// parseResolverIPAddress takes an IPv4/6 address (ip:port, [ip]:port, or :port)
|
|
// and returns a properly formatted `resolver.Address` object. The `Addr` and
|
|
// `ServerName` fields of the returned `resolver.Address` will both be set to
|
|
// host:port or [host]:port if the host is an IPv6 address.
|
|
func parseResolverIPAddress(addr string) (*resolver.Address, error) {
|
|
host, port, err := net.SplitHostPort(addr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("splitting host and port for address %q: %w", addr, err)
|
|
}
|
|
if port == "" {
|
|
// If the port field is empty the address ends with colon (e.g.
|
|
// "[::1]:").
|
|
return nil, fmt.Errorf("address %q missing port after port-separator colon", addr)
|
|
}
|
|
if host == "" {
|
|
// Address only has a port (i.e ipv4-host:port, [ipv6-host]:port,
|
|
// host-name:port). Keep consistent with net.Dial(); if the host is
|
|
// empty (e.g. :80), the local system is assumed.
|
|
host = "127.0.0.1"
|
|
}
|
|
_, err = netip.ParseAddr(host)
|
|
if err != nil {
|
|
// Host is a DNS name or an IPv6 address without brackets.
|
|
return nil, fmt.Errorf("address %q is not an IP address", addr)
|
|
}
|
|
parsedAddr := net.JoinHostPort(host, port)
|
|
return &resolver.Address{
|
|
Addr: parsedAddr,
|
|
ServerName: parsedAddr,
|
|
}, nil
|
|
}
|
|
|
|
// init registers the `staticBuilder` with the gRPC resolver registry.
|
|
func init() {
|
|
resolver.Register(newStaticBuilder())
|
|
}
|