123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- package allregions
- import (
- "fmt"
- "math/rand"
- "github.com/rs/zerolog"
- )
- // Regions stores Cloudflare edge network IPs, partitioned into two regions.
- // This is NOT thread-safe. Users of this package should use it with a lock.
- type Regions struct {
- region1 Region
- region2 Region
- }
- // ------------------------------------
- // Constructors
- // ------------------------------------
- // ResolveEdge resolves the Cloudflare edge, returning all regions discovered.
- func ResolveEdge(log *zerolog.Logger, region string, overrideIPVersion ConfigIPVersion) (*Regions, error) {
- edgeAddrs, err := edgeDiscovery(log, getRegionalServiceName(region))
- if err != nil {
- return nil, err
- }
- if len(edgeAddrs) < 2 {
- return nil, fmt.Errorf("expected at least 2 Cloudflare Regions regions, but SRV only returned %v", len(edgeAddrs))
- }
- return &Regions{
- region1: NewRegion(edgeAddrs[0], overrideIPVersion),
- region2: NewRegion(edgeAddrs[1], overrideIPVersion),
- }, nil
- }
- // StaticEdge creates a list of edge addresses from the list of hostnames.
- // Mainly used for testing connectivity.
- func StaticEdge(hostnames []string, log *zerolog.Logger) (*Regions, error) {
- resolved := ResolveAddrs(hostnames, log)
- if len(resolved) == 0 {
- return nil, fmt.Errorf("failed to resolve any edge address")
- }
- return NewNoResolve(resolved), nil
- }
- // NewNoResolve doesn't resolve the edge. Instead it just uses the given addresses.
- // You probably only need this for testing.
- func NewNoResolve(addrs []*EdgeAddr) *Regions {
- region1 := make([]*EdgeAddr, 0)
- region2 := make([]*EdgeAddr, 0)
- for i, v := range addrs {
- if i%2 == 0 {
- region1 = append(region1, v)
- } else {
- region2 = append(region2, v)
- }
- }
- return &Regions{
- region1: NewRegion(region1, Auto),
- region2: NewRegion(region2, Auto),
- }
- }
- // ------------------------------------
- // Methods
- // ------------------------------------
- // GetAnyAddress returns an arbitrary address from the larger region.
- func (rs *Regions) GetAnyAddress() *EdgeAddr {
- if addr := rs.region1.GetAnyAddress(); addr != nil {
- return addr
- }
- return rs.region2.GetAnyAddress()
- }
- // AddrUsedBy finds the address used by the given connection.
- // Returns nil if the connection isn't using an address.
- func (rs *Regions) AddrUsedBy(connID int) *EdgeAddr {
- if addr := rs.region1.AddrUsedBy(connID); addr != nil {
- return addr
- }
- return rs.region2.AddrUsedBy(connID)
- }
- // GetUnusedAddr gets an unused addr from the edge, excluding the given addr. Prefer to use addresses
- // evenly across both regions.
- func (rs *Regions) GetUnusedAddr(excluding *EdgeAddr, connID int) *EdgeAddr {
- // If both regions have the same number of available addrs, lets randomise which one
- // we pick. The rest of this algorithm will continue to make sure we always use addresses
- // evenly across both regions.
- if rs.region1.AvailableAddrs() == rs.region2.AvailableAddrs() {
- regions := []Region{rs.region1, rs.region2}
- firstChoice := rand.Intn(2)
- return getAddrs(excluding, connID, ®ions[firstChoice], ®ions[1-firstChoice])
- }
- if rs.region1.AvailableAddrs() > rs.region2.AvailableAddrs() {
- return getAddrs(excluding, connID, &rs.region1, &rs.region2)
- }
- return getAddrs(excluding, connID, &rs.region2, &rs.region1)
- }
- // getAddrs tries to grab address form `first` region, then `second` region
- // this is an unrolled loop over 2 element array
- func getAddrs(excluding *EdgeAddr, connID int, first *Region, second *Region) *EdgeAddr {
- addr := first.AssignAnyAddress(connID, excluding)
- if addr != nil {
- return addr
- }
- addr = second.AssignAnyAddress(connID, excluding)
- if addr != nil {
- return addr
- }
- return nil
- }
- // AvailableAddrs returns how many edge addresses aren't used.
- func (rs *Regions) AvailableAddrs() int {
- return rs.region1.AvailableAddrs() + rs.region2.AvailableAddrs()
- }
- // GiveBack the address so that other connections can use it.
- // Returns true if the address is in this edge.
- func (rs *Regions) GiveBack(addr *EdgeAddr, hasConnectivityError bool) bool {
- if found := rs.region1.GiveBack(addr, hasConnectivityError); found {
- return found
- }
- return rs.region2.GiveBack(addr, hasConnectivityError)
- }
- // Return regionalized service name if `region` isn't empty, otherwise return the global service name for origintunneld
- func getRegionalServiceName(region string) string {
- if region != "" {
- return region + "-" + srvService // Example: `us-v2-origintunneld`
- }
- return srvService // Global service is just `v2-origintunneld`
- }
|