Compare commits
153 Commits
Author | SHA1 | Date |
---|---|---|
|
12d8e63d34 | |
|
d608d7282e | |
|
3fdac75fd0 | |
|
b7bb92fafe | |
|
8f53f05a62 | |
|
7c1d5695b4 | |
|
f5474a80f2 | |
|
69176a7e5d | |
|
a202a120f9 | |
|
6ac3b4df0a | |
|
c61e9d1502 | |
|
85cf8fd4c4 | |
|
f412a34986 | |
|
cf6f8ab52e | |
|
3b45fdb759 | |
|
e6d0e188aa | |
|
32963fb3d2 | |
|
d861afb22d | |
|
167ec5a390 | |
|
e69d2950eb | |
|
13ebd8dc96 | |
|
fda10fc439 | |
|
7e92b1b241 | |
|
5a1efd0257 | |
|
7955010144 | |
|
faca7745ba | |
|
024430afc9 | |
|
e6084751c5 | |
|
ede33484c2 | |
|
40ff9af243 | |
|
98fd1a5dcd | |
|
2eb4a3106b | |
|
9130ff4b53 | |
|
c36a0e0259 | |
|
64aa8490f4 | |
|
64ea3ace4e | |
|
9cd2769c2f | |
|
c43c0044a1 | |
|
1a38f04582 | |
|
18133edb5c | |
|
9a9dc4288a | |
|
7b2cafbb8d | |
|
401e4d02e0 | |
|
ace67303af | |
|
76e31c63c5 | |
|
fc6f7fb3d1 | |
|
38733055ee | |
|
5e5da37395 | |
|
be201d72ac | |
|
4c944ba0f0 | |
|
33a5a80a44 | |
|
20d960fdb4 | |
|
2eb0c61922 | |
|
fe10de21ed | |
|
979fc36c78 | |
|
f1de2c9de8 | |
|
be971a90dd | |
|
079755064b | |
|
55e9d809dc | |
|
13612c304e | |
|
85b034dc52 | |
|
b68cf68689 | |
|
8beb7b8c68 | |
|
0aa96151a6 | |
|
452df6bdaf | |
|
24a8aaee77 | |
|
255177d4b7 | |
|
95d8cfcd97 | |
|
13b461eb01 | |
|
6fdfd31005 | |
|
aa8e5cecdd | |
|
60a09e9ff8 | |
|
662472b510 | |
|
c2c8a3e406 | |
|
5885ebe25a | |
|
bd1eead4c4 | |
|
3352b0e7f4 | |
|
260e4c2e40 | |
|
3856dc775c | |
|
fb108925c7 | |
|
a08c418573 | |
|
708bddb8e3 | |
|
6e54b98fd2 | |
|
4a87bc0875 | |
|
9284cdd78a | |
|
b02934cd13 | |
|
b379176355 | |
|
762eb74602 | |
|
a4c30f50f8 | |
|
a77b721858 | |
|
e218676637 | |
|
f0e52f41e5 | |
|
8ddc3e60e0 | |
|
a4054b2145 | |
|
a9641798a7 | |
|
6e353465d5 | |
|
0cef56eb4d | |
|
0bced90a4b | |
|
0bcfd9ac2e | |
|
6af38c0097 | |
|
1a68f1d870 | |
|
15e357b403 | |
|
da26bfe0af | |
|
0ff3c4fa25 | |
|
fdb16582e3 | |
|
cbda00c9b0 | |
|
da96fe2b4b | |
|
1b012329af | |
|
0019846812 | |
|
5547197dd1 | |
|
427a73df73 | |
|
bfedf4fef7 | |
|
c87fafb7b5 | |
|
720eb0f2fe | |
|
6add1712bc | |
|
42bf90c6de | |
|
998a463f92 | |
|
34708af9b2 | |
|
c7d7e44ce8 | |
|
416a70eb0c | |
|
0cbaec92ab | |
|
631d982958 | |
|
0585caa7f5 | |
|
c7787c37b4 | |
|
6544689759 | |
|
1fb248977c | |
|
0970d86437 | |
|
d746f574bb | |
|
284e7b9d1d | |
|
411a968e46 | |
|
44e16dbca2 | |
|
110cbffae0 | |
|
57707475a0 | |
|
c5a1c394b3 | |
|
f708ba2ba2 | |
|
e2cd16cfed | |
|
861958a693 | |
|
6b1dd54645 | |
|
747f907a1c | |
|
853aa99b0d | |
|
c70f6c3b3c | |
|
9dc5ec24f9 | |
|
c6151a6114 | |
|
53442a6880 | |
|
c92d1f080d | |
|
8342a01846 | |
|
22fd2f8003 | |
|
8980412ec4 | |
|
a44b5a012d | |
|
6bdf85842c | |
|
dfb65dbf16 | |
|
479e6474a7 | |
|
cd28e07c0b |
|
@ -1,18 +0,0 @@
|
|||
version: 2.1
|
||||
workflows:
|
||||
main:
|
||||
jobs:
|
||||
- release:
|
||||
context: falco
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /v[0-9]+(\.[0-9]+)*(-.*)*/
|
||||
jobs:
|
||||
release:
|
||||
docker:
|
||||
- image: circleci/golang:1.17
|
||||
steps:
|
||||
- checkout
|
||||
- run: curl -sL https://git.io/goreleaser | bash
|
|
@ -0,0 +1,22 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 10
|
||||
groups:
|
||||
gomod:
|
||||
update-types:
|
||||
- "patch"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 10
|
||||
groups:
|
||||
actions:
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
|
@ -0,0 +1,37 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
run-tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout plugin-sdk-go
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: '^1.19'
|
||||
|
||||
- name: Run tests
|
||||
run: go test ./...
|
||||
|
||||
build-example-plugins:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout plugin-sdk-go
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: '^1.19'
|
||||
|
||||
- name: Build all example plugins
|
||||
run: make examples
|
|
@ -1,22 +1,12 @@
|
|||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main ]
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
- cron: '45 5 * * 2'
|
||||
|
||||
|
@ -32,40 +22,21 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
language:
|
||||
- 'go'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
uses: github/codeql-action/autobuild@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.17
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
|
||||
with:
|
||||
args: release --clean --timeout 60m
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -1 +1,2 @@
|
|||
*~
|
||||
.vscode
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
project_name: plugin-sdk-go
|
||||
build:
|
||||
skip: true
|
||||
version: 2
|
||||
|
||||
builds:
|
||||
- skip: true
|
||||
|
||||
release:
|
||||
github:
|
||||
github: {}
|
||||
prerelease: auto
|
||||
|
|
36
Makefile
36
Makefile
|
@ -1,5 +1,6 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright (C) 2021 The Falco Authors.
|
||||
# Copyright (C) 2025 The Falco Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
|
@ -12,28 +13,39 @@
|
|||
#
|
||||
SHELL := /bin/bash
|
||||
GO ?= $(shell which go)
|
||||
CURL = curl
|
||||
CURL ?= $(shell which curl)
|
||||
PATCH ?= $(shell which patch)
|
||||
|
||||
FALCOSECURITY_LIBS_REVISION ?= e25e44b3ba4cb90ba9ac75bf747978e41fb6b221
|
||||
FALCOSECURITY_LIBS_REVISION ?= 0.21.0
|
||||
FALCOSECURITY_LIBS_REPO ?= falcosecurity/libs
|
||||
|
||||
PLUGIN_INFO_DIR=pkg/sdk
|
||||
PLUGIN_INFO_URL=https://raw.githubusercontent.com/${FALCOSECURITY_LIBS_REPO}/${FALCOSECURITY_LIBS_REVISION}/userspace/libscap/plugin_info.h
|
||||
PLUGINLIB_URL=https://raw.githubusercontent.com/${FALCOSECURITY_LIBS_REPO}/${FALCOSECURITY_LIBS_REVISION}/userspace/plugin
|
||||
|
||||
examples_dir = $(shell ls -d examples/*/ | cut -f2 -d'/' | xargs)
|
||||
examples_build = $(addprefix example-,$(examples_dir))
|
||||
examples_clean = $(addprefix clean-example-,$(examples_dir))
|
||||
|
||||
.PHONY: all
|
||||
all: plugin_info examples
|
||||
all: pluginlib examples
|
||||
|
||||
.PHONY: clean
|
||||
clean: $(examples_clean)
|
||||
@rm -f $(PLUGIN_INFO_DIR)/plugin_info.h
|
||||
clean: clean-pluginlib $(examples_clean)
|
||||
|
||||
.PHONY: plugin_info
|
||||
plugin_info:
|
||||
@$(CURL) -Lso $(PLUGIN_INFO_DIR)/plugin_info.h $(PLUGIN_INFO_URL)
|
||||
.PHONY: pluginlib
|
||||
pluginlib:
|
||||
$(CURL) -Lso pkg/sdk/plugin_types.h $(PLUGINLIB_URL)/plugin_types.h
|
||||
$(CURL) -Lso pkg/sdk/plugin_api.h $(PLUGINLIB_URL)/plugin_api.h
|
||||
$(CURL) -Lso pkg/loader/plugin_loader.h $(PLUGINLIB_URL)/plugin_loader.h
|
||||
$(CURL) -Lso pkg/loader/plugin_loader.c $(PLUGINLIB_URL)/plugin_loader.c
|
||||
$(PATCH) -p1 < pkg/loader/plugin_api_include.patch
|
||||
$(PATCH) -p1 < pkg/loader/strlcpy.patch
|
||||
$(PATCH) -p1 < pkg/sdk/plugin_types_include.patch
|
||||
|
||||
clean-pluginlib:
|
||||
rm -f \
|
||||
pkg/sdk/plugin_types.h \
|
||||
pkg/sdk/plugin_api.h \
|
||||
pkg/loader/plugin_loader.h \
|
||||
pkg/loader/plugin_loader.c
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
|
|
13
OWNERS
13
OWNERS
|
@ -1,16 +1,9 @@
|
|||
approvers:
|
||||
- leogr
|
||||
- jasondellaluce
|
||||
emeritus_approvers:
|
||||
- leodido
|
||||
- fntlnz
|
||||
- kris-nova
|
||||
- mstemm
|
||||
- leogr
|
||||
- ldegio
|
||||
- jasondellaluce
|
||||
reviewers:
|
||||
- leodido
|
||||
- fntlnz
|
||||
- kris-nova
|
||||
- mstemm
|
||||
- leogr
|
||||
- ldegio
|
||||
- jasondellaluce
|
||||
|
|
18
README.md
18
README.md
|
@ -1,12 +1,10 @@
|
|||
# plugin-sdk-go
|
||||
|
||||
[](https://github.com/falcosecurity/evolution/blob/main/REPOSITORIES.md#core-scope) [](https://github.com/falcosecurity/evolution/blob/main/REPOSITORIES.md#stable) [](./LICENSE)
|
||||
|
||||
[](https://pkg.go.dev/github.com/falcosecurity/plugin-sdk-go/pkg/sdk)
|
||||
[](https://github.com/falcosecurity/plugin-sdk-go/releases/latest)
|
||||
[](https://goreportcard.com/report/github.com/falcosecurity/plugin-sdk-go)
|
||||
[](LICENSE)
|
||||
|
||||
|
||||
Note: *The plugin system is a new feature introduced since Falco 0.31.0. You can find more detail in the original [proposal document](https://github.com/falcosecurity/falco/blob/master/proposals/20210501-plugin-system.md).*
|
||||
|
||||
## Introduction
|
||||
|
||||
|
@ -14,18 +12,18 @@ This SDK facilitates writing [plugins](https://falco.org/docs/plugins) for [Falc
|
|||
|
||||
## Quick start
|
||||
|
||||
Before using this SDK, review the [developer's guide](https://falco.org/docs/plugins/developers_guide/) which fully documents the API and provides best practices for writing plugins. The developer's guide includes a [walkthrough](https://falco.org/docs/plugins/developers_guide/#example-go-plugin-dummy) of a plugin written in Go that uses this package.
|
||||
Before using this SDK, review the [developer's guide](https://falco.org/docs/plugins/developers_guide/) which fully documents the API and provides best practices for writing plugins. The developer's guide includes a [walkthrough](https://falco.org/docs/plugins/go-sdk-walkthrough/#example-go-plugin-dummy) of a plugin written in Go that uses this package.
|
||||
|
||||
For a quick start, you can refer to the provided examples:
|
||||
- [extractor plugin](https://github.com/falcosecurity/plugin-sdk-go/tree/main/examples/extractor)
|
||||
- [source plugin](https://github.com/falcosecurity/plugin-sdk-go/tree/main/examples/source)
|
||||
- [source plugin with extraction](https://github.com/falcosecurity/plugin-sdk-go/tree/main/examples/full)
|
||||
- [plugin with field extraction](https://github.com/falcosecurity/plugin-sdk-go/tree/main/examples/extractor)
|
||||
- [plugin with event sourcing](https://github.com/falcosecurity/plugin-sdk-go/tree/main/examples/source)
|
||||
- [plugin with both event sourcing and field extraction](https://github.com/falcosecurity/plugin-sdk-go/tree/main/examples/full)
|
||||
|
||||
|
||||
|
||||
## What's next
|
||||
|
||||
When ready to release your plugin, make sure to register the plugin with the Falcosecurity organization by creating a PR to the [falcosecurity/plugins](https://github.com/falcosecurity/plugins) respository with details on the new plugin. This ensures that a given ID is used by exactly one source plugin, and allows source plugin authors and extractor plugin authors to coordinate about event source formats.
|
||||
When ready to release your plugin, make sure to register the plugin with the Falcosecurity organization by creating a PR to the [falcosecurity/plugins](https://github.com/falcosecurity/plugins) respository with details on the new plugin. This ensures that a given ID is used by exactly one plugin with event sourcing capability, and allows authors of plugins with field extraction capability to coordinate about event source formats.
|
||||
|
||||
## Join the Community
|
||||
|
||||
|
@ -43,7 +41,7 @@ See the [CONTRIBUTING.md](https://github.com/falcosecurity/.github/blob/master/C
|
|||
|
||||
## Security Audit
|
||||
|
||||
A third party security audit was performed by Cure53, you can see the full report [here](./audits/SECURITY_AUDIT_2019_07.pdf).
|
||||
A third party security audit was performed by Cure53, you can see the full report [here](https://github.com/falcosecurity/falco/blob/master/audits/SECURITY_AUDIT_2019_07.pdf).
|
||||
|
||||
## Reporting security vulnerabilities
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
build
|
|
@ -0,0 +1,30 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright (C) 2023 The Falco Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations under the License.
|
||||
#
|
||||
|
||||
SHELL =/bin/bash -o pipefail
|
||||
GO ?= go
|
||||
NAME := bench
|
||||
OUTPUTDIR := build
|
||||
OUTPUT := $(OUTPUTDIR)/bench
|
||||
OUTPUTGO := $(OUTPUTDIR)/libgo$(NAME).a
|
||||
|
||||
all: $(OUTPUT)
|
||||
|
||||
clean:
|
||||
@rm -fr $(OUTPUTDIR)
|
||||
|
||||
$(OUTPUT): bench.cpp bench.go
|
||||
mkdir -p $(OUTPUTDIR)
|
||||
GODEBUG=cgocheck=1 $(GO) build -buildmode=c-archive -o $(OUTPUTGO) bench.go
|
||||
$(CXX) bench.cpp $(OUTPUTGO) -std=c++11 -pthread -o $(OUTPUT)
|
|
@ -0,0 +1,28 @@
|
|||
# Async Extraction Benchmark
|
||||
|
||||
This little program is a benchmarking tool to measure the performance of the async extraction optimization (see: [pkg/sdk/symbols/extract](https://github.com/falcosecurity/plugin-sdk-go/tree/main/pkg/sdk/symbols/extract)).
|
||||
|
||||
### Usage
|
||||
|
||||
```
|
||||
Usage: bench [options]
|
||||
Options:
|
||||
-h, --help Print this usage snippet.
|
||||
-a, --async Run the benchmark by enabling the async extraction optimization (default: off).
|
||||
-n <number> The number of extraction requests performed in the benchmark (default: 10000).
|
||||
-p <number> The number of plugins that run the benchmark in parallel (default: 1).
|
||||
```
|
||||
|
||||
### Example
|
||||
```
|
||||
> ./build/bench -n 100000 -a
|
||||
plugin 1: 251.21 ns/extraction (elapsed time 25121098ns, extractions 100000)
|
||||
```
|
||||
|
||||
### Description
|
||||
|
||||
The benchmark is implemented in C language, whereas the extraction function is implemented in Go by using the Plugin SDK Go. This is achieved by implementing a mock plugin using the SDK, then building it in `c-archive` mode, and then linking the resulting binary with the C code. The end result is a C executable that is able to call the symbols of the C plugin API, such as `plugin_init` and `plugin_extract_fields` (which are the ones we need to perform the benchmark in this case).
|
||||
|
||||
The goal here is to have a real use case estimation of how costly the C -> Go function calls are when the async worker optimization is enabled or disabled. This can't be achieved with the Go benchmarking tools, because the way the Go runtime behaves when built as `c-archive` and `c-shared` might influence the performance results. You can find a Go benchmark for this in https://github.com/falcosecurity/plugin-sdk-go/tree/main/pkg/sdk/symbols/extract/internal/asyncbench.
|
||||
|
||||
**NOTE**: this allows running multiple benchmarks in parallel by using the same shared Go code. This is unsafe with the current async extraction implementation, because it assumes a single-caller-single-worker execution model. However, this feature might become useful in the future one we support parallelized plugin code execution (see point **(B3)** of https://github.com/falcosecurity/falco/issues/2074).
|
|
@ -0,0 +1,175 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#define __STDC_FORMAT_MACROS
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "../../pkg/sdk/plugin_api.h"
|
||||
|
||||
// defined in Go and exported from bench.go
|
||||
extern "C"
|
||||
{
|
||||
void plugin_destroy(ss_plugin_t*);
|
||||
ss_plugin_t* plugin_init(const ss_plugin_init_input *input, ss_plugin_rc *rc);
|
||||
ss_plugin_rc plugin_extract_fields(ss_plugin_t*, const ss_plugin_event_input*, const ss_plugin_field_extract_input*);
|
||||
}
|
||||
|
||||
// global benchmark options
|
||||
static int g_parallelism;
|
||||
static int g_niterations;
|
||||
static bool g_use_async;
|
||||
|
||||
static void print_help()
|
||||
{
|
||||
printf(
|
||||
"Usage: bench [options]\n\n"
|
||||
"Options:\n"
|
||||
" -h, --help Print this usage snippet.\n"
|
||||
" -a, --async Run the benchmark by enabling the async extraction optimization (default: off).\n"
|
||||
" -n <number> The number of extraction requests performed in the benchmark (default: 10000).\n"
|
||||
" -p <number> The number of plugins that run the benchmark in parallel (default: 1).\n");
|
||||
}
|
||||
|
||||
static void parse_options(int argc, char** argv)
|
||||
{
|
||||
g_parallelism = 1;
|
||||
g_niterations = 10000;
|
||||
g_use_async = false;
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
auto arg = std::string(argv[i]);
|
||||
if (arg == "-h" || arg == "--help" )
|
||||
{
|
||||
print_help();
|
||||
exit(0);
|
||||
}
|
||||
else if (arg == "-a" || arg == "--async" )
|
||||
{
|
||||
g_use_async = true;
|
||||
}
|
||||
else if (arg == "-p" || arg == "-n")
|
||||
{
|
||||
int tmp;
|
||||
i++;
|
||||
if (i >= argc)
|
||||
{
|
||||
fprintf(stderr, "option '%s' requires a parameter\n", arg.c_str());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
tmp = atoi(argv[i]);
|
||||
if (tmp <= 0)
|
||||
{
|
||||
fprintf(stderr, "option '%s' parameter must be a positive integer\n", arg.c_str());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (arg == "-p")
|
||||
{
|
||||
g_parallelism = tmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_niterations = tmp;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "unrecognized option '%s'\n", argv[i]);
|
||||
print_help();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void benchmark(ss_plugin_t *plugin) noexcept
|
||||
{
|
||||
// craft a mock extract request
|
||||
ss_plugin_extract_field e;
|
||||
e.field_id = 0;
|
||||
e.field = "sample.field";
|
||||
e.arg_present = false;
|
||||
e.ftype = FTYPE_UINT64;
|
||||
e.flist = false;
|
||||
ss_plugin_field_extract_input in;
|
||||
in.fields = &e;
|
||||
in.num_fields = 1;
|
||||
|
||||
// request multiple extractions and compute total execution time
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
ss_plugin_rc rc = SS_PLUGIN_FAILURE;
|
||||
for (int i = 0; i < g_niterations; i++)
|
||||
{
|
||||
rc = plugin_extract_fields(plugin, NULL, &in);
|
||||
if (rc != SS_PLUGIN_SUCCESS)
|
||||
{
|
||||
fprintf(stderr, "plugin %" PRIu64 ": plugin_extract_fields failure: %d\n", (uint64_t) plugin, rc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// print stats summary
|
||||
auto time_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start);
|
||||
printf("plugin %" PRIu64 ": %.02f ns/extraction (elapsed time %" PRIu64 "ns, extractions %d)\n",
|
||||
(uint64_t) plugin,
|
||||
(double) time_ns.count() / (double) (g_niterations),
|
||||
(uint64_t) time_ns.count(),
|
||||
g_niterations);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
// parse user options
|
||||
parse_options(argc, argv);
|
||||
|
||||
// initialize plugins and launch a benchmark for each of them in parallel
|
||||
std::vector<std::thread> threads;
|
||||
std::vector<ss_plugin_t*> plugins;
|
||||
for (int i = 0; i < g_parallelism; ++i)
|
||||
{
|
||||
ss_plugin_rc rc = SS_PLUGIN_FAILURE;
|
||||
ss_plugin_init_input in;
|
||||
in.config = g_use_async ? "async" : "";
|
||||
plugins.push_back(plugin_init(&in, &rc));
|
||||
if (rc != SS_PLUGIN_SUCCESS)
|
||||
{
|
||||
fprintf(stderr, "can't initialize plugin");
|
||||
exit(1);
|
||||
}
|
||||
threads.push_back(std::thread(benchmark, plugins[i]));
|
||||
}
|
||||
|
||||
// wait for all banchmarks to finish and destroy plugins
|
||||
for (int i = 0; i < g_parallelism; ++i)
|
||||
{
|
||||
if (threads[i].joinable())
|
||||
{
|
||||
threads[i].join();
|
||||
}
|
||||
plugin_destroy(plugins[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This is not a real plugin. The goal of this is to implement few
|
||||
// plugin_* API symbols by leveraging the SDK Go, so that we can use
|
||||
// the to execute the benchmark. This code is compiled as a c-archive
|
||||
// and linked into a C executable so that each symbol will be a C -> Go call,
|
||||
// thus simulating what really happens in the plugin framework.
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins/extractor"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/extract"
|
||||
)
|
||||
|
||||
type MockPlugin struct {
|
||||
plugins.BasePlugin
|
||||
}
|
||||
|
||||
func init() {
|
||||
plugins.SetFactory(func() plugins.Plugin {
|
||||
p := &MockPlugin{}
|
||||
extractor.Register(p)
|
||||
return p
|
||||
})
|
||||
}
|
||||
|
||||
func (m *MockPlugin) Info() *plugins.Info {
|
||||
return &plugins.Info{}
|
||||
}
|
||||
|
||||
func (m *MockPlugin) Fields() []sdk.FieldEntry {
|
||||
return []sdk.FieldEntry{}
|
||||
}
|
||||
|
||||
// note: we enable/disable the async extraction optimization depending on the
|
||||
// passed-in config
|
||||
func (m *MockPlugin) Init(config string) error {
|
||||
extract.SetAsync(config == "async")
|
||||
return nil
|
||||
}
|
||||
|
||||
// note: we do nothing here, we're just interested in measuring the cost of
|
||||
// calling this function from C
|
||||
func (m *MockPlugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// not used but required to build this as a c-archive
|
||||
func main() {}
|
|
@ -0,0 +1,7 @@
|
|||
module github.com/falcosecurity/plugin-sdk-go/benchmarks/async
|
||||
|
||||
replace github.com/falcosecurity/plugin-sdk-go => ../../
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/falcosecurity/plugin-sdk-go v0.0.0-00010101000000-000000000000
|
|
@ -0,0 +1,22 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -1,5 +1,6 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright (C) 2021 The Falco Authors.
|
||||
# Copyright (C) 2023 The Falco Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
|
@ -23,4 +24,4 @@ clean:
|
|||
@rm -f *.so *.h
|
||||
|
||||
$(OUTPUT): *.go
|
||||
@GODEBUG=cgocheck=2 $(GO) build -buildmode=c-shared -o $(OUTPUT)
|
||||
@GODEBUG=cgocheck=1 $(GO) build -buildmode=c-shared -o $(OUTPUT)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -14,8 +15,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This plugin is an advanced example of source plugin with the optional
|
||||
// extraction capabilities. Unlike the other examples, this does not make use
|
||||
// This plugin is an advanced example of both event sourcing and field extraction
|
||||
// capabilities. Unlike the other examples, this does not make use
|
||||
// on the high-level constructs of the sdk/plugins package, but instead
|
||||
// it directly uses the low-level sdk/symbols packages. This approach is
|
||||
// more complex and generally discouraged, but it can be useful in case a
|
||||
|
@ -38,14 +39,12 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
// Each one of these imported package provide a SDK prebuilt implementation
|
||||
// of some exported C symbols needed by the framework. The _ notation near
|
||||
// some inputs is necessary to avoid Go linters to remove the package if
|
||||
// unused.
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/cgo"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/ptr"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins"
|
||||
_ "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/extract"
|
||||
|
@ -95,11 +94,9 @@ func init() {
|
|||
// its type (source or extractor).
|
||||
info.SetId(999)
|
||||
info.SetName("custom-example")
|
||||
info.SetDescription("A Source Plugin Example")
|
||||
info.SetDescription("A Plugin Example")
|
||||
info.SetContact("github.com/falcosecurity/plugin-sdk-go")
|
||||
info.SetVersion("0.1.0")
|
||||
info.SetRequiredAPIVersion("0.2.0")
|
||||
info.SetType(sdk.TypeSourcePlugin)
|
||||
info.SetEventSource("example")
|
||||
|
||||
// Define an initialization callback
|
||||
|
@ -135,7 +132,7 @@ func OnOpen(params string) (sdk.InstanceState, error) {
|
|||
// with the other SDk prebuilt symbols system.
|
||||
//
|
||||
//export plugin_event_to_string
|
||||
func plugin_event_to_string(pState C.uintptr_t, data *C.uint8_t, datalen uint32) *C.char {
|
||||
func plugin_event_to_string(pState C.uintptr_t, evt C.uintptr_t) *C.char {
|
||||
// The prebuilt SDK symbols store the plugin state as a handle of the
|
||||
// SDK cgo.Handle. As such, using it is required to be compliant with the
|
||||
// other imported prebuilt symbols.
|
||||
|
@ -144,30 +141,17 @@ func plugin_event_to_string(pState C.uintptr_t, data *C.uint8_t, datalen uint32)
|
|||
// Our plugin state has a reusable buffer for the event_to_string method
|
||||
buffer := pHandle.Value().(sdk.StringerBuffer).StringerBuffer()
|
||||
|
||||
// We use ptr.BytesReadWriter to safely accessing C-allocated memory
|
||||
bytesReader, err := ptr.NewBytesReadWriter(unsafe.Pointer(data), int64(datalen), int64(datalen))
|
||||
if err != nil {
|
||||
buffer.Write(err.Error())
|
||||
} else {
|
||||
// Read the string written in the event data using io funtions
|
||||
bytes, err := ioutil.ReadAll(bytesReader)
|
||||
if err != nil {
|
||||
buffer.Write(err.Error())
|
||||
} else {
|
||||
// Set the string as return value by writing it in the
|
||||
// reusable buffer
|
||||
buffer.Write(string(bytes))
|
||||
}
|
||||
}
|
||||
buffer.Write("test")
|
||||
|
||||
// Extract a char* pointer from the reusable buffer and use it as
|
||||
// the return value.
|
||||
return (*C.char)(buffer.CharPtr())
|
||||
}
|
||||
|
||||
// This method is optional for source plugins, and enables the extraction
|
||||
// capabilities. This is required and called by the prebuilt
|
||||
// plugin_extract_fields C symbol.
|
||||
// This method is mandatory for the field extraction capability.
|
||||
// This is required and called by the prebuilt plugin_extract_fields C symbol.
|
||||
func (p *MyPlugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) error {
|
||||
bytes, err := ioutil.ReadAll(evt.Reader())
|
||||
if err != nil {
|
||||
|
@ -226,7 +210,7 @@ func (i *MyInstance) NextBatch(pState sdk.PluginState, evts sdk.EventWriters) (i
|
|||
// plugin can assume that it to be always be well-formed when passed to Init().
|
||||
// This is ignored if the return value is nil. The returned schema must follow
|
||||
// the JSON Schema specific. See: https://json-schema.org/
|
||||
// This method is optional for extractor plugins.
|
||||
// This method is optional.
|
||||
// func (m *MyPlugin) InitSchema() *sdk.SchemaInfo {
|
||||
//
|
||||
// }
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -1,5 +1,6 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright (C) 2021 The Falco Authors.
|
||||
# Copyright (C) 2023 The Falco Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
|
@ -23,4 +24,4 @@ clean:
|
|||
@rm -f *.so *.h
|
||||
|
||||
$(OUTPUT): *.go
|
||||
@GODEBUG=cgocheck=2 $(GO) build -buildmode=c-shared -o $(OUTPUT)
|
||||
@GODEBUG=cgocheck=1 $(GO) build -buildmode=c-shared -o $(OUTPUT)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -14,7 +15,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This plugin is a simple example of extractor plugin.
|
||||
// This plugin is a simple example of plugin with field extraction capability.
|
||||
// The plugin extracts the "example.ts" field from the "example" event source,
|
||||
// which simply represents the timestamp of the extraction.
|
||||
package main
|
||||
|
@ -45,12 +46,16 @@ type MyPlugin struct {
|
|||
// interface, so compilation will fail if the mandatory methods are not
|
||||
// implemented.
|
||||
func init() {
|
||||
extractor.Register(&MyPlugin{})
|
||||
plugins.SetFactory(func() plugins.Plugin {
|
||||
p := &MyPlugin{}
|
||||
extractor.Register(p)
|
||||
return p
|
||||
})
|
||||
}
|
||||
|
||||
// Info returns a pointer to a plugin.Info struct, containing all the
|
||||
// general information about this plugin.
|
||||
// This method is mandatory for extractor plugins.
|
||||
// This method is mandatory.
|
||||
func (m *MyPlugin) Info() *plugins.Info {
|
||||
return &plugins.Info{
|
||||
ID: 999,
|
||||
|
@ -63,13 +68,13 @@ func (m *MyPlugin) Info() *plugins.Info {
|
|||
}
|
||||
|
||||
// Init initializes this plugin with a given config string, which is unused
|
||||
// in this example. This method is mandatory for extractor plugins.
|
||||
// in this example. This method is mandatory.
|
||||
func (m *MyPlugin) Init(config string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fields return the list of extractor fields exported by this plugin.
|
||||
// This method is mandatory for extractor plugins.
|
||||
// This method is mandatory for the field extraction capability.
|
||||
func (m *MyPlugin) Fields() []sdk.FieldEntry {
|
||||
return []sdk.FieldEntry{
|
||||
{Type: "uint64", Name: "example.ts", Display: "Current Timestamp", Desc: "The current timestamp"},
|
||||
|
@ -77,7 +82,7 @@ func (m *MyPlugin) Fields() []sdk.FieldEntry {
|
|||
}
|
||||
|
||||
// Extract extracts the value of a single field from a given event data.
|
||||
// This method is mandatory for extractor plugins.
|
||||
// This method is mandatory for the field extraction capability.
|
||||
func (m *MyPlugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) error {
|
||||
switch req.FieldID() {
|
||||
case 0:
|
||||
|
@ -90,7 +95,7 @@ func (m *MyPlugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) error {
|
|||
|
||||
// Destroy is gets called by the SDK when the plugin gets deinitialized.
|
||||
// This is useful to release any open resource used by the plugin.
|
||||
// This method is optional for extractor plugins.
|
||||
// This method is optional.
|
||||
// func (m *MyPlugin) Destroy() {
|
||||
//
|
||||
// }
|
||||
|
@ -102,7 +107,7 @@ func (m *MyPlugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) error {
|
|||
// plugin can assume that it to be always be well-formed when passed to Init().
|
||||
// This is ignored if the return value is nil. The returned schema must follow
|
||||
// the JSON Schema specific. See: https://json-schema.org/
|
||||
// This method is optional for extractor plugins.
|
||||
// This method is optional.
|
||||
// func (m *MyPlugin) InitSchema() *sdk.SchemaInfo {
|
||||
//
|
||||
// }
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -1,5 +1,6 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright (C) 2021 The Falco Authors.
|
||||
# Copyright (C) 2023 The Falco Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
|
@ -23,4 +24,4 @@ clean:
|
|||
@rm -f *.so *.h
|
||||
|
||||
$(OUTPUT): *.go
|
||||
@GODEBUG=cgocheck=2 $(GO) build -buildmode=c-shared -o $(OUTPUT)
|
||||
@GODEBUG=cgocheck=1 $(GO) build -buildmode=c-shared -o $(OUTPUT)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -14,8 +15,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This plugin is a simple example of source plugin with the optional
|
||||
// extraction capabilities.
|
||||
// This plugin is a simple example of plugin with both event sourcing and
|
||||
// field extraction capabilities.
|
||||
// The plugin produces events of the "example" data source containing
|
||||
// a single uint64 representing the incrementing value of a counter,
|
||||
// serialized using a encoding/gob encoder. The plugin is capable of
|
||||
|
@ -28,7 +29,7 @@ import (
|
|||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/jsonschema"
|
||||
|
@ -56,6 +57,7 @@ type MyPluginConfig struct {
|
|||
type MyPlugin struct {
|
||||
plugins.BasePlugin
|
||||
config MyPluginConfig
|
||||
initTime time.Time
|
||||
}
|
||||
|
||||
// Defining a type for the plugin source capture instances returned by Open().
|
||||
|
@ -74,20 +76,23 @@ type MyInstance struct {
|
|||
|
||||
// The plugin must be registered to the SDK in the init() function.
|
||||
// Registering the plugin using both source.Register and extractor.Register
|
||||
// declares to the SDK a source plugin with the optional extraction features
|
||||
// declares to the SDK a plugin with both sourcing and extraction features
|
||||
// enabled. The order in which the two Register functions are called is not
|
||||
// relevant, as the SDK induces that the registered plugin is a source plugin.
|
||||
// relevant.
|
||||
// This requires our plugin to implement the source.Plugin interface, so
|
||||
// compilation will fail if the mandatory methods are not implemented.
|
||||
func init() {
|
||||
plugins.SetFactory(func() plugins.Plugin {
|
||||
p := &MyPlugin{}
|
||||
extractor.Register(p)
|
||||
source.Register(p)
|
||||
extractor.Register(p)
|
||||
return p
|
||||
})
|
||||
}
|
||||
|
||||
// Info returns a pointer to a plugin.Info struct, containing all the
|
||||
// general information about this plugin.
|
||||
// This method is mandatory for source plugins.
|
||||
// This method is mandatory.
|
||||
func (m *MyPlugin) Info() *plugins.Info {
|
||||
return &plugins.Info{
|
||||
ID: 999,
|
||||
|
@ -106,7 +111,7 @@ func (m *MyPlugin) Info() *plugins.Info {
|
|||
// plugin can assume that it to be always be well-formed when passed to Init().
|
||||
// This is ignored if the return value is nil. The returned schema must follow
|
||||
// the JSON Schema specific. See: https://json-schema.org/
|
||||
// This method is optional for source plugins.
|
||||
// This method is optional.
|
||||
func (m *MyPlugin) InitSchema() *sdk.SchemaInfo {
|
||||
// We leverage the jsonschema package to autogenerate the
|
||||
// JSON Schema definition using reflection from our config struct.
|
||||
|
@ -123,8 +128,9 @@ func (m *MyPlugin) InitSchema() *sdk.SchemaInfo {
|
|||
// Since this plugin defines the InitSchema() method, we can assume
|
||||
// that the configuration is pre-validated by the framework and
|
||||
// always well-formed according to the provided schema.
|
||||
// This method is mandatory for source plugins.
|
||||
// This method is mandatory.
|
||||
func (m *MyPlugin) Init(config string) error {
|
||||
m.initTime = time.Now()
|
||||
// Deserialize the config json. Ignoring the error
|
||||
// and not validating the config values is possible
|
||||
// due to the schema defined through InitSchema(),
|
||||
|
@ -134,19 +140,26 @@ func (m *MyPlugin) Init(config string) error {
|
|||
}
|
||||
|
||||
// Fields return the list of extractor fields exported by this plugin.
|
||||
// This method is optional for source plugins, and enables the extraction
|
||||
// capabilities. If the Fields method is defined, the framework expects
|
||||
// an Extract method to be specified too.
|
||||
// This method is mandatory the field extraction capability.
|
||||
// If the Fields method is defined, the framework expects an Extract method
|
||||
// to be specified too.
|
||||
func (m *MyPlugin) Fields() []sdk.FieldEntry {
|
||||
return []sdk.FieldEntry{
|
||||
{Type: "uint64", Name: "example.count", Display: "Counter value", Desc: "Current value of the internal counter"},
|
||||
{Type: "string", Name: "example.countstr", Display: "Counter string value", Desc: "String represetation of current value of the internal counter"},
|
||||
{Type: "bool", Name: "example.oddcount", Display: "Counter value is odd", Desc: "True if the current value of the internal counter is an odd number"},
|
||||
{Type: "reltime", Name: "example.initduration", Display: "Time since init", Desc: "Time since the plugin was initialized"},
|
||||
{Type: "abstime", Name: "example.evttime", Display: "Event timestamp", Desc: "Event timestamp"},
|
||||
{Type: "ipaddr", Name: "example.ipv4addr", Display: "Sample IPv4 address", Desc: "A sample IPv4 address"},
|
||||
{Type: "ipaddr", Name: "example.ipv6addr", Display: "Sample IPv6 address", Desc: "A sample IPv6 address"},
|
||||
{Type: "ipnet", Name: "example.ipv4net", Display: "Sample IPv4 network", Desc: "A sample IPv4 network"},
|
||||
{Type: "ipnet", Name: "example.ipv6net", Display: "Sample IPv6 network", Desc: "A sample IPv6 network"},
|
||||
}
|
||||
}
|
||||
|
||||
// This method is optional for source plugins, and enables the extraction
|
||||
// capabilities. If the Extract method is defined, the framework expects
|
||||
// an Fields method to be specified too.
|
||||
// This method is mandatory the field extraction capability.
|
||||
// If the Extract method is defined, the framework expects an Fields method
|
||||
// to be specified too.
|
||||
func (m *MyPlugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) error {
|
||||
var value uint64
|
||||
encoder := gob.NewDecoder(evt.Reader())
|
||||
|
@ -154,20 +167,52 @@ func (m *MyPlugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) error {
|
|||
return err
|
||||
}
|
||||
|
||||
switch req.FieldID() {
|
||||
case 0:
|
||||
switch req.Field() {
|
||||
case "example.count":
|
||||
req.SetValue(value)
|
||||
return nil
|
||||
case 1:
|
||||
case "example.countstr":
|
||||
req.SetValue(fmt.Sprintf("%d", value))
|
||||
return nil
|
||||
case "example.oddcount":
|
||||
req.SetValue((value%2 == 1))
|
||||
return nil
|
||||
case "example.initduration":
|
||||
req.SetValue(time.Since(m.initTime))
|
||||
return nil
|
||||
case "example.evttime":
|
||||
req.SetValue(time.Unix(0, int64(evt.Timestamp())))
|
||||
return nil
|
||||
case "example.ipv4addr":
|
||||
req.SetValue(net.IPv4allsys.To4())
|
||||
return nil
|
||||
case "example.ipv6addr":
|
||||
req.SetValue(net.IPv6loopback)
|
||||
return nil
|
||||
case "example.ipv4net":
|
||||
_, n, err := net.ParseCIDR("192.0.2.1/24")
|
||||
if err == nil {
|
||||
req.SetValue(n)
|
||||
} else {
|
||||
println(err.Error())
|
||||
}
|
||||
return nil
|
||||
case "example.ipv6net":
|
||||
_, n, err := net.ParseCIDR("2002::1234:abcd:ffff:c0a8:101/64")
|
||||
if err == nil {
|
||||
req.SetValue(n)
|
||||
} else {
|
||||
println(err.Error())
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unsupported field: %s", req.Field())
|
||||
}
|
||||
}
|
||||
|
||||
// OpenParams returns a list of suggested parameters that would be accepted
|
||||
// as valid arguments to Open(). This method is optional for source plugins.
|
||||
// as valid arguments to Open().
|
||||
// This method is optional for the event sourcing capability.
|
||||
func (m *MyPlugin) OpenParams() ([]sdk.OpenParam, error) {
|
||||
return []sdk.OpenParam{
|
||||
{
|
||||
|
@ -179,7 +224,7 @@ func (m *MyPlugin) OpenParams() ([]sdk.OpenParam, error) {
|
|||
|
||||
// Open opens the plugin source and starts a new capture session (e.g. stream
|
||||
// of events), creating a new plugin instance. The state of each instance can
|
||||
// be initialized here. This method is mandatory for source plugins.
|
||||
// be initialized here. This method is mandatory for the event sourcing capability.
|
||||
func (m *MyPlugin) Open(params string) (source.Instance, error) {
|
||||
// An event batch buffer can optionally be defined to specify custom
|
||||
// values for max data size or max batch size. If nothing is set
|
||||
|
@ -201,10 +246,11 @@ func (m *MyPlugin) Open(params string) (source.Instance, error) {
|
|||
}
|
||||
|
||||
// String produces a string representation of an event data produced by the
|
||||
// event source of this plugin. This method is mandatory for source plugins.
|
||||
func (m *MyPlugin) String(in io.ReadSeeker) (string, error) {
|
||||
// event source of this plugin.
|
||||
// This method is optional for the event sourcing capability.
|
||||
func (m *MyPlugin) String(evt sdk.EventReader) (string, error) {
|
||||
var value uint64
|
||||
encoder := gob.NewDecoder(in)
|
||||
encoder := gob.NewDecoder(evt.Reader())
|
||||
if err := encoder.Decode(&value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -212,7 +258,8 @@ func (m *MyPlugin) String(in io.ReadSeeker) (string, error) {
|
|||
}
|
||||
|
||||
// NextBatch produces a batch of new events, and is called repeatedly by the
|
||||
// framework. For source plugins, it's mandatory to specify a NextBatch method.
|
||||
// framework. For plugins with event sourcing capability, it's mandatory to
|
||||
// specify a NextBatch method.
|
||||
// The batch has a maximum size that dependes on the size of the underlying
|
||||
// reusable memory buffer. A batch can be smaller than the maximum size.
|
||||
func (m *MyInstance) NextBatch(pState sdk.PluginState, evts sdk.EventWriters) (int, error) {
|
||||
|
@ -232,21 +279,21 @@ func (m *MyInstance) NextBatch(pState sdk.PluginState, evts sdk.EventWriters) (i
|
|||
|
||||
// Progress returns a percentage indicator referring to the production progress
|
||||
// of the event source of this plugin.
|
||||
// This method is optional for source plugins.
|
||||
// This method is optional for the event sourcing capability.
|
||||
// func (m *MyInstance) Progress(pState sdk.PluginState) (float64, string) {
|
||||
//
|
||||
// }
|
||||
|
||||
// Close is gets called by the SDK when the plugin source capture gets closed.
|
||||
// This is useful to release any open resource used by each plugin instance.
|
||||
// This method is optional for source plugins.
|
||||
// This method is optional for the event sourcing capability.
|
||||
// func (m *MyInstance) Close() {
|
||||
//
|
||||
// }
|
||||
|
||||
// Destroy is gets called by the SDK when the plugin gets deinitialized.
|
||||
// This is useful to release any open resource used by the plugin.
|
||||
// This method is optional for source plugins.
|
||||
// This method is optional.
|
||||
// func (m *MyPlugin) Destroy() {
|
||||
//
|
||||
// }
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright (C) 2023 The Falco Authors.
|
||||
#
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
- rule: Sample plugin rule
|
||||
desc: Some sample rule for testing
|
||||
condition:
|
||||
evt.num > 0 and evt.num < 5
|
||||
and example.count > 0
|
||||
and example.oddcount = true
|
||||
and example.initduration exists
|
||||
and example.evttime exists
|
||||
and example.ipv4addr = "224.0.0.1"
|
||||
and example.ipv6addr = "::1"
|
||||
and example.ipv4net = "192.0.3.1/16"
|
||||
and example.ipv6net = "2002::1234:abcd:ffff:c0a8:102:ffff/32"
|
||||
output: Some event (
|
||||
example.count=%example.count,
|
||||
example.countstr=%example.countstr,
|
||||
example.oddcount=%example.oddcount,
|
||||
example.initduration=%example.initduration,
|
||||
example.evttime=%example.evttime,
|
||||
evt.time=%evt.rawtime,
|
||||
example.ipv4addr=%example.ipv4addr,
|
||||
example.ipv6addr=%example.ipv6addr,
|
||||
example.ipv4net=%example.ipv4net,
|
||||
example.ipv6net=%example.ipv6net
|
||||
info=%evt.plugininfo
|
||||
plugin=%evt.pluginname)
|
||||
priority: CRITICAL
|
||||
source: example
|
|
@ -5,6 +5,6 @@ replace github.com/falcosecurity/plugin-sdk-go => ../../
|
|||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/alecthomas/jsonschema v0.0.0-20211228220459-151e3c21f49d
|
||||
github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b
|
||||
github.com/falcosecurity/plugin-sdk-go v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
|
|
|
@ -1,11 +1,27 @@
|
|||
github.com/alecthomas/jsonschema v0.0.0-20211228220459-151e3c21f49d h1:4BQNwS4T13UU3Yee4GfzZH3Q9SNpKeJvLigfw8fDjX0=
|
||||
github.com/alecthomas/jsonschema v0.0.0-20211228220459-151e3c21f49d/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b h1:doCpXjVwui6HUN+xgNsNS3SZ0/jUZ68Eb+mJRNOZfog=
|
||||
github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk=
|
||||
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709 h1:Ko2LQMrRU+Oy/+EDBwX7eZ2jp3C47eDBB8EIhKTun+I=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright (C) 2021 The Falco Authors.
|
||||
# Copyright (C) 2023 The Falco Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
|
@ -23,5 +24,5 @@ clean:
|
|||
@rm -f *.so *.h
|
||||
|
||||
$(OUTPUT): *.go
|
||||
@GODEBUG=cgocheck=2 $(GO) build -buildmode=c-shared -o $(OUTPUT)
|
||||
@GODEBUG=cgocheck=1 $(GO) build -buildmode=c-shared -o $(OUTPUT)
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -14,16 +15,19 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This plugin is a simple example of source plugin.
|
||||
// This plugin is a simple example of plugin with event sourcing capability.
|
||||
// The plugin produces events of the "example" data source containing
|
||||
// a single uint64 representing the incrementing value of a counter,
|
||||
// serialized using a encoding/gob encoder.
|
||||
// This plugin makes use of the SDK-provided "pull" source instance to
|
||||
// open the event source, so we'll not provide a type implementation of
|
||||
// the source.Instance interface here
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
|
||||
|
@ -44,33 +48,22 @@ type MyPlugin struct {
|
|||
config string
|
||||
}
|
||||
|
||||
// Defining a type for the plugin source capture instances returned by Open().
|
||||
// Multiple instances of the same plugin can be opened for different capture
|
||||
// sessions.
|
||||
//
|
||||
// Composing the struct with plugins.BaseInstance is the recommended practice
|
||||
// as it provides the boilerplate code that satisfies most of the interface
|
||||
// requirements of the SDK.
|
||||
//
|
||||
// State variables to store in each plugin instance must be defined here.
|
||||
// In this example, we store the internal value of the incrementing counter.
|
||||
type MyInstance struct {
|
||||
source.BaseInstance
|
||||
counter uint64
|
||||
}
|
||||
|
||||
// The plugin must be registered to the SDK in the init() function.
|
||||
// The source.Register function initializes our plugin as an source
|
||||
// plugin. This requires our plugin to implement the source.Plugin
|
||||
// interface, so compilation will fail if the mandatory methods are not
|
||||
// implemented.
|
||||
func init() {
|
||||
source.Register(&MyPlugin{})
|
||||
plugins.SetFactory(func() plugins.Plugin {
|
||||
p := &MyPlugin{}
|
||||
source.Register(p)
|
||||
return p
|
||||
})
|
||||
}
|
||||
|
||||
// Info returns a pointer to a plugin.Info struct, containing all the
|
||||
// general information about this plugin.
|
||||
// This method is mandatory for source plugins.
|
||||
// This method is mandatory.
|
||||
func (m *MyPlugin) Info() *plugins.Info {
|
||||
return &plugins.Info{
|
||||
ID: 999,
|
||||
|
@ -83,65 +76,44 @@ func (m *MyPlugin) Info() *plugins.Info {
|
|||
}
|
||||
|
||||
// Init initializes this plugin with a given config string, which is unused
|
||||
// in this example. This method is mandatory for source plugins.
|
||||
// in this example. This method is mandatory.
|
||||
func (m *MyPlugin) Init(config string) error {
|
||||
m.config = config
|
||||
return nil
|
||||
}
|
||||
|
||||
// Open opens the plugin source and starts a new capture session (e.g. stream
|
||||
// of events), creating a new plugin instance. The state of each instance can
|
||||
// be initialized here. This method is mandatory for source plugins.
|
||||
// of events). This uses the SDK built-in source.NewPullInstance() function
|
||||
// that allows creating an event source by simply providing a event-generating
|
||||
// callback. This method is mandatory for the event sourcing capability.
|
||||
func (m *MyPlugin) Open(params string) (source.Instance, error) {
|
||||
return &MyInstance{
|
||||
counter: 0,
|
||||
}, nil
|
||||
counter := uint64(0)
|
||||
pull := func(ctx context.Context, evt sdk.EventWriter) error {
|
||||
counter++
|
||||
if err := gob.NewEncoder(evt.Writer()).Encode(counter); err != nil {
|
||||
return err
|
||||
}
|
||||
evt.SetTimestamp(uint64(time.Now().UnixNano()))
|
||||
return nil
|
||||
}
|
||||
return source.NewPullInstance(pull)
|
||||
}
|
||||
|
||||
// String produces a string representation of an event data produced by the
|
||||
// event source of this plugin. This method is mandatory for source plugins.
|
||||
func (m *MyPlugin) String(in io.ReadSeeker) (string, error) {
|
||||
// event source of this plugin.
|
||||
// This method is optional for the event sourcing capability.
|
||||
func (m *MyPlugin) String(evt sdk.EventReader) (string, error) {
|
||||
var value uint64
|
||||
encoder := gob.NewDecoder(in)
|
||||
encoder := gob.NewDecoder(evt.Reader())
|
||||
if err := encoder.Decode(&value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("counter: %d", value), nil
|
||||
}
|
||||
|
||||
// NextBatch produces a batch of new events, and is called repeatedly by the
|
||||
// framework. For source plugins, it's mandatory to specify a NextBatch method.
|
||||
// The batch has a maximum size that dependes on the size of the underlying
|
||||
// reusable memory buffer. A batch can be smaller than the maximum size.
|
||||
func (m *MyInstance) NextBatch(pState sdk.PluginState, evts sdk.EventWriters) (int, error) {
|
||||
// We ignore the batching feature here, and just produce one event per time
|
||||
evt := evts.Get(0)
|
||||
m.counter++
|
||||
encoder := gob.NewEncoder(evt.Writer())
|
||||
if err := encoder.Encode(m.counter); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
evt.SetTimestamp(uint64(time.Now().UnixNano()))
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// Progress returns a percentage indicator referring to the production progress
|
||||
// of the event source of this plugin.
|
||||
// This method is optional for source plugins.
|
||||
// func (m *MyInstance) Progress(pState sdk.PluginState) (float64, string) {
|
||||
//
|
||||
// }
|
||||
|
||||
// Close is gets called by the SDK when the plugin source capture gets closed.
|
||||
// This is useful to release any open resource used by each plugin instance.
|
||||
// This method is optional for source plugins.
|
||||
// func (m *MyInstance) Close() {
|
||||
//
|
||||
// }
|
||||
|
||||
// Destroy is gets called by the SDK when the plugin gets deinitialized.
|
||||
// This is useful to release any open resource used by the plugin.
|
||||
// This method is optional for source plugins.
|
||||
// This method is optional.
|
||||
// func (m *MyPlugin) Destroy() {
|
||||
//
|
||||
// }
|
||||
|
@ -153,7 +125,7 @@ func (m *MyInstance) NextBatch(pState sdk.PluginState, evts sdk.EventWriters) (i
|
|||
// plugin can assume that it to be always be well-formed when passed to Init().
|
||||
// This is ignored if the return value is nil. The returned schema must follow
|
||||
// the JSON Schema specific. See: https://json-schema.org/
|
||||
// This method is optional for extractor plugins.
|
||||
// This method is optional.
|
||||
// func (m *MyPlugin) InitSchema() *sdk.SchemaInfo {
|
||||
//
|
||||
// }
|
||||
|
|
15
go.mod
15
go.mod
|
@ -1,3 +1,16 @@
|
|||
module github.com/falcosecurity/plugin-sdk-go
|
||||
|
||||
go 1.15
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -17,7 +18,9 @@ limitations under the License.
|
|||
package cgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Handle is an alternative implementation of cgo.Handle introduced by
|
||||
|
@ -32,47 +35,90 @@ import (
|
|||
// an integer type that is large enough to hold the bit pattern of any pointer.
|
||||
// The zero value of a Handle is not valid and thus is safe to use as
|
||||
// a sentinel in C APIs.
|
||||
|
||||
// The performance optimization comes with a limitation: the maximum number
|
||||
// of handles is capped to a fixed value (see MaxHandle). However, since
|
||||
// the intended usage is to pass opaque pointers holding the plugin states
|
||||
// (usually at most two pointers per one instance of a plugin), this hard limit
|
||||
// is considered acceptable. The usage in other contexts is discuraged.
|
||||
//
|
||||
// The performance optimization comes with a limitation: the maximum number
|
||||
// of valid handles is capped to a fixed value (see MaxHandle).
|
||||
// However, since the intended usage is to pass opaque pointers holding the
|
||||
// plugin states (usually at most two pointers per one instance of a plugin),
|
||||
// this hard limit is considered acceptable.
|
||||
//
|
||||
// The thread-safety guarantees have been dropped for further
|
||||
// performance improvements. The current version of the Plugin API does not
|
||||
// require thread safety.
|
||||
//
|
||||
// The usage in other contexts is discuraged.
|
||||
type Handle uintptr
|
||||
|
||||
// The max number of handle that can be created.
|
||||
const MaxHandle = 32 - 1
|
||||
const (
|
||||
// MaxHandle is the largest value that an Handle can hold
|
||||
MaxHandle = 256 - 1
|
||||
|
||||
// max number of times we're willing to iterate over the vector of reusable
|
||||
// handles to do compare-and-swap before giving up
|
||||
maxNewHandleRounds = 20
|
||||
)
|
||||
|
||||
var (
|
||||
handles [MaxHandle + 1]unsafe.Pointer // [int]*interface{}
|
||||
noHandle unsafe.Pointer = nil
|
||||
)
|
||||
|
||||
func init() {
|
||||
resetHandles()
|
||||
}
|
||||
|
||||
// NewHandle returns a handle for a given value.
|
||||
//
|
||||
// The handle is valid until the program calls Delete on it. The handle
|
||||
// uses resources, and this package assumes that C code may hold on to
|
||||
// the handle, so a program must explicitly call Delete when the handle
|
||||
// is no longer needed.
|
||||
// is no longer needed. Programs must not retain deleted handles.
|
||||
//
|
||||
// The intended use is to pass the returned handle to C code, which
|
||||
// passes it back to Go, which calls Value.
|
||||
//
|
||||
// This function panics if called more than MaxHandle times.
|
||||
// The simultaneous number of the valid handles cannot exceed MaxHandle.
|
||||
// This function panics if there are no more handles available.
|
||||
// Previously created handles may be made available again when
|
||||
// invalidated with Delete.
|
||||
//
|
||||
// This function is not thread-safe.
|
||||
func NewHandle(v interface{}) Handle {
|
||||
h := atomic.AddUintptr(&handleIdx, 1)
|
||||
if h > MaxHandle {
|
||||
panic("plugin-sdk-go/cgo: ran out of handle space")
|
||||
rounds := 0
|
||||
for h := uintptr(1); ; h++ {
|
||||
// we acquired ownership of an handle, return it
|
||||
// note: we attempt accessing slots 1..MaxHandle (included)
|
||||
if atomic.CompareAndSwapPointer(&handles[h], noHandle, (unsafe.Pointer)(&v)) {
|
||||
return Handle(h)
|
||||
}
|
||||
|
||||
handles[h] = v
|
||||
return Handle(h)
|
||||
// we haven't acquired a handle, but we can try with the next one
|
||||
if h < MaxHandle {
|
||||
continue
|
||||
}
|
||||
|
||||
// we iterated over the whole vector of handles, so we get back to start
|
||||
// and try again with another round. Once we do this too many times,
|
||||
// we have no choice if not panic-ing
|
||||
h = uintptr(0) // note: will be incremented when continuing
|
||||
if rounds < maxNewHandleRounds {
|
||||
rounds++
|
||||
continue
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("plugin-sdk-go/cgo: could not obtain a new handle after round #%d", rounds))
|
||||
}
|
||||
}
|
||||
|
||||
// Value returns the associated Go value for a valid handle.
|
||||
//
|
||||
// The method panics if the handle is invalid.
|
||||
// This function is not thread-safe.
|
||||
func (h Handle) Value() interface{} {
|
||||
if h > MaxHandle || handles[h] == &noHandle {
|
||||
panic("plugin-sdk-go/cgo: misuse of an invalid Handle")
|
||||
if h > MaxHandle || atomic.LoadPointer(&handles[h]) == noHandle {
|
||||
panic(fmt.Sprintf("plugin-sdk-go/cgo: misuse (value) of an invalid Handle %d", h))
|
||||
}
|
||||
return handles[h]
|
||||
return *(*interface{})(atomic.LoadPointer(&handles[h]))
|
||||
}
|
||||
|
||||
// Delete invalidates a handle. This method should only be called once
|
||||
|
@ -80,26 +126,16 @@ func (h Handle) Value() interface{} {
|
|||
// no longer has a copy of the handle value.
|
||||
//
|
||||
// The method panics if the handle is invalid.
|
||||
// This function is not thread-safe.
|
||||
func (h Handle) Delete() {
|
||||
if h > MaxHandle || handles[h] == &noHandle {
|
||||
panic("plugin-sdk-go/cgo: misuse of an invalid Handle")
|
||||
if h > MaxHandle || atomic.LoadPointer(&handles[h]) == noHandle {
|
||||
panic(fmt.Sprintf("plugin-sdk-go/cgo: misuse (delete) of an invalid Handle %d", h))
|
||||
}
|
||||
handles[h] = &noHandle
|
||||
atomic.StorePointer(&handles[h], noHandle)
|
||||
}
|
||||
|
||||
func resetHandles() {
|
||||
handleIdx = 0
|
||||
for i := 0; i <= MaxHandle; i++ {
|
||||
handles[i] = &noHandle
|
||||
atomic.StorePointer(&handles[i], noHandle)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
handles [MaxHandle + 1]interface{} // [int]interface{}
|
||||
handleIdx uintptr // atomic
|
||||
noHandle int
|
||||
)
|
||||
|
||||
func init() {
|
||||
resetHandles()
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -18,6 +19,7 @@ package cgo
|
|||
|
||||
import (
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -60,7 +62,7 @@ func TestHandle(t *testing.T) {
|
|||
|
||||
siz := 0
|
||||
for i := 0; i < MaxHandle; i++ {
|
||||
if handles[i] != &noHandle {
|
||||
if atomic.LoadPointer(&handles[i]) != noHandle {
|
||||
siz++
|
||||
}
|
||||
}
|
||||
|
@ -84,6 +86,17 @@ func TestInvalidHandle(t *testing.T) {
|
|||
h.Delete()
|
||||
})
|
||||
|
||||
t.Run("zero-value", func(t *testing.T) {
|
||||
h := Handle(0)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
return
|
||||
}
|
||||
t.Fatalf("Delete of zero handle did not trigger a panic")
|
||||
}()
|
||||
h.Value()
|
||||
})
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
h := NewHandle(42)
|
||||
|
||||
|
@ -99,6 +112,41 @@ func TestInvalidHandle(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestMaxHandle(t *testing.T) {
|
||||
t.Run("non-max", func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("NewHandle with non-max handle count triggered a panic")
|
||||
}
|
||||
}()
|
||||
handles := make([]Handle, 0)
|
||||
for i := 1; i <= MaxHandle; i++ {
|
||||
v := i
|
||||
handles = append(handles, NewHandle(&v))
|
||||
}
|
||||
for _, h := range handles {
|
||||
h.Delete()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("max", func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
return
|
||||
}
|
||||
t.Fatalf("NewHandle with max handle count did not triggered a panic")
|
||||
}()
|
||||
handles := make([]Handle, 0)
|
||||
for i := 1; i <= MaxHandle+1; i++ {
|
||||
v := i
|
||||
handles = append(handles, NewHandle(&v))
|
||||
}
|
||||
for _, h := range handles {
|
||||
h.Delete()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkHandle(b *testing.B) {
|
||||
b.Run("non-concurrent", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
|
|
@ -0,0 +1,442 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package loader
|
||||
|
||||
// note: cgo does not support macros and function pointers, so we have to
|
||||
// create wrappers around those to access them from Go code
|
||||
|
||||
/*
|
||||
#cgo linux LDFLAGS: -ldl
|
||||
#cgo CFLAGS: -I ../sdk
|
||||
|
||||
#include "plugin_loader.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
uint32_t __plugin_max_errlen = PLUGIN_MAX_ERRLEN;
|
||||
|
||||
static uint32_t __get_info_u32(uint32_t (*f)())
|
||||
{
|
||||
if (!f) return 0;
|
||||
return f();
|
||||
}
|
||||
|
||||
static const char* __get_info_str(const char *(*f)())
|
||||
{
|
||||
if (!f) return "";
|
||||
return f();
|
||||
}
|
||||
|
||||
static const char *__get_init_schema(plugin_api* p, ss_plugin_schema_type *s)
|
||||
{
|
||||
return p->get_init_schema(s);
|
||||
}
|
||||
|
||||
static ss_plugin_t* __init(plugin_api* p, const ss_plugin_init_input *in, ss_plugin_rc *rc)
|
||||
{
|
||||
return p->init(in, rc);
|
||||
}
|
||||
|
||||
static void __destroy(plugin_api* p, ss_plugin_t* s)
|
||||
{
|
||||
p->destroy(s);
|
||||
}
|
||||
|
||||
static const char* __get_last_err(plugin_api* p, ss_plugin_t* s)
|
||||
{
|
||||
return p->get_last_error(s);
|
||||
}
|
||||
|
||||
static ss_instance_t* __open(plugin_api* p, ss_plugin_t* s, const char* o, ss_plugin_rc* r)
|
||||
{
|
||||
return p->open(s, o, r);
|
||||
}
|
||||
|
||||
static void __close(plugin_api* p, ss_plugin_t* s, ss_instance_t* h)
|
||||
{
|
||||
p->close(s, h);
|
||||
}
|
||||
|
||||
static const char* __list_open_params(plugin_api* p, ss_plugin_t* s, ss_plugin_rc* rc)
|
||||
{
|
||||
return p->list_open_params(s, rc);
|
||||
}
|
||||
|
||||
static const char* __get_progress(plugin_api* p, ss_plugin_t* s, ss_instance_t* h, uint32_t* r)
|
||||
{
|
||||
return p->get_progress(s, h, r);
|
||||
}
|
||||
|
||||
static const char* __event_to_string(plugin_api* p, ss_plugin_t *s, const ss_plugin_event_input *e)
|
||||
{
|
||||
return p->event_to_string(s, e);
|
||||
}
|
||||
|
||||
static ss_plugin_rc __next_batch(plugin_api* p, ss_plugin_t* s, ss_instance_t* h, uint32_t *n, ss_plugin_event ***e)
|
||||
{
|
||||
return p->next_batch(s, h, n, e);
|
||||
}
|
||||
|
||||
static ss_plugin_rc __extract_fields(plugin_api* p, ss_plugin_t *s, const ss_plugin_event_input *e, ss_plugin_field_extract_input *in)
|
||||
{
|
||||
return p->extract_fields(s, e, in);
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins"
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
)
|
||||
|
||||
// todo(jasondellaluce,therealbobo): the loader must support the new features of
|
||||
// the plugin API:
|
||||
// - event parsing capability
|
||||
// - async events capability
|
||||
// - get_extract_event_types
|
||||
|
||||
var (
|
||||
errNotInitialized = errors.New("plugin is not initialized")
|
||||
errNoSourcingCap = errors.New("plugin does not support event sourcing capability")
|
||||
)
|
||||
|
||||
// Plugin represents a Falcosecurity Plugin loaded from an external shared
|
||||
// dynamic library
|
||||
type Plugin struct {
|
||||
m sync.Mutex
|
||||
handle *C.plugin_handle_t
|
||||
state *C.ss_plugin_t
|
||||
caps C.plugin_caps_t
|
||||
info plugins.Info
|
||||
initSchema *sdk.SchemaInfo
|
||||
fields []sdk.FieldEntry
|
||||
validated bool
|
||||
validErr error
|
||||
capBrokenErr error
|
||||
}
|
||||
|
||||
func errAppend(left, right error) error {
|
||||
if left == nil {
|
||||
return right
|
||||
}
|
||||
if right == nil {
|
||||
return left
|
||||
}
|
||||
return fmt.Errorf("%s, %s", left.Error(), right.Error())
|
||||
}
|
||||
|
||||
// NewValidPlugin is the same as NewPlugin(), but returns an error if
|
||||
// the loaded plugin is not valid. It is equivalent to invoking NewPlugin()
|
||||
// and Plugin.Validate() in sequence.
|
||||
func NewValidPlugin(path string) (*Plugin, error) {
|
||||
p, err := NewPlugin(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = p.Validate()
|
||||
if err != nil {
|
||||
p.Unload()
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// NewPlugin loads a Falcosecurity plugin from the dynamic library present in
|
||||
// the local filesystem at the given path. If successful, returns a
|
||||
// *loader.Plugin representing the loaded plugin and a nil error.
|
||||
// Otherwise, returns a non-nil error containing the failure condition.
|
||||
//
|
||||
// Although this reads the content of the dynamic library, this does not check
|
||||
// that the returned Plugin is valid and complies to the currently supported
|
||||
// plugin API version. This does not initialize the plugin either.
|
||||
// For those purposes, refer to the Validate() and Init() functions of the
|
||||
// returned *loader.Plugin.
|
||||
//
|
||||
// Separating the loading step from the validation one allows developers to open
|
||||
// plugins that are either corrupted or developed towards an older API version.
|
||||
// This can be useful to inspect static descriptive data of those plugins too,
|
||||
// such as the name or the version.
|
||||
func NewPlugin(path string) (*Plugin, error) {
|
||||
// load library
|
||||
errBuf := (*C.char)(C.malloc(C.size_t(C.__plugin_max_errlen) * C.sizeof_char))
|
||||
defer C.free(unsafe.Pointer(errBuf))
|
||||
p := &Plugin{}
|
||||
p.handle = C.plugin_load(C.CString(path), errBuf)
|
||||
if p.handle == nil {
|
||||
return nil, errors.New(C.GoString(errBuf))
|
||||
}
|
||||
|
||||
// get supported capabilities
|
||||
p.caps = C.plugin_get_capabilities(p.handle, errBuf)
|
||||
if p.caps&C.CAP_BROKEN != 0 {
|
||||
p.capBrokenErr = errors.New(C.GoString(errBuf))
|
||||
}
|
||||
|
||||
// read static info (if available)
|
||||
p.info = plugins.Info{
|
||||
Version: C.GoString(C.__get_info_str(p.handle.api.get_version)),
|
||||
RequiredAPIVersion: C.GoString(C.__get_info_str(p.handle.api.get_required_api_version)),
|
||||
Name: C.GoString(C.__get_info_str(p.handle.api.get_name)),
|
||||
Description: C.GoString(C.__get_info_str(p.handle.api.get_description)),
|
||||
Contact: C.GoString(C.__get_info_str(p.handle.api.get_contact)),
|
||||
ID: uint32(C.__get_info_u32(p.handle.api.anon0.get_id)),
|
||||
EventSource: C.GoString(C.__get_info_str(p.handle.api.anon0.get_event_source)),
|
||||
ExtractEventSources: []string{},
|
||||
}
|
||||
if p.handle.api.get_init_schema != nil {
|
||||
t := (C.ss_plugin_schema_type)(C.SS_PLUGIN_SCHEMA_NONE)
|
||||
s := C.GoString(C.__get_init_schema(&p.handle.api, &t))
|
||||
// todo(jasondellaluce): update this once we support more schema types
|
||||
if t == (C.ss_plugin_schema_type)(C.SS_PLUGIN_SCHEMA_JSON) {
|
||||
p.initSchema = &sdk.SchemaInfo{Schema: s}
|
||||
}
|
||||
}
|
||||
|
||||
// get static info related to extraction capability (if available)
|
||||
if p.HasCapExtraction() {
|
||||
// capability is considered not supported if data is corrupted
|
||||
if p.handle.api.anon1.get_extract_event_sources != nil {
|
||||
str := C.GoString(C.__get_info_str(p.handle.api.anon1.get_extract_event_sources))
|
||||
if err := json.Unmarshal(([]byte)(str), &p.info.ExtractEventSources); err != nil {
|
||||
p.caps &= ^C.CAP_EXTRACTION
|
||||
p.caps |= C.CAP_BROKEN
|
||||
p.capBrokenErr = errAppend(p.capBrokenErr, errors.New("get_extract_event_sources does not return a well-formed json array"))
|
||||
}
|
||||
}
|
||||
str := C.GoString(C.__get_info_str(p.handle.api.anon1.get_fields))
|
||||
if err := json.Unmarshal(([]byte)(str), &p.fields); err != nil {
|
||||
p.caps &= ^C.CAP_EXTRACTION
|
||||
p.caps |= C.CAP_BROKEN
|
||||
p.capBrokenErr = errAppend(p.capBrokenErr, errors.New("get_fields does not return a well-formed json array"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Unload unloads a Plugin and disposes it allocated resources.
|
||||
// If the plugin was initialized, this invokes the plugin_destroy symbol.
|
||||
//
|
||||
// The behavior of Unload() an already-unloaded Plugins is undefined.
|
||||
func (p *Plugin) Unload() {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
if p.handle != nil {
|
||||
p.destroy()
|
||||
C.plugin_unload(p.handle)
|
||||
p.handle = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Plugin) validate() error {
|
||||
if !p.validated {
|
||||
errBuf := (*C.char)(C.malloc(C.size_t(C.__plugin_max_errlen) * C.sizeof_char))
|
||||
defer C.free(unsafe.Pointer(errBuf))
|
||||
if !C.plugin_check_required_api_version(p.handle, errBuf) ||
|
||||
!C.plugin_check_required_symbols(p.handle, errBuf) {
|
||||
p.validErr = errors.New(C.GoString(errBuf))
|
||||
return p.validErr
|
||||
}
|
||||
if (p.caps & ^C.CAP_BROKEN) == C.CAP_NONE {
|
||||
p.validErr = errors.New("plugin supports no capability")
|
||||
return errAppend(p.validErr, p.capBrokenErr)
|
||||
}
|
||||
p.validated = true
|
||||
}
|
||||
return p.validErr
|
||||
}
|
||||
|
||||
// Validates returns nil if the Plugin is well-formed and compatible with
|
||||
// the plugin API version supported by the loader. Otherwise, returns an
|
||||
// error describing what makes the plugin invalid.
|
||||
func (p *Plugin) Validate() error {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
return p.validate()
|
||||
}
|
||||
|
||||
// HasCapBroken returns true if the plugin has any of its capabilities broken.
|
||||
func (p *Plugin) HasCapBroken() bool {
|
||||
return p.caps&C.CAP_BROKEN != 0
|
||||
}
|
||||
|
||||
// CapBrokenError returns a non-nil error if HasCapBroken returns true.
|
||||
func (p *Plugin) CapBrokenError() error {
|
||||
return p.capBrokenErr
|
||||
}
|
||||
|
||||
// HasCapExtraction returns true if the plugin supports the
|
||||
// field extraction capability.
|
||||
func (p *Plugin) HasCapExtraction() bool {
|
||||
return p.caps&C.CAP_EXTRACTION != 0
|
||||
}
|
||||
|
||||
// HasCapSourcing returns true if the plugin supports the
|
||||
// event sourcing capability.
|
||||
func (p *Plugin) HasCapSourcing() bool {
|
||||
return p.caps&C.CAP_SOURCING != 0
|
||||
}
|
||||
|
||||
// Info returns a pointer to a Info struct, containing all the general
|
||||
// information about this plugin. Can return nil if info are not available.
|
||||
func (p *Plugin) Info() *plugins.Info {
|
||||
return &p.info
|
||||
}
|
||||
|
||||
// InitSchema implements the sdk.InitSchema interface. Returns a
|
||||
// schema describing the data expected to be passed as a configuration
|
||||
// during the plugin initialization.
|
||||
// Can return nil if the schema is not available.
|
||||
func (p *Plugin) InitSchema() *sdk.SchemaInfo {
|
||||
return p.initSchema
|
||||
}
|
||||
|
||||
// Fields return the list of extractor fields exported by this plugin.
|
||||
// If the plugin does not support the field extraction capability, this
|
||||
// returns an empty list.
|
||||
func (p *Plugin) Fields() []sdk.FieldEntry {
|
||||
return p.fields
|
||||
}
|
||||
|
||||
// OpenParams implements the sdk.OpenParams interface.
|
||||
// Returns a list of suggested open parameters.
|
||||
// Returns a non-nil error in one of the following conditions:
|
||||
// - Plugin is not initialized
|
||||
// - Plugin does not support the event sourcing capability
|
||||
func (p *Plugin) OpenParams() ([]sdk.OpenParam, error) {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
if !p.HasCapSourcing() {
|
||||
return nil, errNoSourcingCap
|
||||
}
|
||||
if p.state == nil {
|
||||
return nil, errNotInitialized
|
||||
}
|
||||
|
||||
var ret []sdk.OpenParam
|
||||
if p.handle.api.anon0.list_open_params == nil {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
errBuf := (*C.char)(C.malloc(C.size_t(C.__plugin_max_errlen) * C.sizeof_char))
|
||||
defer C.free(unsafe.Pointer(errBuf))
|
||||
rc := C.ss_plugin_rc(sdk.SSPluginSuccess)
|
||||
str := C.GoString((C.__list_open_params(&p.handle.api, unsafe.Pointer(p.state), (*C.ss_plugin_rc)(&rc))))
|
||||
if rc != C.ss_plugin_rc(sdk.SSPluginSuccess) {
|
||||
return nil, errors.New(C.GoString(errBuf))
|
||||
}
|
||||
|
||||
if len(str) > 0 {
|
||||
if err := json.Unmarshal(([]byte)(str), &ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Init initializes this plugin with a given config string. A successful call
|
||||
// to init returns a nil error.
|
||||
//
|
||||
// If the plugin supports an init config schema (e.g. Plugin.InitSchema
|
||||
// returns a non-nil value), the config string is validated with the schema
|
||||
// and a non-nil error is returned for validation failures.
|
||||
//
|
||||
// The plugin get validated before getting initialized, and a non-nil error is
|
||||
// returned in case of validation errors. Invoking Init() multiple times on
|
||||
// the same plugin returns an error.
|
||||
//
|
||||
// Once initalized, the plugin gets destroyed when calling Unload().
|
||||
func (p *Plugin) Init(config string) error {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
if p.state != nil {
|
||||
return fmt.Errorf("plugin is already initialized")
|
||||
}
|
||||
err := p.validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("plugin is not valid: %s", err.Error())
|
||||
}
|
||||
|
||||
config, err = p.validateInitConfig(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid plugin config: %s", err.Error())
|
||||
}
|
||||
|
||||
// todo(jasondelluce,therealbobo): support owner pointer and implement table access
|
||||
in := C.ss_plugin_init_input{}
|
||||
in.owner = nil
|
||||
in.get_owner_last_error = nil
|
||||
in.tables = nil
|
||||
in.config = C.CString(config)
|
||||
rc := C.ss_plugin_rc(sdk.SSPluginSuccess)
|
||||
p.state = (*C.ss_plugin_t)(C.__init(&p.handle.api, &in, (*C.ss_plugin_rc)(&rc)))
|
||||
if rc == C.ss_plugin_rc(sdk.SSPluginSuccess) {
|
||||
return nil
|
||||
}
|
||||
if p.state != nil {
|
||||
err := p.lastError()
|
||||
p.destroy()
|
||||
return err
|
||||
}
|
||||
return errors.New("unknown initialization error")
|
||||
}
|
||||
|
||||
// todo(jasondellaluce): change this once we support other schema formats
|
||||
func (p *Plugin) validateInitConfig(config string) (string, error) {
|
||||
if p.initSchema != nil {
|
||||
if len(config) == 0 {
|
||||
config = "{}"
|
||||
}
|
||||
schema := gojsonschema.NewStringLoader(p.initSchema.Schema)
|
||||
document := gojsonschema.NewStringLoader(config)
|
||||
result, err := gojsonschema.Validate(schema, document)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !result.Valid() {
|
||||
// return fist error
|
||||
return "", errors.New(result.Errors()[0].Description())
|
||||
}
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (p *Plugin) destroy() {
|
||||
if p.state != nil {
|
||||
C.__destroy(&p.handle.api, unsafe.Pointer(p.state))
|
||||
p.state = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Plugin) lastError() error {
|
||||
if p.state != nil {
|
||||
str := C.GoString(C.__get_last_err(&p.handle.api, unsafe.Pointer(p.state)))
|
||||
if len(str) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.New(str)
|
||||
}
|
||||
return errNotInitialized
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
diff --git a/pkg/loader/plugin_loader.h b/pkg/loader/plugin_loader.h
|
||||
index e6e8333..8db0d70 100644
|
||||
--- a/pkg/loader/plugin_loader.h
|
||||
+++ b/pkg/loader/plugin_loader.h
|
||||
@@ -18,7 +18,7 @@ limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
-#include <plugin/plugin_api.h>
|
||||
+#include "plugin_api.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
|
@ -0,0 +1,367 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
typedef HINSTANCE library_handle_t;
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
typedef void* library_handle_t;
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "plugin_loader.h"
|
||||
|
||||
// note(jasondellaluce,therealbobo): implementation taken from falcosecurity/libs
|
||||
// note(leogr): to avoid clashing with `strlcpy` introduced by glibc 2.38,
|
||||
// the func has been renamed to plugin_loader_strlcpy.
|
||||
// N.B.: our building system here is not smart enough to detect if the function
|
||||
// was declared already.
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
/*!
|
||||
\brief Copy up to size - 1 characters from the NUL-terminated string src to dst, NUL-terminating the result.
|
||||
|
||||
\return The length of the source string.
|
||||
*/
|
||||
|
||||
static inline size_t plugin_loader_strlcpy(char *dst, const char *src, size_t size) {
|
||||
size_t srcsize = strlen(src);
|
||||
if (size == 0) {
|
||||
return srcsize;
|
||||
}
|
||||
|
||||
size_t copysize = srcsize;
|
||||
|
||||
if (copysize > size - 1) {
|
||||
copysize = size - 1;
|
||||
}
|
||||
|
||||
memcpy(dst, src, copysize);
|
||||
dst[copysize] = '\0';
|
||||
|
||||
return srcsize;
|
||||
}
|
||||
|
||||
static inline void err_prepend(char* s, const char* prefix, const char* sep) {
|
||||
char tmp[PLUGIN_MAX_ERRLEN];
|
||||
size_t prefix_len = plugin_loader_strlcpy(tmp, prefix, PLUGIN_MAX_ERRLEN);
|
||||
if(*s != '\0') {
|
||||
plugin_loader_strlcpy(&tmp[prefix_len], sep, PLUGIN_MAX_ERRLEN - prefix_len);
|
||||
prefix_len += strlen(sep);
|
||||
}
|
||||
plugin_loader_strlcpy(&tmp[prefix_len], s, PLUGIN_MAX_ERRLEN - prefix_len);
|
||||
plugin_loader_strlcpy(s, tmp, PLUGIN_MAX_ERRLEN);
|
||||
}
|
||||
|
||||
static inline void err_append(char* s, const char* suffix, const char* sep) {
|
||||
if(*s != '\0') {
|
||||
strlcat(s, sep, PLUGIN_MAX_ERRLEN);
|
||||
}
|
||||
strlcat(s, suffix, PLUGIN_MAX_ERRLEN);
|
||||
}
|
||||
|
||||
static void* getsym(library_handle_t handle, const char* name) {
|
||||
#ifdef _WIN32
|
||||
return (void*)GetProcAddress(handle, name);
|
||||
#else
|
||||
return (void*)dlsym(handle, name);
|
||||
#endif
|
||||
}
|
||||
|
||||
// little hack for simplifying the plugin_load function
|
||||
#define SYM_RESOLVE(h, s) *(void**)(&(h->api.s)) = getsym(h->handle, "plugin_" #s)
|
||||
|
||||
plugin_handle_t* plugin_load(const char* path, char* err) {
|
||||
// alloc and init memory
|
||||
err[0] = '\0';
|
||||
plugin_handle_t* ret = (plugin_handle_t*)calloc(1, sizeof(plugin_handle_t));
|
||||
if(!ret) {
|
||||
plugin_loader_strlcpy(err, "error allocating plugin handle", PLUGIN_MAX_ERRLEN);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// open dynamic library
|
||||
#ifdef _WIN32
|
||||
ret->handle = LoadLibrary(path);
|
||||
if(ret->handle == NULL) {
|
||||
DWORD flg = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS;
|
||||
LPTSTR msg_buf = 0;
|
||||
if(FormatMessageA(flg, 0, GetLastError(), 0, (LPTSTR)&msg_buf, 0, NULL) && msg_buf) {
|
||||
plugin_loader_strlcpy(err, msg_buf, PLUGIN_MAX_ERRLEN);
|
||||
LocalFree(msg_buf);
|
||||
}
|
||||
}
|
||||
#else
|
||||
ret->handle = dlopen(path, RTLD_LAZY);
|
||||
if(ret->handle == NULL) {
|
||||
plugin_loader_strlcpy(err, (const char*)dlerror(), PLUGIN_MAX_ERRLEN);
|
||||
}
|
||||
#endif
|
||||
|
||||
// return NULL if library loading had errors
|
||||
if(ret->handle == NULL) {
|
||||
err_prepend(err, "can't load plugin dynamic library:", " ");
|
||||
free(ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// load all library symbols
|
||||
SYM_RESOLVE(ret, get_required_api_version);
|
||||
SYM_RESOLVE(ret, get_version);
|
||||
SYM_RESOLVE(ret, get_last_error);
|
||||
SYM_RESOLVE(ret, get_name);
|
||||
SYM_RESOLVE(ret, get_description);
|
||||
SYM_RESOLVE(ret, get_contact);
|
||||
SYM_RESOLVE(ret, get_init_schema);
|
||||
SYM_RESOLVE(ret, init);
|
||||
SYM_RESOLVE(ret, destroy);
|
||||
SYM_RESOLVE(ret, get_id);
|
||||
SYM_RESOLVE(ret, get_event_source);
|
||||
SYM_RESOLVE(ret, open);
|
||||
SYM_RESOLVE(ret, close);
|
||||
SYM_RESOLVE(ret, next_batch);
|
||||
SYM_RESOLVE(ret, get_progress);
|
||||
SYM_RESOLVE(ret, list_open_params);
|
||||
SYM_RESOLVE(ret, event_to_string);
|
||||
SYM_RESOLVE(ret, get_fields);
|
||||
SYM_RESOLVE(ret, extract_fields);
|
||||
SYM_RESOLVE(ret, get_extract_event_sources);
|
||||
SYM_RESOLVE(ret, get_extract_event_types);
|
||||
SYM_RESOLVE(ret, get_parse_event_types);
|
||||
SYM_RESOLVE(ret, get_parse_event_sources);
|
||||
SYM_RESOLVE(ret, parse_event);
|
||||
SYM_RESOLVE(ret, get_async_event_sources);
|
||||
SYM_RESOLVE(ret, get_async_events);
|
||||
SYM_RESOLVE(ret, set_async_event_handler);
|
||||
SYM_RESOLVE(ret, dump_state);
|
||||
SYM_RESOLVE(ret, set_config);
|
||||
SYM_RESOLVE(ret, get_metrics);
|
||||
SYM_RESOLVE(ret, capture_open);
|
||||
SYM_RESOLVE(ret, capture_close);
|
||||
return ret;
|
||||
}
|
||||
|
||||
plugin_handle_t* plugin_load_api(const plugin_api* api, char* err) {
|
||||
// alloc and init memory
|
||||
err[0] = '\0';
|
||||
if(!api) {
|
||||
plugin_loader_strlcpy(err, "can't allocate plugin handle with invalid API table", PLUGIN_MAX_ERRLEN);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
plugin_handle_t* ret = (plugin_handle_t*)calloc(1, sizeof(plugin_handle_t));
|
||||
if(!ret) {
|
||||
plugin_loader_strlcpy(err, "error allocating plugin handle", PLUGIN_MAX_ERRLEN);
|
||||
return NULL;
|
||||
}
|
||||
ret->api = *api;
|
||||
|
||||
// todo: remove this if/when we get to API version 4
|
||||
uint32_t major, minor, patch;
|
||||
const char* ver;
|
||||
if(api->get_required_api_version == NULL) {
|
||||
plugin_loader_strlcpy(err, "plugin_get_required_api_version symbol not implemented", PLUGIN_MAX_ERRLEN);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ver = api->get_required_api_version();
|
||||
if(sscanf(ver, "%" PRIu32 ".%" PRIu32 ".%" PRIu32, &major, &minor, &patch) != 3) {
|
||||
snprintf(err,
|
||||
PLUGIN_MAX_ERRLEN,
|
||||
"plugin provided an invalid required API version: '%s'",
|
||||
ver);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// API 3.10 introduced dump_state in the middle of the plugin_api struct.
|
||||
// Fix up older 3.x plugins by shifting the fields by one
|
||||
if(major == 3 && minor < 10) {
|
||||
size_t from_offset = offsetof(plugin_api, dump_state);
|
||||
size_t to_offset = offsetof(plugin_api, set_config);
|
||||
size_t size = sizeof(plugin_api) - to_offset;
|
||||
char* api_ptr = (char*)&ret->api;
|
||||
memmove(api_ptr + to_offset, api_ptr + from_offset, size);
|
||||
ret->api.dump_state = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void plugin_unload(plugin_handle_t* h) {
|
||||
if(h) {
|
||||
if(h->handle) {
|
||||
#ifdef _WIN32
|
||||
FreeLibrary(h->handle);
|
||||
#else
|
||||
dlclose(h->handle);
|
||||
#endif
|
||||
}
|
||||
free(h);
|
||||
}
|
||||
}
|
||||
|
||||
bool plugin_is_loaded(const char* path) {
|
||||
#ifdef _WIN32
|
||||
/*
|
||||
* LoadLibrary maps the module into the address space of the calling process, if necessary,
|
||||
* and increments the modules reference count, if it is already mapped.
|
||||
* GetModuleHandle, however, returns the handle to a mapped module
|
||||
* without incrementing its reference count.
|
||||
*
|
||||
* This returns an HMODULE indeed, but they are the same thing
|
||||
*/
|
||||
return GetModuleHandle(path) != NULL;
|
||||
#else
|
||||
/*
|
||||
* RTLD_NOLOAD (since glibc 2.2)
|
||||
* Don't load the shared object. This can be used to test if
|
||||
* the object is already resident (dlopen() returns NULL if
|
||||
* it is not, or the object's handle if it is resident).
|
||||
* This does not increment dlobject reference count.
|
||||
*/
|
||||
return dlopen(path, RTLD_LAZY | RTLD_NOLOAD) != NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool plugin_check_required_api_version(const plugin_handle_t* h, char* err) {
|
||||
uint32_t major, minor, patch;
|
||||
const char *ver, *failmsg;
|
||||
if(h->api.get_required_api_version == NULL) {
|
||||
plugin_loader_strlcpy(err, "plugin_get_required_api_version symbol not implemented", PLUGIN_MAX_ERRLEN);
|
||||
return false;
|
||||
}
|
||||
|
||||
ver = h->api.get_required_api_version();
|
||||
if(sscanf(ver, "%" PRIu32 ".%" PRIu32 ".%" PRIu32, &major, &minor, &patch) != 3) {
|
||||
snprintf(err,
|
||||
PLUGIN_MAX_ERRLEN,
|
||||
"plugin provided an invalid required API version: '%s'",
|
||||
ver);
|
||||
return false;
|
||||
}
|
||||
|
||||
failmsg = NULL;
|
||||
/* The plugin requires a minimum framework version */
|
||||
if(PLUGIN_API_VERSION_MAJOR != major) {
|
||||
failmsg = "major versions disagree";
|
||||
} else if(PLUGIN_API_VERSION_MINOR < minor) {
|
||||
failmsg = "framework's minor is less than the requested one";
|
||||
} else if(PLUGIN_API_VERSION_MINOR == minor && PLUGIN_API_VERSION_PATCH < patch) {
|
||||
failmsg = "framework's patch is less than the requested one";
|
||||
}
|
||||
|
||||
if(failmsg != NULL) {
|
||||
snprintf(err,
|
||||
PLUGIN_MAX_ERRLEN,
|
||||
"plugin required API version '%s' not compatible with the framework's API version "
|
||||
"'%s': %s",
|
||||
ver,
|
||||
PLUGIN_API_VERSION_STR,
|
||||
failmsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
plugin_caps_t plugin_get_capabilities(const plugin_handle_t* h, char* err) {
|
||||
plugin_caps_t caps = CAP_NONE;
|
||||
plugin_loader_strlcpy(err, "", PLUGIN_MAX_ERRLEN);
|
||||
|
||||
if(h->api.open != NULL && h->api.close != NULL && h->api.next_batch != NULL) {
|
||||
bool has_id = h->api.get_id != NULL && h->api.get_id() != 0;
|
||||
bool has_source = h->api.get_event_source != NULL && strlen(h->api.get_event_source()) > 0;
|
||||
if((has_id && has_source) || (!has_id && !has_source)) {
|
||||
caps = (plugin_caps_t)((uint32_t)caps | (uint32_t)CAP_SOURCING);
|
||||
} else {
|
||||
caps = (plugin_caps_t)((uint32_t)caps | (uint32_t)CAP_BROKEN);
|
||||
err_append(err,
|
||||
"must implement both 'plugin_get_id' and 'plugin_get_event_source' or "
|
||||
"neither (event sourcing)",
|
||||
", ");
|
||||
}
|
||||
} else if(h->api.open != NULL || h->api.close != NULL || h->api.next_batch != NULL) {
|
||||
caps = (plugin_caps_t)((uint32_t)caps | (uint32_t)CAP_BROKEN);
|
||||
err_append(err,
|
||||
"must implement all of 'plugin_open', 'plugin_close', and 'plugin_next_batch' "
|
||||
"(event sourcing)",
|
||||
", ");
|
||||
}
|
||||
|
||||
if(h->api.get_fields != NULL && h->api.extract_fields != NULL) {
|
||||
caps = (plugin_caps_t)((uint32_t)caps | (uint32_t)CAP_EXTRACTION);
|
||||
} else if(h->api.extract_fields != NULL) {
|
||||
caps = (plugin_caps_t)((uint32_t)caps | (uint32_t)CAP_BROKEN);
|
||||
err_append(err,
|
||||
"must implement both 'plugin_get_fields' and 'plugin_extract_fields' (field "
|
||||
"extraction)",
|
||||
", ");
|
||||
}
|
||||
|
||||
if(h->api.parse_event != NULL) {
|
||||
caps = (plugin_caps_t)((uint32_t)caps | (uint32_t)CAP_PARSING);
|
||||
}
|
||||
|
||||
if(h->api.get_async_events != NULL && h->api.set_async_event_handler != NULL) {
|
||||
caps = (plugin_caps_t)((uint32_t)caps | (uint32_t)CAP_ASYNC);
|
||||
} else if(h->api.set_async_event_handler != NULL) {
|
||||
caps = (plugin_caps_t)((uint32_t)caps | (uint32_t)CAP_BROKEN);
|
||||
err_append(err,
|
||||
"must implement both 'plugin_get_async_events' and "
|
||||
"'plugin_set_async_event_handler' (async events)",
|
||||
", ");
|
||||
}
|
||||
|
||||
if(h->api.capture_open != NULL && h->api.capture_close != NULL) {
|
||||
caps = (plugin_caps_t)((uint32_t)caps | (uint32_t)CAP_CAPTURE_LISTENING);
|
||||
} else if(h->api.capture_open != NULL) {
|
||||
caps = (plugin_caps_t)((uint32_t)caps | (uint32_t)CAP_BROKEN);
|
||||
err_append(err,
|
||||
"must implement both 'plugin_capture_open' and 'plugin_capture_close' (capture "
|
||||
"listening)",
|
||||
", ");
|
||||
}
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
// little hack for simplifying the plugin_check_required_symbols function
|
||||
#define SYM_REQCHECK(a, e, s) \
|
||||
do { \
|
||||
if(a->api.s == NULL) { \
|
||||
snprintf(e, PLUGIN_MAX_ERRLEN, "required symbol not implemented: '%s'", #s); \
|
||||
return false; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
bool plugin_check_required_symbols(const plugin_handle_t* h, char* err) {
|
||||
SYM_REQCHECK(h, err, get_required_api_version);
|
||||
SYM_REQCHECK(h, err, get_version);
|
||||
SYM_REQCHECK(h, err, get_name);
|
||||
SYM_REQCHECK(h, err, get_description);
|
||||
SYM_REQCHECK(h, err, get_contact);
|
||||
SYM_REQCHECK(h, err, init);
|
||||
SYM_REQCHECK(h, err, destroy);
|
||||
SYM_REQCHECK(h, err, get_last_error);
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "plugin_api.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*!
|
||||
\brief This enums the capabilities supported by plugins.
|
||||
Each plugin can support one or more of these, in which case the enum flags
|
||||
are or-ed with each other.
|
||||
Currently, the supported capabilities are:
|
||||
* ability to source events and provide them to the event loop
|
||||
* ability to extract fields from events created by other plugins
|
||||
* ability to parse events from the event loop (at most once) before
|
||||
the field extraction phase
|
||||
* ability to inject events asynchronously in the event loop
|
||||
*/
|
||||
typedef enum {
|
||||
CAP_NONE = 0,
|
||||
CAP_SOURCING = 1 << 0,
|
||||
CAP_EXTRACTION = 1 << 1,
|
||||
CAP_PARSING = 1 << 2,
|
||||
CAP_ASYNC = 1 << 3,
|
||||
CAP_CAPTURE_LISTENING = 1 << 4,
|
||||
CAP_BROKEN = 1 << 31, // used to report inconsistencies
|
||||
} plugin_caps_t;
|
||||
|
||||
/*!
|
||||
\brief A handle to a loaded plugin dynamic library.
|
||||
Pointers to this struct must be obtained through the plugin_load()
|
||||
and released through plugin_unload().
|
||||
*/
|
||||
typedef struct plugin_handle_t {
|
||||
#ifdef _WIN32
|
||||
HINSTANCE handle; ///< Handle of the dynamic library
|
||||
#else
|
||||
void* handle; ///< Handle of the dynamic library
|
||||
#endif
|
||||
plugin_api api; ///< The vtable method of the plugin that define its API
|
||||
} plugin_handle_t;
|
||||
|
||||
/*!
|
||||
\brief Uses the given plugin api and returns a plugin_handle_t*
|
||||
representing the loaded plugin. In case of error, returns NULL and fills
|
||||
the err string up to PLUGIN_MAX_ERRLEN chars.
|
||||
*/
|
||||
plugin_handle_t* plugin_load_api(const plugin_api* api, char* err);
|
||||
|
||||
/*!
|
||||
\brief Loads a dynamic library from the given path and returns a
|
||||
plugin_handle_t* representing the loaded plugin. In case of error,
|
||||
returns NULL and fills the err string up to PLUGIN_MAX_ERRLEN chars.
|
||||
*/
|
||||
plugin_handle_t* plugin_load(const char* path, char* err);
|
||||
|
||||
/*!
|
||||
\brief Destroys a plugin_handle_t* previously allocated by
|
||||
invoking plugin_load().
|
||||
*/
|
||||
void plugin_unload(plugin_handle_t* h);
|
||||
|
||||
/*!
|
||||
\brief Returns true if the plugin at the given path is currently loaded.
|
||||
*/
|
||||
bool plugin_is_loaded(const char* path);
|
||||
|
||||
/*!
|
||||
\brief Returns true the API version required by the given plugin is
|
||||
compatible with the API version of the loader. Otherwise, returns false
|
||||
and fills the err string up to PLUGIN_MAX_ERRLEN chars.
|
||||
*/
|
||||
bool plugin_check_required_api_version(const plugin_handle_t* h, char* err);
|
||||
|
||||
/*!
|
||||
\brief Returns true if the given plugin handle implements all the
|
||||
minimum required function symbols for the current API version. Otherwise,
|
||||
returns false and fills the err string up to PLUGIN_MAX_ERRLEN chars.
|
||||
*/
|
||||
bool plugin_check_required_symbols(const plugin_handle_t* h, char* err);
|
||||
|
||||
/*!
|
||||
\brief Returns the capabilities supported by the given plugin handle.
|
||||
In case of inconsistencies, the result will have the CAP_BROKEN bit set
|
||||
and the err string will be filled up to PLUGIN_MAX_ERRLEN chars representing
|
||||
the error encountered.
|
||||
*/
|
||||
plugin_caps_t plugin_get_capabilities(const plugin_handle_t* h, char* err);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,132 @@
|
|||
diff --git a/pkg/loader/plugin_loader.c b/pkg/loader/plugin_loader.c
|
||||
index 2943335..7bebeeb 100644
|
||||
--- a/pkg/loader/plugin_loader.c
|
||||
+++ b/pkg/loader/plugin_loader.c
|
||||
@@ -24,22 +24,52 @@ typedef HINSTANCE library_handle_t;
|
||||
typedef void* library_handle_t;
|
||||
#endif
|
||||
|
||||
-#include <libscap/strl.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
-#include <plugin/plugin_loader.h>
|
||||
+#include "plugin_loader.h"
|
||||
+
|
||||
+// note(jasondellaluce,therealbobo): implementation taken from falcosecurity/libs
|
||||
+// note(leogr): to avoid clashing with `strlcpy` introduced by glibc 2.38,
|
||||
+// the func has been renamed to plugin_loader_strlcpy.
|
||||
+// N.B.: our building system here is not smart enough to detect if the function
|
||||
+// was declared already.
|
||||
+#include <stdint.h>
|
||||
+#include <string.h>
|
||||
+/*!
|
||||
+ \brief Copy up to size - 1 characters from the NUL-terminated string src to dst, NUL-terminating the result.
|
||||
+
|
||||
+ \return The length of the source string.
|
||||
+*/
|
||||
+
|
||||
+static inline size_t plugin_loader_strlcpy(char *dst, const char *src, size_t size) {
|
||||
+ size_t srcsize = strlen(src);
|
||||
+ if (size == 0) {
|
||||
+ return srcsize;
|
||||
+ }
|
||||
+
|
||||
+ size_t copysize = srcsize;
|
||||
+
|
||||
+ if (copysize > size - 1) {
|
||||
+ copysize = size - 1;
|
||||
+ }
|
||||
+
|
||||
+ memcpy(dst, src, copysize);
|
||||
+ dst[copysize] = '\0';
|
||||
+
|
||||
+ return srcsize;
|
||||
+}
|
||||
|
||||
static inline void err_prepend(char* s, const char* prefix, const char* sep) {
|
||||
char tmp[PLUGIN_MAX_ERRLEN];
|
||||
- size_t prefix_len = strlcpy(tmp, prefix, PLUGIN_MAX_ERRLEN);
|
||||
+ size_t prefix_len = plugin_loader_strlcpy(tmp, prefix, PLUGIN_MAX_ERRLEN);
|
||||
if(*s != '\0') {
|
||||
- strlcpy(&tmp[prefix_len], sep, PLUGIN_MAX_ERRLEN - prefix_len);
|
||||
+ plugin_loader_strlcpy(&tmp[prefix_len], sep, PLUGIN_MAX_ERRLEN - prefix_len);
|
||||
prefix_len += strlen(sep);
|
||||
}
|
||||
- strlcpy(&tmp[prefix_len], s, PLUGIN_MAX_ERRLEN - prefix_len);
|
||||
- strlcpy(s, tmp, PLUGIN_MAX_ERRLEN);
|
||||
+ plugin_loader_strlcpy(&tmp[prefix_len], s, PLUGIN_MAX_ERRLEN - prefix_len);
|
||||
+ plugin_loader_strlcpy(s, tmp, PLUGIN_MAX_ERRLEN);
|
||||
}
|
||||
|
||||
static inline void err_append(char* s, const char* suffix, const char* sep) {
|
||||
@@ -65,7 +95,7 @@ plugin_handle_t* plugin_load(const char* path, char* err) {
|
||||
err[0] = '\0';
|
||||
plugin_handle_t* ret = (plugin_handle_t*)calloc(1, sizeof(plugin_handle_t));
|
||||
if(!ret) {
|
||||
- strlcpy(err, "error allocating plugin handle", PLUGIN_MAX_ERRLEN);
|
||||
+ plugin_loader_strlcpy(err, "error allocating plugin handle", PLUGIN_MAX_ERRLEN);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -77,14 +107,14 @@ plugin_handle_t* plugin_load(const char* path, char* err) {
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS;
|
||||
LPTSTR msg_buf = 0;
|
||||
if(FormatMessageA(flg, 0, GetLastError(), 0, (LPTSTR)&msg_buf, 0, NULL) && msg_buf) {
|
||||
- strlcpy(err, msg_buf, PLUGIN_MAX_ERRLEN);
|
||||
+ plugin_loader_strlcpy(err, msg_buf, PLUGIN_MAX_ERRLEN);
|
||||
LocalFree(msg_buf);
|
||||
}
|
||||
}
|
||||
#else
|
||||
ret->handle = dlopen(path, RTLD_LAZY);
|
||||
if(ret->handle == NULL) {
|
||||
- strlcpy(err, (const char*)dlerror(), PLUGIN_MAX_ERRLEN);
|
||||
+ plugin_loader_strlcpy(err, (const char*)dlerror(), PLUGIN_MAX_ERRLEN);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -135,13 +165,13 @@ plugin_handle_t* plugin_load_api(const plugin_api* api, char* err) {
|
||||
// alloc and init memory
|
||||
err[0] = '\0';
|
||||
if(!api) {
|
||||
- strlcpy(err, "can't allocate plugin handle with invalid API table", PLUGIN_MAX_ERRLEN);
|
||||
+ plugin_loader_strlcpy(err, "can't allocate plugin handle with invalid API table", PLUGIN_MAX_ERRLEN);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
plugin_handle_t* ret = (plugin_handle_t*)calloc(1, sizeof(plugin_handle_t));
|
||||
if(!ret) {
|
||||
- strlcpy(err, "error allocating plugin handle", PLUGIN_MAX_ERRLEN);
|
||||
+ plugin_loader_strlcpy(err, "error allocating plugin handle", PLUGIN_MAX_ERRLEN);
|
||||
return NULL;
|
||||
}
|
||||
ret->api = *api;
|
||||
@@ -150,7 +180,7 @@ plugin_handle_t* plugin_load_api(const plugin_api* api, char* err) {
|
||||
uint32_t major, minor, patch;
|
||||
const char* ver;
|
||||
if(api->get_required_api_version == NULL) {
|
||||
- strlcpy(err, "plugin_get_required_api_version symbol not implemented", PLUGIN_MAX_ERRLEN);
|
||||
+ plugin_loader_strlcpy(err, "plugin_get_required_api_version symbol not implemented", PLUGIN_MAX_ERRLEN);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -217,7 +247,7 @@ bool plugin_check_required_api_version(const plugin_handle_t* h, char* err) {
|
||||
uint32_t major, minor, patch;
|
||||
const char *ver, *failmsg;
|
||||
if(h->api.get_required_api_version == NULL) {
|
||||
- strlcpy(err, "plugin_get_required_api_version symbol not implemented", PLUGIN_MAX_ERRLEN);
|
||||
+ plugin_loader_strlcpy(err, "plugin_get_required_api_version symbol not implemented", PLUGIN_MAX_ERRLEN);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -256,7 +286,7 @@ bool plugin_check_required_api_version(const plugin_handle_t* h, char* err) {
|
||||
|
||||
plugin_caps_t plugin_get_capabilities(const plugin_handle_t* h, char* err) {
|
||||
plugin_caps_t caps = CAP_NONE;
|
||||
- strlcpy(err, "", PLUGIN_MAX_ERRLEN);
|
||||
+ plugin_loader_strlcpy(err, "", PLUGIN_MAX_ERRLEN);
|
||||
|
||||
if(h->api.open != NULL && h->api.close != NULL && h->api.next_batch != NULL) {
|
||||
bool has_id = h->api.get_id != NULL && h->api.get_id() != 0;
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -90,5 +91,8 @@ func (s *StringBuffer) String() string {
|
|||
}
|
||||
|
||||
func (s *StringBuffer) Free() {
|
||||
if s.cPtr != nil {
|
||||
C.free(unsafe.Pointer(s.cPtr))
|
||||
s.cPtr = nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -40,8 +41,7 @@ type StringBuffer interface {
|
|||
// (C.free or similars) can lead to undefined behavior.
|
||||
CharPtr() unsafe.Pointer
|
||||
//
|
||||
// Free deallocates the underlying C-allocated buffer. The behavior of Free
|
||||
// after the first call is undefined.
|
||||
// Free deallocates the underlying C-allocated buffer.
|
||||
Free()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -25,11 +26,12 @@ limitations under the License.
|
|||
// (https://falco.org/docs/plugins/developers_guide/#example-go-plugin-dummy)
|
||||
// of a plugin written in Go that uses this package.
|
||||
//
|
||||
// For a quick start, you can refer to the provided examples of extractor plugin
|
||||
// For a quick start, you can refer to the provided examples of plugins with
|
||||
// field extraction capability
|
||||
// (https://github.com/falcosecurity/plugin-sdk-go/tree/main/examples/extractor),
|
||||
// source plugin
|
||||
// with event sourcing capability,
|
||||
// (https://github.com/falcosecurity/plugin-sdk-go/tree/main/examples/source),
|
||||
// and source plugin with extraction
|
||||
// and with both
|
||||
// (https://github.com/falcosecurity/plugin-sdk-go/tree/main/examples/full).
|
||||
//
|
||||
// This SDK is designed to be layered with different levels of abstraction:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2025 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -18,7 +19,7 @@ package sdk
|
|||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include "plugin_info.h"
|
||||
#include "plugin_types.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
@ -29,6 +30,20 @@ import (
|
|||
"github.com/falcosecurity/plugin-sdk-go/pkg/ptr"
|
||||
)
|
||||
|
||||
// pluginEventCode is the event code for the PPME_PLUGINEVENT_E scap event.
|
||||
// todo(jasondellaluce): pull this information from falcosecurity/libs in the future
|
||||
const pluginEventCode = 322
|
||||
|
||||
// todo(leogr): replace this with PLUGIN_EVENT_PAYLOAD_OFFSET from "plugin_api.h"
|
||||
|
||||
// PluginEventPayloadOffset is the size of a scap event header, plus the
|
||||
// params lenght and the plugin ID integers of a PPME_PLUGINEVENT_E event.
|
||||
// In other words, this is the size of a plugin event with an empty data payload.
|
||||
//
|
||||
// 26 bytes for the event header, plus 2*4 bytes for the parameter lengths,
|
||||
// plus 4 bytes for the plugin ID.
|
||||
const PluginEventPayloadOffset = C.sizeof_ss_plugin_event + 4 + 4 + 4
|
||||
|
||||
// EventWriter can be used to represent events produced by a plugin.
|
||||
// This interface is meant to be used in the next/next_batch.
|
||||
//
|
||||
|
@ -114,6 +129,7 @@ type EventWriters interface {
|
|||
|
||||
type eventWriters struct {
|
||||
evts []*eventWriter
|
||||
evtPtrs **C.ss_plugin_event
|
||||
}
|
||||
|
||||
// NewEventWriters creates a new instance of sdk.EventWriters.
|
||||
|
@ -130,15 +146,15 @@ func NewEventWriters(size, dataSize int64) (EventWriters, error) {
|
|||
|
||||
ret := &eventWriters{
|
||||
evts: make([]*eventWriter, size),
|
||||
evtPtrs: (**C.ss_plugin_event)(C.malloc((C.size_t)(size * C.sizeof_uintptr_t))),
|
||||
}
|
||||
pluginEvtArray := (*C.ss_plugin_event)(C.malloc((C.size_t)(size * C.sizeof_ss_plugin_event)))
|
||||
|
||||
var err error
|
||||
for i := range ret.evts {
|
||||
// get i-th element of pluginEvtArray
|
||||
evtPtr := unsafe.Pointer(uintptr(unsafe.Pointer(pluginEvtArray)) + uintptr(i*C.sizeof_ss_plugin_event))
|
||||
if ret.evts[i], err = newEventWriter(evtPtr, dataSize); err != nil {
|
||||
if ret.evts[i], err = newEventWriter(dataSize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*(**C.ss_plugin_event)(unsafe.Pointer(uintptr(unsafe.Pointer(ret.evtPtrs)) + uintptr(i*C.sizeof_uintptr_t))) = ret.evts[i].ssPluginEvt
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
@ -159,37 +175,52 @@ func (p *eventWriters) Free() {
|
|||
}
|
||||
|
||||
func (p *eventWriters) ArrayPtr() unsafe.Pointer {
|
||||
return p.evts[0].ssPluginEvt
|
||||
return unsafe.Pointer(p.evtPtrs)
|
||||
}
|
||||
|
||||
type eventWriter struct {
|
||||
data ptr.BytesReadWriter
|
||||
dataSize int64
|
||||
ssPluginEvt unsafe.Pointer
|
||||
ssPluginEvt *C.ss_plugin_event
|
||||
}
|
||||
|
||||
func newEventWriter(evtPtr unsafe.Pointer, dataSize int64) (*eventWriter, error) {
|
||||
evt := (*C.ss_plugin_event)(evtPtr)
|
||||
func newEventWriter(dataSize int64) (*eventWriter, error) {
|
||||
evt := (*C.ss_plugin_event)(C.calloc(1, C.size_t(dataSize+PluginEventPayloadOffset)))
|
||||
evt._type = pluginEventCode
|
||||
evt.ts = C.uint64_t(C.UINT64_MAX)
|
||||
evt.data = (*C.uint8_t)(C.malloc(C.size_t(dataSize)))
|
||||
evt.datalen = 0
|
||||
brw, err := ptr.NewBytesReadWriter(unsafe.Pointer(evt.data), int64(dataSize), int64(dataSize))
|
||||
|
||||
evt.tid = C.uint64_t(C.UINT64_MAX)
|
||||
evt.len = (C.uint32_t)(PluginEventPayloadOffset)
|
||||
// note(jasondellaluce): CGO fails to properly encode nparams for *reasons*,
|
||||
// so we're forced to write their value manually with an offset
|
||||
*(*C.uint32_t)(unsafe.Pointer(uintptr(unsafe.Pointer(evt)) + 22)) = 2
|
||||
// plugin ID size (4 bytes)
|
||||
*(*C.uint32_t)(unsafe.Pointer(uintptr(unsafe.Pointer(evt)) + C.sizeof_ss_plugin_event + 0)) = 4
|
||||
// data payload size (0 bytes for now)
|
||||
*(*C.uint32_t)(unsafe.Pointer(uintptr(unsafe.Pointer(evt)) + C.sizeof_ss_plugin_event + 4)) = 0
|
||||
// plugin ID value (note: putting zero makes the framework set it automatically)
|
||||
*(*C.uint32_t)(unsafe.Pointer(uintptr(unsafe.Pointer(evt)) + C.sizeof_ss_plugin_event + 8)) = 0
|
||||
// create a read/writer for the data payload
|
||||
brw, err := ptr.NewBytesReadWriter(unsafe.Pointer(uintptr(unsafe.Pointer(evt))+PluginEventPayloadOffset), int64(dataSize), int64(dataSize))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &eventWriter{
|
||||
ssPluginEvt: evtPtr,
|
||||
ssPluginEvt: evt,
|
||||
data: brw,
|
||||
dataSize: dataSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *eventWriter) dataLenPtr() *C.uint32_t {
|
||||
return (*C.uint32_t)(unsafe.Pointer(uintptr(unsafe.Pointer(p.ssPluginEvt)) + C.sizeof_ss_plugin_event + 4))
|
||||
}
|
||||
|
||||
func (p *eventWriter) Writer() io.Writer {
|
||||
p.data.SetLen(p.dataSize)
|
||||
p.data.Seek(0, io.SeekStart)
|
||||
(*C.ss_plugin_event)(p.ssPluginEvt).datalen = 0
|
||||
p.ssPluginEvt.len = (C.uint32_t)(PluginEventPayloadOffset)
|
||||
*p.dataLenPtr() = 0
|
||||
return p
|
||||
}
|
||||
|
||||
|
@ -198,7 +229,8 @@ func (p *eventWriter) Write(data []byte) (n int, err error) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
(*C.ss_plugin_event)(p.ssPluginEvt).datalen += C.uint32_t(n)
|
||||
p.ssPluginEvt.len += C.uint32_t(n)
|
||||
*p.dataLenPtr() += C.uint32_t(n)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -207,26 +239,30 @@ func (p *eventWriter) SetTimestamp(value uint64) {
|
|||
}
|
||||
|
||||
func (p *eventWriter) free() {
|
||||
C.free(unsafe.Pointer((*C.ss_plugin_event)(p.ssPluginEvt).data))
|
||||
C.free(unsafe.Pointer(p.ssPluginEvt))
|
||||
p.data = nil
|
||||
}
|
||||
|
||||
type eventReader C.ss_plugin_event
|
||||
type eventReader C.ss_plugin_event_input
|
||||
|
||||
// NewEventReader wraps a pointer to a ss_plugin_event C structure to create
|
||||
// NewEventReader wraps a pointer to a ss_plugin_event_input C structure to create
|
||||
// a new instance of EventReader. It's not possible to check that the pointer is valid.
|
||||
// Passing an invalid pointer may cause undefined behavior.
|
||||
func NewEventReader(ssPluginEvt unsafe.Pointer) EventReader {
|
||||
return (*eventReader)(ssPluginEvt)
|
||||
func NewEventReader(ssPluginEvtInput unsafe.Pointer) EventReader {
|
||||
return (*eventReader)(ssPluginEvtInput)
|
||||
}
|
||||
|
||||
func (e *eventReader) Reader() io.ReadSeeker {
|
||||
brw, _ := ptr.NewBytesReadWriter(unsafe.Pointer(e.data), int64(e.datalen), int64(e.datalen))
|
||||
if e.evt._type != pluginEventCode {
|
||||
panic(fmt.Sprintf("plugin-sdk-go/sdk: reveived extraction request for non-plugin event (code=%d)", e.evt._type))
|
||||
}
|
||||
datalen := *(*C.uint32_t)(unsafe.Pointer(uintptr(unsafe.Pointer(e.evt)) + C.sizeof_ss_plugin_event + 4))
|
||||
brw, _ := ptr.NewBytesReadWriter(unsafe.Pointer(uintptr(unsafe.Pointer(e.evt))+PluginEventPayloadOffset), int64(datalen), int64(datalen))
|
||||
return brw
|
||||
}
|
||||
|
||||
func (e *eventReader) Timestamp() uint64 {
|
||||
return uint64(e.ts)
|
||||
return uint64(e.evt.ts)
|
||||
}
|
||||
|
||||
func (e *eventReader) EventNum() uint64 {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -69,7 +70,7 @@ func BenchmarkEventWritersNext(b *testing.B) {
|
|||
}
|
||||
|
||||
func BenchmarkEventWritersNextBatch(b *testing.B) {
|
||||
writers, err := NewEventWriters(DefaultBatchSize, int64(DefaultEvtSize))
|
||||
writers, err := NewEventWriters(int64(DefaultBatchSize), int64(DefaultEvtSize))
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
b.Fail()
|
||||
|
@ -88,7 +89,7 @@ func BenchmarkEventWritersNextBatch(b *testing.B) {
|
|||
}
|
||||
|
||||
func TestEventWritersNextBatch(t *testing.T) {
|
||||
events, err := NewEventWriters(DefaultBatchSize, int64(DefaultEvtSize))
|
||||
events, err := NewEventWriters(int64(DefaultBatchSize), int64(DefaultEvtSize))
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
t.Fail()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2025 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -17,15 +18,39 @@ limitations under the License.
|
|||
package sdk
|
||||
|
||||
/*
|
||||
#include "plugin_info.h"
|
||||
#include "plugin_types.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// NOTE: This is just an replica of the anonymous union nested inside
|
||||
// ss_plugin_extract_field. The only difference is that each union field has
|
||||
// one pointer level less than its equivalent of ss_plugin_extract_field.
|
||||
// Keep this in sync with plugin_types.h in case new types will be supported.
|
||||
typedef union {
|
||||
const char* str;
|
||||
uint64_t u64;
|
||||
uint32_t u32;
|
||||
ss_plugin_bool boolean;
|
||||
ss_plugin_byte_buffer buf;
|
||||
} field_result_t;
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/ptr"
|
||||
)
|
||||
|
||||
const (
|
||||
// Initial and minimum length with which the array of results is allocated
|
||||
// for a each extractRequest struct.
|
||||
minResultBufferLen = 512
|
||||
)
|
||||
|
||||
// ExtractRequest represents an high-level abstraction that wraps a pointer to
|
||||
// a ss_plugin_extract_field C structure, providing methods for accessing its
|
||||
// fields in a go-friendly way.
|
||||
|
@ -35,28 +60,72 @@ type ExtractRequest interface {
|
|||
FieldID() uint64
|
||||
//
|
||||
// FieldType returns the type of the field for which the value extraction
|
||||
// is requested. For now, only sdk.FieldTypeUint64 and
|
||||
// sdk.FieldTypeCharBuf are supported.
|
||||
// is requested. For now, the supported types are:
|
||||
// - sdk.FieldTypeBool
|
||||
// - sdk.FieldTypeUint64
|
||||
// - sdk.FieldTypeCharBuf
|
||||
// - sdk.FieldTypeRelTime
|
||||
// - sdk.FieldTypeAbsTime
|
||||
// - sdk.FieldTypeIPAddr
|
||||
// - sdk.FieldTypeIPNet
|
||||
FieldType() uint32
|
||||
//
|
||||
// Field returns the name of the field for which the value extraction
|
||||
// is requested.
|
||||
Field() string
|
||||
//
|
||||
// Arg returns the argument passed for the requested field. An empty string
|
||||
// is returned if no argument is specified.
|
||||
Arg() string
|
||||
// ArgKey must be used when the field arg is a generic string (like a key
|
||||
// in a lookup operation). This field must have the `isKey` flag enabled.
|
||||
ArgKey() string
|
||||
//
|
||||
// ArgIndex must be used when the field arg is an index (0<=index<=2^64-1).
|
||||
// This field must have the `isIndex` flag enabled.
|
||||
ArgIndex() uint64
|
||||
//
|
||||
// ArgPresent clearly defines when an argument is valid or not.
|
||||
ArgPresent() bool
|
||||
//
|
||||
// IsList returns true if the field extracts lists of values.
|
||||
IsList() bool
|
||||
//
|
||||
// SetValue sets the extracted value for the requested field.
|
||||
//
|
||||
// The underlying type of v must be compatible with the field type
|
||||
// associated to this extract request (as the returned by FieldType()),
|
||||
// otherwise SetValue will panic.
|
||||
//
|
||||
// Coherently to the FieldType of the extraction request, this function
|
||||
// panics if the passed value is not one of the following types (or slices
|
||||
// of them, in case IsList() returns true):
|
||||
// - sdk.FieldTypeBool: bool
|
||||
// - sdk.FieldTypeUint64: uint64
|
||||
// - sdk.FieldTypeCharBuf: string
|
||||
// - sdk.FieldTypeRelTime: time.Duration, *time.Duration
|
||||
// - sdk.FieldTypeAbsTime: time.Time, *time.Time
|
||||
// - sdk.FieldTypeIPAddr: net.IP, *net.IP
|
||||
// - sdk.FieldTypeIPNet: net.IPNet, *net.IPNet
|
||||
SetValue(v interface{})
|
||||
//
|
||||
// TODO SetValueOffsets sets the start offset and length of one or
|
||||
// more fields. The start offset for each field must be from the
|
||||
// beginning of the event to the start of the field data.
|
||||
// sdk.PluginEventPayloadOffset should be used to get the event
|
||||
// header size. {0,0} can be used to indicate that the field doesn't
|
||||
// correspond to any bytes in the event or log data.
|
||||
SetValueOffset(start, length uint32)
|
||||
//
|
||||
// SetPtr sets a pointer to a ss_plugin_extract_field C structure to
|
||||
// be wrapped in this instance of ExtractRequest.
|
||||
SetPtr(unsafe.Pointer)
|
||||
//
|
||||
// SetOffsetPtrs sets the pointers to the memory locations that will
|
||||
// hold the values set by SetValueOffset.
|
||||
SetOffsetPtrs(startPtr, lengthPtr unsafe.Pointer)
|
||||
//
|
||||
// WantOffset returns true if the caller is requesting the offset
|
||||
// for the current field.
|
||||
//
|
||||
WantOffset() bool
|
||||
}
|
||||
|
||||
// ExtractRequestPool represents a pool of reusable ExtractRequest objects.
|
||||
|
@ -67,6 +136,18 @@ type ExtractRequestPool interface {
|
|||
// position inside the pool. Indexes can be non-contiguous.
|
||||
Get(requestIndex int) ExtractRequest
|
||||
//
|
||||
// MakeOffsetArrayPtrs allocates and initializes the start offset and length arrays,
|
||||
// then assigns their pointers to the provided ss_plugin_extract_value_offsets structure.
|
||||
// These arrays track field boundaries for extracted values.
|
||||
//
|
||||
// This function must be called before using any ExtractRequest returned by Get,
|
||||
// as the arrays are uninitialized until this point.
|
||||
//
|
||||
// Parameters:
|
||||
// - extractValueOffsets: pointer to ss_plugin_extract_value_offsets structure to populate
|
||||
// - cap: required capacity for the offset arrays
|
||||
MakeOffsetArrayPtrs(extractValueOffsets unsafe.Pointer, cap uint32)
|
||||
//
|
||||
// Free deallocates any memory used by the pool that can't be disposed
|
||||
// through garbage collection. The behavior of Free after the first call
|
||||
// is undefined.
|
||||
|
@ -75,22 +156,63 @@ type ExtractRequestPool interface {
|
|||
|
||||
type extractRequestPool struct {
|
||||
reqs map[uint]*extractRequest
|
||||
startArrayPtr, lengthArrayPtr unsafe.Pointer
|
||||
arrayPtrCap uint32
|
||||
}
|
||||
|
||||
func (e *extractRequestPool) Get(requestIndex int) ExtractRequest {
|
||||
r, ok := e.reqs[uint(requestIndex)]
|
||||
if !ok && requestIndex >= 0 {
|
||||
r = &extractRequest{
|
||||
strBuf: &ptr.StringBuffer{},
|
||||
resBuf: (*C.field_result_t)(C.malloc((C.size_t)(minResultBufferLen * C.sizeof_field_result_t))),
|
||||
resBufLen: minResultBufferLen,
|
||||
resStrBufs: []StringBuffer{&ptr.StringBuffer{}},
|
||||
resValPtrs: make([]unsafe.Pointer, minResultBufferLen),
|
||||
}
|
||||
for i := 0; i < minResultBufferLen; i++ {
|
||||
ptr := (*C.field_result_t)(unsafe.Pointer(uintptr(unsafe.Pointer(r.resBuf)) + uintptr(i*C.sizeof_field_result_t)))
|
||||
r.resValPtrs[i] = unsafe.Pointer(ptr)
|
||||
}
|
||||
e.reqs[uint(requestIndex)] = r
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (e *extractRequestPool) MakeOffsetArrayPtrs(extractValueOffsets unsafe.Pointer, cap uint32) {
|
||||
// If we need more capacity, free old arrays and allocate new ones
|
||||
if cap > e.arrayPtrCap {
|
||||
// Free existing arrays if they exist
|
||||
if e.arrayPtrCap > 0 {
|
||||
C.free(e.startArrayPtr)
|
||||
C.free(e.lengthArrayPtr)
|
||||
}
|
||||
|
||||
// Allocate new arrays with required capacity - calloc zeros the memory
|
||||
e.startArrayPtr = unsafe.Pointer(C.calloc(C.size_t(cap), C.sizeof_uint32_t))
|
||||
e.lengthArrayPtr = unsafe.Pointer(C.calloc(C.size_t(cap), C.sizeof_uint32_t))
|
||||
|
||||
e.arrayPtrCap = cap
|
||||
} else {
|
||||
// Capacity is sufficient, just zero the arrays for fresh extraction request
|
||||
size := C.size_t(cap) * C.sizeof_uint32_t
|
||||
C.memset(e.startArrayPtr, 0, size)
|
||||
C.memset(e.lengthArrayPtr, 0, size)
|
||||
}
|
||||
|
||||
(*C.ss_plugin_extract_value_offsets)(extractValueOffsets).start = (*C.uint32_t)(e.startArrayPtr)
|
||||
(*C.ss_plugin_extract_value_offsets)(extractValueOffsets).length = (*C.uint32_t)(e.lengthArrayPtr)
|
||||
}
|
||||
|
||||
func (e *extractRequestPool) Free() {
|
||||
for _, v := range e.reqs {
|
||||
v.strBuf.Free()
|
||||
for _, b := range v.resStrBufs {
|
||||
b.Free()
|
||||
}
|
||||
C.free(unsafe.Pointer(v.resBuf))
|
||||
}
|
||||
if e.arrayPtrCap > 0 {
|
||||
C.free(e.startArrayPtr)
|
||||
C.free(e.lengthArrayPtr)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,13 +226,31 @@ func NewExtractRequestPool() ExtractRequestPool {
|
|||
|
||||
type extractRequest struct {
|
||||
req *C.ss_plugin_extract_field
|
||||
strBuf StringBuffer
|
||||
// Pointer to the field's offset
|
||||
resOffsetStart *C.uint32_t
|
||||
// Pointer to the field's length
|
||||
resOffsetLength *C.uint32_t
|
||||
// Pointer to a C-allocated array of field_result_t
|
||||
resBuf *C.field_result_t
|
||||
// Length of the array pointed by resBuf
|
||||
resBufLen uint32
|
||||
// List of StringBuffer to return string results
|
||||
resStrBufs []StringBuffer
|
||||
// List of BytesReadWriter to return binary results
|
||||
resBinBufs []ptr.BytesReadWriter
|
||||
// List of *field_result_t to be filled with the values of a request
|
||||
resValPtrs []unsafe.Pointer
|
||||
}
|
||||
|
||||
func (e *extractRequest) SetPtr(pef unsafe.Pointer) {
|
||||
e.req = (*C.ss_plugin_extract_field)(pef)
|
||||
}
|
||||
|
||||
func (e *extractRequest) SetOffsetPtrs(startPtr, lengthPtr unsafe.Pointer) {
|
||||
e.resOffsetStart = (*C.uint32_t)(startPtr)
|
||||
e.resOffsetLength = (*C.uint32_t)(lengthPtr)
|
||||
}
|
||||
|
||||
func (e *extractRequest) FieldID() uint64 {
|
||||
return uint64(e.req.field_id)
|
||||
}
|
||||
|
@ -123,19 +263,184 @@ func (e *extractRequest) Field() string {
|
|||
return ptr.GoString(unsafe.Pointer(e.req.field))
|
||||
}
|
||||
|
||||
func (e *extractRequest) Arg() string {
|
||||
return ptr.GoString(unsafe.Pointer(e.req.arg))
|
||||
func (e *extractRequest) ArgKey() string {
|
||||
return ptr.GoString(unsafe.Pointer(e.req.arg_key))
|
||||
}
|
||||
|
||||
func (e *extractRequest) ArgIndex() uint64 {
|
||||
return uint64(e.req.arg_index)
|
||||
}
|
||||
|
||||
func (e *extractRequest) ArgPresent() bool {
|
||||
return e.req.arg_present != 0
|
||||
}
|
||||
|
||||
func (e *extractRequest) IsList() bool {
|
||||
return e.req.flist != 0
|
||||
}
|
||||
|
||||
func (e *extractRequest) boolToU32(v bool) uint32 {
|
||||
if v {
|
||||
return uint32(1)
|
||||
}
|
||||
return uint32(0)
|
||||
}
|
||||
|
||||
func (e *extractRequest) resizeResValPtrs(length, dataSize int) []unsafe.Pointer {
|
||||
if e.resBufLen < uint32(length) {
|
||||
C.free(unsafe.Pointer(e.resBuf))
|
||||
e.resBufLen = uint32(length)
|
||||
e.resBuf = (*C.field_result_t)(C.malloc((C.size_t)(e.resBufLen * C.sizeof_field_result_t)))
|
||||
e.resValPtrs = make([]unsafe.Pointer, length)
|
||||
}
|
||||
// we need to recompute the pointers everytime, because
|
||||
// the same extractRequest can be reused with different dataSizes.
|
||||
for i := 0; i < length; i++ {
|
||||
e.resValPtrs[i] = unsafe.Pointer(uintptr(unsafe.Pointer(e.resBuf)) + uintptr(i*dataSize))
|
||||
}
|
||||
e.req.res_len = (C.uint64_t)(length)
|
||||
return e.resValPtrs[:length]
|
||||
}
|
||||
|
||||
func (e *extractRequest) SetValue(v interface{}) {
|
||||
switch e.FieldType() {
|
||||
case FieldTypeBool:
|
||||
if e.IsList() {
|
||||
for i, ptr := range e.resizeResValPtrs(len(v.([]bool)), C.sizeof_uint32_t) {
|
||||
*((*C.uint32_t)(ptr)) = (C.uint32_t)(e.boolToU32((v.([]bool))[i]))
|
||||
}
|
||||
} else {
|
||||
ptr := e.resizeResValPtrs(1, C.sizeof_uint32_t)[0]
|
||||
*((*C.uint32_t)(ptr)) = (C.uint32_t)(e.boolToU32(v.(bool)))
|
||||
}
|
||||
case FieldTypeUint64:
|
||||
e.req.res_u64 = (C.uint64_t)(v.(uint64))
|
||||
if e.IsList() {
|
||||
for i, ptr := range e.resizeResValPtrs(len(v.([]uint64)), C.sizeof_uint64_t) {
|
||||
*((*C.uint64_t)(ptr)) = (C.uint64_t)((v.([]uint64))[i])
|
||||
}
|
||||
} else {
|
||||
ptr := e.resizeResValPtrs(1, C.sizeof_uint64_t)[0]
|
||||
*((*C.uint64_t)(ptr)) = (C.uint64_t)(v.(uint64))
|
||||
}
|
||||
case FieldTypeCharBuf:
|
||||
e.strBuf.Write(v.(string))
|
||||
e.req.res_str = (*C.char)(e.strBuf.CharPtr())
|
||||
if e.IsList() {
|
||||
for i, out := range e.resizeResValPtrs(len(v.([]string)), C.sizeof_uintptr_t) {
|
||||
if len(e.resStrBufs) <= i {
|
||||
e.resStrBufs = append(e.resStrBufs, &ptr.StringBuffer{})
|
||||
}
|
||||
e.resStrBufs[i].Write(v.([]string)[i])
|
||||
*((**C.char)(out)) = (*C.char)(e.resStrBufs[i].CharPtr())
|
||||
}
|
||||
} else {
|
||||
out := e.resizeResValPtrs(1, C.sizeof_uintptr_t)[0]
|
||||
e.resStrBufs[0].Write(v.(string))
|
||||
*((**C.char)(out)) = (*C.char)(e.resStrBufs[0].CharPtr())
|
||||
}
|
||||
case FieldTypeRelTime:
|
||||
if e.IsList() {
|
||||
if val, ok := v.([]time.Duration); ok {
|
||||
for i, ptr := range e.resizeResValPtrs(len(val), C.sizeof_uint64_t) {
|
||||
*((*C.uint64_t)(ptr)) = (C.uint64_t)(val[i].Nanoseconds())
|
||||
}
|
||||
} else {
|
||||
for i, ptr := range e.resizeResValPtrs(len(v.([]*time.Duration)), C.sizeof_uint64_t) {
|
||||
*((*C.uint64_t)(ptr)) = (C.uint64_t)(v.([]*time.Duration)[i].Nanoseconds())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ptr := e.resizeResValPtrs(1, C.sizeof_uint64_t)[0]
|
||||
if val, ok := v.(time.Duration); ok {
|
||||
*((*C.uint64_t)(ptr)) = (C.uint64_t)(val.Nanoseconds())
|
||||
} else {
|
||||
*((*C.uint64_t)(ptr)) = (C.uint64_t)(v.(*time.Duration).Nanoseconds())
|
||||
}
|
||||
}
|
||||
case FieldTypeAbsTime:
|
||||
if e.IsList() {
|
||||
if val, ok := v.([]time.Time); ok {
|
||||
for i, ptr := range e.resizeResValPtrs(len(val), C.sizeof_uint64_t) {
|
||||
*((*C.uint64_t)(ptr)) = (C.uint64_t)(val[i].UnixNano())
|
||||
}
|
||||
} else {
|
||||
for i, ptr := range e.resizeResValPtrs(len(v.([]*time.Time)), C.sizeof_uint64_t) {
|
||||
*((*C.uint64_t)(ptr)) = (C.uint64_t)(v.([]*time.Time)[i].UnixNano())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ptr := e.resizeResValPtrs(1, C.sizeof_uint64_t)[0]
|
||||
if val, ok := v.(time.Time); ok {
|
||||
*((*C.uint64_t)(ptr)) = (C.uint64_t)(val.UnixNano())
|
||||
} else {
|
||||
*((*C.uint64_t)(ptr)) = (C.uint64_t)(v.(*time.Time).UnixNano())
|
||||
}
|
||||
}
|
||||
case FieldTypeIPAddr:
|
||||
if e.IsList() {
|
||||
if val, ok := v.([]net.IP); ok {
|
||||
for i, ptr := range e.resizeResValPtrs(len(val), C.sizeof_struct_ss_plugin_byte_buffer) {
|
||||
val := ([]byte)((val)[i])
|
||||
(*C.struct_ss_plugin_byte_buffer)(ptr).len = C.uint32_t(len(val))
|
||||
(*C.struct_ss_plugin_byte_buffer)(ptr).ptr = unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&val)).Data)
|
||||
}
|
||||
} else {
|
||||
for i, ptr := range e.resizeResValPtrs(len(v.([]*net.IP)), C.sizeof_struct_ss_plugin_byte_buffer) {
|
||||
val := ([]byte)(*(v.([]*net.IP))[i])
|
||||
(*C.struct_ss_plugin_byte_buffer)(ptr).len = C.uint32_t(len(val))
|
||||
(*C.struct_ss_plugin_byte_buffer)(ptr).ptr = unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&val)).Data)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var val []byte
|
||||
ptr := e.resizeResValPtrs(1, C.sizeof_struct_ss_plugin_byte_buffer)[0]
|
||||
if ipv, ok := v.(net.IP); ok {
|
||||
val = ([]byte)(ipv)
|
||||
} else {
|
||||
val = ([]byte)(*(v.(*net.IP)))
|
||||
}
|
||||
(*C.struct_ss_plugin_byte_buffer)(ptr).len = C.uint32_t(len(val))
|
||||
(*C.struct_ss_plugin_byte_buffer)(ptr).ptr = unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&val)).Data)
|
||||
}
|
||||
case FieldTypeIPNet:
|
||||
if e.IsList() {
|
||||
if ipv, ok := v.([]net.IPNet); ok {
|
||||
for i, ptr := range e.resizeResValPtrs(len(ipv), C.sizeof_struct_ss_plugin_byte_buffer) {
|
||||
val := ([]byte)((ipv)[i].IP)
|
||||
(*C.struct_ss_plugin_byte_buffer)(ptr).len = C.uint32_t(len(val))
|
||||
(*C.struct_ss_plugin_byte_buffer)(ptr).ptr = unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&val)).Data)
|
||||
}
|
||||
} else {
|
||||
for i, ptr := range e.resizeResValPtrs(len(v.([]*net.IPNet)), C.sizeof_struct_ss_plugin_byte_buffer) {
|
||||
val := ([]byte)((v.([]*net.IPNet))[i].IP)
|
||||
(*C.struct_ss_plugin_byte_buffer)(ptr).len = C.uint32_t(len(val))
|
||||
(*C.struct_ss_plugin_byte_buffer)(ptr).ptr = unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&val)).Data)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var val []byte
|
||||
ptr := e.resizeResValPtrs(1, C.sizeof_struct_ss_plugin_byte_buffer)[0]
|
||||
if ipv, ok := v.(net.IPNet); ok {
|
||||
val = ([]byte)(ipv.IP)
|
||||
} else {
|
||||
val = ([]byte)(v.(*net.IPNet).IP)
|
||||
}
|
||||
(*C.struct_ss_plugin_byte_buffer)(ptr).len = C.uint32_t(len(val))
|
||||
(*C.struct_ss_plugin_byte_buffer)(ptr).ptr = unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&val)).Data)
|
||||
}
|
||||
default:
|
||||
panic("plugin-sdk-go/sdk: called SetValue with unsupported field type")
|
||||
}
|
||||
e.req.field_present = true
|
||||
*((*C.uintptr_t)(unsafe.Pointer(&e.req.res))) = *(*C.uintptr_t)(unsafe.Pointer(&e.resBuf))
|
||||
}
|
||||
|
||||
func (e *extractRequest) WantOffset() bool {
|
||||
return e.resOffsetStart != nil && e.resOffsetLength != nil
|
||||
}
|
||||
|
||||
func (e *extractRequest) SetValueOffset(start, length uint32) {
|
||||
if !e.WantOffset() {
|
||||
return
|
||||
}
|
||||
|
||||
*e.resOffsetStart = C.uint32_t(start)
|
||||
*e.resOffsetLength = C.uint32_t(length)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2025 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -17,34 +18,93 @@ limitations under the License.
|
|||
package sdk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/ptr"
|
||||
)
|
||||
|
||||
func allocSSPluginExtractField(fid, ftype uint32, fname, farg string) (*_Ctype_ss_plugin_extract_field, func()) {
|
||||
func boolToUint32(v bool) _Ctype_uint32_t {
|
||||
if v {
|
||||
return _Ctype_uint32_t(1)
|
||||
}
|
||||
return _Ctype_uint32_t(0)
|
||||
}
|
||||
|
||||
func allocSSPluginExtractField(fid, ftype uint32, fname, farg_key string, farg_index uint64, farg_present bool, list bool) (*_Ctype_ss_plugin_extract_field, func()) {
|
||||
ret := &_Ctype_ss_plugin_extract_field{}
|
||||
ret.field_id = _Ctype_uint32_t(fid)
|
||||
ret.ftype = _Ctype_uint32_t(ftype)
|
||||
ret.arg_present = boolToUint32(farg_present)
|
||||
ret.flist = boolToUint32(list)
|
||||
ret.arg_index = _Ctype_uint64_t(farg_index)
|
||||
|
||||
argBuf := ptr.StringBuffer{}
|
||||
argKeyBuf := ptr.StringBuffer{}
|
||||
fnameBuf := ptr.StringBuffer{}
|
||||
fnameBuf.Write(fname)
|
||||
ret.field = (*_Ctype_char)(fnameBuf.CharPtr())
|
||||
if len(farg) > 0 {
|
||||
argBuf.Write(farg)
|
||||
ret.arg = (*_Ctype_char)(argBuf.CharPtr())
|
||||
if len(farg_key) > 0 {
|
||||
argKeyBuf.Write(farg_key)
|
||||
ret.arg_key = (*_Ctype_char)(argKeyBuf.CharPtr())
|
||||
} else {
|
||||
ret.arg = nil
|
||||
ret.arg_key = nil
|
||||
}
|
||||
|
||||
return ret, func() {
|
||||
argBuf.Free()
|
||||
argKeyBuf.Free()
|
||||
fnameBuf.Free()
|
||||
}
|
||||
}
|
||||
|
||||
func allocSSPluginExtractOffset() *_Ctype_uint32_t {
|
||||
var offset _Ctype_uint32_t = _Ctype_uint32_t(0)
|
||||
return &offset
|
||||
}
|
||||
|
||||
func getBoolResSSPluingExtractField(t *testing.T, ptr *_Ctype_ss_plugin_extract_field, index int) bool {
|
||||
if ptr.res_len < (_Ctype_uint64_t)(index) {
|
||||
t.Errorf("trying to access extract field res at index %d, but res len is %d", index, (int)(ptr.res_len))
|
||||
}
|
||||
value := (uint32)(*((*_Ctype_uint32_t)(unsafe.Pointer(uintptr(*(*_Ctype_uintptr_t)(unsafe.Pointer(&ptr.res))) + uintptr(index*_Ciconst_sizeof_uint32_t)))))
|
||||
return value != uint32(0)
|
||||
}
|
||||
|
||||
func getStrResSSPluingExtractField(t *testing.T, p *_Ctype_ss_plugin_extract_field, index int) string {
|
||||
if p.res_len < (_Ctype_uint64_t)(index) {
|
||||
t.Errorf("trying to access extract field res at index %d, but res len is %d", index, (int)(p.res_len))
|
||||
}
|
||||
return ptr.GoString(unsafe.Pointer((*((**_Ctype_char)(unsafe.Pointer(uintptr(*(*_Ctype_uintptr_t)(unsafe.Pointer(&p.res))) + uintptr(index*_Ciconst_sizeof_uintptr_t)))))))
|
||||
}
|
||||
|
||||
func getU64ResSSPluingExtractField(t *testing.T, ptr *_Ctype_ss_plugin_extract_field, index int) uint64 {
|
||||
if ptr.res_len < (_Ctype_uint64_t)(index) {
|
||||
t.Errorf("trying to access extract field res at index %d, but res len is %d", index, (int)(ptr.res_len))
|
||||
}
|
||||
return (uint64)(*((*_Ctype_uint64_t)(unsafe.Pointer(uintptr(*(*_Ctype_uintptr_t)(unsafe.Pointer(&ptr.res))) + uintptr(index*_Ciconst_sizeof_uint64_t)))))
|
||||
}
|
||||
|
||||
func getBinResSSPluingExtractField(t *testing.T, p *_Ctype_ss_plugin_extract_field, index int) []byte {
|
||||
if p.res_len < (_Ctype_uint64_t)(index) {
|
||||
t.Errorf("trying to access extract field res at index %d, but res len is %d", index, (int)(p.res_len))
|
||||
}
|
||||
|
||||
bufListPtr := *(*unsafe.Pointer)(unsafe.Pointer(&p.res))
|
||||
curBufPtr := (unsafe.Pointer)(unsafe.Pointer(uintptr(bufListPtr) + uintptr(index*_Ciconst_sizeof_field_result_t)))
|
||||
size := *(*uint32)(curBufPtr)
|
||||
buf := make([]byte, size)
|
||||
ptrBytes := *(*unsafe.Pointer)(unsafe.Pointer(uintptr(curBufPtr) + uintptr(8)))
|
||||
for i := 0; i < int(size); i++ {
|
||||
buf[i] = *(*uint8)(unsafe.Pointer(uintptr(unsafe.Pointer(ptrBytes)) + uintptr(i)))
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func getResSSPluginExtractOffsetFromPtr(offset *_Ctype_uint32_t) uint32 {
|
||||
return uint32(*offset)
|
||||
}
|
||||
|
||||
func assertPanic(t *testing.T, fun func()) {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
|
@ -59,10 +119,11 @@ func TestNewExtractRequestPool(t *testing.T) {
|
|||
// Access should be non-contiguous
|
||||
for i := 0; i < 20; i += 2 {
|
||||
req := pool.Get(i)
|
||||
cstruct, freeCStruct := allocSSPluginExtractField(5, FieldTypeUint64, "test.field", "arg")
|
||||
cstruct, freeCStruct := allocSSPluginExtractField(5, FieldTypeUint64, "test.field", "5", 5, true, false)
|
||||
req.SetPtr(unsafe.Pointer(cstruct))
|
||||
if req.FieldType() != FieldTypeUint64 || req.FieldID() != 5 || req.Field() != "test.field" || req.Arg() != "arg" {
|
||||
println(req.FieldType(), ", ", req.FieldID(), ", ", req.Field(), ", ", req.Arg())
|
||||
if req.FieldType() != FieldTypeUint64 || req.FieldID() != 5 || req.Field() != "test.field" || req.ArgKey() != "5" ||
|
||||
req.ArgIndex() != 5 || req.ArgPresent() != true || req.IsList() != false {
|
||||
println(req.FieldType(), ", ", req.FieldID(), ", ", req.Field(), ", ", req.ArgKey(), ", ", req.ArgIndex())
|
||||
t.Errorf("could not read fields from sdk.ExtractRequest")
|
||||
}
|
||||
freeCStruct()
|
||||
|
@ -71,24 +132,190 @@ func TestNewExtractRequestPool(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestExtractRequestSetValue(t *testing.T) {
|
||||
pool := NewExtractRequestPool()
|
||||
u64Ptr, freeU64Ptr := allocSSPluginExtractField(1, FieldTypeUint64, "test.u64", "")
|
||||
strPtr, freeStrPtr := allocSSPluginExtractField(2, FieldTypeCharBuf, "test.str", "")
|
||||
u64Req := pool.Get(0)
|
||||
strReq := pool.Get(1)
|
||||
u64Req.SetPtr(unsafe.Pointer(u64Ptr))
|
||||
strReq.SetPtr(unsafe.Pointer(strPtr))
|
||||
// init test data
|
||||
testStr := "test str"
|
||||
testU64 := uint64(99)
|
||||
testU64Start := uint32(PluginEventPayloadOffset)
|
||||
testU64Length := uint32(8)
|
||||
testBool := true
|
||||
testIPv6 := net.IPv6loopback
|
||||
testStrList := make([]string, 0)
|
||||
testU64List := make([]uint64, 0)
|
||||
testU64ListStart := uint32(PluginEventPayloadOffset)
|
||||
testU64ListLength := uint32(0)
|
||||
testBoolList := make([]bool, 0)
|
||||
dataArray := make([]byte, (minResultBufferLen+1)*int(len(testIPv6)))
|
||||
for i := 0; i < (minResultBufferLen+1)*int(len(testIPv6)); i++ {
|
||||
dataArray[i] = byte(i)
|
||||
}
|
||||
testIPv6List := make([]net.IP, minResultBufferLen+1)
|
||||
for i := 0; i < minResultBufferLen+1; i++ {
|
||||
testStrList = append(testStrList, fmt.Sprintf("test-%d", i))
|
||||
testU64List = append(testU64List, uint64(i))
|
||||
testU64ListLength += testU64Length
|
||||
testBoolList = append(testBoolList, i%3 == 0)
|
||||
testIPv6List[i] = dataArray[i*len(testIPv6) : (i+1)*len(testIPv6)]
|
||||
}
|
||||
|
||||
// init extract requests
|
||||
pool := NewExtractRequestPool()
|
||||
u64Ptr, freeU64Ptr := allocSSPluginExtractField(1, FieldTypeUint64, "test.u64", "", 0, true, false)
|
||||
u64OffsetStartPtr := allocSSPluginExtractOffset()
|
||||
u64OffsetLengthPtr := allocSSPluginExtractOffset()
|
||||
u64ListPtr, freeU64ListPtr := allocSSPluginExtractField(2, FieldTypeUint64, "test.u64", "", 0, true, true)
|
||||
u64ListOffsetStartPtr := allocSSPluginExtractOffset()
|
||||
u64ListOffsetLengthPtr := allocSSPluginExtractOffset()
|
||||
strPtr, freeStrPtr := allocSSPluginExtractField(3, FieldTypeCharBuf, "test.str", "", 0, true, false)
|
||||
strListPtr, freeStrListPtr := allocSSPluginExtractField(4, FieldTypeCharBuf, "test.str", "", 0, true, true)
|
||||
boolPtr, freeBoolPtr := allocSSPluginExtractField(5, FieldTypeBool, "test.bool", "", 0, true, false)
|
||||
boolListPtr, freeBoolListPtr := allocSSPluginExtractField(6, FieldTypeBool, "test.bool", "", 0, true, true)
|
||||
binPtr, freeBinPtr := allocSSPluginExtractField(7, FieldTypeIPAddr, "test.ipv6addr", "", 0, true, false)
|
||||
binListPtr, freeBinListPtr := allocSSPluginExtractField(8, FieldTypeIPAddr, "test.ipv6addr", "", 0, true, true)
|
||||
u64Req := pool.Get(0)
|
||||
u64ReqList := pool.Get(1)
|
||||
strReq := pool.Get(2)
|
||||
strReqList := pool.Get(3)
|
||||
boolReq := pool.Get(4)
|
||||
boolReqList := pool.Get(5)
|
||||
binReq := pool.Get(6)
|
||||
binReqList := pool.Get(7)
|
||||
u64Req.SetPtr(unsafe.Pointer(u64Ptr))
|
||||
u64Req.SetOffsetPtrs(unsafe.Pointer(u64OffsetStartPtr), unsafe.Pointer(u64OffsetLengthPtr))
|
||||
u64ReqList.SetPtr(unsafe.Pointer(u64ListPtr))
|
||||
u64ReqList.SetOffsetPtrs(unsafe.Pointer(u64ListOffsetStartPtr), unsafe.Pointer(u64ListOffsetLengthPtr))
|
||||
strReq.SetPtr(unsafe.Pointer(strPtr))
|
||||
strReqList.SetPtr(unsafe.Pointer(strListPtr))
|
||||
boolReq.SetPtr(unsafe.Pointer(boolPtr))
|
||||
boolReqList.SetPtr(unsafe.Pointer(boolListPtr))
|
||||
binReq.SetPtr(unsafe.Pointer(binPtr))
|
||||
binReqList.SetPtr(unsafe.Pointer(binListPtr))
|
||||
|
||||
// check that info is passed-through correctly
|
||||
if u64Req.FieldID() != 1 {
|
||||
t.Errorf("expected value '%d', but found '%d'", 1, u64Req.FieldID())
|
||||
}
|
||||
if u64Req.FieldType() != FieldTypeUint64 {
|
||||
t.Errorf("expected value '%d', but found '%d'", FieldTypeUint64, u64Req.FieldType())
|
||||
}
|
||||
if u64Req.Field() != "test.u64" {
|
||||
t.Errorf("expected value '%s', but found '%s'", "test.u64", u64Req.Field())
|
||||
}
|
||||
if u64Req.ArgKey() != "" {
|
||||
t.Errorf("expected value '%s', but found '%s'", "", u64Req.ArgKey())
|
||||
}
|
||||
if u64Req.ArgIndex() != 0 {
|
||||
t.Errorf("expected value '%s', but found '%s'", "", u64Req.ArgKey())
|
||||
}
|
||||
if u64Req.ArgPresent() != true {
|
||||
t.Errorf("expected value '%s', but found '%s'", "", u64Req.ArgKey())
|
||||
}
|
||||
if u64Req.IsList() != false {
|
||||
t.Errorf("expected value '%t', but found '%t'", false, u64Req.IsList())
|
||||
}
|
||||
if strReqList.IsList() != true {
|
||||
t.Errorf("expected value '%t', but found '%t'", true, strReqList.IsList())
|
||||
}
|
||||
|
||||
// check panics
|
||||
assertPanic(t, func() {
|
||||
u64Req.SetValue("test")
|
||||
u64Req.SetValue(bool(true))
|
||||
boolReq.SetValue([]byte{0x41, 0x41, 0x41, 0x41})
|
||||
})
|
||||
assertPanic(t, func() {
|
||||
strReq.SetValue(uint64(1))
|
||||
strReq.SetValue(bool(true))
|
||||
boolReq.SetValue([]byte{0x41, 0x41, 0x41, 0x41})
|
||||
})
|
||||
u64Req.SetValue(uint64(1))
|
||||
strReq.SetValue("test")
|
||||
assertPanic(t, func() {
|
||||
boolReq.SetValue(uint64(1))
|
||||
boolReq.SetValue("test")
|
||||
boolReq.SetValue([]byte{0x41, 0x41, 0x41, 0x41})
|
||||
})
|
||||
assertPanic(t, func() {
|
||||
binReq.SetValue(uint64(1))
|
||||
binReq.SetValue("test")
|
||||
binReq.SetValue(bool(true))
|
||||
})
|
||||
|
||||
// check set correct values
|
||||
u64Req.SetValue(testU64)
|
||||
if getU64ResSSPluingExtractField(t, u64Ptr, 0) != testU64 {
|
||||
t.Errorf("expected value '%d', but found '%d'", testU64, getU64ResSSPluingExtractField(t, u64Ptr, 0))
|
||||
}
|
||||
u64Req.SetValueOffset(testU64Start, testU64Length)
|
||||
if getResSSPluginExtractOffsetFromPtr(u64OffsetStartPtr) != testU64Start {
|
||||
t.Errorf("expected start offset '%d', but found '%d'", testU64Start, getResSSPluginExtractOffsetFromPtr(u64OffsetStartPtr))
|
||||
}
|
||||
if getResSSPluginExtractOffsetFromPtr(u64OffsetLengthPtr) != testU64Length {
|
||||
t.Errorf("expected length '%d', but found '%d'", testU64Length, getResSSPluginExtractOffsetFromPtr(u64OffsetLengthPtr))
|
||||
}
|
||||
u64ReqList.SetValue(testU64List)
|
||||
u64ReqList.SetValueOffset(testU64ListStart, testU64ListLength)
|
||||
for i, d := range testU64List {
|
||||
if getU64ResSSPluingExtractField(t, u64ListPtr, i) != d {
|
||||
t.Errorf("expected value '%d', but found '%d'", testU64, getU64ResSSPluingExtractField(t, u64Ptr, i))
|
||||
}
|
||||
}
|
||||
if getResSSPluginExtractOffsetFromPtr(u64ListOffsetStartPtr) != testU64ListStart {
|
||||
t.Errorf("expected start offset '%d', but found '%d'", testU64ListStart, getResSSPluginExtractOffsetFromPtr(u64ListOffsetStartPtr))
|
||||
}
|
||||
if getResSSPluginExtractOffsetFromPtr(u64ListOffsetLengthPtr) != testU64ListLength {
|
||||
t.Errorf("expected length '%d', but found '%d'", testU64ListLength, getResSSPluginExtractOffsetFromPtr(u64ListOffsetLengthPtr))
|
||||
}
|
||||
strReq.SetValue(testStr)
|
||||
if getStrResSSPluingExtractField(t, strPtr, 0) != testStr {
|
||||
t.Errorf("expected value '%s', but found '%s'", testStr, getStrResSSPluingExtractField(t, strPtr, 0))
|
||||
}
|
||||
strReqList.SetValue(testStrList)
|
||||
for i, s := range testStrList {
|
||||
if getStrResSSPluingExtractField(t, strListPtr, i) != s {
|
||||
t.Errorf("expected value '%s', but found '%s'", s, getStrResSSPluingExtractField(t, strPtr, i))
|
||||
}
|
||||
}
|
||||
boolReq.SetValue(testBool)
|
||||
if getBoolResSSPluingExtractField(t, boolPtr, 0) != testBool {
|
||||
t.Errorf("expected value '%v', but found '%v'", testBool, getBoolResSSPluingExtractField(t, boolPtr, 0))
|
||||
}
|
||||
boolReqList.SetValue(testBoolList)
|
||||
for i, b := range testBoolList {
|
||||
if getBoolResSSPluingExtractField(t, boolListPtr, i) != b {
|
||||
t.Errorf("expected value '%v', but found '%v' at index %d", b, getBoolResSSPluingExtractField(t, boolPtr, i), i)
|
||||
}
|
||||
}
|
||||
binReq.SetValue(testIPv6)
|
||||
testIPv6res := getBinResSSPluingExtractField(t, binPtr, 0)
|
||||
if len(testIPv6res) != len(testIPv6) {
|
||||
t.Errorf("expected value '%v', but found '%v'", testIPv6, getBinResSSPluingExtractField(t, binPtr, 0))
|
||||
} else {
|
||||
for i := 0; i < len(testIPv6); i++ {
|
||||
if testIPv6[i] != testIPv6res[i] {
|
||||
t.Errorf("expected value '%v', but found '%v'", testIPv6, getBinResSSPluingExtractField(t, binPtr, 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
binReqList.SetValue(testIPv6List)
|
||||
for i, s := range testIPv6List {
|
||||
testIPv6res := getBinResSSPluingExtractField(t, binListPtr, i)
|
||||
if len(testIPv6res) != len(s) {
|
||||
t.Errorf("expected size '%v', but found '%v'", len(s), len(testIPv6res))
|
||||
} else {
|
||||
for k := 0; k < len(s); k++ {
|
||||
if s[k] != testIPv6res[k] {
|
||||
t.Errorf("expected value '%v', but found '%v'", s, testIPv6res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pool.Free()
|
||||
freeU64Ptr()
|
||||
freeU64ListPtr()
|
||||
freeStrPtr()
|
||||
freeStrListPtr()
|
||||
freeBoolPtr()
|
||||
freeBoolListPtr()
|
||||
freeBinPtr()
|
||||
freeBinListPtr()
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"unsafe"
|
||||
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
|
||||
)
|
||||
|
||||
// InMemoryExtractRequest is an in-memory implementation of
|
||||
// sdk.ExtractRequest that allows changing its internal values.
|
||||
type InMemoryExtractRequest struct {
|
||||
ValFieldID uint64
|
||||
ValFieldType uint32
|
||||
ValField string
|
||||
ValArgKey string
|
||||
ValArgIndex uint64
|
||||
ValArgPresent bool
|
||||
ValIsList bool
|
||||
ValValue interface{}
|
||||
ValPtr unsafe.Pointer
|
||||
}
|
||||
|
||||
func (i *InMemoryExtractRequest) FieldID() uint64 {
|
||||
return i.ValFieldID
|
||||
}
|
||||
|
||||
func (i *InMemoryExtractRequest) FieldType() uint32 {
|
||||
return i.ValFieldType
|
||||
}
|
||||
|
||||
func (i *InMemoryExtractRequest) Field() string {
|
||||
return i.ValField
|
||||
}
|
||||
|
||||
func (i *InMemoryExtractRequest) ArgKey() string {
|
||||
return i.ValArgKey
|
||||
}
|
||||
|
||||
func (i *InMemoryExtractRequest) ArgIndex() uint64 {
|
||||
return i.ValArgIndex
|
||||
}
|
||||
|
||||
func (i *InMemoryExtractRequest) ArgPresent() bool {
|
||||
return i.ValArgPresent
|
||||
}
|
||||
|
||||
func (i *InMemoryExtractRequest) IsList() bool {
|
||||
return i.ValIsList
|
||||
}
|
||||
|
||||
func (i *InMemoryExtractRequest) SetValue(v interface{}) {
|
||||
i.ValValue = v
|
||||
}
|
||||
|
||||
func (i *InMemoryExtractRequest) SetPtr(ptr unsafe.Pointer) {
|
||||
i.ValPtr = ptr
|
||||
}
|
||||
|
||||
// InMemoryExtractRequestPool is an in-memory implementation of
|
||||
// sdk.ExtractRequestPool that allows changing its internal values.
|
||||
type InMemoryExtractRequestPool struct {
|
||||
Requests map[int]sdk.ExtractRequest
|
||||
}
|
||||
|
||||
func (i *InMemoryExtractRequestPool) Get(requestIndex int) sdk.ExtractRequest {
|
||||
return i.Requests[requestIndex]
|
||||
}
|
||||
|
||||
func (i *InMemoryExtractRequest) Free() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// InMemoryEventWriter is an in-memory implementation of
|
||||
// sdk.EventWriter that allows changing its internal values.
|
||||
type InMemoryEventWriter struct {
|
||||
Buffer bytes.Buffer
|
||||
ValTimestamp uint64
|
||||
}
|
||||
|
||||
func (i *InMemoryEventWriter) Writer() io.Writer {
|
||||
i.Buffer.Reset()
|
||||
return &i.Buffer
|
||||
}
|
||||
|
||||
func (i *InMemoryEventWriter) SetTimestamp(value uint64) {
|
||||
i.ValTimestamp = value
|
||||
}
|
||||
|
||||
// InMemoryEventWriters is an in-memory implementation of
|
||||
// sdk.EventWriters that allows changing its internal values.
|
||||
type InMemoryEventWriters struct {
|
||||
Writers []sdk.EventWriter
|
||||
ValArrayPtr unsafe.Pointer
|
||||
OnFree func()
|
||||
}
|
||||
|
||||
func (i *InMemoryEventWriters) Get(eventIndex int) sdk.EventWriter {
|
||||
return i.Writers[eventIndex]
|
||||
}
|
||||
|
||||
func (i *InMemoryEventWriters) Len() int {
|
||||
return len(i.Writers)
|
||||
}
|
||||
|
||||
func (i *InMemoryEventWriters) ArrayPtr() unsafe.Pointer {
|
||||
return i.ValArrayPtr
|
||||
}
|
||||
|
||||
func (i *InMemoryEventWriters) Free() {
|
||||
if i.OnFree != nil {
|
||||
i.OnFree()
|
||||
}
|
||||
}
|
||||
|
||||
// InMemoryEventReader is an in-memory implementation of
|
||||
// sdk.EventReader that allows changing its internal values.
|
||||
type InMemoryEventReader struct {
|
||||
Buffer []byte
|
||||
ValEventNum uint64
|
||||
ValTimestamp uint64
|
||||
}
|
||||
|
||||
func (i *InMemoryEventReader) EventNum() uint64 {
|
||||
return i.ValEventNum
|
||||
}
|
||||
|
||||
func (i *InMemoryEventReader) Timestamp() uint64 {
|
||||
return i.ValTimestamp
|
||||
}
|
||||
|
||||
func (i *InMemoryEventReader) Reader() io.ReadSeeker {
|
||||
return bytes.NewReader(i.Buffer)
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -16,8 +17,6 @@ limitations under the License.
|
|||
|
||||
package sdk
|
||||
|
||||
import "io"
|
||||
|
||||
// PluginState represents the state of a plugin returned by plugin_init().
|
||||
type PluginState interface {
|
||||
}
|
||||
|
@ -47,12 +46,13 @@ type Destroyer interface {
|
|||
}
|
||||
|
||||
// Stringer is an interface wrapping the basic String method.
|
||||
// String takes a io.ReadSeeker byte input and produces a string representation
|
||||
// String takes an EventReader and produces a string representation
|
||||
// describing its internal data. This is meant to be used in
|
||||
// plugin_event_to_string(), where the byte input represents event data
|
||||
// provided by the framework and previouly produced by the plugin_next_batch().
|
||||
// plugin_event_to_string(), where the event is provided by the framework
|
||||
// and previouly produced by an invocation of plugin_next_batch() of this
|
||||
// plugin.
|
||||
type Stringer interface {
|
||||
String(in io.ReadSeeker) (string, error)
|
||||
String(evt EventReader) (string, error)
|
||||
}
|
||||
|
||||
// Extractor is an interface wrapping the basic Extract method.
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,581 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
//
|
||||
// This file contains the prototype and type definitions of sinsp/scap plugins
|
||||
//
|
||||
|
||||
//
|
||||
// API versions of this plugin engine
|
||||
//
|
||||
#define PLUGIN_API_VERSION_MAJOR 0
|
||||
#define PLUGIN_API_VERSION_MINOR 2
|
||||
#define PLUGIN_API_VERSION_PATCH 0
|
||||
|
||||
//
|
||||
// Just some not so smart defines to retrieve plugin api version as string
|
||||
//
|
||||
#define QUOTE(str) #str
|
||||
#define EXPAND_AND_QUOTE(str) QUOTE(str)
|
||||
#define PLUGIN_API_VERSION PLUGIN_API_VERSION_MAJOR.PLUGIN_API_VERSION_MINOR.PLUGIN_API_VERSION_PATCH
|
||||
#define PLUGIN_API_VERSION_STR EXPAND_AND_QUOTE(PLUGIN_API_VERSION)
|
||||
|
||||
//
|
||||
// There are two plugin types: source plugins and extractor plugins.
|
||||
//
|
||||
// Source plugins implement a new sinsp/scap event source and have the
|
||||
// ability to provide events to the event loop. Optionally, they can
|
||||
// extract fields from events so they can be displayed/used in
|
||||
// filters.
|
||||
//
|
||||
// Extractor plugins do not provide events, but have the ability to
|
||||
// extract fields from events created by other plugins. A good example
|
||||
// of an extractor plugin is a json extractor, which can extract
|
||||
// information from any json payload, regardless of where the payloads
|
||||
// come from.
|
||||
//
|
||||
typedef enum ss_plugin_type
|
||||
{
|
||||
TYPE_SOURCE_PLUGIN = 1,
|
||||
TYPE_EXTRACTOR_PLUGIN = 2
|
||||
}ss_plugin_type;
|
||||
|
||||
// The noncontinguous numbers are to maintain equality with underlying
|
||||
// falcosecurity libs types.
|
||||
typedef enum ss_plugin_field_type
|
||||
{
|
||||
FTYPE_UINT64 = 8,
|
||||
FTYPE_STRING = 9
|
||||
}ss_plugin_field_type;
|
||||
|
||||
// Values to return from init() / open() / next_batch() /
|
||||
// extract_fields().
|
||||
typedef enum ss_plugin_rc
|
||||
{
|
||||
SS_PLUGIN_SUCCESS = 0,
|
||||
SS_PLUGIN_FAILURE = 1,
|
||||
SS_PLUGIN_TIMEOUT = -1,
|
||||
SS_PLUGIN_EOF = 2,
|
||||
SS_PLUGIN_NOT_SUPPORTED = 3,
|
||||
} ss_plugin_rc;
|
||||
|
||||
// The supported schema formats for the init configuration.
|
||||
typedef enum ss_plugin_schema_type
|
||||
{
|
||||
// The schema is undefined and the init configuration
|
||||
// is an opaque string.
|
||||
SS_PLUGIN_SCHEMA_NONE = 0,
|
||||
//
|
||||
// The schema follows the JSON Schema specific, and the
|
||||
// init configuration must be represented as a json.
|
||||
// see: https://json-schema.org/
|
||||
SS_PLUGIN_SCHEMA_JSON = 1,
|
||||
} ss_plugin_schema_type;
|
||||
|
||||
// This struct represents an event returned by the plugin, and is used
|
||||
// below in next_batch().
|
||||
// - evtnum: incremented for each event returned. Might not be contiguous.
|
||||
// - data: pointer to a memory buffer pointer. The plugin will set it
|
||||
// to point to the memory containing the next event.
|
||||
// - datalen: pointer to a 32bit integer. The plugin will set it the size of the
|
||||
// buffer pointed by data.
|
||||
// - ts: the event timestamp, in nanoseconds since the epoch.
|
||||
// Can be (uint64_t)-1, in which case the engine will automatically
|
||||
// fill the event time with the current time.
|
||||
//
|
||||
// Note: event numbers are assigned by the plugin
|
||||
// framework. Therefore, there isn't any need to fill in evtnum when
|
||||
// returning an event via plugin_next_batch. It will be ignored.
|
||||
typedef struct ss_plugin_event
|
||||
{
|
||||
uint64_t evtnum;
|
||||
const uint8_t *data;
|
||||
uint32_t datalen;
|
||||
uint64_t ts;
|
||||
} ss_plugin_event;
|
||||
|
||||
// Used in extract_fields functions below to receive a field/arg
|
||||
// pair and return an extracted value.
|
||||
// field_id: id of the field, as of its index in the list of
|
||||
// fields specified by the plugin.
|
||||
// field: the field name.
|
||||
// arg: the field argument, if an argument has been specified
|
||||
// for the field, otherwise it's NULL.
|
||||
// For example:
|
||||
// * if the field specified by the user is foo.bar[pippo], arg will be the
|
||||
// string "pippo"
|
||||
// * if the field specified by the user is foo.bar, arg will be NULL
|
||||
// ftype: the type of the field. Could be derived from the field name alone,
|
||||
// but including here can prevent a second lookup of field names.
|
||||
// The following should be filled in by the extraction function:
|
||||
// - field_present: set to true if the event has a meaningful
|
||||
// extracted value for the provided field, false otherwise
|
||||
// - res_str: if the corresponding field was type==string, this should be
|
||||
// filled in with the string value. The string must be allocated and set
|
||||
// by the plugin.
|
||||
// - res_u64: if the corresponding field was type==uint64, this should be
|
||||
// filled in with the uint64 value.
|
||||
|
||||
typedef struct ss_plugin_extract_field
|
||||
{
|
||||
uint32_t field_id;
|
||||
const char* field;
|
||||
const char* arg;
|
||||
uint32_t ftype;
|
||||
|
||||
bool field_present;
|
||||
const char* res_str;
|
||||
uint64_t res_u64;
|
||||
} ss_plugin_extract_field;
|
||||
|
||||
//
|
||||
// This is the opaque pointer to the state of a plugin.
|
||||
// It points to any data that might be needed plugin-wise. It is
|
||||
// allocated by init() and must be destroyed by destroy().
|
||||
// It is defined as void because the engine doesn't care what it is
|
||||
// and it treats is as opaque.
|
||||
//
|
||||
typedef void ss_plugin_t;
|
||||
|
||||
//
|
||||
// This is the opaque pointer to the state of an open instance of the source
|
||||
// plugin.
|
||||
// It points to any data that is needed while a capture is running. It is
|
||||
// allocated by open() and must be destroyed by close().
|
||||
// It is defined as void because the engine doesn't care what it is
|
||||
// and it treats is as opaque.
|
||||
//
|
||||
typedef void ss_instance_t;
|
||||
|
||||
//
|
||||
// The structs below define the functions and arguments for source and
|
||||
// extractor plugins. The structs are used by the plugin framework to
|
||||
// load and interface with plugins.
|
||||
//
|
||||
// From the perspective of the plugin, each function below should be
|
||||
// exported from the dynamic library as a C calling convention
|
||||
// function, adding a prefix "plugin_" to the function name
|
||||
// (e.g. plugin_get_required_api_version, plugin_init, etc.)
|
||||
//
|
||||
// Plugins are totally responsible of both allocating and deallocating memory.
|
||||
// Plugins have the guarantee that they can safely deallocate memory in
|
||||
// these cases:
|
||||
// - During close(), for all the memory allocated in the context of a plugin
|
||||
// instance after open().
|
||||
// - During destroy(), for all the memory allocated by the plugin, as it stops
|
||||
// being executed.
|
||||
// - During subsequent calls to the same function, for all the exported
|
||||
// functions returning memory pointers.
|
||||
//
|
||||
// Plugins must not free memory passed in by the framework (i.e. function input
|
||||
// parameters) if not corresponding to plugin-allocated memory in the
|
||||
// cases above. Plugins can safely use the passed memory during the execution
|
||||
// of the exported functions.
|
||||
|
||||
//
|
||||
// Interface for a sinsp/scap source plugin.
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
//
|
||||
// Return the version of the plugin API used by this plugin.
|
||||
// Required: yes
|
||||
// Return value: the API version string, in the following format:
|
||||
// "<major>.<minor>.<patch>", e.g. "1.2.3".
|
||||
// NOTE: to ensure correct interoperability between the engine and the plugins,
|
||||
// we use a semver approach. Plugins are required to specify the version
|
||||
// of the API they run against, and the engine will take care of checking
|
||||
// and enforcing compatibility.
|
||||
//
|
||||
const char* (*get_required_api_version)();
|
||||
//
|
||||
// Return the plugin type.
|
||||
// Required: yes
|
||||
// Should return TYPE_SOURCE_PLUGIN. It still makes sense to
|
||||
// have a function get_type() as the plugin interface will
|
||||
// often dlsym() functions from shared libraries, and can't
|
||||
// inspect any C struct type.
|
||||
//
|
||||
uint32_t (*get_type)();
|
||||
//
|
||||
// Return a string representation of a schema describing the data expected
|
||||
// to be passed as a configuration during the plugin initialization.
|
||||
// Required: no
|
||||
// Arguments:
|
||||
// - schema_type: The schema format type of the returned value among the
|
||||
// list of the supported ones according to the ss_plugin_config_schema
|
||||
// enumeration.
|
||||
// Return value: a string representation of the schema for the config
|
||||
// to be passed to init().
|
||||
//
|
||||
// Plugins can optionally export this symbol to specify the expected
|
||||
// format for the configuration string passed to init(). If specified,
|
||||
// the init() function can assume the config string to always be
|
||||
// well-formed. The framework will take care of automatically parsing it
|
||||
// against the provided schema and generating ad-hoc errors accordingly.
|
||||
// This also serves as a piece of documentation for users about how the
|
||||
// plugin needs to be configured.
|
||||
//
|
||||
const char* (*get_init_schema)(ss_plugin_schema_type* schema_type);
|
||||
//
|
||||
// Initialize the plugin and, if needed, allocate its state.
|
||||
// Required: yes
|
||||
// Arguments:
|
||||
// - config: a string with the plugin configuration. The format of the
|
||||
// string is chosen by the plugin itself.
|
||||
// - rc: pointer to a ss_plugin_rc that will contain the initialization result
|
||||
// Return value: pointer to the plugin state that will be treated as opaque
|
||||
// by the engine and passed to the other plugin functions.
|
||||
// If rc is SS_PLUGIN_FAILURE, this function should return NULL.
|
||||
//
|
||||
ss_plugin_t* (*init)(const char* config, ss_plugin_rc* rc);
|
||||
//
|
||||
// Destroy the plugin and, if plugin state was allocated, free it.
|
||||
// Required: yes
|
||||
//
|
||||
void (*destroy)(ss_plugin_t* s);
|
||||
//
|
||||
// Return a string with the error that was last generated by
|
||||
// the plugin.
|
||||
// Required: yes
|
||||
//
|
||||
// In cases where any other api function returns an error, the
|
||||
// plugin should be prepared to return a human-readable error
|
||||
// string with more context for the error. The plugin manager
|
||||
// calls get_last_error() to access that string.
|
||||
//
|
||||
const char* (*get_last_error)(ss_plugin_t* s);
|
||||
//
|
||||
// Return the unique ID of the plugin.
|
||||
// Required: yes
|
||||
// EVERY SOURCE PLUGIN (see get_type()) MUST OBTAIN AN OFFICIAL ID FROM THE
|
||||
// FALCOSECURITY ORGANIZATION, OTHERWISE IT WON'T PROPERLY COEXIST WITH OTHER PLUGINS.
|
||||
//
|
||||
uint32_t (*get_id)();
|
||||
//
|
||||
// Return the name of the plugin, which will be printed when displaying
|
||||
// information about the plugin.
|
||||
// Required: yes
|
||||
//
|
||||
const char* (*get_name)();
|
||||
//
|
||||
// Return the descriptions of the plugin, which will be printed when displaying
|
||||
// information about the plugin or its events.
|
||||
// Required: yes
|
||||
//
|
||||
const char* (*get_description)();
|
||||
//
|
||||
// Return a string containing contact info (url, email, twitter, etc) for
|
||||
// the plugin authors.
|
||||
// Required: yes
|
||||
//
|
||||
const char* (*get_contact)();
|
||||
//
|
||||
// Return the version of this plugin itself
|
||||
// Required: yes
|
||||
// Return value: a string with a version identifier, in the following format:
|
||||
// "<major>.<minor>.<patch>", e.g. "1.2.3".
|
||||
// This differs from the api version in that this versions the
|
||||
// plugin itself, as compared to the plugin interface. When
|
||||
// reading capture files, the major version of the plugin that
|
||||
// generated events must match the major version of the plugin
|
||||
// used to read events.
|
||||
//
|
||||
const char* (*get_version)();
|
||||
//
|
||||
// Return a string describing the events generated by this source plugin.
|
||||
// Required: yes
|
||||
// Example event sources would be strings like "syscall",
|
||||
// "k8s_audit", etc. The source can be used by extractor
|
||||
// plugins to filter the events they receive.
|
||||
//
|
||||
const char* (*get_event_source)();
|
||||
//
|
||||
// Return the list of extractor fields exported by this plugin. Extractor
|
||||
// fields can be used in Falco rule conditions and sysdig filters.
|
||||
// Required: no
|
||||
// Return value: a string with the list of fields encoded as a json
|
||||
// array.
|
||||
// Each field entry is a json object with the following properties:
|
||||
// "type": one of "string", "uint64"
|
||||
// "name": a string with a name for the field
|
||||
// "argRequired: (optional) If present and set to true, notes
|
||||
// that the field requires an argument e.g. field[arg].
|
||||
// "display": (optional) If present, a string that will be used to
|
||||
// display the field instead of the name. Used in tools
|
||||
// like wireshark.
|
||||
// "desc": a string with a description of the field
|
||||
// Example return value:
|
||||
// [
|
||||
// {"type": "string", "name": "field1", "argRequired": true, "desc": "Describing field 1"},
|
||||
// {"type": "uint64", "name": "field2", "desc": "Describing field 2"}
|
||||
// ]
|
||||
const char* (*get_fields)();
|
||||
//
|
||||
// Open the source and start a capture (e.g. stream of events)
|
||||
// Required: yes
|
||||
// Arguments:
|
||||
// - s: the plugin state returned by init()
|
||||
// - params: the open parameters, as a string. The format is defined by the plugin
|
||||
// itsef
|
||||
// - rc: pointer to a ss_plugin_rc that will contain the open result
|
||||
// Return value: a pointer to the open context that will be passed to next_batch(),
|
||||
// close(), event_to_string() and extract_fields.
|
||||
//
|
||||
ss_instance_t* (*open)(ss_plugin_t* s, const char* params, ss_plugin_rc* rc);
|
||||
//
|
||||
// Close a capture.
|
||||
// Required: yes
|
||||
// Arguments:
|
||||
// - s: the plugin context, returned by init(). Can be NULL.
|
||||
// - h: the capture context, returned by open(). Can be NULL.
|
||||
//
|
||||
void (*close)(ss_plugin_t* s, ss_instance_t* h);
|
||||
//
|
||||
// Return a list of suggested open parameters supported by this plugin.
|
||||
// Any of the values in the returned list are valid parameters for open().
|
||||
// Required: no
|
||||
// Return value: a string with the list of open params encoded as
|
||||
// a json array.
|
||||
// Each field entry is a json object with the following properties:
|
||||
// "value": a string usable as an open() parameter.
|
||||
// "desc": (optional) a string with a description of the parameter.
|
||||
// Example return value:
|
||||
// [
|
||||
// {"value": "resource1", "desc": "An example of openable resource"},
|
||||
// {"value": "resource2", "desc": "Another example of openable resource"}
|
||||
// ]
|
||||
const char* (*list_open_params)(ss_plugin_t* s, ss_plugin_rc* rc);
|
||||
//
|
||||
// Return the read progress.
|
||||
// Required: no
|
||||
// Arguments:
|
||||
// - progress_pct: the read progress, as a number between 0 (no data has been read)
|
||||
// and 10000 (100% of the data has been read). This encoding allows the engine to
|
||||
// print progress decimals without requiring to deal with floating point numbers
|
||||
// (which could cause incompatibility problems with some languages).
|
||||
// Return value: a string representation of the read
|
||||
// progress. This might include the progress percentage
|
||||
// combined with additional context added by the plugin. If
|
||||
// NULL, progress_pct should be used.
|
||||
// The returned memory pointer must be allocated by the plugin
|
||||
// and must not be deallocated or modified until the next call to
|
||||
// get_progress().
|
||||
// NOTE: reporting progress is optional and in some case could be impossible. However,
|
||||
// when possible, it's recommended as it provides valuable information to the
|
||||
// user.
|
||||
//
|
||||
const char* (*get_progress)(ss_plugin_t* s, ss_instance_t* h, uint32_t* progress_pct);
|
||||
//
|
||||
// Return a text representation of an event generated by this source plugin.
|
||||
// Required: yes
|
||||
// Arguments:
|
||||
// - data: the buffer from an event produced by next_batch().
|
||||
// - datalen: the length of the buffer from an event produced by next_batch().
|
||||
// Return value: the text representation of the event. This is used, for example,
|
||||
// by sysdig to print a line for the given event.
|
||||
// The returned memory pointer must be allocated by the plugin
|
||||
// and must not be deallocated or modified until the next call to
|
||||
// event_to_string().
|
||||
//
|
||||
const char* (*event_to_string)(ss_plugin_t *s, const uint8_t *data, uint32_t datalen);
|
||||
//
|
||||
// Extract one or more a filter field values from an event.
|
||||
// Required: no
|
||||
// Arguments:
|
||||
// - evt: an event struct produced by a call to next_batch().
|
||||
// This is allocated by the framework, and it is not guaranteed
|
||||
// that the event struct pointer is the same returned by the last
|
||||
// next_batch() call.
|
||||
// - num_fields: the length of the fields array.
|
||||
// - fields: an array of ss_plugin_extract_field structs. Each entry
|
||||
// contains a single field + optional argument as input, and the corresponding
|
||||
// extracted value as output. Memory pointers set as output must be allocated
|
||||
// by the plugin and must not be deallocated or modified until the next
|
||||
// extract_fields() call.
|
||||
//
|
||||
// Return value: A ss_plugin_rc with values SS_PLUGIN_SUCCESS or SS_PLUGIN_FAILURE.
|
||||
//
|
||||
ss_plugin_rc (*extract_fields)(ss_plugin_t *s, const ss_plugin_event *evt, uint32_t num_fields, ss_plugin_extract_field *fields);
|
||||
//
|
||||
// Return the next batch of events.
|
||||
// On success:
|
||||
// - nevts will be filled in with the number of events.
|
||||
// - evts: pointer to an ss_plugin_event pointer. The plugin must
|
||||
// allocate an array of contiguous ss_plugin_event structs
|
||||
// and each data buffer within each ss_plugin_event struct.
|
||||
// Memory pointers set as output must be allocated by the plugin
|
||||
// and must not be deallocated or modified until the next call to
|
||||
// next_batch() or close().
|
||||
// Required: yes
|
||||
//
|
||||
ss_plugin_rc (*next_batch)(ss_plugin_t* s, ss_instance_t* h, uint32_t *nevts, ss_plugin_event **evts);
|
||||
//
|
||||
// The following members are PRIVATE for the engine and should not be touched.
|
||||
//
|
||||
ss_plugin_t* state;
|
||||
ss_instance_t* handle;
|
||||
uint32_t id;
|
||||
const char* name;
|
||||
} source_plugin_info;
|
||||
|
||||
//
|
||||
// Interface for a sinsp/scap extractor plugin
|
||||
//
|
||||
typedef struct
|
||||
{
|
||||
//
|
||||
// Return the version of the plugin API used by this plugin.
|
||||
// Required: yes
|
||||
// Return value: the API version string, in the following format:
|
||||
// "<major>.<minor>.<patch>", e.g. "1.2.3".
|
||||
// NOTE: to ensure correct interoperability between the engine and the plugins,
|
||||
// we use a semver approach. Plugins are required to specify the version
|
||||
// of the API they run against, and the engine will take care of checking
|
||||
// and enforcing compatibility.
|
||||
//
|
||||
const char* (*get_required_api_version)();
|
||||
//
|
||||
// Return the plugin type.
|
||||
// Required: yes
|
||||
// Should return TYPE_EXTRACTOR_PLUGIN. It still makes sense to
|
||||
// have a function get_type() as the plugin interface will
|
||||
// often dlsym() functions from shared libraries, and can't
|
||||
// inspect any C struct type.
|
||||
//
|
||||
uint32_t (*get_type)();
|
||||
//
|
||||
// Return a string representation of a schema describing the data expected
|
||||
// to be passed as a configuration during the plugin initialization.
|
||||
// Required: no
|
||||
// Arguments:
|
||||
// - schema_type: The schema format type of the returned value among the
|
||||
// list of the supported ones according to the ss_plugin_config_schema
|
||||
// enumeration.
|
||||
// Return value: a string representation of the schema for the config
|
||||
// to be passed to init().
|
||||
//
|
||||
// Plugins can optionally export this symbol to specify the expected
|
||||
// format for the configuration string passed to init(). If specified,
|
||||
// the init() function can assume the config string to always be
|
||||
// well-formed. The framework will take care of automatically parsing it
|
||||
// against the provided schema and generating ad-hoc errors accordingly.
|
||||
// This also serves as a piece of documentation for users about how the
|
||||
// plugin needs to be configured.
|
||||
//
|
||||
const char* (*get_init_schema)(ss_plugin_schema_type* schema_type);
|
||||
//
|
||||
// Initialize the plugin and, if needed, allocate its state.
|
||||
// Required: yes
|
||||
// Arguments:
|
||||
// - config: a string with the plugin configuration. The format of the
|
||||
// string is chosen by the plugin itself.
|
||||
// - rc: pointer to a ss_plugin_rc that will contain the initialization result
|
||||
// Return value: pointer to the plugin state that will be treated as opaque
|
||||
// by the engine and passed to the other plugin functions.
|
||||
//
|
||||
ss_plugin_t* (*init)(const char* config, ss_plugin_rc* rc);
|
||||
//
|
||||
// Destroy the plugin and, if plugin state was allocated, free it.
|
||||
// Required: yes
|
||||
//
|
||||
void (*destroy)(ss_plugin_t* s);
|
||||
//
|
||||
// Return a string with the error that was last generated by
|
||||
// the plugin.
|
||||
// Required: yes
|
||||
//
|
||||
// In cases where any other api function returns an error, the
|
||||
// plugin should be prepared to return a human-readable error
|
||||
// string with more context for the error. The plugin manager
|
||||
// calls get_last_error() to access that string.
|
||||
//
|
||||
const char* (*get_last_error)(ss_plugin_t* s);
|
||||
//
|
||||
// Return the name of the plugin, which will be printed when displaying
|
||||
// information about the plugin.
|
||||
// Required: yes
|
||||
//
|
||||
const char* (*get_name)();
|
||||
//
|
||||
// Return the descriptions of the plugin, which will be printed when displaying
|
||||
// information about the plugin or its events.
|
||||
// Required: yes
|
||||
//
|
||||
const char* (*get_description)();
|
||||
//
|
||||
// Return a string containing contact info (url, email, twitter, etc) for
|
||||
// the plugin author.
|
||||
// Required: yes
|
||||
//
|
||||
const char* (*get_contact)();
|
||||
//
|
||||
// Return the version of this plugin itself
|
||||
// Required: yes
|
||||
// Return value: a string with a version identifier, in the following format:
|
||||
// "<major>.<minor>.<patch>", e.g. "1.2.3".
|
||||
// This differs from the api version in that this versions the
|
||||
// plugin itself, as compared to the plugin interface. When
|
||||
// reading capture files, the major version of the plugin that
|
||||
// generated events must match the major version of the plugin
|
||||
// used to read events.
|
||||
//
|
||||
const char* (*get_version)();
|
||||
//
|
||||
// Return a string describing the event sources that this
|
||||
// extractor plugin can consume.
|
||||
// Required: no
|
||||
// Return value: a json array of strings containing event
|
||||
// sources returned by a source plugin's get_event_source()
|
||||
// function.
|
||||
// This function is optional--if NULL then the exctractor
|
||||
// plugin will receive every event.
|
||||
//
|
||||
const char* (*get_extract_event_sources)();
|
||||
//
|
||||
// Return the list of extractor fields exported by this plugin. Extractor
|
||||
// fields can be used in Falco rules and sysdig filters.
|
||||
// Required: yes
|
||||
// Return value: a string with the list of fields encoded as a json
|
||||
// array.
|
||||
//
|
||||
const char* (*get_fields)();
|
||||
//
|
||||
// Extract one or more a filter field values from an event.
|
||||
// Required: yes
|
||||
// Arguments:
|
||||
// - evt: an event struct provided by the framework.
|
||||
// - num_fields: the length of the fields array.
|
||||
// - fields: an array of ss_plugin_extract_field structs. Each entry
|
||||
// contains a single field + optional argument as input, and the corresponding
|
||||
// extracted value as output. Memory pointers set as output must be allocated
|
||||
// by the plugin and must not be deallocated or modified until the next
|
||||
// extract_fields() call.
|
||||
//
|
||||
// Return value: A ss_plugin_rc with values SS_PLUGIN_SUCCESS or SS_PLUGIN_FAILURE.
|
||||
//
|
||||
ss_plugin_rc (*extract_fields)(ss_plugin_t *s, const ss_plugin_event *evt, uint32_t num_fields, ss_plugin_extract_field *fields);
|
||||
//
|
||||
// The following members are PRIVATE for the engine and should not be touched.
|
||||
//
|
||||
ss_plugin_t* state;
|
||||
} extractor_plugin_info;
|
|
@ -0,0 +1,349 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
// An implementation-independent representation of boolean.
|
||||
// A 4-byte representation is equal to how bools are encoded in falcosecurity libs.
|
||||
typedef uint32_t ss_plugin_bool;
|
||||
|
||||
// The noncontinguous numbers are to maintain equality with underlying
|
||||
// falcosecurity libs types.
|
||||
typedef enum ss_plugin_field_type {
|
||||
// A 64bit unsigned integer.
|
||||
FTYPE_UINT64 = 8,
|
||||
// A printable buffer of bytes, NULL terminated
|
||||
FTYPE_STRING = 9,
|
||||
// A relative time. Seconds * 10^9 + nanoseconds. 64bit.
|
||||
FTYPE_RELTIME = 20,
|
||||
// An absolute time interval. Seconds from epoch * 10^9 + nanoseconds. 64bit.
|
||||
FTYPE_ABSTIME = 21,
|
||||
// A boolean value, 4 bytes.
|
||||
FTYPE_BOOL = 25,
|
||||
// Either an IPv4 or IPv6 address. The length indicates which one it is.
|
||||
FTYPE_IPADDR = 40,
|
||||
// Either an IPv4 or IPv6 network. The length indicates which one it is.
|
||||
// The field encodes only the IP address, so this differs from FTYPE_IPADDR,
|
||||
// from the way the framework perform runtime checks and comparisons.
|
||||
FTYPE_IPNET = 41,
|
||||
} ss_plugin_field_type;
|
||||
|
||||
// Values to return from init() / open() / next_batch() /
|
||||
// extract_fields().
|
||||
typedef enum ss_plugin_rc {
|
||||
SS_PLUGIN_SUCCESS = 0,
|
||||
SS_PLUGIN_FAILURE = 1,
|
||||
SS_PLUGIN_TIMEOUT = -1,
|
||||
SS_PLUGIN_EOF = 2,
|
||||
SS_PLUGIN_NOT_SUPPORTED = 3,
|
||||
} ss_plugin_rc;
|
||||
|
||||
// The supported schema formats for the init configuration.
|
||||
typedef enum ss_plugin_schema_type {
|
||||
// The schema is undefined and the init configuration
|
||||
// is an opaque string.
|
||||
SS_PLUGIN_SCHEMA_NONE = 0,
|
||||
//
|
||||
// The schema follows the JSON Schema specific, and the
|
||||
// init configuration must be represented as a json.
|
||||
// see: https://json-schema.org/
|
||||
SS_PLUGIN_SCHEMA_JSON = 1,
|
||||
} ss_plugin_schema_type;
|
||||
|
||||
// This struct represents an event returned by the plugin, and is used
|
||||
// below in next_batch(). It observes the event specifics of libscap.
|
||||
// An event is represented as a contiguous region of memory composed by
|
||||
// a header and a list of parameters appended, in the form of:
|
||||
//
|
||||
// | evt header | len param 1 (2B/4B) | ... | len param N (2B/4B) | data param 1 | ... | data param
|
||||
// N |
|
||||
//
|
||||
// The event header is composed of:
|
||||
// - ts: the event timestamp, in nanoseconds since the epoch.
|
||||
// Can be (uint64_t)-1, in which case the framework will automatically
|
||||
// fill the event time with the current time.
|
||||
// - tid: the tid of the thread that generated this event.
|
||||
// Can be (uint64_t)-1 in case no thread is specified, such as when generating
|
||||
// a plugin event (type code 322).
|
||||
// - len: the event len, including the header
|
||||
// - type: the type of the event, as per the ones supported by the libscap specifics.
|
||||
// This dictates the number and kind of parameters, and whether the lenght is
|
||||
// encoded as a 2 bytes or 4 bytes integer.
|
||||
// - nparams: the number of parameters of the event
|
||||
#if defined _MSC_VER
|
||||
#pragma pack(push)
|
||||
#pragma pack(1)
|
||||
#else
|
||||
#pragma pack(push, 1)
|
||||
#endif
|
||||
struct ss_plugin_event {
|
||||
#ifdef PPM_ENABLE_SENTINEL
|
||||
uint32_t sentinel_begin;
|
||||
#endif
|
||||
uint64_t ts; /* timestamp, in nanoseconds from epoch */
|
||||
uint64_t tid; /* the tid of the thread that generated this event */
|
||||
uint32_t len; /* the event len, including the header */
|
||||
uint16_t type; /* the event type */
|
||||
uint32_t nparams; /* the number of parameters of the event */
|
||||
};
|
||||
#pragma pack(pop)
|
||||
typedef struct ss_plugin_event ss_plugin_event;
|
||||
|
||||
// This struct represents an event provided by the framework to the plugin
|
||||
// as a read-only input.
|
||||
// - evt: a pointer to the header of the provided event.
|
||||
// - evtnum: assigned by the framework and incremented for each event.
|
||||
// Might not be contiguous.
|
||||
// - evtsrc: The name of the event's source. Can be "syscall" or any other
|
||||
// event source name implemented by a plugin.
|
||||
typedef struct ss_plugin_event_input {
|
||||
const ss_plugin_event* evt;
|
||||
uint64_t evtnum;
|
||||
const char* evtsrc;
|
||||
} ss_plugin_event_input;
|
||||
|
||||
typedef struct ss_plugin_byte_buffer {
|
||||
uint32_t len;
|
||||
const void* ptr;
|
||||
} ss_plugin_byte_buffer;
|
||||
|
||||
// Used in extract_fields_and_offsets to receive field value offsets
|
||||
// along with field data.
|
||||
// Extraction functions that support offsets should be set these to an
|
||||
// array of zero-indexed start offsets and lengths of each returned
|
||||
// value in the event or log data. {0, 0} can be used to indicate that
|
||||
// there are no valid offsets, e.g. if the value was generated or
|
||||
// computed from other data.
|
||||
// Extraction functions might not support offsets. In order to detect
|
||||
// this, callers should initialize the start and length to nullptr.
|
||||
typedef struct ss_plugin_extract_value_offsets {
|
||||
uint32_t* start;
|
||||
uint32_t* length;
|
||||
} ss_plugin_extract_value_offsets;
|
||||
|
||||
// Used in extract_fields functions below to receive a field/arg
|
||||
// pair and return an extracted value.
|
||||
// field_id: id of the field, as of its index in the list of
|
||||
// fields specified by the plugin.
|
||||
// field: the field name.
|
||||
// arg_key: the field argument, if a 'key' argument has been specified
|
||||
// for the field (isKey=true), otherwise it's NULL.
|
||||
// For example:
|
||||
// * if the field specified by the user is foo.bar[pippo], arg_key
|
||||
// will be the string "pippo"
|
||||
// * if the field specified by the user is foo.bar, arg will be NULL
|
||||
// arg_index: the field argument, if a 'index' argument has been specified
|
||||
// for the field (isIndex=true), otherwise it's 0.
|
||||
// For example:
|
||||
// * if the field specified by the user is foo.bar[1], arg_index
|
||||
// will be the uint64_t '1'.
|
||||
// Please note the ambiguity with a 0
|
||||
// argument which could be a real argument of just the default
|
||||
// value to point out the absence. The `arg_present` field resolves
|
||||
// this ambiguity.
|
||||
// arg_present: helps to understand if the arg is there since arg_index is
|
||||
// 0-based.
|
||||
// ftype: the type of the field. Could be derived from the field name alone,
|
||||
// but including here can prevent a second lookup of field names.
|
||||
// flist: whether the field can extract lists of values or not.
|
||||
// Could be derived from the field name alone, but including it
|
||||
// here can prevent a second lookup of field names.
|
||||
// The following should be filled in by the extraction function:
|
||||
// - res: this union should be filled with a pointer to an array of values.
|
||||
// The array represent the list of extracted values for this field from a given event.
|
||||
// Each array element should be filled with a char* string if the corresponding
|
||||
// field was type==string, and with a uint64 value if the corresponding field was
|
||||
// type==uint64.
|
||||
// - res_len: the length of the array of pointed by res.
|
||||
// If the field is not a list type, then res_len must be either 0 or 1.
|
||||
// If the field is a list type, then res_len can must be any value from 0 to N, depending
|
||||
// on how many values can be extracted from a given event.
|
||||
// Setting res_len to 0 means that no value of this field can be extracted from a given event.
|
||||
typedef struct ss_plugin_extract_field {
|
||||
// NOTE: For a given architecture, this has always the same size which
|
||||
// is sizeof(uintptr_t). Adding new value types will not create breaking
|
||||
// changes in the plugin API. However, we must make sure that each added
|
||||
// type is always a pointer.
|
||||
union {
|
||||
const char** str;
|
||||
uint64_t* u64;
|
||||
uint32_t* u32;
|
||||
ss_plugin_bool* boolean;
|
||||
ss_plugin_byte_buffer* buf;
|
||||
} res;
|
||||
uint64_t res_len;
|
||||
|
||||
// NOTE: When/if adding new input fields, make sure of appending them
|
||||
// at the end of the struct to avoid introducing breaking changes in the
|
||||
// plugin API.
|
||||
uint32_t field_id;
|
||||
const char* field;
|
||||
const char* arg_key;
|
||||
uint64_t arg_index;
|
||||
ss_plugin_bool arg_present;
|
||||
uint32_t ftype;
|
||||
ss_plugin_bool flist;
|
||||
} ss_plugin_extract_field;
|
||||
|
||||
// Opaque a pointer to a state table. The falcosecurity libs define stateful
|
||||
// components in the form of tables.
|
||||
typedef void ss_plugin_table_t;
|
||||
|
||||
// Opaque a pointer to an entry of a state table.
|
||||
typedef void ss_plugin_table_entry_t;
|
||||
|
||||
// Opaque accessor to a data field available in the entries of a state table.
|
||||
typedef void ss_plugin_table_field_t;
|
||||
|
||||
// Types supported by entry fields of state tables.
|
||||
// The noncontinguous numbers are to maintain equality with underlying
|
||||
// falcosecurity libs types.
|
||||
// todo(jasondellaluce): should we merge this with ss_plugin_field_type?
|
||||
typedef enum ss_plugin_state_type {
|
||||
SS_PLUGIN_ST_INT8 = 1,
|
||||
SS_PLUGIN_ST_INT16 = 2,
|
||||
SS_PLUGIN_ST_INT32 = 3,
|
||||
SS_PLUGIN_ST_INT64 = 4,
|
||||
SS_PLUGIN_ST_UINT8 = 5,
|
||||
SS_PLUGIN_ST_UINT16 = 6,
|
||||
SS_PLUGIN_ST_UINT32 = 7,
|
||||
SS_PLUGIN_ST_UINT64 = 8,
|
||||
SS_PLUGIN_ST_STRING = 9,
|
||||
SS_PLUGIN_ST_TABLE = 10,
|
||||
SS_PLUGIN_ST_BOOL = 25
|
||||
} ss_plugin_state_type;
|
||||
|
||||
// Data representation of entry fields of state tables.
|
||||
// todo(jasondellaluce): should we merge this with what we have for field extraction?
|
||||
typedef union ss_plugin_state_data {
|
||||
int8_t s8;
|
||||
int16_t s16;
|
||||
int32_t s32;
|
||||
int64_t s64;
|
||||
uint8_t u8;
|
||||
uint16_t u16;
|
||||
uint32_t u32;
|
||||
uint64_t u64;
|
||||
const char* str;
|
||||
ss_plugin_bool b;
|
||||
ss_plugin_table_t* table;
|
||||
} ss_plugin_state_data;
|
||||
|
||||
// Info about a state table.
|
||||
typedef struct ss_plugin_table_info {
|
||||
const char* name;
|
||||
ss_plugin_state_type key_type;
|
||||
} ss_plugin_table_info;
|
||||
|
||||
// Info about a data field contained in the entires of a state table.
|
||||
typedef struct ss_plugin_table_fieldinfo {
|
||||
const char* name;
|
||||
ss_plugin_state_type field_type;
|
||||
ss_plugin_bool read_only;
|
||||
} ss_plugin_table_fieldinfo;
|
||||
|
||||
// Opaque pointer to the owner of a plugin. It can be used to invert the
|
||||
// control and invoke functions of the plugin's owner from within the plugin.
|
||||
typedef void ss_plugin_owner_t;
|
||||
|
||||
//
|
||||
// This is the opaque pointer to the state of a plugin.
|
||||
// It points to any data that might be needed plugin-wise. It is
|
||||
// allocated by init() and must be destroyed by destroy().
|
||||
// It is defined as void because the framework doesn't care what it is
|
||||
// and it treats is as opaque.
|
||||
//
|
||||
typedef void ss_plugin_t;
|
||||
|
||||
//
|
||||
// This is the opaque pointer to the state of an open instance of the source
|
||||
// plugin.
|
||||
// It points to any data that is needed while a capture is running. It is
|
||||
// allocated by open() and must be destroyed by close().
|
||||
// It is defined as void because the framework doesn't care what it is
|
||||
// and it treats is as opaque.
|
||||
//
|
||||
typedef void ss_instance_t;
|
||||
|
||||
//
|
||||
// Severity available in the logging facility provided by the framework
|
||||
typedef enum ss_plugin_log_severity {
|
||||
SS_PLUGIN_LOG_SEV_FATAL = 1,
|
||||
SS_PLUGIN_LOG_SEV_CRITICAL = 2,
|
||||
SS_PLUGIN_LOG_SEV_ERROR = 3,
|
||||
SS_PLUGIN_LOG_SEV_WARNING = 4,
|
||||
SS_PLUGIN_LOG_SEV_NOTICE = 5,
|
||||
SS_PLUGIN_LOG_SEV_INFO = 6,
|
||||
SS_PLUGIN_LOG_SEV_DEBUG = 7,
|
||||
SS_PLUGIN_LOG_SEV_TRACE = 8,
|
||||
} ss_plugin_log_severity;
|
||||
|
||||
// Types supported by the by the metric values
|
||||
typedef enum ss_plugin_metric_value_type {
|
||||
SS_PLUGIN_METRIC_VALUE_TYPE_U32 = 0,
|
||||
SS_PLUGIN_METRIC_VALUE_TYPE_S32 = 1,
|
||||
SS_PLUGIN_METRIC_VALUE_TYPE_U64 = 2,
|
||||
SS_PLUGIN_METRIC_VALUE_TYPE_S64 = 3,
|
||||
SS_PLUGIN_METRIC_VALUE_TYPE_D = 4,
|
||||
SS_PLUGIN_METRIC_VALUE_TYPE_F = 5,
|
||||
SS_PLUGIN_METRIC_VALUE_TYPE_I = 6,
|
||||
} ss_plugin_metric_value_type;
|
||||
|
||||
// Data representation of metric values
|
||||
typedef union ss_plugin_metric_value {
|
||||
uint32_t u32;
|
||||
int32_t s32;
|
||||
uint64_t u64;
|
||||
int64_t s64;
|
||||
double d;
|
||||
float f;
|
||||
int i;
|
||||
} ss_plugin_metric_value;
|
||||
|
||||
// Metric types
|
||||
typedef enum ss_plugin_metric_type {
|
||||
SS_PLUGIN_METRIC_TYPE_MONOTONIC = 0,
|
||||
SS_PLUGIN_METRIC_TYPE_NON_MONOTONIC = 1,
|
||||
} ss_plugin_metric_type;
|
||||
|
||||
//
|
||||
// Struct representing a metric to be provided to the plugin framework
|
||||
typedef struct ss_plugin_metric {
|
||||
//
|
||||
// Opaque string representing the metric name
|
||||
const char* name;
|
||||
//
|
||||
// Metric type
|
||||
ss_plugin_metric_type type;
|
||||
//
|
||||
// Metric numeric value
|
||||
ss_plugin_metric_value value;
|
||||
//
|
||||
// Metric value data type
|
||||
ss_plugin_metric_value_type value_type;
|
||||
} ss_plugin_metric;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,13 @@
|
|||
diff --git a/pkg/sdk/plugin_api.h b/pkg/sdk/plugin_api.h
|
||||
index 0c877a2..54949b3 100644
|
||||
--- a/pkg/sdk/plugin_api.h
|
||||
+++ b/pkg/sdk/plugin_api.h
|
||||
@@ -18,7 +18,7 @@ limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
-#include <plugin/plugin_types.h>
|
||||
+#include "plugin_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -30,8 +31,7 @@ limitations under the License.
|
|||
// build their plugins.
|
||||
//
|
||||
// Most of the time, a plugin author only needs to import the following packages,
|
||||
// which provide the "default" streamlined interfaces to implementing a source
|
||||
// or extractor plugin:
|
||||
// which provide the "default" streamlined interfaces to implementing plugins:
|
||||
// "github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
|
||||
// "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins"
|
||||
// "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins/{source,extractor}"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,7 +16,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
// Package extractor provides high-level constructs to easily build
|
||||
// extractor plugins.
|
||||
// plugins with field extraction capability.
|
||||
package extractor
|
||||
|
||||
import (
|
||||
|
@ -25,15 +26,10 @@ import (
|
|||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/extract"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/fields"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/info"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/initialize"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/initschema"
|
||||
_ "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/lasterr"
|
||||
)
|
||||
|
||||
var registered = false
|
||||
|
||||
// Plugin is an interface representing an extractor plugin.
|
||||
// Plugin is an interface representing a plugin with field extraction capability.
|
||||
type Plugin interface {
|
||||
plugins.Plugin
|
||||
sdk.Extractor
|
||||
|
@ -43,57 +39,33 @@ type Plugin interface {
|
|||
Fields() []sdk.FieldEntry
|
||||
}
|
||||
|
||||
// Register registers a Plugin extractor plugin in the framework. This function
|
||||
// needs to be called in a Go init() function. Calling this function more than
|
||||
// once will cause a panic.
|
||||
//
|
||||
// Register can also be called to register source plugins with optional
|
||||
// extraction capabilities. If this function is called before, or after, having
|
||||
// registered a source plugin in the SDK, the registered plugin will be a
|
||||
// plugin of type sdk.TypeSourcePlugin with extraction capabilities enabled.
|
||||
func Register(p Plugin) {
|
||||
if registered {
|
||||
panic("plugin-sdk-go/sdk/plugins/extractor: register can be called only once")
|
||||
}
|
||||
// note: here the plugin Init method might have
|
||||
// called extract.SetAsync, which influences whether the async optimization
|
||||
// will be actually used or not. Potentially, extract.SetAsync might be
|
||||
// invoked with different boolean values at subsequent plugin initializations,
|
||||
// however this code is still safe since:
|
||||
// - Init methods are called in sequence and not concurrently. As such,
|
||||
// every plugin invoking extract.SetAsync influences behavior of
|
||||
// extract.StartAsync only for itself.
|
||||
// - The hooks.SetOnBeforeDestroy callback is idempotent, because
|
||||
// extract.StopAsync works without having context on whether the current
|
||||
// plugin actually used the async optimization. Hence, extract.StopAsync
|
||||
// is not influenced by the invocations of extract.SetAsync.
|
||||
func enableAsync(handle cgo.Handle) {
|
||||
extract.StartAsync(handle)
|
||||
hooks.SetOnBeforeDestroy(func(handle cgo.Handle) {
|
||||
extract.StopAsync(handle)
|
||||
})
|
||||
}
|
||||
|
||||
// Currently TypeExtractorPlugin is also compatible with source plugins
|
||||
// that export extract-related symbols.
|
||||
switch info.Type() {
|
||||
case 0:
|
||||
info.SetType(sdk.TypeExtractorPlugin)
|
||||
case sdk.TypeExtractorPlugin:
|
||||
case sdk.TypeSourcePlugin: // source plugins have the priority over extractor plugins
|
||||
break
|
||||
default:
|
||||
panic("plugin-sdk-go/sdk/plugins/extractor: unsupported type has already been set")
|
||||
}
|
||||
i := p.Info()
|
||||
info.SetId(i.ID)
|
||||
info.SetName(i.Name)
|
||||
info.SetDescription(i.Description)
|
||||
info.SetEventSource(i.EventSource)
|
||||
info.SetContact(i.Contact)
|
||||
info.SetVersion(i.Version)
|
||||
info.SetRequiredAPIVersion(i.RequiredAPIVersion)
|
||||
info.SetExtractEventSources(i.ExtractEventSources)
|
||||
if initSchema, ok := p.(sdk.InitSchema); ok {
|
||||
initschema.SetInitSchema(initSchema.InitSchema())
|
||||
}
|
||||
// Register registers the field extraction capability in the framework for the given Plugin.
|
||||
//
|
||||
// This function should be called from the provided plugins.FactoryFunc implementation.
|
||||
// See the parent package for more detail. This function is idempotent.
|
||||
func Register(p Plugin) {
|
||||
|
||||
fields.SetFields(p.Fields())
|
||||
|
||||
initialize.SetOnInit(func(c string) (sdk.PluginState, error) {
|
||||
err := p.Init(c)
|
||||
return p, err
|
||||
})
|
||||
|
||||
// setup hooks for automatically start/stop async extraction
|
||||
hooks.SetOnAfterInit(func(handle cgo.Handle) {
|
||||
extract.StartAsync()
|
||||
hooks.SetOnBeforeDestroy(func(handle cgo.Handle) {
|
||||
extract.StopAsync()
|
||||
})
|
||||
})
|
||||
|
||||
registered = true
|
||||
hooks.SetOnAfterInit(enableAsync)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -22,7 +23,6 @@ import (
|
|||
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/info"
|
||||
)
|
||||
|
||||
type testPlugin struct {
|
||||
|
@ -55,6 +55,9 @@ func (m *testPlugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) error
|
|||
switch req.FieldID() {
|
||||
case 0:
|
||||
req.SetValue(uint64(0))
|
||||
if req.WantOffset() {
|
||||
req.SetValueOffset(sdk.PluginEventPayloadOffset, 8)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unsupported field: %s", req.Field())
|
||||
|
@ -69,27 +72,3 @@ func assertPanic(t *testing.T, fun func()) {
|
|||
}()
|
||||
fun()
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
registerFunc := func() {
|
||||
Register(&testPlugin{})
|
||||
}
|
||||
registerFunc()
|
||||
if info.Type() != sdk.TypeExtractorPlugin {
|
||||
t.Errorf("plugin type should be extractor")
|
||||
}
|
||||
assertPanic(t, registerFunc)
|
||||
|
||||
// With source plugin type
|
||||
registered = false
|
||||
info.SetType(sdk.TypeSourcePlugin)
|
||||
Register(&testPlugin{})
|
||||
if info.Type() != sdk.TypeSourcePlugin {
|
||||
t.Errorf("plugin type should be source")
|
||||
}
|
||||
|
||||
// With unknown plugin type
|
||||
registered = false
|
||||
info.SetType(3)
|
||||
assertPanic(t, registerFunc)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -19,6 +20,9 @@ package plugins
|
|||
import (
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/ptr"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/info"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/initialize"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/initschema"
|
||||
)
|
||||
|
||||
// Info is a struct containing the general information about a plugin.
|
||||
|
@ -133,3 +137,62 @@ type BasePlugin struct {
|
|||
BaseExtractRequests
|
||||
BaseOpenParams
|
||||
}
|
||||
|
||||
// FactoryFunc creates a new Plugin
|
||||
type FactoryFunc func() Plugin
|
||||
|
||||
// SetFactory sets the FactoryFunc to be used by the SDK when creating a new Plugin
|
||||
//
|
||||
// SetFactory should be called in the Go init() function of the plugin main package.
|
||||
// It hooks the plugin framework initialization stage to create a new Plugin and
|
||||
// to set up common facilities provided by this SDK. The given FactoryFunc must create
|
||||
// a Plugin and can optionally enable plugin capabilities by using the Register functions
|
||||
// provided by sub-packages. This function is idempotent.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins"
|
||||
// "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins/extractor"
|
||||
// "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins/source"
|
||||
// )
|
||||
//
|
||||
// func init() {
|
||||
// plugins.SetFactory(func() plugins.Plugin {
|
||||
// p := &MyPlugin{} // create a new Plugin
|
||||
// source.Register(p) // enable event sourcing capability
|
||||
// extractor.Register(p) // enable field extraction capability
|
||||
// return p
|
||||
// })
|
||||
// }
|
||||
//
|
||||
func SetFactory(f FactoryFunc) {
|
||||
|
||||
// Create a new plugin instance to get static plugin info
|
||||
p := f()
|
||||
|
||||
// Set up plugin info
|
||||
i := p.Info()
|
||||
info.SetId(i.ID)
|
||||
info.SetName(i.Name)
|
||||
info.SetDescription(i.Description)
|
||||
info.SetEventSource(i.EventSource)
|
||||
info.SetExtractEventSources(i.ExtractEventSources)
|
||||
info.SetContact(i.Contact)
|
||||
info.SetVersion(i.Version)
|
||||
info.SetRequiredAPIVersion(i.RequiredAPIVersion)
|
||||
|
||||
// Set up plugin init schema, if any
|
||||
if initSchema, ok := p.(sdk.InitSchema); ok {
|
||||
initschema.SetInitSchema(initSchema.InitSchema())
|
||||
}
|
||||
|
||||
initialize.SetOnInit(func(c string) (sdk.PluginState, error) {
|
||||
// Create a new plugin instance
|
||||
p := f()
|
||||
err := p.Init(c)
|
||||
return p, err
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -0,0 +1,349 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultInstanceTimeout = 30 * time.Millisecond
|
||||
)
|
||||
|
||||
type builtinInstance struct {
|
||||
BaseInstance
|
||||
shutdown func()
|
||||
progress func() (float64, string)
|
||||
ctx context.Context
|
||||
timeout time.Duration
|
||||
timeoutTicker *time.Ticker
|
||||
eof bool
|
||||
eventSize uint32
|
||||
batchSize uint32
|
||||
}
|
||||
|
||||
func (s *builtinInstance) Close() {
|
||||
// this cancels the context and calls the optional callback
|
||||
s.shutdown()
|
||||
|
||||
// stop timeout ticker
|
||||
s.timeoutTicker.Stop()
|
||||
}
|
||||
|
||||
func (s *builtinInstance) Progress(pState sdk.PluginState) (float64, string) {
|
||||
if s.progress != nil {
|
||||
return s.progress()
|
||||
}
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
// WithInstanceContext sets a custom context in the opened event source.
|
||||
// If the context is cancelled, the event source is closed and sdk.ErrEOF
|
||||
// is returned by the current invocation of NextBatch() and by any subsequent
|
||||
// invocation.
|
||||
func WithInstanceContext(ctx context.Context) func(*builtinInstance) {
|
||||
return func(s *builtinInstance) {
|
||||
s.ctx = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// WithInstanceTimeout sets a custom timeout in the opened event source.
|
||||
// When the timeout is reached, the current invocation of NextBatch() returns
|
||||
// sdk.ErrTimeout.
|
||||
func WithInstanceTimeout(timeout time.Duration) func(*builtinInstance) {
|
||||
return func(s *builtinInstance) {
|
||||
s.timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// WithInstanceClose sets a custom closing callback in the opened event source.
|
||||
// The passed-in function is invoked when the event source gets closed.
|
||||
func WithInstanceClose(close func()) func(*builtinInstance) {
|
||||
return func(s *builtinInstance) {
|
||||
s.shutdown = close
|
||||
}
|
||||
}
|
||||
|
||||
// WithInstanceBatchSize sets a custom size for the pre-allocated event batch
|
||||
// used by NextBatch()
|
||||
func WithInstanceBatchSize(size uint32) func(*builtinInstance) {
|
||||
return func(s *builtinInstance) {
|
||||
s.batchSize = size
|
||||
}
|
||||
}
|
||||
|
||||
// WithInstanceEventSize sets a custom maximum size for each event returned
|
||||
// by NextBatch()
|
||||
func WithInstanceEventSize(size uint32) func(*builtinInstance) {
|
||||
return func(s *builtinInstance) {
|
||||
s.eventSize = size
|
||||
}
|
||||
}
|
||||
|
||||
// WithInstanceProgress sets a custom callback for the framework to request
|
||||
// a the progress state of the opened event stream
|
||||
func WithInstanceProgress(progress func() (float64, string)) func(*builtinInstance) {
|
||||
return func(s *builtinInstance) {
|
||||
s.progress = progress
|
||||
}
|
||||
}
|
||||
|
||||
// PullFunc produces a new event and returns a non-nil error in case of failure.
|
||||
//
|
||||
// The event data is produced through the sdk.EventWriter interface.
|
||||
// The context argument can be used to check for termination signals, which
|
||||
// happen when the framework closes the event source or when the optional
|
||||
// context passed-in by the user gets cancelled.
|
||||
type PullFunc func(context.Context, sdk.EventWriter) error
|
||||
|
||||
type pullInstance struct {
|
||||
builtinInstance
|
||||
pull PullFunc
|
||||
}
|
||||
|
||||
// NewPullInstance opens a new event source and starts a capture session,
|
||||
// filling the event batches with a pull model.
|
||||
//
|
||||
// The PullFunc required argument is a function that creates a new event and
|
||||
// returns a non-nil error in case of success. The returned source.Instance
|
||||
// provides a pre-built implementation of NextBatch() that correctly handles
|
||||
// termination and timeouts. This should be used by developers to open an event
|
||||
// source without defining a new type and by using a functional design.
|
||||
//
|
||||
// The pull function is invoked sequentially and is blocking for the event
|
||||
// source, meaning that it must not be a suspensive function. This implies
|
||||
// avoiding suspending an execution through a select or through synchronization
|
||||
// primitives.
|
||||
//
|
||||
// Users can pass option parameters to influence the behavior of the opened
|
||||
// event source, such as passing a context or setting a custom timeout duration.
|
||||
//
|
||||
// The context passed-in to the pull function is cancelled automatically
|
||||
// when the framework invokes Close() on the event source, or when the
|
||||
// user-configured context is cancelled.
|
||||
func NewPullInstance(pull PullFunc, options ...func(*builtinInstance)) (Instance, error) {
|
||||
res := &pullInstance{
|
||||
pull: pull,
|
||||
builtinInstance: builtinInstance{
|
||||
ctx: context.Background(),
|
||||
timeout: defaultInstanceTimeout,
|
||||
shutdown: func() {},
|
||||
eof: false,
|
||||
batchSize: sdk.DefaultBatchSize,
|
||||
eventSize: sdk.DefaultEvtSize,
|
||||
},
|
||||
}
|
||||
|
||||
// apply options
|
||||
for _, opt := range options {
|
||||
opt(&res.builtinInstance)
|
||||
}
|
||||
|
||||
// create custom-sized event batch
|
||||
batch, err := sdk.NewEventWriters(int64(res.batchSize), int64(res.eventSize))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.SetEvents(batch)
|
||||
|
||||
// init timer
|
||||
res.timeoutTicker = time.NewTicker(res.timeout)
|
||||
|
||||
// setup internally-cancellable context
|
||||
prevCancel := res.shutdown
|
||||
cancelableCtx, cancelCtx := context.WithCancel(res.ctx)
|
||||
res.ctx = cancelableCtx
|
||||
res.shutdown = func() {
|
||||
cancelCtx()
|
||||
prevCancel()
|
||||
}
|
||||
|
||||
// return opened instance
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *pullInstance) NextBatch(pState sdk.PluginState, evts sdk.EventWriters) (n int, err error) {
|
||||
// once EOF has been hit, we should return it at each new call of NextBatch
|
||||
if s.eof {
|
||||
return 0, sdk.ErrEOF
|
||||
}
|
||||
|
||||
// timeout needs to be resetted for this batch
|
||||
s.timeoutTicker.Reset(s.timeout)
|
||||
|
||||
// attempt filling the event batch
|
||||
n = 0
|
||||
for n < evts.Len() {
|
||||
// check if we should return before pulling another event
|
||||
select {
|
||||
// timeout hits, so we flush a partial batch
|
||||
case <-s.timeoutTicker.C:
|
||||
return n, sdk.ErrTimeout
|
||||
// context has been canceled, so we exit
|
||||
case <-s.ctx.Done():
|
||||
s.eof = true
|
||||
return n, sdk.ErrEOF
|
||||
default:
|
||||
}
|
||||
|
||||
// pull a new event
|
||||
if err = s.pull(s.ctx, evts.Get(n)); err != nil {
|
||||
// in case of non-timeout error, we consider the event source ended
|
||||
if err != sdk.ErrTimeout {
|
||||
s.eof = true
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
// return a full batch
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// PushEvent represents an event produced from an event source with the push model.
|
||||
//
|
||||
// If the event source produced the event successfully, then Data must be non-nil
|
||||
// and Err must be ni. If the event source encountered a failure, Data must be
|
||||
// nil and Err should contain an error describing the failure.
|
||||
//
|
||||
// Timestamp can be optionally set to indicate a specific timestamp for the
|
||||
// produced event.
|
||||
type PushEvent struct {
|
||||
Err error
|
||||
Data []byte
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
type pushInstance struct {
|
||||
builtinInstance
|
||||
evtC <-chan PushEvent
|
||||
}
|
||||
|
||||
// NewPushInstance opens a new event source and starts a capture session,
|
||||
// filling the event batches with a push model.
|
||||
//
|
||||
// In this model, events are produced through a channel in the form of
|
||||
// source.PushEvent messages. This is suitable for cases in which event
|
||||
// production is suspensive, meaning that the time elapsed waiting for a
|
||||
// new event to be produced is not deterministic or has no guaranteed limit.
|
||||
//
|
||||
// Users can pass option parameters to influence the behavior of the opened
|
||||
// event source, such as passing a context or setting a custom timeout duration.
|
||||
//
|
||||
// The opened event source can be manually closed by cancelling the optional
|
||||
// passed-in context, by closing the event cannel, or by sending
|
||||
// source.PushEvent containing a non-nil Err.
|
||||
func NewPushInstance(evtC <-chan PushEvent, options ...func(*builtinInstance)) (Instance, error) {
|
||||
res := &pushInstance{
|
||||
evtC: evtC,
|
||||
builtinInstance: builtinInstance{
|
||||
ctx: context.Background(),
|
||||
timeout: defaultInstanceTimeout,
|
||||
shutdown: func() {},
|
||||
eof: false,
|
||||
batchSize: sdk.DefaultBatchSize,
|
||||
eventSize: sdk.DefaultEvtSize,
|
||||
},
|
||||
}
|
||||
|
||||
// apply options
|
||||
for _, opt := range options {
|
||||
opt(&res.builtinInstance)
|
||||
}
|
||||
|
||||
// create custom-sized event batch
|
||||
batch, err := sdk.NewEventWriters(int64(res.batchSize), int64(res.eventSize))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.SetEvents(batch)
|
||||
|
||||
// init timer
|
||||
res.timeoutTicker = time.NewTicker(res.timeout)
|
||||
|
||||
// setup internally-cancellable context
|
||||
prevCancel := res.shutdown
|
||||
cancelableCtx, cancelCtx := context.WithCancel(res.ctx)
|
||||
res.ctx = cancelableCtx
|
||||
res.shutdown = func() {
|
||||
cancelCtx()
|
||||
prevCancel()
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *pushInstance) NextBatch(pState sdk.PluginState, evts sdk.EventWriters) (int, error) {
|
||||
// once EOF has been hit, we should return it at each new call of NextBatch
|
||||
if s.eof {
|
||||
return 0, sdk.ErrEOF
|
||||
}
|
||||
|
||||
// timeout needs to be resetted for this batch
|
||||
s.timeoutTicker.Reset(s.timeout)
|
||||
|
||||
// attempt filling the event batch
|
||||
n := 0
|
||||
for n < evts.Len() {
|
||||
select {
|
||||
// an event is received, so we add it in the batch
|
||||
case evt, ok := <-s.evtC:
|
||||
// event channel is closed, we reached EOF
|
||||
if !ok {
|
||||
evt.Err = sdk.ErrEOF
|
||||
}
|
||||
// if there are no errors so far, try writing the event
|
||||
if evt.Err == nil {
|
||||
if l, wErr := evts.Get(n).Writer().Write(evt.Data); wErr != nil {
|
||||
evt.Err = wErr
|
||||
} else if l < len(evt.Data) {
|
||||
evt.Err = io.ErrShortWrite
|
||||
}
|
||||
}
|
||||
// an error occurred, so we need to exit
|
||||
if evt.Err != nil {
|
||||
// in case of non-timeout error, we consider the event source ended
|
||||
if evt.Err != sdk.ErrTimeout {
|
||||
s.eof = true
|
||||
}
|
||||
return n, evt.Err
|
||||
}
|
||||
// event added to the batch successfully
|
||||
if evt.Timestamp.IsZero() {
|
||||
evts.Get(n).SetTimestamp(math.MaxUint64)
|
||||
} else {
|
||||
evts.Get(n).SetTimestamp(uint64(evt.Timestamp.UnixNano()))
|
||||
}
|
||||
n++
|
||||
// timeout hits, so we flush a partial batch
|
||||
case <-s.timeoutTicker.C:
|
||||
return n, sdk.ErrTimeout
|
||||
// context has been canceled, so we exit
|
||||
case <-s.ctx.Done():
|
||||
s.eof = true
|
||||
return n, sdk.ErrEOF
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
|
@ -0,0 +1,391 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package source
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
|
||||
sdkint "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/internal/sdk"
|
||||
)
|
||||
|
||||
const (
|
||||
benchFixedEvtDataSize = 1024
|
||||
benchMinEvtDataSize = 1024
|
||||
benchMaxEvtDataSize = 1 * 1024 * 1204
|
||||
benchEvtCount = 1024
|
||||
benchEvtBatchSize = sdk.DefaultBatchSize
|
||||
benchEvtTimeout = 30 * time.Millisecond
|
||||
)
|
||||
|
||||
func benchNextBatch(b *testing.B, inst Instance, batchSize uint32, evtCount int) {
|
||||
batch := &sdkint.InMemoryEventWriters{}
|
||||
for i := uint32(0); i < batchSize; i++ {
|
||||
batch.Writers = append(batch.Writers, &sdkint.InMemoryEventWriter{})
|
||||
}
|
||||
b.ResetTimer()
|
||||
tot := 0
|
||||
n := 0
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
tot = 0
|
||||
for tot < evtCount {
|
||||
n, err = inst.NextBatch(nil, batch)
|
||||
if err != nil {
|
||||
if err == sdk.ErrEOF {
|
||||
break
|
||||
}
|
||||
if err != sdk.ErrTimeout {
|
||||
b.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
tot += n
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
if closer, ok := inst.(sdk.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func benchPullInstance(b *testing.B, onEvt func() []byte) {
|
||||
pull := func(c context.Context, e sdk.EventWriter) error {
|
||||
_, err := e.Writer().Write(onEvt())
|
||||
return err
|
||||
}
|
||||
inst, err := NewPullInstance(pull, WithInstanceTimeout(benchEvtTimeout))
|
||||
if err != nil {
|
||||
b.Fatal(err.Error())
|
||||
}
|
||||
benchNextBatch(b, inst, benchEvtBatchSize, benchEvtCount)
|
||||
}
|
||||
|
||||
func benchPushInstance(b *testing.B, onEvt func() []byte) {
|
||||
evtChan := make(chan PushEvent)
|
||||
stopChan := make(chan bool)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case evtChan <- PushEvent{Data: onEvt()}:
|
||||
case <-stopChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
inst, err := NewPushInstance(evtChan, WithInstanceTimeout(benchEvtTimeout))
|
||||
if err != nil {
|
||||
b.Fatal(err.Error())
|
||||
}
|
||||
benchNextBatch(b, inst, benchEvtBatchSize, benchEvtCount)
|
||||
stopChan <- true
|
||||
close(stopChan)
|
||||
close(evtChan)
|
||||
}
|
||||
|
||||
// simulate event generation
|
||||
func createEventData(size uint32) []byte {
|
||||
buf := bytes.Buffer{}
|
||||
for size > 0 {
|
||||
buf.WriteByte(0)
|
||||
size--
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func BenchmarkPullEmpty(b *testing.B) {
|
||||
data := []byte{}
|
||||
benchPullInstance(b, func() []byte { return data })
|
||||
}
|
||||
|
||||
func BenchmarkPushEmpty(b *testing.B) {
|
||||
data := []byte{}
|
||||
benchPushInstance(b, func() []byte { return data })
|
||||
}
|
||||
|
||||
func BenchmarkPullFixed(b *testing.B) {
|
||||
benchPullInstance(b, func() []byte { return createEventData(benchFixedEvtDataSize) })
|
||||
}
|
||||
|
||||
func BenchmarkPushFixed(b *testing.B) {
|
||||
benchPushInstance(b, func() []byte { return createEventData(benchFixedEvtDataSize) })
|
||||
}
|
||||
|
||||
func BenchmarkPullRandom(b *testing.B) {
|
||||
benchPullInstance(b, func() []byte {
|
||||
return createEventData((rand.Uint32() % (benchMaxEvtDataSize - benchMinEvtDataSize)) + benchMinEvtDataSize)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkPushRandom(b *testing.B) {
|
||||
benchPushInstance(b, func() []byte {
|
||||
return createEventData((rand.Uint32() % (benchMaxEvtDataSize - benchMinEvtDataSize)) + benchMinEvtDataSize)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPullInstance(t *testing.T) {
|
||||
timeout := time.Millisecond * 10
|
||||
|
||||
// create batch
|
||||
batch := &sdkint.InMemoryEventWriters{}
|
||||
for i := uint32(0); i < sdk.DefaultBatchSize; i++ {
|
||||
batch.Writers = append(batch.Writers, &sdkint.InMemoryEventWriter{})
|
||||
}
|
||||
|
||||
// setup evt generation callback
|
||||
nEvt := 0
|
||||
pull := func(c context.Context, e sdk.EventWriter) error {
|
||||
if nEvt == 0 {
|
||||
time.Sleep(timeout * 10)
|
||||
}
|
||||
if nEvt == 3 {
|
||||
return sdk.ErrEOF
|
||||
}
|
||||
nEvt++
|
||||
e.Writer().Write(createEventData(100))
|
||||
return nil
|
||||
}
|
||||
|
||||
// setup closing callback
|
||||
closed := false
|
||||
close := func() { closed = true }
|
||||
|
||||
// open instance
|
||||
inst, err := NewPullInstance(
|
||||
pull,
|
||||
WithInstanceTimeout(timeout),
|
||||
WithInstanceClose(close),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// fist call to nextbatch should trigger the timeout and return 1 evt
|
||||
n, err := inst.NextBatch(nil, batch)
|
||||
if err != sdk.ErrTimeout {
|
||||
t.Fatalf("expected sdk.ErrTimeout, but found error: %s ", err)
|
||||
} else if n != 1 {
|
||||
t.Fatalf("expected %d, but found %d", 1, n)
|
||||
}
|
||||
|
||||
// second call to nextbatch should trigger the EOF and return 2 evts
|
||||
n, err = inst.NextBatch(nil, batch)
|
||||
if err != sdk.ErrEOF {
|
||||
t.Fatalf("expected sdk.ErrEOF, but found error: %s ", err)
|
||||
} else if n != 2 {
|
||||
t.Fatalf("expected %d, but found %d", 2, n)
|
||||
}
|
||||
|
||||
// close instance
|
||||
closer, ok := inst.(sdk.Closer)
|
||||
if !ok {
|
||||
t.Fatalf("instance does not implement sdk.Closer")
|
||||
}
|
||||
closer.Close()
|
||||
if !closed {
|
||||
t.Fatalf("expected close callback to be invoked")
|
||||
}
|
||||
|
||||
// every other call should return EOF
|
||||
n, err = inst.NextBatch(nil, batch)
|
||||
if err != sdk.ErrEOF {
|
||||
t.Fatalf("expected sdk.ErrEOF, but found error: %s ", err)
|
||||
} else if n != 0 {
|
||||
t.Fatalf("expected %d, but found %d", 0, n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullInstanceCtxCanceling(t *testing.T) {
|
||||
// create batch
|
||||
batch := &sdkint.InMemoryEventWriters{}
|
||||
for i := uint32(0); i < sdk.DefaultBatchSize; i++ {
|
||||
batch.Writers = append(batch.Writers, &sdkint.InMemoryEventWriter{})
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
pull := func(c context.Context, e sdk.EventWriter) error {
|
||||
return sdk.ErrTimeout
|
||||
}
|
||||
inst, err := NewPullInstance(pull, WithInstanceContext(ctx))
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// fist call to nextbatch should trigger the timeout and return no evts
|
||||
n, err := inst.NextBatch(nil, batch)
|
||||
if err != sdk.ErrTimeout {
|
||||
t.Fatalf("expected sdk.ErrTimeout, but found error: %s ", err)
|
||||
} else if n != 0 {
|
||||
t.Fatalf("expected %d, but found %d", 0, n)
|
||||
}
|
||||
|
||||
// cancel context
|
||||
cancel()
|
||||
|
||||
// next call to nextbatch should trigger EOF
|
||||
n, err = inst.NextBatch(nil, batch)
|
||||
if err != sdk.ErrEOF {
|
||||
t.Fatalf("expected sdk.ErrEOF, but found error: %s ", err)
|
||||
} else if n != 0 {
|
||||
t.Fatalf("expected %d, but found %d", 0, n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushInstance(t *testing.T) {
|
||||
timeout := time.Millisecond * 100
|
||||
|
||||
// create batch
|
||||
batch := &sdkint.InMemoryEventWriters{}
|
||||
for i := uint32(0); i < sdk.DefaultBatchSize; i++ {
|
||||
batch.Writers = append(batch.Writers, &sdkint.InMemoryEventWriter{})
|
||||
}
|
||||
|
||||
// setup evt generation worker
|
||||
evtChan := make(chan PushEvent)
|
||||
waitChan := make(chan bool)
|
||||
defer close(evtChan)
|
||||
defer close(waitChan)
|
||||
go func() {
|
||||
data := createEventData(100)
|
||||
evtChan <- PushEvent{Data: data}
|
||||
<-waitChan // trigger timeout at first event
|
||||
evtChan <- PushEvent{Data: data}
|
||||
evtChan <- PushEvent{Data: data}
|
||||
evtChan <- PushEvent{Err: sdk.ErrEOF}
|
||||
}()
|
||||
|
||||
// setup closing callback
|
||||
closed := false
|
||||
close := func() { closed = true }
|
||||
|
||||
// open instance
|
||||
inst, err := NewPushInstance(
|
||||
evtChan,
|
||||
WithInstanceTimeout(timeout),
|
||||
WithInstanceClose(close),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// fist call to nextbatch should trigger the timeout and return evts
|
||||
n, err := inst.NextBatch(nil, batch)
|
||||
if err != sdk.ErrTimeout {
|
||||
t.Fatalf("expected sdk.ErrTimeout, but found error: %s ", err)
|
||||
} else if n != 1 {
|
||||
t.Fatalf("expected %d, but found %d", 1, n)
|
||||
}
|
||||
waitChan <- true
|
||||
|
||||
// second call to nextbatch should trigger the EOF and return 2 evts
|
||||
n, err = inst.NextBatch(nil, batch)
|
||||
if err != sdk.ErrEOF {
|
||||
t.Fatalf("expected sdk.ErrEOF, but found error: %s ", err)
|
||||
} else if n != 2 {
|
||||
t.Fatalf("expected %d, but found %d", 2, n)
|
||||
}
|
||||
|
||||
// close instance
|
||||
closer, ok := inst.(sdk.Closer)
|
||||
if !ok {
|
||||
t.Fatalf("instance does not implement sdk.Closer")
|
||||
}
|
||||
closer.Close()
|
||||
if !closed {
|
||||
t.Fatalf("expected close callback to be invoked")
|
||||
}
|
||||
|
||||
// every other call should return EOF
|
||||
n, err = inst.NextBatch(nil, batch)
|
||||
if err != sdk.ErrEOF {
|
||||
t.Fatalf("expected sdk.ErrEOF, but found error: %s ", err)
|
||||
} else if n != 0 {
|
||||
t.Fatalf("expected %d, but found %d", 0, n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushInstanceChanClosing(t *testing.T) {
|
||||
// create batch
|
||||
batch := &sdkint.InMemoryEventWriters{}
|
||||
for i := uint32(0); i < sdk.DefaultBatchSize; i++ {
|
||||
batch.Writers = append(batch.Writers, &sdkint.InMemoryEventWriter{})
|
||||
}
|
||||
|
||||
evtChan := make(chan PushEvent)
|
||||
inst, err := NewPushInstance(evtChan)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// fist call to nextbatch should trigger the timeout and return no evts
|
||||
n, err := inst.NextBatch(nil, batch)
|
||||
if err != sdk.ErrTimeout {
|
||||
t.Fatalf("expected sdk.ErrTimeout, but found error: %s ", err)
|
||||
} else if n != 0 {
|
||||
t.Fatalf("expected %d, but found %d", 0, n)
|
||||
}
|
||||
|
||||
// close channel
|
||||
close(evtChan)
|
||||
|
||||
// next call to nextbatch should trigger EOF
|
||||
n, err = inst.NextBatch(nil, batch)
|
||||
if err != sdk.ErrEOF {
|
||||
t.Fatalf("expected sdk.ErrEOF, but found error: %s ", err)
|
||||
} else if n != 0 {
|
||||
t.Fatalf("expected %d, but found %d", 0, n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushInstanceCtxCanceling(t *testing.T) {
|
||||
// create batch
|
||||
batch := &sdkint.InMemoryEventWriters{}
|
||||
for i := uint32(0); i < sdk.DefaultBatchSize; i++ {
|
||||
batch.Writers = append(batch.Writers, &sdkint.InMemoryEventWriter{})
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
evtChan := make(chan PushEvent)
|
||||
defer close(evtChan)
|
||||
inst, err := NewPushInstance(evtChan, WithInstanceContext(ctx))
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// fist call to nextbatch should trigger the timeout and return no evts
|
||||
n, err := inst.NextBatch(nil, batch)
|
||||
if err != sdk.ErrTimeout {
|
||||
t.Fatalf("expected sdk.ErrTimeout, but found error: %s ", err)
|
||||
} else if n != 0 {
|
||||
t.Fatalf("expected %d, but found %d", 0, n)
|
||||
}
|
||||
|
||||
// cancel context
|
||||
cancel()
|
||||
|
||||
// next call to nextbatch should trigger EOF
|
||||
n, err = inst.NextBatch(nil, batch)
|
||||
if err != sdk.ErrEOF {
|
||||
t.Fatalf("expected sdk.ErrEOF, but found error: %s ", err)
|
||||
} else if n != 0 {
|
||||
t.Fatalf("expected %d, but found %d", 0, n)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,16 +16,13 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
// Package source provides high-level constructs to easily build
|
||||
// source plugins.
|
||||
// plugins with event sourcing capability.
|
||||
package source
|
||||
|
||||
import (
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins"
|
||||
_ "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/evtstr"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/info"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/initialize"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/initschema"
|
||||
_ "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/lasterr"
|
||||
_ "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/listopen"
|
||||
_ "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/nextbatch"
|
||||
|
@ -32,15 +30,13 @@ import (
|
|||
_ "github.com/falcosecurity/plugin-sdk-go/pkg/sdk/symbols/progress"
|
||||
)
|
||||
|
||||
var registered = false
|
||||
|
||||
// Plugin is an interface representing a source plugin.
|
||||
// Plugin is an interface representing a plugin with event sourcing capability
|
||||
type Plugin interface {
|
||||
plugins.Plugin
|
||||
sdk.Stringer
|
||||
sdk.StringerBuffer
|
||||
sdk.OpenParamsBuffer
|
||||
// (optional) sdk.OpenParams
|
||||
// (optional) sdk.Stringer
|
||||
|
||||
//
|
||||
// Open opens the source and starts a capture (e.g. stream of events).
|
||||
|
@ -63,7 +59,7 @@ type Plugin interface {
|
|||
}
|
||||
|
||||
// Instance is an interface representing a source capture session instance
|
||||
// returned by a call to Open of a source plugin.
|
||||
// returned by a call to Open of a plugin with event sourcing capability.
|
||||
//
|
||||
// Implementations of this interface must implement sdk.NextBatcher, and can
|
||||
// optionally implement sdk.Closer and sdk.Progresser.
|
||||
|
@ -85,44 +81,12 @@ type BaseInstance struct {
|
|||
plugins.BaseProgress
|
||||
}
|
||||
|
||||
// Register registers a Plugin source plugin in the framework. This function
|
||||
// needs to be called in a Go init() function. Calling this function more than
|
||||
// once will cause a panic.
|
||||
// Register registers the event sourcing capability in the framework for the given Plugin.
|
||||
//
|
||||
// Register registers a source plugin in the SDK. In order to
|
||||
// register a source plugin with optional extraction capabilities, the
|
||||
// extractor.Register function must be called by passing the same Plugin
|
||||
// argument. In this case, the order in which Register and extractor.Register
|
||||
// are called in the init() function is not relevant. This is needed for the
|
||||
// framework to notice that the source plugin implements the extraction-related
|
||||
// methods.
|
||||
// This function should be called from the provided plugins.FactoryFunc implementation.
|
||||
// See the parent package for more detail. This function is idempotent.
|
||||
func Register(p Plugin) {
|
||||
if registered {
|
||||
panic("plugin-sdk-go/sdk/plugins/source: register can be called only once")
|
||||
}
|
||||
|
||||
i := p.Info()
|
||||
info.SetType(sdk.TypeSourcePlugin)
|
||||
info.SetId(i.ID)
|
||||
info.SetName(i.Name)
|
||||
info.SetDescription(i.Description)
|
||||
info.SetEventSource(i.EventSource)
|
||||
info.SetContact(i.Contact)
|
||||
info.SetVersion(i.Version)
|
||||
info.SetRequiredAPIVersion(i.RequiredAPIVersion)
|
||||
info.SetExtractEventSources(i.ExtractEventSources)
|
||||
if initSchema, ok := p.(sdk.InitSchema); ok {
|
||||
initschema.SetInitSchema(initSchema.InitSchema())
|
||||
}
|
||||
|
||||
initialize.SetOnInit(func(c string) (sdk.PluginState, error) {
|
||||
err := p.Init(c)
|
||||
return p, err
|
||||
})
|
||||
|
||||
open.SetOnOpen(func(c string) (sdk.InstanceState, error) {
|
||||
return p.Open(c)
|
||||
})
|
||||
|
||||
registered = true
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -17,9 +18,6 @@ limitations under the License.
|
|||
package source
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins"
|
||||
)
|
||||
|
@ -52,27 +50,10 @@ func (m *testPlugin) Open(params string) (Instance, error) {
|
|||
return &testInstance{}, nil
|
||||
}
|
||||
|
||||
func (m *testPlugin) String(in io.ReadSeeker) (string, error) {
|
||||
func (m *testPlugin) String(evt sdk.EventReader) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (m *testInstance) NextBatch(pState sdk.PluginState, evts sdk.EventWriters) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func assertPanic(t *testing.T, fun func()) {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Errorf("expected panic")
|
||||
}
|
||||
}()
|
||||
fun()
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
registerFunc := func() {
|
||||
Register(&testPlugin{})
|
||||
}
|
||||
registerFunc()
|
||||
assertPanic(t, registerFunc)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -39,42 +41,60 @@ const (
|
|||
SSPluginNotSupported int32 = 3
|
||||
)
|
||||
|
||||
// One of these values should be returned by plugin_get_type().
|
||||
const (
|
||||
TypeSourcePlugin uint32 = 1
|
||||
TypeExtractorPlugin uint32 = 2
|
||||
)
|
||||
|
||||
// DefaultEvtSize is the default size for the data payload allocated
|
||||
// for each event in the EventWriters interface used by the SDK.
|
||||
const DefaultEvtSize uint32 = 256 * 1024
|
||||
|
||||
// DefaultBatchSize is the default number of events in the EventWriters
|
||||
// interface used by the SDK.
|
||||
const DefaultBatchSize = 128
|
||||
const DefaultBatchSize uint32 = 128
|
||||
|
||||
// The full set of values that can be returned in the ftype
|
||||
// member of ss_plugin_extract_field structs.
|
||||
// member of ss_plugin_extract_field structs (ppm_events_public.h).
|
||||
const (
|
||||
// A 64bit unsigned integer.
|
||||
FieldTypeUint64 uint32 = 8
|
||||
FieldTypeCharBuf uint32 = 9 // A printable buffer of bytes, NULL terminated
|
||||
// A printable buffer of bytes, NULL terminated.
|
||||
FieldTypeCharBuf uint32 = 9
|
||||
// A relative time. Seconds * 10^9 + nanoseconds. 64bit.
|
||||
FieldTypeRelTime uint32 = 20
|
||||
// An absolute time interval. Seconds from epoch * 10^9 + nanoseconds. 64bit.
|
||||
FieldTypeAbsTime uint32 = 21
|
||||
// A boolean value, 4 bytes.
|
||||
FieldTypeBool uint32 = 25
|
||||
// Either an IPv4 or IPv6 address. The length indicates which one it is.
|
||||
FieldTypeIPAddr uint32 = 40
|
||||
// Either an IPv4 or IPv6 network. The length indicates which one it is.
|
||||
FieldTypeIPNet uint32 = 41
|
||||
)
|
||||
|
||||
// FieldEntry represents a single field entry that an extractor plugin can expose.
|
||||
// FieldEntry represents a single field entry that a plugin with field extraction
|
||||
// capability can expose.
|
||||
// Should be used when implementing plugin_get_fields().
|
||||
type FieldEntry struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
ArgRequired bool `json:"argRequired"`
|
||||
Type string `json:"type"`
|
||||
IsList bool `json:"isList"`
|
||||
Arg FieldEntryArg `json:"arg"`
|
||||
Display string `json:"display"`
|
||||
Desc string `json:"desc"`
|
||||
Properties []string `json:"properties"`
|
||||
}
|
||||
|
||||
// FieldEntryArg describes the argument of a single field entry that
|
||||
// an plugin with field extraction capability can expose.
|
||||
// Should be used when implementing plugin_get_fields().
|
||||
type FieldEntryArg struct {
|
||||
IsRequired bool `json:"isRequired"`
|
||||
IsIndex bool `json:"isIndex"`
|
||||
IsKey bool `json:"isKey"`
|
||||
}
|
||||
|
||||
// OpenParam represents a valid parameter for plugin_open().
|
||||
type OpenParam struct {
|
||||
Value string `json:"value"`
|
||||
Desc string `json:"desc"`
|
||||
Separator string `json:"separator"`
|
||||
}
|
||||
|
||||
// SchemaInfo represent a schema describing a structured data type.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,7 +16,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
// Package symbols provides prebuilt implementations for all the C symbols
|
||||
// required to develop plugins as for the definitions of plugin_info.h.
|
||||
// required to develop plugins as for the definitions of plugin_types.h.
|
||||
//
|
||||
// This package defines low-level constructs for plugin development meant
|
||||
// for advanced users that wish to use only a portion of the SDK internals.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,45 +16,40 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
// This package exports the following C function:
|
||||
// - char* plugin_event_to_string(ss_plugin_t *s, const uint8_t *data, uint32_t datalen)
|
||||
// - char* plugin_event_to_string(ss_plugin_t *s, const ss_plugin_event *evt)
|
||||
//
|
||||
// The exported plugin_event_to_string requires that s to be a handle
|
||||
// of cgo.Handle from this SDK. The value of the s handle must implement
|
||||
// the sdk.Stringer and sdk.StringerBuffer interfaces.
|
||||
//
|
||||
// This function is part of the source_plugin_info interface as defined in
|
||||
// plugin_info.h. In almost all cases, your plugin should import this module,
|
||||
// This function is part of the plugin_api interface as defined in plugin_api.h.
|
||||
// In almost all cases, your plugin should import this module,
|
||||
// unless your plugin exports those symbols by other means.
|
||||
package evtstr
|
||||
|
||||
/*
|
||||
#include <stdint.h>
|
||||
#include "../../plugin_types.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/cgo"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/ptr"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
|
||||
)
|
||||
|
||||
//export plugin_event_to_string
|
||||
func plugin_event_to_string(pState C.uintptr_t, data *C.uint8_t, datalen uint32) *C.char {
|
||||
pHandle := cgo.Handle(pState)
|
||||
evtStringer := pHandle.Value().(sdk.Stringer)
|
||||
buf := pHandle.Value().(sdk.StringerBuffer).StringerBuffer()
|
||||
brw, err := ptr.NewBytesReadWriter(unsafe.Pointer(data), int64(datalen), int64(datalen))
|
||||
|
||||
if err != nil {
|
||||
buf.Write(err.Error())
|
||||
} else {
|
||||
if str, err := evtStringer.String(brw); err == nil {
|
||||
func plugin_event_to_string(pState C.uintptr_t, evt *C.ss_plugin_event_input) *C.char {
|
||||
buf := cgo.Handle(pState).Value().(sdk.StringerBuffer).StringerBuffer()
|
||||
stringer, ok := cgo.Handle(pState).Value().(sdk.Stringer)
|
||||
if ok {
|
||||
if str, err := stringer.String(sdk.NewEventReader(unsafe.Pointer(evt))); err == nil {
|
||||
buf.Write(str)
|
||||
} else {
|
||||
buf.Write(err.Error())
|
||||
}
|
||||
} else {
|
||||
buf.Write("")
|
||||
}
|
||||
|
||||
return (*C.char)(buf.CharPtr())
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -19,9 +20,9 @@ package evtstr
|
|||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/cgo"
|
||||
|
@ -39,15 +40,28 @@ type sampleEvtStr struct {
|
|||
expectedData []byte
|
||||
}
|
||||
|
||||
func allocSSPluginEvent(num, ts uint64, data []byte) (*_Ctype_struct_ss_plugin_event_input, func()) {
|
||||
ret := &_Ctype_struct_ss_plugin_event_input{}
|
||||
evts, _ := sdk.NewEventWriters(1, int64(len(data)))
|
||||
evt := evts.Get(0)
|
||||
evt.Writer().Write(data)
|
||||
ret.evt = *(**_Ctype_struct_ss_plugin_event)(evts.ArrayPtr())
|
||||
ret.evtnum = _Ctype_uint64_t(num)
|
||||
|
||||
return ret, func() {
|
||||
evts.Free()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sampleEvtStr) StringerBuffer() sdk.StringBuffer {
|
||||
return &s.strBuf
|
||||
}
|
||||
|
||||
func (s *sampleEvtStr) String(in io.ReadSeeker) (string, error) {
|
||||
func (s *sampleEvtStr) String(evt sdk.EventReader) (string, error) {
|
||||
if s.shouldError {
|
||||
return "", errTest
|
||||
}
|
||||
data, err := ioutil.ReadAll(in)
|
||||
data, err := ioutil.ReadAll(evt.Reader())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -68,7 +82,9 @@ func TestEvtStr(t *testing.T) {
|
|||
sample.expectedData = data
|
||||
|
||||
// test success
|
||||
cStr := plugin_event_to_string(_Ctype_uintptr_t(handle), (*_Ctype_uint8_t)(&data[0]), uint32(len(data)))
|
||||
event, freeEvent := allocSSPluginEvent(1, uint64(time.Now().UnixNano()), data)
|
||||
defer freeEvent()
|
||||
cStr := plugin_event_to_string(_Ctype_uintptr_t(handle), event)
|
||||
str := ptr.GoString(unsafe.Pointer(cStr))
|
||||
if str != strSuccess {
|
||||
t.Errorf("expected %s, but found %s", strSuccess, str)
|
||||
|
@ -76,7 +92,7 @@ func TestEvtStr(t *testing.T) {
|
|||
|
||||
// test forced error
|
||||
sample.shouldError = true
|
||||
cStr = plugin_event_to_string(_Ctype_uintptr_t(handle), (*_Ctype_uint8_t)(&data[0]), uint32(len(data)))
|
||||
cStr = plugin_event_to_string(_Ctype_uintptr_t(handle), event)
|
||||
str = ptr.GoString(unsafe.Pointer(cStr))
|
||||
if str != errTest.Error() {
|
||||
t.Errorf("expected %s, but found %s", strSuccess, str)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -21,107 +22,208 @@ package extract
|
|||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/cgo"
|
||||
)
|
||||
|
||||
//
|
||||
// The code below implements of the async extraction optimization.
|
||||
// Our goal is to defeat the C -> Go calls overhead and achieve better
|
||||
// performance during the field extraction path, which is one of the hottest
|
||||
// of in the plugin framework.
|
||||
//
|
||||
// The design follows the P3-S4 solution documented in the discussion of
|
||||
// https://github.com/falcosecurity/plugin-sdk-go/issues/62.
|
||||
// We have N concurrent consumers from the C world, N locks shared between C
|
||||
// and Go, and M async workers in the Go world. The shared spinlocks are the
|
||||
// point of synchronization between the C consumers and the Go workers.
|
||||
// There is a 1-1 mapping between a C consumer and a shared lock, so there
|
||||
// are no collisions between consumers on the same lock. As such, each consumer
|
||||
// will be served always by the same one worker. On the countrary, each worker
|
||||
// synchronizes with 1+ locks (and consumers), with a given rotation policy.
|
||||
// The number of workers M, is not necessarily correlated con the number
|
||||
// of consumers N. Note, each worker heavily occupies the CPU for small time
|
||||
// bursts so M should be less than runtime.GOMAXPROCS.
|
||||
//
|
||||
// Each worker has an integer index and will just loop over slots at positions
|
||||
// at (workerIndex + maxWorkers * i). This ensures that multiple async workers
|
||||
// never collide trying to sync on the same slot.
|
||||
// For example, assuming a maximum number of 3 workers:
|
||||
// - Worker 0 will sync over batch slots: 0, 3, 6, 9, ...
|
||||
// - Worker 1 will sync over batch slots: 1, 4, 7, 10, ...
|
||||
// - Worker 2 will sync over batch slots: 2, 5, 8, 11, ...
|
||||
//
|
||||
// note: this package is aware and dependent on the current implementation
|
||||
// of our cgo package. Since cgo.Handle values are regular integers between 1
|
||||
// and cgo.MaxHandle, they are used to assign the batch slot index to
|
||||
// each consumer. Each initialized plugin is a distinct consumer and has
|
||||
// its own assigned cgo.Handle value. I really don't like leaking this
|
||||
// implementation knowledge of our cgo package, however the goal here is to
|
||||
// reach maximum performance and using array-based access is our best option.
|
||||
//
|
||||
// todo(jasondellaluce): change this and extract.c if we change the current
|
||||
// cgo.Handle implementation
|
||||
|
||||
const (
|
||||
state_unused = iota // the lock is unused
|
||||
state_wait // the lock is free and a new request can be sent
|
||||
state_data_req // an extraction request has been sent by the consumer
|
||||
state_exit_req // an exit request has been sent by the consumer
|
||||
state_exit_ack // an exit request has been resolved by the worker
|
||||
)
|
||||
|
||||
const (
|
||||
state_wait = iota
|
||||
state_data_req
|
||||
state_exit_req
|
||||
state_exit_ack
|
||||
)
|
||||
|
||||
const (
|
||||
starvationThresholdNs = 1e6
|
||||
sleepTimeNs = 1e4 * time.Nanosecond
|
||||
// starvationThresholdNs is the time in nanoseconds on which async workers
|
||||
// can be in busy-loop without going to sleep
|
||||
starvationThresholdNs = int64(1e6)
|
||||
//
|
||||
// sleepTimeNs is the sleep time in nanoseconds for async workers
|
||||
// after busy-looping for starvationThresholdNs time
|
||||
sleepTimeNs = 1e7 * time.Nanosecond
|
||||
//
|
||||
// asyncBatchSize is the physical size of batches allocated
|
||||
// in C memory, namely the total number of locks available
|
||||
asyncBatchSize = cgo.MaxHandle + 1
|
||||
//
|
||||
// max number of seconds we're willing to wait for a worker to exit
|
||||
// once released before triggering a panic
|
||||
workerReleaseTimeoutInSeconds = 10
|
||||
)
|
||||
|
||||
var (
|
||||
asyncCtx *C.async_extractor_info
|
||||
asyncMutex sync.Mutex
|
||||
asyncEnabled bool = true
|
||||
asyncCount int32 = 0
|
||||
// ctx is the asyncContext instance used by the SDK
|
||||
ctx asyncContext
|
||||
)
|
||||
|
||||
func asyncAvailable() bool {
|
||||
return runtime.NumCPU() > 1
|
||||
// asyncContext bundles all the state information used by the async
|
||||
// extraction optimization
|
||||
type asyncContext struct {
|
||||
// disabled is false if the async optimization is configured
|
||||
// to be enabled
|
||||
disabled bool
|
||||
//
|
||||
// available if the underlying hardware configuration supports the
|
||||
// async optimization
|
||||
available bool
|
||||
//
|
||||
// m ensures that Async/SetAsync/StartAsync/StopAsync are invoked
|
||||
// with mutual exclusion
|
||||
m sync.Mutex
|
||||
//
|
||||
// count is incremented at every call to StartAsync and
|
||||
// is decremented at every call to StopAsync
|
||||
count int32
|
||||
//
|
||||
// batch is a batch of info that is shared between C and Go.
|
||||
// Each slot of the batch contains a distinct lock and is assigned
|
||||
// to only one cgo.Handle value.
|
||||
batch []C.async_extractor_info
|
||||
//
|
||||
// maxWorkers is the max number of workers that can be active at the same time
|
||||
maxWorkers int32
|
||||
//
|
||||
// activeWorkers entries are true if an async worker is currently active
|
||||
// at the given index. The size of activeWorkers is maxWorkers.
|
||||
activeWorkers []bool
|
||||
//
|
||||
// maxBatchIdx is the greatest slot index occupied in the batch.
|
||||
// This value is >= 0 and < asyncCtxBatchCap. This is used by async workers
|
||||
// to avoid looping over batch slots that are known to be unused in order to
|
||||
// minimize the synchronization overhead.
|
||||
maxBatchIdx int32
|
||||
}
|
||||
|
||||
func SetAsync(enable bool) {
|
||||
asyncEnabled = enable
|
||||
func (a *asyncContext) SetAsync(enable bool) {
|
||||
a.m.Lock()
|
||||
defer a.m.Unlock()
|
||||
a.disabled = !enable
|
||||
}
|
||||
|
||||
func Async() bool {
|
||||
return asyncEnabled
|
||||
func (a *asyncContext) Async() bool {
|
||||
a.m.Lock()
|
||||
defer a.m.Unlock()
|
||||
return !a.disabled
|
||||
}
|
||||
|
||||
// StartAsync initializes and starts the asynchronous extraction mode.
|
||||
// Once StartAsync has been called, StopAsync must be called before terminating
|
||||
// the program. The number of calls to StartAsync and StopAsync must be equal
|
||||
// in the program. Independently by the number of StartAsync/StopAsync calls,
|
||||
// there will never be more than one async worker activated at the same time.
|
||||
//
|
||||
// This is a way to optimize field extraction for use cases in which the rate
|
||||
// of calls to plugin_extract_fields() is considerably high, so that the
|
||||
// overhead of the C -> Go calls may become unacceptable for performance.
|
||||
// Asynchronous extraction solves this problem by launching a worker
|
||||
// goroutine and by synchronizing with it through a spinlock.
|
||||
// The worker implements a busy wait, in order to ensure that the scheduler
|
||||
// sleeps it from its execution as less as possible. This is only suitable
|
||||
// for multi-core architectures, and has a significant impact on CPU usage,
|
||||
// so it should be carefully used only if the rate of C -> Go calls makes
|
||||
// the tradeoff worth it.
|
||||
//
|
||||
// After calling StartAsync, the framework automatically shift the extraction
|
||||
// strategy from the regular C -> Go call one to the alternative worker
|
||||
// synchronization one.
|
||||
func StartAsync() {
|
||||
asyncMutex.Lock()
|
||||
defer asyncMutex.Unlock()
|
||||
// 1 batch slot maps to only 1 worker
|
||||
func (a *asyncContext) batchIdxToWorkerIdx(slotIdx int32) int32 {
|
||||
return slotIdx % a.maxWorkers
|
||||
}
|
||||
|
||||
asyncCount += 1
|
||||
if !asyncAvailable() || !asyncEnabled || asyncCount > 1 {
|
||||
// 1 worker maps to 1+ batch slots
|
||||
func (a *asyncContext) workerIdxToBatchIdxs(workerIdx int32) (res []int32) {
|
||||
for i := int32(workerIdx); i < int32(asyncBatchSize); i += a.maxWorkers {
|
||||
res = append(res, i)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *asyncContext) handleToBatchIdx(h cgo.Handle) int32 {
|
||||
return int32(h) - 1
|
||||
}
|
||||
|
||||
func (a *asyncContext) getMaxWorkers(maxProcs int) int32 {
|
||||
return int32(math.Ceil(math.Log2(float64(maxProcs))))
|
||||
}
|
||||
|
||||
func (a *asyncContext) acquireWorker(workerIdx int32) {
|
||||
if a.activeWorkers[workerIdx] {
|
||||
// worker is already running
|
||||
return
|
||||
}
|
||||
|
||||
asyncCtx = C.async_init()
|
||||
atomic.StoreInt32((*int32)(&asyncCtx.lock), state_wait)
|
||||
// start the worker
|
||||
a.activeWorkers[workerIdx] = true
|
||||
go func() {
|
||||
lock := (*int32)(&asyncCtx.lock)
|
||||
waitStartTime := time.Now().Nanosecond()
|
||||
|
||||
waitStartTime := time.Now().UnixNano()
|
||||
batchIdxs := a.workerIdxToBatchIdxs(workerIdx)
|
||||
for {
|
||||
// Check for incoming request, if any, otherwise busy waits
|
||||
switch atomic.LoadInt32(lock) {
|
||||
// loop over async context batch slots in round-robin
|
||||
for _, i := range batchIdxs {
|
||||
// reduce sync overhead by skipping unused batch slots
|
||||
if i > a.maxBatchIdx {
|
||||
// from this point on we'll only encountered unused slots
|
||||
// so we mind as well just start over
|
||||
break
|
||||
}
|
||||
|
||||
// check for incoming request, if any, otherwise busy waits
|
||||
switch atomic.LoadInt32((*int32)(&a.batch[i].lock)) {
|
||||
|
||||
case state_data_req:
|
||||
// Incoming data request. Process it...
|
||||
asyncCtx.rc = C.int32_t(
|
||||
// incoming data request, process it...
|
||||
a.batch[i].rc = C.int32_t(
|
||||
plugin_extract_fields_sync(
|
||||
C.uintptr_t(uintptr(asyncCtx.s)),
|
||||
asyncCtx.evt,
|
||||
uint32(asyncCtx.num_fields),
|
||||
asyncCtx.fields,
|
||||
C.uintptr_t(uintptr(a.batch[i].s)),
|
||||
a.batch[i].evt,
|
||||
uint32(a.batch[i].num_fields),
|
||||
a.batch[i].fields,
|
||||
a.batch[i].value_offsets,
|
||||
),
|
||||
)
|
||||
// Processing done, return back to waiting state
|
||||
atomic.StoreInt32(lock, state_wait)
|
||||
// Reset waiting start time
|
||||
// processing done, return back to waiting state
|
||||
atomic.StoreInt32((*int32)(&a.batch[i].lock), state_wait)
|
||||
// reset waiting start time
|
||||
waitStartTime = 0
|
||||
|
||||
case state_exit_req:
|
||||
// Incoming exit request. Send ack and exit.
|
||||
atomic.StoreInt32(lock, state_exit_ack)
|
||||
atomic.StoreInt32((*int32)(&a.batch[i].lock), state_exit_ack)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
// busy wait, then sleep after 1ms
|
||||
if waitStartTime == 0 {
|
||||
waitStartTime = time.Now().Nanosecond()
|
||||
} else if time.Now().Nanosecond()-waitStartTime > starvationThresholdNs {
|
||||
waitStartTime = time.Now().UnixNano()
|
||||
} else if time.Now().UnixNano()-waitStartTime > starvationThresholdNs {
|
||||
time.Sleep(sleepTimeNs)
|
||||
}
|
||||
}
|
||||
|
@ -129,30 +231,208 @@ func StartAsync() {
|
|||
}()
|
||||
}
|
||||
|
||||
// StopAsync deinitializes and stops the asynchronous extraction mode, and
|
||||
// undoes a single StartAsync call. It is a run-time error if StartAsync was
|
||||
// not called before calling StopAsync.
|
||||
func StopAsync() {
|
||||
asyncMutex.Lock()
|
||||
defer asyncMutex.Unlock()
|
||||
func (a *asyncContext) releaseWorker(workerIdx int32) {
|
||||
if !a.activeWorkers[workerIdx] {
|
||||
// work is not running, no need to stop it
|
||||
return
|
||||
}
|
||||
|
||||
asyncCount -= 1
|
||||
if asyncCount < 0 {
|
||||
// check all the batch slots assigned to the worker,
|
||||
// and stop it only if all of them are unused
|
||||
for _, i := range a.workerIdxToBatchIdxs(workerIdx) {
|
||||
if atomic.LoadInt32((*int32)(&a.batch[i].lock)) != state_unused {
|
||||
// worker is still needed, we should not stop it
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// at this point, all slots assigned to the worker are
|
||||
// unused and the worker is looping over unused locks. Right from the Go
|
||||
// side, we use the first visible slot and set an exit request. The worker
|
||||
// will eventually synchronize with the used lock and stop.
|
||||
idx := a.workerIdxToBatchIdxs(workerIdx)[0]
|
||||
waitStartTime := time.Now()
|
||||
for !atomic.CompareAndSwapInt32((*int32)(&a.batch[idx].lock), state_unused, state_exit_req) {
|
||||
// spinning, but let's yield first
|
||||
runtime.Gosched()
|
||||
if time.Since(waitStartTime).Seconds() > workerReleaseTimeoutInSeconds {
|
||||
panic("plugin-sdk-go/sdk/symbols/extract: async worker release timeout expired (1)")
|
||||
}
|
||||
}
|
||||
|
||||
// wait for worker exiting
|
||||
waitStartTime = time.Now()
|
||||
for atomic.LoadInt32((*int32)(&a.batch[idx].lock)) != state_exit_ack {
|
||||
// spinning, but let's yield first
|
||||
runtime.Gosched()
|
||||
if time.Since(waitStartTime).Seconds() > workerReleaseTimeoutInSeconds {
|
||||
panic("plugin-sdk-go/sdk/symbols/extract: async worker release timeout expired (2)")
|
||||
}
|
||||
}
|
||||
|
||||
// restore first worker slot
|
||||
atomic.StoreInt32((*int32)(&a.batch[idx].lock), state_unused)
|
||||
a.activeWorkers[workerIdx] = false
|
||||
}
|
||||
|
||||
func (a *asyncContext) StartAsync(handle cgo.Handle, allocBatch func() []C.async_extractor_info) {
|
||||
a.m.Lock()
|
||||
defer a.m.Unlock()
|
||||
|
||||
a.count += 1
|
||||
|
||||
// at the first StartAsync call, we check if the optimization is supported
|
||||
if a.count == 1 {
|
||||
// since on high workloads an async worker can potentially occupy a whole
|
||||
// thread (and its CPU), we consider the optimization available only if
|
||||
// we run on at least 2 CPUs.
|
||||
//
|
||||
// note: runtime.GOMAXPROCS(0) should be more accurate than
|
||||
// runtime.NumCPU() and and better aligns with the developer/user
|
||||
// thread pool configuration.
|
||||
a.available = runtime.GOMAXPROCS(0) > 1
|
||||
}
|
||||
|
||||
// do nothing if the optimization can't be started
|
||||
if !a.available || a.disabled {
|
||||
return
|
||||
}
|
||||
|
||||
// init the context when the first consumer starts the async optimization
|
||||
if a.count >= 1 && a.batch == nil {
|
||||
// init a new batch
|
||||
a.batch = allocBatch()
|
||||
for i := 0; i < asyncBatchSize; i++ {
|
||||
atomic.StoreInt32((*int32)(&a.batch[i].lock), state_unused)
|
||||
}
|
||||
|
||||
// no batch index is used at the beginning
|
||||
atomic.StoreInt32(&a.maxBatchIdx, 0)
|
||||
|
||||
// compute the max number of workers and set all of them as unused
|
||||
a.maxWorkers = a.getMaxWorkers(runtime.GOMAXPROCS(0))
|
||||
a.activeWorkers = make([]bool, a.maxWorkers)
|
||||
}
|
||||
|
||||
// assign a batch slot to this handle and acquire a worker.
|
||||
// Each handle has a 1-1 mapping with a batch slot
|
||||
batchIdx := a.handleToBatchIdx(handle)
|
||||
atomic.StoreInt32((*int32)(&a.batch[batchIdx].lock), state_wait)
|
||||
if batchIdx > atomic.LoadInt32(&a.maxBatchIdx) {
|
||||
atomic.StoreInt32(&a.maxBatchIdx, batchIdx)
|
||||
}
|
||||
a.acquireWorker(a.batchIdxToWorkerIdx(batchIdx))
|
||||
}
|
||||
|
||||
func (a *asyncContext) StopAsync(handle cgo.Handle, freeBatch func([]C.async_extractor_info)) {
|
||||
a.m.Lock()
|
||||
defer a.m.Unlock()
|
||||
|
||||
a.count -= 1
|
||||
if a.count < 0 {
|
||||
panic("plugin-sdk-go/sdk/symbols/extract: async worker stopped without being started")
|
||||
}
|
||||
|
||||
if asyncCount == 0 && asyncCtx != nil {
|
||||
lock := (*int32)(&asyncCtx.lock)
|
||||
if a.batch != nil {
|
||||
// update the state vars if this handle used async extraction
|
||||
batchIdx := a.handleToBatchIdx(handle)
|
||||
if atomic.LoadInt32((*int32)(&a.batch[batchIdx].lock)) != state_unused {
|
||||
// set the assigned batch slot as unused and release worker
|
||||
atomic.StoreInt32((*int32)(&a.batch[batchIdx].lock), state_unused)
|
||||
a.releaseWorker(a.batchIdxToWorkerIdx(batchIdx))
|
||||
|
||||
for !atomic.CompareAndSwapInt32(lock, state_wait, state_exit_req) {
|
||||
// spin
|
||||
// update the current maximum used slot, so that async workers
|
||||
// will not try to sync over this index
|
||||
if batchIdx == atomic.LoadInt32(&a.maxBatchIdx) {
|
||||
for i := int32(batchIdx) - 1; i >= 0; i-- {
|
||||
if atomic.LoadInt32((*int32)(&a.batch[i].lock)) != state_unused {
|
||||
atomic.StoreInt32(&a.maxBatchIdx, i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// state_exit_req acquired, wait for worker exiting
|
||||
for atomic.LoadInt32(lock) != state_exit_ack {
|
||||
// spin
|
||||
// if this was the last handle to be destroyed,
|
||||
// then we can safely destroy the batch too
|
||||
if a.count == 0 {
|
||||
// all workers should already be stopped by now
|
||||
for i := int32(0); i < a.maxWorkers; i++ {
|
||||
if a.activeWorkers[i] {
|
||||
panic(fmt.Sprintf("plugin-sdk-go/sdk/symbols/extract: worker %d can't be stopped", i))
|
||||
}
|
||||
}
|
||||
freeBatch(a.batch)
|
||||
a.batch = nil
|
||||
}
|
||||
asyncCtx = nil
|
||||
C.async_deinit()
|
||||
}
|
||||
}
|
||||
|
||||
func allocBatchInCMemory() (res []C.async_extractor_info) {
|
||||
cBuf := unsafe.Pointer(C.async_init((C.size_t)(asyncBatchSize)))
|
||||
(*reflect.SliceHeader)(unsafe.Pointer(&res)).Data = uintptr(cBuf)
|
||||
(*reflect.SliceHeader)(unsafe.Pointer(&res)).Len = int(asyncBatchSize)
|
||||
(*reflect.SliceHeader)(unsafe.Pointer(&res)).Cap = int(asyncBatchSize)
|
||||
return
|
||||
}
|
||||
|
||||
func freeBatchInCMemory(c []C.async_extractor_info) {
|
||||
C.async_deinit()
|
||||
}
|
||||
|
||||
// SetAsync enables or disables the async extraction optimization depending
|
||||
// on the passed-in boolean.
|
||||
//
|
||||
// Note, setting the optimization as enabled does not guarantee that it will
|
||||
// be actually used at runtime, as the SDK will first check whether the hardware
|
||||
// configuration matches some minimum requirements.
|
||||
func SetAsync(enable bool) {
|
||||
ctx.SetAsync(enable)
|
||||
}
|
||||
|
||||
// Async returns true if the async extraction optimization is
|
||||
// configured to be enabled, and false otherwise. This is true by default.
|
||||
func Async() bool {
|
||||
return ctx.Async()
|
||||
}
|
||||
|
||||
// StartAsync initializes and starts the asynchronous extraction mode for the
|
||||
// given plugin handle. Once StartAsync has been called, StopAsync must be
|
||||
// called before terminating the program. The number of calls to StartAsync
|
||||
// and StopAsync must be equal in the program.
|
||||
//
|
||||
// This is a way to optimize field extraction for use cases in which the rate
|
||||
// of calls to plugin_extract_fields() is considerably high, so that the
|
||||
// overhead of the C -> Go calls may become unacceptable for performance.
|
||||
// Asynchronous extraction solves this problem by launching worker
|
||||
// goroutines and by synchronizing with them through shared spinlocks.
|
||||
// Each worker implements a busy wait, in order to ensure that the scheduler
|
||||
// sleeps it from its execution as less as possible. This is only suitable
|
||||
// for multi-core architectures, and has a significant impact on CPU usage,
|
||||
// so it should be carefully used only if the rate of C -> Go calls makes
|
||||
// the tradeoff worth it.
|
||||
//
|
||||
// The behavior of StartAsync is influenced by the value set through SetAsync:
|
||||
// if set to true the SDK will attempt to run the optimization depending on
|
||||
// the underlying runtime capacity, otherwise this will have no effect.
|
||||
// After calling StartAsync with SetAsync set to true, the framework will try to
|
||||
// automatically shift the extraction strategy from the regular C -> Go call
|
||||
// one to the alternative worker synchronization one.
|
||||
//
|
||||
// Note, StartAsync first checks the value of runtime.GOMAXPROCS(0) to detect
|
||||
// if the underlying Go runtime is capable of supporting the async optimization.
|
||||
// After the first call to StartAsync, changing the value of runtime.GOMAXPROCS
|
||||
// has no effect on the async workers until undone with the respecting
|
||||
// StopAsync call. As such, descreasing runtime.GOMAXPROCS is generally unsafe
|
||||
// StartAsync and StopAsync calls because the optimization can eccessively
|
||||
// occupy the downsized Go runtime and eventually block it.
|
||||
func StartAsync(handle cgo.Handle) {
|
||||
ctx.StartAsync(handle, allocBatchInCMemory)
|
||||
}
|
||||
|
||||
// StopAsync deinitializes the asynchronous extraction mode for the given plugin
|
||||
// handle, and undoes a single previous StartAsync call. It is a run-time error
|
||||
// if StartAsync was not called before calling StopAsync.
|
||||
func StopAsync(handle cgo.Handle) {
|
||||
ctx.StopAsync(handle, freeBatchInCMemory)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,244 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package extract
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/cgo"
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
|
||||
)
|
||||
|
||||
const testAsyncMaxPlugins = 32 // note: must be <= cgo.MaxHandle
|
||||
|
||||
type sampleAsyncExtract struct {
|
||||
sampleExtract
|
||||
counter uint64
|
||||
}
|
||||
|
||||
func (s *sampleAsyncExtract) Extract(req sdk.ExtractRequest, evt sdk.EventReader) error {
|
||||
req.SetValue(s.counter)
|
||||
s.counter++
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAllocAsyncBatch() []_Ctype_async_extractor_info {
|
||||
return make([]_Ctype_async_extractor_info, asyncBatchSize)
|
||||
}
|
||||
|
||||
func testReleaseAsyncBatch(c []_Ctype_async_extractor_info) {}
|
||||
|
||||
func TestSetAsync(t *testing.T) {
|
||||
a := asyncContext{}
|
||||
if !a.Async() {
|
||||
t.Fatalf("Async returned %v but expected %v", false, true)
|
||||
}
|
||||
|
||||
a.SetAsync(false)
|
||||
if a.Async() {
|
||||
t.Fatalf("Async returned %v but expected %v", true, false)
|
||||
}
|
||||
|
||||
a.SetAsync(true)
|
||||
if !a.Async() {
|
||||
t.Fatalf("Async returned %v but expected %v", false, true)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsyncGetMaxWorkers(t *testing.T) {
|
||||
a := asyncContext{}
|
||||
expected := []int32{0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5}
|
||||
for i, ex := range expected {
|
||||
v := a.getMaxWorkers(i + 1)
|
||||
if v != ex {
|
||||
t.Fatalf("getMaxWorkers returned %d but expected %d", v, ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsyncBatchIdxWorkerIdx(t *testing.T) {
|
||||
for maxWorkers := 1; maxWorkers < 100; maxWorkers++ {
|
||||
a := asyncContext{maxWorkers: int32(maxWorkers)}
|
||||
for i := int32(0); i < 10; i++ {
|
||||
if a.batchIdxToWorkerIdx(i) != i%a.maxWorkers {
|
||||
t.Fatalf("batchIdxToWorkerIdx returned %d but expected %d", a.batchIdxToWorkerIdx(i), i%a.maxWorkers)
|
||||
}
|
||||
}
|
||||
for i := int32(0); i < a.maxWorkers; i++ {
|
||||
expectedIdx := int32(i)
|
||||
for _, v := range a.workerIdxToBatchIdxs(i) {
|
||||
if v != expectedIdx {
|
||||
t.Fatalf("workerIdxToBatchIdxs returned %d but expected %d", v, expectedIdx)
|
||||
}
|
||||
expectedIdx += a.maxWorkers
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testWithMockPlugins(n int, f func([]cgo.Handle)) {
|
||||
plugins := make([]sampleAsyncExtract, n)
|
||||
handles := make([]cgo.Handle, n)
|
||||
for i := 0; i < n; i++ {
|
||||
handles[i] = cgo.NewHandle(&plugins[i])
|
||||
plugins[i].SetExtractRequests(sdk.NewExtractRequestPool())
|
||||
}
|
||||
f(handles)
|
||||
for i := 0; i < n; i++ {
|
||||
handles[i].Delete()
|
||||
plugins[i].ExtractRequests().Free()
|
||||
}
|
||||
}
|
||||
|
||||
// this simulates a C consumer as in extract.c
|
||||
func testSimulateAsyncRequest(t testing.TB, a *asyncContext, h cgo.Handle, r *_Ctype_ss_plugin_extract_field) {
|
||||
i := a.handleToBatchIdx(h)
|
||||
a.batch[i].s = unsafe.Pointer(h)
|
||||
a.batch[i].evt = nil
|
||||
a.batch[i].num_fields = 1
|
||||
a.batch[i].fields = r
|
||||
|
||||
atomic.StoreInt32((*int32)(&a.batch[i].lock), state_data_req)
|
||||
for atomic.LoadInt32((*int32)(&a.batch[i].lock)) != state_wait {
|
||||
// spin
|
||||
}
|
||||
if int32(a.batch[i].rc) != sdk.SSPluginSuccess {
|
||||
t.Fatalf("extraction failed with rc %v", int32(a.batch[i].rc))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsyncExtract(t *testing.T) {
|
||||
a := asyncContext{}
|
||||
workload := func(nPlugins, nExtractions int) {
|
||||
testWithMockPlugins(nPlugins, func(handles []cgo.Handle) {
|
||||
var wg sync.WaitGroup
|
||||
for _, h := range handles {
|
||||
wg.Add(1)
|
||||
go func(h cgo.Handle) {
|
||||
counter := uint64(0)
|
||||
field, freeField := allocSSPluginExtractField(1, sdk.FieldTypeUint64, "", "")
|
||||
defer freeField()
|
||||
|
||||
// note: StartAsync/StopAsync are not invoked concurrently
|
||||
// in the plugin framework, however we want to test them to
|
||||
// be thread-safe as they are designed
|
||||
a.StartAsync(h, testAllocAsyncBatch)
|
||||
for e := 0; e < nExtractions; e++ {
|
||||
testSimulateAsyncRequest(t, &a, h, field)
|
||||
value := **((**uint64)(unsafe.Pointer(&field.res[0])))
|
||||
if value != counter {
|
||||
panic(fmt.Sprintf("extracted %d but expected %d", value, counter))
|
||||
}
|
||||
counter++
|
||||
}
|
||||
a.StopAsync(h, testReleaseAsyncBatch)
|
||||
wg.Done()
|
||||
}(h)
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
}
|
||||
|
||||
// run with increasing number of concurrent consumers
|
||||
for i := 1; i <= testAsyncMaxPlugins; i *= 2 {
|
||||
// run with increasing number of extractions
|
||||
for j := 1; j < 10000; j *= 10 {
|
||||
workload(i, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartStopAsync(t *testing.T) {
|
||||
nPlugins := testAsyncMaxPlugins
|
||||
// checking that an odd or event number of plugins is not relevant
|
||||
for i := nPlugins - 1; i <= nPlugins+1; i++ {
|
||||
t.Run(fmt.Sprintf("unbalanced-startstop#%d", i), func(t *testing.T) {
|
||||
testWithMockPlugins(nPlugins, func(handles []cgo.Handle) {
|
||||
// test unbalanced start/stop calls
|
||||
assertPanic(t, func() {
|
||||
a := asyncContext{}
|
||||
a.StopAsync(handles[0], testReleaseAsyncBatch)
|
||||
})
|
||||
assertPanic(t, func() {
|
||||
a := asyncContext{}
|
||||
a.StartAsync(handles[0], testAllocAsyncBatch)
|
||||
a.StopAsync(handles[0], testReleaseAsyncBatch)
|
||||
a.StopAsync(handles[0], testReleaseAsyncBatch)
|
||||
})
|
||||
|
||||
// test with bad start/stop-handle pair
|
||||
assertPanic(t, func() {
|
||||
a := asyncContext{}
|
||||
a.StartAsync(handles[0], testAllocAsyncBatch)
|
||||
a.StartAsync(handles[1], testAllocAsyncBatch)
|
||||
a.StopAsync(handles[0], testReleaseAsyncBatch)
|
||||
a.StopAsync(handles[0], testReleaseAsyncBatch)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run(fmt.Sprintf("inconsistent-async#%d", i), func(t *testing.T) {
|
||||
testWithMockPlugins(nPlugins, func(handles []cgo.Handle) {
|
||||
// test with inconsistent enabled values
|
||||
a := asyncContext{}
|
||||
enabled := false
|
||||
for i := 0; i < nPlugins; i++ {
|
||||
a.SetAsync(enabled)
|
||||
a.StartAsync(handles[i], testAllocAsyncBatch)
|
||||
enabled = !enabled
|
||||
}
|
||||
for i := 0; i < nPlugins; i++ {
|
||||
a.StopAsync(handles[i], testReleaseAsyncBatch)
|
||||
}
|
||||
|
||||
// test workload after already having started/stopped the same context
|
||||
var wg sync.WaitGroup
|
||||
for _, h := range handles {
|
||||
wg.Add(1)
|
||||
a.SetAsync(enabled)
|
||||
a.StartAsync(h, testAllocAsyncBatch)
|
||||
go func(h cgo.Handle, enabled bool) {
|
||||
counter := uint64(0)
|
||||
field, freeField := allocSSPluginExtractField(1, sdk.FieldTypeUint64, "", "")
|
||||
defer freeField()
|
||||
for e := 0; e < 1000; e++ {
|
||||
if enabled {
|
||||
testSimulateAsyncRequest(t, &a, h, field)
|
||||
value := **((**uint64)(unsafe.Pointer(&field.res[0])))
|
||||
if value != counter {
|
||||
panic(fmt.Sprintf("extracted %d but expected %d", value, counter))
|
||||
}
|
||||
}
|
||||
counter++
|
||||
}
|
||||
wg.Done()
|
||||
}(h, enabled)
|
||||
enabled = !enabled
|
||||
}
|
||||
wg.Wait()
|
||||
for _, h := range handles {
|
||||
a.StopAsync(h, testReleaseAsyncBatch)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -20,69 +21,79 @@ limitations under the License.
|
|||
#include <sys/time.h>
|
||||
#include "extract.h"
|
||||
|
||||
// Possibly oversimplified version of https://gcc.gnu.org/wiki/Visibility
|
||||
#if defined _WIN32 || defined __CYGWIN__
|
||||
#define FALCO_PLUGIN_SDK_PUBLIC __declspec(dllexport)
|
||||
#else
|
||||
#define FALCO_PLUGIN_SDK_PUBLIC
|
||||
#endif
|
||||
|
||||
enum worker_state
|
||||
{
|
||||
WAIT = 0,
|
||||
DATA_REQ = 1,
|
||||
EXIT_REQ = 2,
|
||||
EXIT_ACK = 3,
|
||||
UNUSED = 0,
|
||||
WAIT = 1,
|
||||
DATA_REQ = 2,
|
||||
EXIT_REQ = 3,
|
||||
EXIT_ACK = 4,
|
||||
};
|
||||
|
||||
static async_extractor_info *s_async_extractor_ctx = NULL;
|
||||
static async_extractor_info *s_async_ctx_batch = NULL;
|
||||
|
||||
async_extractor_info *async_init()
|
||||
async_extractor_info *async_init(size_t size)
|
||||
{
|
||||
s_async_extractor_ctx = (async_extractor_info *)malloc(sizeof(async_extractor_info));
|
||||
return s_async_extractor_ctx;
|
||||
s_async_ctx_batch = (async_extractor_info *)malloc(sizeof(async_extractor_info) * size);
|
||||
return s_async_ctx_batch;
|
||||
}
|
||||
|
||||
void async_deinit()
|
||||
{
|
||||
free(s_async_extractor_ctx);
|
||||
s_async_extractor_ctx = NULL;
|
||||
free(s_async_ctx_batch);
|
||||
s_async_ctx_batch = NULL;
|
||||
}
|
||||
|
||||
// Defined in extract.go
|
||||
extern int32_t plugin_extract_fields_sync(ss_plugin_t *s,
|
||||
const ss_plugin_event *evt,
|
||||
const ss_plugin_event_input *evt,
|
||||
uint32_t num_fields,
|
||||
ss_plugin_extract_field *fields);
|
||||
ss_plugin_extract_field *fields,
|
||||
ss_plugin_extract_value_offsets *offsets);
|
||||
|
||||
static inline int32_t async_extract_request(ss_plugin_t *s,
|
||||
const ss_plugin_event *evt,
|
||||
uint32_t num_fields,
|
||||
ss_plugin_extract_field *fields)
|
||||
{
|
||||
// Since no concurrent requests are supported,
|
||||
// we assume worker is already in WAIT state
|
||||
|
||||
// Set input data
|
||||
s_async_extractor_ctx->s = s;
|
||||
s_async_extractor_ctx->evt = evt;
|
||||
s_async_extractor_ctx->num_fields = num_fields;
|
||||
s_async_extractor_ctx->fields = fields;
|
||||
|
||||
// notify data request
|
||||
atomic_store_explicit(&s_async_extractor_ctx->lock, DATA_REQ, memory_order_seq_cst);
|
||||
|
||||
// busy-wait until worker completation
|
||||
while (atomic_load_explicit(&s_async_extractor_ctx->lock, memory_order_seq_cst) != WAIT);
|
||||
|
||||
return s_async_extractor_ctx->rc;
|
||||
}
|
||||
|
||||
// This is the plugin API function. If s_async_extractor_ctx is
|
||||
// This is the plugin API function. If s_async_ctx_batch is
|
||||
// non-NULL, it calls the async extractor function. Otherwise, it
|
||||
// calls the synchronous extractor function.
|
||||
int32_t plugin_extract_fields(ss_plugin_t *s,
|
||||
const ss_plugin_event *evt,
|
||||
uint32_t num_fields,
|
||||
ss_plugin_extract_field *fields)
|
||||
FALCO_PLUGIN_SDK_PUBLIC int32_t plugin_extract_fields(ss_plugin_t *s,
|
||||
const ss_plugin_event_input *evt,
|
||||
const ss_plugin_field_extract_input* in)
|
||||
{
|
||||
if (s_async_extractor_ctx != NULL)
|
||||
// note: concurrent requests are supported on the context batch, but each
|
||||
// slot with a different value of ss_plugin_t *s. As such, for each lock
|
||||
// we assume worker is already in WAIT state. This is possible because
|
||||
// ss_plugin_t *s is an integer number representing a cgo.Handle, and can
|
||||
// have values in the range of [1, cgo.MaxHandle]
|
||||
//
|
||||
// todo(jasondellaluce): this is dependent on the implementation of our
|
||||
// cgo.Handle to optimize performance, so change this if we ever change
|
||||
// how cgo.Handles are represented
|
||||
|
||||
// if async optimization is not available, go with a simple C -> Go call
|
||||
if (s_async_ctx_batch == NULL
|
||||
|| atomic_load_explicit(&s_async_ctx_batch[(size_t)s - 1].lock, memory_order_seq_cst) != WAIT)
|
||||
{
|
||||
return async_extract_request(s, evt, num_fields, fields);
|
||||
return plugin_extract_fields_sync(s, evt, in->num_fields, in->fields, in->value_offsets);
|
||||
}
|
||||
|
||||
return plugin_extract_fields_sync(s, evt, num_fields, fields);
|
||||
// Set input data
|
||||
s_async_ctx_batch[(size_t)s - 1].s = s;
|
||||
s_async_ctx_batch[(size_t)s - 1].evt = evt;
|
||||
s_async_ctx_batch[(size_t)s - 1].num_fields = in->num_fields;
|
||||
s_async_ctx_batch[(size_t)s - 1].fields = in->fields;
|
||||
s_async_ctx_batch[(size_t)s - 1].value_offsets = in->value_offsets;
|
||||
|
||||
// notify data request
|
||||
atomic_store_explicit(&s_async_ctx_batch[(size_t)s - 1].lock, DATA_REQ, memory_order_seq_cst);
|
||||
|
||||
// busy-wait until worker completation
|
||||
while (atomic_load_explicit(&s_async_ctx_batch[(size_t)s - 1].lock, memory_order_seq_cst) != WAIT);
|
||||
|
||||
return s_async_ctx_batch[(size_t)s - 1].rc;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2025 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -21,13 +22,13 @@ limitations under the License.
|
|||
// of cgo.Handle from this SDK. The value of the s handle must implement
|
||||
// the sdk.Extractor and sdk.ExtractRequests interfaces.
|
||||
//
|
||||
// This function is part of the source_plugin_info and extractor_plugin_info
|
||||
// interfaces as defined in plugin_info.h.
|
||||
// This function is part of the plugin_api interface as defined in plugin_api.h.
|
||||
// In almost all cases, your plugin should import this module, unless your
|
||||
// plugin exports those symbols by other means.
|
||||
package extract
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include "extract.h"
|
||||
*/
|
||||
import "C"
|
||||
|
@ -39,20 +40,34 @@ import (
|
|||
)
|
||||
|
||||
//export plugin_extract_fields_sync
|
||||
func plugin_extract_fields_sync(plgState C.uintptr_t, evt *C.ss_plugin_event, numFields uint32, fields *C.ss_plugin_extract_field) int32 {
|
||||
func plugin_extract_fields_sync(plgState C.uintptr_t, evt *C.ss_plugin_event_input, numFields uint32, fields *C.ss_plugin_extract_field, offsets *C.ss_plugin_extract_value_offsets) int32 {
|
||||
pHandle := cgo.Handle(plgState)
|
||||
extract := pHandle.Value().(sdk.Extractor)
|
||||
extrReqs := pHandle.Value().(sdk.ExtractRequests)
|
||||
|
||||
// https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
|
||||
// https://go.dev/wiki/cgo#turning-c-arrays-into-go-slices
|
||||
flds := (*[1 << 28]C.struct_ss_plugin_extract_field)(unsafe.Pointer(fields))[:numFields:numFields]
|
||||
var i uint32
|
||||
var extrReq sdk.ExtractRequest
|
||||
|
||||
if offsets != nil {
|
||||
extrReqs.ExtractRequests().MakeOffsetArrayPtrs(unsafe.Pointer(offsets), numFields)
|
||||
}
|
||||
|
||||
for i = 0; i < numFields; i++ {
|
||||
flds[i].field_present = false
|
||||
flds[i].res_len = (C.uint64_t)(0)
|
||||
extrReq = extrReqs.ExtractRequests().Get(int(flds[i].field_id))
|
||||
extrReq.SetPtr(unsafe.Pointer(&flds[i]))
|
||||
|
||||
if offsets == nil {
|
||||
extrReq.SetOffsetPtrs(nil, nil)
|
||||
} else {
|
||||
extrReq.SetOffsetPtrs(
|
||||
unsafe.Add(unsafe.Pointer(offsets.start), i*C.sizeof_uint32_t),
|
||||
unsafe.Add(unsafe.Pointer(offsets.length), i*C.sizeof_uint32_t),
|
||||
)
|
||||
}
|
||||
|
||||
err := extract.Extract(extrReq, sdk.NewEventReader(unsafe.Pointer(evt)))
|
||||
if err != nil {
|
||||
pHandle.Value().(sdk.LastError).SetLastError(err)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -17,7 +18,7 @@ limitations under the License.
|
|||
#pragma once
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include "../../plugin_info.h"
|
||||
#include "../../plugin_api.h"
|
||||
|
||||
typedef struct async_extractor_info
|
||||
{
|
||||
|
@ -26,13 +27,14 @@ typedef struct async_extractor_info
|
|||
|
||||
// input data
|
||||
ss_plugin_t *s;
|
||||
const ss_plugin_event *evt;
|
||||
const ss_plugin_event_input *evt;
|
||||
uint32_t num_fields;
|
||||
ss_plugin_extract_field *fields;
|
||||
ss_plugin_extract_value_offsets *value_offsets;
|
||||
|
||||
// output data
|
||||
int32_t rc;
|
||||
} async_extractor_info;
|
||||
|
||||
async_extractor_info *async_init();
|
||||
async_extractor_info *async_init(size_t size);
|
||||
void async_deinit();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -65,9 +66,9 @@ func allocSSPluginExtractField(fid, ftype uint32, fname, farg string) (*_Ctype_s
|
|||
ret.field = (*_Ctype_char)(fnameBuf.CharPtr())
|
||||
if len(farg) > 0 {
|
||||
argBuf.Write(farg)
|
||||
ret.arg = (*_Ctype_char)(argBuf.CharPtr())
|
||||
ret.arg_key = (*_Ctype_char)(argBuf.CharPtr())
|
||||
} else {
|
||||
ret.arg = nil
|
||||
ret.arg_key = nil
|
||||
}
|
||||
|
||||
return ret, func() {
|
||||
|
@ -76,15 +77,24 @@ func allocSSPluginExtractField(fid, ftype uint32, fname, farg string) (*_Ctype_s
|
|||
}
|
||||
}
|
||||
|
||||
func allocSSPluginEvent(num, ts uint64, data []byte) (*_Ctype_struct_ss_plugin_event, func()) {
|
||||
ret := &_Ctype_struct_ss_plugin_event{}
|
||||
func allocSSPluginExtractValueOffsets(start *uint32, length *uint32) (*_Ctype_ss_plugin_extract_value_offsets) {
|
||||
ret := &_Ctype_ss_plugin_extract_value_offsets{}
|
||||
ret.start = (*_Ctype_uint32_t)(start)
|
||||
ret.length = (*_Ctype_uint32_t)(length)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func allocSSPluginEvent(num, ts uint64, data []byte) (*_Ctype_struct_ss_plugin_event_input, func()) {
|
||||
ret := &_Ctype_struct_ss_plugin_event_input{}
|
||||
evts, _ := sdk.NewEventWriters(1, int64(len(data)))
|
||||
evt := evts.Get(0)
|
||||
evt.Writer().Write(data)
|
||||
ret.evt = *(**_Ctype_struct_ss_plugin_event)(evts.ArrayPtr())
|
||||
ret.evtnum = _Ctype_uint64_t(num)
|
||||
ret.ts = _Ctype_uint64_t(ts)
|
||||
ret.data = (*_Ctype_uint8_t)(&data[0])
|
||||
ret.datalen = _Ctype_uint32_t(len(data))
|
||||
|
||||
return ret, func() {
|
||||
// nothing to deallocate here
|
||||
evts.Free()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,11 +126,22 @@ func TestExtract(t *testing.T) {
|
|||
// panic
|
||||
badHandle := cgo.NewHandle(1)
|
||||
assertPanic(t, func() {
|
||||
plugin_extract_fields_sync(_Ctype_uintptr_t(badHandle), event, 1, field)
|
||||
plugin_extract_fields_sync(_Ctype_uintptr_t(badHandle), event, 1, field, nil)
|
||||
})
|
||||
|
||||
// success
|
||||
res = plugin_extract_fields_sync(_Ctype_uintptr_t(handle), event, 1, field)
|
||||
res = plugin_extract_fields_sync(_Ctype_uintptr_t(handle), event, 1, field, nil)
|
||||
if res != sdk.SSPluginSuccess {
|
||||
t.Errorf("(res): expected %d, but found %d", sdk.SSPluginSuccess, res)
|
||||
} else if sample.lastErr != nil {
|
||||
t.Errorf("(lastErr): should be nil")
|
||||
}
|
||||
|
||||
// success + offsets
|
||||
val_start := uint32(0)
|
||||
val_length := uint32(8)
|
||||
offsets := allocSSPluginExtractValueOffsets(&val_start, &val_length)
|
||||
res = plugin_extract_fields_sync(_Ctype_uintptr_t(handle), event, 1, field, offsets)
|
||||
if res != sdk.SSPluginSuccess {
|
||||
t.Errorf("(res): expected %d, but found %d", sdk.SSPluginSuccess, res)
|
||||
} else if sample.lastErr != nil {
|
||||
|
@ -129,7 +150,7 @@ func TestExtract(t *testing.T) {
|
|||
|
||||
// error
|
||||
sample.err = errTest
|
||||
res = plugin_extract_fields_sync(_Ctype_uintptr_t(handle), event, 1, field)
|
||||
res = plugin_extract_fields_sync(_Ctype_uintptr_t(handle), event, 1, field, nil)
|
||||
if res != sdk.SSPluginFailure {
|
||||
t.Errorf("(res): expected %d, but found %d", sdk.SSPluginFailure, res)
|
||||
} else if sample.lastErr != errTest {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -17,8 +18,7 @@ limitations under the License.
|
|||
// This package exports the following C function:
|
||||
// - char* plugin_get_fields()
|
||||
//
|
||||
// This function is part of the source_plugin_info and extractor_plugin_info
|
||||
// interfaces as defined in plugin_info.h.
|
||||
// This function is part of the plugin_api interface as defined in plugin_api.h.
|
||||
// In almost all cases, your plugin should import this module, unless your
|
||||
// plugin exports those symbols by other means.
|
||||
package fields
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2022 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,9 +16,19 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
#include "info.h"
|
||||
#include "../../plugin_info.h"
|
||||
#include "../../plugin_types.h"
|
||||
#include "../../plugin_api.h"
|
||||
|
||||
const char* get_default_required_api_version()
|
||||
{
|
||||
return PLUGIN_API_VERSION_STR;
|
||||
}
|
||||
|
||||
// todo(jasondellaluce,therealbobo): support this for real when we decide to
|
||||
// deal with non-plugin events in the SDK Go
|
||||
uint16_t* plugin_get_extract_event_types(uint32_t* num_types)
|
||||
{
|
||||
static uint16_t types[] = { 322 }; // PPME_PLUGINEVENT_E
|
||||
*num_types = sizeof(types) / sizeof(uint16_t);
|
||||
return &types[0];
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -16,6 +17,7 @@ limitations under the License.
|
|||
|
||||
// This package exports a set of C functions that provide general
|
||||
// information about the plugin. The exported functions are:
|
||||
//
|
||||
// uint32_t get_type();
|
||||
// uint32_t get_id();
|
||||
// char* get_name();
|
||||
|
@ -36,6 +38,7 @@ package info
|
|||
import "C"
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/ptr"
|
||||
)
|
||||
|
@ -52,19 +55,6 @@ var (
|
|||
pExtractEventSources ptr.StringBuffer
|
||||
)
|
||||
|
||||
//export plugin_get_type
|
||||
func plugin_get_type() uint32 {
|
||||
return pType
|
||||
}
|
||||
|
||||
func SetType(t uint32) {
|
||||
pType = t
|
||||
}
|
||||
|
||||
func Type() uint32 {
|
||||
return pType
|
||||
}
|
||||
|
||||
//export plugin_get_id
|
||||
func plugin_get_id() uint32 {
|
||||
return pId
|
||||
|
@ -118,7 +108,33 @@ func plugin_get_required_api_version() *C.char {
|
|||
return (*C.char)(pRequiredAPIVersion.CharPtr())
|
||||
}
|
||||
|
||||
func splitVersionString(version string) (string, string, string) {
|
||||
nums := strings.Split(version, ".")
|
||||
if len(nums) != 3 {
|
||||
panic("Incorrect format. Expected: Semantic Versioning: X.Y.Z")
|
||||
}
|
||||
return nums[0], nums[1], nums[2]
|
||||
}
|
||||
|
||||
func SetRequiredAPIVersion(apiVer string) {
|
||||
if apiVer != "" {
|
||||
pluginRequiredMajor, pluginRequiredMinor, pluginRequiredPatch := splitVersionString(apiVer)
|
||||
sdkRequiredMajor, sdkRequiredMinor, sdkRequiredPatch := splitVersionString(C.GoString(C.get_default_required_api_version()))
|
||||
|
||||
// The plugin should always require a version lower or equal to the one required by the SDK
|
||||
// because the SDK couldn't support features coming from new framework versions.
|
||||
// On the other side the plugin could require a lower version because maybe it doesn't
|
||||
// need all features provided by the framework.
|
||||
if sdkRequiredMajor != pluginRequiredMajor {
|
||||
panic("Incompatible required Major version between SDK and the plugin. Major SDK version is equal to " + sdkRequiredMajor + " but the plugin uses " + pluginRequiredMajor + ". The 2 Major versions should be equal.")
|
||||
}
|
||||
if sdkRequiredMinor < pluginRequiredMinor {
|
||||
panic("The plugin requires a Minor version greater than the SDK one. Minor SDK version is equal to " + sdkRequiredMinor + " but the plugin uses " + pluginRequiredMinor + ". The plugin should always require a Minor version lower or equal to the SDK one.")
|
||||
}
|
||||
if sdkRequiredMinor == pluginRequiredMinor && sdkRequiredPatch < pluginRequiredPatch {
|
||||
panic("The plugin requires a Patch version greater than the SDK one. Patch SDK version is equal to " + sdkRequiredPatch + " but the plugin uses " + pluginRequiredPatch + ". The plugin should always require a Patch version lower or equal to the SDK one.")
|
||||
}
|
||||
}
|
||||
pRequiredAPIVersion.Write(apiVer)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2022 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -18,16 +19,22 @@ package info
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/falcosecurity/plugin-sdk-go/pkg/ptr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var testStr = "test"
|
||||
var testU32 = uint32(1)
|
||||
var testStrSlice = []string{"hello", "world"}
|
||||
|
||||
func testFormatVer(major, minor, patch int) string {
|
||||
return fmt.Sprintf("%d.%d.%d", major, minor, patch)
|
||||
}
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
var resU32 uint32
|
||||
var resStr string
|
||||
|
@ -36,17 +43,7 @@ func TestInfo(t *testing.T) {
|
|||
SetId(testU32)
|
||||
resU32 = plugin_get_id()
|
||||
if resU32 != testU32 {
|
||||
t.Errorf("(id) expected %d, but found %d", testU32, resU32)
|
||||
}
|
||||
|
||||
SetType(testU32)
|
||||
resU32 = plugin_get_type()
|
||||
if resU32 != testU32 {
|
||||
t.Errorf("(type) expected %d, but found %d", testU32, resU32)
|
||||
}
|
||||
resU32 = Type()
|
||||
if resU32 != testU32 {
|
||||
t.Errorf("(type) expected %d, but found %d", testU32, resU32)
|
||||
t.Errorf("(plugin id) expected %d, but found %d", testU32, resU32)
|
||||
}
|
||||
|
||||
SetName(testStr)
|
||||
|
@ -73,12 +70,6 @@ func TestInfo(t *testing.T) {
|
|||
t.Errorf("(version) expected %s, but found %s", testStr, resStr)
|
||||
}
|
||||
|
||||
SetRequiredAPIVersion(testStr)
|
||||
resStr = ptr.GoString(unsafe.Pointer(plugin_get_required_api_version()))
|
||||
if resStr != testStr {
|
||||
t.Errorf("(requiredApiVersion) expected %s, but found %s", testStr, resStr)
|
||||
}
|
||||
|
||||
SetEventSource(testStr)
|
||||
resStr = ptr.GoString(unsafe.Pointer(plugin_get_event_source()))
|
||||
if resStr != testStr {
|
||||
|
@ -112,3 +103,109 @@ func TestInfo(t *testing.T) {
|
|||
t.Errorf("(extractEventSources) expected %s, but found %s", testStr, resStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitVersionString(t *testing.T) {
|
||||
t.Run("invalid version string 1", func(t *testing.T) {
|
||||
panicFunc := func() {
|
||||
splitVersionString("2..1..2")
|
||||
}
|
||||
assert.Panics(t, panicFunc)
|
||||
})
|
||||
|
||||
t.Run("invalid version string 2", func(t *testing.T) {
|
||||
panicFunc := func() {
|
||||
splitVersionString("2.2.3..32")
|
||||
}
|
||||
assert.Panics(t, panicFunc)
|
||||
})
|
||||
|
||||
t.Run("invalid version string 3", func(t *testing.T) {
|
||||
panicFunc := func() {
|
||||
splitVersionString("2.2.3.")
|
||||
}
|
||||
assert.Panics(t, panicFunc)
|
||||
})
|
||||
|
||||
t.Run("invalid version string 4", func(t *testing.T) {
|
||||
panicFunc := func() {
|
||||
splitVersionString("2..2.3")
|
||||
}
|
||||
assert.Panics(t, panicFunc)
|
||||
})
|
||||
|
||||
t.Run("valid version string", func(t *testing.T) {
|
||||
panicFunc := func() {
|
||||
splitVersionString("2.2.3")
|
||||
}
|
||||
assert.NotPanics(t, panicFunc)
|
||||
})
|
||||
|
||||
t.Run("check split version string", func(t *testing.T) {
|
||||
major, minor, patch := splitVersionString("2.4.3")
|
||||
if major != "2" {
|
||||
t.Errorf("(Major) expected %s, but found %s", "2", major)
|
||||
}
|
||||
if minor != "4" {
|
||||
t.Errorf("(Minor) expected %s, but found %s", "4", minor)
|
||||
}
|
||||
if patch != "3" {
|
||||
t.Errorf("(Patch) expected %s, but found %s", "3", patch)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSemver(t *testing.T) {
|
||||
// Get the SDK required version
|
||||
// we set an empty string to obtain the default SDK version
|
||||
SetRequiredAPIVersion("")
|
||||
versionSDK := ptr.GoString(unsafe.Pointer(plugin_get_required_api_version()))
|
||||
var majorSDK, minorSDK, patchSDK int
|
||||
nums, err := fmt.Sscanf(versionSDK, "%d.%d.%d", &majorSDK, &minorSDK, &patchSDK)
|
||||
if nums != 3 || err != nil {
|
||||
t.Errorf("Unable to obtain the default SDK version")
|
||||
}
|
||||
|
||||
// plguin Major == SDK Major && plguin Minor == SDK Minor && plguin Patch == SDK Patch
|
||||
t.Run("plguin Major == SDK Major && plguin Minor == SDK Minor && plguin Patch == SDK Patch", func(t *testing.T) {
|
||||
SetRequiredAPIVersion(testFormatVer(majorSDK, minorSDK, patchSDK))
|
||||
requiredAPIVersion := ptr.GoString(unsafe.Pointer(plugin_get_required_api_version()))
|
||||
expectedRequiredAPIVersion := testFormatVer(majorSDK, minorSDK, patchSDK)
|
||||
if expectedRequiredAPIVersion != requiredAPIVersion {
|
||||
t.Errorf("(requiredApiVersion) expected %s, but found %s", expectedRequiredAPIVersion, requiredAPIVersion)
|
||||
}
|
||||
})
|
||||
|
||||
// plguin Major > SDK Major
|
||||
t.Run("plguin Major > SDK Major", func(t *testing.T) {
|
||||
panicFunc := func() {
|
||||
SetRequiredAPIVersion(testFormatVer(majorSDK+1, minorSDK, patchSDK))
|
||||
}
|
||||
assert.PanicsWithValue(t, fmt.Sprintf("Incompatible required Major version between SDK and the plugin. Major SDK version is equal to %d but the plugin uses %d. The 2 Major versions should be equal.", majorSDK, majorSDK+1), panicFunc)
|
||||
})
|
||||
|
||||
// plguin Major == SDK Major && plguin Minor > SDK Minor
|
||||
t.Run("plguin Major == SDK Major && plguin Minor > SDK Minor", func(t *testing.T) {
|
||||
panicFunc := func() {
|
||||
SetRequiredAPIVersion(testFormatVer(majorSDK, minorSDK+1, patchSDK))
|
||||
}
|
||||
assert.PanicsWithValue(t, fmt.Sprintf("The plugin requires a Minor version greater than the SDK one. Minor SDK version is equal to %d but the plugin uses %d. The plugin should always require a Minor version lower or equal to the SDK one.", minorSDK, minorSDK+1), panicFunc)
|
||||
})
|
||||
|
||||
// plguin Major == SDK Major && plguin Minor == SDK Minor && plguin Patch > SDK Patch
|
||||
t.Run("plguin Major == SDK Major && plguin Minor == SDK Minor && plguin Patch > SDK Patch", func(t *testing.T) {
|
||||
panicFunc := func() {
|
||||
SetRequiredAPIVersion(testFormatVer(majorSDK, minorSDK, patchSDK+1))
|
||||
}
|
||||
assert.PanicsWithValue(t, fmt.Sprintf("The plugin requires a Patch version greater than the SDK one. Patch SDK version is equal to %d but the plugin uses %d. The plugin should always require a Patch version lower or equal to the SDK one.", patchSDK, patchSDK+1), panicFunc)
|
||||
})
|
||||
|
||||
t.Run("empty plugin version", func(t *testing.T) {
|
||||
// This should set as default the SDK required version since we don't provide the required plugin version
|
||||
SetRequiredAPIVersion("")
|
||||
requiredAPIVersion := ptr.GoString(unsafe.Pointer(plugin_get_required_api_version()))
|
||||
expectedRequiredAPIVersion := testFormatVer(majorSDK, minorSDK, patchSDK)
|
||||
if expectedRequiredAPIVersion != requiredAPIVersion {
|
||||
t.Errorf("(requiredApiVersion) expected %s, but found %s", expectedRequiredAPIVersion, requiredAPIVersion)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -32,14 +33,13 @@ limitations under the License.
|
|||
// on the returned sdk.StringBuffer. Finally, the function deletes the
|
||||
// s cgo.Handle.
|
||||
//
|
||||
// This function is part of the source_plugin_info and extractor_plugin_info
|
||||
// interfaces as defined in plugin_info.h.
|
||||
// This function is part of the plugin_api interface as defined in plugin_api.h.
|
||||
// In almost all cases, your plugin should import this module, unless your
|
||||
// plugin exports those symbols by other means.
|
||||
package initialize
|
||||
|
||||
/*
|
||||
#include <stdint.h>
|
||||
#include "../../plugin_api.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
@ -83,11 +83,12 @@ func SetOnInit(fn OnInitFn) {
|
|||
}
|
||||
|
||||
//export plugin_init
|
||||
func plugin_init(config *C.char, rc *int32) C.uintptr_t {
|
||||
func plugin_init(in *C.ss_plugin_init_input, rc *int32) C.uintptr_t {
|
||||
var state sdk.PluginState
|
||||
var err error
|
||||
|
||||
state, err = onInitFn(C.GoString(config))
|
||||
// todo(jasondellaluce,therealbobo): support table access and owner operations
|
||||
state, err = onInitFn(C.GoString(in.config))
|
||||
if err != nil {
|
||||
state = &baseInit{}
|
||||
state.(sdk.LastError).SetLastError(err)
|
||||
|
@ -119,6 +120,7 @@ func plugin_destroy(pState C.uintptr_t) {
|
|||
}
|
||||
if state, ok := handle.Value().(sdk.ExtractRequests); ok {
|
||||
state.ExtractRequests().Free()
|
||||
state.SetExtractRequests(nil)
|
||||
}
|
||||
if state, ok := handle.Value().(sdk.LastErrorBuffer); ok {
|
||||
state.LastErrorBuffer().Free()
|
||||
|
@ -129,7 +131,6 @@ func plugin_destroy(pState C.uintptr_t) {
|
|||
if state, ok := handle.Value().(sdk.ProgressBuffer); ok {
|
||||
state.ProgressBuffer().Free()
|
||||
}
|
||||
|
||||
handle.Delete()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -80,7 +81,16 @@ func TestInitialize(t *testing.T) {
|
|||
SetOnInit(func(config string) (sdk.PluginState, error) {
|
||||
return nil, nil
|
||||
})
|
||||
handle = cgo.Handle(plugin_init((*_Ctype_char)(cStr.CharPtr()), &res))
|
||||
|
||||
// create an init input
|
||||
var in _Ctype_struct_ss_plugin_init_input
|
||||
in.config = nil
|
||||
in.owner = nil
|
||||
in.get_owner_last_error = nil
|
||||
in.tables = nil
|
||||
|
||||
in.config = (*_Ctype_char)(cStr.CharPtr())
|
||||
handle = cgo.Handle(plugin_init(&in, &res))
|
||||
if res != sdk.SSPluginSuccess {
|
||||
t.Errorf("(res): expected %d, but found %d", sdk.SSPluginSuccess, res)
|
||||
} else if handle.Value() != nil {
|
||||
|
@ -92,7 +102,8 @@ func TestInitialize(t *testing.T) {
|
|||
SetOnInit(func(config string) (sdk.PluginState, error) {
|
||||
return nil, errTest
|
||||
})
|
||||
handle = cgo.Handle(plugin_init((*_Ctype_char)(cStr.CharPtr()), &res))
|
||||
in.config = (*_Ctype_char)(cStr.CharPtr())
|
||||
handle = cgo.Handle(plugin_init(&in, &res))
|
||||
if res != sdk.SSPluginFailure {
|
||||
t.Errorf("(res): expected %d, but found %d", sdk.SSPluginFailure, res)
|
||||
}
|
||||
|
@ -109,7 +120,8 @@ func TestInitialize(t *testing.T) {
|
|||
SetOnInit(func(config string) (sdk.PluginState, error) {
|
||||
return state, nil
|
||||
})
|
||||
handle = cgo.Handle(plugin_init((*_Ctype_char)(cStr.CharPtr()), &res))
|
||||
in.config = (*_Ctype_char)(cStr.CharPtr())
|
||||
handle = cgo.Handle(plugin_init(&in, &res))
|
||||
if res != sdk.SSPluginSuccess {
|
||||
t.Errorf("(res): expected %d, but found %d", sdk.SSPluginSuccess, res)
|
||||
} else if handle.Value() != state {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -17,14 +18,13 @@ limitations under the License.
|
|||
// This package exports the following C function:
|
||||
// - const char* get_init_schema(ss_plugin_schema_type* schema_type)
|
||||
//
|
||||
// This function is part of the source_plugin_info and extractor_plugin_info
|
||||
// interfaces as defined in plugin_info.h.
|
||||
// This function is part of the plugin_api interface as defined in plugin_api.h.
|
||||
// In almost all cases, your plugin should import this module, unless your
|
||||
// plugin exports those symbols by other means.
|
||||
package initschema
|
||||
|
||||
/*
|
||||
#include "../../plugin_info.h"
|
||||
#include "../../plugin_types.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -22,8 +23,7 @@ limitations under the License.
|
|||
// the sdk.LastErrorBuffer interface, and either the error or the sdk.LastError
|
||||
// interfaces.
|
||||
//
|
||||
// This function is part of the source_plugin_info and extractor_plugin_info
|
||||
// interfaces as defined in plugin_info.h.
|
||||
// This function is part of the plugin_api interface as defined in plugin_api.h.
|
||||
// In almost all cases, your plugin should import this module, unless your
|
||||
// plugin exports those symbols by other means.
|
||||
package lasterr
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -17,8 +18,7 @@ limitations under the License.
|
|||
// This package exports the following C function:
|
||||
// - char* plugin_list_open_params()
|
||||
//
|
||||
// This function is part of the source_plugin_info and capture_plugin_info
|
||||
// interfaces as defined in plugin_info.h.
|
||||
// This function is part of the plugin_api interface as defined in plugin_api.h.
|
||||
// In almost all cases, your plugin should import this module, unless your
|
||||
// plugin exports those symbols by other means.
|
||||
package listopen
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,21 +16,20 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
// This package exports the following C function:
|
||||
// - ss_plugin_rc plugin_next_batch(ss_plugin_t* s, ss_instance_t* h, uint32_t *nevts, ss_plugin_event **evts)
|
||||
// - ss_plugin_rc plugin_next_batch(ss_plugin_t* s, ss_instance_t* h, uint32_t *nevts, ss_plugin_event ***evts)
|
||||
//
|
||||
// The exported plugin_next_batch requires s and h to be a handles
|
||||
// of cgo.Handle from this SDK. The value of the s handle must implement
|
||||
// the sdk.PluginState interface. The value of the h handle must implement
|
||||
// the sdk.Events and the sdk.NextBatcher interfaces.
|
||||
//
|
||||
// This function is part of the source_plugin_info interface as defined in
|
||||
// plugin_info.h.
|
||||
// This function is part of the plugin_api interface as defined in plugin_api.h.
|
||||
// In almost all cases, your plugin should import this module, unless your
|
||||
// plugin exports those symbols by other means.
|
||||
package nextbatch
|
||||
|
||||
/*
|
||||
#include "../../plugin_info.h"
|
||||
#include "../../plugin_types.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
@ -38,12 +38,12 @@ import (
|
|||
)
|
||||
|
||||
//export plugin_next_batch
|
||||
func plugin_next_batch(pState C.uintptr_t, iState C.uintptr_t, nevts *uint32, retEvts **C.ss_plugin_event) int32 {
|
||||
func plugin_next_batch(pState C.uintptr_t, iState C.uintptr_t, nevts *uint32, retEvts ***C.ss_plugin_event) int32 {
|
||||
events := cgo.Handle(iState).Value().(sdk.Events).Events()
|
||||
n, err := cgo.Handle(iState).Value().(sdk.NextBatcher).NextBatch(cgo.Handle(pState).Value().(sdk.PluginState), events)
|
||||
|
||||
*nevts = uint32(n)
|
||||
*retEvts = (*C.ss_plugin_event)(events.ArrayPtr())
|
||||
*retEvts = (**C.ss_plugin_event)(events.ArrayPtr())
|
||||
switch err {
|
||||
case nil:
|
||||
return sdk.SSPluginSuccess
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -68,7 +69,7 @@ func TestNextBatch(t *testing.T) {
|
|||
// generic testing callback
|
||||
doTest := func(name string, res int32, num uint32, ptr unsafe.Pointer, err error) {
|
||||
var resNum uint32
|
||||
var resPtr *_Ctype_ss_plugin_event
|
||||
var resPtr **_Ctype_ss_plugin_event
|
||||
r := plugin_next_batch(_Ctype_uintptr_t(handle), _Ctype_uintptr_t(handle), &resNum, &resPtr)
|
||||
if r != res {
|
||||
t.Errorf("(%s - res): expected %d, but found %d", name, res, r)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
Copyright (C) 2021 The Falco Authors.
|
||||
Copyright (C) 2023 The Falco Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -33,8 +34,7 @@ limitations under the License.
|
|||
// on the returned sdk.EventWriters. Finally, the function deletes the
|
||||
// h cgo.Handle.
|
||||
//
|
||||
// This function is part of the source_plugin_info interface as defined
|
||||
// in plugin_info.h.
|
||||
// This function is part of the plugin_api interface as defined in plugin_api.h.
|
||||
// In almost all cases, your plugin should import this module, unless your
|
||||
// plugin exports those symbols by other means.
|
||||
package open
|
||||
|
@ -101,6 +101,7 @@ func plugin_close(plgState C.uintptr_t, instanceState C.uintptr_t) {
|
|||
}
|
||||
if state, ok := handle.Value().(sdk.Events); ok {
|
||||
state.Events().Free()
|
||||
state.SetEvents(nil)
|
||||
}
|
||||
handle.Delete()
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue