... | @@ -18,12 +18,16 @@ Each callout function has the same signature. It accepts a reference to a `Callo |
... | @@ -18,12 +18,16 @@ Each callout function has the same signature. It accepts a reference to a `Callo |
|
|
|
|
|
The `CalloutHandle` object provides two methods to get and set the arguments passed to the callout. These methods are called (naturally enough) `getArgument` and `setArgument`.
|
|
The `CalloutHandle` object provides two methods to get and set the arguments passed to the callout. These methods are called (naturally enough) `getArgument` and `setArgument`.
|
|
|
|
|
|
|
|
Callout handle stores the status of the hook execution. The status values are:
|
|
|
|
NEXT_STEP_CONTINUE (continue with the next hook), NEXT_STEP_SKIP (skip next hooks), NEXT_STEP_DROP (drop packet and skip next hooks), and NEXT_STEP_PARK.
|
|
|
|
|
|
The hook libraries provide their own log messages and logger instances.
|
|
The hook libraries provide their own log messages and logger instances.
|
|
|
|
|
|
The `LibraryManager` manages a single hook. It is responsible for calling the main hook functions. Additionally, it registers the callouts in the `HooksManager`. Available hook names are first recorded in the `HookManager` by the `*Hooks` structures. These structures contain the hook indices that are used to call the specific hook. The `LibraryManager` iterates over the hook names and checks if they are available in the shared library. If yes, the hook is added to the list in the `HooksManager`.
|
|
The `LibraryManager` manages a single hook. It is responsible for calling the main hook functions. Additionally, it registers the callouts in the `HooksManager`. Available hook names are first recorded in the `HookManager` by the `*Hooks` structures. These structures contain the hook indices that are used to call the specific hook. The `LibraryManager` iterates over the hook names and checks if they are available in the shared library. If yes, the hook is added to the list in the `HooksManager`.
|
|
|
|
|
|
The hooks are independent of the main Kea codebase. There is a single common module. It contains some constants, signatures of primary functions, and `CalloutHandle` class.
|
|
The hooks are independent of the main Kea codebase. There is a single common module. It contains some constants, signatures of primary functions, and `CalloutHandle` class.
|
|
|
|
|
|
|
|
The hook locations are specified in the Kea DHCPv4 daemon configuration and in the Kea Control Agent configuration.
|
|
### Strengths and weaknesses
|
|
### Strengths and weaknesses
|
|
|
|
|
|
The main advantage of the Kea design is a separation between the main codebase and hooks. It allows writing hook code without knowledge about the Kea itself. Kea also separates the log messages that making any troubleshooting easier. Another beneficial feature is the easy and bidirectional data exchange between core and hook.
|
|
The main advantage of the Kea design is a separation between the main codebase and hooks. It allows writing hook code without knowledge about the Kea itself. Kea also separates the log messages that making any troubleshooting easier. Another beneficial feature is the easy and bidirectional data exchange between core and hook.
|
... | @@ -37,4 +41,81 @@ There are three different ways to implement hooks in Golang. |
... | @@ -37,4 +41,81 @@ There are three different ways to implement hooks in Golang. |
|
2. Use [go-plugin](https://github.com/hashicorp/go-plugin) library
|
|
2. Use [go-plugin](https://github.com/hashicorp/go-plugin) library
|
|
3. Implement them as C extensions
|
|
3. Implement them as C extensions
|
|
|
|
|
|
We discussed a proper approach during a meeting in Berlin and decided to use the `plugin` module. It is a pretty new Go feature, but it seems that this method should be most similar to the Kea hook solution and cover all expected use cases. We are only unsure how to handle the UI with a hook based on the `plugin` module. We want to start with backend-only hooks and re-think this problem later. We don't exclude the possibility of implementing the hooks with GUI as separate services using `go-plugin` and RPC connection. We decided to avoid the third approach because we think the hooks should be written in Go if possible. |
|
We discussed a proper approach during a meeting in Berlin and decided to use the `plugin` module. It is a pretty new Go feature, but it seems that this method should be most similar to the Kea hook solution and cover all expected use cases. We are only unsure how to handle the UI with a hook based on the `plugin` module. We want to start with backend-only hooks and re-think this problem later. We don't exclude the possibility of implementing the hooks with GUI as separate services using `go-plugin` an RPC connection. We decided to avoid the third approach because we think the hooks should be written in Go if possible.
|
|
|
|
|
|
|
|
## Stork hook structure
|
|
|
|
|
|
|
|
The Kea hook design has been working for years. It seems to be reliable and flexible. We will try to avoid the drawbacks resulting from the specificity of the C++ language and take advantage of Go.
|
|
|
|
|
|
|
|
The `plugin` module provides a single `Open` function. It accepts the path and returns the plugin object. A specific plugin can be loaded only once, and there is no possibility to "close" the plugin.
|
|
|
|
|
|
|
|
The plugin object provides a single `Lookup` function. It accepts a string identifier and returns a plugin member if it exists. The caller is responsible for casting it to a valid type. The caller can search for functions, constants, variables, interfaces, structs, etc.
|
|
|
|
|
|
|
|
It is possible to implement the Kea hook structure in Go. We can search for `load`, `unload`, and `version` functions (`multi_threading_compatible` is not necessary) or any callout. Callouts can have the same signature as in Kea.
|
|
|
|
|
|
|
|
I think that we can use the same primary functions. Stork should search for plugins in a specific directory (it should be the same for all plugins) and load them on startup calling the `load` function if provided. During gracefully stopping the server, we can call the `unload` function too. The `version` function will be used to check the compatibility between a core and a plugin.
|
|
|
|
|
|
|
|
We also need the hook module that will be shared between the core and hooks.
|
|
|
|
|
|
|
|
However, we should redesign the callout signature. It should be strongly typed but not strongly related to the core codebase. The function shouldn't accept the `CalloutHandle` object and lookup in runtime for specific arguments. It should just take all needed arguments. The expected signature can be defined as a type in the hook module and used with an interface check in the hook implementation.
|
|
|
|
|
|
|
|
In the Kea layout, the hook is a set of callout functions. It means that we need to search each callout by name. We must share between the core and hook the type (function signature) and the identifier (function name). In Go, the hook can provide an object that will be tested for implementing expected hook interfaces. We don't need to use any identifiers.
|
|
|
|
|
|
|
|
Opened question is how to exchange data between hooks registered on the same callout. I think that the specific solution depends on the hook.
|
|
|
|
|
|
|
|
We probably need to implement something similar to the `LibraryManager` from Kea to manage the hooks. We also need the `HooksManager` to register and call the hook callouts.
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
classDiagram
|
|
|
|
class IHook{
|
|
|
|
<<interface>>
|
|
|
|
+ string Version()
|
|
|
|
+ error Load()
|
|
|
|
+ error Unload()
|
|
|
|
+ interface Callouts
|
|
|
|
}
|
|
|
|
|
|
|
|
class FooCallout{
|
|
|
|
<<interface>>
|
|
|
|
+ error Foo(int x)
|
|
|
|
}
|
|
|
|
|
|
|
|
class MyHook{
|
|
|
|
+ FooCallout Callouts
|
|
|
|
+ string Version()
|
|
|
|
+ error Load()
|
|
|
|
+ error Unload()
|
|
|
|
}
|
|
|
|
|
|
|
|
IHook <|.. MyHook
|
|
|
|
MyHook o.. FooCallout
|
|
|
|
|
|
|
|
HookModule *-- FooCallout
|
|
|
|
HookModule *-- IHook
|
|
|
|
|
|
|
|
class LibraryManager{
|
|
|
|
+ LM+error New(string path)$
|
|
|
|
+ error Load()
|
|
|
|
+ error Unload()
|
|
|
|
+ bool IsCompatible()
|
|
|
|
+ interface Callouts()
|
|
|
|
}
|
|
|
|
|
|
|
|
LibraryManager *-- IHook
|
|
|
|
|
|
|
|
class HookManager{
|
|
|
|
+ RegisterCallouts(interface)
|
|
|
|
+ error Foo(int x)
|
|
|
|
}
|
|
|
|
|
|
|
|
FooCallout <|.. HookManager
|
|
|
|
HookManager ..> LibraryManager
|
|
|
|
|
|
|
|
class HookLoader{
|
|
|
|
+ LoadAll()
|
|
|
|
+ UnloadAll()
|
|
|
|
}
|
|
|
|
|
|
|
|
HookLoader o-- LibraryManager
|
|
|
|
HookLoader *-- HookManager
|
|
|
|
``` |
|
|
|
\ No newline at end of file |