1
#include "source/common/quic/envoy_deterministic_connection_id_generator.h"
2

            
3
#include <cstdint>
4

            
5
#include "source/common/network/socket_option_impl.h"
6
#include "source/common/quic/envoy_quic_utils.h"
7

            
8
#include "quiche/quic/load_balancer/load_balancer_encoder.h"
9

            
10
namespace Envoy {
11
namespace Quic {
12

            
13
namespace {
14

            
15
// Modify new_connection_id according to given old_connection_id to make sure packets with the new
16
// one can be routed to the same listener.
17
void adjustNewConnectionIdForRouting(quic::QuicConnectionId& new_connection_id,
18
36546
                                     const quic::QuicConnectionId& old_connection_id) {
19
36546
  char* new_connection_id_data = new_connection_id.mutable_data();
20
36546
  const char* old_connection_id_ptr = old_connection_id.data();
21
  // Override the first 4 bytes of the new CID to the original CID's first 4 bytes.
22
36546
  memcpy(new_connection_id_data, old_connection_id_ptr, 4); // NOLINT(safe-memcpy)
23
36546
}
24
} // namespace
25

            
26
absl::optional<quic::QuicConnectionId>
27
EnvoyDeterministicConnectionIdGenerator::GenerateNextConnectionId(
28
36545
    const quic::QuicConnectionId& original) {
29
36545
  auto new_cid = DeterministicConnectionIdGenerator::GenerateNextConnectionId(original);
30
36545
  if (new_cid.has_value()) {
31
36545
    adjustNewConnectionIdForRouting(new_cid.value(), original);
32
36545
  }
33
36545
  return (new_cid.has_value() && new_cid.value() == original) ? absl::nullopt : new_cid;
34
36545
}
35

            
36
absl::optional<quic::QuicConnectionId>
37
EnvoyDeterministicConnectionIdGenerator::MaybeReplaceConnectionId(
38
2942
    const quic::QuicConnectionId& original, const quic::ParsedQuicVersion& version) {
39
2942
  auto new_cid = DeterministicConnectionIdGenerator::MaybeReplaceConnectionId(original, version);
40
2942
  if (new_cid.has_value()) {
41
1
    adjustNewConnectionIdForRouting(new_cid.value(), original);
42
1
  }
43
2942
  return (new_cid.has_value() && new_cid.value() == original) ? absl::nullopt : new_cid;
44
2942
}
45

            
46
QuicConnectionIdGeneratorPtr
47
2172
EnvoyDeterministicConnectionIdGeneratorFactory::createQuicConnectionIdGenerator(uint32_t) {
48
2172
  return std::make_unique<EnvoyDeterministicConnectionIdGenerator>(
49
2172
      quic::kQuicDefaultConnectionIdLength);
50
2172
}
51

            
52
Network::Socket::OptionConstSharedPtr
53
EnvoyDeterministicConnectionIdGeneratorFactory::createCompatibleLinuxBpfSocketOption(
54
11
    uint32_t concurrency) {
55
11
#if defined(SO_ATTACH_REUSEPORT_CBPF) && defined(__linux__)
56
  // This BPF filter reads the 1st word of QUIC connection id in the UDP payload and mods it by the
57
  // number of workers to get the socket index in the SO_REUSEPORT socket groups. QUIC packets
58
  // should be at least 9 bytes, with the 1st byte indicating one of the below QUIC packet headers:
59
  // 1) IETF QUIC long header: most significant bit is 1. The connection id starts from the 7th
60
  // byte.
61
  // 2) IETF QUIC short header: most significant bit is 0. The connection id starts from 2nd
62
  // byte.
63
  // 3) Google QUIC header: most significant bit is 0. The connection id starts from 2nd
64
  // byte.
65
  // Any packet that doesn't belong to any of the three packet header types are dispatched
66
  // based on 5-tuple source/destination addresses.
67
  // SPELLCHECKER(off)
68
11
  filter_ = {
69
11
      {0x80, 0, 0, 0000000000}, //                   ld len
70
11
      {0x35, 0, 9, 0x00000009}, //                   jlt #0x9, packet_too_short
71
11
      {0x30, 0, 0, 0000000000}, //                   ldb [0]
72
11
      {0x54, 0, 0, 0x00000080}, //                   and #0x80
73
11
      {0x15, 0, 2, 0000000000}, //                   jne #0, ietf_long_header
74
11
      {0x20, 0, 0, 0x00000001}, //                   ld [1]
75
11
      {0x05, 0, 0, 0x00000005}, //                   ja return
76
11
      {0x80, 0, 0, 0000000000}, // ietf_long_header: ld len
77
11
      {0x35, 0, 2, 0x0000000e}, //                   jlt #0xe, packet_too_short
78
11
      {0x20, 0, 0, 0x00000006}, //                   ld [6]
79
11
      {0x05, 0, 0, 0x00000001}, //                   ja return
80
11
      {0x20, 0, 0,              // packet_too_short: ld rxhash
81
11
       static_cast<uint32_t>(SKF_AD_OFF + SKF_AD_RXHASH)},
82
11
      {0x94, 0, 0, concurrency}, // return:         mod #socket_count
83
11
      {0x16, 0, 0, 0000000000},  //                 ret a
84
11
  };
85
  // SPELLCHECKER(on)
86

            
87
  // Note that this option refers to the BPF program data above, which must live until the
88
  // option is used. The program is kept as a member variable for this purpose.
89
11
  prog_.len = filter_.size();
90
11
  prog_.filter = filter_.data();
91
11
  return std::make_shared<Network::SocketOptionImpl>(
92
11
      envoy::config::core::v3::SocketOption::STATE_BOUND, ENVOY_ATTACH_REUSEPORT_CBPF,
93
11
      absl::string_view(reinterpret_cast<char*>(&prog_), sizeof(prog_)));
94
#else
95
  UNREFERENCED_PARAMETER(concurrency);
96
  return nullptr;
97
#endif
98
11
}
99

            
100
static uint32_t bpfEquivalentFunction(const Buffer::Instance& packet, uint32_t concurrency,
101
637
                                      uint32_t default_value) {
102
  // This is a re-implementation of the same algorithm written in BPF in
103
  // createCompatibleLinuxBpfSocketOption
104
637
  const uint64_t packet_length = packet.length();
105
637
  if (packet_length < 9) {
106
2
    return default_value;
107
2
  }
108

            
109
635
  uint8_t first_octet;
110
635
  packet.copyOut(0, sizeof(first_octet), &first_octet);
111

            
112
635
  uint32_t connection_id_snippet;
113
635
  if (first_octet & 0x80) {
114
    // IETF QUIC long header.
115
    // The connection id starts from 7th byte.
116
    // Minimum length of a long header packet is 14.
117
233
    if (packet_length < 14) {
118
2
      return default_value;
119
2
    }
120

            
121
231
    packet.copyOut(6, sizeof(connection_id_snippet), &connection_id_snippet);
122
402
  } else {
123
    // IETF QUIC short header, or gQUIC.
124
    // The connection id starts from 2nd byte.
125
402
    packet.copyOut(1, sizeof(connection_id_snippet), &connection_id_snippet);
126
402
  }
127

            
128
633
  connection_id_snippet = htonl(connection_id_snippet);
129
633
  return connection_id_snippet % concurrency;
130
635
}
131

            
132
QuicConnectionIdWorkerSelector
133
EnvoyDeterministicConnectionIdGeneratorFactory::getCompatibleConnectionIdWorkerSelector(
134
2141
    uint32_t concurrency) {
135
2705
  return [concurrency](const Buffer::Instance& packet, uint32_t default_value) {
136
637
    return bpfEquivalentFunction(packet, concurrency, default_value);
137
637
  };
138
2141
}
139

            
140
} // namespace Quic
141
} // namespace Envoy