1
#include "cilium/proxylib.h"
2

            
3
#include <dlfcn.h>
4
#include <fmt/format.h>
5

            
6
#include <cstdint>
7
#include <memory>
8
#include <string>
9

            
10
#include "envoy/buffer/buffer.h"
11
#include "envoy/common/exception.h"
12
#include "envoy/network/connection.h"
13

            
14
#include "source/common/common/assert.h"
15
#include "source/common/common/logger.h"
16
#include "source/common/protobuf/protobuf.h" // IWYU pragma: keep
17

            
18
#include "absl/container/fixed_array.h"
19
#include "proxylib/types.h"
20

            
21
namespace Envoy {
22
namespace Cilium {
23

            
24
GoFilter::GoFilter(const std::string& go_module,
25
33
                   const Protobuf::Map<::std::string, ::std::string>& params) {
26
33
  ENVOY_LOG(info, "GoFilter: Opening go module {}", go_module);
27
33
  ::dlerror(); // clear any possible error state
28
33
  go_module_handle_ = ::dlopen(go_module.c_str(), RTLD_NOW);
29
33
  if (!go_module_handle_) {
30
    throw EnvoyException(
31
        fmt::format("cilium.network: Cannot load go module \'{}\': {}", go_module, dlerror()));
32
  }
33

            
34
33
  go_close_module_ = GoCloseModuleCB(::dlsym(go_module_handle_, "CloseModule"));
35
33
  if (!go_close_module_) {
36
    throw EnvoyException(fmt::format("cilium.network: Cannot find symbol \'CloseModule\' from "
37
                                     "module \'{}\': {}",
38
                                     go_module, dlerror()));
39
  }
40
33
  GoOpenModuleCB go_open_module = GoOpenModuleCB(::dlsym(go_module_handle_, "OpenModule"));
41
33
  if (!go_open_module) {
42
    throw EnvoyException(fmt::format("cilium.network: Cannot find symbol \'OpenModule\' from "
43
                                     "module \'{}\': {}",
44
                                     go_module, dlerror()));
45
33
  } else {
46
    // Convert params to KeyValue pairs
47
33
    auto num = params.size();
48
33
    absl::FixedArray<GoStringPair> values(num);
49

            
50
33
    int i = 0;
51
33
    for (const auto& pair : params) {
52
6
      values[i].key = GoString(pair.first);
53
6
      values[i++].value = GoString(pair.second);
54
6
    }
55

            
56
33
    go_module_id_ =
57
33
        go_open_module(GoKeyValueSlice(values.data(), num), ENVOY_LOG_CHECK_LEVEL(debug));
58
33
    if (go_module_id_ == 0) {
59
      throw EnvoyException(
60
          fmt::format("cilium.network: \'{}::OpenModule()\' rejected parameters", go_module));
61
    }
62
33
  }
63

            
64
33
  go_on_new_connection_ = GoOnNewConnectionCB(::dlsym(go_module_handle_, "OnNewConnection"));
65
33
  if (!go_on_new_connection_) {
66
    throw EnvoyException(fmt::format("cilium.network: Cannot find symbol \'OnNewConnection\' "
67
                                     "from module \'{}\': {}",
68
                                     go_module, dlerror()));
69
  }
70
33
  go_on_data_ = GoOnDataCB(::dlsym(go_module_handle_, "OnData"));
71
33
  if (!go_on_data_) {
72
    throw EnvoyException(
73
        fmt::format("cilium.network: Cannot find symbol \'OnData\' from module \'{}\': {}",
74
                    go_module, dlerror()));
75
  }
76
33
  go_close_ = GoCloseCB(::dlsym(go_module_handle_, "Close"));
77
33
  if (!go_close_) {
78
    throw EnvoyException(
79
        fmt::format("cilium.network: Cannot find symbol \'Close\' from module \'{}\': {}",
80
                    go_module, dlerror()));
81
  }
82
33
}
83

            
84
33
GoFilter::~GoFilter() {
85
33
  if (go_module_id_ != 0) {
86
33
    go_close_module_(go_module_id_);
87
33
  }
88
33
  if (go_module_handle_) {
89
33
    ::dlclose(go_module_handle_);
90
33
  }
91
33
}
92

            
93
GoFilter::InstancePtr GoFilter::newInstance(Network::Connection& conn, const std::string& go_proto,
94
                                            bool ingress, uint32_t src_id, uint32_t dst_id,
95
                                            const std::string& src_addr,
96
                                            const std::string& dst_addr,
97
18
                                            const std::string& policy_name) const {
98
18
  InstancePtr parser{nullptr};
99
18
  if (go_module_handle_) {
100
18
    parser = std::make_unique<Instance>(*this, conn);
101
18
    ENVOY_CONN_LOG(trace, "GoFilter: Calling go module", conn);
102
18
    auto res = (*go_on_new_connection_)(
103
18
        go_module_id_, go_proto, conn.id(), ingress, src_id, dst_id, src_addr, dst_addr,
104
18
        policy_name, &parser->orig_.inject_slice_, &parser->reply_.inject_slice_);
105
18
    if (res == FILTER_OK) {
106
18
      parser->connection_id_ = conn.id();
107
18
    } else {
108
      ENVOY_CONN_LOG(warn, "Cilium Network: Connection with parser \"{}\" rejected: {}", conn,
109
                     go_proto, toString(res));
110
      parser.reset(nullptr);
111
    }
112
18
  }
113
18
  return parser;
114
18
}
115

            
116
89
FilterResult GoFilter::Instance::onIo(bool reply, Buffer::Instance& data, bool end_stream) {
117
89
  auto& dir = reply ? reply_ : orig_;
118
89
  int64_t data_len = data.length();
119

            
120
  // Pass bytes based on an earlier verdict?
121
89
  if (dir.pass_bytes_ > 0) {
122
2
    ASSERT(dir.drop_bytes_ == 0);      // Can't drop and pass the same bytes
123
2
    ASSERT(dir.buffer_.length() == 0); // Passed data is not buffered
124
2
    ASSERT(dir.need_bytes_ == 0);      // Passed bytes can't be needed
125
    // Can return immediately if passing more that we have input.
126
    // May need to process injected data even when there is no input left.
127
2
    if (dir.pass_bytes_ > data_len) {
128
1
      if (data_len > 0) {
129
1
        ENVOY_CONN_LOG(debug, "Cilium Network::OnIO: Passing all input: {} bytes: {} ", conn_,
130
1
                       data_len, data.toString());
131
1
        dir.pass_bytes_ -= data_len;
132
1
      }
133
1
      return FILTER_OK; // all of 'data' is passed to the next filter
134
1
    }
135
    // Pass of dir.pass_bytes_ is done after buffer rearrangement below.
136
    // Using the available APIs it is easier to move data from the beginning of
137
    // a buffer to another rather than from the end of a buffer to another.
138
87
  } else {
139
    // Drop bytes based on an earlier verdict?
140
87
    if (dir.drop_bytes_ > 0) {
141
      ASSERT(dir.buffer_.length() == 0); // Dropped data is not buffered
142
      ASSERT(dir.need_bytes_ == 0);      // Dropped bytes can't be needed
143
      // Can return immediately if passing more that we have input.
144
      // May need to process injected data even when there is no input left.
145
      if (dir.drop_bytes_ > data_len) {
146
        if (data_len > 0) {
147
          ENVOY_CONN_LOG(debug, "Cilium Network::OnIO: Dropping all input: {} bytes: {} ", conn_,
148
                         data_len, data.toString());
149
          dir.drop_bytes_ -= data_len;
150
          data.drain(data_len);
151
        }
152
        return FILTER_OK; // everything was dropped, nothing more to be done
153
      }
154
      ENVOY_CONN_LOG(debug, "Cilium Network::OnIO: Dropping first {} bytes of input: {}", conn_,
155
                     dir.drop_bytes_, data.toString());
156
      data.drain(dir.drop_bytes_);
157
      dir.drop_bytes_ = 0;
158
      // At frame boundary, more data may remain
159
    }
160
87
  }
161

            
162
  // Move data to the end of the input buffer, use 'data' as the output buffer
163
88
  dir.buffer_.move(data);
164
88
  ASSERT(data.length() == 0);
165
88
  auto& input = dir.buffer_;
166
88
  int64_t input_len = input.length();
167
88
  auto& output = data;
168

            
169
  // Move pre-passed input to output.
170
  // Note that the case of all new input being passed is already taken care of
171
  // above.
172
88
  if (dir.pass_bytes_ > 0) {
173
1
    ENVOY_CONN_LOG(debug, "Cilium Network::OnIO: Passing first {} bytes of input: {}", conn_,
174
1
                   input_len, input.toString());
175
1
    output.move(input, dir.pass_bytes_);
176
1
    input_len -= dir.pass_bytes_;
177
1
    dir.pass_bytes_ = 0;
178
    // At frame boundary, more data may remain
179
1
  }
180

            
181
  // Output now at frame boundary, output frame(s) injected by the reverse
182
  // direction first
183
88
  if (dir.inject_slice_.len() > 0) {
184
7
    ENVOY_CONN_LOG(
185
7
        debug, "Cilium Network::OnIO: Reverse Injecting: {} bytes: {} ", conn_,
186
7
        dir.inject_slice_.len(),
187
7
        std::string(reinterpret_cast<char*>(dir.inject_slice_.data_), dir.inject_slice_.len()));
188
7
    output.add(dir.inject_slice_.data_, dir.inject_slice_.len());
189
7
    dir.inject_slice_.reset();
190
7
  }
191

            
192
  // Do nothing if we don't have enough input (partial input remains buffered)
193
88
  if (input_len < dir.need_bytes_) {
194
    return FILTER_OK;
195
  }
196
88
  dir.need_bytes_ = 0;
197

            
198
88
  const int max_ops = 16; // Make shorter for testing purposes
199
88
  FilterOp ops[max_ops];
200
88
  GoFilterOpSlice op_slice(ops, max_ops);
201

            
202
88
  FilterResult res;
203
88
  bool terminal_op_seen = false;
204
88
  bool inject_buf_exhausted = false;
205

            
206
92
  do {
207
92
    op_slice.reset();
208
92
    Buffer::RawSliceVector raw_slices = input.getRawSlices();
209

            
210
92
    int64_t total_length = 0;
211
92
    absl::FixedArray<GoSlice<uint8_t>> buffer_slices(raw_slices.size());
212
92
    uint64_t non_empty_slices = 0;
213
9644
    for (const Buffer::RawSlice& raw_slice : raw_slices) {
214
9644
      if (raw_slice.len_ > 0) {
215
9644
        buffer_slices[non_empty_slices++] =
216
9644
            GoSlice<uint8_t>(reinterpret_cast<uint8_t*>(raw_slice.mem_), raw_slice.len_);
217
9644
        total_length += raw_slice.len_;
218
9644
      }
219
9644
    }
220
92
    GoDataSlices input_slices(buffer_slices.begin(), non_empty_slices);
221

            
222
92
    ENVOY_CONN_LOG(trace, "Cilium Network::OnIO: Calling go module with {} bytes of data", conn_,
223
92
                   total_length);
224
92
    res = (*parent_.go_on_data_)(connection_id_, reply, end_stream, &input_slices, &op_slice);
225
92
    ENVOY_CONN_LOG(trace, "Cilium Network::OnIO: \'go_on_data\' returned {}, ops({})", conn_,
226
92
                   toString(res), op_slice.len());
227
92
    if (res == FILTER_OK) {
228
      // Process all returned filter operations.
229
160
      for (int i = 0; i < op_slice.len(); i++) {
230
68
        auto op = ops[i].op;
231
68
        auto n_bytes = ops[i].n_bytes;
232

            
233
68
        if (n_bytes == 0) {
234
          ENVOY_CONN_LOG(warn, "Cilium Network::OnIO: INVALID op ({}) length: {} bytes", conn_, op,
235
                         n_bytes);
236
          return FILTER_PARSER_ERROR;
237
        }
238

            
239
68
        if (terminal_op_seen) {
240
          ENVOY_CONN_LOG(warn,
241
                         "Cilium Network::OnIO: Filter operation {} after "
242
                         "terminal operation.",
243
                         conn_, op);
244
          return FILTER_PARSER_ERROR;
245
        }
246

            
247
68
        switch (op) {
248
2
        case FILTEROP_MORE:
249
2
          ENVOY_CONN_LOG(debug, "Cilium Network::OnIO: FILTEROP_MORE: {} bytes", conn_, n_bytes);
250
2
          dir.need_bytes_ = input_len + n_bytes;
251
2
          terminal_op_seen = true; // MORE can not be followed with other ops.
252
2
          continue;                // errors out if more operations follow
253

            
254
39
        case FILTEROP_PASS:
255
39
          ENVOY_CONN_LOG(debug, "Cilium Network::OnIO: FILTEROP_PASS: {} bytes", conn_, n_bytes);
256
39
          if (n_bytes > input_len) {
257
1
            output.move(input, input_len);
258
1
            dir.pass_bytes_ = n_bytes - input_len; // pass the remainder later
259
1
            input_len = 0;
260
1
            terminal_op_seen = true; // PASS more than input is terminal operation.
261
1
            continue;                // errors out if more operations follow
262
1
          }
263
38
          output.move(input, n_bytes);
264
38
          input_len -= n_bytes;
265
38
          break;
266

            
267
20
        case FILTEROP_DROP:
268
20
          ENVOY_CONN_LOG(debug, "Cilium Network::OnIO: FILTEROP_DROP: {} bytes", conn_, n_bytes);
269
20
          if (n_bytes > input_len) {
270
            input.drain(input_len);
271
            dir.drop_bytes_ = n_bytes - input_len; // drop the remainder later
272
            input_len = 0;
273
            terminal_op_seen = true; // DROP more than input is terminal operation.
274
            continue;                // errors out if more operations follow
275
          }
276
20
          input.drain(n_bytes);
277
20
          input_len -= n_bytes;
278
20
          break;
279

            
280
7
        case FILTEROP_INJECT:
281
7
          if (n_bytes > dir.inject_slice_.len()) {
282
            ENVOY_CONN_LOG(warn,
283
                           "Cilium Network::OnIO: FILTEROP_INJECT: INVALID "
284
                           "length: {} bytes",
285
                           conn_, n_bytes);
286
            return FILTER_PARSER_ERROR;
287
          }
288
7
          ENVOY_CONN_LOG(debug, "Cilium Network::OnIO: FILTEROP_INJECT: {} bytes: {}", conn_,
289
7
                         n_bytes,
290
7
                         std::string(reinterpret_cast<char*>(dir.inject_slice_.data_),
291
7
                                     dir.inject_slice_.len()));
292
7
          output.add(dir.inject_slice_.data_, n_bytes);
293
7
          dir.inject_slice_.drain(n_bytes);
294
7
          break;
295

            
296
        case FILTEROP_ERROR:
297
        default:
298
          ENVOY_CONN_LOG(warn, "Cilium Network::OnIO: FILTEROP_ERROR: {} bytes", conn_, n_bytes);
299
          return FILTER_PARSER_ERROR;
300
68
        }
301
68
      }
302
92
    } else {
303
      // Close the connection an any error
304
      ENVOY_CONN_LOG(warn, "Cilium Network::OnIO: FILTER_POLICY_DROP {}", conn_, toString(res));
305
      return FILTER_PARSER_ERROR;
306
    }
307

            
308
92
    if (dir.inject_slice_.len() > 0) {
309
      ENVOY_CONN_LOG(warn, "Cilium Network::OnIO: {} bytes abandoned in inject buffer", conn_,
310
                     dir.inject_slice_.len());
311
      return FILTER_PARSER_ERROR;
312
    }
313

            
314
92
    inject_buf_exhausted = dir.inject_slice_.atCapacity();
315

            
316
    // Make space for more injected data
317
92
    dir.inject_slice_.reset();
318

            
319
    // Loop back if ops or inject buffer was exhausted
320
92
  } while (!terminal_op_seen && (op_slice.len() == max_ops || inject_buf_exhausted));
321

            
322
88
  if (output.length() < 100) {
323
76
    ENVOY_CONN_LOG(debug, "Cilium Network::OnIO: Output on return: {}", conn_, output.toString());
324
76
  } else {
325
12
    ENVOY_CONN_LOG(debug, "Cilium Network::OnIO: Output length return: {}", conn_, output.length());
326
12
  }
327
88
  return res;
328
88
}
329

            
330
void GoFilter::Instance::close() {
331
  (*parent_.go_close_)(connection_id_);
332
  connection_id_ = 0;
333
  conn_.close(Network::ConnectionCloseType::FlushWrite);
334
}
335

            
336
} // namespace Cilium
337
} // namespace Envoy