You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by sh...@apache.org on 2024/07/10 21:12:22 UTC
(trafficserver) 01/07: AVX-33061: Move to ATS10
This is an automated email from the ASF dual-hosted git repository.
shinrich pushed a commit to branch avx-53179
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit 9e9164df4441afd36192e73b77c303d836b81536
Author: Susan Hinrichs <sh...@aviatrix.com>
AuthorDate: Wed Mar 27 12:48:49 2024 +0000
AVX-33061: Move to ATS10
---
.gitignore | 2 +-
AVIATRIX.md | 174 +++
CMakeLists.txt | 4 +
CMakePresets.json | 22 +
aviatrix/10_0_0.0001/avx-gw-trafficserver.service | 18 +
aviatrix/10_0_0.0001/configs/logging.yaml | 41 +
aviatrix/10_0_0.0001/configs/plugin.config | 15 +
aviatrix/10_0_0.0001/configs/records.yaml | 218 ++++
aviatrix/10_0_0.0001/control | 6 +
.../10_0_0.0001/logrotate/avx-gw-trafficserver | 11 +
aviatrix/10_0_0.0001/postinst | 30 +
aviatrix/build/.gitignore | 3 +
aviatrix/build/docker_scripts/env_command.sh | 179 +++
aviatrix/build/docker_scripts/setup_debug.sh | 3 +
aviatrix/build/docker_scripts/setup_local.sh | 94 ++
aviatrix/build/docker_scripts/setup_release.sh | 3 +
aviatrix/build/dockerfile-atsbuild | 116 ++
aviatrix/build/dockerfile-atsbuildv2 | 61 +
aviatrix/build/dockerfile-buildtools | 37 +
aviatrix/build/dockerfile-openssl | 51 +
aviatrix/build/makeprotos.sh | 3 +
aviatrix/build/setup_build_tools.sh | 21 +
aviatrix/build/setup_build_tools_1.sh | 66 ++
aviatrix/build/setup_build_tools_2.sh | 24 +
aviatrix/dagger/.gitignore | 2 +
aviatrix/dagger/Makefile | 59 +
aviatrix/dagger/build.go | 594 ++++++++++
aviatrix/dagger/build_manifest.sh | 27 +
aviatrix/dagger/builder.sh | 13 +
aviatrix/dagger/go.mod | 23 +
aviatrix/dagger/go.sum | 48 +
aviatrix/dagger/main.go | 312 +++++
aviatrix/plugins/CMakeLists.txt | 3 +
aviatrix/plugins/avx_certifier/CMakeLists.txt | 4 +
aviatrix/plugins/avx_certifier/README | 33 +
aviatrix/plugins/avx_certifier/avx_certifier.cc | 1192 ++++++++++++++++++++
aviatrix/plugins/avx_policy_driver/.gitignore | 3 +
aviatrix/plugins/avx_policy_driver/CMakeLists.txt | 67 ++
aviatrix/plugins/avx_policy_driver/Makefile.inc | 116 ++
aviatrix/plugins/avx_policy_driver/jsonwriter.h | 291 +++++
aviatrix/plugins/avx_policy_driver/logging.cc | 80 ++
aviatrix/plugins/avx_policy_driver/logging.h | 30 +
aviatrix/plugins/avx_policy_driver/plugin.h | 6 +
aviatrix/plugins/avx_policy_driver/policy.h | 44 +
.../plugins/avx_policy_driver/policy_client.cc | 92 ++
.../plugins/avx_policy_driver/policy_driver.cc | 303 +++++
.../plugins/avx_policy_driver/policy_hit_counter.h | 167 +++
.../plugins/avx_policy_driver/rules/mitm_test.json | 64 ++
.../plugins/avx_policy_driver/rules/no-ids.json | 451 ++++++++
.../plugins/avx_policy_driver/rules/rules1.json | 30 +
aviatrix/plugins/avx_policy_driver/test_client.cc | 60 +
aviatrix/plugins/avx_policy_driver/test_server.cc | 181 +++
.../avx_policy_driver/unit_test_hitcounter.cc | 203 ++++
aviatrix/plugins/avx_policy_driver/web_filter.cc | 961 ++++++++++++++++
aviatrix/plugins/avx_policy_driver/web_filter.h | 79 ++
aviatrix/plugins/avx_tee_decrypt/CMakeLists.txt | 4 +
aviatrix/plugins/avx_tee_decrypt/plugin.h | 5 +
aviatrix/plugins/avx_tee_decrypt/tee_decrypt.cc | 467 ++++++++
aviatrix/plugins/avx_tee_decrypt/tee_info.cc | 366 ++++++
aviatrix/plugins/avx_tee_decrypt/tee_info.h | 40 +
include/ts/ts.h | 27 +
include/tscore/TestBox.h | 3 +
src/api/InkAPI.cc | 62 +
src/proxy/http/HttpSessionManager.cc | 3 +
64 files changed, 7716 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index e1fd4bc749..65e49bb2b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,7 +44,7 @@ m4/ltversion.m4
m4/lt~obsolete.m4
# common directory name for out-of-tree CMake builds
-build*
+#build*
configs/records.yaml.default
configs/storage.config.default
diff --git a/AVIATRIX.md b/AVIATRIX.md
new file mode 100644
index 0000000000..31d4bf8211
--- /dev/null
+++ b/AVIATRIX.md
@@ -0,0 +1,174 @@
+# Aviatrix Apache Traffic Server
+
+This is the Aviatrix build of apache traffic server
+- run aviatrix/build/setup_build_tools.sh to install grpc and libswoc in ~/.local
+- run ./build_debs.sh to generate the debs for deployment
+ this will reconfigure the project and needs docker
+
+## Building with Dagger
+
+This package uses [Dagger](https://dagger.io/) as the meta-build
+system to build an Ubuntu package that contains Traffic Server, all
+the right plugins, and various non-upstream dependencies.
+
+Usage:
+```
+% make -C aviatrix/dagger help
+TrafficServer meta-build system
+
+TARGETS
+
+ package Build the TrafficServer Ubuntu package
+ build-image Build a base container image for development
+ packaging-image Build a base container image for packaging
+
+ unit-tests Run Traffic Server unit tests
+ regression-tests Run Traffic Server regression tests
+
+ shell Drop into a shell in the development image
+ clean Remove build outputs
+
+VARIABLES
+
+ UBUNTU Base Ubuntu version (default is 22.04)
+```
+
+The `UBUNTU` make variable specifies the Ubuntu release to base the
+build action on. Currently, `"22.04"` is the default, but the legacy
+`"18.04"` is still tested and included in CI checks.
+
+If you need more flexibility than the Makefile provides, you can run
+the `./aviatrix/dagger/builder.sh` tool directly:
+
+```
+% ./aviatrix/dagger/builder.sh --help
+Usage:
+ build [command]
+
+Available Commands:
+ build-image Build a Docker image for local development
+ completion Generate the autocompletion script for the specified shell
+ help Help about any command
+ package Build the Ubuntu .deb package
+ packaging-image Build a Docker image for packaging
+ regression-tests Run Traffic Server regression tests
+ unit-tests Run Traffic Server unit tests
+
+Flags:
+ --branch string Traffic Server source branch
+ --cloudn-tag string Cloudn source repository tag
+ -h, --help help for build
+ --source string Traffic Server source repository or directory path (default "../..")
+ --tag string Traffic Server source tag
+ --ubuntu string Base Ubuntu release version (default "22.04")
+
+Use "build [command] --help" for more information about a command.
+```
+
+### Building an installable package
+
+To build the package, use the Dagger
+[Makefile](./aviatrix/dagger/Makefile). You don't need to have the
+`dagger` command-line utility installed, but you do need Docker.
+
+To build an install the Traffic Server package to a gateway instance:
+
+```
+% make -C aviatrix/dagger package
+Creating new Engine session... OK!
+...
+
+% ./install_deb_to_gw.sh avx-controller gw-spoke-ap-1 aviatrix/dagger/packages/22.04/ats-10.0.0.deb
+```
+
+By default, the Dagger builder will use the SSH agent socket to
+pull the latest cloudn protobufs from the master branch of the
+cloudn repository. To prevent this, unset the `SSH_AUTH_SOCK`
+environment variable before running the build. This will make the
+Dagger build look for a cloudn working copy next to this repository
+(i.e. in `../cloudn`).
+
+To debug the package building process, you can build and enter a
+Docker image that contains the staged package build:
+
+```
+% make -C aviatrix/dagger packaging-image UBUNTU=22.04
+...
+✅ tagged build container image "sha256:c0960c50f597fd1abf78d7d91a431f646e71af7f4ced8fea1a2c41257f2d4bb2" as "trafficserver-packaging:22.04"
+ % docker run --rm -it trafficserver-packaging:22.04
+root@2d568c64b760:/build/ats-10.0.0#
+```
+
+### Local development
+
+The `build-image` target builds and exports a Docker image that can
+be used for local development.
+
+The easiest way to use the local development image is to run `make
+shell`, which will drop you into the development image with this
+repository and your local `cloudn` working copy mounted. You can
+use the `conf.sh` script there to configure Traffic Server the same
+way that the Dagger build does it.
+
+The `build-package` is similar, in that it builds and exports a
+Docker image that is set up for building the Debian packages.
+There's no wrapper script to run a shell with this container image,
+so you have to use it directly with a `docker run` command.
+
+## docker tooling:
+
+./devenv_docker.sh setup
+setup docker image and remove previous images/containers/volumes
+
+./devenv_docker.sh shell
+jump in shell to look around
+
+./devenv_docker <debug/release> <clean/build/plugins> [nodeb] [shell]
+debug debug build
+release release build
+clean autoreconf/configure and clean folder
+build build changes
+plugins only build changes for plugins
+nodeb do not build debs
+shell jump in the docker shell to look around and try stuff
+
+## Installing binaries to gateway:
+
+to install the binaries to the gateway we can use the next command:
+
+./install_tgz_to_gw.sh <controller_host> <gatewayname > <ats/plugins>
+controller_host is the controller host
+gatewayname the name as seen by sshgw
+ats copy bin/lib/libexec on the gateway
+plugins copy the policy_driver and certifier plugins on the gateway
+
+layout source_root
+ |-- thirdparty-trafficserver
+ |-- cloudn
+ |-- trafficserver-buildtools
+ | |-- grpc (generated by setup_build_tools.sh)
+ | |-- libswoc (generated by setup_build_tools.sh)
+
+The build presumes the libs to be in ~/.local
+This can be overridden by make -e GRPC_INSTALL=<alternative path>
+~/.local/bin is not added to the path
+~/.local/lib/pkgconfig is not added to PKG_CONFIG_PATH
+
+build information:
+to get build information run this command against the debian
+
+dpkg --fsys-tarfile ats_10.0.0.deb | tar xOf - ./opt/ats/ats_10.0.0/ats_10.0.0.deb.manifest
+
+
+todos:
+- copy the ATS source tree to the docker to compile, instead of running from the original FS
+ this will prevent contamination of the build systems
+- add ubuntu 22.04 build
+- add requirement check for:
+ - docker
+ - grpc
+ - cloudn
+- make the grpc versioned and install it in the source_root, this enables us to build new
+ versions without changing
+- add a check script in the makefile, so we know that we have the right version of the
+ protos for the build
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7d9e4c4c7c..5fea770578 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -37,6 +37,7 @@ set(CMAKE_CXX_STANDARD
)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
+set(ENV{PKG_CONFIG_PATH} "~/.local/lib/pkgconfig:/opt/ats/lib/pkgconfig")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE -DATS_BUILD)
@@ -625,6 +626,8 @@ add_subdirectory(plugins)
# set up auto options for experimental plugins
include(ExperimentalPlugins)
+add_subdirectory(aviatrix/plugins)
+
add_subdirectory(configs)
if(ENABLE_EXAMPLE)
add_subdirectory(example)
@@ -677,6 +680,7 @@ add_clang_format_target(include)
add_clang_format_target(plugins)
add_clang_format_target(tools)
add_clang_format_target(tests)
+add_clang_format_target(aviatrix)
add_custom_target(
clang-format
diff --git a/CMakePresets.json b/CMakePresets.json
index 4ea2a27dda..d5be717766 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -375,6 +375,28 @@
"CMAKE_CXX_FLAGS_DEBUG": "--coverage",
"CMAKE_C_FLAGS_DEBUG": "--coverage"
}
+ },
+ {
+ "name": "aviatrix",
+ "displayName": "Aviatrix",
+ "description": "Aviatrix Presets",
+ "inherits": ["release"],
+ "generator": "Unix Makefiles",
+ "binaryDir": "${sourceDir}/build-avx",
+ "cacheVariables": {
+ "CMAKE_BUILD_TYPE": "RelWithDebInfo",
+ "CMAKE_PREFIX_PATH": "/opt/ats/ats_10.0.0;/opt/ats",
+ "ENABLE_LUAJIT": false,
+ "ENABLE_JEMALLOC": true,
+ "ENABLE_MIMALLOC": false,
+ "ENABLE_MALLOC_ALLOCATOR": true,
+ "ENABLE_AUTEST": false,
+ "BUILD_EXPERIMENTAL_PLUGINS": false,
+ "BUILD_REGRESSION_TESTING": false,
+ "CLOUDN_SOURCE_ROOT": "/src/cloudn",
+ "CMAKE_CXX_FLAGS": "-Werror",
+ "CMAKE_INSTALL_PREFIX": "/opt/ats/ats_10.0.0"
+ }
}
],
"buildPresets": [
diff --git a/aviatrix/10_0_0.0001/avx-gw-trafficserver.service b/aviatrix/10_0_0.0001/avx-gw-trafficserver.service
new file mode 100644
index 0000000000..34b230db78
--- /dev/null
+++ b/aviatrix/10_0_0.0001/avx-gw-trafficserver.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=Service that runs the trafficserver
+
+
+[Service]
+Type=simple
+User=ats-user
+ExecStart=-/opt/ats/ats_10.0.0/bin/traffic_server --bind_stdout /var/log/tslogs/traffic.out --bind_stderr /var/log/tslogs/traffic.out -f -F
+Restart=on-failure
+RestartSec=5s
+LimitNOFILE=1000000
+PIDFile=/opt/ats/ats_10.0.0/var/trafficserver/server.lock
+TimeoutStopSec=5s
+#ExecReload=@exp_bindir@/traffic_ctl config reload
+KillMode=process
+
+[Install]
+WantedBy=multi-user.target
diff --git a/aviatrix/10_0_0.0001/configs/logging.yaml b/aviatrix/10_0_0.0001/configs/logging.yaml
new file mode 100755
index 0000000000..98c9817a40
--- /dev/null
+++ b/aviatrix/10_0_0.0001/configs/logging.yaml
@@ -0,0 +1,41 @@
+# Custom log configuration
+#
+# Documentation on logging:
+# https://docs.trafficserver.apache.org/en/9.0.x/admin-guide/logging/index.en.html
+#
+# Documentation on logging.yaml file format:
+# https://docs.trafficserver.apache.org/en/9.0.x/admin-guide/files/logging.yaml.en.html
+#
+# Example log configurations:
+# https://docs.trafficserver.apache.org/en/9.0.x/admin-guide/logging/examples.en.html
+
+
+logging:
+ formats:
+ # WebTrends Enhanced Log Format.
+ #
+ # The following is compatible with the WebTrends Enhanced Log Format.
+ # If you want to generate a log that can be parsed by WebTrends
+ # reporting tools, simply create a log that uses this format.
+ - name: welf
+ format: |-
+ id=firewall time="%<cqtd> %<cqtt>" fw=%<phn> pri=6 proto=%<cqus> duration=%<ttmsf> sent=%<psql> rcvd=%<cqhl> src=%<chi> dst=%<shi> dstname=%<shn> user=%<caun> op=%<cqhm> arg="%<cqup>" result=%<pssc> ref="%<{Referer}cqh>" agent="%<{user-agent}cqh>" cache=%<crc>
+ # Squid Log Format with seconds resolution timestamp.
+ # The following is the squid format but with a seconds-only timestamp
+ # (cqts) instead of a seconds and milliseconds timestamp (cqtq).
+ - name: squid_seconds_only_timestamp
+ format: '%<cqts> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<cquc> %<caun> %<phr>/%<shn> %<psct>'
+
+ # Squid Log Format.
+ - name: squid
+ format: '%<cqtq> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<cquc> %<caun> %<phr>/%<shn> %<psct>'
+
+ - name: aviatrix
+ format: 'ts=%<cqtq> ttms=%<ttms> sni=%<cssn> meth=%<cqhm> cqpv=%<cqpv> pssc=%<pssc> cqtr=%<cqtr> cqssl=%<cqssl> chi=%<chi> cqql=%<cqql> shi=%<shi> shn=%<shn> psql=%<psql> sstc=%<sstc> cquc=%<cquc[:512]>'
+
+ logs:
+ - filename: access
+ format: aviatrix
+ mode: text
+
+# vim: set ft=yaml :
diff --git a/aviatrix/10_0_0.0001/configs/plugin.config b/aviatrix/10_0_0.0001/configs/plugin.config
new file mode 100644
index 0000000000..e4395bd082
--- /dev/null
+++ b/aviatrix/10_0_0.0001/configs/plugin.config
@@ -0,0 +1,15 @@
+#
+# plugin.config
+#
+# Documentation:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/plugin.config.en.html
+#
+# Comments start with a '#' and continue to the end of the line
+# Blank lines are ignored
+
+avx_policy_driver.so 5557 60
+#
+avx_tee_decrypt.so 127.255.0.10 127.255.0.20
+#
+#use certifier to automatically run with MITM
+avx_certifier.so --sign-cert=/opt/ats/ats_10.0.0/etc/local_ca/mitm_ca.cert --sign-key=/opt/ats/ats_10.0.0/etc/local_ca/mitm_ca.key --sign-serial=/opt/ats/ats_10.0.0/var/local_ca/serial.txt --store=/opt/ats/ats_10.0.0/var/local_ca/keys --max=200
diff --git a/aviatrix/10_0_0.0001/configs/records.yaml b/aviatrix/10_0_0.0001/configs/records.yaml
new file mode 100644
index 0000000000..9bb22a8a5f
--- /dev/null
+++ b/aviatrix/10_0_0.0001/configs/records.yaml
@@ -0,0 +1,218 @@
+##############################################################################
+# *NOTE*: All options covered in this file should be documented in the docs:
+#
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html
+##############################################################################
+
+ts:
+ accept_threads: 1
+ cache:
+ limits:
+ http:
+
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#proxy-config-cache-limits-http-max-alts
+ max_alts: 5
+ log:
+ alternate:
+
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#proxy-config-cache-log-alternate-eviction
+ eviction: 0
+
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#proxy-config-cache-max-doc-size
+ max_doc_size: 0
+ min_average_object_size: 8000
+
+##############################################################################
+# RAM and disk cache configurations. Docs:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#ram-cache
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/storage.config.en.html
+##############################################################################
+ ram_cache:
+ size: -1
+ ram_cache_cutoff: 4194304
+ threads_per_disk: 8
+##############################################################################
+# Debugging. Docs:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#diagnostic-logging-configuration
+##############################################################################
+ diags:
+ debug:
+ enabled: 0
+ tags: http|dns
+ output:
+ debug: EL
+ status: EL
+ note: EL
+ warning: SEL
+ error: SEL
+ fatal: SEL
+
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#proxy-config-dump-mem-info-frequency
+ dump_mem_info_frequency: 0
+
+##############################################################################
+# Thread configurations. Docs:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#thread-variables
+##############################################################################
+ exec_thread:
+ affinity: 1
+ autoconfig:
+ enabled: 1
+ scale: 1.0
+ limit: 2
+ http:
+ accept_no_activity_timeout: 120
+ attach_server_session_to_client: 1
+ cache:
+
+##############################################################################
+# Enable / disable HTTP caching. Useful for testing, but also as an
+# overridable (per remap) config
+##############################################################################
+ http: 0
+
+##############################################################################
+# Origin server connect attempts. Docs:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#origin-server-connect-attempts
+##############################################################################
+ connect_attempts_max_retries: 0
+ connect_attempts_max_retries_down_server: 1
+ connect_attempts_rr_retries: 3
+ connect_attempts_timeout: 30
+ down_server:
+ cache_time: 60
+ forward:
+ proxy_auth_to_parent: 0
+
+##############################################################################
+# Proxy users variables. Docs:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#proxy-user-variables
+##############################################################################
+ insert_client_ip: 0
+
+##############################################################################
+# Via: headers. Docs:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#proxy-config-http-insert-response-via-str
+##############################################################################
+ insert_request_via_str: 1
+ insert_response_via_str: 0
+ insert_squid_x_forwarded_for: 1
+ keep_alive_no_activity_timeout_in: 120
+ keep_alive_no_activity_timeout_out: 120
+
+##############################################################################
+# Negative response caching, for redirects and errors. Docs:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#negative-response-caching
+##############################################################################
+ negative_caching_enabled: 0
+ negative_caching_lifetime: 1800
+ normalize_ae: 1
+
+##############################################################################
+# Parent proxy configuration, in addition to these settings also see parent.config. Docs:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#parent-proxy-configuration
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/parent.config.en.html
+##############################################################################
+ parent_proxy:
+ retry_time: 300
+
+##############################################################################
+# Security. Docs:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#security
+##############################################################################
+ push_method_enabled: 0
+
+##############################################################################
+# Specify server addresses and ports to bind for HTTP and HTTPS. Docs:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#proxy.config.http.server_ports
+##############################################################################
+ server_ports: 8443:ssl:tr-full:allow-plain
+
+ server_session_sharing:
+ match: none
+
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#proxy-config-http-slow-log-threshold
+ slow:
+ log:
+ threshold: 0
+
+##############################################################################
+# HTTP connection timeouts (secs). Docs:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#http-connection-timeouts
+##############################################################################
+ transaction_active_timeout_in: 0
+ transaction_active_timeout_out: 0
+ # It seems that the transaction timeout is used for SNI tunneling as well
+ transaction_no_activity_timeout_in: 1200 # 20 minutes
+ transaction_no_activity_timeout_out: 1200
+ uncacheable_requests_bypass_parent: 1
+ use_client_target_addr: 2
+
+##############################################################################
+# Logging Config. Docs:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#logging-configuration
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/logging.yaml.en.html
+##############################################################################
+ log:
+ auto_delete_rolled_files: 1
+ logging_enabled: 3
+ logfile_dir: /var/log/tslogs
+ max_space_mb_for_logs: 25000
+ max_space_mb_headroom: 1000
+ periodic_tasks_interval: 5
+ rolling_enabled: 0
+ #rolling_interval_sec: 86400
+ #rolling_size_mb: 10
+
+##############################################################################
+# Network. Docs:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#network
+##############################################################################
+ net:
+ connections_throttle: 30000
+ default_inactivity_timeout: 86400
+ max_connections_in: 30000
+ max_requests_in: 0
+
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#proxy-config-res-track-memory
+ res_track_memory: 0
+
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#reverse-proxy
+ reverse_proxy:
+ enabled: 0
+ ssl:
+ client:
+ CA:
+ cert:
+ filename: mitm-trustbundle.crt
+ path: certs
+
+##############################################################################
+# SSL Termination. Docs:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#client-related-configuration
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/ssl_multicert.config.en.html
+##############################################################################
+ verify:
+ server:
+ policy: PERMISSIVE
+ properties: SIGNATURE
+ server:
+ private_key:
+ path: ssl
+ cert:
+ path: ssl
+
+ syslog_facility: LOG_LOCAL3
+
+ task_threads: 2
+
+##############################################################################
+# These settings control remapping, and if the proxy allows (open) forward proxy or not. Docs:
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#url-remap-rules
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/remap.config.en.html
+##############################################################################
+ url_remap:
+
+# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#proxy-config-url-remap-pristine-host-hdr
+ pristine_host_hdr: 0
+ remap_required: 0
diff --git a/aviatrix/10_0_0.0001/control b/aviatrix/10_0_0.0001/control
new file mode 100644
index 0000000000..6b91ce5eb3
--- /dev/null
+++ b/aviatrix/10_0_0.0001/control
@@ -0,0 +1,6 @@
+Package: ats
+Version: 10.0.0
+Architecture: amd64
+Maintainer: Aviatrix Engineering <Av...@aviatrix.com>
+Description: Aviatrix variant of Apache TrafficServer
+Depends: libunwind8,libnuma1,libcap2-bin
diff --git a/aviatrix/10_0_0.0001/logrotate/avx-gw-trafficserver b/aviatrix/10_0_0.0001/logrotate/avx-gw-trafficserver
new file mode 100644
index 0000000000..ff97959ddc
--- /dev/null
+++ b/aviatrix/10_0_0.0001/logrotate/avx-gw-trafficserver
@@ -0,0 +1,11 @@
+/var/log/tslogs/access.log /var/log/tslogs/diags.log {
+ su ats-user ats-user
+ rotate 12
+ daily
+ size 5M
+ missingok
+ notifempty
+ compress
+ copytruncate
+}
+
diff --git a/aviatrix/10_0_0.0001/postinst b/aviatrix/10_0_0.0001/postinst
new file mode 100755
index 0000000000..9e7e43075b
--- /dev/null
+++ b/aviatrix/10_0_0.0001/postinst
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+atsdir='/opt/ats/ats_10.0.0'
+ats_common_dir='/opt/ats/ats'
+
+#creating log directory and setting permissions
+test -e /var/log/tslogs || mkdir -p /var/log/tslogs
+useradd --shell /usr/sbin/nologin ats-user
+chown ats-user:ats-user /var/log/tslogs
+chmod 775 /var/log/tslogs
+
+#creating trustbundle directory
+mkdir -p $atsdir/certs
+
+#fixing owners and setting permissions
+chown -R ats-user:ats-user $atsdir
+chown root:ats-user $atsdir/etc/local_ca
+chmod 750 $atsdir/etc/local_ca
+chown -R root:ats-user $atsdir/var/local_ca
+chmod -R 770 $atsdir/var/local_ca
+
+#fixing permissions for logrotate conf
+chown root:root /etc/logrotate.d/avx-gw-trafficserver
+chmod 644 /etc/logrotate.d/avx-gw-trafficserver
+
+setcap cap_net_admin,cap_net_raw+eip $atsdir/bin/traffic_server
+
+# Set a symlink to the canonical instal directory
+rm -rf $ats_common_dir
+ln -s $atsdir $ats_common_dir
diff --git a/aviatrix/build/.gitignore b/aviatrix/build/.gitignore
new file mode 100644
index 0000000000..d9b5b0e6a6
--- /dev/null
+++ b/aviatrix/build/.gitignore
@@ -0,0 +1,3 @@
+debs
+temp
+logs
diff --git a/aviatrix/build/docker_scripts/env_command.sh b/aviatrix/build/docker_scripts/env_command.sh
new file mode 100644
index 0000000000..548635859d
--- /dev/null
+++ b/aviatrix/build/docker_scripts/env_command.sh
@@ -0,0 +1,179 @@
+set -xeuo
+
+export OPENSSL=/opt/ssl/openssl_1.1.1
+export DEB=ats_9.1.3
+
+
+arg3=${3:-}
+arg4=${4:-}
+arg5=${5:-}
+
+if [ -z $1 ]; then
+ echo script needs environment
+ exit
+fi
+environment=$1
+if [ -z $2 ]; then
+ exit
+fi
+command=`echo $2 | grep -E '^build|plugins|clean$'`
+
+
+if [ -z "$command" ]; then
+ echo use clean, build, plugins
+ exit
+fi
+
+
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+
+source $SCRIPT_DIR/setup_$environment.sh
+
+if [ "$command" == "plugins" ] && [ ! -e "$ats_install_path/bin" ]; then
+ command="build"
+fi
+
+if [ $2 == "build" ]; then
+ command=build
+ if [ ! -e "/app/build/$environment/thirdparty-trafficserver" ]; then
+ command=clean
+ fi
+fi
+
+
+
+if [ $2 == "clean" ]; then
+ command=$2
+fi
+echo Command: $command
+
+
+if [ "$ats_install_path" == "" ]; then
+ echo bailing, no ats install path
+fi
+
+echo $ats_install_path
+
+mkdir -p /app/build/$environment/thirdparty-trafficserver
+cd /app/build/$environment/thirdparty-trafficserver
+if [ -e "/app/build/$environment/cloudn" ]; then
+ unlink "/app/build/$environment/cloudn"
+fi
+ln -s /app_data/cloudn /app/build/$environment/cloudn 2>/dev/null
+if [ "$command" == "clean" ]; then
+ echo cleaning up
+ rm -rf *
+fi
+
+echo syncing
+tar xf /app/source/copy-for-docker.tgz
+
+
+if [ $2 == 'shell' ]; then
+ exit
+fi
+
+cd /app/build/$environment/thirdparty-trafficserver/plugins/experimental/policy_driver
+make -f Makefile-proto
+cd /app/build/$environment/thirdparty-trafficserver
+
+if [ $command == "shell" ]; then
+ cd "/app/build/$1/thirdparty-trafficserver/plugins/"
+ bash
+ exit
+fi
+
+if [ $command == "clean" ]; then
+
+ autoreconf -if
+ ./configure $(echo $configure_with)
+
+fi
+
+
+PKG_CONFIG_PATH=/root/.local/lib/pkgconfig make -j 8
+current_install=""
+if [ ! -e "$ats_install_path/environment"]; then
+ current_install=`cat $ats_install_path/environment`
+fi
+echo updating ats_install_path
+echo $command
+echo $current_install
+if [ "$command" == "clean" ] || [ "$current_install" != "$1" ]; then
+ echo deleting $ats_install_path for $1 $2
+ # our current install was not same environment, need a complete build
+ if [ "$command" == "plugins" ]; then
+ command="build"
+ fi
+ if [ "$ats_install_path" != "" ]; then
+ rm -rf $ats_install_path/*
+ fi
+fi
+
+mkdir -p $ats_install_path/lib/
+cp /usr/lib/x86_64-linux-gnu/libhwloc.so.15 $ats_install_path/lib/
+cp /usr/lib/x86_64-linux-gnu/libhwloc.so.15.5.2 $ats_install_path/lib/
+ln -sf $ats_install_path/lib/libhwloc.so.15.5.2 $ats_install_path/lib/libhwloc.so.15
+
+
+printf $1 > $ats_install_path/environment
+cp /app/build/$environment/thirdparty-trafficserver/avx-manifest $ats_install_path/ats_9.1.3.deb.manifest
+if [ "$command" == "plugins" ]; then
+ pushd plugins
+ PKG_CONFIG_PATH=/root/.local/lib/pkgconfig make -j 8
+ PKG_CONFIG_PATH=/root/.local/lib/pkgconfig make -j 8 install
+ popd
+else
+ PKG_CONFIG_PATH=/root/.local/lib/pkgconfig make -j 8
+ PKG_CONFIG_PATH=/root/.local/lib/pkgconfig make -j 8 install
+ if [ "$1" == "debug" ] || [ "$arg3" == "test_client" ] || [ "$arg4" == "test_client" ] || [ "$arg5" == "test_client" ] ; then
+ cp /app/build/$environment/thirdparty-trafficserver/plugins/experimental/policy_driver/test_client $ats_install_path/bin
+ cp /app/build/$environment/thirdparty-trafficserver/plugins/experimental/policy_driver/test_server $ats_install_path/bin
+ fi
+fi
+
+
+if [ "$build_deb" == "true" ]; then
+ mkdir -p /build
+ cd /build
+
+
+ mkdir $DEB && mkdir -p $DEB/opt/ats && mkdir -p $DEB/lib/systemd/system && mkdir -p $DEB/etc/logrotate.d && mkdir $DEB/DEBIAN && cd $DEB/DEBIAN && \
+ printf "package: ats\nversion: 9.1.3\nmaintainer: Kasun\narchitecture: all\ndescription: testing it\n" > control
+ mkdir -p /build/$DEB/lib/systemd/system
+ mkdir -p /build/$DEB/etc/
+ mkdir -p /build/$DEB/DEBIAN
+
+ cp -r /app/source/aviatrix/9_1_3.0001/configs/* ${ats_install_path}/etc/trafficserver
+ cp /app/source/aviatrix/9_1_3.0001/avx-gw-trafficserver.service /build/$DEB/lib/systemd/system
+ cp /app/source/aviatrix/9_1_3.0001/logrotate/avx-gw-trafficserver /build/$DEB/etc/logrotate.d
+ cp /app/source/aviatrix/9_1_3.0001/postinst /build/$DEB/DEBIAN
+ mkdir -p $ats_install_path/etc/local_ca
+ mkdir -p $ats_install_path/var/local_ca/keys
+ echo 12345 > $ats_install_path/var/local_ca/serial.txt
+ mkdir -p /build/$DEB/opt/ats/
+
+ cp -R $ats_install_path /build/$DEB/opt/ats/
+ chown root:root /build/$DEB/DEBIAN/postinst && chmod 0775 /build/$DEB/DEBIAN/postinst
+ cd /build/$DEB
+ tar cf ../plugin_ats_9.1.3.tgz opt/ats/ats_9.1.3/libexec/trafficserver/avx_certifier.* opt/ats/ats_9.1.3/libexec/trafficserver/policy_driver.*
+ cp /build/plugin_ats_9.1.3.tgz /debs
+ tar cf ../ats_9.1.3.tgz opt/ats/ats_9.1.3/libexec/* opt/ats/ats_9.1.3/bin/* opt/ats/ats_9.1.3/lib/*
+ cp /build/ats_9.1.3.tgz /debs
+ cp /app/build/$environment/thirdparty-trafficserver/avx-manifest /debs/ats_9.1.3.deb.manifest
+ cd /build
+
+
+ if [ "$arg3" != "nodeb" ]; then
+ time dpkg-deb --build $DEB
+ cp /build/ats_9.1.3.deb /debs
+ fi
+fi
+
+
+
+if [ "$arg3" == "shell" ] || [ "$arg4" == "shell" ] || [ "$arg5" == "shell" ]; then
+ cd /app/build/release/thirdparty-trafficserver/plugins/
+ bash
+fi
diff --git a/aviatrix/build/docker_scripts/setup_debug.sh b/aviatrix/build/docker_scripts/setup_debug.sh
new file mode 100644
index 0000000000..a14400d794
--- /dev/null
+++ b/aviatrix/build/docker_scripts/setup_debug.sh
@@ -0,0 +1,3 @@
+export ats_install_path=/opt/ats/ats_9.1.3
+export configure_with="--with-user=ubuntu --enable-debug --with-openssl=$OPENSSL --enable-tproxy --prefix=$ats_install_path --disable-dependency-checking --enable-example-plugins --enable-experimental-plugins"
+export build_deb=true
diff --git a/aviatrix/build/docker_scripts/setup_local.sh b/aviatrix/build/docker_scripts/setup_local.sh
new file mode 100644
index 0000000000..800a3c1100
--- /dev/null
+++ b/aviatrix/build/docker_scripts/setup_local.sh
@@ -0,0 +1,94 @@
+#!/bin/bash
+export ats_install_path=/opt/ats/ats_local_debug
+export configure_with="--with-user=ubuntu --enable-debug --with-openssl=$OPENSSL --enable-tproxy --prefix=$ats_install_path --disable-dependency-checking --enable-example-plugins --enable-experimental-plugins"
+export build_deb=false
+
+function apply_netmask {
+ # Parse IP and netmask from input
+ ip=$(echo "$1" | cut -d/ -f1)
+ netmask=$(echo "$1" | cut -d/ -f2)
+
+ # Convert netmask to bitmask
+ bitmask=$((0xffffffff << (32 - netmask)))
+
+ # Convert IP address to integer
+ IFS='.' read -r i1 i2 i3 i4 <<< "$ip"
+ int=$((i1 * 256 ** 3 + i2 * 256 ** 2 + i3 * 256 + i4))
+
+ # Apply bitmask to integer
+ masked_int=$((int & bitmask))
+
+ # Convert masked integer back to IP address
+ masked_ip=$(printf "%d.%d.%d.%d" \
+ $((masked_int >> 24)) \
+ $((masked_int >> 16 & 255)) \
+ $((masked_int >> 8 & 255)) \
+ $((masked_int & 255)))
+
+ # Return masked IP address
+ echo "$masked_ip/$netmask"
+}
+
+
+function setupnet {
+ local ns=$1
+ local veth1=$2
+ local veth2=$4
+ local ip1=$3
+ local ip2=$5
+ local cleanip1=$(printf $3 | sed -r 's#(.*)/.*#\1#')
+ local cleanip2=$(printf $4 | sed -r 's#(.*)/.*#\1#')
+ local cleanroutenetmask=$(apply_netmask $3)
+ echo $ns $veth1 $veth2 $ip1 $ip2 $cleanip1 $cleanip2
+ # create network namespace
+ ip netns add $ns
+ # create interfaces
+ ip link add $veth1 type veth peer name $veth121
+ # set outside interface ip address
+ ip addr add $ip1 dev $veth1
+ # up outside facing interface
+ ip link set $veth1 up
+ # move inner interface to network namespace
+ ip link set $veth2 netns $ns
+ # set ip address
+ ip netns exec $ns ip addr add $ip2 dev $veth2
+ # ip inner interface
+ ip netns exec $ns ip link set $veth2 up
+ # add the route for the inner network
+ ip route add $cleanroutenetmask dev $veth1
+ # add the default route for the network namespace to the outside
+ ip netns exec $ns ip route add default via $cleanip1 dev $veth2 onlink
+
+}
+
+setupnet "net1011" "veth111" "10.11.0.1/24" "veth112" "10.11.0.2/24"
+setupnet "net1012" "veth121" "10.12.0.1/24" "veth122" "10.12.0.2/24"
+
+
+
+
+echo '
+escape ^Xa
+# skip the startup message
+startupmessage off
+
+# Display a caption string below, appearing like tabs and
+# displaying the window number and application name (by default).
+caption always
+caption string "Use ctrl-x as screen key %{kw}%-w%{wr}%n %t%{-}%+w"
+#
+# j to move down a window, k to move up. Like teh VIM!
+bind j focus down
+bind k focus up
+bind > focus next
+bind < focus prev
+#
+# Default screens
+screen -t main
+screen -t net1011
+screen -t net1012 ls ; bash
+screen -t nginx1012
+#
+# Select first screen
+select 0
+' > ~/.screenrc
\ No newline at end of file
diff --git a/aviatrix/build/docker_scripts/setup_release.sh b/aviatrix/build/docker_scripts/setup_release.sh
new file mode 100644
index 0000000000..1f1760bf67
--- /dev/null
+++ b/aviatrix/build/docker_scripts/setup_release.sh
@@ -0,0 +1,3 @@
+export ats_install_path=/opt/ats/ats_9.1.3
+export configure_with="--with-user=ubuntu --with-openssl=$OPENSSL --enable-tproxy --prefix=$ats_install_path --disable-dependency-checking --enable-example-plugins --enable-experimental-plugins"
+export build_deb=true
diff --git a/aviatrix/build/dockerfile-atsbuild b/aviatrix/build/dockerfile-atsbuild
new file mode 100644
index 0000000000..bba6f2387c
--- /dev/null
+++ b/aviatrix/build/dockerfile-atsbuild
@@ -0,0 +1,116 @@
+
+FROM --platform=linux/amd64 ubuntu:22.04 AS build-stage
+
+
+ENV PATH="/root/.local/bin:${PATH}"
+ENV PKG_CONFIG_PATH="/root/.local/lib/pkgconfig"
+
+SHELL ["/bin/bash", "-c"]
+
+#RUN --mount=type=bind,source=/home/ubuntu/dockerdir/target,target=/app
+
+#ADD ./target /app
+
+RUN useradd -ms /bin/bash -g root -G sudo ubuntu
+
+WORKDIR /build
+
+ENV DEB=ats_9.1.3
+
+
+ENV ATSPATH_SOURCE=.
+ENV ATSPATH=/opt/ats/ats_9.1.3
+
+ENV ARTIFACTS_SOURCE=aviatrix/build
+ENV ARTIFACTS=/app/build/
+
+ENV OPENSSL=/opt/ssl/openssl_1.1.1
+
+#RUN ATSPATH=/opt/ats/ats_9.1.3 && DEB=ats
+
+#RUN echo ats_path > check.txt && echo deb_name >> check.txt
+
+RUN mkdir $DEB && mkdir -p $DEB/opt/ats && mkdir -p $DEB/lib/systemd/system && mkdir -p $DEB/etc/logrotate.d && mkdir $DEB/DEBIAN && cd $DEB/DEBIAN && \
+ printf "package: ats\nversion: 9.1.3\nmaintainer: Kasun\narchitecture: all\ndescription: testing it\n" > control
+
+#USER root
+
+RUN rm -f /etc/apt/apt.conf.d/docker-clean
+RUN echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > \
+ /etc/apt/apt.conf.d/keep-cache
+
+RUN --mount=type=cache,target=/var/cache/apt \
+ apt update && \
+ apt install -y autoconf \
+ automake \
+ libtool \
+ pkg-config \
+ libmodule-install-perl \
+ gcc \
+ g++ \
+ zlib1g-dev \
+ libssl-dev \
+ libcap-dev \
+ libhwloc-dev \
+ libncurses5-dev \
+ libcurl4-openssl-dev \
+ flex \
+ make \
+ patchelf
+
+#USER root
+RUN --mount=type=bind,rw,source=.,target=/app/thirdparty-trafficserver \
+ --mount=type=bind,rw,source=aviatrix/build/debs,target=/app/build \
+ cd / ; \
+ tar xvzf /app/build/buildtools.tgz && \
+ echo $PATH
+RUN echo $PKG_CONFIG_PATH
+RUN ls /root/.local/lib/pkgconfig
+
+RUN --mount=type=bind,rw,source=.,target=/app/thirdparty-trafficserver \
+ --mount=type=bind,rw,source=aviatrix/build/temp/cloudn,target=/app/cloudn \
+ --mount=type=bind,rw,source=aviatrix/build/debs,target=/app/build \
+ cd /app/thirdparty-trafficserver/plugins/experimental/policy_driver && \
+ make -f Makefile-proto && \
+ cd /app/thirdparty-trafficserver && \
+ dpkg -i /app/build/openssl_1.1.1.deb && \
+ echo $PATH && \
+ patchelf --set-rpath $OPENSSL/lib $OPENSSL/bin/openssl && \
+ autoreconf -if && \
+ ./configure --with-user=ubuntu --with-openssl=$OPENSSL --enable-tproxy --prefix=$ATSPATH --disable-dependency-checking --enable-example-plugins --enable-experimental-plugins && \
+ PKG_CONFIG_PATH=/root/.local/lib/pkgconfig make -j 8 install && \
+ cp -r aviatrix/9_1_3.0001/configs/* $ATSPATH/etc/trafficserver && \
+ cp aviatrix/9_1_3.0001/avx-gw-trafficserver.service /build/$DEB/lib/systemd/system && \
+ cp aviatrix/9_1_3.0001/logrotate/avx-gw-trafficserver /build/$DEB/etc/logrotate.d
+
+#remounting here so we won't have to wait for trafficserver compilation to add changes to post install script
+RUN --mount=type=bind,rw,source=.,target=/app/thirdparty-trafficserver \
+ --mount=type=bind,rw,source=aviatrix/build/debs,target=/app/build \
+ cd /app/thirdparty-trafficserver && \
+ cp aviatrix/9_1_3.0001/postinst /build/$DEB/DEBIAN
+
+RUN cp /usr/lib/x86_64-linux-gnu/libhwloc.so.5 $ATSPATH/lib/ && cp /usr/lib/x86_64-linux-gnu/libhwloc.so.5.7.6 $ATSPATH/lib/ && ln -sf $ATSPATH/lib/libhwloc.so.5.7.6 $ATSPATH/lib/libhwloc.so.5
+
+RUN mkdir $ATSPATH/etc/local_ca && mkdir -p $ATSPATH/var/local_ca/keys && echo 12345 > $ATSPATH/var/local_ca/serial.txt
+
+#RUN mkdir -p /build/ats/opt/ats/ && mv /opt/ts_run /opt/ats_9.1.3 && mv /opt/ats_9.1.3 /build/ats/opt/ats/ats_9.1.3
+
+RUN mv $ATSPATH /build/$DEB/opt/ats/
+
+RUN chown root:root /build/$DEB/DEBIAN/postinst && chmod 0775 /build/$DEB/DEBIAN/postinst
+
+WORKDIR /build
+
+RUN dpkg-deb --build $DEB
+
+#RUN ls /usr/lib/x86_64-linux-gnu | grep libhwloc > check.txt
+
+#RUN cat /app/target/tosound > temp
+
+#RUN ls /app >> temp
+
+FROM scratch AS export-stage
+
+COPY --from=build-stage /build/ats_9.1.3.deb .
+
+#COPY --from=build-stage /build/check.txt /build
diff --git a/aviatrix/build/dockerfile-atsbuildv2 b/aviatrix/build/dockerfile-atsbuildv2
new file mode 100644
index 0000000000..7f0f740c9a
--- /dev/null
+++ b/aviatrix/build/dockerfile-atsbuildv2
@@ -0,0 +1,61 @@
+FROM --platform=linux/amd64 ubuntu:22.04 AS build-stage
+ENV PATH="/root/.local/bin:${PATH}"
+ENV PKG_CONFIG_PATH="/root/.local/lib/pkgconfig"
+ENV ATSPATH_SOURCE=.
+ENV ATSPATH=/opt/ats/ats_9.1.3
+ENV DEB=ats_9.1.3
+
+ENV ARTIFACTS_SOURCE=aviatrix/build
+ENV ARTIFACTS=/app/build/
+
+ENV OPENSSL=/opt/ssl/openssl_1.1.1
+
+RUN echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > \
+ /etc/apt/apt.conf.d/keep-cache
+
+RUN \
+ apt update && \
+ apt install -y autoconf \
+ automake \
+ libtool \
+ pkg-config \
+ libmodule-install-perl \
+ gcc \
+ g++ \
+ zlib1g-dev \
+ libssl-dev \
+ libcap-dev \
+ libhwloc-dev \
+ libncurses5-dev \
+ libcurl4-openssl-dev \
+ flex \
+ make \
+ patchelf \
+ iproute2 \
+ less \
+ inetutils-ping \
+ screen \
+ nginx \
+ curl \
+ wget \
+ git
+
+RUN --mount=type=bind,rw,source=.,target=/app/thirdparty-trafficserver \
+ --mount=type=bind,rw,source=aviatrix/build/u22/debs,target=/app/build \
+ cd / ; \
+ tar xvzf /app/build/buildtools.tgz
+
+RUN --mount=type=bind,rw,source=.,target=/app/thirdparty-trafficserver \
+ --mount=type=bind,rw,source=aviatrix/build/u22/debs,target=/app/build \
+ dpkg -i /app/build/openssl_1.1.1.deb && \
+ echo $PATH && \
+ patchelf --set-rpath $OPENSSL/lib $OPENSSL/bin/openssl
+
+SHELL ["/bin/bash", "-c"]
+RUN echo > /test
+RUN useradd -ms /bin/bash -g root -G sudo ubuntu
+RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/app/build/.bash_history" \
+ && echo "$SNIPPET" >> "/root/.bashrc"
+
+
+CMD ["/bin/bash"]
diff --git a/aviatrix/build/dockerfile-buildtools b/aviatrix/build/dockerfile-buildtools
new file mode 100644
index 0000000000..0a7c0651db
--- /dev/null
+++ b/aviatrix/build/dockerfile-buildtools
@@ -0,0 +1,37 @@
+
+FROM --platform=linux/amd64 ubuntu:22.04 AS build-stage
+SHELL ["/bin/bash", "-c"]
+
+WORKDIR /build
+
+
+
+ENV PATH="/root/.local/bin:${PATH}"
+ENV PKG_CONFIG_PATH="/root/.local/lib/pkgconfig"
+ENV libswoc_version=1.5.1
+ENV random=1
+
+
+ENV DIR=
+
+RUN --mount=type=cache,target=/var/cache/apt \
+ apt update && \
+ apt install -y make git g++ gcc sudo zlib1g-dev build-essential autoconf libtool pkg-config lsb-release
+
+COPY aviatrix/build/setup_build_tools*.sh /build
+
+
+RUN --mount=type=cache,target=/var/cache/apt \
+ /build/setup_build_tools_1.sh /build
+
+RUN /build/setup_build_tools_2.sh /build
+
+RUN ls -l /root/.local/bin
+
+WORKDIR /build
+
+RUN tar cvfz /build/buildtools.tgz /root/.local
+
+FROM scratch AS export-stage
+
+COPY --from=build-stage /build/buildtools.tgz .
\ No newline at end of file
diff --git a/aviatrix/build/dockerfile-openssl b/aviatrix/build/dockerfile-openssl
new file mode 100644
index 0000000000..8d088bec9b
--- /dev/null
+++ b/aviatrix/build/dockerfile-openssl
@@ -0,0 +1,51 @@
+
+
+FROM --platform=linux/amd64 ubuntu:22.04 AS build-stage
+
+#RUN --mount=type=bind,source=/home/ubuntu/dockerdir/target,target=/app
+
+#ADD ./target /app
+
+WORKDIR /build
+
+ENV DIR=/opt/ssl/openssl_1.1.1
+
+ENV DEB=openssl_1.1.1
+
+RUN mkdir $DEB && mkdir -p $DEB/opt/ssl && mkdir $DEB/DEBIAN && cd $DEB/DEBIAN && \
+ printf "package: openssldeb\nversion: 1.1.1\nmaintainer: Kasun\narchitecture: all\ndescription: Open SSL 1.1.1\nupstream_version:1.1.1t\n" > control
+
+#RUN echo 'check line' > here.txt
+
+RUN apt update && apt install -y \
+ curl \
+ perl \
+ ca-certificates \
+ make \
+ gcc \
+ patchelf \
+ git
+
+#RUN curl -L https://cpanmin.us/ -o cpanm
+
+#RUN chmod +x cpanm
+
+#RUN --mount=type=bind,source=target,target=/app cp /app/cpanm /usr/local/bin/cpanm
+
+RUN git clone -b OpenSSL_1_1_1t https://github.com/openssl/openssl /app/openssl
+
+#WORKDIR /app/openssl
+
+RUN /app/openssl/config --prefix=$DIR --openssldir=$DIR && make -j4 && make -j4 test && make install_sw
+
+RUN patchelf --set-rpath $DIR/lib $DIR/bin/openssl
+
+RUN mv $DIR /build/$DEB$DIR
+
+WORKDIR /build
+
+RUN dpkg-deb --build $DEB
+
+FROM scratch AS export-stage
+
+COPY --from=build-stage /build/openssl_1.1.1.deb .
\ No newline at end of file
diff --git a/aviatrix/build/makeprotos.sh b/aviatrix/build/makeprotos.sh
new file mode 100644
index 0000000000..e1ad14bfeb
--- /dev/null
+++ b/aviatrix/build/makeprotos.sh
@@ -0,0 +1,3 @@
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+cd $SCRIPT_DIR/../../plugins/experimental/policy_driver && \
+ make -f Makefile-proto
\ No newline at end of file
diff --git a/aviatrix/build/setup_build_tools.sh b/aviatrix/build/setup_build_tools.sh
new file mode 100755
index 0000000000..d2311649c2
--- /dev/null
+++ b/aviatrix/build/setup_build_tools.sh
@@ -0,0 +1,21 @@
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+BUILD_DIR=${SCRIPT_DIR}/../../../trafficserver-buildtools
+
+path_has_local_bin=`echo $PATH|grep -E "/\.local/bin"`
+echo $path_has_local_bin
+has_error=''
+if [ ! -e ~/.local/bin ]; then
+ echo Creating "\"\$HOME/.local/bin\""
+ mkdir -p ~/.local/bin
+ has_error=true
+fi
+if [ -z "$path_has_local_bin" ]; then
+ export PATH=~/.local/bin/:$PATH
+fi
+
+export PKG_CONFIG_PATH=~/.local/lib/pkgconfig
+
+#get the stuff
+$SCRIPT_DIR/setup_build_tools_1.sh $BUILD_DIR
+#build the stuff
+$SCRIPT_DIR/setup_build_tools_2.sh $BUILD_DIR
diff --git a/aviatrix/build/setup_build_tools_1.sh b/aviatrix/build/setup_build_tools_1.sh
new file mode 100755
index 0000000000..cbfb956248
--- /dev/null
+++ b/aviatrix/build/setup_build_tools_1.sh
@@ -0,0 +1,66 @@
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+BUILD_DIR=${SCRIPT_DIR}/../../../trafficserver-buildtools
+if [ ! -z $1 ]; then
+ BUILD_DIR=$1
+fi
+mkdir -p $BUILD_DIR
+is_ubuntu18=`lsb_release -a|grep "Ubuntu 18.04"`
+if [ ! -z "$is_ubuntu18" ]; then
+ sudo apt install wget
+ cd /tmp
+ mkdir cmakedownload
+ cd cmakedownload
+ wget wget https://github.com/Kitware/CMake/releases/download/v3.25.3/cmake-3.25.3-linux-x86_64.tar.gz
+ tar xvzf cmake-3.25.3-linux-x86_64.tar.gz
+ cd cmake-3.25.3-linux-x86_64
+ mkdir -p ~/.local/
+ cp -r * ~/.local/
+ ls ~/.local/bin
+ if [ ! -d make-4.3 ]; then
+ cd $BUILD_DIR
+
+ wget https://ftp.gnu.org/gnu/make/make-4.3.tar.gz
+ tar xfz make-4.3.tar.gz
+ cd make-4.3/
+ ./configure --prefix=/home/develop/.local
+ make install
+
+ fi
+fi
+cd $BUILD_DIR
+pwd
+MY_INSTALL_DIR=$HOME/.local
+sudo apt install -y build-essential autoconf libtool pkg-config cmake
+if [ ! -d grpc ]; then
+ echo 'grpc does not exist cloning'
+ mkdir -p $HOME/.local
+ git clone --recurse-submodules -b v1.48.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc
+ cd grpc
+ mkdir -p cmake/build
+ pushd cmake/build
+ cmake -DgRPC_INSTALL=ON \
+ -DgRPC_BUILD_TESTS=OFF \
+ -DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR \
+ ../..
+ popd
+fi
+cd $BUILD_DIR
+if [ ! -d libswoc ]; then
+ echo 'libswoc does not exist, cloning'
+ git clone https://github.com/SolidWallOfCode/libswoc
+ cd libswoc
+ # we should change this to a version
+ echo checking out libswoc ${libswoc_version}
+ git checkout ${libswoc_version}
+ git status
+ # don't build examples
+ echo > example/CMakeLists.txt
+ echo > unit_tests/CMakeLists.txt
+ mkdir -p cmake/build
+ pushd cmake/build
+
+ cmake -DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR \
+ ../..
+ popd
+fi
+
diff --git a/aviatrix/build/setup_build_tools_2.sh b/aviatrix/build/setup_build_tools_2.sh
new file mode 100755
index 0000000000..c45f51696d
--- /dev/null
+++ b/aviatrix/build/setup_build_tools_2.sh
@@ -0,0 +1,24 @@
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+BUILD_DIR=${SCRIPT_DIR}/../../../trafficserver-buildtools
+
+if [ ! -z $1 ]; then
+ BUILD_DIR=$1
+else
+ BUILD_DIR
+fi
+MY_INSTALL_DIR=$HOME/.local
+cd $BUILD_DIR
+pwd
+cd grpc
+pushd cmake/build
+ncpus=$(grep -E '^processor' /proc/cpuinfo | wc -l)
+make -j$ncpus
+make install
+popd
+pushd third_party/re2
+CPPFLAGS=-fPIC make -j$ncpus -e prefix=$MY_INSTALL_DIR static static-install
+popd
+cd $BUILD_DIR/libswoc/cmake/build
+c++ --version
+echo $libswoc_version
+make -j$ncpus -e VERBOSE=true --trace -e prefix=$MY_INSTALL_DIR install
\ No newline at end of file
diff --git a/aviatrix/dagger/.gitignore b/aviatrix/dagger/.gitignore
new file mode 100644
index 0000000000..5ebd4ed5b2
--- /dev/null
+++ b/aviatrix/dagger/.gitignore
@@ -0,0 +1,2 @@
+# Unignore Makefile :)
+!Makefile
diff --git a/aviatrix/dagger/Makefile b/aviatrix/dagger/Makefile
new file mode 100644
index 0000000000..13083a3143
--- /dev/null
+++ b/aviatrix/dagger/Makefile
@@ -0,0 +1,59 @@
+# This is the base Ubuntu version to use. The user may set this to
+# a different version.
+UBUNTU = "22.04"
+
+RUN := go run $(wildcard *.go)
+
+DEVEL_IMAGE := trafficserver-devel:$(UBUNTU)
+PACKAGING_IMAGE := trafficserver-packaging:$(UBUNTU)
+
+.PHONY: help
+help:
+ @echo "TrafficServer meta-build system"
+ @echo
+ @echo "TARGETS"
+ @echo
+ @echo " package Build the TrafficServer Ubuntu package"
+ @echo " build-image Build a base container image for development"
+ @echo " packaging-image Build a base container image for packaging"
+ @echo
+ @echo " unit-tests Run Traffic Server unit tests"
+ @echo " regression-tests Run Traffic Server regression tests"
+ @echo
+ @echo " shell Drop into a shell in the development image"
+ @echo " clean Remove build outputs"
+ @echo
+ @echo "VARIABLES"
+ @echo
+ @echo " UBUNTU Base Ubuntu version (default is $(UBUNTU))"
+
+.PHONY: package
+package:
+ $(RUN) $@ --ubuntu=$(UBUNTU)
+
+.PHONY: build-image
+build-image:
+ $(RUN) $@ --ubuntu=$(UBUNTU)
+
+.PHONY: packaging-image
+packaging-image:
+ $(RUN) $@ --ubuntu=$(UBUNTU)
+
+.PHONY: regression-tests
+regression-tests:
+ $(RUN) $@ --ubuntu=$(UBUNTU)
+
+.PHONY: unit-tests
+unit-tests:
+ $(RUN) $@ --ubuntu=$(UBUNTU)
+
+.PHONY: shell
+shell:
+ docker run -it --rm \
+ --volume $$(cd ../.. && pwd):/src/trafficserver \
+ --volume $$(cd ../../../cloudn && pwd):/src/cloudn \
+ $(DEVEL_IMAGE)
+
+.PHONY: clean
+clean:
+ $(RM) -r images packages
diff --git a/aviatrix/dagger/build.go b/aviatrix/dagger/build.go
new file mode 100644
index 0000000000..ea8c4e319c
--- /dev/null
+++ b/aviatrix/dagger/build.go
@@ -0,0 +1,594 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "os/exec"
+ "path"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "dagger.io/dagger"
+ "golang.org/x/exp/maps"
+)
+
+// DevInstallBase is the prefix where we install libraries and tools
+// that are needed to build Traffic Server. We use /opt/ats here because
+// the libraries will be included in the final Ubuntu package.
+const DevInstallBase = "/opt/ats"
+
+// TrafficServerInstallDir is the path TrafficServer is installed in.
+const TrafficServerInstallDir = "/opt/ats/ats_10.0.0"
+
+// writeExecScript wraps the given command-line in a trivial bash
+// script. This is useful for cases where we want to run something in
+// a build step, but also give developers the opportunity to run the
+// identical command later.
+func writeExecScript(cmd ...string) dagger.ContainerWithNewFileOpts {
+ b := strings.Builder{}
+
+ b.WriteString("#! /usr/bin/env bash\n\nexec \\\n")
+
+ for _, s := range cmd {
+ b.WriteString(s)
+ b.WriteString(" \\\n")
+ }
+
+ b.WriteString(`"$@"`)
+
+ return dagger.ContainerWithNewFileOpts{
+ Permissions: 0755,
+ Contents: b.String(),
+ }
+}
+
+// makeInstall runs "make" and then "make install". In makeArgs is
+// provided, these are appended to the "make" command.
+func makeInstall(makeArgs ...string) dagger.WithContainerFunc {
+ return func(container *dagger.Container) *dagger.Container {
+ return container.
+ WithExec([]string{"sh", "-c", "echo MAKEFLAGS=$MAKEFLAGS"}).
+ WithExec(append([]string{"make"}, makeArgs...)).
+ WithExec([]string{"make", "install"})
+ }
+}
+
+func aptUpdate() dagger.WithContainerFunc {
+ return func(container *dagger.Container) *dagger.Container {
+ cmd := []string{"env", "DEBIAN_FRONTEND=noninteractive",
+ "apt-get", "update", "--no-install-recommends", "--quiet", "--assume-yes",
+ }
+ return container.WithExec(cmd)
+ }
+}
+
+func aptInstall(packages ...string) dagger.WithContainerFunc {
+ return func(container *dagger.Container) *dagger.Container {
+ cmd := []string{"env", "DEBIAN_FRONTEND=noninteractive",
+ "apt-get", "install", "--no-install-recommends", "--assume-yes",
+ }
+
+ cmd = append(cmd, packages...)
+ return container.WithExec(cmd)
+ }
+}
+
+func withHostFiles(client *dagger.Client, files map[string]string) dagger.WithContainerFunc {
+ // Make a stable list of destination files so that we don't bust the cache.
+ targets := maps.Keys(files)
+ sort.Strings(targets)
+
+ return func(container *dagger.Container) *dagger.Container {
+ for _, t := range targets {
+ container = container.WithFile(t, client.Host().File(files[t]))
+ }
+ return container
+ }
+}
+
+// getTrafficServerSourceVersion generates a version string that is
+// passed in to the Traffic Server build. If we are building a git
+// repository directly, use the branch or tag name.
+func getTrafficServerSourceVersion(client *dagger.Client) string {
+ if strings.HasPrefix(TrafficServerDir, "git@") {
+ opts := dagger.GitOpts{}
+
+ // If we are running in a local dev environment, then we can use SSH_AUTH_SOCK
+ // to do an authenticated pull of the repository.
+ if sshAgentPath := os.Getenv("SSH_AUTH_SOCK"); sshAgentPath != "" {
+ opts.SSHAuthSocket = client.Host().UnixSocket(sshAgentPath)
+ }
+
+ repo := client.Git(TrafficServerDir, opts)
+
+ // Building from "master" is a reasonably intuitive default.
+ name := "master"
+ ref := repo.Tag(name)
+
+ // The tag and branch flags are mutually exclusive, so
+ // it doesn't matter which order we check them in.
+ switch {
+ case TrafficServerBranch != "":
+ name = TrafficServerBranch
+ ref = repo.Branch(TrafficServerBranch)
+ case TrafficServerTag != "":
+ name = TrafficServerTag
+ ref = repo.Tag(TrafficServerTag)
+ }
+
+ commit, err := ref.Commit(context.Background())
+ if err != nil {
+ Errorf("Unable to determine Git commit: %s", err) // yet?
+ os.Exit(1)
+ }
+
+ return fmt.Sprintf("%s-%s", name, commit[:min(len(commit), 10)])
+ }
+
+ // NOTE: GitHub PRs don't clone the tags, so we need to set --always as a fallback.
+ cmd := exec.Command("git", "describe", "--always", "--dirty=+dirty")
+ cmd.Dir = Must(filepath.Abs(TrafficServerDir))
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ if len(out) != 0 {
+ Errorf("%s", strings.TrimSpace(string(out)))
+ }
+ Errorf("git describe failed: %s", err)
+ os.Exit(1)
+ }
+
+ return strings.TrimSpace(string(out))
+}
+
+func bindTrafficServerSources(client *dagger.Client) *dagger.Directory {
+ if strings.HasPrefix(TrafficServerDir, "git@") {
+ repo := client.Git(TrafficServerDir)
+ opts := dagger.GitRefTreeOpts{}
+
+ // If we are running in a local dev environment, then we can use SSH_AUTH_SOCK
+ // to do an authenticated pull of the repository.
+ if sshAgentPath := os.Getenv("SSH_AUTH_SOCK"); sshAgentPath != "" {
+ opts.SSHAuthSocket = client.Host().UnixSocket(sshAgentPath)
+ }
+
+ // The tag and branch flags are mutually exclusive, so
+ // it doesn't matter which order we check them in.
+ if TrafficServerBranch != "" {
+ return repo.Branch(TrafficServerBranch).Tree(opts)
+ }
+
+ if TrafficServerTag != "" {
+ return repo.Tag(TrafficServerTag).Tree(opts)
+ }
+
+ // Building from "master" is a reasonably intuitive default.
+ return repo.Tag("master").Tree(opts)
+ }
+
+ if strings.HasPrefix(TrafficServerDir, "https://") {
+ Errorf("HTTPS Git repositories are not supported") // yet?
+ os.Exit(1)
+ }
+
+ // Now, assume that TrafficServerDir is a local directory path.
+ // NOTE: Dagger needs an absolute path to copy a directory from outside
+ // our current workdir.
+ repoBaseDir := Must(filepath.Abs(TrafficServerDir))
+
+ // Copy the build source from the host to the container. We exclude
+ // directories with build tooling so that changes to packaging
+ // and scripts don't blow the Dagger cache.
+ return client.Host().Directory(
+ repoBaseDir,
+ dagger.HostDirectoryOpts{
+ Exclude: []string{
+ ".git/",
+ ".github/",
+ "aviatrix/dagger",
+ "aviatrix/build",
+ "build/_aux",
+ "copy-for-docker.tgz",
+ },
+ },
+ )
+
+}
+
+// bindCloudnProtobufs returns the "proto" subdirectory of the cloudn repository.
+func bindCloudnProtobufs(client *dagger.Client) *dagger.Directory {
+ // TODO(jpeach) first check if we have a GitHub access
+ // token, and use that. We would use the access token in GitHub
+ // Actions builds.
+
+ // If we are running in a local dev environment, then we can use SSH_AUTH_SOCK
+ // to do an authenticated pull of the cloudn repository.
+ if sshAgentPath := os.Getenv("SSH_AUTH_SOCK"); sshAgentPath != "" {
+ opts := dagger.GitRefTreeOpts{
+ SSHAuthSocket: client.Host().UnixSocket(sshAgentPath),
+ }
+
+ repo := client.Git("git@github.com:AviatrixDev/cloudn.git")
+
+ if CloudnTag != "" {
+ return repo.Tag(CloudnTag).Tree(opts).Directory("proto")
+ }
+
+ if CloudnBranch != "" {
+ return repo.Branch(CloudnBranch).Tree(opts).Directory("proto")
+ }
+
+ return repo.Branch("master").Tree(opts).Directory("proto")
+ }
+
+ // If there's no SSH agent, then we assume that the cloudn
+ // repository is checked out next to our local repository.
+
+ // Absolute path to the top of the current repository.
+ repoBaseDir := Must(filepath.Abs("../.."))
+ return client.Host().Directory(
+ path.Join(repoBaseDir, "../cloudn/proto"),
+ )
+}
+
+func buildJemalloc(client *dagger.Client, container *dagger.Container) *dagger.Container {
+ const srcdir = "/src/jemalloc"
+ const vers = "5.3.0"
+
+ tarball := client.HTTP(fmt.Sprintf(
+ "https://github.com/jemalloc/jemalloc/releases/download/%s/jemalloc-%s.tar.bz2", vers, vers))
+
+ return container.
+ With(aptInstall("libunwind-dev")).
+ WithExec([]string{"mkdir", "-p", srcdir}).
+ WithWorkdir(srcdir).
+ WithFile(path.Join(srcdir, "jemalloc-source.tgz"), tarball).
+ WithExec([]string{"tar", "--strip-components=1", "-xf", "jemalloc-source.tgz"}).
+ WithExec([]string{
+ "./configure",
+ "--prefix=" + DevInstallBase,
+ "--enable-prof",
+ "--enable-prof-libunwind",
+ }).
+ With(makeInstall())
+}
+
+func buildOpenssl(client *dagger.Client, container *dagger.Container) *dagger.Container {
+ const srcdir = "/src/openssl"
+ const prefix = DevInstallBase
+
+ branch := client.Git("https://github.com/openssl/openssl").
+ Tag("openssl-3.0.13").
+ Tree()
+
+ return container.
+ WithDirectory(srcdir, branch).
+ WithWorkdir(srcdir).
+ WithExec([]string{
+ "./config",
+ "--release",
+ "--prefix=" + prefix,
+ "--libdir=" + path.Join(prefix, "lib"), // Override the "lib64" default.
+ "no-tests",
+ "no-makedepend",
+ // TODO(jpeach) master also has no-apps and no-docs build options
+ }).
+ With(makeInstall())
+}
+
+func buildGrpc(client *dagger.Client, container *dagger.Container) *dagger.Container {
+ srcdir := "/src/grpc"
+ builddir := "/build/grpc"
+ ssldir := DevInstallBase
+
+ // Note that dagger recursively checks out submodules without
+ // special configuration.
+ branch := client.Git("https://github.com/grpc/grpc").
+ Branch("v1.48.0").
+ Tree()
+
+ // See also https://github.com/grpc/grpc/blob/master/BUILDING.md.
+ return container.
+ With(aptInstall("cmake")).
+ WithDirectory(srcdir, branch).
+ WithExec([]string{"mkdir", "-p", builddir}).
+ WithWorkdir(builddir).
+ WithExec([]string{
+ "/usr/bin/cmake",
+ "-DCMAKE_INSTALL_PREFIX=" + DevInstallBase,
+ "-DCMAKE_BUILD_TYPE:STRING=Release",
+ // Enable shared libraries.
+ "-DBUILD_SHARED_LIBS=ON",
+ // Force cmake to set rpath so that gRPC tools work.
+ "-DCMAKE_SKIP_RPATH=FALSE",
+ "-DCMAKE_INSTALL_RPATH=" + path.Join(DevInstallBase, "lib"),
+ "-DCMAKE_CXX_STANDARD=17",
+ "-DCMAKE_CXX_STANDARD_REQUIRED=On",
+ "-DCMAKE_CXX_EXTENSIONS=On",
+ // Note that this installs abseil as a side-effect.
+ "-DgRPC_INSTALL=ON",
+ "-DgRPC_BUILD_TESTS=OFF",
+ "-DgRPC_BUILD_CSHARP_EXT=OFF",
+ "-DgRPC_BUILD_GRPC_CSHARP_PLUGIN=OFF",
+ "-DgRPC_BUILD_GRPC_NODE_PLUGIN=OFF",
+ "-DgRPC_BUILD_GRPC_OBJECTIVE_C_PLUGIN=OFF",
+ "-DgRPC_BUILD_GRPC_PHP_PLUGIN=OFF",
+ "-DgRPC_BUILD_GRPC_PYTHON_PLUGIN=OFF",
+ "-DgRPC_BUILD_GRPC_RUBY_PLUGIN=OFF",
+ // Don't the bundled BoringSSL.
+ "-DgRPC_SSL_PROVIDER=package",
+ "-DOPENSSL_ROOT_DIR=" + ssldir,
+ // Don't use the bundled zlib.
+ "-DgRPC_ZLIB_PROVIDER=package",
+ srcdir,
+ }).
+ With(makeInstall())
+}
+
+func buildSwoc(client *dagger.Client, container *dagger.Container) *dagger.Container {
+ srcdir := "/src/swoc"
+ builddir := "/build/swoc"
+
+ branch := client.Git("https://github.com/SolidWallOfCode/libswoc").
+ Tag("1.5.1").
+ Tree()
+
+ return container.
+ WithDirectory(srcdir, branch).
+ WithExec([]string{"mkdir", "-p", builddir}).
+ WithWorkdir(builddir).
+ WithExec([]string{
+ path.Join(DevInstallBase, "bin", "cmake"),
+ "-DCMAKE_INSTALL_PREFIX=" + DevInstallBase,
+ "-DCMAKE_BUILD_TYPE:STRING=Release",
+ "-DLIBSWOC_TEST=OFF", // Disable the tests and examples.
+ srcdir,
+ }).
+ With(makeInstall())
+}
+
+// setupTrafficServer does all the preparation steps for
+// building Traffic Server, but not the actual build. The configure
+// and make steps are saved in the build directory.
+//
+// Although this makes things a bit complicated, it also makes it
+// possible to generate a build image where people can build Traffic
+// Server manually with the correct configuration.
+func setupTrafficServer(client *dagger.Client, container *dagger.Container) *dagger.Container {
+ const srcdir = "/src/trafficserver"
+ const cloudndir = "/src/cloudn"
+ const prefix = TrafficServerInstallDir
+
+ // Bind the cloudn protobuf directory. Note that we set
+ // CLOUDN_PROTO to the cloudn repository root, because the
+ // proto files include the leading "proto/" in their import paths.
+ cloudnProtoDir := bindCloudnProtobufs(client)
+
+ return container.
+ // Add TrafficServer-specific dependencies.
+ With(aptInstall(
+ // Some tests that assume Python3 is installed.
+ "python3",
+ "libcap-dev",
+ "libhwloc-dev",
+ "libpcre3-dev",
+ "libpcre2-dev",
+ // So that jemalloc will link. Otherwise,
+ // the Traffic Server jemalloc detection will
+ // fail, but the build will continue without it.
+ "libunwind8",
+ "cmake",
+ "pipenv",
+ )).
+ WithDirectory(srcdir, bindTrafficServerSources(client)).
+ WithMountedDirectory(path.Join(cloudndir, "proto"), cloudnProtoDir).
+ // Add the TrafficServer role account for the build and install.
+ WithExec([]string{
+ "useradd",
+ "--create-home",
+ "--shell", "/bin/bash",
+ "--gid", "root",
+ "--groups", "sudo",
+ "--system",
+ "ats-user",
+ })
+}
+
+func buildTrafficServer(client *dagger.Client, container *dagger.Container) *dagger.Container {
+ const srcdir = "/src/trafficserver"
+ const prefix = TrafficServerInstallDir
+
+ builder := setupTrafficServer(client, container)
+ return builder.
+ WithWorkdir(srcdir).
+ WithExec([]string{
+ "cmake",
+ "--preset",
+ "aviatrix",
+ }).
+ WithExec([]string{
+ "cmake",
+ "--build",
+ "build-avx",
+ }).
+ WithExec([]string{
+ "cmake",
+ "--install",
+ "build-avx",
+ }).
+ // Remove garbage libtool archives.
+ WithExec([]string{
+ "find",
+ prefix,
+ "-type", "f",
+ "-name", "*.la",
+ "-exec", "rm", "{}", "+",
+ })
+}
+
+func buildDebianPackages(
+ packager *dagger.Container,
+) *dagger.Container {
+ const builddir = "/build/ats-10.0.0"
+
+ base := packager.
+ WithWorkdir(builddir).
+ // Strip shared library symbols, see https://github.com/Debian/debhelper/blob/master/dh_strip.
+ WithExec([]string{"sh", "-c",
+ "find opt/ats/lib opt/ats/ats_10.0.0/lib -type f -name '*.so' -print | " +
+ "xargs strip --remove-section=.comment --remove-section=.note --strip-unneeded",
+ }).
+ // Strip binary symbols, see https://github.com/Debian/debhelper/blob/master/dh_strip.
+ WithExec([]string{"sh", "-c",
+ "find opt/ats/ats_10.0.0/bin -type f -name 'traffic_*' | " +
+ "xargs strip --remove-section=.comment --remove-section=.note",
+ }).
+ WithExec([]string{"dpkg-deb", "--build", builddir, "/build/ats-10.0.0.deb"})
+
+ debug := packager.
+ WithExec([]string{"dpkg-deb", "--build", builddir, "/build/ats-dbg-10.0.0.deb"})
+
+ // Note that even though the deb files have different names,
+ // they are still the same package as far as dpkg is concerned.
+ // This means that dpkg is happy for you to install one of these
+ // on top of the other (it says it's a reinstall). This seems a
+ // bit weird, but desirable on balance.
+
+ return packager.
+ WithFile("/build/ats-10.0.0.deb", base.File("/build/ats-10.0.0.deb")).
+ WithFile("/build/ats-dbg-10.0.0.deb", debug.File("/build/ats-dbg-10.0.0.deb"))
+}
+
+func setupDebianPackage(
+ client *dagger.Client,
+ trafficserver *dagger.Container,
+ builder *dagger.Container,
+) *dagger.Container {
+ const builddir = "/build/ats-10.0.0" // Path we build the package in.
+ const installdir = TrafficServerInstallDir
+
+ repoBaseDir := Must(filepath.Abs("../.."))
+ packagingDir := path.Join(repoBaseDir, "aviatrix/10_0_0.0001")
+
+ hostFiles := map[string]string{
+ "DEBIAN/control": path.Join(packagingDir, "control"),
+ "DEBIAN/postinst": path.Join(packagingDir, "postinst"),
+ "etc/logrotate.d/avx-gw-trafficserver": path.Join(packagingDir, "logrotate/avx-gw-trafficserver"),
+ "lib/systemd/system/avx-gw-trafficserver.service": path.Join(packagingDir, "avx-gw-trafficserver.service"),
+ }
+ manifest := path.Join(repoBaseDir, "ats-manifest")
+ if _, err := os.Stat(manifest); err == nil {
+ hostFiles["opt/ats/ats_10.0.0.deb.manifest"] = manifest
+ }
+
+ return builder.
+ With(aptInstall(
+ // So we can package the DSOs in a later step.
+ "libhwloc-dev",
+ // https://wiki.debian.org/Packaging/Intro
+ "build-essential",
+ "debhelper",
+ "devscripts",
+ )).
+ WithExec([]string{
+ "mkdir", "-p",
+ path.Join(builddir, "/lib/systemd/system"),
+ path.Join(builddir, "/etc/logrotate.d"),
+ path.Join(builddir, "DEBIAN"),
+ }).
+ WithWorkdir(builddir).
+ With(withHostFiles(client, hostFiles)).
+ // Copy the TrafficServer build over.
+ WithDirectory(
+ path.Join("./", installdir),
+ trafficserver.Directory(installdir),
+ dagger.ContainerWithDirectoryOpts{Exclude: []string{
+ "share", // Man pages.
+ "include", // Header files.
+ "lib/perl5", // Perl bindings.
+ "lib/pkgconfig", // Pkg-config spec.
+ "lib/plugin_*.so", // Remap test DSOs.
+ }},
+ ).
+ // Package the lib directory that we linked against.
+ WithDirectory(
+ path.Join("./", DevInstallBase, "lib"),
+ trafficserver.Directory(path.Join(DevInstallBase, "lib")),
+ dagger.ContainerWithDirectoryOpts{
+ Exclude: []string{"cmake/", "pkgconfig/", "*.a"},
+ },
+ ).
+ // Copy libhwloc so that we don't take a dependency on a package
+ // that we might not be able to install at upgrade time.
+ With(func(container *dagger.Container) *dagger.Container {
+ return container.WithDirectory(
+ path.Join("./", installdir, "lib"),
+ container.Directory("/usr/lib/x86_64-linux-gnu"),
+ dagger.ContainerWithDirectoryOpts{
+ Include: []string{"libhwloc.so*"},
+ },
+ )
+ }).
+ // Copy custom configs over the top.
+ WithDirectory(
+ path.Join("./", installdir, "etc/trafficserver"),
+ client.Host().Directory(path.Join(packagingDir, "configs")),
+ ).
+ WithExec([]string{
+ "mkdir", "-p",
+ path.Join("./", installdir, "etc/local_ca"),
+ path.Join("./", installdir, "var/local_ca/keys"),
+ }).
+ WithNewFile(path.Join("./", installdir, "var/local_ca/serial.txt"), dagger.ContainerWithNewFileOpts{
+ Contents: "12345",
+ })
+}
+
+func buildBaseContainer(client *dagger.Client, ubuntuVersion string) *dagger.Container {
+ // Start off with a base Ubuntu image, with just basic build dependencies in it.
+ base := client.Container().
+ From(fmt.Sprintf("ubuntu:%s", ubuntuVersion)).
+ With(aptUpdate()).
+ With(aptInstall(
+ "autoconf",
+ "automake",
+ "build-essential",
+ "libtool",
+ "libz-dev",
+ "pkg-config",
+ ))
+
+ ncpu, err := ContainerNumCPUs(base)
+ if err != nil {
+ Errorf("failed to count container CPUs: %s", err.Error())
+ ncpu = 4 // Arbitrary default.
+ }
+
+ return base.WithEnvVariable("MAKEFLAGS", fmt.Sprintf("-j%d", ncpu))
+}
+
+func buildBuilderContainer(client *dagger.Client, base *dagger.Container) *dagger.Container {
+ // The builder is a dev container that has custom dependencies
+ // installed in DevInstallBase.
+ builder := base.
+ With(func(container *dagger.Container) *dagger.Container {
+ return buildOpenssl(client, container)
+ }).
+ With(func(container *dagger.Container) *dagger.Container {
+ // We can attempt to build these packages in parallel.
+ jemalloc := buildJemalloc(client, container)
+ grpc := buildGrpc(client, container)
+ //swoc := buildSwoc(client, container)
+
+ // Merge the output of both builds.
+ return container.
+ WithDirectory(DevInstallBase, jemalloc.Directory(DevInstallBase)).
+ WithDirectory(DevInstallBase, grpc.Directory(DevInstallBase))
+ })
+
+ // Now copy the installed packages back to base so that we don't
+ // have any intermediate artifacts.
+ return base.
+ WithDirectory(DevInstallBase, builder.Directory(DevInstallBase))
+}
diff --git a/aviatrix/dagger/build_manifest.sh b/aviatrix/dagger/build_manifest.sh
new file mode 100755
index 0000000000..db56f3d52f
--- /dev/null
+++ b/aviatrix/dagger/build_manifest.sh
@@ -0,0 +1,27 @@
+#! /usr/bin/env bash
+
+set -o errexit
+set -o nounset
+set -o pipefail
+manifest_dir=`realpath $(dirname $0)/../../`
+cd $manifest_dir
+rm -rf ats-manifest
+git_status="$(git status)"
+echo "build command: $@" >> ats-manifest
+echo "ats branch: " $(git rev-parse --abbrev-ref HEAD) >> ats-manifest
+echo "ats commit: " $(git rev-parse --verify HEAD) >> ats-manifest
+echo "ats user: " $(git config --get user.email) >> ats-manifest
+
+echo "ats timestamp: " $(date -Is) >> ats-manifest
+echo >> ats-manifest
+pushd ../cloudn > /dev/null
+echo "cloudn branch: " $(git rev-parse --abbrev-ref HEAD) >> $manifest_dir/ats-manifest
+echo "cloudn commit: " $(git rev-parse --verify HEAD) >> $manifest_dir/ats-manifest
+echo "cloudn user: " $(git config --get user.email) >> $manifest_dir/ats-manifest
+popd > /dev/null
+
+
+echo >> ats-manifest
+echo >> ats-manifest
+echo =================================================================== >> ats-manifest
+printf "%s" "$git_status" >> ats-manifest
\ No newline at end of file
diff --git a/aviatrix/dagger/builder.sh b/aviatrix/dagger/builder.sh
new file mode 100755
index 0000000000..452030282e
--- /dev/null
+++ b/aviatrix/dagger/builder.sh
@@ -0,0 +1,13 @@
+#! /usr/bin/env bash
+
+set -o errexit
+set -o nounset
+set -o pipefail
+rm -rf $(dirname $0)/../../ats-manifest
+all_args="$@"
+if [ "$all_args" == "package" ]; then
+ ./build_manifest.sh
+fi
+
+cd $(dirname $0)
+exec "${GO:-go}" run *.go "$@"
diff --git a/aviatrix/dagger/go.mod b/aviatrix/dagger/go.mod
new file mode 100644
index 0000000000..1985797fe9
--- /dev/null
+++ b/aviatrix/dagger/go.mod
@@ -0,0 +1,23 @@
+module github.com/AviatrixDev/thirdparty-trafficserver/aviatrix/dagger
+
+go 1.21
+
+require (
+ dagger.io/dagger v0.9.3
+ github.com/spf13/cobra v1.7.0
+ golang.org/x/exp v0.0.0-20231108232855-2478ac86f678
+)
+
+require (
+ github.com/99designs/gqlgen v0.17.40 // indirect
+ github.com/Khan/genqlient v0.6.0 // indirect
+ github.com/adrg/xdg v0.4.0 // indirect
+ github.com/google/uuid v1.4.0 // indirect
+ github.com/inconshreveable/mousetrap v1.1.0 // indirect
+ github.com/mitchellh/go-homedir v1.1.0 // indirect
+ github.com/sosodev/duration v1.2.0 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/vektah/gqlparser/v2 v2.5.10 // indirect
+ golang.org/x/sync v0.5.0 // indirect
+ golang.org/x/sys v0.14.0 // indirect
+)
diff --git a/aviatrix/dagger/go.sum b/aviatrix/dagger/go.sum
new file mode 100644
index 0000000000..e48a25c5a2
--- /dev/null
+++ b/aviatrix/dagger/go.sum
@@ -0,0 +1,48 @@
+dagger.io/dagger v0.9.3 h1:igFU1d6R933Jn6741k5HI/TjAlkpb2/wiBTypNbE0Pw=
+dagger.io/dagger v0.9.3/go.mod h1:1iiFzqKOri9kJxUDYUibthMpkfzaWP25B2kx7F/AXIk=
+github.com/99designs/gqlgen v0.17.40 h1:/l8JcEVQ93wqIfmH9VS1jsAkwm6eAF1NwQn3N+SDqBY=
+github.com/99designs/gqlgen v0.17.40/go.mod h1:b62q1USk82GYIVjC60h02YguAZLqYZtvWml8KkhJps4=
+github.com/Khan/genqlient v0.6.0 h1:Bwb1170ekuNIVIwTJEqvO8y7RxBxXu639VJOkKSrwAk=
+github.com/Khan/genqlient v0.6.0/go.mod h1:rvChwWVTqXhiapdhLDV4bp9tz/Xvtewwkon4DpWWCRM=
+github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
+github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
+github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
+github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+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/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
+github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
+github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
+github.com/sosodev/duration v1.2.0 h1:pqK/FLSjsAADWY74SyWDCjOcd5l7H8GSnnOGEB9A1Us=
+github.com/sosodev/duration v1.2.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
+github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
+github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
+github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/vektah/gqlparser/v2 v2.5.10 h1:6zSM4azXC9u4Nxy5YmdmGu4uKamfwsdKTwp5zsEealU=
+github.com/vektah/gqlparser/v2 v2.5.10/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc=
+golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
+golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
+golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
+golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
+golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+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=
diff --git a/aviatrix/dagger/main.go b/aviatrix/dagger/main.go
new file mode 100644
index 0000000000..99479e1bb7
--- /dev/null
+++ b/aviatrix/dagger/main.go
@@ -0,0 +1,312 @@
+package main
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "os"
+ "os/exec"
+ "path"
+ "regexp"
+ "strings"
+
+ "github.com/spf13/cobra"
+
+ "dagger.io/dagger"
+)
+
+// BaseUbuntuVersion is the Ubuntu release version to build for.
+var BaseUbuntuVersion string
+
+// TrafficServerDir is the path to the Traffic Server source code.
+// If this is a git or https URL, then we pull the Git repository.
+// Otherwise, we assume it is a local path.
+var TrafficServerDir string
+
+// TrafficServerTag is the Traffic Server source tag. Mutually
+// exclusive with the branch. Ignored unless we are building a git
+// repository.
+var TrafficServerTag string
+
+// TrafficServerBranch is the Traffic Server source branch. Mutually
+// exclusive with the tag. Ignored unless we are building a git
+// repository.
+var TrafficServerBranch string
+
+// CloudnTag is the tag at which we pull protobuf definitions from
+// the cloudn repository. Default is to use the master branch. Mutually
+// exclusive with CloudnBranch.
+var CloudnTag string
+
+// CloudnBranch is the branch from which we pull protobuf definitions from
+// the cloudn repository. Default is to use the master branch. Mutually
+// exclusive with CloudnTag.
+var CloudnBranch string
+
+// ContainerNumCPUs inspects the given container and returns the number of available CPUs.
+func ContainerNumCPUs(container *dagger.Container) (int, error) {
+ // procfs is only mounbtedfor processes, so we need to
+ // sample it with an exec before we can copy it out.
+ container = container.WithExec(
+ []string{"cat", "/proc/cpuinfo"},
+ dagger.ContainerWithExecOpts{RedirectStdout: "/tmp/cpuinfo"},
+ )
+
+ cpuInfo, err := container.File("/tmp/cpuinfo").Contents(context.Background())
+ if err != nil {
+ return -1, err
+ }
+
+ count := 0
+ scanner := bufio.NewScanner(strings.NewReader(cpuInfo))
+
+ for scanner.Scan() {
+ fields := strings.SplitN(scanner.Text(), ": ", 2)
+
+ // Skip over empty lines.
+ if len(fields) != 2 {
+ continue
+ }
+
+ if strings.TrimSpace(fields[0]) == "processor" {
+ count++
+ }
+ }
+
+ return count, nil
+}
+
+func Successf(format string, args ...any) {
+ fmt.Fprintf(os.Stderr, "✅ %s\n", fmt.Sprintf(format, args...))
+}
+
+func Errorf(format string, args ...any) {
+ fmt.Fprintf(os.Stderr, "✴️ %s\n", fmt.Sprintf(format, args...))
+}
+
+func Must[T any](value T, err error) T {
+ if err != nil {
+ Errorf(err.Error())
+ os.Exit(1)
+ }
+
+ return value
+}
+
+func ExportContainerImage(
+ ctx context.Context,
+ container *dagger.Container,
+ repoName string,
+ tagName string,
+) error {
+ buildImagePath := path.Join("images", repoName, tagName+".tgz")
+ if _, err := container.Export(ctx, buildImagePath); err != nil {
+ return err
+ }
+
+ Successf("exported build container image to %q", buildImagePath)
+
+ // Load the exported OCI image into the local Docker instance.
+ // Note that we have to load, not import.
+ cmdImport := exec.Command("docker", "load", "--quiet", "--input", buildImagePath)
+ out, err := cmdImport.CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("failed to export builder image: %w", err)
+ }
+
+ Successf("imported build container image from %q", buildImagePath)
+
+ // Capture the image ID from the output of the load
+ // command so that we can tag it. Remember that the
+ // first result from Submatch is the full matched
+ // sequence, so the capture group is the second match.
+ r := regexp.MustCompile("Loaded image ID: ([:a-z0-9]+)")
+ matches := r.FindStringSubmatch(string(out))
+ if len(matches) == 0 {
+ Errorf("failed to match image ID from Docker output %q", string(out))
+ os.Exit(1)
+ }
+
+ fullName := fmt.Sprintf("%s:%s", repoName, tagName)
+ cmdTag := exec.Command("docker", "tag", matches[1], fullName)
+ if out, err := cmdTag.CombinedOutput(); err != nil {
+ return fmt.Errorf("failed to tag builder image: %s", string(out))
+ }
+
+ Successf("tagged build container image %q as %q", matches[1], fullName)
+
+ return nil
+}
+
+func packageCmd(ctx context.Context, client *dagger.Client) error {
+ base := buildBaseContainer(client, BaseUbuntuVersion)
+ builder := buildBuilderContainer(client, base)
+
+ // Now build TrafficServer in the builder container.
+ trafficserver := buildTrafficServer(client, builder)
+
+ // Fork a container off the base image to wrap the TrafficServer
+ // build in a Debian package.
+ packager := buildDebianPackages(setupDebianPackage(client, trafficserver, base))
+
+ for _, pkg := range []string{"ats-10.0.0.deb", "ats-dbg-10.0.0.deb"} {
+ // Copy the final debian package out of the packager container.
+ packagePath := path.Join("packages", BaseUbuntuVersion, pkg)
+ packageFile := client.Directory().
+ WithFile(packagePath, packager.File("/build/"+pkg))
+
+ if _, err := packageFile.Export(ctx, "."); err != nil {
+ return fmt.Errorf("failed to export Debian package: %s", err.Error())
+ }
+
+ Successf("wrote Debian package to %s", packagePath)
+ }
+
+ return nil
+}
+
+func packagingImageCmd(ctx context.Context, client *dagger.Client) error {
+ base := buildBaseContainer(client, BaseUbuntuVersion)
+ builder := buildBuilderContainer(client, base)
+ trafficserver := buildTrafficServer(client, builder)
+ packager := setupDebianPackage(client, trafficserver, builder)
+
+ return ExportContainerImage(ctx, packager, "trafficserver-packaging", BaseUbuntuVersion)
+}
+
+func buildImageCmd(ctx context.Context, client *dagger.Client) error {
+ base := buildBaseContainer(client, BaseUbuntuVersion)
+ builder := buildBuilderContainer(client, base)
+
+ // Do TrafficServer build setup so that the exported
+ // image contains the configure and make steps that
+ // the package build would use.
+ trafficserver := setupTrafficServer(client, builder)
+
+ return ExportContainerImage(ctx, trafficserver, "trafficserver-devel", BaseUbuntuVersion)
+}
+
+func regressionTestsCmd(ctx context.Context, client *dagger.Client) error {
+ base := buildBaseContainer(client, BaseUbuntuVersion)
+ builder := buildBuilderContainer(client, base)
+
+ trafficserver := setupTrafficServer(client, builder).
+ WithExec([]string{"./conf.sh", "--enable-tests"}).
+ With(makeInstall()).
+ WithExec([]string{
+ path.Join(TrafficServerInstallDir, "bin/traffic_server"),
+ "--clear_hostdb", "--clear_cache", "--regression", "1",
+ })
+
+ if _, err := trafficserver.Sync(ctx); err != nil {
+ return fmt.Errorf("regression tests failed: %s", err)
+ }
+
+ Successf("regression tests passed")
+ return nil
+}
+
+func unitTestsCmd(ctx context.Context, client *dagger.Client) error {
+ base := buildBaseContainer(client, BaseUbuntuVersion)
+ builder := buildBuilderContainer(client, base)
+ trafficserver := buildTrafficServer(client, builder).
+ // First build the tests without running any, to catch build errors.
+ WithExec([]string{"make", "check", "TESTS="}).
+ // Next, run the tests.
+ WithExec([]string{"make", "check"})
+
+ if _, err := trafficserver.Sync(ctx); err != nil {
+ return fmt.Errorf("unit tests failed: %s", err)
+ }
+
+ Successf("unit tests passed")
+ return nil
+}
+
+func main() {
+ var daggerCtx context.Context
+ var daggerClient *dagger.Client
+
+ defer func() {
+ if daggerClient != nil {
+ daggerClient.Close()
+ }
+ }()
+
+ rootCmd := cobra.Command{
+ Use: path.Base(os.Args[0]),
+ PersistentPreRunE: func(*cobra.Command, []string) error {
+ var err error
+ daggerCtx = context.Background()
+
+ // Initialize a Dagger client.
+ daggerClient, err = dagger.Connect(daggerCtx, dagger.WithLogOutput(os.Stdout))
+ return err
+ },
+ }
+
+ rootCmd.PersistentFlags().
+ StringVar(&BaseUbuntuVersion, "ubuntu", "22.04", "Base Ubuntu release version")
+
+ rootCmd.PersistentFlags().
+ StringVar(&TrafficServerDir, "source", "../..", "Traffic Server source repository or directory path")
+
+ rootCmd.PersistentFlags().
+ StringVar(&TrafficServerBranch, "branch", "", "Traffic Server source branch")
+
+ rootCmd.PersistentFlags().
+ StringVar(&TrafficServerTag, "tag", "", "Traffic Server source tag")
+
+ rootCmd.PersistentFlags().
+ StringVar(&CloudnTag, "cloudn-tag", "", "Cloudn source repository tag")
+
+ rootCmd.PersistentFlags().
+ StringVar(&CloudnBranch, "cloudn-branch", "", "Cloudn source repository branch")
+
+ rootCmd.AddCommand(
+ &cobra.Command{
+ Use: "package",
+ Short: "Build the Ubuntu .deb package",
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return packageCmd(daggerCtx, daggerClient)
+ },
+ },
+ &cobra.Command{
+ Use: "packaging-image",
+ Short: "Build a Docker image for packaging",
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return packagingImageCmd(daggerCtx, daggerClient)
+ },
+ },
+ &cobra.Command{
+ Use: "build-image",
+ Short: "Build a Docker image for local development",
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return buildImageCmd(daggerCtx, daggerClient)
+ },
+ },
+ &cobra.Command{
+ Use: "regression-tests",
+ Short: "Run Traffic Server regression tests",
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return regressionTestsCmd(daggerCtx, daggerClient)
+ },
+ },
+ &cobra.Command{
+ Use: "unit-tests",
+ Short: "Run Traffic Server unit tests",
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return unitTestsCmd(daggerCtx, daggerClient)
+ },
+ },
+ )
+
+ for _, c := range rootCmd.Commands() {
+ c.MarkFlagsMutuallyExclusive("tag", "branch")
+ c.MarkFlagsMutuallyExclusive("cloudn-tag", "cloudn-branch")
+ }
+
+ if err := rootCmd.Execute(); err != nil {
+ Errorf("%s", err)
+ }
+}
diff --git a/aviatrix/plugins/CMakeLists.txt b/aviatrix/plugins/CMakeLists.txt
new file mode 100644
index 0000000000..cf0996dd6e
--- /dev/null
+++ b/aviatrix/plugins/CMakeLists.txt
@@ -0,0 +1,3 @@
+add_subdirectory(avx_tee_decrypt)
+add_subdirectory(avx_certifier)
+add_subdirectory(avx_policy_driver)
diff --git a/aviatrix/plugins/avx_certifier/CMakeLists.txt b/aviatrix/plugins/avx_certifier/CMakeLists.txt
new file mode 100644
index 0000000000..fac8784b7d
--- /dev/null
+++ b/aviatrix/plugins/avx_certifier/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_atsplugin(avx_certifier avx_certifier.cc)
+
+target_link_libraries(avx_certifier PRIVATE OpenSSL::Crypto OpenSSL::SSL)
+verify_global_plugin(avx_certifier)
diff --git a/aviatrix/plugins/avx_certifier/README b/aviatrix/plugins/avx_certifier/README
new file mode 100644
index 0000000000..2c90dd5861
--- /dev/null
+++ b/aviatrix/plugins/avx_certifier/README
@@ -0,0 +1,33 @@
+AVX_Certifier
+TODO: Update description to describe connect to origin
+This plugin performs two basic tasks -
+
+Load SSL certificates from file storage on demand. The total number of loaded certificates can be configured.
+Generates SSL certificates on demand.
+Generated certificates can be written to file storage for later retrieval.
+
+Description
+Certifier manages SSL certificates for Traffic Server. There are two sources for certificates, certificate files in the file system and dynamically generated certificates. Dynamic certificates can be stored to the file system to avoid generating them again.
+
+Configuration
+Certifier is a global plugin and is configured by arguments in plugin.config.
+
+|Name|
+
+--sign-cert <path>
+Specify the signing certificate for dynamically generated certificates. path should be the path and file name of the certificate. If it is relative it is relative to the Traffic Server configuration directory. If this is not specified then dynamic certificate generation is disabled.
+
+--sign-key <path>
+Specify the signing key for dynamically generated certificates.
+
+--sign-serial <path>
+Specify the signing serial number from a file. Serial file should be a number with a trailing newline.
+
+--max <N>
+The maximum number of certificates to keep in memory. If more certificates are loaded the least recently used certificates are deleted from memory. This is intended to control the amount of memory used by the in memory certificate store.
+
+--store <path>
+The directory to use as the root of file system certificate store.
+
+Note:
+Use OpenSSL 1.0.2 and above.
diff --git a/aviatrix/plugins/avx_certifier/avx_certifier.cc b/aviatrix/plugins/avx_certifier/avx_certifier.cc
new file mode 100644
index 0000000000..ed160c3d01
--- /dev/null
+++ b/aviatrix/plugins/avx_certifier/avx_certifier.cc
@@ -0,0 +1,1192 @@
+// Copyright (c) 2022, Aviatrix Systems, Inc. All rights reserved.
+
+// TODOS:
+// * refactor/simplify the cont handlers in certifier
+// * implement timeout for the probe origin connection
+// in the records.config we now use an infinite handshake timeout
+// * limit disk usage to prevent DDOS
+// * Remove dead code
+
+// * ...
+
+// DONE:
+// * copy enddate from origin
+// * implement expiration logic for the certs
+// * enforce short expiration timeouts even when origin cert has a long one -- this is
+// prevent having same local certs for a long time
+// * encode special characters for filesystem paths to prevent path traversing
+// * fix Certifier class delete in (leaks now by design)
+// the Resume... functions are the end of the logical flow
+// we could still have events on the probe vconn/vio continuation after that?
+// we need to be sure that we won't run into a race condition there
+// * fix cont cleanups
+// * do a zero byte wri`te into the probe connection to force TLS handshake
+// this is now a 2 byte (spaces), which could cause the origin to close
+// on us before we can process the handshake
+
+#include <atomic>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cerrno>
+#include <getopt.h>
+
+#include <time.h>
+
+#include <sys/stat.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/conf.h>
+#include <openssl/x509v3.h>
+#include <openssl/x509.h>
+#include <openssl/rsa.h>
+#include <openssl/md5.h>
+#include <openssl/bio.h>
+#include <openssl/opensslv.h>
+
+#include <unordered_map> // cnDataMap
+#include <queue> // vconnQ
+#include <string> // std::string
+#include <fstream> // ofstream
+#include <memory>
+#include <algorithm>
+
+#include "ts/ts.h"
+
+namespace
+{
+const char *PLUGIN_NAME = "certifier";
+DbgCtl dbg_ctl{PLUGIN_NAME};
+} // namespace
+const char *no_sni_servername_placeholder_domain = "placeholder.no.sni.hostname.certificate";
+const int ASSUME_EXPIRED_TIME = 10 * 60;
+const int RETRY_TIME = 5 * 60;
+const int MAX_CERT_EXP_TIME = 7 * 24 * 3600;
+const char *CertifierPolicyTag = "CertifierPolicy";
+
+/// Override default delete for unique ptrs to openSSL objects
+namespace std
+{
+template <> struct default_delete<X509> {
+ void
+ operator()(X509 *n)
+ {
+ if (n != nullptr)
+ X509_free(n);
+ }
+};
+template <> struct default_delete<X509_REQ> {
+ void
+ operator()(X509_REQ *n)
+ {
+ if (n != nullptr)
+ X509_REQ_free(n);
+ }
+};
+template <> struct default_delete<EVP_PKEY> {
+ void
+ operator()(EVP_PKEY *n)
+ {
+ if (n != nullptr)
+ EVP_PKEY_free(n);
+ }
+};
+template <> struct default_delete<SSL_CTX> {
+ void
+ operator()(SSL_CTX *n)
+ {
+ if (n != nullptr)
+ SSL_CTX_free(n);
+ }
+};
+} // namespace std
+
+/// Name aliases for unique pts to openSSL objects
+using scoped_X509 = std::unique_ptr<X509>;
+using scoped_X509_REQ = std::unique_ptr<X509_REQ>;
+using scoped_EVP_PKEY = std::unique_ptr<EVP_PKEY>;
+using scoped_SSL_CTX = std::unique_ptr<SSL_CTX>;
+
+class SslLRUList
+{
+private:
+ struct SslData {
+ std::queue<void *> vconnQ; ///< Current queue of connections waiting for cert
+ std::unique_ptr<SSL_CTX> ctx; ///< Context generated
+ scoped_X509 cert; ///< Cert generated
+ std::string commonName; ///< SNI
+ bool scheduled = false; ///< If a TASK thread has been scheduled to generate cert
+ ///< The first thread might fail to do so, this flag will help reschedule
+ bool wontdo = false; ///< if certs not on disk and dynamic gen is disabled
+ /// Doubly Linked List pointers for LRU
+ SslData *prev = nullptr;
+ SslData *next = nullptr;
+
+ time_t exptime;
+ time_t begintime;
+
+ SslData() = default;
+ ~SslData() { Dbg(dbg_ctl, "Deleting ssl data for [%s]", commonName.c_str()); }
+ };
+
+ using scoped_SslData = std::unique_ptr<SslLRUList::SslData>;
+
+ // unordered_map is much faster in terms of insertion/lookup/removal
+ // Although it uses more space than map, the time efficiency should be more important
+ std::unordered_map<std::string, scoped_SslData> cnDataMap; ///< Map from CN to sslData
+ TSMutex list_mutex;
+
+ int size = 0;
+ int limit;
+ SslData *head = nullptr;
+ SslData *tail = nullptr;
+
+public:
+ SslLRUList(int in_limit = 4096) : limit(in_limit) { list_mutex = TSMutexCreate(); }
+
+ ~SslLRUList() { TSMutexDestroy(list_mutex); }
+
+ // Returns valid ptr to SSL_CTX if successful lookup
+ // nullptr if not found and create SslData in the map
+ SSL_CTX *
+ lookup_and_create(const char *servername, void *edata, bool &wontdo, bool &delete_cert_from_disk, bool &lookup_scheduled)
+ {
+ SslData *ssl_data = nullptr;
+ scoped_SslData scoped_ssl_data = nullptr;
+ SSL_CTX *ref_ctx = nullptr;
+ std::string commonName(servername);
+ lookup_scheduled = false;
+ TSMutexLock(list_mutex);
+ auto dataItr = cnDataMap.find(commonName);
+ /// If such a context exists in dict
+ if (dataItr != cnDataMap.end()) {
+ ssl_data = dataItr->second.get();
+ // if expired reset ssl data entry struct and set ssl data to null and delete_cert_from_disk=True
+ // else ...
+ time_t nowtime = time(nullptr);
+ if (ssl_data->exptime != -1 && ssl_data->exptime - 15 < nowtime) {
+ ssl_data->ctx.reset(nullptr);
+ ssl_data->cert.reset(nullptr);
+ ssl_data->scheduled = false;
+ ssl_data->exptime = -1;
+ ssl_data->begintime = -1;
+ ssl_data->wontdo = false;
+ delete_cert_from_disk = true;
+
+ Dbg(dbg_ctl, "clearing the ssl_data struct because cert expired");
+ }
+
+ /// Reuse context if already built, self queued if not
+ if (ssl_data->wontdo) {
+ wontdo = true;
+ } else if (ssl_data->ctx) {
+ ref_ctx = ssl_data->ctx.get();
+ } else {
+ if (ssl_data->vconnQ.size() > 0) {
+ lookup_scheduled = true;
+ }
+ ssl_data->vconnQ.push(edata);
+ }
+ }
+ if (ssl_data == nullptr) {
+ /// Add a new ssl_data to dict if not exist
+ scoped_ssl_data.reset(new SslData);
+ ssl_data = scoped_ssl_data.get();
+ ssl_data->commonName = std::move(commonName);
+ ssl_data->vconnQ.push(edata);
+ ssl_data->exptime = -1;
+ ssl_data->begintime = -1;
+
+ cnDataMap[ssl_data->commonName] = std::move(scoped_ssl_data);
+ }
+ // With a valid sslData pointer
+ if (ssl_data != nullptr) {
+ // Add to the list and set scheduled flag
+ prepend(ssl_data);
+ if (ref_ctx == nullptr || !ssl_data->scheduled) {
+ ssl_data->scheduled = true;
+ }
+ }
+ TSMutexUnlock(list_mutex);
+ return ref_ctx;
+ }
+
+ // Setup ssldata 1) ctx 2) cert 3) swapping queue
+ // Ownership of unique pointers are transferred into this function
+ // Then if the entry is found, the ownership is further transferred to the entry
+ // if not, the objects are destroyed here. (As per design, this is caused by LRU management deleting oldest entry)
+ void
+ setup_data_ctx(const std::string &commonName, std::queue<void *> &localQ, std::unique_ptr<SSL_CTX> ctx, scoped_X509 cert,
+ const bool &wontdo, time_t exp_time_cert, time_t begin_time_cert)
+ {
+ TSMutexLock(list_mutex);
+ auto iter = cnDataMap.find(commonName);
+ if (iter != cnDataMap.end()) {
+ std::swap(localQ, iter->second->vconnQ);
+ iter->second->ctx = std::move(ctx);
+ iter->second->cert = std::move(cert); ///< We might not need cert, can be easily removed
+ iter->second->wontdo = wontdo;
+ iter->second->exptime = exp_time_cert;
+ iter->second->begintime = begin_time_cert;
+ }
+ TSMutexUnlock(list_mutex);
+ }
+
+ // Prepend to the LRU list
+ void
+ prepend(SslData *data)
+ {
+ TSMutexLock(list_mutex);
+ std::unique_ptr<SslData> local = nullptr;
+ if (data != nullptr) {
+ // If data is the most recent node in the list,
+ // we leave it unchanged.
+ if (head != data) {
+ // Remove data from the list (does size decrement)
+ remove_from_list(data);
+
+ // Prepend to head
+ data->prev = nullptr;
+ data->next = head;
+ if (data->next != nullptr) {
+ data->next->prev = data;
+ }
+ head = data;
+ if (tail == nullptr) {
+ tail = data;
+ }
+
+ // Remove oldest node if size exceeds limit
+ if (++size > limit) {
+ Dbg(dbg_ctl, "Removing %s", tail->commonName.c_str());
+ auto iter = cnDataMap.find(tail->commonName);
+ if (iter != cnDataMap.end()) {
+ local = std::move(iter->second); // copy ownership
+ cnDataMap.erase(iter);
+ }
+ if ((tail = tail->prev) != nullptr) {
+ tail->next = nullptr;
+ }
+ size -= 1;
+ }
+ }
+ }
+ Dbg(dbg_ctl, "%s Prepend to LRU list...List Size:%d Map Size: %d", data->commonName.c_str(), size,
+ static_cast<int>(cnDataMap.size()));
+
+ TSMutexUnlock(list_mutex);
+ }
+
+ // Remove list node
+ void
+ remove_from_list(SslData *data)
+ {
+ TSMutexLock(list_mutex);
+ // If data and list are both valid
+ if (data != nullptr) {
+ // If data is linked in list
+ if (data->prev != nullptr || data->next != nullptr || head == data) {
+ if (data->prev != nullptr) {
+ data->prev->next = data->next;
+ }
+ if (data->next != nullptr) {
+ data->next->prev = data->prev;
+ }
+ if (head == data) {
+ head = data->next;
+ }
+ if (tail == data) {
+ tail = data->prev;
+ }
+ data->prev = nullptr;
+ data->next = nullptr;
+ size -= 1;
+ }
+ }
+ TSMutexUnlock(list_mutex);
+ }
+
+ SslData *
+ get_newest()
+ {
+ TSMutexLock(list_mutex);
+ SslData *ret = head;
+ TSMutexUnlock(list_mutex);
+ return ret;
+ }
+
+ SslData *
+ get_oldest()
+ {
+ TSMutexLock(list_mutex);
+ SslData *ret = tail;
+ TSMutexUnlock(list_mutex);
+ return ret;
+ }
+
+ int
+ get_size()
+ {
+ TSMutexLock(list_mutex);
+ int ret = size;
+ TSMutexUnlock(list_mutex);
+ return ret;
+ }
+
+ // Set scheduled flag
+ int
+ set_schedule(const std::string &commonName, bool flag)
+ {
+ int ret = -1;
+ TSMutexLock(list_mutex);
+ auto iter = cnDataMap.find(commonName);
+ if (iter != cnDataMap.end()) {
+ iter->second->scheduled = flag;
+ ret = 0;
+ }
+ TSMutexUnlock(list_mutex);
+ return ret;
+ }
+};
+
+// Flag for dynamic cert generation
+static bool sign_enabled = false;
+
+// Trusted CA private key and cert
+static scoped_X509 ca_cert_scoped;
+static scoped_EVP_PKEY ca_pkey_scoped;
+// static scoped_EVP_PKEY ts_pkey_scoped;
+
+static int ca_serial; ///< serial number
+static std::fstream serial_file; ///< serial number file
+static TSMutex serial_mutex; ///< serial number mutex
+
+// Management Object
+static std::unique_ptr<SslLRUList> ssl_list = nullptr;
+static std::string store_path;
+
+/// Local helper function that generates a CSR based on common name
+static scoped_X509_REQ
+mkcsr(const scoped_X509 &source_cert)
+{
+ Dbg(dbg_ctl, "Entering mkcsr()...");
+ scoped_X509_REQ req;
+ req.reset(X509_REQ_new());
+
+ /// Set X509 version
+ X509_REQ_set_version(req.get(), 1);
+
+ /// Get handle to subject name
+ auto subject = X509_get_subject_name(source_cert.get());
+ X509_REQ_set_subject_name(req.get(), subject);
+ int san_index = X509_get_ext_by_NID((source_cert.get()), NID_subject_alt_name, -1);
+ if (san_index != -1) {
+ X509_EXTENSION *san_ext = X509_get_ext((source_cert.get()), san_index);
+ // Create a new stack of extensions
+ STACK_OF(X509_EXTENSION) *exts = sk_X509_EXTENSION_new_null();
+
+ // Add the SAN extension to the stack of extensions
+ sk_X509_EXTENSION_push(exts, san_ext);
+
+ // Add the stack of extensions to the certificate request
+ X509_REQ_add_extensions(req.get(), exts);
+
+ // Free allocated resources
+ sk_X509_EXTENSION_free(exts);
+ }
+
+ // Todo : Remove or refactor ?
+ /*
+ auto expdate = X509_get_notAfter(source_cert.get());
+ auto startdate = X509_get_notBefore(source_cert.get());
+
+ char expdatebuf[256];
+ char startdatebuf[256];
+
+ BIO *bio,*bio2;
+ int write = 0;
+ bio = BIO_new(BIO_s_mem());
+ bio2 = BIO_new(BIO_s_mem());
+ if (bio) {
+ if (ASN1_TIME_print(bio,expdate))
+ write = BIO_read(bio, expdatebuf, 255);
+ BIO_free(bio);
+ }
+ expdatebuf[write] = '\0';
+ if (bio2) {
+ if (ASN1_TIME_print(bio2,startdate))
+ write = BIO_read(bio2, startdatebuf, 255);
+ BIO_free(bio2);
+ }
+ startdatebuf[write] = '\0';
+
+ Dbg(dbg_ctl, "exp date of server cert : %s ; start date : %s", expdatebuf, startdatebuf);
+ */
+
+ /// Set Traffic Server public key
+ if (X509_REQ_set_pubkey(req.get(), ca_pkey_scoped.get()) != 1) {
+ TSError("[%s] mkcsr(): Failed to set pubkey.", PLUGIN_NAME);
+ return nullptr;
+ }
+ /// Sign with Traffic Server private key
+ if (X509_REQ_sign(req.get(), ca_pkey_scoped.get(), EVP_sha256()) <= 0) {
+ TSError("[%s] mkcsr(): Failed to Sign.", PLUGIN_NAME);
+ return nullptr;
+ }
+ return req;
+}
+
+/// Local helper function that generates a X509 certificate based on CSR
+static scoped_X509
+mkcrt(X509_REQ *req, int serial, const ASN1_TIME *expdate, const ASN1_TIME *startdate)
+{
+ Dbg(dbg_ctl, "Entering mkcrt()...");
+ X509_NAME *subj, *tmpsubj;
+ scoped_EVP_PKEY pktmp;
+ scoped_X509 cert;
+
+ cert.reset(X509_new());
+
+ /// Set X509V3
+ if (X509_set_version(cert.get(), 2) == 0) {
+ TSError("[%s] mkcrt(): Failed to set X509V3.", PLUGIN_NAME);
+ return nullptr;
+ }
+
+ /// Set serial number
+ // Dbg("txn_monitor", "serial: %d", serial);
+ ASN1_INTEGER_set(X509_get_serialNumber(cert.get()), serial);
+
+ /// Set issuer from CA cert
+ if (X509_set_issuer_name(cert.get(), X509_get_subject_name(ca_cert_scoped.get())) == 0) {
+ TSError("[%s] mkcrt(): Failed to set issuer.", PLUGIN_NAME);
+ return nullptr;
+ }
+ /// Set certificate time
+ // X509_gmtime_adj(X509_get_notBefore(cert.get()), 0);
+ X509_set_notBefore(cert.get(), startdate);
+ // todo: lower , configurable + add expiration handling logic
+ // X509_gmtime_adj(X509_get_notAfter(cert.get()), static_cast<long>(3650) * 24 * 3600);
+ if (ASN1_TIME_cmp_time_t(expdate, time(nullptr) + MAX_CERT_EXP_TIME) > 0) {
+ expdate = ASN1_TIME_set(nullptr, time(nullptr) + MAX_CERT_EXP_TIME);
+ X509_set_notAfter(cert.get(), expdate);
+ free((void *)expdate);
+ expdate = nullptr;
+ } else {
+ X509_set_notAfter(cert.get(), expdate);
+ }
+ /// Get a handle to csr subject name
+ subj = X509_REQ_get_subject_name(req);
+ if ((tmpsubj = X509_NAME_dup(subj)) == nullptr) {
+ Dbg(dbg_ctl, "mkcrt(): Failed to duplicate subject name.");
+ return nullptr;
+ }
+ if ((X509_set_subject_name(cert.get(), tmpsubj)) == 0) {
+ Dbg(dbg_ctl, "mkcrt(): Failed to set X509 subject name");
+ X509_NAME_free(tmpsubj); ///< explicit call to free X509_NAME object
+ return nullptr;
+ }
+
+ STACK_OF(X509_EXTENSION) *exts = X509_REQ_get_extensions(req);
+ int num_exts = sk_X509_EXTENSION_num(exts);
+ for (int i = 0; i < num_exts; i++) {
+ X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, i);
+ X509_add_ext(cert.get(), ext, -1);
+ }
+ sk_X509_EXTENSION_free(exts);
+
+ pktmp.reset(X509_REQ_get_pubkey(req));
+ if (pktmp == nullptr) {
+ Dbg(dbg_ctl, "mkcrt(): Failed to get CSR public key.");
+ X509_NAME_free(tmpsubj);
+ return nullptr;
+ }
+ if (X509_set_pubkey(cert.get(), pktmp.get()) == 0) {
+ Dbg(dbg_ctl, "mkcrt(): Failed to set X509 public key.");
+ X509_NAME_free(tmpsubj);
+ return nullptr;
+ }
+
+ X509_sign(cert.get(), ca_pkey_scoped.get(), EVP_sha256());
+
+ return cert;
+}
+
+class Certifier
+{
+private:
+ std::string _lookup;
+ std::string _cert_filename;
+ std::string _servername;
+ TSVConn _client_vc;
+ TSSslConnection _sslobj;
+ SSL *_ssl;
+ TSIOBuffer _buf;
+ TSVConn _origin_conn;
+ TSEventThread _net_thread;
+ scoped_X509 _origin_cert;
+ bool _delete_cert_from_disk;
+ std::atomic_int _reference_count;
+ TSMutex _mutex;
+
+public:
+ Certifier()
+ : _lookup(""),
+ _client_vc(nullptr),
+ _ssl(nullptr),
+ _buf(nullptr),
+ _origin_conn(nullptr),
+ _net_thread(nullptr),
+ _delete_cert_from_disk(false),
+ _reference_count(1),
+ _mutex(nullptr)
+
+ {
+ }
+ void
+ AddReference()
+ {
+ int cur_rc = ++_reference_count;
+ Dbg(dbg_ctl, "addref to %p:%d", this, cur_rc);
+ }
+ bool
+ ReleaseReference()
+ {
+ int cur_rc = --_reference_count;
+ Dbg(dbg_ctl, "releaseref to %p:%d", this, cur_rc);
+ bool ret = cur_rc == 0;
+ if (ret)
+ delete this;
+ return ret;
+ }
+ static int
+ cont_cert_retriever(TSCont contp, TSEvent event, void *edata)
+ {
+ TSVConn ssl_vc = reinterpret_cast<TSVConn>(edata);
+ TSSslConnection sslobj = TSVConnSslConnectionGet(ssl_vc);
+ SSL *ssl = reinterpret_cast<SSL *>(sslobj);
+ const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+
+ // If the policy argument is not set, continue on
+ int certifierPolicyIndex;
+ if (TS_SUCCESS != TSUserArgIndexReserve(TS_USER_ARGS_VCONN, CertifierPolicyTag, "Certifier policy", &certifierPolicyIndex)) {
+ TSError("Failed to register IDS policy index");
+ return TS_ERROR;
+ }
+ void *val = TSUserArgGet(ssl_vc, certifierPolicyIndex);
+ if (val == nullptr) {
+ TSVConnReenable(ssl_vc);
+ return TS_SUCCESS;
+ }
+
+ if (servername != nullptr && strlen(servername) > 254) {
+ Dbg(dbg_ctl, "Invalid servername -- greater than 255 characters");
+ TSVConnReenable(ssl_vc);
+ } else {
+ char lookup[300];
+ SSL_CTX *ref_ctx = nullptr;
+
+ char tempaddress[52];
+
+ auto target = TSNetVConnLocalAddrGet(ssl_vc);
+ sprintf(lookup, "[%s][%s]v2", servername ? servername : "", TSIPNPToP(target, tempaddress, 52));
+
+ bool wontdo = false;
+ bool delete_cert_from_disk = false;
+ bool lookup_scheduled = false;
+ ref_ctx = ssl_list->lookup_and_create(lookup, ssl_vc, wontdo, delete_cert_from_disk, lookup_scheduled);
+ if (lookup_scheduled) {
+ Dbg(dbg_ctl, "cert_retriever(): cert is already running for %s, connection %p is attached", lookup, ssl_vc);
+ } else if (wontdo) {
+ Dbg(dbg_ctl, "cert_retriever(): Won't generate cert for %s", lookup);
+ TSVConnReenable(ssl_vc);
+ } else if (nullptr != ref_ctx) {
+ // Use existing context
+ Dbg(dbg_ctl, "cert_retriever(): Reuse existing cert and context for %s", servername);
+ SSL_set_SSL_CTX(ssl, ref_ctx);
+ TSVConnReenable(ssl_vc);
+ } else {
+ auto c = new Certifier();
+ c->_delete_cert_from_disk = delete_cert_from_disk;
+ c->ScheduleRetrieveFromDisk(std::string(servername ? servername : ""), std::string(lookup), ssl_vc, sslobj, ssl);
+ // If no existing context, schedule TASK thread to generate
+ }
+ }
+ return TS_SUCCESS;
+ }
+
+ TSMutex
+ GetCertifierMutex()
+ {
+ if (_mutex == nullptr) {
+ _mutex = TSMutexCreate();
+ }
+ return _mutex;
+ }
+
+ int
+ ScheduleRetrieveFromDisk(const std::string &servername, const std::string &lookup, TSVConn client_vc,
+ TSSslConnection client_ssl_conn, SSL *client_ssl)
+ {
+ Dbg(dbg_ctl, "Scheduling retrieve from disk %p %s:%s", this, servername.c_str(), lookup.c_str());
+ _servername = servername;
+ _lookup = lookup;
+ _client_vc = client_vc;
+ _ssl = client_ssl;
+ _sslobj = client_ssl_conn;
+ _net_thread = TSEventThreadSelf();
+ TSCont schedule_cont = TSContCreate(cont_retrieve_from_disk, GetCertifierMutex());
+
+ TSContDataSet(schedule_cont, (void *)this);
+ TSContScheduleOnPool(schedule_cont, 0, TS_THREAD_POOL_TASK);
+ return TS_SUCCESS;
+ }
+ static inline Certifier *
+ CertifierFromCont(TSCont contp)
+ {
+ return reinterpret_cast<Certifier *>(TSContDataGet(contp));
+ }
+ static int
+ cont_retrieve_from_disk(TSCont contp, TSEvent event, void *edata)
+ {
+ Certifier *c = CertifierFromCont(contp);
+ int ret = TS_ERROR;
+ if (c != nullptr) {
+ c->AddReference();
+ ret = c->handle_retrieve_from_disk();
+ c->ReleaseReference();
+ TSContDestroy(contp);
+ }
+ return ret;
+ }
+
+ int
+ handle_retrieve_from_disk()
+ {
+ scoped_X509_REQ req;
+ scoped_X509 cert;
+ int ret = TS_SUCCESS;
+ cert = ReadCertFromDisk();
+
+ if (cert != nullptr) {
+ ret = ResumeConnectionsWithCert(std::move(cert));
+ ReleaseReference();
+ } else {
+ if (!sign_enabled) {
+ ResumeConnectionsWithWontDo();
+ ReleaseReference();
+ } else {
+ ret = ScheduleProbeOrigin();
+ }
+ }
+ return ret;
+ }
+ int
+ ScheduleProbeOrigin()
+ {
+ TSCont schedule_cont = TSContCreate(cont_probe_origin, GetCertifierMutex());
+ TSContDataSet(schedule_cont, (void *)this);
+ TSContScheduleOnThread(schedule_cont, 0, _net_thread);
+ return TS_SUCCESS;
+ }
+ int
+ ScheduleCreateCert()
+ {
+ TSCont schedule_cont = TSContCreate(cont_create_cert, GetCertifierMutex());
+ TSContDataSet(schedule_cont, (void *)this);
+ TSContScheduleOnPool(schedule_cont, 0, TS_THREAD_POOL_TASK);
+ return TS_SUCCESS;
+ }
+
+ bool
+ hex_encode(const char *from_data, char *to_data, int to_len)
+ {
+ int i = 0;
+ int j = 0;
+ char hex_digits[] = "0123456789abcdef";
+ while (from_data[i] != '\0' && j < to_len - 1) {
+ if (isalnum(from_data[i])) {
+ to_data[j++] = from_data[i];
+ } else {
+ if (j + 3 >= to_len - 1) {
+ break;
+ }
+ to_data[j++] = '-';
+ to_data[j++] = hex_digits[(from_data[i] >> 4) & 0x0f];
+ to_data[j++] = hex_digits[from_data[i] & 0x0f];
+ }
+ i++;
+ }
+ to_data[j] = '\0';
+ return (from_data[i] == '\0');
+ }
+
+ // Todo : change name because it can also delete cert
+ scoped_X509
+ ReadCertFromDisk()
+ {
+ scoped_X509 cert;
+ /// Calculate hash and path, try certs on disk first
+ unsigned char digest[MD5_DIGEST_LENGTH];
+ MD5(reinterpret_cast<unsigned char const *>(_lookup.data()), _lookup.length(), digest);
+ char md5String[5];
+ sprintf(md5String, "%02hhx%02hhx", digest[0], digest[1]);
+ std::string path = store_path + "/" + std::string(md5String, 3);
+ // Todo : _lookup should be encoded here -- all the non alpha-numeric characters should be encoded
+ char encoded_lookup[950];
+ hex_encode(_lookup.c_str(), encoded_lookup, 950);
+ std::string encoded_lookup_cstr = std::string(encoded_lookup);
+ _cert_filename = path + '/' + encoded_lookup_cstr + ".crt";
+
+ // if delete_cert from disk is true delete the file from disk and return an empty scopedX509
+
+ struct stat st;
+ FILE *fp = nullptr;
+
+ int stat_retval = stat(path.c_str(), &st);
+
+ if (this->_delete_cert_from_disk && stat_retval != -1) {
+ if (std::remove(_cert_filename.c_str())) {
+ Dbg(dbg_ctl, "Could not delete cert file %s", _cert_filename.c_str());
+ }
+ // Todo : set delete_cert to false
+ Dbg(dbg_ctl, "Successfully deleted expired cert %s", _cert_filename.c_str());
+ cert.reset(nullptr);
+ return cert;
+ }
+
+ /// If directory doesn't exist, create one
+ if (stat_retval == -1) {
+ mkdir(path.c_str(), 0755);
+ } else {
+ /// Try open the file if directory exists
+ fp = fopen(_cert_filename.c_str(), "rt");
+ }
+ Dbg(dbg_ctl, "shadow_cert_generator(): Cert file is expected at %s", _cert_filename.c_str());
+ /// If cert file exists and is readable
+ if (fp != nullptr) {
+ cert.reset(PEM_read_X509(fp, nullptr, nullptr, nullptr));
+ fclose(fp);
+
+ if (cert == nullptr) {
+ /// Problem with cert file / openssl read
+ TSError("[%s] [shadow_cert_generator] Problem with loading certs", PLUGIN_NAME);
+ std::remove(_cert_filename.c_str());
+ } else {
+ Dbg(dbg_ctl, "shadow_cert_generator(): Loaded cert from file");
+ const ASN1_TIME *expdate_origin_cert = X509_get0_notAfter(cert.get());
+ // Todo : Do we need to check begin dates here ?
+ // const ASN1_TIME *startdate_origin_cert = X509_get0_notBefore(cert.get());
+ // if cert is expired or expires with ASSUME_EXPIRED_TIME delete and probe origin
+ if (ASN1_TIME_cmp_time_t(expdate_origin_cert, (time(nullptr) + ASSUME_EXPIRED_TIME)) < 0) {
+ std::remove(_cert_filename.c_str());
+ cert.reset(nullptr);
+ Dbg(dbg_ctl, "Cert read from file is expired -- it will be deleted");
+ }
+ }
+ }
+ return cert;
+ }
+ void
+ ResumeConnectionsWithWontDo()
+ {
+ std::queue<void *> localQ;
+ // Todo: add reason in logging
+ // Dbg(dbg_ctl, "shadow_cert_generator(): No certs found and dynamic generation disabled. Marked as wontdo.");
+ // There won't be certs available. Mark this servername as wontdo
+ // Pass on as if plugin doesn't exist
+
+ time_t current_time = time(nullptr);
+
+ // TODO : add an exp time here as well current time + 5 min
+ ssl_list->setup_data_ctx(_lookup, localQ, nullptr, nullptr, true, current_time + RETRY_TIME, -1);
+ while (!localQ.empty()) {
+ // Dbg(dbg_ctl, "\tClearing the queue size %lu", localQ.size());
+ TSVConn ssl_vc = reinterpret_cast<TSVConn>(localQ.front());
+ Dbg(dbg_ctl, "Resuming connection %p", ssl_vc);
+ localQ.pop();
+ TSVConnReenable(ssl_vc);
+ }
+ //_deleteme=true;
+ }
+ scoped_X509
+ CreateCert()
+ {
+ FILE *fp = nullptr;
+ scoped_X509_REQ req;
+ scoped_X509 cert;
+
+ // Todo(move out)
+
+ Dbg(dbg_ctl, "shadow_cert_generator(): Creating shadow certs");
+
+ /// Get serial number
+ TSMutexLock(serial_mutex);
+ int serial = ca_serial++;
+
+ /// Write to serial file with lock held
+ if (serial_file) {
+ serial_file.seekp(0, serial_file.beg); ///< Reset to beginning fo file
+ serial_file << serial << "\n";
+ }
+
+ TSMutexUnlock(serial_mutex);
+
+ const ASN1_TIME *expdate_origin_cert = X509_get0_notAfter(_origin_cert.get());
+ const ASN1_TIME *startdate_origin_cert = X509_get0_notBefore(_origin_cert.get());
+
+ /*struct tm exp_tm = {0};
+ struct tm begin_tm = {0};
+ int ret1 = ASN1_TIME_to_tm(expdate_origin_cert, &exp_tm);
+ int ret2 = ASN1_TIME_to_tm(startdate_origin_cert, &begin_tm);
+ Dbg(dbg_ctl, "checking asn1 ret values %d %d %d ", ret1, ret2, exp_tm.tm_hour);
+ if (ret1 && ret2) {
+ this->_cert_exp_date = mktime(&exp_tm);
+ this->_cert_begin_date = mktime(&begin_tm);
+ }
+ else {
+ Dbg(dbg_ctl, "Expiration/Begin date extraction from origin cert failed");
+ }*/
+
+ /// Create CSR and cert
+ req = mkcsr(_origin_cert);
+ if (req == nullptr) {
+ Dbg(dbg_ctl, "[shadow_cert_generator] CSR generation failed");
+
+ ssl_list->set_schedule(_lookup, false);
+ return nullptr;
+ }
+ cert = mkcrt(req.get(), serial, expdate_origin_cert, startdate_origin_cert);
+
+ if (cert == nullptr) {
+ Dbg(dbg_ctl, "[shadow_cert_generator] Cert generation failed");
+
+ ssl_list->set_schedule(_lookup, false);
+ return nullptr;
+ }
+
+ /// Write certs to file
+ if ((fp = fopen(_cert_filename.c_str(), "w+")) == nullptr) {
+ Dbg(dbg_ctl, "shadow_cert_generator(): Error opening file: %s\n", strerror(errno));
+ } else {
+ if (!PEM_write_X509(fp, cert.get())) {
+ Dbg(dbg_ctl, "shadow_cert_generator(): Error writing cert to disk");
+ }
+ fclose(fp);
+ }
+ return cert;
+ }
+ int
+ ResumeConnectionsWithCert(scoped_X509 cert)
+ {
+ SSL_CTX *ref_ctx;
+ scoped_SSL_CTX ctx;
+ std::queue<void *> localQ;
+
+ // _deleteme=true;
+
+ /// Create SSL context based on cert
+ ref_ctx = SSL_CTX_new(SSLv23_server_method());
+ ctx.reset(ref_ctx);
+ Dbg(dbg_ctl, "%p", this);
+ if (SSL_CTX_use_certificate(ref_ctx, cert.get()) < 1) {
+ TSError("[%s] shadow_cert_handler(): Failed to use certificate in SSL_CTX.", PLUGIN_NAME);
+
+ ssl_list->set_schedule(_lookup, false);
+ return TS_ERROR;
+ }
+ Dbg(dbg_ctl, "%p", this);
+ if (SSL_CTX_use_PrivateKey(ref_ctx, ca_pkey_scoped.get()) < 1) {
+ TSError("[%s] shadow_cert_handler(): Failed to use private key in SSL_CTX.", PLUGIN_NAME);
+
+ ssl_list->set_schedule(_lookup, false);
+ return TS_ERROR;
+ }
+ Dbg(dbg_ctl, "%p", this);
+ Dbg(dbg_ctl, "shadow_cert_generator(): cert and context ready, clearing the queue");
+ // Todo: determine expiry for the ssl list
+ // if curnt time before end date - 10 min then exp date = end date
+ // else expdate = curnt time + 5min
+ // times must be in UTC
+ // Todo : Pull end date from cert
+
+ const ASN1_TIME *expdate_origin_cert = X509_get0_notAfter(cert.get());
+ const ASN1_TIME *startdate_origin_cert = X509_get0_notBefore(cert.get());
+ struct tm exp_tm;
+ struct tm begin_tm;
+ int ret1 = ASN1_TIME_to_tm(expdate_origin_cert, &exp_tm);
+ int ret2 = ASN1_TIME_to_tm(startdate_origin_cert, &begin_tm);
+ time_t cert_exp_date = time(nullptr), cert_begin_date = time(nullptr);
+ Dbg(dbg_ctl, "checking asn1 ret values %d %d %d ", ret1, ret2, exp_tm.tm_hour);
+ if (ret1 && ret2) {
+ cert_exp_date = timegm(&exp_tm);
+ cert_begin_date = timegm(&begin_tm);
+ } else {
+ Dbg(dbg_ctl, "Expiration/Begin date extraction from origin cert failed");
+ }
+
+ time_t current_time = time(nullptr);
+ if (current_time < (cert_exp_date - ASSUME_EXPIRED_TIME)) {
+ current_time = cert_exp_date;
+ } else {
+ // todo : make this delay configurable
+ current_time = current_time + RETRY_TIME;
+ }
+
+ ssl_list->setup_data_ctx(_lookup, localQ, std::move(ctx), std::move(cert), false, current_time, cert_begin_date);
+ Dbg(dbg_ctl, "%p", this);
+ /// Clear the queue by setting context for each and reenable them
+ while (!localQ.empty()) {
+ Dbg(dbg_ctl, "\tClearing the queue size %lu", localQ.size());
+ TSVConn ssl_vc = reinterpret_cast<TSVConn>(localQ.front());
+ localQ.pop();
+ TSSslConnection sslobj = TSVConnSslConnectionGet(ssl_vc);
+ SSL *ssl = reinterpret_cast<SSL *>(sslobj);
+ SSL_set_SSL_CTX(ssl, ref_ctx);
+ TSVConnReenable(ssl_vc);
+ }
+ return TS_SUCCESS;
+ }
+
+ static int
+ cont_probe_origin(TSCont contp, TSEvent event, void *edata)
+ {
+ Certifier *c = CertifierFromCont(contp);
+ // TODO
+ int ret = TS_ERROR;
+ bool destroy_cont = false;
+ Dbg(dbg_ctl, "probe origin %d %p %p", event, edata, c);
+ if (c != nullptr) {
+ c->AddReference();
+ ret = c->HandleProbeOrigin(contp, event, edata, destroy_cont);
+ if (c->ReleaseReference() || destroy_cont) {
+ TSContDestroy(contp);
+ }
+ }
+ return ret;
+ }
+
+ int
+ HandleProbeOrigin(TSCont contp, TSEvent event, void *edata, bool &destroy_cont)
+ {
+ destroy_cont = false;
+ switch (event) {
+ case TS_EVENT_IMMEDIATE: {
+ TSNetConnectOptions opt;
+ memset(&opt, 0, sizeof(TSNetConnectOptions));
+ opt.to = TSNetVConnLocalAddrGet(this->_client_vc);
+ opt.from = TSNetVConnRemoteAddrGet(this->_client_vc);
+ opt.use_from_port = false;
+ opt.tls = true;
+ opt.verify_origin_cert = true;
+ opt.sni_host_name = _servername.length() != 0 ? _servername.c_str() : nullptr;
+ char tempbuf[2][52];
+ Dbg(dbg_ctl, "from: %s to: %s", TSIPNPToP(opt.from, tempbuf[0], 52), TSIPNPToP(opt.to, tempbuf[1], 52));
+ TSNetConnectAdvanced(contp, &opt);
+ return TS_SUCCESS;
+ } break;
+ case TS_EVENT_NET_CONNECT: {
+ _origin_conn = reinterpret_cast<TSVConn>(edata);
+ _buf = TSIOBufferCreate();
+ TSIOBufferWrite(_buf, "", 0);
+ TSIOBufferReader reader = TSIOBufferReaderAlloc(_buf);
+ TSVConnWrite(_origin_conn, contp, reader, 1);
+ TSVConnReenable(_origin_conn);
+ } break;
+ case TS_EVENT_VCONN_WRITE_READY:
+ case TS_EVENT_VCONN_WRITE_COMPLETE: {
+ TSSslConnection sslobj = TSVConnSslConnectionGet(_origin_conn);
+ SSL *ssl = reinterpret_cast<SSL *>(sslobj);
+ X509 *cert = SSL_get_peer_certificate(ssl);
+ if (cert) {
+ this->_origin_cert.reset(cert);
+ ScheduleCreateCert();
+ TSVConnClose(_origin_conn);
+ destroy_cont = true;
+ } else {
+ TSVConnClose(_origin_conn);
+ ResumeConnectionsWithWontDo();
+ ReleaseReference();
+ }
+
+ } break;
+ case TS_EVENT_NET_CONNECT_FAILED:
+ case TS_EVENT_ERROR: {
+ Dbg(dbg_ctl, "Unexpected event %d", event);
+ // TODO: set timeout on errored probe connections
+ ResumeConnectionsWithWontDo();
+ if (_origin_conn)
+ TSVConnAbort(_origin_conn, 1);
+ ReleaseReference();
+ } break;
+ default: {
+ Dbg(dbg_ctl, "Unexpected event %d", event);
+ // TODO: set timeout on errored probe connections
+ ResumeConnectionsWithWontDo();
+ if (_origin_conn)
+ TSVConnAbort(_origin_conn, 1);
+ ReleaseReference();
+ } break;
+ }
+ return TS_SUCCESS;
+ }
+ static int
+ cont_create_cert(TSCont contp, TSEvent event, void *edata)
+ {
+ Certifier *c = CertifierFromCont(contp);
+ // TODO
+ int ret = TS_ERROR;
+ if (c != nullptr) {
+ c->AddReference();
+ ret = c->HandleCreateCert();
+ TSContDestroy(contp);
+ c->ReleaseReference();
+ }
+ return ret;
+ }
+ int
+ HandleCreateCert()
+ {
+ scoped_X509_REQ req;
+ scoped_X509 cert;
+ int ret = TS_ERROR;
+ /// Create CSR and cert
+ cert = std::move(CreateCert());
+ Dbg(dbg_ctl, "Cert ptr:%p", cert.get());
+ if (cert != nullptr) {
+ ret = this->ResumeConnectionsWithCert(std::move(cert));
+ } else {
+ this->ResumeConnectionsWithWontDo();
+ }
+ // req = mkcsr_from_cert(_origin_cert);
+ Dbg(dbg_ctl, "Cert ptr:%p", cert.get());
+ ReleaseReference();
+ return ret;
+ }
+
+ // void
+ // ExtractDatesFromCert()
+};
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+ Dbg(dbg_ctl, "initializing plugin");
+ Dbg(dbg_ctl, "OpenSSL version %s", OpenSSL_version(OPENSSL_VERSION));
+ Dbg(dbg_ctl, "OpenSSL built on %s", OpenSSL_version(OPENSSL_BUILT_ON));
+
+ // Initialization data and callback
+ TSPluginRegistrationInfo info;
+ TSCont cb_shadow = nullptr;
+ info.plugin_name = "avx_certifier";
+ info.vendor_name = "Aviatrix";
+ info.support_email = "info@aviatrix.com";
+
+ const char *key = nullptr;
+ const char *cert = nullptr;
+ const char *serial = nullptr;
+
+ // Read options from plugin.config
+ static const struct option longopts[] = {
+ {"sign-cert", required_argument, nullptr, 'c'},
+ {"sign-key", required_argument, nullptr, 'k'},
+ {"sign-serial", required_argument, nullptr, 'r'},
+ {"max", required_argument, nullptr, 'm'},
+ {"store", required_argument, nullptr, 's'},
+ {nullptr, no_argument, nullptr, 0 }
+ };
+
+ int opt = 0;
+
+ while (opt >= 0) {
+ opt = getopt_long(argc, const_cast<char *const *>(argv), "c:k:r:m:s:", longopts, nullptr);
+ switch (opt) {
+ case 'c': {
+ cert = optarg;
+ break;
+ }
+ case 'k': {
+ key = optarg;
+ break;
+ }
+ case 'r': {
+ serial = optarg;
+ break;
+ }
+ case 'm': {
+ ssl_list.reset(new SslLRUList(static_cast<int>(std::strtol(optarg, nullptr, 0))));
+ break;
+ }
+ case 's': {
+ store_path = std::string(optarg);
+ break;
+ }
+ case -1:
+ case '?':
+ break;
+ default:
+ Dbg(dbg_ctl, "Unexpected options.");
+ TSError("[%s] Unexpected options error.", PLUGIN_NAME);
+ return;
+ }
+ }
+
+ // Register plugin and create callback
+ if (TSPluginRegister(&info) != TS_SUCCESS) {
+ TSError("[%s] Unable to initialize plugin (disabled). Failed to register plugin.", PLUGIN_NAME);
+ } else if ((cb_shadow = TSContCreate(Certifier::cont_cert_retriever, nullptr)) == nullptr) {
+ TSError("[%s] Unable to initialize plugin (disabled). Failed to create shadow cert cb.", PLUGIN_NAME);
+ } else {
+ if ((sign_enabled = cert && key && serial)) {
+ // Dynamic cert generation enabled. Initialize CA key, cert and serial
+ // To comply to openssl, key and cert file are opened as FILE*
+ FILE *fp = nullptr;
+ if ((fp = fopen(cert, "rt")) == nullptr) {
+ Dbg(dbg_ctl, "fopen() error is %d: %s for %s", errno, strerror(errno), cert);
+ TSError("[%s] Unable to initialize plugin. Failed to open ca cert.", PLUGIN_NAME);
+ return;
+ }
+ ca_cert_scoped.reset(PEM_read_X509(fp, nullptr, nullptr, nullptr));
+ fclose(fp);
+
+ if ((fp = fopen(key, "rt")) == nullptr) {
+ Dbg(dbg_ctl, "fopen() error is %d: %s for %s", errno, strerror(errno), key);
+ TSError("[%s] Unable to initialize plugin. Failed to open ca key.", PLUGIN_NAME);
+ return;
+ }
+ ca_pkey_scoped.reset(PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr));
+ fclose(fp);
+
+ if (ca_pkey_scoped == nullptr || ca_cert_scoped == nullptr) {
+ Dbg(dbg_ctl, "PEM_read failed to read %s %s", ca_pkey_scoped ? "" : "pkey", ca_cert_scoped ? "" : "cert");
+ TSError("[%s] Unable to initialize plugin. Failed to read ca key/cert.", PLUGIN_NAME);
+ return;
+ }
+
+ // Read serial file
+ serial_file.open(serial, std::fstream::in | std::fstream::out);
+ if (!serial_file.is_open()) {
+ Dbg(dbg_ctl, "Failed to open serial file.");
+ TSError("[%s] Unable to initialize plugin. Failed to open serial.", PLUGIN_NAME);
+ return;
+ }
+ /// Initialize mutex and serial number
+ serial_mutex = TSMutexCreate();
+ ca_serial = 0;
+
+ serial_file.seekg(0, serial_file.beg);
+ serial_file >> ca_serial;
+ if (serial_file.bad() || serial_file.fail()) {
+ ca_serial = 0;
+ }
+ }
+ Dbg(dbg_ctl, "Dynamic cert generation %s", sign_enabled ? "enabled" : "disabled");
+
+ /// Add global hooks
+ TSHttpHookAdd(TS_SSL_CERT_HOOK, cb_shadow);
+ }
+
+ return;
+}
diff --git a/aviatrix/plugins/avx_policy_driver/.gitignore b/aviatrix/plugins/avx_policy_driver/.gitignore
new file mode 100644
index 0000000000..4ade8858ac
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/.gitignore
@@ -0,0 +1,3 @@
+proto
+test_server
+test_client
\ No newline at end of file
diff --git a/aviatrix/plugins/avx_policy_driver/CMakeLists.txt b/aviatrix/plugins/avx_policy_driver/CMakeLists.txt
new file mode 100644
index 0000000000..9a5243efd2
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/CMakeLists.txt
@@ -0,0 +1,67 @@
+#set(ENV{PKG_CONFIG_PATH} "/home/ubuntu/.local:/opt/ats/lib/pkgconfig")
+
+find_package(PkgConfig REQUIRED)
+find_package(protobuf CONFIG REQUIRED)
+find_package(gRPC CONFIG REQUIRED)
+#pkg_check_modules(GRPC REQUIRED IMPORTED_TARGET grpc++)
+#pkg_check_modules(PROTOBUF REQUIRED IMPORTED_TARGET protobuf)
+
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/generated")
+set(PROTO_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
+
+add_library(
+ proto-objects OBJECT
+ "${CLOUDN_SOURCE_ROOT}/proto/common/types.proto" "${CLOUDN_SOURCE_ROOT}/proto/conduit/v2/layer7.proto"
+ "${CLOUDN_SOURCE_ROOT}/proto/conduit/v2/microseg.proto"
+ "${CLOUDN_SOURCE_ROOT}/proto/conduit/v2/external_groups.proto"
+)
+
+target_link_libraries(proto-objects PUBLIC protobuf::libprotobuf gRPC::grpc++)
+target_include_directories(proto-objects PUBLIC "$<BUILD_INTERFACE:${PROTO_BINARY_DIR}>")
+
+protobuf_generate(TARGET proto-objects IMPORT_DIRS "${CLOUDN_SOURCE_ROOT}" PROTOC_OUT_DIR "${PROTO_BINARY_DIR}")
+
+protobuf_generate(
+ TARGET
+ proto-objects
+ LANGUAGE
+ grpc
+ GENERATE_EXTENSIONS
+ .grpc.pb.h
+ .grpc.pb.cc
+ PLUGIN
+ "protoc-gen-grpc=\$<TARGET_FILE:gRPC::grpc_cpp_plugin>"
+ IMPORT_DIRS
+ ${CLOUDN_SOURCE_ROOT}
+ PROTOC_OUT_DIR
+ "${PROTO_BINARY_DIR}"
+)
+
+add_atsplugin(
+ avx_policy_driver
+ policy_driver.cc
+ logging.cc
+ policy_client.cc
+ web_filter.cc
+ ${PROTO_BINARY_DIR}/proto/conduit/v2/layer7.pb.cc
+ ${PROTO_BINARY_DIR}/proto/conduit/v2/layer7.grpc.pb.cc
+ ${PROTO_BINARY_DIR}/proto/conduit/v2/microseg.pb.cc
+ ${PROTO_BINARY_DIR}/proto/conduit/v2/external_groups.pb.cc
+ ${PROTO_BINARY_DIR}/proto/common/types.pb.cc
+)
+
+target_include_directories(
+ avx_policy_driver PUBLIC "$<BUILD_INTERFACE:${PROTO_BINARY_DIR}>" ${GRPC_INCLUDES} "~/.local/include"
+)
+target_link_libraries(
+ avx_policy_driver
+ PRIVATE libswoc::libswoc
+ ${PROTOBUF_LIBRARY}
+ ${GRPC_LIBRARY}
+ OpenSSL::Crypto
+ OpenSSL::SSL
+ protobuf::libprotobuf
+ gRPC::grpc++
+)
+
+verify_global_plugin(avx_policy_driver)
diff --git a/aviatrix/plugins/avx_policy_driver/Makefile.inc b/aviatrix/plugins/avx_policy_driver/Makefile.inc
new file mode 100644
index 0000000000..5d3260fc73
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/Makefile.inc
@@ -0,0 +1,116 @@
+pkglib_LTLIBRARIES += avx_policy_driver/avx_policy_driver.la
+
+# Generic link flags for all the artifacts that we build here. Note
+# that we use AM_LDFLAGS to pick up important configure-time settings,
+# like rpath.
+AVX_POLICY_DRIVER_LD_FLAGS = \
+ $(GRPC_LDFLAGS) \
+ $(LIBSWOC_LDFLAGS) \
+ $(AM_LDFLAGS)
+
+AVX_POLICY_DRIVER_CPP_FLAGS = \
+ $(AM_CPPFLAGS) \
+ -Iavx_policy_driver \
+ $(GRPC_CPPFLAGS) \
+ $(LIBSWOC_CPPFLAGS)
+
+AVX_POLICY_DRIVER_C_FLAGS = \
+ $(AM_CFLAGS) \
+ $(GRPC_CFLAGS) \
+ $(LIBSWOC_CFLAGS)
+
+avx_policy_driver_avx_policy_driver_la_LDFLAGS = $(AVX_POLICY_DRIVER_LD_FLAGS)
+avx_policy_driver_avx_policy_driver_la_CPPFLAGS = $(AVX_POLICY_DRIVER_CPP_FLAGS)
+avx_policy_driver_avx_policy_driver_la_CFLAGS = $(AVX_POLICY_DRIVER_C_FLAGS)
+
+AVX_LAYER7_PROTO_SRCS := \
+ avx_policy_driver/proto/conduit/v2/layer7.pb.cc \
+ avx_policy_driver/proto/conduit/v2/layer7.grpc.pb.cc
+
+AVX_LAYER7_PROTO_HDRS := \
+ avx_policy_driver/proto/conduit/v2/layer7.pb.h \
+ avx_policy_driver/proto/conduit/v2/layer7.grpc.pb.h
+AVX_MICROSEG_PROTO_HDRS := avx_policy_driver/proto/conduit/v2/microseg.pb.h
+AVX_MICROSEG_PROTO_SRCS := avx_policy_driver/proto/conduit/v2/microseg.pb.cc
+AVX_EXTGROUPS_PROTO_HDRS := avx_policy_driver/proto/conduit/v2/external_groups.pb.h
+AVX_EXTGROUPS_PROTO_SRCS := avx_policy_driver/proto/conduit/v2/external_groups.pb.cc
+AVX_TYPES_PROTO_HDRS := avx_policy_driver/proto/common/types.pb.h
+AVX_TYPES_PROTO_SRCS := avx_policy_driver/proto/common/types.pb.cc
+
+policy_driver_PROTOBUF_SRCS = \
+ $(AVX_LAYER7_PROTO_SRCS) \
+ $(AVX_MICROSEG_PROTO_SRCS) \
+ $(AVX_EXTGROUPS_PROTO_SRCS) \
+ $(AVX_TYPES_PROTO_SRCS)
+
+policy_driver_PROTOBUF_HDRS = \
+ $(AVX_LAYER7_PROTO_HDRS) \
+ $(AVX_MICROSEG_PROTO_HDRS) \
+ $(AVX_EXTGROUPS_PROTO_HDRS) \
+ $(AVX_TYPES_PROTO_HDRS)
+
+# Tell automake to clean the generated sources.
+CLEANFILES += $(policy_driver_PROTOBUF_SRCS) $(policy_driver_PROTOBUF_HDRS)
+
+$(AVX_LAYER7_PROTO_HDRS) $(AVX_LAYER7_PROTO_SRCS): $(CLOUDN_SOURCE_ROOT)/proto/conduit/v2/layer7.proto
+ $(AM_V_at)$(MKDIR_P) avx_policy_driver
+ $(PROTOC) -I$(CLOUDN_SOURCE_ROOT) --cpp_out=avx_policy_driver --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN) --grpc_out=avx_policy_driver $<
+
+$(AVX_MICROSEG_PROTO_HDRS) $(AVX_MICROSEG_PROTO_SRCS): $(CLOUDN_SOURCE_ROOT)/proto/conduit/v2/microseg.proto
+ $(AM_V_at)$(MKDIR_P) avx_policy_driver
+ $(PROTOC) -I$(CLOUDN_SOURCE_ROOT) --cpp_out=avx_policy_driver $<
+
+$(AVX_EXTGROUPS_PROTO_HDRS) $(AVX_EXTGROUPS_PROTO_SRCS): $(CLOUDN_SOURCE_ROOT)/proto/conduit/v2/external_groups.proto
+ $(AM_V_at)$(MKDIR_P) avx_policy_driver
+ $(PROTOC) -I$(CLOUDN_SOURCE_ROOT) --cpp_out=avx_policy_driver $<
+
+$(AVX_TYPES_PROTO_HDRS) $(AVX_TYPES_PROTO_SRCS): $(CLOUDN_SOURCE_ROOT)/proto/common/types.proto
+ $(AM_V_at)$(MKDIR_P) avx_policy_driver
+ $(PROTOC) -I$(CLOUDN_SOURCE_ROOT) --cpp_out=avx_policy_driver $<
+
+avx_policy_driver_avx_policy_driver_la_SOURCES = \
+ $(policy_driver_PROTOBUF_SRCS) \
+ avx_policy_driver/web_filter.cc \
+ avx_policy_driver/policy_driver.cc \
+ avx_policy_driver/policy_client.cc \
+ avx_policy_driver/logging.cc
+
+# Make the sources depend on the headers since any sources may
+# included generated headers. We could also use automake BUILT_SOURCES
+# to solve this, but this works too.
+$(avx_policy_driver_avx_policy_driver_la_SOURCES): $(policy_driver_PROTOBUF_HDRS)
+
+noinst_PROGRAMS += \
+ avx_policy_driver/test_server \
+ avx_policy_driver/test_client
+
+avx_policy_driver_test_server_SOURCES = \
+ avx_policy_driver/test_server.cc \
+ $(policy_driver_PROTOBUF_SRCS)
+
+$(avx_policy_driver_test_server_SOURCES): $(policy_driver_PROTOBUF_HDRS)
+
+avx_policy_driver_test_server_LDFLAGS = $(AVX_POLICY_DRIVER_LD_FLAGS)
+avx_policy_driver_test_server_CPPFLAGS = $(AVX_POLICY_DRIVER_CPP_FLAGS)
+avx_policy_driver_test_server_CFLAGS = $(AVX_POLICY_DRIVER_C_FLAGS)
+
+avx_policy_driver_test_client_SOURCES = \
+ avx_policy_driver/test_client.cc \
+ $(policy_driver_PROTOBUF_SRCS)
+
+$(avx_policy_driver_test_client_SOURCES): $(policy_driver_PROTOBUF_HDRS)
+
+avx_policy_driver_test_client_LDFLAGS = $(AVX_POLICY_DRIVER_LD_FLAGS)
+avx_policy_driver_test_client_CPPFLAGS = $(AVX_POLICY_DRIVER_CPP_FLAGS)
+avx_policy_driver_test_client_CFLAGS = $(AVX_POLICY_DRIVER_C_FLAGS)
+
+check_PROGRAMS += avx_policy_driver/unit_test_hitcounter
+
+avx_policy_driver_unit_test_hitcounter_LDFLAGS = $(AVX_POLICY_DRIVER_LD_FLAGS)
+avx_policy_driver_unit_test_hitcounter_CPPFLAGS = $(AVX_POLICY_DRIVER_CPP_FLAGS)
+avx_policy_driver_unit_test_hitcounter_CFLAGS = $(AVX_POLICY_DRIVER_C_FLAGS)
+
+avx_policy_driver_unit_test_hitcounter_SOURCES = \
+ avx_policy_driver/unit_test_hitcounter.cc
+
+# vim: ft=make ts=8 sw=8 et:
diff --git a/aviatrix/plugins/avx_policy_driver/jsonwriter.h b/aviatrix/plugins/avx_policy_driver/jsonwriter.h
new file mode 100644
index 0000000000..3ce8220246
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/jsonwriter.h
@@ -0,0 +1,291 @@
+#pragma once
+#include <string>
+
+class jsonwriter
+{
+ enum jsonwriter_mode {
+ none = 0x001000001,
+ in_object = 0x010000002,
+ in_array = 0x101000003,
+ in_field = 0x101000004,
+ error = 0x000000099,
+ overflowed = 0x00000009,
+ finished = 0x000000100,
+ pre_object_array = 0x001000000,
+ pre_field = 0x010000000,
+ pre_literal = 0x100000000
+
+ };
+ struct mode_element {
+ jsonwriter_mode mode;
+ bool first;
+ };
+ struct mode_element mode_stack[20];
+ int mode_stack_depth = 0;
+ char *_message = nullptr;
+ char *_current = nullptr;
+ char *_end = nullptr;
+ bool _valid = false;
+
+public:
+ jsonwriter(char *message, int length) : _message(message), _current(message), _end(message + length - 1)
+ {
+ mode_stack[0].first = true;
+ mode_stack[0].mode = none;
+ }
+ inline jsonwriter_mode
+ current_mode()
+ {
+ return mode_stack[mode_stack_depth].mode;
+ }
+
+ inline bool
+ first()
+ {
+ return mode_stack[mode_stack_depth].first;
+ }
+ inline void
+ reset_first()
+ {
+ mode_stack[mode_stack_depth].first = false;
+ }
+
+ inline void
+ set_mode(jsonwriter_mode mode)
+ {
+ mode_stack_depth++;
+ mode_stack[mode_stack_depth].first = true;
+ mode_stack[mode_stack_depth].mode = mode;
+ }
+ inline void
+ pop_mode()
+ {
+ mode_stack_depth--;
+ }
+ bool
+ addpair(const char *field, const char *data, bool skip_empty = true, bool literaldata = false)
+ {
+ if (data == nullptr)
+ data = "";
+ if (data[0] == 0 && skip_empty)
+ return true;
+ if ((current_mode() & jsonwriter_mode::pre_field) != pre_field) {
+ set_mode(error);
+ return false;
+ }
+ checkfirst();
+ addjsonstring(field);
+ addstring(":");
+ if (literaldata)
+ addstring(data);
+ else
+ addjsonstring(data);
+ return _current != _end;
+ }
+ inline void
+ addstring(const char *data)
+ {
+ while (*data && _current != _end) {
+ *_current++ = *data++;
+ }
+ }
+ inline void
+ addjsonstring(const char *data)
+ {
+ addstring("\"");
+ while (*data && _current != _end) {
+ unsigned char c = (unsigned char)*data++, control = 0;
+ switch (c) {
+ case '\n':
+ control = 'n';
+ break;
+ case '\r':
+ control = 'r';
+ break;
+ case '"':
+ control = '"';
+ break;
+ case '\'':
+ control = '\'';
+ break;
+ case '\\':
+ control = '\\';
+ break;
+ case '\t':
+ control = 't';
+ break;
+ case '\b':
+ control = 'b';
+ break;
+ case '\f':
+ control = 'f';
+ break;
+ default:
+ if (c < 32) {
+ char hexstring[7];
+ sprintf(hexstring, "\\u%04x", (int)c);
+ addstring(hexstring);
+ continue;
+ }
+ *_current++ = (char)c;
+ continue;
+ }
+ if (_current == (_end - 2)) {
+ _end = _current;
+ continue;
+ }
+ *_current++ = '\\';
+ *_current++ = control;
+ }
+ addstring("\"");
+ }
+ bool
+ addfield(const char *field)
+ {
+ if ((current_mode() & jsonwriter_mode::pre_field) != pre_field) {
+ set_mode(jsonwriter_mode::error);
+ return false;
+ }
+ checkfirst();
+ set_mode(jsonwriter_mode::in_field);
+ addjsonstring(field);
+ addstring(":");
+ return true;
+ }
+ bool
+ addliteral(const char *data, bool literal_data = false)
+ {
+ bool in_field = current_mode() == jsonwriter_mode::in_field;
+ if ((current_mode() & jsonwriter_mode::pre_literal) != pre_literal) {
+ set_mode(jsonwriter_mode::error);
+ return false;
+ }
+ if (!in_field)
+ checkfirst();
+ addjsonstring(data);
+ if (in_field) {
+ pop_mode();
+ }
+ if (current_mode() == jsonwriter_mode::none) {
+ set_mode(overflow() ? jsonwriter_mode::error : jsonwriter_mode::finished);
+ }
+ return overflow();
+ }
+ bool
+ addpair(const char *field, bool data)
+ {
+ return addpair(field, data ? "true" : "false", false, true);
+ }
+
+ bool
+ addpair(const char *field, int data, bool skip_empty = true, int empty_value = 0)
+ {
+ if (skip_empty && data == empty_value)
+ return true;
+ auto sdata = std::to_string(data);
+ return addpair(field, sdata.c_str(), true, true);
+ }
+ bool
+ addpair(const char *field, long data, bool skip_empty = true, int empty_value = 0)
+ {
+ if (skip_empty && data == empty_value)
+ return true;
+ auto sdata = std::to_string(data);
+ return addpair(field, sdata.c_str(), true, true);
+ }
+ bool
+ addpair(const char *field, struct sockaddr *addr, bool skip_empty = true)
+ {
+ if ((current_mode() & jsonwriter_mode::in_object) != in_object) {
+ }
+ if (skip_empty && addr == nullptr)
+ return true;
+ return false;
+
+ // addpair(field, TSIPNPToP(addr, ip, 200), true, true);
+ }
+ void
+ checkfirst()
+ {
+ auto mode = current_mode();
+ if (!first() && (mode == jsonwriter_mode::in_array || mode == jsonwriter_mode::in_object)) {
+ addstring(",");
+ } else {
+ reset_first();
+ }
+ }
+ bool
+ open_array()
+ {
+ if ((current_mode() & jsonwriter_mode::pre_object_array) != pre_object_array) {
+ set_mode(jsonwriter_mode::error);
+ // todo, error
+ return false;
+ }
+ checkfirst();
+
+ addstring("[");
+ set_mode(jsonwriter_mode::in_array);
+ }
+ bool
+ close_array()
+ {
+ if (current_mode() != jsonwriter_mode::in_array) {
+ set_mode(jsonwriter_mode::error);
+ // todo, error
+ return false;
+ }
+ addstring("]");
+ pop_mode();
+ if (current_mode() == jsonwriter_mode::none) {
+ *_current = 0;
+ set_mode(overflow() ? jsonwriter_mode::error : jsonwriter_mode::finished);
+ }
+ if (current_mode() == jsonwriter_mode::in_field) {
+ pop_mode();
+ }
+ return current_mode() != jsonwriter_mode::error;
+ }
+ bool
+ open_object()
+ {
+ if ((current_mode() & jsonwriter_mode::pre_object_array) != pre_object_array) {
+ set_mode(jsonwriter_mode::error);
+ // todo, error
+ return false;
+ }
+ checkfirst();
+ addstring("{");
+ set_mode(jsonwriter_mode::in_object);
+ return true;
+ }
+ bool
+ close_object()
+ {
+ if (current_mode() != jsonwriter_mode::in_object) {
+ set_mode(jsonwriter_mode::error);
+ // todo, error
+ return false;
+ }
+ addstring("}");
+ pop_mode();
+ if (current_mode() == jsonwriter_mode::none) {
+ *_current = 0;
+ set_mode(overflow() ? jsonwriter_mode::error : jsonwriter_mode::finished);
+ }
+ if (current_mode() == jsonwriter_mode::in_field) {
+ pop_mode();
+ }
+ return current_mode() != jsonwriter_mode::error;
+ }
+ inline bool
+ overflow()
+ {
+ return _current == _end;
+ }
+ bool
+ valid()
+ {
+ return current_mode() == jsonwriter_mode::finished;
+ }
+};
diff --git a/aviatrix/plugins/avx_policy_driver/logging.cc b/aviatrix/plugins/avx_policy_driver/logging.cc
new file mode 100644
index 0000000000..91d51eeeb4
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/logging.cc
@@ -0,0 +1,80 @@
+#include <netinet/in.h>
+#include <syslog.h>
+#include <ts/ts.h>
+#include <inttypes.h>
+
+#include <string>
+#include "logging.h"
+#include "jsonwriter.h"
+#include "plugin.h"
+
+union raw_uuid {
+ uint64_t data[2];
+
+ struct {
+ uint32_t timeLow;
+ uint16_t timeMid;
+ uint16_t timeHighAndVersion;
+ uint8_t clockSeqAndReserved;
+ uint8_t clockSeqLow;
+ uint8_t node[6];
+ };
+};
+
+bool
+sprint_uuid(char *buf, uint64_t a, uint64_t b)
+{
+ int len = snprintf(buf, TS_UUID_STRING_LEN + 1, "%08" PRIx64 "-%04" PRIx64 "-%04" PRIx64 "-%04" PRIx64 "-%012" PRIx64, a >> 32,
+ (a >> 16 & 0xffff), (a & 0xffff), b >> 48, (b & 0xffffffffffff));
+ return (len == TS_UUID_STRING_LEN);
+}
+
+const char *priorities[] = {"LOG_EMERG", "LOG_ALERT", "LOG_CRIT", "LOG_ERROR",
+ "LOG_WARNING", "LOG_NOTICE", "LOG_INFO", "LOG_DEBUG"};
+__attribute__((noinline)) void
+policy_log(policy_log_message &msg)
+{
+ char jsonmessage[8192]; // should be safe on the stack
+ char ip[128];
+ jsonwriter w(jsonmessage, 8192);
+
+ if (msg.priority == policy_log_prio::log_emerg || msg.priority < 0 || msg.priority > policy_log_prio::log_info) {
+ msg.priority = policy_log_prio::log_info;
+ }
+ w.open_object();
+ w.addpair("priority", priorities[msg.priority]);
+ w.addpair("action", msg.action);
+ w.addpair("reason", msg.reason);
+ if (msg.src != nullptr) {
+ w.addpair("src", TSIPNToP((sockaddr *)msg.src, ip, 128));
+ w.addpair("src_port", ntohs(msg.src->sin_port));
+ }
+ if (msg.dest != nullptr) {
+ w.addpair("dest", TSIPNToP((sockaddr *)msg.dest, ip, 128));
+ w.addpair("dest_port", ntohs(msg.dest->sin_port));
+ }
+ w.addpair("enforced", msg.enforced);
+ w.addpair("ids", msg.ids);
+ w.addpair("errorcode", msg.errorcode);
+ w.addpair("message", msg.message);
+ w.addpair("stage", msg.stage);
+ w.addpair("sni_hostname", msg.sni_hostname);
+ w.addpair("decrypted_by", msg.decrypted_by);
+ w.addpair("decided_by", msg.decided_by);
+ // fixed value for now
+ w.addpair("proto", "TCP");
+ if (msg.url) {
+ // fix this to stringview
+ std::string url(msg.url);
+ if (url.length() > 4096) {
+ url = url.substr(0, 4093) + "...";
+ }
+ w.addpair("url", url.c_str());
+ }
+
+ w.addpair("timestamp", msg.timestamp != 0 ? msg.timestamp : time(nullptr));
+
+ w.close_object();
+ syslog(msg.priority, "%s", jsonmessage);
+ Dbg(dbg_ctl, "syslog: %s", jsonmessage);
+}
diff --git a/aviatrix/plugins/avx_policy_driver/logging.h b/aviatrix/plugins/avx_policy_driver/logging.h
new file mode 100644
index 0000000000..21686c0765
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/logging.h
@@ -0,0 +1,30 @@
+#pragma once
+#include <atomic>
+#include <netinet/in.h>
+#include <time.h>
+
+enum policy_log_prio { log_emerg, log_alert, log_crit, log_err, log_warning, log_notice, log_info, log_debug };
+struct policy_log_message {
+ policy_log_prio priority;
+ const char *action;
+ const char *reason;
+ struct sockaddr_in *src;
+ struct sockaddr_in *dest;
+ bool enforced;
+ int connection_id;
+ int txn_id;
+ const char *errorcode;
+ const char *stage;
+ const char *message;
+ const char *sni_hostname;
+ const char *decided_by;
+ const char *decrypted_by;
+ const char *url;
+ bool decision;
+ bool ids;
+ time_t timestamp;
+};
+
+void policy_log(policy_log_message &msg);
+
+bool sprint_uuid(char *buf, uint64_t a, uint64_t b);
diff --git a/aviatrix/plugins/avx_policy_driver/plugin.h b/aviatrix/plugins/avx_policy_driver/plugin.h
new file mode 100644
index 0000000000..d66a391354
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/plugin.h
@@ -0,0 +1,6 @@
+namespace avx_policy_driver
+{
+extern DbgCtl dbg_ctl;
+extern const char PLUGIN_NAME[];
+} // namespace avx_policy_driver
+using namespace avx_policy_driver;
diff --git a/aviatrix/plugins/avx_policy_driver/policy.h b/aviatrix/plugins/avx_policy_driver/policy.h
new file mode 100644
index 0000000000..72ffbd163b
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/policy.h
@@ -0,0 +1,44 @@
+/*
+ * Definitions about the policy framework
+ */
+#ifndef _POLICY_H
+#define _POLICY_H
+
+#include "proto/common/types.pb.h"
+#include "web_filter.h"
+#include "ts/ts.h"
+
+extern const char *L7PolicyTag;
+extern int L7PolicyIndex;
+
+enum PolicyResult {
+ POLICY_INIT,
+ POLICY_PERMIT,
+ POLICY_DROP,
+ POLICY_IDS,
+ POLICY_CONTINUE,
+ POLICY_ERROR,
+ POLICY_END,
+};
+
+union PolicyEvalInfo {
+ struct {
+ uint16_t initialized;
+ int16_t watched;
+ uint16_t generation;
+ int16_t offset;
+ } fields;
+ uint64_t value;
+};
+
+bool do_filter(void *policy, const char *server_name);
+bool has_ids(void *policy);
+bool IsPolicyLoaded();
+bool LoadPolicy(bool skip_if_exists);
+PolicyResult EvaluatePolicySni(TSVConn vconn, uint64_t *policy_offset);
+PolicyResult EvaluatePolicyTxn(TSHttpTxn, uint64_t *policy_offset);
+
+void RotatePolicyHitCounters();
+bool SendPolicyHitCounters(int offset, int length);
+
+#endif
diff --git a/aviatrix/plugins/avx_policy_driver/policy_client.cc b/aviatrix/plugins/avx_policy_driver/policy_client.cc
new file mode 100644
index 0000000000..32e275e8e5
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/policy_client.cc
@@ -0,0 +1,92 @@
+#include <string>
+#include <memory>
+#include <mutex>
+#include <shared_mutex>
+
+#include <grpcpp/grpcpp.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+#include "proto/conduit/v2/layer7.grpc.pb.h"
+#include "proto/conduit/v2/layer7.pb.h"
+#include "proto/common/types.pb.h"
+
+#include "ts/ts.h"
+#include "plugin.h"
+
+using grpc::Channel;
+using grpc::ClientContext;
+using grpc::Status;
+
+class Layer7PolicyServerClient
+{
+public:
+ Layer7PolicyServerClient(std::shared_ptr<Channel> channel) : stub_(Layer7PolicyServer::NewStub(channel)) {}
+
+ Layer7Config *
+ GetLayer7Policy(const EmptyArgs &request)
+ {
+ Layer7Config *reply = new Layer7Config();
+
+ ClientContext context;
+
+ Status status = stub_->GetL7PolicyConfig(&context, request, reply);
+
+ if (status.ok()) {
+ return reply;
+ } else {
+ std::cout << status.error_code() << ": " << status.error_message() << std::endl;
+ return nullptr;
+ }
+ }
+
+ const HitCounterResult *
+ UpdateHitCounters(HitCounters &hit_counters)
+ {
+ ClientContext context;
+ auto reply = new HitCounterResult();
+
+ Status status = stub_->UpdateHitCounters(&context, hit_counters, reply);
+ if (status.ok()) {
+ return reply;
+ } else {
+ reply->set_result(false);
+ reply->set_message(status.error_message());
+ }
+ return reply;
+ }
+
+private:
+ std::unique_ptr<Layer7PolicyServer::Stub> stub_;
+};
+
+std::shared_ptr<grpc::Channel>
+GetLayer7Channel()
+{
+ std::string address("127.0.0.1:5557");
+ return grpc::CreateChannel(address, grpc::InsecureChannelCredentials());
+}
+
+Layer7Config *
+FetchLayer7Policy()
+{
+ Layer7PolicyServerClient client(GetLayer7Channel());
+
+ EmptyArgs args;
+ Layer7Config *result = client.GetLayer7Policy(args);
+ if (result == nullptr) {
+ Dbg(dbg_ctl, "Null response");
+ // Clear the policy argument
+ } else {
+ Dbg(dbg_ctl, "Policy: received");
+ }
+ return result;
+}
+
+const HitCounterResult *
+SendLayer7PolicyHitCounters(HitCounters &hit_counters)
+{
+ Layer7PolicyServerClient client(GetLayer7Channel());
+ return client.UpdateHitCounters(hit_counters);
+}
diff --git a/aviatrix/plugins/avx_policy_driver/policy_driver.cc b/aviatrix/plugins/avx_policy_driver/policy_driver.cc
new file mode 100644
index 0000000000..dbf878cefb
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/policy_driver.cc
@@ -0,0 +1,303 @@
+/** @file
+
+ An example program that does a null transform of response body content.
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+ */
+
+// Copyright (c) 2023, Aviatrix Systems, Inc. All rights reserved.
+
+#include <stdio.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+#include <string.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "ts/ts.h"
+#include "plugin.h"
+#include "web_filter.h"
+#include "policy.h"
+#include "logging.h"
+
+void RunTest(struct sockaddr_in *src_addr, struct sockaddr_in *dst_addr);
+
+int data_arg_index = -1;
+
+const char *L7PolicyTag = "L7Policy";
+int L7PolicyIndex = -1;
+const char *IDSPolicyTag = "IDSPolicy";
+int IDSPolicyIndex = -1;
+int IDSPolicyTxnIndex = -1;
+const char *CertifierPolicyTag = "CertifierPolicy";
+int CertifierPolicyIndex = -1;
+
+namespace avx_policy_driver
+{
+const char PLUGIN_NAME[] = "policy_driver";
+DbgCtl dbg_ctl{PLUGIN_NAME};
+} // namespace avx_policy_driver
+
+bool
+register_arg_indices()
+{
+ if (TS_SUCCESS != TSUserArgIndexReserve(TS_USER_ARGS_VCONN, L7PolicyTag, "L7 policy", &L7PolicyIndex)) {
+ TSError("Failed to register L7 policy index");
+ return false;
+ }
+ if (TS_SUCCESS != TSUserArgIndexReserve(TS_USER_ARGS_VCONN, IDSPolicyTag, "IDS policy", &IDSPolicyIndex)) {
+ TSError("Failed to register IDS connection policy index");
+ return false;
+ }
+ if (TS_SUCCESS != TSUserArgIndexReserve(TS_USER_ARGS_TXN, IDSPolicyTag, "IDS policy", &IDSPolicyTxnIndex)) {
+ TSError("Failed to register IDS transaction policy index");
+ return false;
+ }
+ if (TS_SUCCESS != TSUserArgIndexReserve(TS_USER_ARGS_VCONN, CertifierPolicyTag, "Certifier policy", &CertifierPolicyIndex)) {
+ TSError("Failed to register Certifier policy index");
+ return false;
+ }
+ return true;
+}
+
+static int
+filter_load(TSCont cont, TSEvent event, void *edata)
+{
+ if (TS_EVENT_LIFECYCLE_MSG == event) {
+ Dbg(dbg_ctl, "Load web filter");
+ }
+ if (TS_EVENT_LIFECYCLE_PORTS_INITIALIZED == event) {
+ Dbg(dbg_ctl, "Load web filter at process start");
+ }
+ if (!LoadPolicy(false)) {
+ TSError("[%s] Failed to reload policy", PLUGIN_NAME);
+ return TS_ERROR;
+ }
+ return TS_SUCCESS;
+}
+
+uint64_t
+get_l7_policy_offset(TSHttpTxn txnp)
+{
+ TSHttpSsn ssnp = TSHttpTxnSsnGet(txnp);
+ if (ssnp != nullptr) {
+ TSVConn vc = TSHttpSsnClientVConnGet(ssnp);
+ return reinterpret_cast<uintptr_t>(TSUserArgGet(vc, L7PolicyIndex));
+ }
+ // todo: precompute value
+ return 0;
+}
+
+static int
+policy_sni(TSCont cont, TSEvent event, void *edata)
+{
+ TSVConn ssl_vc = reinterpret_cast<TSVConn>(edata);
+ int server_name_length = 0;
+ const char *server_name = TSVConnSslSniGet(ssl_vc, &server_name_length);
+ Dbg(dbg_ctl, "SNI filter %s", server_name);
+
+ // Evalulate policy
+ uint64_t policy_offset = 0;
+ // Should return information about whether logging is required.
+ auto result = EvaluatePolicySni(ssl_vc, &policy_offset);
+ switch (result) {
+ case POLICY_PERMIT:
+ Dbg(dbg_ctl, "No IDS policy, tunnel");
+ TSVConnTunnel(ssl_vc);
+ break;
+ case POLICY_IDS:
+ Dbg(dbg_ctl, "IDS policy, continue");
+ TSUserArgSet(ssl_vc, IDSPolicyIndex, reinterpret_cast<void *>(static_cast<intptr_t>(1)));
+ TSUserArgSet(ssl_vc, CertifierPolicyIndex, reinterpret_cast<void *>(static_cast<intptr_t>(1)));
+ Dbg(dbg_ctl, "Policy_ids set offset 0x%" PRIx64 " vc=0x%p", policy_offset, ssl_vc);
+ TSUserArgSet(ssl_vc, L7PolicyIndex, reinterpret_cast<void *>(static_cast<uintptr_t>(policy_offset)));
+ // Continue processing
+ TSVConnReenable(ssl_vc);
+ break;
+ case POLICY_CONTINUE:
+ // Set the policy offet and continue
+ // The TXN hook should catch it
+ Dbg(dbg_ctl, "Policy_continue set offset 0x%" PRIx64 " vc=0x%p", policy_offset, ssl_vc);
+ TSUserArgSet(ssl_vc, L7PolicyIndex, reinterpret_cast<void *>(static_cast<uintptr_t>(policy_offset)));
+ // Let the certifier know it should terminate the client connection
+ TSUserArgSet(ssl_vc, CertifierPolicyIndex, reinterpret_cast<void *>(static_cast<intptr_t>(1)));
+ TSVConnReenable(ssl_vc);
+ break;
+ case POLICY_DROP:
+ default:
+ Dbg(dbg_ctl, "Filter drop");
+ // int ssl_fd = TSVConnFdGet(ssl_vc);
+ // close(ssl_fd);
+ // One might think that the TSVConnClose would be more appropriate
+ // than just closing the file descriptor. But calling TSVConnClose
+ // with or without the reenable just causes a core dump. Presume it is a problem
+ // that TSVConnClose frees of data structure that are needed to wind down the state machine
+ // TSVConnClose(ssl_vc);
+ TSVConnReenable(ssl_vc);
+ break;
+ }
+ return TS_SUCCESS;
+}
+
+static int
+transaction_start(TSCont cont, TSEvent event, void *edata)
+{
+ TSHttpTxn txnp = reinterpret_cast<TSHttpTxn>(edata);
+ // Is a policy loaded?
+ if (!IsPolicyLoaded()) {
+ // Big error if not
+ TSError("[%s] Policy not loaded at Txn Hdr", PLUGIN_NAME);
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR);
+ return TS_ERROR;
+ }
+
+ // Is there more policy to interpret
+ uint64_t policy_offset = get_l7_policy_offset(txnp);
+ Dbg(dbg_ctl, "Txn Policy Offset=0x%" PRIX64, policy_offset);
+ auto result = EvaluatePolicyTxn(txnp, &policy_offset);
+ switch (result) {
+ case POLICY_PERMIT:
+ Dbg(dbg_ctl, "No IDS policy, permit the translation and move on");
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ break;
+ case POLICY_IDS:
+ Dbg(dbg_ctl, "IDS policy, continue");
+ TSUserArgSet(txnp, IDSPolicyTxnIndex, reinterpret_cast<void *>(static_cast<intptr_t>(1)));
+ // Continue processing
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ break;
+ case POLICY_DROP:
+ default:
+ Dbg(dbg_ctl, "URL Filter drop");
+ // TODO, I assume continuing with ERROR will cause the transaction to fail
+ // This will end up returning a 50x status code, we could replace this with a 401
+ // unauthorized or something configurable to the customer's pleasing. We would need to
+ // set a hook on the send client response to adjust the status code and message.
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR);
+ return TS_ERROR;
+ break;
+ }
+ return TS_SUCCESS;
+}
+
+static int
+connection_start(TSCont cont, TSEvent event, void *edata)
+{
+ TSVConn ssl_vc = reinterpret_cast<TSVConn>(edata);
+ // Is a policy loaded?
+ if (!IsPolicyLoaded()) {
+ // LoadPolicy, but skip config request if we get a policy in the meantime
+ // this is to prevent thundering herd DOS at the state sync service
+ if (!LoadPolicy(true)) {
+ TSError("[%s] Failed to load policy", PLUGIN_NAME);
+ TSVConnReenable(ssl_vc);
+ return TS_ERROR;
+ }
+ }
+ // Set up the hooks for transactions on this session
+ // Potentially SNI and client request request
+
+ TSVConnReenable(ssl_vc);
+ return TS_SUCCESS;
+}
+
+static int
+send_stats(TSCont cont, TSEvent event, void *edata)
+{
+ Dbg(dbg_ctl, "Entering send_stats");
+ auto data = reinterpret_cast<uint64_t>(TSContDataGet(cont));
+ if (data) {
+ Dbg(dbg_ctl, "send_stats: %" PRId64, data);
+ int offset = data >> 32;
+ int length = data & 0xffffffff;
+ SendPolicyHitCounters(offset, length);
+ TSContDestroy(cont);
+ } else {
+ Dbg(dbg_ctl, "send_stats: rotating");
+ RotatePolicyHitCounters();
+ }
+ return TS_SUCCESS;
+}
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+ TSPluginRegistrationInfo info;
+
+ info.plugin_name = PLUGIN_NAME;
+ info.vendor_name = "Aviatrix";
+ info.support_email = "dev@trafficserver.apache.org";
+
+ if (TSPluginRegister(&info) != TS_SUCCESS) {
+ TSError("[%s] Plugin registration failed", PLUGIN_NAME);
+
+ } else {
+ if (argc < 3) {
+ TSError("[%s] Plugin failed. Requires argument <policy server port stats_interval_seconds >", PLUGIN_NAME);
+ return;
+ }
+ // we need to make this configurable by the localgateway config
+ // and environment
+ int stats_timeout = atoi(argv[2]);
+ if (stats_timeout <= 0) {
+ stats_timeout = 60;
+ }
+ TSCont start_contp = TSContCreate(connection_start, NULL);
+ TSCont start_txn_contp = TSContCreate(transaction_start, NULL);
+ // Trigger on connection start, apply hooks on txn start as necssary
+ TSHttpHookAdd(TS_VCONN_START_HOOK, start_contp);
+ TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, start_txn_contp);
+ // Set up next hook
+ TSCont cb_sni = TSContCreate(policy_sni, nullptr);
+ TSHttpHookAdd(TS_SSL_CERT_HOOK, cb_sni);
+
+ auto mutex_send_stats = TSMutexCreate();
+ TSCont cb_send_stats = TSContCreate(send_stats, mutex_send_stats);
+ // explicitly set continuation to be null
+ // this will force rotation
+ TSContDataSet(cb_send_stats, nullptr);
+
+ TSContScheduleEveryOnPool(cb_send_stats, stats_timeout * 1000, TSThreadPool::TS_THREAD_POOL_TASK);
+
+ send_stats_callback = [mutex_send_stats](int offset, int length) {
+ // we create a new continuation here because we use the continuation data
+ // to relay what needs to be sent
+ TSCont cb_send_stats_direct = TSContCreate(send_stats, mutex_send_stats);
+ // explicitly set continuation, with the data offset and length to be used
+ TSContDataSet(cb_send_stats_direct,
+ reinterpret_cast<void *>(static_cast<uint64_t>(offset) << 32 | static_cast<uint64_t>(length)));
+ TSContScheduleOnPool(cb_send_stats_direct, 1, TSThreadPool::TS_THREAD_POOL_TASK);
+ };
+
+ TSCont cb_filter_load = TSContCreate(filter_load, nullptr);
+ TSLifecycleHookAdd(TSLifecycleHookID::TS_LIFECYCLE_MSG_HOOK, cb_filter_load);
+ TSLifecycleHookAdd(TSLifecycleHookID::TS_LIFECYCLE_PORTS_INITIALIZED_HOOK, cb_filter_load);
+ // this hook will rotate the data and send it before shutting down
+ TSLifecycleHookAdd(TSLifecycleHookID::TS_LIFECYCLE_SHUTDOWN_HOOK, cb_send_stats);
+ register_arg_indices();
+ Dbg(dbg_ctl, "Index is %d", L7PolicyIndex);
+ }
+ return;
+}
diff --git a/aviatrix/plugins/avx_policy_driver/policy_hit_counter.h b/aviatrix/plugins/avx_policy_driver/policy_hit_counter.h
new file mode 100644
index 0000000000..99e53ef528
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/policy_hit_counter.h
@@ -0,0 +1,167 @@
+// hit counter manager
+// naive locked increment implementation, under the web_filter's lock we move
+// to a new span of memory periodically and when the counter configuration changes
+//
+// possible optimizations:
+// - move to a TLS based counters, we can increment and read without lock, aiming for
+// eventual consistency
+// - assign dynamic offsets and retire old counters, to keep memory usage low and prevent
+// overrunning counters not sent yet
+// - move to a single counter with an added type of counter as hit an watch are
+// mutually exclusive in this implementation (we move to next block of memory
+// periodically and on counter config change, a counter instance in a span of
+// memory will not receive hit and hit_watch at the same time) this will almost
+// half the memory needed
+//
+// issues:
+// - if we get a lot of configuration changes with large number of policies
+// in a very short period of time we might lag in sending the policies
+// and cause an overrun of old counters
+// - locked increment of the counters is an extra shared lock
+
+#pragma once
+#include <atomic>
+#include <thread>
+#include <shared_mutex>
+
+struct policy_hit_counter {
+ // UUID, mirroring the proto_buf naming
+ uint64_t id_a;
+ uint64_t id_b;
+ // number of hits
+ uint64_t hit;
+ // number of hits on watch
+ uint64_t hit_watch;
+};
+
+const int hit_counter_array_size = 200000;
+
+// todo, move to thread_local storage to stop locking
+// make use of alignas for safe access and do deltas on
+// the totals to get to a eventual consistent correct count
+// challenges:
+
+class PolicyHitCounterManager
+{
+ policy_hit_counter *_hit_counters;
+ uint32_t _current_offset, _current_length;
+ mutable std::shared_mutex mutex;
+
+public:
+ PolicyHitCounterManager()
+ {
+ // allocate counters
+ _hit_counters = new policy_hit_counter[hit_counter_array_size];
+ _current_offset = 0;
+ _current_length = 0;
+ }
+ ~PolicyHitCounterManager() { delete[] _hit_counters; }
+ uint64_t
+ Hit(uint32_t index)
+ {
+ if (index >= _current_length) {
+ return 0;
+ } else {
+ return __atomic_add_fetch(&(_hit_counters[_current_offset + index].hit), 1, static_cast<int>(std::memory_order_relaxed));
+ /*std::atomic<uint64_t> &counter = *reinterpret_cast<std::atomic<uint64_t> *>(&(_hit_counters[_current_offset + index].hit));
+ return counter.fetch_add(1, std::memory_order_relaxed);*/
+ }
+ }
+ uint64_t
+ HitWatch(uint32_t index)
+ {
+ if (index >= _current_length) {
+ return 0;
+ }
+ return __atomic_add_fetch(&(_hit_counters[_current_offset + index].hit_watch), 1, static_cast<int>(std::memory_order_relaxed));
+ /*std::atomic<uint64_t> &counter =
+ *reinterpret_cast<std::atomic<uint64_t> *>(&(_hit_counters[_current_offset + index].hit_watch));
+ return counter.fetch_add(1, std::memory_order_relaxed);*/
+ }
+
+ const policy_hit_counter &
+ GetAbsoluteCounter(uint32_t index)
+ {
+ return _hit_counters[index];
+ }
+
+ // move offset to next stretch of memory with
+ // zeroed counters, returns false if there was nothing in the old stretch
+ // and nothing to process
+ bool
+ CreateNew(uint32_t new_length)
+ {
+ if (new_length > GetMaxSize()) {
+ new_length = 0;
+ }
+ bool anyvalue = false;
+ for (uint32_t c = 0; c < _current_length; c++) {
+ anyvalue = ((_hit_counters[_current_offset + c].hit != 0) || (_hit_counters[_current_offset + c].hit_watch != 0));
+ if (anyvalue)
+ break;
+ }
+
+ uint32_t new_offset = _current_offset + (anyvalue ? _current_length : 0);
+ if ((new_offset + new_length) > hit_counter_array_size) {
+ new_offset = 0;
+ }
+ _current_offset = new_offset;
+ _current_length = new_length;
+ return anyvalue;
+ }
+
+ uint32_t
+ GetMaxSize()
+ {
+ return hit_counter_array_size >> 2;
+ }
+
+ uint32_t
+ CurrentOffset()
+ {
+ return _current_offset;
+ }
+ uint32_t
+ CurrentLength()
+ {
+ return _current_length;
+ }
+
+ bool
+ ResetCounter(uint32_t index, uint64_t id_a, uint64_t id_b)
+ {
+ if (index >= _current_length) {
+ return false;
+ } else {
+ _hit_counters[_current_offset + index] = {id_a, id_b, 0, 0};
+ return true;
+ }
+ }
+
+ // copies current counter ids (a,b) to next stretch of memory with
+ // zeroed counters, returns false if there was nothing to copy and to
+ // process
+ bool
+ CopyNext()
+ {
+ auto new_offset = _current_offset + _current_length;
+ // only allow for continuous stretches
+ if ((new_offset + _current_length) > hit_counter_array_size) {
+ new_offset = 0;
+ }
+ bool anyvalue = false;
+ for (uint32_t c = 0; c < _current_length; c++) {
+ _hit_counters[new_offset + c] = _hit_counters[_current_offset + c];
+ _hit_counters[new_offset + c].hit = 0;
+ _hit_counters[new_offset + c].hit_watch = 0;
+ anyvalue |= (_hit_counters[_current_offset + c].hit != 0);
+ anyvalue |= (_hit_counters[_current_offset + c].hit_watch != 0);
+ }
+ // do not move to next stretch if no changes
+ if (!anyvalue)
+ return false;
+
+ _current_offset = new_offset;
+ return true;
+ }
+};
diff --git a/aviatrix/plugins/avx_policy_driver/rules/mitm_test.json b/aviatrix/plugins/avx_policy_driver/rules/mitm_test.json
new file mode 100644
index 0000000000..0cdff26d45
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/rules/mitm_test.json
@@ -0,0 +1,64 @@
+{
+ "web_filters": [],
+ "filters": [
+ {
+ "id": {
+ "a": "1227538073995136577",
+ "b": "11244872765655294467"
+ },
+ "cidrs": [
+ {
+ "addr": {
+ "v4": 168493056
+ },
+ "prefix_len": 16
+ }
+ ],
+ "fqdns": []
+ },
+ {
+ "id": {
+ "a": "5646710414998389048",
+ "b": "11606242933273799987"
+ },
+ "cidrs": [
+ {
+ "addr": {
+ "v4": 168558592
+ },
+ "prefix_len": 16
+ }
+ ],
+ "fqdns": []
+ }
+ ],
+ "policies": [
+ {
+ "id": {
+ "a": "17049098458355221748",
+ "b": "11486844873440621944"
+ },
+ "src_filters": [
+ {
+ "a": "1227538073995136577",
+ "b": "11244872765655294467"
+ }
+ ],
+ "dst_filters": [
+ {
+ "a": "5646710414998389048",
+ "b": "11606242933273799987"
+ }
+ ],
+ "web_filter_lists": [],
+ "port_ranges": [],
+ "protocol": "PROTOCOL_UNSPECIFIED",
+ "action": "INTRUSION_DETECTION",
+ "decryption_allowed": true,
+ "log": true,
+ "priority": 0,
+ "watch": true,
+ "flow_app_requirement": "APP_UNSPECIFIED"
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/aviatrix/plugins/avx_policy_driver/rules/no-ids.json b/aviatrix/plugins/avx_policy_driver/rules/no-ids.json
new file mode 100644
index 0000000000..1e63ede2d1
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/rules/no-ids.json
@@ -0,0 +1,451 @@
+{
+ "web_filters": [
+ {
+ "id": {
+ "a": "11573145354659187496",
+ "b": "11631669224600077383"
+ },
+ "name": "filter-sni-test",
+ "type": "TYPE_UNSPECIFIED",
+ "target": "TARGET_SNI",
+ "filter_match": [
+ "yahoo.com",
+ "*.google.com",
+ "aviatrix.com",
+ "*.github.com"
+ ]
+ },
+ {
+ "id": {
+ "a": "14928245104871097574",
+ "b": "11977577459993465621"
+ },
+ "name": "filter-sni-test2",
+ "type": "TYPE_UNSPECIFIED",
+ "target": "TARGET_SNI",
+ "filter_match": [
+ "*.foo.com",
+ "network-geographics.com"
+ ]
+ },
+ {
+ "id": {
+ "a": "13390857880490101613",
+ "b": "9607836885550754618"
+ },
+ "name": "filter-url-test",
+ "type": "TYPE_UNSPECIFIED",
+ "target": "TARGET_URL",
+ "filter_match": [
+ "https://yahoo.com/test",
+ "https://google.com/",
+ "https://aviatrix.com/test2"
+ ]
+ }
+ ],
+ "filters": [
+ {
+ "id": {
+ "a": "11857660137499673847",
+ "b": "13603655541779480239"
+ },
+ "cidrs": [
+ {
+ "addr": {
+ "v4": 167858850
+ },
+ "prefix_len": 32
+ }
+ ],
+ "fqdns": []
+ },
+ {
+ "id": {
+ "a": "16598927387781450337",
+ "b": "10427520129841876705"
+ },
+ "cidrs": [
+ {
+ "addr": {
+ "v4": 167917914
+ },
+ "prefix_len": 32
+ }
+ ],
+ "fqdns": []
+ },
+ {
+ "id": {
+ "a": "2943319083006644129",
+ "b": "12088172350522341206"
+ },
+ "cidrs": [
+ {
+ "addr": {
+ "v4": 167858851
+ },
+ "prefix_len": 32
+ },
+ {
+ "addr": {
+ "v4": 167858850
+ },
+ "prefix_len": 32
+ }
+ ],
+ "fqdns": []
+ },
+ {
+ "id": {
+ "a": "2121727585539343233",
+ "b": "13790429357605610247"
+ },
+ "cidrs": [
+ {
+ "addr": {
+ "v4": 167903232
+ },
+ "prefix_len": 16
+ },
+ {
+ "addr": {
+ "v4": 167837696
+ },
+ "prefix_len": 16
+ }
+ ],
+ "fqdns": []
+ }
+ ],
+ "policies": [
+ {
+ "id": {
+ "a": "11828068427654778845",
+ "b": "13655412897838382517"
+ },
+ "src_filters": [
+ {
+ "a": "11857660137499673847",
+ "b": "13603655541779480239"
+ }
+ ],
+ "dst_filters": [
+ {
+ "a": "16598927387781450337",
+ "b": "10427520129841876705"
+ }
+ ],
+ "web_filter_lists": [
+ {
+ "web_filter_list": [
+ {
+ "a": "14928245104871097574",
+ "b": "11977577459993465621"
+ }
+ ]
+ }
+ ],
+ "port_ranges": [
+ {
+ "lo": 80,
+ "hi": 80
+ }
+ ],
+ "protocol": "TCP",
+ "action": "PERMIT",
+ "decryption_allowed": false,
+ "log": false,
+ "priority": 0,
+ "watch": false,
+ "flow_app_requirement": "APP_UNSPECIFIED"
+ },
+ {
+ "id": {
+ "a": "6410705922635877365",
+ "b": "12335562460678745664"
+ },
+ "src_filters": [
+ {
+ "a": "11857660137499673847",
+ "b": "13603655541779480239"
+ }
+ ],
+ "dst_filters": [
+ {
+ "a": "16598927387781450337",
+ "b": "10427520129841876705"
+ }
+ ],
+ "web_filter_lists": [
+ {
+ "web_filter_list": [
+ {
+ "a": "13390857880490101613",
+ "b": "9607836885550754618"
+ }
+ ]
+ }
+ ],
+ "port_ranges": [
+ {
+ "lo": 80,
+ "hi": 80
+ }
+ ],
+ "protocol": "TCP",
+ "action": "PERMIT",
+ "decryption_allowed": false,
+ "log": false,
+ "priority": 0,
+ "watch": false,
+ "flow_app_requirement": "APP_UNSPECIFIED"
+ },
+ {
+ "id": {
+ "a": "9653359137356205499",
+ "b": "10100342088580073738"
+ },
+ "src_filters": [
+ {
+ "a": "11857660137499673847",
+ "b": "13603655541779480239"
+ }
+ ],
+ "dst_filters": [
+ {
+ "a": "16598927387781450337",
+ "b": "10427520129841876705"
+ }
+ ],
+ "web_filter_lists": [],
+ "port_ranges": [
+ {
+ "lo": 80,
+ "hi": 80
+ }
+ ],
+ "protocol": "TCP",
+ "action": "DENY",
+ "decryption_allowed": false,
+ "log": false,
+ "priority": 0,
+ "watch": false,
+ "flow_app_requirement": "APP_UNSPECIFIED"
+ },
+ {
+ "id": {
+ "a": "3764076106982113902",
+ "b": "12775128745753212750"
+ },
+ "src_filters": [
+ {
+ "a": "11857660137499673847",
+ "b": "13603655541779480239"
+ }
+ ],
+ "dst_filters": [
+ {
+ "a": "16598927387781450337",
+ "b": "10427520129841876705"
+ }
+ ],
+ "web_filter_lists": [
+ {
+ "web_filter_list": [
+ {
+ "a": "14928245104871097574",
+ "b": "11977577459993465621"
+ }
+ ]
+ }
+ ],
+ "port_ranges": [
+ {
+ "lo": 443,
+ "hi": 443
+ }
+ ],
+ "protocol": "TCP",
+ "action": "DENY",
+ "decryption_allowed": true,
+ "log": false,
+ "priority": 0,
+ "watch": false,
+ "flow_app_requirement": "TLS_REQUIRED"
+ },
+ {
+ "id": {
+ "a": "14943161210721028237",
+ "b": "11188960495199580110"
+ },
+ "src_filters": [
+ {
+ "a": "11857660137499673847",
+ "b": "13603655541779480239"
+ }
+ ],
+ "dst_filters": [
+ {
+ "a": "16598927387781450337",
+ "b": "10427520129841876705"
+ }
+ ],
+ "web_filter_lists": [
+ {
+ "web_filter_list": [
+ {
+ "a": "13390857880490101613",
+ "b": "9607836885550754618"
+ }
+ ]
+ }
+ ],
+ "port_ranges": [
+ {
+ "lo": 443,
+ "hi": 443
+ }
+ ],
+ "protocol": "TCP",
+ "action": "PERMIT",
+ "decryption_allowed": true,
+ "log": false,
+ "priority": 0,
+ "watch": false,
+ "flow_app_requirement": "TLS_REQUIRED"
+ },
+ {
+ "id": {
+ "a": "17834375904944408020",
+ "b": "13063152365985692207"
+ },
+ "src_filters": [
+ {
+ "a": "11857660137499673847",
+ "b": "13603655541779480239"
+ }
+ ],
+ "dst_filters": [
+ {
+ "a": "16598927387781450337",
+ "b": "10427520129841876705"
+ }
+ ],
+ "web_filter_lists": [
+ {
+ "web_filter_list": [
+ {
+ "a": "11573145354659187496",
+ "b": "11631669224600077383"
+ }
+ ]
+ }
+ ],
+ "port_ranges": [
+ {
+ "lo": 443,
+ "hi": 443
+ }
+ ],
+ "protocol": "TCP",
+ "action": "PERMIT",
+ "decryption_allowed": true,
+ "log": false,
+ "priority": 0,
+ "watch": false,
+ "flow_app_requirement": "TLS_REQUIRED"
+ },
+ {
+ "id": {
+ "a": "6796208347563577099",
+ "b": "10137280459967609472"
+ },
+ "src_filters": [
+ {
+ "a": "11857660137499673847",
+ "b": "13603655541779480239"
+ }
+ ],
+ "dst_filters": [
+ {
+ "a": "16598927387781450337",
+ "b": "10427520129841876705"
+ }
+ ],
+ "web_filter_lists": [],
+ "port_ranges": [
+ {
+ "lo": 443,
+ "hi": 443
+ }
+ ],
+ "protocol": "TCP",
+ "action": "DENY",
+ "decryption_allowed": true,
+ "log": false,
+ "priority": 0,
+ "watch": false,
+ "flow_app_requirement": "APP_UNSPECIFIED"
+ },
+ {
+ "id": {
+ "a": "292127853411584270",
+ "b": "12688436159954087515"
+ },
+ "src_filters": [
+ {
+ "a": "11857660137499673847",
+ "b": "13603655541779480239"
+ }
+ ],
+ "dst_filters": [
+ {
+ "a": "16598927387781450337",
+ "b": "10427520129841876705"
+ }
+ ],
+ "web_filter_lists": [],
+ "port_ranges": [],
+ "protocol": "PROTOCOL_UNSPECIFIED",
+ "action": "PERMIT",
+ "decryption_allowed": false,
+ "log": false,
+ "priority": 0,
+ "watch": false,
+ "flow_app_requirement": "APP_UNSPECIFIED"
+ },
+ {
+ "id": {
+ "a": "4442387576226660903",
+ "b": "11119345813591189141"
+ },
+ "src_filters": [
+ {
+ "a": "2943319083006644129",
+ "b": "12088172350522341206"
+ },
+ {
+ "a": "2121727585539343233",
+ "b": "13790429357605610247"
+ }
+ ],
+ "dst_filters": [
+ {
+ "a": "2121727585539343233",
+ "b": "13790429357605610247"
+ }
+ ],
+ "web_filter_lists": [],
+ "port_ranges": [],
+ "protocol": "PROTOCOL_UNSPECIFIED",
+ "action": "DENY",
+ "decryption_allowed": false,
+ "log": false,
+ "priority": 0,
+ "watch": false,
+ "flow_app_requirement": "APP_UNSPECIFIED"
+ }
+ ]
+ }
+
+
\ No newline at end of file
diff --git a/aviatrix/plugins/avx_policy_driver/rules/rules1.json b/aviatrix/plugins/avx_policy_driver/rules/rules1.json
new file mode 100644
index 0000000000..ccaafff837
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/rules/rules1.json
@@ -0,0 +1,30 @@
+{
+ "web_filters": [
+ {
+ "name": "",
+ "type": "TYPE_UNSPECIFIED",
+ "target": "TARGET_UNSPECIFIED",
+ "filter_match": []
+ }
+ ],
+ "filters": [
+ {
+ "cidrs": [],
+ "fqdns": []
+ }
+ ],
+ "policies": [
+ {
+ "src_filters": [],
+ "dst_filters": [],
+ "web_filter_lists": [],
+ "port_ranges": [],
+ "protocol": "TCP",
+ "action": "DENY",
+ "encryption_allowed": true,
+ "log": true,
+ "priority": 5,
+ "watch": false
+ }
+ ]
+}
\ No newline at end of file
diff --git a/aviatrix/plugins/avx_policy_driver/test_client.cc b/aviatrix/plugins/avx_policy_driver/test_client.cc
new file mode 100644
index 0000000000..663de694e9
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/test_client.cc
@@ -0,0 +1,60 @@
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include <grpcpp/grpcpp.h>
+
+using grpc::Channel;
+using grpc::ClientContext;
+using grpc::Status;
+
+#include "proto/conduit/v2/layer7.grpc.pb.h"
+
+class OracleClient
+{
+public:
+ OracleClient(std::shared_ptr<Channel> channel) : stub_(Layer7PolicyServer::NewStub(channel)) {}
+
+ ::Layer7Config *
+ GetL7PolicyConfig()
+ {
+ Layer7Config *response = new Layer7Config();
+ ClientContext context;
+ ::EmptyArgs request;
+
+ Status status = stub_->GetL7PolicyConfig(&context, request, response);
+
+ if (status.ok()) {
+ return response;
+ } else {
+ std::cout << "RPC failed: " << status.error_code() << "-" << status.error_details() << "-" << status.error_message()
+ << std::endl;
+ return nullptr;
+ }
+ }
+
+private:
+ std::unique_ptr<Layer7PolicyServer::Stub> stub_;
+};
+
+int
+main(int argc, char **argv)
+{
+ std::string name("world");
+
+ OracleClient client(grpc::CreateChannel("127.0.0.1:5557", grpc::InsecureChannelCredentials()));
+
+ auto *message = client.GetL7PolicyConfig();
+ if (message == nullptr) {
+ return 1;
+ }
+
+ std::string json_string;
+ google::protobuf::util::JsonPrintOptions options;
+ options.add_whitespace = true;
+ options.always_print_primitive_fields = true;
+ options.preserve_proto_field_names = true;
+ MessageToJsonString(*message, &json_string, options);
+ std::cout << json_string << std::endl;
+ return 0;
+}
diff --git a/aviatrix/plugins/avx_policy_driver/test_server.cc b/aviatrix/plugins/avx_policy_driver/test_server.cc
new file mode 100644
index 0000000000..b73099d5c3
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/test_server.cc
@@ -0,0 +1,181 @@
+#include <iostream>
+#include <memory>
+#include <string>
+#include <thread>
+#include <functional>
+#include <tuple>
+#include <vector>
+#include <fstream>
+#include <sstream>
+#include <grpcpp/grpcpp.h>
+
+using grpc::Server;
+using grpc::ServerBuilder;
+using grpc::ServerContext;
+using grpc::Status;
+
+#include "proto/conduit/v2/layer7.pb.h"
+#include "proto/conduit/v2/layer7.grpc.pb.h"
+/*
+class ResponseList
+{
+ typedef std::tuple<std::string, std::function<bool(const ::MicrosegTuple *, ::L7Policy *)>> index_pair;
+ std::vector<index_pair> _mylist;
+ std::string _current;
+public:
+ void
+ AddResponseObject(std::string id, std::function<bool(const ::MicrosegTuple *, ::L7Policy *)> processor)
+ {
+ if (_current.empty())
+ _current=id;
+ _mylist.emplace_back(index_pair(id, processor));
+ }
+ void
+ Print()
+ {
+ for (index_pair &p : _mylist) {
+ std::cout << std::get<0>(p) << std::endl;
+ }
+ }
+ void ProcessResponse(const ::MicrosegTuple *mst, ::L7Policy *pol)
+ {
+ for (index_pair &p : _mylist) {
+ if(_current==std::get<0>(p))
+ {
+ (std::get<1>(p))(mst,pol);
+ break;
+ }
+ }
+ }
+ void SetCurrent(std::string current) {
+ _current=current;
+ }
+
+};
+*/
+
+// ResponseList rs;
+
+std::string
+readFileIntoString(const std::string &filename)
+{
+ std::ifstream file(filename);
+ std::stringstream buffer;
+ buffer << file.rdbuf();
+ return buffer.str();
+}
+
+std::string current_file;
+
+void
+ReadConfig(::Layer7Config *response)
+{
+ std::string json_string = readFileIntoString(current_file);
+ google::protobuf::util::JsonParseOptions options2;
+ auto status = JsonStringToMessage(json_string, response, options2);
+ std::cout << status.message() << std::endl << "---------" << std::endl;
+ std::cout << response->DebugString() << std::endl;
+}
+
+class Layer7PolicyServerImpl final : public Layer7PolicyServer::Service
+{
+public:
+ ::grpc::Status
+ GetL7PolicyConfig(::grpc::ServerContext *context, const ::EmptyArgs *request, ::Layer7Config *response) override
+ {
+ std::cout << "GetL7PolicyConfig 1" << std::endl;
+ ReadConfig(response);
+ std::cout << "GetL7PolicyConfig 2" << std::endl;
+ return Status::OK;
+ }
+ ::grpc::Status
+ UpdateHitCounters(::grpc::ServerContext *context, const ::HitCounters *request, ::HitCounterResult *response)
+ {
+ std::string json_string;
+ google::protobuf::util::JsonPrintOptions options;
+ options.add_whitespace = true;
+ options.always_print_primitive_fields = true;
+ options.preserve_proto_field_names = true;
+
+ MessageToJsonString(*request, &json_string, options);
+ response->set_result(true);
+ return Status::OK;
+ }
+};
+
+void
+RunServer()
+{
+ std::string server_address("127.0.0.1:5557");
+ Layer7PolicyServerImpl service;
+
+ ServerBuilder builder;
+ builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
+ builder.RegisterService(&service);
+
+ std::unique_ptr<Server> server(builder.BuildAndStart());
+ std::cout << "Server listening on " << server_address << std::endl;
+ server->Wait();
+}
+
+void
+print_example()
+{
+ Layer7Config c;
+ std::string json_string;
+
+ auto policy = c.add_policies();
+ auto filter = c.add_filters();
+ auto webfilter = c.add_web_filters();
+ policy->set_action(Layer7Policy_Action::Layer7Policy_Action_DENY);
+ policy->set_decrypt_policy(Layer7Policy_DecryptPolicy_DECRYPT_ALLOWED);
+ policy->set_log(true);
+ policy->set_priority(5);
+ policy->set_protocol(Layer7Policy_Protocol::Layer7Policy_Protocol_TCP);
+ policy->set_watch(false);
+ google::protobuf::util::JsonPrintOptions options;
+ options.add_whitespace = true;
+ options.always_print_primitive_fields = true;
+ options.preserve_proto_field_names = true;
+ MessageToJsonString(c, &json_string, options);
+ std::cout << json_string << std::endl;
+}
+
+int
+main(int argc, char **argv)
+{
+ bool validate = false;
+ std::vector<std::string> files;
+ for (int i = 1; i < argc; i++) {
+ std::string curarg = std::string(argv[i]);
+ std::cout << curarg << std::endl;
+ if (curarg == "--validate") {
+ validate = true;
+ }
+ if (curarg == "--example") {
+ print_example();
+ return 0;
+ }
+ if (curarg.substr(0, 2) != "--") {
+ current_file = curarg;
+ }
+ }
+ if (current_file.empty()) {
+ std::cout << "Usage: test_server [file] --example --validate" << std::endl;
+
+ return 1;
+ }
+ if (validate) {
+ ::Layer7Config l7config;
+ std::string json_string;
+ google::protobuf::util::JsonParseOptions options2;
+ json_string = readFileIntoString(*(files.begin()));
+ std::cout << *(files.begin()) << std::endl << json_string << std::endl << "-----------------------" << std::endl;
+ auto status = JsonStringToMessage(json_string, &l7config, options2);
+ std::cout << status.message() << std::endl;
+ std::cout << l7config.DebugString() << std::endl;
+ } else {
+ RunServer();
+ }
+ return 0;
+}
diff --git a/aviatrix/plugins/avx_policy_driver/unit_test_hitcounter.cc b/aviatrix/plugins/avx_policy_driver/unit_test_hitcounter.cc
new file mode 100644
index 0000000000..38d1109c4d
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/unit_test_hitcounter.cc
@@ -0,0 +1,203 @@
+#include <stdlib.h>
+#include <string>
+#define CATCH_CONFIG_MAIN
+#include "policy_hit_counter.h"
+#include <catch.hpp>
+
+class TestCase
+{
+public:
+ std::string testInput;
+ bool expectedResult;
+};
+
+TEST_CASE("Initial should be empty", "[Hitcounter]")
+{
+ PolicyHitCounterManager pm;
+ REQUIRE(pm.CurrentLength() == 0);
+ REQUIRE(pm.CurrentOffset() == 0);
+}
+
+TEST_CASE("createnew boundaries", "[Hitcounter]")
+{
+ PolicyHitCounterManager pm;
+ REQUIRE(pm.CurrentLength() == 0);
+ REQUIRE(pm.CurrentOffset() == 0);
+
+ SECTION("Out of bounds should yield length 0")
+ {
+ pm.CreateNew(-1);
+ REQUIRE(pm.CurrentLength() == 0);
+ REQUIRE(pm.CurrentOffset() == 0);
+
+ pm.CreateNew(pm.GetMaxSize() + 1);
+ REQUIRE(pm.CurrentLength() == 0);
+ REQUIRE(pm.CurrentOffset() == 0);
+ }
+ SECTION("Valid length should return creation length")
+ {
+ pm.CreateNew(0);
+ REQUIRE(pm.CurrentLength() == 0);
+ REQUIRE(pm.CurrentOffset() == 0);
+
+ pm.CreateNew(2000);
+ REQUIRE(pm.CurrentLength() == 2000);
+ REQUIRE(pm.CurrentOffset() == 0);
+
+ pm.CreateNew(pm.GetMaxSize());
+ REQUIRE(pm.CurrentLength() == pm.GetMaxSize());
+ REQUIRE(pm.CurrentOffset() == 0);
+ }
+}
+
+TEST_CASE("Empty runs should not move forward in the buffer", "[HitCounter]")
+{
+ auto run = [](int size) -> void {
+
+ };
+ uint32_t sizes[] = {0, 1, 2000};
+ for (auto size : sizes) {
+ DYNAMIC_SECTION("Run " << size)
+ {
+ PolicyHitCounterManager pm;
+ REQUIRE(pm.CurrentLength() == 0);
+ auto movenext = pm.CreateNew(size);
+ REQUIRE(movenext == false);
+ movenext = pm.CopyNext();
+ REQUIRE(movenext == false);
+ REQUIRE(pm.CurrentLength() == size);
+ REQUIRE(pm.CurrentOffset() == 0);
+ }
+ }
+}
+
+TEST_CASE("A hit or hit_watch should move the buffer forward with CopyNext and CreateNext", "[HitCounter]")
+{
+ PolicyHitCounterManager pm;
+ REQUIRE(pm.CurrentLength() == 0);
+ pm.CreateNew(2000);
+ for (uint32_t c = 0; c < 2000; c++)
+ pm.ResetCounter(c, c + 1, c + 2);
+ pm.Hit(1);
+ auto movenext = pm.CopyNext();
+ REQUIRE(movenext == true);
+ REQUIRE(pm.CurrentLength() == 2000);
+ REQUIRE(pm.CurrentOffset() == 2000);
+
+ pm.HitWatch(1);
+ movenext = pm.CopyNext();
+ REQUIRE(movenext == true);
+ REQUIRE(pm.CurrentLength() == 2000);
+ REQUIRE(pm.CurrentOffset() == 4000);
+ pm.Hit(1);
+ movenext = pm.CreateNew(2000);
+ REQUIRE(movenext == true);
+
+ for (uint32_t c = 0; c < 2000; c++)
+ pm.ResetCounter(c, c + 1, c + 2);
+
+ REQUIRE(pm.CurrentLength() == 2000);
+ REQUIRE(pm.CurrentOffset() == 6000);
+ pm.HitWatch(1);
+ movenext = pm.CreateNew(2000);
+ REQUIRE(movenext == true);
+ for (uint32_t c = 0; c < 2000; c++)
+ pm.ResetCounter(c, c + 1, c + 2);
+
+ REQUIRE(pm.CurrentLength() == 2000);
+ REQUIRE(pm.CurrentOffset() == 8000);
+}
+
+TEST_CASE("Copynext should preserve the ids ", "[HitCounter]")
+{
+ PolicyHitCounterManager pm;
+ REQUIRE(pm.CurrentLength() == 0);
+ pm.CreateNew(2000);
+ for (uint32_t c = 0; c < 2000; c++) {
+ pm.ResetCounter(c, c + 1, c + 2);
+ }
+ auto movenext = pm.CopyNext();
+ REQUIRE(movenext == false);
+ REQUIRE(pm.CurrentLength() == 2000);
+ REQUIRE(pm.CurrentOffset() == 0);
+
+ for (uint32_t c = 0; c < 2000; c++) {
+ auto pc = pm.GetAbsoluteCounter(c);
+ REQUIRE(((pc.id_a == c + 1) && (pc.id_b == c + 2)));
+ }
+
+ pm.HitWatch(1);
+ movenext = pm.CopyNext();
+ REQUIRE(movenext == true);
+ REQUIRE(pm.CurrentLength() == 2000);
+ REQUIRE(pm.CurrentOffset() == 2000);
+
+ for (uint32_t c = 0; c < 2000; c++) {
+ auto pc = pm.GetAbsoluteCounter(c + pm.CurrentOffset());
+ REQUIRE((pc.id_a == c + 1 && pc.id_b == c + 2));
+ }
+}
+
+TEST_CASE("hit and hitwatch should count correctly and reset to 0 with copynext ", "[HitCounter]")
+{
+ PolicyHitCounterManager pm;
+ REQUIRE(pm.CurrentLength() == 0);
+ pm.CreateNew(2000);
+ for (uint32_t c = 0; c < 2000; c++) {
+ pm.ResetCounter(c, c + 1, c + 2);
+ }
+
+ for (int c = 0; c < 2000; c++) {
+ for (uint32_t d = 0; d <= c; d++) {
+ pm.Hit(d);
+ pm.HitWatch(1999 - d);
+ }
+ }
+ auto movenext = pm.CopyNext();
+ REQUIRE(movenext == true);
+ REQUIRE(pm.CurrentLength() == 2000);
+ REQUIRE(pm.CurrentOffset() == 2000);
+
+ for (uint32_t c = 0; c < 2000; c++) {
+ auto pc = pm.GetAbsoluteCounter(c);
+ REQUIRE(pc.id_a == c + 1);
+ REQUIRE(pc.id_b == c + 2);
+ REQUIRE(pc.hit == 2000 - c);
+ REQUIRE(pc.hit_watch == c + 1);
+ }
+ // counter id's should be carried over and hit/hit_watch should be 0
+ for (uint32_t c = 0; c < 2000; c++) {
+ auto pc = pm.GetAbsoluteCounter(c + pm.CurrentOffset());
+ REQUIRE((pc.id_a == c + 1 && pc.id_b == c + 2));
+ REQUIRE(((pc.hit == 0)));
+ REQUIRE(((pc.hit_watch == 0)));
+ }
+}
+
+TEST_CASE("moving to the next buffer we should wrap around correctly ", "[HitCounter]")
+{
+ PolicyHitCounterManager pm;
+ int n = hit_counter_array_size / 768;
+ int rest = hit_counter_array_size % 768;
+ for (uint32_t c = 0; c < n; c++) {
+ pm.CreateNew(768);
+ REQUIRE(pm.CurrentOffset() == c * 768);
+ pm.ResetCounter(0, 1, 2);
+ pm.Hit(0);
+ }
+ pm.Hit(1);
+ SECTION("Wrap around exactly at boundary")
+ {
+ pm.CreateNew(rest);
+ REQUIRE(pm.CurrentOffset() == hit_counter_array_size - rest);
+ pm.ResetCounter(0, 1, 2);
+ pm.Hit(1);
+ pm.CreateNew(768);
+ REQUIRE(pm.CurrentOffset() == 0);
+ };
+ SECTION("Wrap around at boundary + 1 ")
+ {
+ pm.CreateNew(rest + 1);
+ REQUIRE(pm.CurrentOffset() == 0);
+ };
+}
diff --git a/aviatrix/plugins/avx_policy_driver/web_filter.cc b/aviatrix/plugins/avx_policy_driver/web_filter.cc
new file mode 100644
index 0000000000..2596b267fa
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/web_filter.cc
@@ -0,0 +1,961 @@
+/*
+ * Implement web filter action
+ */
+#include <dirent.h>
+#include <unistd.h>
+#include <mutex>
+#include <shared_mutex>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <openssl/err.h>
+#include "web_filter.h"
+#include <google/protobuf/arena.h>
+#include <google/protobuf/util/message_differencer.h>
+#include "proto/common/types.pb.h"
+#include "proto/conduit/v2/layer7.pb.h"
+
+#include "ts/ts.h"
+#include "plugin.h"
+#include "policy.h"
+#include "logging.h"
+#include "policy_hit_counter.h"
+
+using google::protobuf::util::MessageDifferencer;
+
+const char *empty_string = "";
+
+PolicyHitCounterManager policy_hit_counter_manager;
+std::function<void(int, int)> send_stats_callback;
+
+bool
+isFinalPolicyResult(PolicyResult result)
+{
+ return result != PolicyResult::POLICY_CONTINUE && result != POLICY_INIT;
+}
+class PolicyHolder
+{
+public:
+ void
+ SetPolicies(Layer7Config &new_config)
+ {
+ Dbg(dbg_ctl, "UpdatePolicy old generation=0x%x", this->generationNumber);
+ std::unique_lock<std::shared_mutex> lock(this->mutex);
+
+ this->current_config = std::make_shared<Layer7Config>(new_config);
+ auto it = new_config.mutable_policies()->begin();
+ auto offset = policy_hit_counter_manager.CurrentOffset();
+ auto length = policy_hit_counter_manager.CurrentLength();
+ auto send_old = policy_hit_counter_manager.CreateNew(new_config.policies_size());
+ uint32_t index = 0;
+
+ if (send_old && send_stats_callback) {
+ send_stats_callback(offset, length);
+ }
+ while (it != new_config.mutable_policies()->end()) {
+ policy_hit_counter_manager.ResetCounter(index++, it->id().a(), it->id().b());
+ it++;
+ }
+
+ trafficFilters.load(new_config);
+ webFilters.load(new_config);
+ this->generationNumber = this->generationNumber + 1;
+ }
+
+ bool
+ IsPolicySet()
+ {
+ std::shared_lock<std::shared_mutex> lock(this->mutex);
+ return this->current_config != nullptr;
+ }
+ std::shared_ptr<Layer7Config>
+ GetCurrentConfig()
+ {
+ return this->current_config;
+ }
+ PolicyResult internalEvaluatePolicySni(TSVConn ssl_vc, PolicyEvalInfo *policy_offset);
+ PolicyResult internalEvaluatePolicyTxn(TSHttpTxn txnp, PolicyEvalInfo *policy_offset);
+
+ std::shared_mutex &
+ Mutex()
+ {
+ return mutex;
+ }
+
+private:
+ bool logAndWatchResult(const char *stage, PolicyResult result, struct sockaddr_in *client_addr, struct sockaddr_in *server_addr,
+ const char *sni_hostname, const char *url, int policy_offset, int decrypt_policy_offset);
+ PolicyResult checkAction(int policy_offset);
+ MatchResult MatchTuple(int policy_offset, struct sockaddr_in *client_addr, struct sockaddr_in *server_addr);
+ MatchResult MatchWebFilter(int policy_offset, std::string_view sni_target, std::string_view url_target);
+ bool HasWebFilters(int policy_offset);
+ bool IsLogPolicy(int policy_offset);
+ bool IsWatchPolicy(int policy_offset);
+ bool MeetFlowRequirements(int policy_offset, TSHttpTxn txnp);
+ bool AllowsDecrypt(int policy_offset);
+ void SetGenerationNumber(PolicyEvalInfo *policy_eval_info);
+ int GetGenerationNumber(PolicyEvalInfo *policy_eval_info);
+ void SetPolicyOffset(PolicyEvalInfo *policy_eval_info, int policy_offset);
+ int GetPolicyOffset(PolicyEvalInfo *policy_eval_info);
+ bool GetPolicyWatched(PolicyEvalInfo *policy_eval_info);
+ void SetPolicyWatched(PolicyEvalInfo *policy_eval_info);
+
+ mutable std::shared_mutex mutex;
+ std::shared_ptr<Layer7Config> current_config;
+ WebFilters webFilters;
+ TrafficFilters trafficFilters;
+ uint16_t generationNumber = 1;
+};
+
+PolicyHolder activePolicy;
+
+void
+PolicyHolder::SetGenerationNumber(PolicyEvalInfo *policy_eval_info)
+{
+ policy_eval_info->fields.generation = this->generationNumber;
+ // *policy_eval_info = ((*policy_eval_info) & (~GENERATION_MASK)) | ((this->generationNumber << GENERATION_SHIFT) &
+ // GENERATION_MASK);
+ Dbg(dbg_ctl, "Set generation number 0x%" PRIx64 " generation=0x%x", policy_eval_info->value, this->generationNumber);
+}
+
+int
+PolicyHolder::GetGenerationNumber(PolicyEvalInfo *policy_eval_info)
+{
+ return policy_eval_info->fields.generation;
+}
+
+void
+PolicyHolder::SetPolicyOffset(PolicyEvalInfo *policy_eval_info, int policy_offset)
+{
+ policy_eval_info->fields.offset = static_cast<int16_t>(policy_offset);
+ policy_eval_info->fields.generation = this->generationNumber;
+ //*policy_eval_info = (((this->generationNumber << GENERATION_SHIFT) & GENERATION_MASK) | (policy_offset & OFFSET_MASK));
+ Dbg(dbg_ctl, "Set policy offset 0x%" PRIx64 " generation=0x%x offset=0x%x", policy_eval_info->value, this->generationNumber,
+ policy_offset);
+}
+
+int
+PolicyHolder::GetPolicyOffset(PolicyEvalInfo *policy_eval_info)
+{
+ if (policy_eval_info->fields.initialized == 0 || policy_eval_info->fields.generation != this->generationNumber) {
+ Dbg(dbg_ctl, "Reset policy offset due to generation number change 0x%x to 0x%" PRIx64, this->generationNumber,
+ policy_eval_info->value);
+ policy_eval_info->fields.initialized = 1;
+ this->SetPolicyOffset(policy_eval_info, 0);
+ return 0;
+ } else {
+ return policy_eval_info->fields.offset;
+ }
+}
+
+bool
+PolicyHolder::GetPolicyWatched(PolicyEvalInfo *policy_eval_info)
+{
+ return policy_eval_info->fields.watched != 0;
+}
+
+void
+PolicyHolder::SetPolicyWatched(PolicyEvalInfo *policy_eval_info)
+{
+ policy_eval_info->fields.watched = 1;
+}
+
+bool
+IsPolicyLoaded()
+{
+ return activePolicy.IsPolicySet();
+}
+
+static std::mutex load_policy_mutex;
+bool
+LoadPolicy(bool skip_if_exists)
+{
+ std::unique_lock<std::mutex> lock(load_policy_mutex);
+
+ auto current_config = activePolicy.GetCurrentConfig();
+ if (skip_if_exists && current_config != nullptr) {
+ // this is on startup, we just want a config
+ // and don't care about the 2nd,3rd,etc request
+ // being blocked by the lock
+ return true;
+ }
+
+ Layer7Config *policyConfig = FetchLayer7Policy();
+ if (policyConfig != nullptr) {
+ if (current_config != nullptr) {
+ // ignore duplicates
+ bool isEqual = MessageDifferencer::Equals(*current_config, *policyConfig);
+ if (isEqual) {
+ Dbg(dbg_ctl, "Skipping policy, same");
+ return true;
+ }
+ }
+ activePolicy.SetPolicies(*policyConfig);
+ }
+ return policyConfig != nullptr;
+}
+
+bool
+SendPolicyHitCounters(int offset, int length)
+{
+ google::protobuf::Arena arena;
+ HitCounters hc;
+ for (int c = offset; c < offset + length; c++) {
+ auto counter = policy_hit_counter_manager.GetAbsoluteCounter(c);
+ if (counter.hit == 0 && counter.hit_watch == 0) {
+ continue;
+ }
+
+ auto tocounter = hc.add_counters();
+
+ // UUID *id = new UUID();
+ auto id = tocounter->mutable_id();
+ id->set_a(counter.id_a);
+ id->set_b(counter.id_b);
+
+ // tocounter->set_allocated_id(id);
+ tocounter->set_hit(counter.hit);
+ tocounter->set_hit_watch(counter.hit_watch);
+ Dbg(dbg_ctl, "%" PRId64 "/%" PRId64 " %" PRId64, counter.id_a, counter.id_b, counter.hit);
+ }
+ Dbg(dbg_ctl, "SendPolicyHitCounters: %d", hc.counters_size());
+ if (hc.counters_size() > 0) {
+ auto result = SendLayer7PolicyHitCounters(hc);
+ Dbg(dbg_ctl, "Result: %d %s", (int)result->result(), result->message().c_str());
+ delete result;
+ }
+ return true;
+}
+
+void
+RotatePolicyHitCounters()
+{
+ bool sendvalues = false;
+ uint32_t offset = 0;
+ uint32_t cur_length = 0;
+ {
+ std::unique_lock<std::shared_mutex> lock(activePolicy.Mutex());
+ offset = policy_hit_counter_manager.CurrentOffset();
+ cur_length = policy_hit_counter_manager.CurrentLength();
+ sendvalues = policy_hit_counter_manager.CopyNext();
+ }
+ Dbg(dbg_ctl, "RotatePolicyHitCounters: %d %d %d", offset, cur_length, (int)sendvalues);
+ if (sendvalues) {
+ SendPolicyHitCounters(offset, cur_length);
+ }
+}
+
+int
+getTupleInfo(int ssl_fd, sockaddr_in *client_addr, sockaddr_in *origin_addr)
+{
+ if (ssl_fd < 0) {
+ auto e = ERR_get_error();
+ TSError("[%s] Failed to access file descriptor for SSL object: %s fd=%x", PLUGIN_NAME, ERR_error_string(e, nullptr), ssl_fd);
+ return -1;
+ }
+ socklen_t addr_size = sizeof(struct sockaddr_in);
+ int res = getpeername(ssl_fd, (struct sockaddr *)client_addr, &addr_size);
+ if (res < 0) {
+ TSError("[%s] Failed to access peer address", PLUGIN_NAME);
+ return -1;
+ }
+ res = getsockname(ssl_fd, (struct sockaddr *)origin_addr, &addr_size);
+ if (res < 0) {
+ TSError("[%s] Failed to access origin address", PLUGIN_NAME);
+ return -1;
+ }
+ return 0;
+}
+
+PolicyResult
+EvaluatePolicySni(TSVConn ssl_vc, uint64_t *policy_eval_info)
+{
+ PolicyEvalInfo *temp = reinterpret_cast<PolicyEvalInfo *>(policy_eval_info);
+ return activePolicy.internalEvaluatePolicySni(ssl_vc, temp);
+}
+
+bool
+PolicyHolder::HasWebFilters(int policy_offset)
+{
+ if (policy_offset < this->current_config->policies_size()) {
+ const Layer7Policy &policy = this->current_config->policies(policy_offset);
+ return policy.web_filter_lists_size() > 0;
+ }
+ return false;
+}
+
+bool
+PolicyHolder::IsWatchPolicy(int policy_offset)
+{
+ if (policy_offset < this->current_config->policies_size()) {
+ const Layer7Policy &policy = this->current_config->policies(policy_offset);
+ return policy.watch();
+ }
+ return false;
+}
+
+bool
+PolicyHolder::IsLogPolicy(int policy_offset)
+{
+ if (policy_offset < this->current_config->policies_size()) {
+ const Layer7Policy &policy = this->current_config->policies(policy_offset);
+ return policy.log();
+ }
+ return false;
+}
+
+bool
+PolicyHolder::AllowsDecrypt(int policy_offset)
+{
+ if (policy_offset < this->current_config->policies_size()) {
+ const Layer7Policy &policy = this->current_config->policies(policy_offset);
+ return policy.decrypt_policy() == Layer7Policy_DecryptPolicy_DECRYPT_ALLOWED;
+ }
+ return false;
+}
+
+PolicyResult
+PolicyHolder::checkAction(int policy_offset)
+{
+ if (policy_offset < this->current_config->policies_size()) {
+ const Layer7Policy &policy = this->current_config->policies(policy_offset);
+ switch (policy.action()) {
+ case Layer7Policy_Action_INTRUSION_DETECTION:
+ return POLICY_IDS;
+ case Layer7Policy_Action_PERMIT:
+ return POLICY_PERMIT;
+ case Layer7Policy_Action_DENY:
+ return POLICY_DROP;
+ default:
+ return POLICY_ERROR;
+ }
+ }
+ return POLICY_END;
+}
+
+bool
+PolicyHolder::logAndWatchResult(const char *stage, PolicyResult result, struct sockaddr_in *client_addr,
+ struct sockaddr_in *server_addr, const char *sni_hostname, const char *url, int policy_offset,
+ int decrypt_policy_offset = -1)
+{
+ Dbg(dbg_ctl, "Entering %s %d", stage, static_cast<int>(result));
+ bool log = false, watch = false;
+ UUID decision_by, decrypted_by;
+ bool decision_by_set = false, decrypted_by_set = false;
+
+ if (policy_offset >= 0 && policy_offset < this->current_config->policies_size()) {
+ const Layer7Policy &policy = this->current_config->policies(policy_offset);
+ log = policy.log();
+ watch = policy.watch();
+ decision_by = policy.id();
+ decision_by_set = true;
+ Dbg(dbg_ctl, "Policy rule %s %s", stage, decision_by.DebugString().c_str());
+ }
+
+ if (!log && result != PolicyResult::POLICY_ERROR) {
+ Dbg(dbg_ctl, "Fast exit %s %d", stage, static_cast<int>(result));
+ return false;
+ }
+
+ if (decrypt_policy_offset >= 0 && decrypt_policy_offset < this->current_config->policies_size()) {
+ const Layer7Policy &policy = this->current_config->policies(decrypt_policy_offset);
+
+ decrypted_by = policy.id();
+ decrypted_by_set = true;
+ }
+
+ // scope the logging of the message
+ {
+ policy_log_message m = {};
+ char decision_by_string[TS_UUID_STRING_LEN + 1];
+ char decrypted_by_string[TS_UUID_STRING_LEN + 1];
+ m.decision = true;
+ m.stage = stage;
+ m.priority = policy_log_prio::log_alert;
+ m.sni_hostname = sni_hostname ? sni_hostname : "";
+ m.src = client_addr;
+ m.dest = server_addr;
+ m.url = url;
+
+ switch (result) {
+ case PolicyResult::POLICY_PERMIT:
+ m.action = "PERMIT";
+ m.reason = "POLICY";
+ break;
+
+ case PolicyResult::POLICY_DROP:
+ m.action = "DROP";
+ m.reason = "POLICY";
+ break;
+ case PolicyResult::POLICY_ERROR:
+ m.action = "DROP";
+ m.reason = "POLICY_ERROR";
+ m.message = "See tslogs";
+ break;
+ case PolicyResult::POLICY_IDS:
+ m.action = "PERMIT";
+ m.reason = "POLICY";
+ // right now IDS is always decrypted
+ // this will change and this logic has to
+ // be changed as well
+ if (!decrypted_by_set) {
+ decrypted_by_set = decision_by_set;
+ decrypted_by = decision_by;
+ }
+ m.ids = true;
+ break;
+ case PolicyResult::POLICY_CONTINUE:
+ // this should not happen
+ return false;
+ default:
+ // should not happen
+ m.action = "DROP";
+ m.reason = "UNEXPECTED_ERROR";
+ m.message = "See tslogs";
+ break;
+ }
+ if (decision_by_set) {
+ sprint_uuid(decision_by_string, decision_by.a(), decision_by.b());
+ m.decided_by = decision_by_string;
+ }
+ if (decrypted_by_set) {
+ sprint_uuid(decrypted_by_string, decrypted_by.a(), decrypted_by.b());
+ m.decrypted_by = decrypted_by_string;
+ }
+
+ m.enforced = !watch;
+ Dbg(dbg_ctl, "Logging now");
+ policy_log(m);
+ }
+ return true;
+}
+
+PolicyResult
+PolicyHolder::internalEvaluatePolicySni(TSVConn ssl_vc, PolicyEvalInfo *policy_eval_info)
+{
+ // Get the tuple info
+ struct sockaddr_in client_addr, origin_addr;
+ bool address_fail = true;
+
+ int server_name_length = 0;
+ const char *server_name = TSVConnSslSniGet(ssl_vc, &server_name_length);
+ if (getTupleInfo(TSVConnFdGet(ssl_vc), &client_addr, &origin_addr) < 0) {
+ Dbg(dbg_ctl, "Failed to get tuple");
+ logAndWatchResult("SNI", POLICY_ERROR, address_fail ? nullptr : &client_addr, address_fail ? nullptr : &origin_addr,
+ server_name, nullptr, -1, -1);
+ return POLICY_ERROR;
+ }
+
+ address_fail = false;
+
+ std::string_view sni_view;
+ std::string_view empty_url_view;
+
+ if (server_name != nullptr) {
+ sni_view = std::string_view(server_name);
+ } else {
+ // MatchWebFilter does not like nullptrs
+ sni_view = std::string_view(empty_string);
+ }
+
+ std::shared_lock<std::shared_mutex> lock(this->mutex);
+ if (policy_eval_info->fields.initialized == 0) {
+ policy_eval_info->fields.initialized = 1;
+ }
+ this->SetGenerationNumber(policy_eval_info);
+ int policy_offset = this->GetPolicyOffset(policy_eval_info);
+ for (int i = policy_offset; i < this->current_config->policies_size(); i++) {
+ auto watch_policy = IsWatchPolicy(i);
+ // move to next policy if we reported a watched policy earlier
+ // no need to evaluate
+ if (watch_policy && GetPolicyWatched(policy_eval_info)) {
+ continue;
+ }
+
+ PolicyResult result = PolicyResult::POLICY_INIT;
+ auto tuple_result = this->MatchTuple(i, &client_addr, &origin_addr);
+ if (tuple_result == MatchValues) {
+ Dbg(dbg_ctl, "Match tuple");
+ // Check the SNI
+ auto web_filter_result = this->MatchWebFilter(i, sni_view, empty_url_view);
+ if (web_filter_result == MatchValues) {
+ Dbg(dbg_ctl, "Match SNI");
+ result = this->checkAction(i);
+ } else if (web_filter_result == NoAttributeToMatch) {
+ if (this->HasWebFilters(i)) {
+ Dbg(dbg_ctl, "Evaluate URL filter");
+ // Are we allowed to decrypt, if not fail now
+ if (!this->AllowsDecrypt(i)) {
+ Dbg(dbg_ctl, "Decrypt is not allowed, drop");
+ result = POLICY_DROP;
+ // The web filters must be for URL
+ } else if (i >= this->current_config->policies_size()) {
+ result = POLICY_END;
+ } else {
+ result = POLICY_CONTINUE;
+ }
+ } else {
+ // Otherwise there were no web_filters on this rule, so we are done
+ Dbg(dbg_ctl, "No SNI filter");
+ result = this->checkAction(i);
+ }
+ } // Otherwise, look at the next policy
+ } else if (tuple_result != NotMatchValues) {
+ Dbg(dbg_ctl, "Does not match tuple");
+ result = POLICY_ERROR; // Shouldn't get here
+ } else {
+ Dbg(dbg_ctl, "Not match policy=%d/0x%" PRIx64, i, policy_eval_info->value);
+ }
+
+ // did not get a result, move on to the next policy
+ if (result == POLICY_INIT) {
+ continue;
+ }
+
+ // only log final results
+ if (isFinalPolicyResult(result)) {
+ if (watch_policy) {
+ policy_hit_counter_manager.HitWatch(i);
+ } else {
+ policy_hit_counter_manager.Hit(i);
+ }
+ if (IsLogPolicy(i)) {
+ logAndWatchResult("SNI", result, address_fail ? nullptr : &client_addr, address_fail ? nullptr : &origin_addr, server_name,
+ nullptr, i);
+ }
+ }
+ // continue evaluating if it is
+ // a watch policy (watch=true is enforced=false)
+ if (watch_policy && result != PolicyResult::POLICY_CONTINUE) {
+ SetPolicyWatched(policy_eval_info);
+ continue;
+ }
+
+ SetPolicyOffset(policy_eval_info, i);
+ return result;
+ }
+ // Ran out of rules before a match
+ Dbg(dbg_ctl, "No matching policy");
+ SetPolicyOffset(policy_eval_info, -1);
+ logAndWatchResult("SNI", POLICY_END, address_fail ? nullptr : &client_addr, address_fail ? nullptr : &origin_addr, server_name,
+ nullptr, -1);
+ return POLICY_END;
+}
+
+PolicyResult
+EvaluatePolicyTxn(TSHttpTxn txnp, uint64_t *policy_eval_info)
+{
+ PolicyEvalInfo *temp = reinterpret_cast<PolicyEvalInfo *>(policy_eval_info);
+ return activePolicy.internalEvaluatePolicyTxn(txnp, temp);
+}
+
+bool
+PolicyHolder::MeetFlowRequirements(int policy_offset, TSHttpTxn txnp)
+{
+ if (policy_offset >= this->current_config->policies_size() || policy_offset < 0) {
+ Dbg(dbg_ctl, "FlowRequirements: No rule offset=%d size=%d", policy_offset, this->current_config->policies_size());
+ return false;
+ }
+ const Layer7Policy &policy = this->current_config->policies(policy_offset);
+ // Is this required to be TLS?
+ if (policy.flow_app_requirement() == Layer7Policy_FlowApp_TLS_REQUIRED) {
+ TSHttpSsn ssnp = TSHttpTxnSsnGet(txnp);
+ if (ssnp == nullptr) {
+ TSError("[%s] Failed to access Txn Session", PLUGIN_NAME);
+ return false;
+ }
+ TSVConn vc = TSHttpSsnClientVConnGet(ssnp);
+ if (vc == nullptr) {
+ TSError("[%s] Failed to access Txn connection", PLUGIN_NAME);
+ return false;
+ }
+ bool retval = TSVConnIsSsl(vc);
+ Dbg(dbg_ctl, "Enforce TLS test=%d", retval);
+ return retval;
+ }
+ return true; // Don't care if this is TLS
+}
+
+PolicyResult
+PolicyHolder::internalEvaluatePolicyTxn(TSHttpTxn txnp, PolicyEvalInfo *policy_eval_info)
+{
+ // Get the tuple info
+ struct sockaddr_in client_addr, origin_addr;
+ std::string s_sni_hostname; // need to rewrite policy_log to string_view instead of const char *
+ std::string s_url; // see above
+ PolicyResult result = PolicyResult::POLICY_ERROR;
+ int fd = -1;
+
+ if (TSHttpTxnClientFdGet(txnp, &fd) == TS_SUCCESS) {
+ if (getTupleInfo(fd, &client_addr, &origin_addr) < 0) {
+ // todo: we should report decrypted_by here, but can not as we're not in the lock
+ logAndWatchResult("txn", POLICY_ERROR, nullptr, nullptr, nullptr, nullptr, -1, -1);
+ return POLICY_ERROR;
+ }
+ } else {
+ TSError("[%s] Failed to access Txn Client file descriptor", PLUGIN_NAME);
+ // todo: we should report decrypted_by here, but can not as we're not in the lock
+ logAndWatchResult("txn", POLICY_ERROR, nullptr, nullptr, nullptr, nullptr, -1, -1);
+ return POLICY_ERROR;
+ }
+
+ std::string_view sni_view;
+ sni_view = std::string_view(empty_string);
+ TSMBuffer mbuf;
+ TSMLoc hdr_loc;
+ if (TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &mbuf, &hdr_loc)) {
+ int host_len = 0;
+ const char *host = TSHttpHdrHostGet(mbuf, hdr_loc, &host_len);
+ if (host != nullptr) {
+ sni_view = std::string_view(host, host_len);
+ s_sni_hostname = std::string(sni_view); // todo: policy_log needs to take string views
+ }
+ }
+
+ int url_len = 0;
+ const char *url = TSHttpTxnEffectiveUrlStringGet(txnp, &url_len);
+ if (url == nullptr || url_len <= 0) {
+ TSError("[%s] Failed load URLx", PLUGIN_NAME);
+ // todo: we should report decrypted_by here, but can not as we're not in the lock
+ logAndWatchResult("txn", POLICY_ERROR, &client_addr, &origin_addr, s_sni_hostname.c_str(), nullptr, -1, -1);
+ return POLICY_ERROR;
+ }
+
+ std::string_view url_view(url, url_len);
+ s_url = std::string(url_view);
+
+ Dbg(dbg_ctl, "Evaluate with FQDN=%.*s and URL=%.*s", static_cast<int>(sni_view.length()), sni_view.data(),
+ static_cast<int>(url_view.length()), url_view.data());
+ std::shared_lock<std::shared_mutex> lock(this->mutex);
+ // Cross check the generation number
+ int policy_offset = 0, decrypted_by = -1;
+
+ if (policy_eval_info->fields.initialized) {
+ if (this->generationNumber == GetGenerationNumber(policy_eval_info)) {
+ policy_offset = this->GetPolicyOffset(policy_eval_info);
+ decrypted_by = policy_offset;
+ }
+ }
+
+ Dbg(dbg_ctl, "incoming policy_offset: %d, decryption offset:%d", policy_offset, decrypted_by);
+ for (int i = policy_offset; i < this->current_config->policies_size(); i++) {
+ auto watch_policy = IsWatchPolicy(i);
+ // move to next policy if we reported a watched policy earlier
+ // no need to evaluate, we can do this early
+ if (watch_policy && GetPolicyWatched(policy_eval_info)) {
+ continue;
+ }
+
+ if (!this->MeetFlowRequirements(i, txnp)) {
+ // Does not match the TLS requirement, try the next rule
+ continue;
+ }
+ auto tuple_result = this->MatchTuple(i, &client_addr, &origin_addr);
+ if (tuple_result == MatchValues) {
+ // Check the SNI
+ auto web_filter_result = this->MatchWebFilter(i, sni_view, url_view);
+ if (web_filter_result == MatchValues || web_filter_result == NoAttributeToMatch) {
+ result = this->checkAction(i);
+
+ // need this for logging
+ if (isFinalPolicyResult(result)) {
+ if (watch_policy) {
+ policy_hit_counter_manager.HitWatch(i);
+ } else {
+ policy_hit_counter_manager.Hit(i);
+ }
+
+ if (IsLogPolicy(i)) {
+ logAndWatchResult("txn", result, &client_addr, &origin_addr, s_sni_hostname.c_str(), s_url.c_str(), i, decrypted_by);
+ }
+ }
+ // continue evaluating if it is
+ // a watch policy (watch=true is enforced=false)
+ if (watch_policy) {
+ SetPolicyWatched(policy_eval_info);
+ continue;
+ }
+ TSfree(const_cast<void *>(reinterpret_cast<const void *>(url)));
+ return result;
+ }
+ }
+ }
+ // Ran out of rules before a match
+
+ logAndWatchResult("txn", POLICY_END, &client_addr, &origin_addr, s_sni_hostname.c_str(), s_url.c_str(), -1, decrypted_by);
+ TSfree(const_cast<void *>(reinterpret_cast<const void *>(url)));
+ return POLICY_END;
+}
+
+MatchResult
+PolicyHolder::MatchWebFilter(int policy_offset, std::string_view sni_view, std::string_view url_view)
+{
+ if (policy_offset >= this->current_config->policies_size() || policy_offset < 0) {
+ Dbg(dbg_ctl, "No rule offset=%d size=%d", policy_offset, this->current_config->policies_size());
+ return NoRuleToMatch;
+ }
+ const Layer7Policy &policy = this->current_config->policies(policy_offset);
+ if (policy.web_filter_lists_size() <= 0) {
+ Dbg(dbg_ctl, "No web filter list %d policy=%d", policy.web_filter_lists_size(), policy_offset);
+ return NoAttributeToMatch;
+ }
+ // If there are multiple web filter lists, must match every list
+ for (int i = 0; i < policy.web_filter_lists_size(); i++) {
+ auto list = policy.web_filter_lists(i);
+ Dbg(dbg_ctl, "Check web list %d", i);
+ bool found_match = false;
+ for (int j = 0; j < list.web_filter_list_size(); j++) {
+ auto id = list.web_filter_list(j);
+ Dbg(dbg_ctl, "Check inner web list %d id=%" PRId64 " %" PRId64, j, id.a(), id.b());
+ auto result = this->webFilters.match(id, sni_view, url_view);
+ if (result == MatchValues) {
+ found_match = true;
+ break;
+ } else if (result == NoAttributeToMatch) {
+ // Wrong type, return immediately
+ return NoAttributeToMatch;
+ }
+ }
+ if (!found_match) {
+ return NotMatchValues;
+ }
+ }
+ // Made it through all lists, must match
+ return MatchValues;
+}
+
+MatchResult
+PolicyHolder::MatchTuple(int policy_offset, struct sockaddr_in *client_addr, struct sockaddr_in *server_addr)
+{
+ if (policy_offset >= this->current_config->policies_size() || policy_offset < 0) {
+ return NoRuleToMatch;
+ }
+ MatchResult src_retval = NotMatchValues;
+ MatchResult dst_retval = NotMatchValues;
+
+ const Layer7Policy &policy = this->current_config->policies(policy_offset);
+
+ if (policy.src_filters_size() == 0) {
+ src_retval = MatchValues;
+ }
+ if (policy.dst_filters_size() == 0) {
+ dst_retval = MatchValues;
+ }
+ // Must match at least one of the src filters and one of the dst_filters
+ for (int i = 0; i < policy.src_filters_size(); i++) {
+ auto id = policy.src_filters(i);
+ if (this->trafficFilters.match(id, client_addr) == MatchValues) {
+ Dbg(dbg_ctl, "Match src");
+ src_retval = MatchValues;
+ break;
+ }
+ }
+ for (int i = 0; i < policy.dst_filters_size(); i++) {
+ auto id = policy.dst_filters(i);
+ if (this->trafficFilters.match(id, server_addr) == MatchValues) {
+ Dbg(dbg_ctl, "Match dst");
+ dst_retval = MatchValues;
+ break;
+ }
+ }
+ if (src_retval != MatchValues || dst_retval != MatchValues) {
+ Dbg(dbg_ctl, "One side doesn't match");
+ return NotMatchValues;
+ }
+
+ // Check the protocol and ports
+ if (policy.protocol() != Layer7Policy_Protocol_PROTOCOL_UNSPECIFIED) {
+ if (policy.protocol() == Layer7Policy_Protocol_TCP) {
+ if (policy.port_ranges_size() > 0) {
+ bool found_match = false;
+ for (int i = 0; i < policy.port_ranges_size(); i++) {
+ const PortRange &pr = policy.port_ranges(i);
+ Dbg(dbg_ctl, "Compare port %d against %d-%d", ntohs(server_addr->sin_port), pr.lo(), pr.hi());
+ if ((pr.lo() <= ntohs(server_addr->sin_port)) && (pr.hi() == 0 || pr.hi() >= ntohs(server_addr->sin_port))) {
+ found_match = true;
+ break;
+ }
+ }
+ if (!found_match) {
+ Dbg(dbg_ctl, "Found no port match %d ports", policy.port_ranges_size());
+ return NotMatchValues;
+ }
+ }
+ } else {
+ Dbg(dbg_ctl, "Policy not TCP %d", policy.protocol());
+ return NotMatchValues;
+ }
+ }
+ return MatchValues;
+}
+
+WebFilterImpl::WebFilterImpl(WebFilter &wb)
+{
+ switch (wb.target()) {
+ case WebFilter_FilterTarget_TARGET_SNI:
+ this->type = SniFilter;
+ break;
+ case WebFilter_FilterTarget_TARGET_URL:
+ this->type = UrlFilter;
+ break;
+ default:
+ break;
+ }
+ this->name = wb.name();
+ Dbg(dbg_ctl, "Load filter %s type %d", this->name.c_str(), this->type);
+ for (int i = 0; i < wb.filter_match_size(); i++) {
+ auto filter_match = wb.filter_match(i);
+ if (filter_match.length() > 0) {
+ if (filter_match[0] == '*') {
+ if (filter_match.length() > 1) {
+ this->suffix_partial_match[filter_match.substr(1)] = true;
+ } else {
+ this->match_all = true;
+ }
+ } else if (filter_match[filter_match.length() - 1] == '*') {
+ this->prefix_partial_match[filter_match.substr(0, filter_match.length() - 1)] = true;
+ } else {
+ this->complete_match[filter_match] = true;
+ }
+ }
+ }
+}
+
+MatchResult
+WebFilterImpl::match(std::string_view sni_target, std::string_view url_target)
+{
+ std::string_view match_target;
+ if (this->type == SniFilter) {
+ match_target = sni_target;
+ } else {
+ if (url_target.length() == 0) {
+ Dbg(dbg_ctl, "Filter type %d name %s", this->type, this->name.c_str());
+ // Don't have input to exercise the URL filter
+ return NoAttributeToMatch;
+ }
+ match_target = url_target;
+ }
+ std::string match_key(match_target.data(), match_target.length());
+ Dbg(dbg_ctl, "Check %s against filter %s", match_key.c_str(), this->name.c_str());
+ if (this->complete_match.find(match_key) != this->complete_match.end()) {
+ Dbg(dbg_ctl, "Complete match");
+ return MatchValues; // A complete match
+ }
+ if (this->type == SniFilter) {
+ // Pull off prefixes and look for partial match
+ if (this->match_all) {
+ Dbg(dbg_ctl, "Wildcard * match %s", match_key.c_str());
+ return MatchValues; // webfilter has a '*' entry
+ }
+ auto offset = match_key.find('.');
+ while (offset != std::string::npos) {
+ match_key = match_key.substr(offset);
+ if (this->suffix_partial_match.find(match_key) != this->suffix_partial_match.end()) {
+ Dbg(dbg_ctl, "Partial match %s", match_key.c_str());
+ return MatchValues; // A partial match
+ }
+ match_key = match_key.substr(1);
+ offset = match_key.find('.');
+ }
+ } // Need to figure out how to evaluate the URL partial match
+ return NotMatchValues;
+}
+
+bool
+WebFilters::load(Layer7Config &config)
+{
+ bool retval = false;
+ // Delete the old data
+ for (auto iter = web_filter_map.begin(); iter != web_filter_map.end(); ++iter) {
+ delete iter->second;
+ }
+ this->web_filter_map.clear();
+ for (int i = 0; i < config.web_filters_size(); i++) {
+ auto wb = config.web_filters(i);
+ if (wb.has_id()) {
+ WebFilterImpl *newWebFilter = new WebFilterImpl(wb);
+ if (newWebFilter != nullptr) {
+ std::pair<uint64_t, uint64_t> key;
+ key.first = wb.id().a();
+ key.second = wb.id().b();
+ Dbg(dbg_ctl, "Load filter %" PRId64 " %" PRId64, wb.id().a(), wb.id().b());
+ web_filter_map.insert(std::make_pair(key, newWebFilter));
+ }
+ }
+ }
+ return retval;
+}
+
+MatchResult
+WebFilters::match(UUID &uuid, std::string_view sni_input, std::string_view url_input)
+{
+ std::pair<uint64_t, uint64_t> key;
+ key.first = uuid.a();
+ key.second = uuid.b();
+ auto ptr = web_filter_map.find(key);
+ if (ptr != web_filter_map.end()) {
+ return ptr->second->match(sni_input, url_input);
+ }
+ Dbg(dbg_ctl, "Did not find filter.");
+ return NoAttributeToMatch;
+}
+
+TrafficFilterImpl::TrafficFilterImpl(MicrosegTrafficFilter &tf)
+{
+ for (int i = 0; i < tf.cidrs_size(); i++) {
+ auto cidr = tf.cidrs(i);
+ Dbg(dbg_ctl, "Add 0x%x / %d", cidr.addr().v4(), cidr.prefix_len());
+ swoc::IP4Net ip_cidr(swoc::IP4Addr(cidr.addr().v4()), swoc::IPMask(cidr.prefix_len()));
+ lookup_map.mark(ip_cidr.as_range(), true);
+ }
+}
+
+bool
+TrafficFilters::load(Layer7Config &config)
+{
+ bool retval = false;
+ // Delete the old data
+ for (auto iter = this->traffic_filter_map.begin(); iter != this->traffic_filter_map.end(); ++iter) {
+ delete iter->second;
+ }
+ this->traffic_filter_map.clear();
+ for (int i = 0; i < config.filters_size(); i++) {
+ auto tf = config.filters(i);
+ if (tf.has_id()) {
+ TrafficFilterImpl *newTrafficFilter = new TrafficFilterImpl(tf);
+ if (newTrafficFilter != nullptr) {
+ std::pair<uint64_t, uint64_t> key;
+ key.first = tf.id().a();
+ key.second = tf.id().b();
+ traffic_filter_map[key] = newTrafficFilter;
+ }
+ }
+ }
+ return retval;
+}
+
+MatchResult
+TrafficFilterImpl::match(const sockaddr_in *sock_addr)
+{
+ Dbg(dbg_ctl, "Lookup %x", ntohl(sock_addr->sin_addr.s_addr));
+ if (this->lookup_map.find(swoc::IP4Addr(sock_addr)) != this->lookup_map.end()) {
+ return MatchValues;
+ } else {
+ return NotMatchValues;
+ }
+}
+
+MatchResult
+TrafficFilters::match(UUID &uuid, struct sockaddr_in *addr)
+{
+ std::pair<uint64_t, uint64_t> key;
+ key.first = uuid.a();
+ key.second = uuid.b();
+ auto ptr = traffic_filter_map.find(key);
+ if (ptr != traffic_filter_map.end()) {
+ return ptr->second->match(addr);
+ }
+ return NotMatchValues;
+}
+
+PolicyHolder ActivePolicy;
diff --git a/aviatrix/plugins/avx_policy_driver/web_filter.h b/aviatrix/plugins/avx_policy_driver/web_filter.h
new file mode 100644
index 0000000000..02467490e0
--- /dev/null
+++ b/aviatrix/plugins/avx_policy_driver/web_filter.h
@@ -0,0 +1,79 @@
+#include <string>
+#include <unordered_map>
+#include <swoc/swoc_ip.h>
+
+#ifndef _WEB_FILTER_H
+#define _WEB_FILTER_H
+#include "proto/conduit/v2/layer7.pb.h"
+#include "proto/conduit/v2/microseg.pb.h"
+#include "proto/common/types.pb.h"
+
+using namespace proto::common;
+
+Layer7Config *FetchLayer7Policy();
+const HitCounterResult *SendLayer7PolicyHitCounters(HitCounters &hit_counters);
+
+enum MatchResult { MatchValues, NotMatchValues, NoRuleToMatch, NoAttributeToMatch };
+
+// A hash function used to hash a UUID
+struct hash_uuid {
+ uint64_t
+ operator()(const std::pair<const uint64_t, const uint64_t> &uuid) const
+ {
+ return uuid.first ^ uuid.second;
+ }
+};
+/*
+ * Data structures to encoding string matches for web filtering
+ */
+class WebFilterImpl
+{
+public:
+ WebFilterImpl(WebFilter &);
+ enum FilterType { SniFilter, UrlFilter };
+ FilterType type = SniFilter;
+ bool match_all = false;
+ MatchResult match(std::string_view sni_input, std::string_view url_input);
+
+private:
+ std::string name;
+ std::unordered_map<std::string, bool> complete_match;
+ std::unordered_map<std::string, bool> suffix_partial_match;
+ std::unordered_map<std::string, bool> prefix_partial_match;
+};
+
+class WebFilters
+{
+public:
+ MatchResult match(UUID &uuid, std::string_view sni_input, std::string_view url_input);
+ bool load(Layer7Config &config);
+
+private:
+ // Keyed by uuid
+ std::unordered_map<std::pair<uint64_t, uint64_t>, WebFilterImpl *, hash_uuid> web_filter_map;
+};
+
+class TrafficFilterImpl
+{
+public:
+ TrafficFilterImpl(MicrosegTrafficFilter &tf);
+ MatchResult match(const struct sockaddr_in *);
+
+private:
+ std::string name;
+ swoc::IPSpace<bool> lookup_map;
+};
+
+class TrafficFilters
+{
+public:
+ MatchResult match(UUID &uuid, struct sockaddr_in *);
+ bool load(Layer7Config &config);
+
+private:
+ // keyed by uuid
+ std::unordered_map<std::pair<uint64_t, uint64_t>, TrafficFilterImpl *, hash_uuid> traffic_filter_map;
+};
+
+extern std::function<void(int, int)> send_stats_callback;
+#endif
diff --git a/aviatrix/plugins/avx_tee_decrypt/CMakeLists.txt b/aviatrix/plugins/avx_tee_decrypt/CMakeLists.txt
new file mode 100644
index 0000000000..313c47a03e
--- /dev/null
+++ b/aviatrix/plugins/avx_tee_decrypt/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_atsplugin(avx_tee_decrypt tee_decrypt.cc tee_info.cc)
+
+target_link_libraries(avx_tee_decrypt)
+verify_global_plugin(avx_tee_decrypt)
diff --git a/aviatrix/plugins/avx_tee_decrypt/plugin.h b/aviatrix/plugins/avx_tee_decrypt/plugin.h
new file mode 100644
index 0000000000..8090a3afd0
--- /dev/null
+++ b/aviatrix/plugins/avx_tee_decrypt/plugin.h
@@ -0,0 +1,5 @@
+namespace avx_tee_decrypt
+{
+extern DbgCtl dbg_ctl;
+}
+using namespace avx_tee_decrypt;
diff --git a/aviatrix/plugins/avx_tee_decrypt/tee_decrypt.cc b/aviatrix/plugins/avx_tee_decrypt/tee_decrypt.cc
new file mode 100644
index 0000000000..68d15cdd6e
--- /dev/null
+++ b/aviatrix/plugins/avx_tee_decrypt/tee_decrypt.cc
@@ -0,0 +1,467 @@
+/** @file
+
+ An example program that does a null transform of response body content.
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+ */
+
+// Copyright (c) 2022, Aviatrix Systems, Inc. All rights reserved.
+
+#include <stdio.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "ts/ts.h"
+#include "plugin.h"
+#include "tee_info.h"
+
+const char PLUGIN_NAME[] = "tee_decrypt";
+namespace avx_tee_decrypt
+{
+DbgCtl dbg_ctl{PLUGIN_NAME};
+}
+
+int data_arg_index = -1;
+
+const char *IDSPolicyTag = "IDSPolicy";
+int IDSPolicyIndex = -1;
+
+class MyData
+{
+public:
+ MyData(TSHttpTxn txnp)
+ {
+ this->resp_output_buffer = TSIOBufferCreate();
+ this->req_output_buffer = TSIOBufferCreate();
+ this->resp_output_reader = TSIOBufferReaderAlloc(this->resp_output_buffer);
+ this->req_output_reader = TSIOBufferReaderAlloc(this->req_output_buffer);
+ this->txnp = txnp;
+ }
+ ~MyData()
+ {
+ Dbg(dbg_ctl, "Delete MyData");
+ if (this->req_output_buffer) {
+ TSIOBufferDestroy(this->req_output_buffer);
+ }
+ if (this->resp_output_buffer) {
+ TSIOBufferDestroy(this->resp_output_buffer);
+ }
+ if (this->tee_info) {
+ delete tee_info;
+ }
+ }
+ TSVIO resp_output_vio = nullptr;
+ TSVIO req_output_vio = nullptr;
+ TSIOBuffer req_output_buffer = nullptr;
+ TSIOBufferReader req_output_reader = nullptr;
+ TSIOBuffer resp_output_buffer = nullptr;
+ TSIOBufferReader resp_output_reader = nullptr;
+ TSHttpTxn txnp;
+ TeeInfo *tee_info = nullptr;
+};
+
+class ContData
+{
+public:
+ ContData(MyData *mydata) : data(mydata) {}
+ ~ContData() { Dbg(dbg_ctl, "~ContData forward=%d", this->forward); }
+ MyData *data = nullptr;
+ bool forward = true;
+};
+
+static void
+send_request_header(TSHttpTxn txnp)
+{
+ MyData *data = static_cast<MyData *>(TSUserArgGet(txnp, data_arg_index));
+ TSMBuffer creq_buff;
+ TSMLoc creq_loc;
+ if (TS_SUCCESS != TSHttpTxnClientReqGet(txnp, &creq_buff, &creq_loc)) {
+ fprintf(stderr, "Failed to get client request");
+ return;
+ }
+ data->tee_info->send_header(creq_buff, creq_loc, true);
+ TSHandleMLocRelease(creq_buff, TS_NULL_MLOC, creq_loc);
+}
+
+static void
+send_response_header(TSHttpTxn txnp)
+{
+ MyData *data = static_cast<MyData *>(TSUserArgGet(txnp, data_arg_index));
+ TSMBuffer sresp_buff;
+ TSMLoc sresp_loc;
+ if (TS_SUCCESS != TSHttpTxnServerRespGet(txnp, &sresp_buff, &sresp_loc)) {
+ TSError("Failed to get server response");
+ // fprintf(stderr, "Failed to get server response");
+ return;
+ }
+ auto field_loc = TSMimeHdrFieldFind(sresp_buff, sresp_loc, TS_MIME_FIELD_TRANSFER_ENCODING, TS_MIME_LEN_TRANSFER_ENCODING);
+ if (field_loc) {
+ TSMimeHdrFieldRemove(sresp_buff, sresp_loc, field_loc);
+ }
+ data->tee_info->send_header(sresp_buff, sresp_loc, false);
+ if (field_loc) {
+ TSMimeHdrFieldAppend(sresp_buff, sresp_loc, field_loc);
+ }
+ TSHandleMLocRelease(sresp_buff, TS_NULL_MLOC, sresp_loc);
+}
+
+static void
+populate_tee_info(MyData *data)
+{
+ if (data->tee_info == nullptr) {
+ // Deferred setting up the tee_info until we knew the destination address
+ sockaddr const *src_addr = TSHttpTxnOutgoingAddrGet(data->txnp); // TSHttpTxnIncomingAddrGet(data->txnp);
+ sockaddr const *dst_addr = TSHttpTxnIncomingAddrGet(data->txnp); // TSHttpTxnOutgoingAddrGet(data->txnp);
+ data->tee_info = new TeeInfo{src_addr, dst_addr};
+ // Go ahead and send the handshake and the request header
+ data->tee_info->send_handshake();
+ send_request_header(data->txnp);
+ }
+}
+
+static void
+check_txn_data(TSHttpTxn txnp)
+{
+ MyData *data = static_cast<MyData *>(TSUserArgGet(txnp, data_arg_index));
+ populate_tee_info(data);
+}
+
+ContData *
+get_cont_data(TSCont contp)
+{
+ ContData *conn_data = static_cast<ContData *>(TSContDataGet(contp));
+ populate_tee_info(conn_data->data);
+ return conn_data;
+}
+
+static void
+handle_transform(TSCont contp, TSEvent event)
+{
+ TSVConn output_conn;
+ TSVIO input_vio;
+ ContData *conn_data;
+ int64_t towrite;
+
+ Dbg(dbg_ctl, "Entering handle_transform()");
+ /* Get the output (downstream) vconnection where we'll write data to. */
+
+ output_conn = TSTransformOutputVConnGet(contp);
+
+ /* Get the write VIO for the write operation that was performed on
+ * ourself. This VIO contains the buffer that we are to read from
+ * as well as the continuation we are to call when the buffer is
+ * empty. This is the input VIO (the write VIO for the upstream
+ * vconnection).
+ */
+ input_vio = TSVConnWriteVIOGet(contp);
+
+ /* Get our data structure for this operation. The private data
+ * structure contains the output VIO and output buffer. If the
+ * private data structure pointer is NULL, then we'll create it
+ * and initialize its internals.
+ */
+ TSVIO output_vio;
+ conn_data = get_cont_data(contp);
+ if (conn_data->forward) {
+ if (conn_data->data->req_output_vio == nullptr && output_conn != nullptr) {
+ conn_data->data->req_output_vio = TSVConnWrite(output_conn, contp, conn_data->data->req_output_reader, INT64_MAX);
+ }
+ output_vio = conn_data->data->req_output_vio;
+ } else if (!conn_data->forward) {
+ if (conn_data->data->resp_output_vio == nullptr && output_conn != nullptr) {
+ conn_data->data->resp_output_vio = TSVConnWrite(output_conn, contp, conn_data->data->resp_output_reader, INT64_MAX);
+ }
+ output_vio = conn_data->data->resp_output_vio;
+ }
+
+ /* Determine how much data we have left to read. For this null
+ * transform plugin this is also the amount of data we have left
+ * to write to the output connection.
+ */
+
+ /* We also check to see if the input VIO's buffer is non-NULL. A
+ * NULL buffer indicates that the write operation has been
+ * shutdown and that the upstream continuation does not want us to send any
+ * more WRITE_READY or WRITE_COMPLETE events. For this simplistic
+ * transformation that means we're done. In a more complex
+ * transformation we might have to finish writing the transformed
+ * data to our output connection.
+ */
+ TSIOBuffer buf_test;
+
+ buf_test = TSVIOBufferGet(input_vio);
+ if (!buf_test) {
+ TSVIONBytesSet(output_vio, TSVIONDoneGet(input_vio));
+ TSVIOReenable(output_vio);
+ return;
+ }
+
+ towrite = TSVIONTodoGet(input_vio);
+ Dbg(dbg_ctl, "\ttoWrite is %" PRId64 "", towrite);
+
+ if (towrite > 0) {
+ /* The amount of data left to read needs to be truncated by
+ * the amount of data actually in the read buffer.
+ */
+ int64_t avail = TSIOBufferReaderAvail(TSVIOReaderGet(input_vio));
+ Dbg(dbg_ctl, "\tavail is %" PRId64 "", avail);
+ if (towrite > avail) {
+ towrite = avail;
+ }
+
+ if (towrite > 0) {
+ /* Copy the data from the read buffer to the output buffer. */
+ TSIOBufferCopy(TSVIOBufferGet(output_vio), TSVIOReaderGet(input_vio), towrite, 0);
+
+ /* Tee the packets to the side for analysis */
+ Dbg(dbg_ctl, "starting send data\n");
+ conn_data->data->tee_info->send_data(TSVIOReaderGet(input_vio), conn_data->forward);
+ Dbg(dbg_ctl, "ending send data\n");
+ /* Tell the read buffer that we have read the data and are no
+ * longer interested in it.
+ */
+ TSIOBufferReaderConsume(TSVIOReaderGet(input_vio), towrite);
+
+ /* Modify the input VIO to reflect how much data we've
+ * completed.
+ */
+ TSVIONDoneSet(input_vio, TSVIONDoneGet(input_vio) + towrite);
+ // TSVIONBytesSet(output_vio, TSVIONDoneGet(input_vio));
+ }
+ }
+
+ /* Now we check the input VIO to see if there is data left to
+ * read.
+ */
+ if (TSVIONTodoGet(input_vio) > 0) {
+ // TSVIOReenable(output_vio);
+ if (towrite > 0) {
+ // TSVIONBytesSet(output_vio, towrite);
+ /* If there is data left to read, then we reenable the output
+ * connection by reenabling the output VIO. This will wake up
+ * the output connection and allow it to consume data from the
+ * output buffer.
+ */
+ TSVIOReenable(output_vio);
+
+ /* Call back the input VIO continuation to let it know that we
+ * are ready for more data.
+ */
+ TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_READY, input_vio);
+ }
+ } else {
+ /* If there is no data left to read, then we modify the output
+ * VIO to reflect how much data the output connection should
+ * expect. This allows the output connection to know when it
+ * is done reading. We then reenable the output connection so
+ * that it can consume the data we just gave it.
+ */
+ TSVIONBytesSet(output_vio, TSVIONDoneGet(input_vio));
+
+ if (TSVConnClosedGet(contp)) {
+ Dbg(dbg_ctl, "\tVConn is closed");
+ delete conn_data;
+ TSContDestroy(contp);
+ } else if (towrite > 0) {
+ /* Call back the input VIO continuation to let it know that we
+ * have completed the write operation.
+ */
+ TSVIOReenable(output_vio);
+ TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_COMPLETE, input_vio);
+ }
+ }
+}
+
+static int
+null_transform(TSCont contp, TSEvent event, void *edata)
+{
+ /* Check to see if the transformation has been closed by a call to
+ * TSVConnClose.
+ */
+ Dbg(dbg_ctl, "Entering null_transform() event=%d", event);
+
+ if (TSVConnClosedGet(contp)) {
+ Dbg(dbg_ctl, "\tVConn is closed");
+ ContData *conn_data = (ContData *)TSContDataGet(contp);
+ if (conn_data) {
+ delete conn_data;
+ }
+ TSContDestroy(contp);
+ return 0;
+ } else {
+ switch (event) {
+ case TS_EVENT_ERROR: {
+ TSVIO input_vio;
+
+ Dbg(dbg_ctl, "\tEvent is TS_EVENT_ERROR");
+ /* Get the write VIO for the write operation that was
+ * performed on ourself. This VIO contains the continuation of
+ * our parent transformation. This is the input VIO.
+ */
+ input_vio = TSVConnWriteVIOGet(contp);
+
+ /* Call back the write VIO continuation to let it know that we
+ * have completed the write operation.
+ */
+ TSContCall(TSVIOContGet(input_vio), TS_EVENT_ERROR, input_vio);
+ } break;
+ case TS_EVENT_VCONN_WRITE_COMPLETE:
+ Dbg(dbg_ctl, "\tEvent is TS_EVENT_VCONN_WRITE_COMPLETE");
+ /* When our output connection says that it has finished
+ * reading all the data we've written to it then we should
+ * shutdown the write portion of its connection to
+ * indicate that we don't want to hear about it anymore.
+ */
+ TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1);
+ break;
+
+ /* If we get a WRITE_READY event or any other type of
+ * event (sent, perhaps, because we were re-enabled) then
+ * we'll attempt to transform more data.
+ */
+ case TS_EVENT_VCONN_WRITE_READY:
+ Dbg(dbg_ctl, "\tEvent is TS_EVENT_VCONN_WRITE_READY");
+ handle_transform(contp, event);
+ break;
+ default:
+ Dbg(dbg_ctl, "\t(event is %d)", event);
+ handle_transform(contp, event);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static void
+transform_add(TSHttpTxn txnp, TSCont orig_contp)
+{
+ TSVConn connp, rev_connp;
+
+ Dbg(dbg_ctl, "Entering transform_add()");
+ connp = TSTransformCreate(null_transform, txnp);
+ rev_connp = TSTransformCreate(null_transform, txnp);
+ MyData *data = new MyData{txnp};
+ ContData *conn_data = new ContData{data};
+ ContData *rev_conn_data = new ContData{data};
+ rev_conn_data->forward = false;
+ TSContDataSet(connp, conn_data);
+ TSContDataSet(rev_connp, rev_conn_data);
+ TSUserArgSet(txnp, data_arg_index, data);
+ TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, rev_connp);
+ TSHttpTxnHookAdd(txnp, TS_HTTP_REQUEST_TRANSFORM_HOOK, connp);
+ TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, orig_contp);
+}
+
+bool
+IDSPolicySet(TSHttpTxn txnp)
+{
+ // First see if the txn arg is set
+ if (TS_SUCCESS != TSUserArgIndexReserve(TS_USER_ARGS_TXN, IDSPolicyTag, "IDS policy", &IDSPolicyIndex)) {
+ TSError("Failed to register IDS policy index");
+ return false;
+ }
+ void *val = TSUserArgGet(txnp, IDSPolicyIndex);
+ if (val == nullptr) { // Check the vconn arg
+ // Fetch the vconn from the txn
+ TSHttpSsn ssnp = TSHttpTxnSsnGet(txnp);
+ TSVConn vconn = TSHttpSsnClientVConnGet(ssnp);
+ if (TS_SUCCESS != TSUserArgIndexReserve(TS_USER_ARGS_VCONN, IDSPolicyTag, "IDS policy", &IDSPolicyIndex)) {
+ TSError("Failed to register IDS policy index");
+ return false;
+ }
+ val = TSUserArgGet(vconn, IDSPolicyIndex);
+ Dbg(dbg_ctl, "Policy arg set on vconn idx=%d val=0x%x", IDSPolicyIndex, static_cast<int>(reinterpret_cast<intptr_t>(val)));
+ } else {
+ Dbg(dbg_ctl, "Policy arg set on txn idx=%d val=0x%x", IDSPolicyIndex, static_cast<int>(reinterpret_cast<intptr_t>(val)));
+ }
+ // Do the IDS if val is not 0
+ return val != nullptr;
+}
+
+static int
+transform_plugin(TSCont contp, TSEvent event, void *edata)
+{
+ TSHttpTxn txnp = (TSHttpTxn)edata;
+
+ Dbg(dbg_ctl, "Entering transform_plugin() event=%d", event);
+ switch (event) {
+ case TS_EVENT_HTTP_TXN_CLOSE: {
+ Dbg(dbg_ctl, "\tEvent is TS_EVENT_HTTP_TXN_CLOSE");
+ // Clean things up.
+ TSHttpTxn txnp = static_cast<TSHttpTxn>(edata);
+ MyData *data = static_cast<MyData *>(TSUserArgGet(txnp, data_arg_index));
+ if (data) {
+ if (data->tee_info) {
+ data->tee_info->send_finack();
+ }
+ delete data;
+ }
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ return 0;
+ }
+ case TS_EVENT_HTTP_READ_REQUEST_HDR:
+ Dbg(dbg_ctl, "\tEvent is TS_EVENT_HTTP_READ_REQUEST_HDR");
+ // Is this indicated by policy?
+ if (IDSPolicySet(txnp)) {
+ transform_add(txnp, contp);
+ }
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ return 0;
+ case TS_EVENT_HTTP_READ_RESPONSE_HDR:
+ Dbg(dbg_ctl, "\tEvent is TS_EVENT_HTTP_READ_RESPONSE_HDR");
+ if (TSUserArgGet(txnp, data_arg_index) != nullptr) {
+ check_txn_data(txnp);
+ send_response_header(txnp);
+ }
+ TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+ return 0;
+ default:
+ Dbg(dbg_ctl, "\tOther Event %d", event);
+ break;
+ }
+
+ return 0;
+}
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+ TSPluginRegistrationInfo info;
+
+ info.plugin_name = PLUGIN_NAME;
+ info.vendor_name = "Aviatrix";
+ info.support_email = "dev@trafficserver.apache.org";
+
+ if (TSPluginRegister(&info) != TS_SUCCESS) {
+ TSError("[%s] Plugin registration failed", PLUGIN_NAME);
+
+ } else {
+ if (argc < 3) {
+ TSError("[%s] Plugin failed. Requires arguments <src_gre_address> and <dst_gre_address>", PLUGIN_NAME);
+ return;
+ }
+ TSCont contp = TSContCreate(transform_plugin, NULL);
+ TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, contp);
+ TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, contp);
+ TSUserArgIndexReserve(TS_USER_ARGS_TXN, "tee_data", "", &data_arg_index);
+ gre_info.init(argv, argc); // Initialize some data structures
+ }
+ return;
+}
diff --git a/aviatrix/plugins/avx_tee_decrypt/tee_info.cc b/aviatrix/plugins/avx_tee_decrypt/tee_info.cc
new file mode 100644
index 0000000000..dbd9aafc57
--- /dev/null
+++ b/aviatrix/plugins/avx_tee_decrypt/tee_info.cc
@@ -0,0 +1,366 @@
+// Copyright (c) 2022, Aviatrix Systems, Inc. All rights reserved.
+
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <limits.h>
+#include <sys/capability.h>
+#include <errno.h>
+#include <time.h>
+#include "ts/ts.h"
+#include "plugin.h"
+#include "tee_info.h"
+
+struct grehdr {
+ uint16_t flags;
+ uint16_t protocol;
+} __packed;
+
+GreInfo gre_info;
+
+thread_local int Raw_socket = 0;
+
+TeeInfo::TeeInfo(sockaddr const *src_addr, sockaddr const *dst_addr)
+{
+ if (src_addr->sa_family != AF_INET || dst_addr->sa_family != AF_INET) {
+ // Only support IPv4 right now
+ TSError(" Invalid request. Only support IPv4 so far");
+ return;
+ }
+
+ struct sockaddr_in *addr = (struct sockaddr_in *)src_addr;
+ this->src_port = addr->sin_port;
+ this->src_addr = addr->sin_addr.s_addr;
+
+ Dbg(dbg_ctl, "\tSrc addr is %u ", addr->sin_addr.s_addr);
+
+ addr = (struct sockaddr_in *)dst_addr;
+ this->dst_port = addr->sin_port;
+ this->dst_addr = addr->sin_addr.s_addr;
+ Dbg(dbg_ctl, "\tDest addr is %u ", addr->sin_addr.s_addr);
+}
+
+struct tcphdr *
+get_tcp_hdr(char *buffer)
+{
+ return (struct tcphdr *)(buffer + 2 * sizeof(struct iphdr) + sizeof(struct grehdr));
+}
+
+int
+TeeInfo::build_packet(char *buffer, int payload_size, bool forward)
+{
+ struct tcphdr *tcph;
+ struct iphdr *outter_iphdr, *inner_iphdr;
+
+ outter_iphdr = (struct iphdr *)(buffer);
+ inner_iphdr = (struct iphdr *)(buffer + sizeof(struct iphdr) + sizeof(struct grehdr));
+ struct grehdr *gre_hdr = (struct grehdr *)(buffer + sizeof(struct iphdr));
+ gre_hdr->flags = 0;
+ gre_hdr->protocol = htons(0x0800); // IP
+
+ outter_iphdr->ihl = 5; /* header length 5 * sizeof(uint32) = 20 bytes – no optional elements*/
+ outter_iphdr->version = 4; /* ip version 4 */
+ outter_iphdr->tos = 0; /* type of service */
+ outter_iphdr->tot_len = htons(2 * sizeof(struct iphdr) + sizeof(struct grehdr) + sizeof(struct tcphdr) + payload_size);
+ outter_iphdr->ttl = 64; /* time to live */
+ outter_iphdr->protocol = 47;
+ outter_iphdr->saddr = gre_info.src_addr; /* use loopback address for source */
+ outter_iphdr->daddr = gre_info.dst_addr; /* use loopback address for dest */
+ outter_iphdr->check = 0;
+
+ inner_iphdr->ihl = 5; /* header length 5 * sizeof(uint32) = 20 bytes – no optional elements*/
+ inner_iphdr->version = 4; /* ip version 4 */
+ inner_iphdr->tos = 0; /* type of service */
+ inner_iphdr->tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr) + payload_size);
+ inner_iphdr->ttl = 64; /* time to live */
+ inner_iphdr->protocol = IPPROTO_TCP;
+ inner_iphdr->saddr = forward ? this->src_addr : this->dst_addr;
+ inner_iphdr->daddr = forward ? this->dst_addr : this->src_addr;
+ inner_iphdr->check = 0;
+
+ tcph = get_tcp_hdr(buffer);
+
+ // TCP Header
+ tcph->source = forward ? this->src_port : this->dst_port;
+ tcph->dest = forward ? this->dst_port : this->src_port;
+ tcph->seq = 0;
+ tcph->ack_seq = 0;
+ tcph->doff = 5; // tcp header size
+ tcph->fin = 0;
+ tcph->syn = 0;
+ tcph->rst = 0;
+ tcph->psh = 0;
+ tcph->ack = 0;
+ tcph->urg = 0;
+ tcph->window = htons(5840); /* maximum allowed window size */
+ tcph->check = 0; // leave checksum 0 now, filled later by pseudo header
+ tcph->urg_ptr = 0;
+ return 1;
+}
+
+void
+GreInfo::init(const char *argv[], int argc)
+{
+ this->src_addr = inet_addr(argv[1]);
+ this->clientaddr.sin_family = AF_INET;
+ this->clientaddr.sin_addr.s_addr = this->src_addr;
+ this->dst_addr = inet_addr(argv[2]);
+}
+
+int
+open_raw_socket()
+{
+ // Must acquire CAP_NET_RAW capability
+ cap_t new_cap_state = cap_get_proc();
+ cap_value_t cap_list[1];
+ cap_list[0] = CAP_NET_RAW;
+ cap_set_flag(new_cap_state, CAP_EFFECTIVE, 1, cap_list, CAP_SET);
+ if (cap_set_proc(new_cap_state) != 0) {
+ perror("cap_set_proc set");
+ cap_free(new_cap_state);
+ return 0;
+ }
+ Raw_socket = socket(PF_INET, SOCK_RAW, IPPROTO_RAW);
+ if (Raw_socket < 0) {
+ perror("socket() error");
+ }
+ // Drop the extra privilege
+ cap_set_flag(new_cap_state, CAP_EFFECTIVE, 1, cap_list, CAP_CLEAR);
+ if (cap_set_proc(new_cap_state) != 0) {
+ perror("cap_set_proc clear");
+ cap_free(new_cap_state);
+ return 0;
+ }
+ cap_free(new_cap_state);
+ return Raw_socket > 0;
+}
+
+int
+GreInfo::send_packet(char *buffer, struct iovec *ios, int iovec_count)
+{
+ ssize_t num_bytes;
+ // Fetch or create the raw_socket
+ if (Raw_socket <= 0) {
+ if (!open_raw_socket()) {
+ exit(1);
+ return 0;
+ }
+ }
+ if (iovec_count == 0) {
+ num_bytes =
+ sendto(Raw_socket, buffer, TeeInfo::full_hdr_size, 0, (struct sockaddr *)&this->clientaddr, sizeof(struct sockaddr_in));
+ } else {
+ // Use vectoring mechanism to avoid copying payload yet again
+ struct msghdr msg;
+ msg.msg_name = &this->clientaddr;
+ msg.msg_namelen = sizeof(struct sockaddr_in);
+ msg.msg_iov = ios;
+ msg.msg_iovlen = iovec_count;
+ msg.msg_control = nullptr;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+
+ ssize_t total = 0;
+ for (int i = 0; i < iovec_count; i++) {
+ total += ios[i].iov_len;
+ }
+ Dbg(dbg_ctl, "length sending %zu\n", total);
+ num_bytes = sendmsg(Raw_socket, &msg, 0);
+ }
+ if (num_bytes == -1) {
+ TSError("Could not send data on raw socket, err %d \n", errno);
+
+ return 0;
+ }
+ return 1;
+}
+
+int
+TeeInfo::send_handshake()
+{
+ srand(time(NULL));
+ int r = rand();
+ int r2 = rand();
+ char buffer[TeeInfo::full_hdr_size] = {0};
+ this->build_packet(buffer, 0, true);
+ struct tcphdr *tcph = get_tcp_hdr(buffer);
+ tcph->syn = 1;
+ tcph->seq = htonl(r); // 199
+ tcph->window = htons(65535);
+ if (!gre_info.send_packet(buffer, nullptr, 0)) {
+ return 0;
+ }
+
+ this->build_packet(buffer, 0, false);
+ tcph = get_tcp_hdr(buffer);
+ tcph->syn = 1;
+ tcph->ack = 1;
+ tcph->ack_seq = htonl(r + 1); // 200
+ tcph->seq = htonl(r2); // 1
+ tcph->window = htons(65535);
+ if (!gre_info.send_packet(buffer, nullptr, 0)) {
+ return 0;
+ }
+
+ // buffer[TeeInfo::full_hdr_size] = {0};
+ this->build_packet(buffer, 0, true);
+ tcph = get_tcp_hdr(buffer);
+ tcph->ack = 1;
+ tcph->seq = htonl(r + 1);
+ tcph->ack_seq = htonl(r2 + 1);
+ this->forward_seqno = r + 1;
+ this->reverse_seqno = r2 + 1;
+ tcph->window = htons(65535);
+ if (!gre_info.send_packet(buffer, nullptr, 0)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+// Returns the next sequence number or -1
+int
+TeeInfo::send_data(TSIOBufferReader reader, bool forward)
+{
+ char buffer[TeeInfo::full_hdr_size] = {0};
+ struct iovec ios[IOV_MAX];
+ int iov_count = 1;
+
+ int MAX_SIZE = 65463;
+
+ // How much data?
+ int64_t data_len;
+ const char *buf;
+ int64_t total_size = 0;
+ TSIOBufferBlock blk = TSIOBufferReaderStart(reader);
+ while (blk != nullptr) {
+ buf = TSIOBufferBlockReadStart(blk, reader, &data_len);
+
+ do { // Iterate over current blk
+ int data_tosend = data_len;
+ const char *buf2 = const_cast<char *>(buf);
+ bool send_packet = false;
+
+ if ((data_len + total_size) > MAX_SIZE) {
+ data_tosend = MAX_SIZE - total_size;
+ buf = buf + data_tosend;
+ send_packet = true;
+ }
+
+ data_len = data_len - data_tosend;
+
+ ios[iov_count].iov_base = const_cast<char *>(buf2);
+ ios[iov_count++].iov_len = data_tosend;
+ total_size += data_tosend;
+
+ if (data_len == 0) {
+ blk = TSIOBufferBlockNext(blk);
+ if (blk == nullptr) {
+ send_packet = true;
+ }
+ }
+
+ if (iov_count == IOV_MAX) {
+ send_packet = true;
+ }
+
+ if (send_packet) {
+ Dbg(dbg_ctl, "Sending data size %ld %s \n", total_size, forward ? "H -> E" : "E -> H");
+
+ this->build_packet(buffer, total_size, forward);
+ ios[0].iov_base = buffer;
+ ios[0].iov_len = TeeInfo::full_hdr_size;
+ struct tcphdr *tcph = get_tcp_hdr(buffer);
+ tcph->seq = htonl(forward ? this->forward_seqno : this->reverse_seqno);
+ tcph->ack_seq = htonl(!forward ? this->forward_seqno : this->reverse_seqno);
+ tcph->ack = 1;
+ tcph->psh = 1;
+ tcph->window = htons(65535);
+ if (!gre_info.send_packet(nullptr, ios, iov_count)) {
+ return 0;
+ }
+
+ if (forward) {
+ this->forward_seqno += total_size;
+ } else {
+ this->reverse_seqno += total_size;
+ }
+
+ // Send the ACK immediately
+ this->build_packet(buffer, 0, !forward);
+ tcph = get_tcp_hdr(buffer);
+ tcph->ack_seq = htonl(forward ? this->forward_seqno : this->reverse_seqno);
+ tcph->seq = htonl(!forward ? this->forward_seqno : this->reverse_seqno);
+ tcph->ack = 1;
+ tcph->window = htons(65535);
+ if (!gre_info.send_packet(buffer, nullptr, 0)) {
+ return 0;
+ }
+
+ Dbg(dbg_ctl, "sending ACK %s\n", !forward ? "H -> E" : "E -> H");
+
+ iov_count = 1;
+ total_size = 0;
+ }
+
+ } while (data_len > 0);
+ }
+
+ return 1;
+}
+
+int
+TeeInfo::send_header(TSMBuffer bufp, TSMLoc hdr, bool forward)
+{
+ TSIOBuffer iobuffer = TSIOBufferCreate();
+ TSHttpHdrPrint(bufp, hdr, iobuffer);
+ int retval = this->send_data(TSIOBufferReaderAlloc(iobuffer), forward);
+ TSIOBufferDestroy(iobuffer);
+ return retval;
+}
+
+int
+TeeInfo::send_finack()
+{
+ char buffer[TeeInfo::full_hdr_size] = {0};
+ this->build_packet(buffer, 0, true);
+ struct tcphdr *tcph = get_tcp_hdr(buffer);
+ tcph->fin = 1;
+ tcph->ack = 1;
+ tcph->seq = htonl(this->forward_seqno);
+ tcph->ack_seq = htonl(this->reverse_seqno);
+ tcph->window = htons(65535);
+ if (!gre_info.send_packet(buffer, nullptr, 0)) {
+ return 0;
+ }
+
+ this->build_packet(buffer, 0, false);
+ tcph = get_tcp_hdr(buffer);
+ tcph->fin = 1;
+ tcph->ack = 1;
+ tcph->seq = htonl(this->reverse_seqno);
+ tcph->ack_seq = htonl(this->forward_seqno);
+ tcph->window = htons(65535);
+ if (!gre_info.send_packet(buffer, nullptr, 0)) {
+ return 0;
+ }
+
+ this->build_packet(buffer, 0, true);
+ tcph = get_tcp_hdr(buffer);
+ tcph->ack = 1;
+ tcph->seq = htonl(this->forward_seqno + 1);
+ tcph->ack_seq = htonl(this->reverse_seqno + 1);
+ tcph->window = htons(65535);
+ this->forward_seqno = 200;
+ this->reverse_seqno = 2;
+ tcph->window = htons(65535);
+ if (!gre_info.send_packet(buffer, nullptr, 0)) {
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/aviatrix/plugins/avx_tee_decrypt/tee_info.h b/aviatrix/plugins/avx_tee_decrypt/tee_info.h
new file mode 100644
index 0000000000..afa56c2e92
--- /dev/null
+++ b/aviatrix/plugins/avx_tee_decrypt/tee_info.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2022, Aviatrix Systems, Inc. All rights reserved.
+
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include "ts/ts.h"
+
+class TeeInfo
+{
+public:
+ TeeInfo(sockaddr const *src_addr, sockaddr const *dst_addr);
+ int build_packet(char *buffer, int payload_size, bool forward);
+ int send_data(TSIOBufferReader reader, bool forward = true);
+ int send_header(TSMBuffer bufp, TSMLoc hdr, bool forward = true);
+ int send_handshake();
+ int send_finack();
+
+ static const int full_hdr_size = 2 * sizeof(struct iphdr) + sizeof(struct tcphdr) + 4;
+
+private:
+ uint32_t forward_seqno = 0;
+ uint32_t reverse_seqno = 0;
+ uint16_t src_port = 0;
+ uint16_t dst_port = 0;
+ in_addr_t src_addr = 0;
+ in_addr_t dst_addr = 0;
+};
+
+class GreInfo
+{
+public:
+ void init(const char *argv[], int argc);
+ int send_packet(char *, struct iovec *data, int data_count);
+ in_addr_t src_addr = 0;
+ in_addr_t dst_addr = 0;
+
+private:
+ struct sockaddr_in clientaddr;
+};
+
+extern GreInfo gre_info;
diff --git a/include/ts/ts.h b/include/ts/ts.h
index 956963faae..00a6e7b2cd 100644
--- a/include/ts/ts.h
+++ b/include/ts/ts.h
@@ -1995,6 +1995,33 @@ TSAction TSNetConnect(
struct sockaddr const *to /**< Address to which to connect. */
);
+struct TSNetConnectOptions {
+ struct sockaddr const *from; /**< Address from which to connect. NULL is default */
+ bool use_from_port; /**< Use the from port, if false the from port will be determined by the OS */
+ struct sockaddr const *to; /**< Address to which to connect. */
+ bool tls; /**< Use TLS to connect */
+ bool verify_origin_cert; /**< Verify the signature of the origin certificate, use records.config settings */
+ const char *sni_host_name; /**< sni hostname to use, NULL for none */
+ const char *alpn_protocol_group[20]; /**< alpn protocols list, NULL terminated, NULL is default h1/h2 */
+};
+
+TSAction TSNetConnectAdvanced(
+ TSCont contp, /**< continuation that is called back when the attempted net connection either succeeds or fails. */
+ struct TSNetConnectOptions *net_options);
+/**
+ * * print the internet address into a buffer, with port
+ * */
+const char *TSIPNPToP(const struct sockaddr *addr, ///< Address.
+ char *dst, ///< Output buffer.
+ size_t size);
+
+/**
+ * print the internet address into a buffer, without port
+ */
+const char *TSIPNToP(const struct sockaddr *addr, ///< Address.
+ char *dst, ///< Output buffer.
+ size_t size);
+
/**
* Retrieves the continuation associated with creating the TSVConn
*/
diff --git a/include/tscore/TestBox.h b/include/tscore/TestBox.h
index c605f0f148..4d093f9b57 100644
--- a/include/tscore/TestBox.h
+++ b/include/tscore/TestBox.h
@@ -54,6 +54,8 @@ struct TestBox {
}
};
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
bool
TestBox::check(bool result, char const *fmt, ...)
{
@@ -69,4 +71,5 @@ TestBox::check(bool result, char const *fmt, ...)
}
return result;
}
+#pragma GCC diagnostic pop
} // namespace
diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc
index 17c10b3bba..5c0d5f60ae 100644
--- a/src/api/InkAPI.cc
+++ b/src/api/InkAPI.cc
@@ -5932,6 +5932,68 @@ TSNetConnect(TSCont contp, sockaddr const *addr)
return reinterpret_cast<TSAction>(netProcessor.connect_re(reinterpret_cast<INKContInternal *>(contp), addr, opt));
}
+TSAction
+TSNetConnectAdvanced(
+ TSCont contp, /**< continuation that is called back when the attempted net connection either succeeds or fails. */
+ TSNetConnectOptions *net_options)
+{
+ sdk_assert(sdk_sanity_check_continuation(contp) == TS_SUCCESS);
+ sdk_assert(ats_is_ip(net_options->to));
+ NetVCOptions opt;
+
+ HttpConfigParams *http_config_param = HttpConfig::acquire();
+
+ if (http_config_param) {
+ opt.set_sock_param(http_config_param->oride.sock_recv_buffer_size_out, http_config_param->oride.sock_send_buffer_size_out,
+ http_config_param->oride.sock_option_flag_out, http_config_param->oride.sock_packet_mark_out,
+ http_config_param->oride.sock_packet_tos_out);
+ HttpConfig::release(http_config_param);
+ }
+
+ if (net_options->from) {
+ sdk_assert(ats_is_ip(net_options->from));
+ opt.addr_binding = NetVCOptions::FOREIGN_ADDR;
+ opt.local_ip.assign(net_options->from);
+ opt.local_port = ats_ip_port_host_order(net_options->use_from_port ? net_options->from : 0);
+ }
+ FORCE_PLUGIN_SCOPED_MUTEX(contp);
+ if (net_options->tls) {
+ if (net_options->sni_host_name && net_options->sni_host_name[0] != 0) {
+ opt.set_sni_hostname(net_options->sni_host_name, strlen(net_options->sni_host_name));
+ }
+ TSWarning("TSNetConnectAdvanced connecting ssl");
+ if (net_options->verify_origin_cert) {
+ TSWarning("TSNetConnectAdvanced validate origin certificate");
+ // Set based on the current values of proxy.config.ssl.client.verify.server.policy
+ // and proxy.config.ssl.client.verify.server.properties
+ SSLConfigParams *params = SSLConfig::acquire();
+ if (params) {
+ opt.verifyServerPolicy = params->verifyServerPolicy;
+ opt.verifyServerProperties = params->verifyServerProperties;
+ }
+ }
+ return reinterpret_cast<TSAction>(sslNetProcessor.connect_re(reinterpret_cast<INKContInternal *>(contp), net_options->to, opt));
+ } else {
+ TSWarning("TSNetConnectAdvanced connecting plain");
+ return reinterpret_cast<TSAction>(netProcessor.connect_re(reinterpret_cast<INKContInternal *>(contp), net_options->to, opt));
+ }
+}
+
+const char *
+TSIPNPToP(const sockaddr *addr, ///< Address.
+ char *dst, ///< Output buffer.
+ size_t size)
+{
+ return ats_ip_nptop(addr, dst, size);
+}
+const char *
+TSIPNToP(const sockaddr *addr, ///< Address.
+ char *dst, ///< Output buffer.
+ size_t size)
+{
+ return ats_ip_ntop(addr, dst, size);
+}
+
TSAction
TSNetConnectTransparent(TSCont contp, sockaddr const *client_addr, sockaddr const *server_addr)
{
diff --git a/src/proxy/http/HttpSessionManager.cc b/src/proxy/http/HttpSessionManager.cc
index b64ba0ce70..3f146e0e63 100644
--- a/src/proxy/http/HttpSessionManager.cc
+++ b/src/proxy/http/HttpSessionManager.cc
@@ -182,6 +182,8 @@ ServerSessionPool::acquireSession(sockaddr const *addr, CryptoHash const &hostna
// The range is all that is needed in the match IP case, otherwise need to scan for matching fqdn
// And matches the other constraints as well
// Note the port is matched as part of the address key so it doesn't need to be checked again.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstringop-overread"
if (match_style & (~TS_SERVER_SESSION_SHARING_MATCH_MASK_IP)) {
while (iter != end) {
if ((!(match_style & TS_SERVER_SESSION_SHARING_MATCH_MASK_HOSTONLY) || iter->hostname_hash == hostname_hash) &&
@@ -196,6 +198,7 @@ ServerSessionPool::acquireSession(sockaddr const *addr, CryptoHash const &hostna
} else if (iter != end) {
zret = HSM_DONE;
}
+#pragma GCC diagnostic push
if (zret == HSM_DONE) {
to_return = &*iter;
if (!to_return->is_multiplexing()) {