1
#include "source/common/network/happy_eyeballs_connection_impl.h"
2

            
3
#include <set>
4

            
5
#include "envoy/network/address.h"
6

            
7
#include "source/common/network/connection_impl.h"
8

            
9
namespace Envoy {
10
namespace Network {
11

            
12
HappyEyeballsConnectionProvider::HappyEyeballsConnectionProvider(
13
    Event::Dispatcher& dispatcher, const std::vector<Address::InstanceConstSharedPtr>& address_list,
14
    const std::shared_ptr<const Upstream::UpstreamLocalAddressSelector>&
15
        upstream_local_address_selector,
16
    UpstreamTransportSocketFactory& socket_factory,
17
    TransportSocketOptionsConstSharedPtr transport_socket_options,
18
    const Upstream::HostDescriptionConstSharedPtr& host,
19
    const ConnectionSocket::OptionsSharedPtr options,
20
    const envoy::config::cluster::v3::UpstreamConnectionOptions::HappyEyeballsConfig&
21
        happy_eyeballs_config)
22
38
    : dispatcher_(dispatcher), address_list_(sortAddresses(address_list, happy_eyeballs_config)),
23
38
      upstream_local_address_selector_(upstream_local_address_selector),
24
38
      socket_factory_(socket_factory), transport_socket_options_(transport_socket_options),
25
38
      host_(host), options_(options) {}
26

            
27
27
bool HappyEyeballsConnectionProvider::hasNextConnection() {
28
27
  return next_address_ < address_list_.size();
29
27
}
30

            
31
45
ClientConnectionPtr HappyEyeballsConnectionProvider::createNextConnection(const uint64_t id) {
32
45
  if (first_connection_created_) {
33
    // The stats for the first connection are handled in ActiveClient::ActiveClient
34
7
    host_->stats().cx_total_.inc();
35
7
    host_->cluster().trafficStats()->upstream_cx_total_.inc();
36
7
  }
37
45
  first_connection_created_ = true;
38
45
  ASSERT(hasNextConnection());
39
45
  ENVOY_LOG_EVENT(debug, "happy_eyeballs_cx_attempt", "C[{}] address={}", id,
40
45
                  address_list_[next_address_]->asStringView());
41
45
  auto& address = address_list_[next_address_++];
42
45
  auto upstream_local_address = upstream_local_address_selector_->getUpstreamLocalAddress(
43
45
      address, options_, makeOptRefFromPtr(transport_socket_options_.get()));
44

            
45
45
  return dispatcher_.createClientConnection(
46
45
      address, upstream_local_address.address_,
47
45
      socket_factory_.createTransportSocket(transport_socket_options_, host_),
48
45
      upstream_local_address.socket_options_, transport_socket_options_);
49
45
}
50

            
51
size_t HappyEyeballsConnectionProvider::nextConnection() { return next_address_; }
52

            
53
size_t HappyEyeballsConnectionProvider::totalConnections() { return address_list_.size(); }
54

            
55
namespace {
56

            
57
struct AddressFamily {
58
  Address::Type type;
59
  absl::optional<Address::IpVersion> version;
60

            
61
409
  bool operator==(const AddressFamily& other) const {
62
409
    return type == other.type && version == other.version;
63
409
  }
64

            
65
1382
  bool operator<(const AddressFamily& other) const {
66
1382
    if (type != other.type) {
67
265
      return type < other.type;
68
265
    }
69
1117
    return version < other.version;
70
1382
  }
71
};
72

            
73
354
AddressFamily getFamily(const Address::InstanceConstSharedPtr& addr) {
74
354
  if (addr->type() == Address::Type::Ip) {
75
284
    return {Address::Type::Ip, addr->ip()->version()};
76
284
  }
77
70
  return {addr->type(), absl::nullopt};
78
354
}
79

            
80
} // namespace
81

            
82
std::vector<Address::InstanceConstSharedPtr> HappyEyeballsConnectionProvider::sortAddresses(
83
    const std::vector<Address::InstanceConstSharedPtr>& in,
84
    const envoy::config::cluster::v3::UpstreamConnectionOptions::HappyEyeballsConfig&
85
64
        happy_eyeballs_config) {
86
  // Sort the addresses according to https://datatracker.ietf.org/doc/html/rfc8305#section-4.
87
  // Currently the first_address_family version and count options are supported. This allows
88
  // specifying the address family version to prefer over others, and the number (count) of
89
  // addresses in that family to attempt before moving to the next family.
90
  //
91
  // If no family version is specified, the version is taken from the first address in the list.
92
  // The default count is 1. As an example, assume the first family version is v6, and the count
93
  // is 3, then the output list will be:
94
  //
95
  //     [3*v6, 1*v4, 3*v6, 1*v4, ...]
96
  //
97
  // assuming sufficient addresses exist in the input.
98
  //
99
  // This implementation generalizes this to multiple address types (IPv4, IPv6, Pipe, Internal).
100
64
  ENVOY_LOG_EVENT(trace, "happy_eyeballs_sort_address", "sort address with happy_eyeballs config.");
101
64
  std::vector<Address::InstanceConstSharedPtr> address_list;
102
64
  address_list.reserve(in.size());
103

            
104
64
  ASSERT(!in.empty());
105

            
106
64
  AddressFamily preferred_family = getFamily(in[0]);
107
64
  switch (happy_eyeballs_config.first_address_family_version()) {
108
50
  case envoy::config::cluster::v3::UpstreamConnectionOptions::DEFAULT:
109
50
    break;
110
11
  case envoy::config::cluster::v3::UpstreamConnectionOptions::V4:
111
11
    preferred_family = {Address::Type::Ip, Address::IpVersion::v4};
112
11
    break;
113
  case envoy::config::cluster::v3::UpstreamConnectionOptions::V6:
114
    preferred_family = {Address::Type::Ip, Address::IpVersion::v6};
115
    break;
116
1
  case envoy::config::cluster::v3::UpstreamConnectionOptions::PIPE:
117
1
    preferred_family = {Address::Type::Pipe, absl::nullopt};
118
1
    break;
119
2
  case envoy::config::cluster::v3::UpstreamConnectionOptions::INTERNAL:
120
2
    preferred_family = {Address::Type::EnvoyInternal, absl::nullopt};
121
2
    break;
122
  default:
123
    break;
124
64
  }
125

            
126
  // Group addresses by family, preserving original family order except placing preferred_family
127
  // in the first position. Store each family with an index that will be used for iterating through
128
  // the bucket of addresses with that address family.
129
64
  std::vector<std::pair<AddressFamily, uint32_t>> family_order;
130
64
  std::map<AddressFamily, std::vector<Address::InstanceConstSharedPtr>> buckets;
131
290
  for (const auto& addr : in) {
132
290
    AddressFamily family = getFamily(addr);
133
290
    auto& bucket = buckets[family];
134
290
    if (bucket.empty()) {
135
102
      if (family == preferred_family) {
136
61
        family_order.insert(family_order.begin(), {family, 0});
137
73
      } else {
138
41
        family_order.push_back({family, 0});
139
41
      }
140
102
    }
141
290
    bucket.push_back(addr);
142
290
  }
143

            
144
64
  const auto first_address_family_count =
145
64
      PROTOBUF_GET_WRAPPED_OR_DEFAULT(happy_eyeballs_config, first_address_family_count, 1);
146

            
147
  // Loop through address families.
148
371
  for (int i = 0; address_list.size() < in.size(); i = (i + 1) % family_order.size()) {
149
307
    std::vector<Address::InstanceConstSharedPtr>& bucket = buckets[family_order[i].first];
150
    // Push first_address_family_count addresses for the preferred family. We can't just check if
151
    // i == 0 because the preferred family may not be present.
152
307
    int num_addrs_to_push =
153
307
        (family_order[i].first == preferred_family) ? first_address_family_count : 1;
154
597
    for (int j = 0; family_order[i].second < bucket.size() && j < num_addrs_to_push; ++j) {
155
290
      address_list.push_back(std::move(bucket[family_order[i].second++]));
156
290
    }
157
307
  }
158

            
159
64
  ASSERT(address_list.size() == in.size());
160
64
  return address_list;
161
64
}
162

            
163
} // namespace Network
164
} // namespace Envoy