1
#pragma once
2

            
3
#include <filesystem>
4
#include <memory>
5
#include <string>
6

            
7
#include "absl/status/statusor.h"
8
#include "absl/strings/string_view.h"
9

            
10
namespace Envoy {
11
namespace Extensions {
12
namespace DynamicModules {
13

            
14
/**
15
 * A class for loading and managing dynamic modules. This corresponds to a single dlopen handle
16
 * (for dynamically loaded modules) or a statically-linked symbol namespace (for static modules).
17
 * When the DynamicModule object is destroyed, the dlopen handle is closed (if applicable).
18
 *
19
 * This class is supposed to be initialized once in the main thread and can be shared with other
20
 * threads.
21
 */
22
class DynamicModule {
23
public:
24
  // Constructor for a dynamically loaded module (via dlopen).
25
1026
  explicit DynamicModule(void* handle) : handle_(handle) {}
26
  // Constructor for a statically linked module. Symbols are resolved via dlsym(RTLD_DEFAULT)
27
  // with the module name used as a prefix: "<static_module_name>_<symbol>".
28
  explicit DynamicModule(absl::string_view static_module_name)
29
26
      : handle_(nullptr), static_module_name_(static_module_name) {}
30
  ~DynamicModule();
31

            
32
  /**
33
   * Get a function pointer from the dynamic module with a specific type.
34
   * @param T the function pointer type to cast the symbol to.
35
   * @param symbol_ref the symbol to look up.
36
   * @return the symbol if found, otherwise nullptr.
37
   */
38
  template <typename T>
39
15081
  absl::StatusOr<T> getFunctionPointer(const absl::string_view symbol_ref) const {
40
15081
    static_assert(std::is_pointer<T>::value &&
41
15081
                      std::is_function<typename std::remove_pointer<T>::type>::value,
42
15081
                  "T must be a function pointer type");
43
15081
    auto symbol = getSymbol(symbol_ref);
44
15081
    if (symbol == nullptr) {
45
1029
      return absl::InvalidArgumentError("Failed to resolve symbol " + std::string(symbol_ref));
46
1029
    }
47
14052
    return reinterpret_cast<T>(symbol);
48
15081
  }
49

            
50
private:
51
  /**
52
   * Get a symbol from the dynamic module.
53
   * @param symbol_ref the symbol to look up.
54
   * @return the symbol if found, otherwise nullptr.
55
   */
56
  void* getSymbol(const absl::string_view symbol_ref) const;
57

            
58
  // The raw dlopen handle that can be used to look up symbols. nullptr for static modules.
59
  void* handle_;
60
  // Non-empty for statically linked modules. When set, getSymbol() looks up
61
  // "<static_module_name>_<symbol>" via dlsym(RTLD_DEFAULT) instead of using handle_.
62
  const std::string static_module_name_;
63
};
64

            
65
using DynamicModulePtr = std::unique_ptr<DynamicModule>;
66

            
67
/**
68
 * Creates a new DynamicModule. This is mainly exposed for testing purposes. Use
69
 * newDynamicModuleByName in wiring up dynamic modules.
70
 * @param object_file_absolute_path the absolute path to the object file to load.
71
 * @param do_not_close if true, the dlopen will be called with RTLD_NODELETE, so the loaded object
72
 * will not be destroyed. This is useful when an object has some global state that should not be
73
 * terminated. For example, c-shared objects compiled by Go doesn't support dlclose
74
 * https://github.com/golang/go/issues/11100.
75
 * @param load_globally if true, the dlopen will be called with RTLD_GLOBAL, so the loaded object
76
 * can share symbols with other dynamically loaded modules. This is useful for modules that need to
77
 * share symbols with other modules.
78
 */
79
absl::StatusOr<DynamicModulePtr>
80
newDynamicModule(const std::filesystem::path& object_file_absolute_path, const bool do_not_close,
81
                 const bool load_globally = false);
82

            
83
/**
84
 * Creates a new DynamicModule by name under the search path specified by the environment variable
85
 * `DYNAMIC_MODULES_SEARCH_PATH`. The file name is assumed to be `lib<module_name>.so`.
86
 * This is mostly a wrapper around newDynamicModule.
87
 * @param module_name the name of the module to load. If the symbol
88
 * ``<module_name>_envoy_dynamic_module_on_program_init`` is found in the process binary, the
89
 * module is treated as statically linked and newStaticModule is called instead of loading a
90
 * shared object. In that case, do_not_close and load_globally are ignored.
91
 * @param do_not_close if true, the dlopen will be called with RTLD_NODELETE, so the loaded object
92
 * will not be destroyed. This is useful when an object has some global state that should not be
93
 * terminated.
94
 * @param load_globally if true, the dlopen will be called with RTLD_GLOBAL, so the loaded object
95
 * can share symbols with other dynamically loaded modules. This is useful for modules that need to
96
 * share symbols with other modules.
97
 */
98
absl::StatusOr<DynamicModulePtr> newDynamicModuleByName(const absl::string_view module_name,
99
                                                        const bool do_not_close,
100
                                                        const bool load_globally = false);
101

            
102
/**
103
 * Creates a new DynamicModule backed by symbols already present in the process binary (i.e.,
104
 * statically linked). No shared object file is loaded. Instead, symbols are resolved via
105
 * dlsym(RTLD_DEFAULT, ...) with the module name used as a prefix:
106
 * "<module_name>_<symbol_name>".
107
 * @param module_name the module name used to build the symbol prefix.
108
 */
109
absl::StatusOr<DynamicModulePtr> newStaticModule(const absl::string_view module_name);
110

            
111
/**
112
 * Creates a new DynamicModule from the given module bytes. The module is expected to be in ELF
113
 * format and the bytes should be exactly the same as the content of the shared object file. The
114
 * bytes are written to a temporary file and loaded via dlopen.
115
 * @param module_bytes the content of the shared object file.
116
 * @param sha256 the sha256 hash of the module bytes, used for temp file naming. The caller is
117
 * responsible for verifying the hash before calling this function.
118
 * @param do_not_close if true, the dlopen will be called with RTLD_NODELETE, so the loaded object
119
 * will not be destroyed. This is useful when an object has some global state that should not be
120
 * terminated.
121
 * @param load_globally if true, the dlopen will be called with RTLD_GLOBAL, so the loaded object
122
 * can share symbols with other dynamically loaded modules. This is useful for modules that need to
123
 * share symbols with other modules.
124
 */
125
absl::StatusOr<DynamicModulePtr> newDynamicModuleFromBytes(const absl::string_view module_bytes,
126
                                                           const absl::string_view sha256,
127
                                                           const bool do_not_close,
128
                                                           const bool load_globally = false);
129

            
130
/**
131
 * Returns the canonical temporary file path for a remote module identified by its SHA256 hash.
132
 * This is the path where newDynamicModuleFromBytes writes the module and where the cache looks
133
 * it up.
134
 * @param sha256 the hex-encoded SHA256 hash of the module bytes.
135
 */
136
std::filesystem::path moduleTempPath(absl::string_view sha256);
137

            
138
} // namespace DynamicModules
139
} // namespace Extensions
140
} // namespace Envoy