mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 10:03:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			294 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			294 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
 *
 | 
						|
 * Copyright 2017 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 naming
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"net"
 | 
						|
	"strconv"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"google.golang.org/grpc/grpclog"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	defaultPort = "443"
 | 
						|
	defaultFreq = time.Minute * 30
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	errMissingAddr  = errors.New("missing address")
 | 
						|
	errWatcherClose = errors.New("watcher has been closed")
 | 
						|
 | 
						|
	lookupHost = net.DefaultResolver.LookupHost
 | 
						|
	lookupSRV  = net.DefaultResolver.LookupSRV
 | 
						|
)
 | 
						|
 | 
						|
// NewDNSResolverWithFreq creates a DNS Resolver that can resolve DNS names, and
 | 
						|
// create watchers that poll the DNS server using the frequency set by freq.
 | 
						|
func NewDNSResolverWithFreq(freq time.Duration) (Resolver, error) {
 | 
						|
	return &dnsResolver{freq: freq}, nil
 | 
						|
}
 | 
						|
 | 
						|
// NewDNSResolver creates a DNS Resolver that can resolve DNS names, and create
 | 
						|
// watchers that poll the DNS server using the default frequency defined by defaultFreq.
 | 
						|
func NewDNSResolver() (Resolver, error) {
 | 
						|
	return NewDNSResolverWithFreq(defaultFreq)
 | 
						|
}
 | 
						|
 | 
						|
// dnsResolver handles name resolution for names following the DNS scheme
 | 
						|
type dnsResolver struct {
 | 
						|
	// frequency of polling the DNS server that the watchers created by this resolver will use.
 | 
						|
	freq time.Duration
 | 
						|
}
 | 
						|
 | 
						|
// formatIP returns ok = false if addr is not a valid textual representation of an IP address.
 | 
						|
// If addr is an IPv4 address, return the addr and ok = true.
 | 
						|
// If addr is an IPv6 address, return the addr enclosed in square brackets and ok = true.
 | 
						|
func formatIP(addr string) (addrIP string, ok bool) {
 | 
						|
	ip := net.ParseIP(addr)
 | 
						|
	if ip == nil {
 | 
						|
		return "", false
 | 
						|
	}
 | 
						|
	if ip.To4() != nil {
 | 
						|
		return addr, true
 | 
						|
	}
 | 
						|
	return "[" + addr + "]", true
 | 
						|
}
 | 
						|
 | 
						|
// parseTarget takes the user input target string, returns formatted host and port info.
 | 
						|
// If target doesn't specify a port, set the port to be the defaultPort.
 | 
						|
// If target is in IPv6 format and host-name is enclosed in square brackets, brackets
 | 
						|
// are stripped when setting the host.
 | 
						|
// examples:
 | 
						|
// target: "www.google.com" returns host: "www.google.com", port: "443"
 | 
						|
// target: "ipv4-host:80" returns host: "ipv4-host", port: "80"
 | 
						|
// target: "[ipv6-host]" returns host: "ipv6-host", port: "443"
 | 
						|
// target: ":80" returns host: "localhost", port: "80"
 | 
						|
// target: ":" returns host: "localhost", port: "443"
 | 
						|
func parseTarget(target string) (host, port string, err error) {
 | 
						|
	if target == "" {
 | 
						|
		return "", "", errMissingAddr
 | 
						|
	}
 | 
						|
 | 
						|
	if ip := net.ParseIP(target); ip != nil {
 | 
						|
		// target is an IPv4 or IPv6(without brackets) address
 | 
						|
		return target, defaultPort, nil
 | 
						|
	}
 | 
						|
	if host, port, err := net.SplitHostPort(target); err == nil {
 | 
						|
		// target has port, i.e ipv4-host:port, [ipv6-host]:port, host-name:port
 | 
						|
		if host == "" {
 | 
						|
			// Keep consistent with net.Dial(): If the host is empty, as in ":80", the local system is assumed.
 | 
						|
			host = "localhost"
 | 
						|
		}
 | 
						|
		if port == "" {
 | 
						|
			// If the port field is empty(target ends with colon), e.g. "[::1]:", defaultPort is used.
 | 
						|
			port = defaultPort
 | 
						|
		}
 | 
						|
		return host, port, nil
 | 
						|
	}
 | 
						|
	if host, port, err := net.SplitHostPort(target + ":" + defaultPort); err == nil {
 | 
						|
		// target doesn't have port
 | 
						|
		return host, port, nil
 | 
						|
	}
 | 
						|
	return "", "", fmt.Errorf("invalid target address %v", target)
 | 
						|
}
 | 
						|
 | 
						|
// Resolve creates a watcher that watches the name resolution of the target.
 | 
						|
func (r *dnsResolver) Resolve(target string) (Watcher, error) {
 | 
						|
	host, port, err := parseTarget(target)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if net.ParseIP(host) != nil {
 | 
						|
		ipWatcher := &ipWatcher{
 | 
						|
			updateChan: make(chan *Update, 1),
 | 
						|
		}
 | 
						|
		host, _ = formatIP(host)
 | 
						|
		ipWatcher.updateChan <- &Update{Op: Add, Addr: host + ":" + port}
 | 
						|
		return ipWatcher, nil
 | 
						|
	}
 | 
						|
 | 
						|
	ctx, cancel := context.WithCancel(context.Background())
 | 
						|
	return &dnsWatcher{
 | 
						|
		r:      r,
 | 
						|
		host:   host,
 | 
						|
		port:   port,
 | 
						|
		ctx:    ctx,
 | 
						|
		cancel: cancel,
 | 
						|
		t:      time.NewTimer(0),
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
// dnsWatcher watches for the name resolution update for a specific target
 | 
						|
type dnsWatcher struct {
 | 
						|
	r    *dnsResolver
 | 
						|
	host string
 | 
						|
	port string
 | 
						|
	// The latest resolved address set
 | 
						|
	curAddrs map[string]*Update
 | 
						|
	ctx      context.Context
 | 
						|
	cancel   context.CancelFunc
 | 
						|
	t        *time.Timer
 | 
						|
}
 | 
						|
 | 
						|
// ipWatcher watches for the name resolution update for an IP address.
 | 
						|
type ipWatcher struct {
 | 
						|
	updateChan chan *Update
 | 
						|
}
 | 
						|
 | 
						|
// Next returns the address resolution Update for the target. For IP address,
 | 
						|
// the resolution is itself, thus polling name server is unnecessary. Therefore,
 | 
						|
// Next() will return an Update the first time it is called, and will be blocked
 | 
						|
// for all following calls as no Update exists until watcher is closed.
 | 
						|
func (i *ipWatcher) Next() ([]*Update, error) {
 | 
						|
	u, ok := <-i.updateChan
 | 
						|
	if !ok {
 | 
						|
		return nil, errWatcherClose
 | 
						|
	}
 | 
						|
	return []*Update{u}, nil
 | 
						|
}
 | 
						|
 | 
						|
// Close closes the ipWatcher.
 | 
						|
func (i *ipWatcher) Close() {
 | 
						|
	close(i.updateChan)
 | 
						|
}
 | 
						|
 | 
						|
// AddressType indicates the address type returned by name resolution.
 | 
						|
type AddressType uint8
 | 
						|
 | 
						|
const (
 | 
						|
	// Backend indicates the server is a backend server.
 | 
						|
	Backend AddressType = iota
 | 
						|
	// GRPCLB indicates the server is a grpclb load balancer.
 | 
						|
	GRPCLB
 | 
						|
)
 | 
						|
 | 
						|
// AddrMetadataGRPCLB contains the information the name resolver for grpclb should provide. The
 | 
						|
// name resolver used by the grpclb balancer is required to provide this type of metadata in
 | 
						|
// its address updates.
 | 
						|
type AddrMetadataGRPCLB struct {
 | 
						|
	// AddrType is the type of server (grpc load balancer or backend).
 | 
						|
	AddrType AddressType
 | 
						|
	// ServerName is the name of the grpc load balancer. Used for authentication.
 | 
						|
	ServerName string
 | 
						|
}
 | 
						|
 | 
						|
// compileUpdate compares the old resolved addresses and newly resolved addresses,
 | 
						|
// and generates an update list
 | 
						|
func (w *dnsWatcher) compileUpdate(newAddrs map[string]*Update) []*Update {
 | 
						|
	var res []*Update
 | 
						|
	for a, u := range w.curAddrs {
 | 
						|
		if _, ok := newAddrs[a]; !ok {
 | 
						|
			u.Op = Delete
 | 
						|
			res = append(res, u)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for a, u := range newAddrs {
 | 
						|
		if _, ok := w.curAddrs[a]; !ok {
 | 
						|
			res = append(res, u)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return res
 | 
						|
}
 | 
						|
 | 
						|
func (w *dnsWatcher) lookupSRV() map[string]*Update {
 | 
						|
	newAddrs := make(map[string]*Update)
 | 
						|
	_, srvs, err := lookupSRV(w.ctx, "grpclb", "tcp", w.host)
 | 
						|
	if err != nil {
 | 
						|
		grpclog.Infof("grpc: failed dns SRV record lookup due to %v.\n", err)
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	for _, s := range srvs {
 | 
						|
		lbAddrs, err := lookupHost(w.ctx, s.Target)
 | 
						|
		if err != nil {
 | 
						|
			grpclog.Warningf("grpc: failed load balancer address dns lookup due to %v.\n", err)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		for _, a := range lbAddrs {
 | 
						|
			a, ok := formatIP(a)
 | 
						|
			if !ok {
 | 
						|
				grpclog.Errorf("grpc: failed IP parsing due to %v.\n", err)
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			addr := a + ":" + strconv.Itoa(int(s.Port))
 | 
						|
			newAddrs[addr] = &Update{Addr: addr,
 | 
						|
				Metadata: AddrMetadataGRPCLB{AddrType: GRPCLB, ServerName: s.Target}}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return newAddrs
 | 
						|
}
 | 
						|
 | 
						|
func (w *dnsWatcher) lookupHost() map[string]*Update {
 | 
						|
	newAddrs := make(map[string]*Update)
 | 
						|
	addrs, err := lookupHost(w.ctx, w.host)
 | 
						|
	if err != nil {
 | 
						|
		grpclog.Warningf("grpc: failed dns A record lookup due to %v.\n", err)
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	for _, a := range addrs {
 | 
						|
		a, ok := formatIP(a)
 | 
						|
		if !ok {
 | 
						|
			grpclog.Errorf("grpc: failed IP parsing due to %v.\n", err)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		addr := a + ":" + w.port
 | 
						|
		newAddrs[addr] = &Update{Addr: addr}
 | 
						|
	}
 | 
						|
	return newAddrs
 | 
						|
}
 | 
						|
 | 
						|
func (w *dnsWatcher) lookup() []*Update {
 | 
						|
	newAddrs := w.lookupSRV()
 | 
						|
	if newAddrs == nil {
 | 
						|
		// If failed to get any balancer address (either no corresponding SRV for the
 | 
						|
		// target, or caused by failure during resolution/parsing of the balancer target),
 | 
						|
		// return any A record info available.
 | 
						|
		newAddrs = w.lookupHost()
 | 
						|
	}
 | 
						|
	result := w.compileUpdate(newAddrs)
 | 
						|
	w.curAddrs = newAddrs
 | 
						|
	return result
 | 
						|
}
 | 
						|
 | 
						|
// Next returns the resolved address update(delta) for the target. If there's no
 | 
						|
// change, it will sleep for 30 mins and try to resolve again after that.
 | 
						|
func (w *dnsWatcher) Next() ([]*Update, error) {
 | 
						|
	for {
 | 
						|
		select {
 | 
						|
		case <-w.ctx.Done():
 | 
						|
			return nil, errWatcherClose
 | 
						|
		case <-w.t.C:
 | 
						|
		}
 | 
						|
		result := w.lookup()
 | 
						|
		// Next lookup should happen after an interval defined by w.r.freq.
 | 
						|
		w.t.Reset(w.r.freq)
 | 
						|
		if len(result) > 0 {
 | 
						|
			return result, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (w *dnsWatcher) Close() {
 | 
						|
	w.cancel()
 | 
						|
}
 |