regions.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package allregions
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "github.com/rs/zerolog"
  6. )
  7. // Regions stores Cloudflare edge network IPs, partitioned into two regions.
  8. // This is NOT thread-safe. Users of this package should use it with a lock.
  9. type Regions struct {
  10. region1 Region
  11. region2 Region
  12. }
  13. // ------------------------------------
  14. // Constructors
  15. // ------------------------------------
  16. // ResolveEdge resolves the Cloudflare edge, returning all regions discovered.
  17. func ResolveEdge(log *zerolog.Logger, region string, overrideIPVersion ConfigIPVersion) (*Regions, error) {
  18. edgeAddrs, err := edgeDiscovery(log, getRegionalServiceName(region))
  19. if err != nil {
  20. return nil, err
  21. }
  22. if len(edgeAddrs) < 2 {
  23. return nil, fmt.Errorf("expected at least 2 Cloudflare Regions regions, but SRV only returned %v", len(edgeAddrs))
  24. }
  25. return &Regions{
  26. region1: NewRegion(edgeAddrs[0], overrideIPVersion),
  27. region2: NewRegion(edgeAddrs[1], overrideIPVersion),
  28. }, nil
  29. }
  30. // StaticEdge creates a list of edge addresses from the list of hostnames.
  31. // Mainly used for testing connectivity.
  32. func StaticEdge(hostnames []string, log *zerolog.Logger) (*Regions, error) {
  33. resolved := ResolveAddrs(hostnames, log)
  34. if len(resolved) == 0 {
  35. return nil, fmt.Errorf("failed to resolve any edge address")
  36. }
  37. return NewNoResolve(resolved), nil
  38. }
  39. // NewNoResolve doesn't resolve the edge. Instead it just uses the given addresses.
  40. // You probably only need this for testing.
  41. func NewNoResolve(addrs []*EdgeAddr) *Regions {
  42. region1 := make([]*EdgeAddr, 0)
  43. region2 := make([]*EdgeAddr, 0)
  44. for i, v := range addrs {
  45. if i%2 == 0 {
  46. region1 = append(region1, v)
  47. } else {
  48. region2 = append(region2, v)
  49. }
  50. }
  51. return &Regions{
  52. region1: NewRegion(region1, Auto),
  53. region2: NewRegion(region2, Auto),
  54. }
  55. }
  56. // ------------------------------------
  57. // Methods
  58. // ------------------------------------
  59. // GetAnyAddress returns an arbitrary address from the larger region.
  60. func (rs *Regions) GetAnyAddress() *EdgeAddr {
  61. if addr := rs.region1.GetAnyAddress(); addr != nil {
  62. return addr
  63. }
  64. return rs.region2.GetAnyAddress()
  65. }
  66. // AddrUsedBy finds the address used by the given connection.
  67. // Returns nil if the connection isn't using an address.
  68. func (rs *Regions) AddrUsedBy(connID int) *EdgeAddr {
  69. if addr := rs.region1.AddrUsedBy(connID); addr != nil {
  70. return addr
  71. }
  72. return rs.region2.AddrUsedBy(connID)
  73. }
  74. // GetUnusedAddr gets an unused addr from the edge, excluding the given addr. Prefer to use addresses
  75. // evenly across both regions.
  76. func (rs *Regions) GetUnusedAddr(excluding *EdgeAddr, connID int) *EdgeAddr {
  77. // If both regions have the same number of available addrs, lets randomise which one
  78. // we pick. The rest of this algorithm will continue to make sure we always use addresses
  79. // evenly across both regions.
  80. if rs.region1.AvailableAddrs() == rs.region2.AvailableAddrs() {
  81. regions := []Region{rs.region1, rs.region2}
  82. firstChoice := rand.Intn(2)
  83. return getAddrs(excluding, connID, &regions[firstChoice], &regions[1-firstChoice])
  84. }
  85. if rs.region1.AvailableAddrs() > rs.region2.AvailableAddrs() {
  86. return getAddrs(excluding, connID, &rs.region1, &rs.region2)
  87. }
  88. return getAddrs(excluding, connID, &rs.region2, &rs.region1)
  89. }
  90. // getAddrs tries to grab address form `first` region, then `second` region
  91. // this is an unrolled loop over 2 element array
  92. func getAddrs(excluding *EdgeAddr, connID int, first *Region, second *Region) *EdgeAddr {
  93. addr := first.AssignAnyAddress(connID, excluding)
  94. if addr != nil {
  95. return addr
  96. }
  97. addr = second.AssignAnyAddress(connID, excluding)
  98. if addr != nil {
  99. return addr
  100. }
  101. return nil
  102. }
  103. // AvailableAddrs returns how many edge addresses aren't used.
  104. func (rs *Regions) AvailableAddrs() int {
  105. return rs.region1.AvailableAddrs() + rs.region2.AvailableAddrs()
  106. }
  107. // GiveBack the address so that other connections can use it.
  108. // Returns true if the address is in this edge.
  109. func (rs *Regions) GiveBack(addr *EdgeAddr, hasConnectivityError bool) bool {
  110. if found := rs.region1.GiveBack(addr, hasConnectivityError); found {
  111. return found
  112. }
  113. return rs.region2.GiveBack(addr, hasConnectivityError)
  114. }
  115. // Return regionalized service name if `region` isn't empty, otherwise return the global service name for origintunneld
  116. func getRegionalServiceName(region string) string {
  117. if region != "" {
  118. return region + "-" + srvService // Example: `us-v2-origintunneld`
  119. }
  120. return srvService // Global service is just `v2-origintunneld`
  121. }