diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 07932bba..90bbada7 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -7,7 +7,7 @@ inputs: description: vcpkg build preset, e.g, windows-arm64 config: - default: Release + default: RelWithDebInfo description: build type target: @@ -20,20 +20,25 @@ inputs: runs: using: "composite" steps: + - name: macOS tools + if: runner.os == 'macOS' + shell: bash + run: brew install autoconf autoconf-archive automake pkg-config + - name: simple build run: echo "preset = ${{ inputs.preset }}" shell: bash - name: install contemporary cmake - uses: lukka/get-cmake@latest + uses: lukka/get-cmake@v3.27.7 # pin version to avoid failed glibc dependency on ubuntu 20 runners. go back to @latest when ubuntu 22+ is adopted for runner os. - - uses: lukka/run-vcpkg@v10 + - uses: lukka/run-vcpkg@v11 with: - # use 2023.02.24 vcpkg baseline, + # use 2024.08.23 vcpkg baseline, # see https://learn.microsoft.com/en-us/vcpkg/users/examples/versioning.getting-started#builtin-baseline - vcpkgGitCommitId: 'a7b6122f6b6504d16d96117336a0562693579933' + vcpkgGitCommitId: '3508985146f1b1d248c67ead13f8f54be5b4f5da' - - uses: lukka/run-cmake@v10 + - uses: lukka/run-cmake@v10.6 # pin version to avoid failed glibc dependency on ubuntu 20 runners. go back to @latest when ubuntu 22+ is adopted for runner os. name: Configure CMake with: configurePreset: ci-${{ inputs.preset }} diff --git a/.github/actions/openziti-tunnel-build-action/README.md b/.github/actions/openziti-tunnel-build-action/README.md index e6c41ac3..7ef0c3c7 100644 --- a/.github/actions/openziti-tunnel-build-action/README.md +++ b/.github/actions/openziti-tunnel-build-action/README.md @@ -15,13 +15,6 @@ - name: ubuntu version: "20.04" type: deb - - name: ubuntu - version: "18.04" - type: deb - - name: redhat - version: "7" - type: rpm - container: docker.io/library/centos:7 - name: redhat version: "8" type: rpm diff --git a/.github/actions/openziti-tunnel-build-action/redhat-7/Dockerfile b/.github/actions/openziti-tunnel-build-action/redhat-7/Dockerfile deleted file mode 100644 index 5e9b788c..00000000 --- a/.github/actions/openziti-tunnel-build-action/redhat-7/Dockerfile +++ /dev/null @@ -1,46 +0,0 @@ -ARG CMAKE_VERSION="3.26.3" - -FROM docker.io/library/centos:7 - -ARG CMAKE_VERSION - -LABEL org.opencontainers.image.authors="support@netfoundry.io" - -USER root -WORKDIR /root/ - -ENV PATH="/usr/local/:${PATH}" -ENV GIT_DISCOVERY_ACROSS_FILESYSTEM=1 -ENV TZ=UTC - -RUN yum -y install \ - "@Development Tools" \ - centos-release-scl \ - doxygen \ - graphviz \ - python3 \ - zlib-devel \ - epel-release \ - && yum -y install \ - devtoolset-11 \ - devtoolset-11-libatomic-devel \ - ninja-build \ - && yum clean all - -# needed only to build openssl. we can't use openssl from rocky's repos because it is too old. -RUN yum install -y perl-IPC-Cmd - -RUN curl -sSfL https://cmake.org/files/v${CMAKE_VERSION%.*}/cmake-${CMAKE_VERSION}-linux-$(uname -m).sh -o cmake.sh \ - && (bash cmake.sh --skip-license --prefix=/usr/local) \ - && rm cmake.sh - -ENV VCPKG_ROOT=/usr/local/vcpkg -# this must be set on arm. see https://learn.microsoft.com/en-us/vcpkg/users/config-environment#vcpkg_force_system_binaries -ENV VCPKG_FORCE_SYSTEM_BINARIES=yes - -RUN cd /usr/local \ - && git clone --branch 2023.04.15 https://github.com/microsoft/vcpkg \ - && ./vcpkg/bootstrap-vcpkg.sh -disableMetrics - -COPY ./entrypoint.sh /root/ -ENTRYPOINT [ "/root/entrypoint.sh" ] diff --git a/.github/actions/openziti-tunnel-build-action/redhat-7/entrypoint.sh b/.github/actions/openziti-tunnel-build-action/redhat-7/entrypoint.sh deleted file mode 100755 index cad61ac7..00000000 --- a/.github/actions/openziti-tunnel-build-action/redhat-7/entrypoint.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env bash -# -# RedHat 7 -# - -set -euo pipefail - -# these commands must be in the entrypoint so they are run after workspace is mounted on Docker workdir -echo "INFO: GIT_DISCOVERY_ACROSS_FILESYSTEM=${GIT_DISCOVERY_ACROSS_FILESYSTEM}" -echo "INFO: WORKDIR=${PWD}" -echo "INFO: $(git --version)" - -# if first positional is an expected arch string then set cmake preset, -# else use ci-linux-x64 (which actually just uses native/host tools - e.g. not cross compile) -if [ ${#} -ge 1 ]; then - cmake_preset="${1}" -else - cmake_preset="ci-linux-x64" -fi - -if [ ${#} -ge 2 ]; then - cmake_config="${2}" -else - cmake_config="Release" -fi - -# workspace dir for each build env is added to "safe" dirs in global config e.g. -# ~/.gitconfig so both runner and builder containers trust these dirs -# owned by different UIDs from that of Git's EUID. This is made necessary -# by newly-enforced directory boundaries in Git v2.35.2 -# ref: https://lore.kernel.org/git/xmqqv8veb5i6.fsf@gitster.g/ -for SAFE in \ - /github/workspace \ - /__w/ziti-tunnel-sdk-c/ziti-tunnel-sdk-c \ - /mnt ; do - git config --global --add safe.directory ${SAFE} -done - -cmake -E make_directory ./build -( - [[ -d ./build ]] && rm -r ./build - cmake -E make_directory ./build - # allow unset for scl_source scripts - set +u - source scl_source enable devtoolset-11 \ - && cmake \ - --preset "${cmake_preset}" \ - -DCMAKE_BUILD_TYPE="${cmake_config}" \ - -DBUILD_DIST_PACKAGES=ON \ - -DDISABLE_LIBSYSTEMD_FEATURE=ON \ - -DVCPKG_OVERLAY_PORTS="./vcpkg-overlays/linux-syslibs/redhat7" \ - -S . \ - -B ./build - source scl_source enable devtoolset-11 \ - && cmake \ - --build ./build \ - --config "${cmake_config}" \ - --target package \ - --verbose -) diff --git a/.github/actions/openziti-tunnel-build-action/redhat-8/Dockerfile b/.github/actions/openziti-tunnel-build-action/redhat-8/Dockerfile index 3866ce0c..00458373 100644 --- a/.github/actions/openziti-tunnel-build-action/redhat-8/Dockerfile +++ b/.github/actions/openziti-tunnel-build-action/redhat-8/Dockerfile @@ -25,6 +25,7 @@ RUN dnf install -y \ systemd-rpm-macros \ cmake-rpm-macros \ openssl-devel \ + perl \ && dnf config-manager --set-enabled powertools \ && dnf install -y \ doxygen \ @@ -42,7 +43,7 @@ ENV VCPKG_ROOT=/usr/local/vcpkg ENV VCPKG_FORCE_SYSTEM_BINARIES=yes RUN cd /usr/local \ - && git clone --branch 2023.04.15 https://github.com/microsoft/vcpkg \ + && git clone --branch 2024.08.23 https://github.com/microsoft/vcpkg \ && ./vcpkg/bootstrap-vcpkg.sh -disableMetrics WORKDIR /github/workspace diff --git a/.github/actions/openziti-tunnel-build-action/redhat-8/entrypoint.sh b/.github/actions/openziti-tunnel-build-action/redhat-8/entrypoint.sh index e4bdfd04..ea83cf47 100755 --- a/.github/actions/openziti-tunnel-build-action/redhat-8/entrypoint.sh +++ b/.github/actions/openziti-tunnel-build-action/redhat-8/entrypoint.sh @@ -21,7 +21,7 @@ fi if [ ${#} -ge 2 ]; then cmake_config="${2}" else - cmake_config="Release" + cmake_config="RelWithDebInfo" fi # workspace dir for each build env is added to "safe" dirs in global config e.g. diff --git a/.github/actions/openziti-tunnel-build-action/redhat-9/Dockerfile b/.github/actions/openziti-tunnel-build-action/redhat-9/Dockerfile index bb42556f..4765262c 100644 --- a/.github/actions/openziti-tunnel-build-action/redhat-9/Dockerfile +++ b/.github/actions/openziti-tunnel-build-action/redhat-9/Dockerfile @@ -1,8 +1,10 @@ ARG CMAKE_VERSION="3.26.3" +ARG VCPKG_VERSION="2024.08.23" FROM rockylinux:9 ARG CMAKE_VERSION +ARG VCPKG_VERSION LABEL org.opencontainers.image.authors="support@netfoundry.io" @@ -23,6 +25,7 @@ RUN dnf install -y \ systemd-rpm-macros \ cmake-rpm-macros \ openssl-devel \ + perl-FindBin perl-IPC-Cmd perl-File-Compare perl-File-Copy \ libatomic \ && dnf config-manager --set-enabled crb \ && dnf install -y \ @@ -43,7 +46,7 @@ ENV VCPKG_ROOT=/usr/local/vcpkg ENV VCPKG_FORCE_SYSTEM_BINARIES=yes RUN cd /usr/local \ - && git clone --branch 2023.04.15 https://github.com/microsoft/vcpkg \ + && git clone --branch "${VCPKG_VERSION}" https://github.com/microsoft/vcpkg \ && ./vcpkg/bootstrap-vcpkg.sh -disableMetrics \ && chmod -R ugo+rwX /usr/local/vcpkg diff --git a/.github/actions/openziti-tunnel-build-action/redhat-9/entrypoint.sh b/.github/actions/openziti-tunnel-build-action/redhat-9/entrypoint.sh index e435407f..aa991116 100755 --- a/.github/actions/openziti-tunnel-build-action/redhat-9/entrypoint.sh +++ b/.github/actions/openziti-tunnel-build-action/redhat-9/entrypoint.sh @@ -21,7 +21,7 @@ fi if [ ${#} -ge 2 ]; then cmake_config="${2}" else - cmake_config="Release" + cmake_config="RelWithDebInfo" fi # workspace dir for each build env is added to "safe" dirs in global config e.g. @@ -45,6 +45,7 @@ done --preset "${cmake_preset}" \ -DCMAKE_BUILD_TYPE="${cmake_config}" \ -DBUILD_DIST_PACKAGES=ON \ + "${TLSUV_TLSLIB:+-DTLSUV_TLSLIB=${TLSUV_TLSLIB}}" \ -S . \ -B ./build cmake \ diff --git a/.github/actions/openziti-tunnel-build-action/ubuntu-16.04/Dockerfile b/.github/actions/openziti-tunnel-build-action/ubuntu-16.04/Dockerfile deleted file mode 100644 index 50f9e826..00000000 --- a/.github/actions/openziti-tunnel-build-action/ubuntu-16.04/Dockerfile +++ /dev/null @@ -1,49 +0,0 @@ -ARG CMAKE_VERSION="3.26.3" - -FROM ubuntu:xenial - -ARG CMAKE_VERSION - -LABEL org.opencontainers.image.authors="support@netfoundry.io" - -ENV DEBIAN_FRONTEND=noninteractive -ENV GIT_DISCOVERY_ACROSS_FILESYSTEM=1 -ENV TZ=UTC - -USER root -WORKDIR /root/ - -ENV PATH="/usr/local/:${PATH}" - -RUN apt-get -y update \ - && apt-get -y install \ - build-essential \ - curl \ - zip \ - unzip \ - tar \ - doxygen \ - git \ - graphviz \ - pkg-config \ - python3 \ - zlib1g-dev \ - ninja-build \ - && rm -rf /var/lib/apt/lists/* - -RUN curl -sSfL https://cmake.org/files/v${CMAKE_VERSION%.*}/cmake-${CMAKE_VERSION}-linux-$(uname -m).sh -o cmake.sh \ - && (bash cmake.sh --skip-license --prefix=/usr/local) \ - && rm cmake.sh - -ENV VCPKG_ROOT=/usr/local/vcpkg -# this must be set on arm. see https://learn.microsoft.com/en-us/vcpkg/users/config-environment#vcpkg_force_system_binaries -ENV VCPKG_FORCE_SYSTEM_BINARIES=yes - -RUN cd /usr/local \ - && git config --global advice.detachedHead false \ - && git clone --branch 2023.04.15 https://github.com/microsoft/vcpkg \ - && ./vcpkg/bootstrap-vcpkg.sh -disableMetrics - -WORKDIR /github/workspace -COPY ./entrypoint.sh /root/ -ENTRYPOINT [ "/root/entrypoint.sh" ] diff --git a/.github/actions/openziti-tunnel-build-action/ubuntu-16.04/entrypoint.sh b/.github/actions/openziti-tunnel-build-action/ubuntu-16.04/entrypoint.sh deleted file mode 100755 index ad3e167d..00000000 --- a/.github/actions/openziti-tunnel-build-action/ubuntu-16.04/entrypoint.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash -# -# Ubuntu Xenial 16.04 -# - -set -euo pipefail - -# these commands must be in the entrypoint so they are run after workspace is mounted on Docker workdir -echo "INFO: GIT_DISCOVERY_ACROSS_FILESYSTEM=${GIT_DISCOVERY_ACROSS_FILESYSTEM}" -echo "INFO: WORKDIR=${PWD}" -echo "INFO: $(git --version)" - -# if first positional is an expected arch string then set cmake preset, -# else use ci-linux-x64 (which actually just uses native/host tools - e.g. not cross compile) -if [ ${#} -ge 1 ]; then - cmake_preset="${1}" -else - cmake_preset="ci-linux-x64" -fi - -if [ ${#} -ge 2 ]; then - cmake_config="${2}" -else - cmake_config="Release" -fi - -# workspace dir for each build env is added to "safe" dirs in global config e.g. -# ~/.gitconfig so both runner and builder containers trust these dirs -# owned by different UIDs from that of Git's EUID. This is made necessary -# by newly-enforced directory boundaries in Git v2.35.2 -# ref: https://lore.kernel.org/git/xmqqv8veb5i6.fsf@gitster.g/ -for SAFE in \ - /github/workspace \ - /__w/ziti-tunnel-sdk-c/ziti-tunnel-sdk-c \ - /mnt ; do - git config --global --add safe.directory ${SAFE} -done - -[[ -d ./build ]] && rm -r ./build -cmake \ - -E make_directory \ - ./build -cmake \ - --preset "${cmake_preset}" \ - -DCMAKE_BUILD_TYPE="${cmake_config}" \ - -DBUILD_DIST_PACKAGES=ON \ - -DDISABLE_LIBSYSTEMD_FEATURE=ON \ - -DVCPKG_OVERLAY_PORTS="./vcpkg-overlays/linux-syslibs/ubuntu16" \ - -S . \ - -B ./build -cmake \ - --build ./build \ - --config "${cmake_config}" \ - --target package \ - --verbose diff --git a/.github/actions/openziti-tunnel-build-action/ubuntu-18.04/Dockerfile b/.github/actions/openziti-tunnel-build-action/ubuntu-18.04/Dockerfile deleted file mode 100644 index 7295d8f4..00000000 --- a/.github/actions/openziti-tunnel-build-action/ubuntu-18.04/Dockerfile +++ /dev/null @@ -1,62 +0,0 @@ -ARG CMAKE_VERSION="3.26.3" - -# Ubuntu Bionic 18.04 LTS -FROM ubuntu:bionic - -ARG CMAKE_VERSION - -LABEL org.opencontainers.image.authors="support@netfoundry.io" - -ENV DEBIAN_FRONTEND=noninteractive -ENV GIT_DISCOVERY_ACROSS_FILESYSTEM=1 -ENV TZ=UTC - -USER root -WORKDIR /root/ - -ENV PATH="/usr/local/:${PATH}" - -RUN apt-get update \ - && apt-get -y install \ - build-essential \ - crossbuild-essential-armhf \ - crossbuild-essential-arm64 \ - curl zip unzip tar \ - doxygen \ - git \ - graphviz \ - libsystemd-dev \ - pkg-config \ - python3 \ - zlib1g-dev \ - libssl-dev \ - ninja-build \ - && rm -rf /var/lib/apt/lists/* - -RUN curl -sSfL https://cmake.org/files/v${CMAKE_VERSION%.*}/cmake-${CMAKE_VERSION}-linux-$(uname -m).sh -o cmake.sh \ - && (bash cmake.sh --skip-license --prefix=/usr/local) \ - && rm cmake.sh - -RUN dpkg --add-architecture armhf -RUN dpkg --add-architecture arm64 -COPY ./crossbuild.list /etc/apt/sources.list.d/crossbuild.list -RUN sed -Ei 's/^deb/deb [arch=amd64]/g' /etc/apt/sources.list -RUN apt-get update -RUN apt-get -y install \ - zlib1g-dev:armhf \ - zlib1g-dev:arm64 \ - libssl-dev:armhf \ - libssl-dev:arm64 - -ENV VCPKG_ROOT=/usr/local/vcpkg -# this must be set on arm. see https://learn.microsoft.com/en-us/vcpkg/users/config-environment#vcpkg_force_system_binaries -ENV VCPKG_FORCE_SYSTEM_BINARIES=yes - -RUN cd /usr/local \ - && git config --global advice.detachedHead false \ - && git clone --branch 2023.04.15 https://github.com/microsoft/vcpkg \ - && ./vcpkg/bootstrap-vcpkg.sh -disableMetrics - -WORKDIR /github/workspace -COPY ./entrypoint.sh /root/ -ENTRYPOINT [ "/root/entrypoint.sh" ] \ No newline at end of file diff --git a/.github/actions/openziti-tunnel-build-action/ubuntu-18.04/crossbuild.list b/.github/actions/openziti-tunnel-build-action/ubuntu-18.04/crossbuild.list deleted file mode 100644 index f4fb8ab6..00000000 --- a/.github/actions/openziti-tunnel-build-action/ubuntu-18.04/crossbuild.list +++ /dev/null @@ -1,7 +0,0 @@ -deb [arch=armhf,arm64] http://ports.ubuntu.com/ bionic main restricted -deb [arch=armhf,arm64] http://ports.ubuntu.com/ bionic-updates main restricted -deb [arch=armhf,arm64] http://ports.ubuntu.com/ bionic universe -deb [arch=armhf,arm64] http://ports.ubuntu.com/ bionic-updates universe -deb [arch=armhf,arm64] http://ports.ubuntu.com/ bionic multiverse -deb [arch=armhf,arm64] http://ports.ubuntu.com/ bionic-updates multiverse -deb [arch=armhf,arm64] http://ports.ubuntu.com/ bionic-backports main restricted universe multiverse diff --git a/.github/actions/openziti-tunnel-build-action/ubuntu-18.04/entrypoint.sh b/.github/actions/openziti-tunnel-build-action/ubuntu-18.04/entrypoint.sh deleted file mode 100755 index 01acd19f..00000000 --- a/.github/actions/openziti-tunnel-build-action/ubuntu-18.04/entrypoint.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash -# -# Ubuntu Bionic 18.04 -# - -set -euo pipefail - -# these commands must be in the entrypoint so they are run after workspace is mounted on Docker workdir -echo "INFO: GIT_DISCOVERY_ACROSS_FILESYSTEM=${GIT_DISCOVERY_ACROSS_FILESYSTEM}" -echo "INFO: WORKDIR=${PWD}" -echo "INFO: $(git --version)" - -# if first positional is an expected arch string then set cmake preset, -# else use ci-linux-x64 (which actually just uses native/host tools - e.g. not cross compile) -if [ ${#} -ge 1 ]; then - cmake_preset="${1}" -else - cmake_preset="ci-linux-x64" -fi - -if [ ${#} -ge 2 ]; then - cmake_config="${2}" -else - cmake_config="Release" -fi - -# workspace dir for each build env is added to "safe" dirs in global config e.g. -# ~/.gitconfig so both runner and builder containers trust these dirs -# owned by different UIDs from that of Git's EUID. This is made necessary -# by newly-enforced directory boundaries in Git v2.35.2 -# ref: https://lore.kernel.org/git/xmqqv8veb5i6.fsf@gitster.g/ -for SAFE in \ - /github/workspace \ - /__w/ziti-tunnel-sdk-c/ziti-tunnel-sdk-c \ - /mnt ; do - git config --global --add safe.directory ${SAFE} -done - -[[ -d ./build ]] && rm -r ./build -cmake \ - -E make_directory \ - ./build -cmake \ - --preset "${cmake_preset}" \ - -DCMAKE_BUILD_TYPE="${cmake_config}" \ - -DBUILD_DIST_PACKAGES=ON \ - -DVCPKG_OVERLAY_PORTS="./vcpkg-overlays/linux-syslibs/ubuntu18" \ - -S . \ - -B ./build -cmake \ - --build ./build \ - --config "${cmake_config}" \ - --target package \ - --verbose - -# The original idea behind that was to crudely test the built artifact inside -# the container image with the correct architecture before returning to allow -# the build job to succeed. Basically a smoke test to see if it would execute as -# built at all. I don't recall why I/we abandoned that idea in favor of only -# running the x86 artifact in the job container. So, we're not getting any value -# from those lines of the entrypoint scripts right now, and I agree we'd have to -# embellish the option parsing a bit to get that working. -# if (( ${#} )); then -# echo "INFO: running ziti-edge-tunnel" -# set -x -# "./build/programs/ziti-edge-tunnel/${cmake_config}/ziti-edge-tunnel" ${@} -# fi diff --git a/.github/actions/openziti-tunnel-build-action/ubuntu-20.04/Dockerfile b/.github/actions/openziti-tunnel-build-action/ubuntu-20.04/Dockerfile index 28629145..610ac5f0 100644 --- a/.github/actions/openziti-tunnel-build-action/ubuntu-20.04/Dockerfile +++ b/.github/actions/openziti-tunnel-build-action/ubuntu-20.04/Dockerfile @@ -16,6 +16,7 @@ WORKDIR /root/ RUN apt-get update \ && apt-get -y install \ + autoconf automake autopoint \ gcc-arm-linux-gnueabihf \ g++-arm-linux-gnueabihf \ gcc-aarch64-linux-gnu \ @@ -27,6 +28,7 @@ RUN apt-get update \ git \ graphviz \ libsystemd-dev \ + libtool \ pkg-config \ python3 \ zlib1g-dev \ @@ -55,7 +57,7 @@ ENV VCPKG_FORCE_SYSTEM_BINARIES=yes RUN cd /usr/local \ && git config --global advice.detachedHead false \ - && git clone --branch 2023.04.15 https://github.com/microsoft/vcpkg \ + && git clone --branch 2024.08.23 https://github.com/microsoft/vcpkg \ && ./vcpkg/bootstrap-vcpkg.sh -disableMetrics COPY ./entrypoint.sh /root/ diff --git a/.github/actions/openziti-tunnel-build-action/ubuntu-20.04/entrypoint.sh b/.github/actions/openziti-tunnel-build-action/ubuntu-20.04/entrypoint.sh index 14dc0f9e..1cb65f8f 100755 --- a/.github/actions/openziti-tunnel-build-action/ubuntu-20.04/entrypoint.sh +++ b/.github/actions/openziti-tunnel-build-action/ubuntu-20.04/entrypoint.sh @@ -15,13 +15,13 @@ echo "INFO: $(git --version)" if [ ${#} -ge 1 ]; then cmake_preset="${1}" else - cmake_preset="ci-linux-x64" + cmake_preset="ci-linux-x64-static-libssl" fi if [ ${#} -ge 2 ]; then cmake_config="${2}" else - cmake_config="Release" + cmake_config="RelWithDebInfo" fi # workspace dir for each build env is added to "safe" dirs in global config e.g. diff --git a/.github/actions/openziti-tunnel-build-action/ubuntu-22.04/Dockerfile b/.github/actions/openziti-tunnel-build-action/ubuntu-22.04/Dockerfile index bd8bd863..c06545f9 100644 --- a/.github/actions/openziti-tunnel-build-action/ubuntu-22.04/Dockerfile +++ b/.github/actions/openziti-tunnel-build-action/ubuntu-22.04/Dockerfile @@ -1,9 +1,11 @@ ARG CMAKE_VERSION="3.26.3" +ARG VCPKG_VERSION="2024.08.23" # Ubuntu Jammy 22.04 LTS FROM ubuntu:jammy ARG CMAKE_VERSION +ARG VCPKG_VERSION LABEL org.opencontainers.image.authors="support@netfoundry.io" @@ -16,6 +18,7 @@ WORKDIR /root/ RUN apt-get update \ && apt-get -y install \ + autoconf automake autopoint \ gcc-arm-linux-gnueabihf \ g++-arm-linux-gnueabihf \ gcc-aarch64-linux-gnu \ @@ -27,6 +30,7 @@ RUN apt-get update \ git \ graphviz \ libsystemd-dev \ + libtool \ pkg-config \ python3 \ zlib1g-dev \ @@ -45,7 +49,7 @@ RUN apt-get update \ zlib1g-dev:armhf \ && rm -rf /var/lib/apt/lists/* -RUN curl -sSfL https://cmake.org/files/v${CMAKE_VERSION%.*}/cmake-${CMAKE_VERSION}-linux-$(uname -m).sh -o cmake.sh \ +RUN curl -sSfL "https://cmake.org/files/v${CMAKE_VERSION%.*}/cmake-${CMAKE_VERSION}-linux-$(uname -m).sh" -o cmake.sh \ && (bash cmake.sh --skip-license --prefix=/usr/local) \ && rm cmake.sh @@ -55,7 +59,7 @@ ENV VCPKG_FORCE_SYSTEM_BINARIES=yes RUN cd /usr/local \ && git config --global advice.detachedHead false \ - && git clone --branch 2023.04.15 https://github.com/microsoft/vcpkg \ + && git clone --branch "${VCPKG_VERSION}" https://github.com/microsoft/vcpkg \ && ./vcpkg/bootstrap-vcpkg.sh -disableMetrics COPY ./entrypoint.sh /root/ diff --git a/.github/actions/openziti-tunnel-build-action/ubuntu-22.04/entrypoint.sh b/.github/actions/openziti-tunnel-build-action/ubuntu-22.04/entrypoint.sh index d82561ae..0a1285c3 100755 --- a/.github/actions/openziti-tunnel-build-action/ubuntu-22.04/entrypoint.sh +++ b/.github/actions/openziti-tunnel-build-action/ubuntu-22.04/entrypoint.sh @@ -15,13 +15,13 @@ echo "INFO: $(git --version)" if [ ${#} -ge 1 ]; then cmake_preset="${1}" else - cmake_preset="ci-linux-x64" + cmake_preset="ci-linux-x64-static-libssl" fi if [ ${#} -ge 2 ]; then cmake_config="${2}" else - cmake_config="Release" + cmake_config="RelWithDebInfo" fi # workspace dir for each build env is added to "safe" dirs in global config e.g. @@ -44,6 +44,7 @@ cmake \ --preset "${cmake_preset}" \ -DCMAKE_BUILD_TYPE="${cmake_config}" \ -DBUILD_DIST_PACKAGES=ON \ + "${TLSUV_TLSLIB:+-DTLSUV_TLSLIB=${TLSUV_TLSLIB}}" \ -S "${PWD}/" \ -B ./build cmake \ diff --git a/.github/cpack-matrix.yml b/.github/cpack-matrix.yml new file mode 100644 index 00000000..eb8747bb --- /dev/null +++ b/.github/cpack-matrix.yml @@ -0,0 +1,39 @@ +cpack_matrix: + arch: + - cmake: ci-linux-x64-static-libssl # selects cmake preset + rpm: x86_64 # yum $basearch + deb: amd64 # dpkg --print-architecture + - cmake: ci-linux-arm-static-libssl + rpm: armhfp + deb: armhf + - cmake: ci-linux-arm64-static-libssl + rpm: aarch64 + deb: arm64 + distro: + - name: ubuntu + version: "22.04" + release_name: jammy + type: deb + - name: ubuntu + version: "20.04" + release_name: focal + type: deb + - name: redhat + version: "8" + release_name: ${{ null }} + type: rpm + container: docker.io/library/rockylinux:8 + - name: redhat + version: "9" + release_name: ${{ null }} + type: rpm + container: docker.io/library/rockylinux:9 + exclude: + - distro: + name: redhat + arch: + cmake: ci-linux-arm-static-libssl + - distro: + name: redhat + arch: + cmake: ci-linux-arm64-static-libssl diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..7b500f39 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 10 diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index c312c106..df176e09 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -2,7 +2,9 @@ name: CI build on: pull_request: - branches: [ main ] + branches: + - main + - release-* workflow_dispatch: workflow_call: @@ -22,40 +24,43 @@ jobs: fail-fast: false matrix: include: - - os: macOS-11 + - os: macOS-12 name: macOS x86_64 preset: macOS-x64 - - os: macOS-11 + - os: macOS-12 name: macOS arm64 preset: macOS-arm64 - os: windows-latest name: Windows x86_64 - preset: windows-x64 + preset: windows-x64-mingw - os: windows-latest name: Windows arm64 - preset: windows-arm64 + preset: windows-arm64-vs2022 - os: ubuntu-20.04 - container: openziti/ziti-builder:1.0.3 + container: openziti/ziti-builder:v2 name: Linux x86_64 preset: linux-x64-static-libssl - os: ubuntu-20.04 - container: openziti/ziti-builder:1.0.3 + container: openziti/ziti-builder:v2 name: Linux arm preset: linux-arm-static-libssl - os: ubuntu-20.04 - container: openziti/ziti-builder:1.0.3 + container: openziti/ziti-builder:v2 name: Linux arm64 preset: linux-arm64-static-libssl steps: + - name: Debug action + uses: hmarr/debug-action@v3 + - name: checkout workspace - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -74,9 +79,8 @@ jobs: working-directory: ./build/programs/ziti-edge-tunnel/ - name: upload bundle artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.preset }} - path: | - ./build/bundle/ziti-edge-tunnel-*.zip + path: ./build/bundle/ziti-edge-tunnel-*.zip if-no-files-found: error diff --git a/.github/workflows/cpack.yml b/.github/workflows/cpack.yml index e6488a27..0cd36df1 100644 --- a/.github/workflows/cpack.yml +++ b/.github/workflows/cpack.yml @@ -3,6 +3,13 @@ name: CI package on: workflow_dispatch: push: + branches: + - main + - release-* + pull_request: + branches: + - main + - release-* paths: - programs/ziti-edge-tunnel/package/* - .github/actions/openziti-tunnel-build-action/* @@ -11,119 +18,70 @@ on: types: - published +# cancel older, redundant runs of same workflow on same branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }} + cancel-in-progress: true + jobs: + set_matrix: + name: Set CPack Config Matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set_matrix.outputs.matrix }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set Matrix + id: set_matrix + shell: bash + run: | + matrix="$( + yq --output-format json .github/cpack-matrix.yml \ + | jq --compact-output '.cpack_matrix' + )" + echo "matrix=$matrix" | tee -a $GITHUB_OUTPUT package: + needs: set_matrix + name: ${{ matrix.arch.rpm }} ${{ matrix.distro.name }} ${{ matrix.distro.version }} runs-on: ubuntu-20.04 - # optionally override entire container image:tag string + # build image name it from matrix values name:version unless override container is specified container: ${{ matrix.distro.container || format('{0}:{1}', matrix.distro.name, matrix.distro.version) }} - # only override container image name and tag is distro version - #container: ${{ matrix.distro.container || matrix.distro.name }}:${{ matrix.distro.version }} strategy: fail-fast: false - matrix: - arch: - - cmake: ci-linux-x64 # selects cmake preset - rpm: x86_64 # yum $basearch - deb: amd64 # dpkg --print-architecture - - cmake: ci-linux-arm - rpm: armhfp - deb: armhf - - cmake: ci-linux-arm64 - rpm: aarch64 - deb: arm64 - distro: - - name: ubuntu - version: "22.04" - release_name: jammy - type: deb - - name: ubuntu - version: "20.04" - release_name: focal - type: deb - - name: ubuntu - version: "18.04" - release_name: bionic - type: deb - - name: ubuntu - version: "16.04" - release_name: xenial - type: deb - - name: redhat - version: "7" - release_name: ${{ null }} - type: rpm - container: docker.io/library/centos:7 - - name: redhat - version: "8" - release_name: ${{ null }} - type: rpm - container: docker.io/library/rockylinux:8 - - name: redhat - version: "9" - release_name: ${{ null }} - type: rpm - container: docker.io/library/rockylinux:9 - exclude: - - distro: - name: ubuntu - release_name: xenial - arch: - cmake: ci-linux-arm - - distro: - name: ubuntu - release_name: xenial - arch: - cmake: ci-linux-arm64 - - distro: - name: ubuntu - release_name: bionic - arch: - cmake: ci-linux-arm - - distro: - name: redhat - arch: - cmake: ci-linux-arm - - distro: - name: redhat - arch: - cmake: ci-linux-arm64 - + matrix: ${{ fromJSON(needs.set_matrix.outputs.matrix) }} + env: + ZITI_DEB_TEST_REPO: ${{ vars.ZITI_DEB_TEST_REPO || 'zitipax-openziti-deb-test' }} + ZITI_RPM_TEST_REPO: ${{ vars.ZITI_RPM_TEST_REPO || 'zitipax-openziti-rpm-test' }} steps: + - name: Debug action + uses: hmarr/debug-action@v3 + # only focal-20.04 has >= 2.18, which is required by actions/checkout to clone # which enables cmake version discovery - name: install contemporary Git in runner container if Ubuntu if: ${{ matrix.distro.name == 'ubuntu' }} + shell: bash run: | - apt -y update - apt-get -y install software-properties-common - add-apt-repository -y ppa:git-core/ppa - apt -y update - apt -y install git + apt-get update + apt-get install --yes software-properties-common + add-apt-repository --yes ppa:git-core/ppa + apt-get update + apt-get install --yes git git --version - name: install contemporary Git in runner container if RedHat 8 or 9 if: ${{ matrix.distro.name == 'redhat' && (matrix.distro.version == '8' || matrix.distro.version == '9') }} + shell: bash run: | dnf -y update dnf -y install git git --version - - name: install contemporary Git in runner container if RedHat 7 - if: ${{ matrix.distro.name == 'redhat' && matrix.distro.version == '7' }} - run: | - yum -y update - yum -y install centos-release-scl - yum -y install rh-git218 - source scl_source enable rh-git218 && git --version - cat << 'EOF' >| /root/git.sh - #!/bin/bash - source scl_source enable rh-git218 && git "${@}" - EOF - chmod +x /root/git.sh - update-alternatives --install /usr/bin/git git /root/git.sh 50 - - name: checkout workspace - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -139,67 +97,70 @@ jobs: uses: ./.github/actions/openziti-tunnel-build-action with: arch: ${{ matrix.arch.cmake }} - config: Release - - - name: list build artifacts - run: | - cat /etc/*-release - ls -horAS ./build/ + config: RelWithDebInfo - - name: list program artifacts + - name: list artifacts + shell: bash run: | + set -x cat /etc/*-release - ls -horAS ./build/programs/ziti-edge-tunnel/ + ls -horAS ./build/*.${{ matrix.distro.type }} + ls -horAS ./build/programs/ziti-edge-tunnel/RelWithDebInfo/ziti-edge-tunnel - name: install package artifact in runner container if Ubuntu x86_64 if: ${{ matrix.arch.cmake == 'ci-linux-x64' && matrix.distro.name == 'ubuntu' }} env: DEBIAN_FRONTEND: noninteractive + shell: bash run: | - apt -y install ./build/ziti-edge-tunnel-*.deb + apt-get -y install ./build/ziti-edge-tunnel-*.deb - name: install package artifact in runner container if RedHat if: ${{ matrix.arch.cmake == 'ci-linux-x64' && matrix.distro.name == 'redhat' }} + shell: bash run: | set -x yum -y install ./build/ziti-edge-tunnel-*.rpm - name: run binary artifact if: ${{ matrix.arch.cmake == 'ci-linux-x64' }} + shell: bash run: | set -x cat /etc/*-release ldd ./build/programs/ziti-edge-tunnel/Release/ziti-edge-tunnel - ./build/programs/ziti-edge-tunnel/Release/ziti-edge-tunnel version --verbose + ./build/programs/ziti-edge-tunnel/RelWithDebInfo/ziti-edge-tunnel version --verbose - - name: upload package artifact - uses: actions/upload-artifact@v3 + - name: Upload Package to Workflow Summary Page + uses: actions/upload-artifact@v4 with: name: ${{ matrix.distro.name }}-${{ matrix.distro.version }}-${{ matrix.arch.rpm }}-${{ matrix.distro.type }} path: ./build/ziti-edge-tunnel-*.${{ matrix.distro.type }} if-no-files-found: error - name: Configure jFrog CLI - if: ${{ github.event.release.published && startsWith(github.ref, 'refs/tags/v') }} - uses: jfrog/setup-jfrog-cli@v3 + if: ${{ github.event_name == 'release' && startsWith(github.ref, 'refs/tags/v') }} + uses: jfrog/setup-jfrog-cli@v4 env: JF_ENV_1: ${{ secrets.ZITI_ARTIFACTORY_CLI_CONFIG_PACKAGE_UPLOAD }} - name: Upload RPM to Artifactory with jFrog CLI - if: ${{ github.event.release.published && startsWith(github.ref, 'refs/tags/v') && matrix.distro.name == 'redhat' }} + if: ${{ github.event_name == 'release' && startsWith(github.ref, 'refs/tags/v') && matrix.distro.name == 'redhat' }} + shell: bash run: > jf rt upload ./build/ziti-edge-tunnel-*.${{ matrix.distro.type }} - /zitipax-openziti-rpm-stable/redhat${{ matrix.distro.version }}/${{ matrix.arch.rpm }}/ + ${{ env.ZITI_RPM_TEST_REPO }}/redhat${{ matrix.distro.version }}/${{ matrix.arch.rpm }}/ --recursive=false --flat=true - name: Upload DEB to Artifactory with jFrog CLI - if: ${{ github.event.release.published && startsWith(github.ref, 'refs/tags/v') && matrix.distro.name == 'ubuntu' }} + if: ${{ github.event_name == 'release' && startsWith(github.ref, 'refs/tags/v') && matrix.distro.name == 'ubuntu' }} + shell: bash run: > jf rt upload ./build/ziti-edge-tunnel-*.${{ matrix.distro.type }} - /zitipax-openziti-deb-stable/pool/ziti-edge-tunnel/${{ matrix.distro.release_name }}/${{ matrix.arch.deb }}/ + ${{ env.ZITI_DEB_TEST_REPO }}/pool/ziti-edge-tunnel/${{ matrix.distro.release_name }}/${{ matrix.arch.deb }}/ --deb=${{ matrix.distro.release_name }}/main/${{ matrix.arch.deb }} --recursive=false --flat=true diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 50787535..806fa856 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -4,12 +4,14 @@ on: push: branches: - main + - release-1.x jobs: update_release_draft: runs-on: ubuntu-latest steps: # Drafts your next Release notes as Pull Requests are merged into "master" - - uses: release-drafter/release-drafter@v5 + - name: Draft Release + uses: release-drafter/release-drafter@v6 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml new file mode 100644 index 00000000..ffb7c605 --- /dev/null +++ b/.github/workflows/linters.yml @@ -0,0 +1,17 @@ +name: Linters + +on: pull_request + +jobs: + codespell: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Run code spelling check + uses: codespell-project/actions-codespell@v2 + with: + ignore_words_list: ans,nd,precendence,seh,uknown diff --git a/.github/workflows/promote-downstreams.yml b/.github/workflows/promote-downstreams.yml new file mode 100644 index 00000000..43def6f2 --- /dev/null +++ b/.github/workflows/promote-downstreams.yml @@ -0,0 +1,139 @@ +name: Promote Downstream Releases + +on: + workflow_dispatch: + release: + types: [released] # this release event activity type excludes prereleases + +# cancel older, redundant runs of same workflow on same branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }} + cancel-in-progress: true + +jobs: + wait_for_release: + name: Wait for Release Builds to Succeed + runs-on: ubuntu-latest + steps: + - name: Debug action + uses: hmarr/debug-action@v3 + + - name: Wait for all checks on this ref + uses: lewagon/wait-on-check-action@v1.3.4 + with: + ref: ${{ github.ref }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + # seconds between polling the checks api for job statuses + wait-interval: 20 + # confusingly, this means "pause this step until all jobs from all workflows in same run have completed" + running-workflow-name: Wait for Release Builds to Succeed + + parse_version: + needs: wait_for_release + name: Parse Release Version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.parse.outputs.version }} + steps: + - name: Parse Release Version + id: parse + shell: bash + env: + RELEASE_REF: ${{ github.ref_name }} + run: | + if [[ "${RELEASE_REF}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "RELEASE_REF=${RELEASE_REF} is a semver release ref" + echo "version=${RELEASE_REF#v}" | tee -a $GITHUB_OUTPUT + else + echo "RELEASE_REF=${RELEASE_REF} is not a semver release ref" >&2 + exit 1 + fi + + set_matrix: + needs: wait_for_release + name: Set CPack Config Matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set_matrix.outputs.matrix }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set Matrix + id: set_matrix + shell: bash + run: | + matrix="$( + yq --output-format json .github/cpack-matrix.yml \ + | jq --compact-output '.cpack_matrix' + )" + echo "matrix=$matrix" | tee -a $GITHUB_OUTPUT + + promote_docker: + needs: parse_version + name: Promote Docker Hub to Latest + runs-on: ubuntu-latest + env: + ZITI_EDGE_TUNNEL_IMAGE: ${{ vars.ZITI_EDGE_TUNNEL_IMAGE || 'docker.io/openziti/ziti-edge-tunnel' }} + ZITI_HOST_IMAGE: ${{ vars.ZITI_HOST_IMAGE || 'docker.io/openziti/ziti-host' }} + steps: + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKER_HUB_API_USER || secrets.DOCKER_HUB_API_USER }} + password: ${{ secrets.DOCKER_HUB_API_TOKEN }} + + - name: Tag Latest ziti-edge-tunnel + shell: bash + run: > + docker buildx imagetools create --tag + ${{ env.ZITI_EDGE_TUNNEL_IMAGE }}:latest + ${{ env.ZITI_EDGE_TUNNEL_IMAGE }}:${{ needs.parse_version.outputs.version }} + + - name: Tag Latest ziti-host + shell: bash + run: > + docker buildx imagetools create --tag + ${{ env.ZITI_HOST_IMAGE }}:latest + ${{ env.ZITI_HOST_IMAGE }}:${{ needs.parse_version.outputs.version }} + + promote_artifactory: + needs: + - set_matrix + - parse_version + name: ${{ matrix.arch.rpm }} ${{ matrix.distro.name }} ${{ matrix.distro.version }} + runs-on: ubuntu-latest + env: + ZITI_DEB_TEST_REPO: ${{ vars.ZITI_DEB_TEST_REPO || 'zitipax-openziti-deb-test' }} + ZITI_RPM_TEST_REPO: ${{ vars.ZITI_RPM_TEST_REPO || 'zitipax-openziti-rpm-test' }} + ZITI_DEB_PROD_REPO: ${{ vars.ZITI_DEB_PROD_REPO || 'zitipax-openziti-deb-stable' }} + ZITI_RPM_PROD_REPO: ${{ vars.ZITI_RPM_PROD_REPO || 'zitipax-openziti-rpm-stable' }} + strategy: + fail-fast: true + matrix: ${{ fromJSON(needs.set_matrix.outputs.matrix) }} + steps: + - name: Configure jFrog CLI + uses: jfrog/setup-jfrog-cli@v4 + env: + JF_ENV_1: ${{ secrets.ZITI_ARTIFACTORY_CLI_CONFIG_PACKAGE_UPLOAD }} + + - name: Copy RPM from testing to release Artifactory repo with jFrog CLI + if: matrix.distro.type == 'rpm' + shell: bash + run: > + jf rt copy + --recursive=false + --flat=true + ${{ env.ZITI_RPM_TEST_REPO }}/redhat${{ matrix.distro.version }}/${{ matrix.arch.rpm }}/ziti-edge-tunnel-${{ needs.parse_version.outputs.version }}-*.${{ matrix.arch.rpm }}.rpm + ${{ env.ZITI_RPM_PROD_REPO }}/redhat${{ matrix.distro.version }}/${{ matrix.arch.rpm }}/ + + - name: Copy DEB from testing to release Artifactory repo with jFrog CLI + if: matrix.distro.type == 'deb' + shell: bash + run: > + jf rt copy + --recursive=false + --flat=true + ${{ env.ZITI_DEB_TEST_REPO }}/pool/ziti-edge-tunnel/${{ matrix.distro.release_name }}/${{ matrix.arch.deb }}/ziti-edge-tunnel-${{ needs.parse_version.outputs.version }}-*.deb + ${{ env.ZITI_DEB_PROD_REPO }}/pool/ziti-edge-tunnel/${{ matrix.distro.release_name }}/${{ matrix.arch.deb }}/ diff --git a/.github/workflows/publish-container-images.yml b/.github/workflows/publish-container-images.yml new file mode 100644 index 00000000..c060a1a7 --- /dev/null +++ b/.github/workflows/publish-container-images.yml @@ -0,0 +1,100 @@ +name: Container Images + +on: + workflow_call: + inputs: + ziti-version: + description: 'Ziti Tunneler Release Version' + type: string + required: true + workflow_dispatch: + inputs: + ziti-version: + description: 'Ziti Tunneler Release Version' + type: string + required: true + +# no need for concurrency group in callable workflows + +jobs: + publish-container-images: + runs-on: ubuntu-latest + env: + ZITI_VERSION: ${{ inputs.ziti-version || github.event.inputs.ziti-version }} + ZITI_EDGE_TUNNEL_IMAGE: ${{ vars.ZITI_EDGE_TUNNEL_IMAGE || 'docker.io/openziti/ziti-edge-tunnel' }} + ZITI_HOST_IMAGE: ${{ vars.ZITI_HOST_IMAGE || 'docker.io/openziti/ziti-host' }} + steps: + - name: Debug action + uses: hmarr/debug-action@v3 + + - name: Checkout Workspace + uses: actions/checkout@v4 + + - name: Download CMake Artifacts + uses: actions/download-artifact@v4 + with: + pattern: linux-* + path: ./downloads + merge_multiple: false # some artifacts have the same name and so can not be aggregated in a single directory + + - name: Unpack CMake Artifacts + shell: bash + run: | + set -x + ls -horRAS ./downloads + mkdir -p ./build/{arm64,amd64}/linux/ + unzip -d ./build/arm64/linux/ ./downloads/linux-arm64-static-libssl/ziti-edge-tunnel-Linux_aarch64.zip + unzip -d ./build/amd64/linux/ ./downloads/linux-x64-static-libssl/ziti-edge-tunnel-Linux_x86_64.zip + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: amd64,arm64 + + - name: Set up Docker BuildKit + id: buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKER_HUB_API_USER || secrets.DOCKER_HUB_API_USER }} + password: ${{ secrets.DOCKER_HUB_API_TOKEN }} + + - name: Set up Docker image tags for ziti-edge-tunnel image + env: + IMAGE_REPO: ${{ env.ZITI_EDGE_TUNNEL_IMAGE }} + id: tagprep_run + run: echo DOCKER_TAGS="${IMAGE_REPO}:unstable,${IMAGE_REPO}:${ZITI_VERSION}" | tee -a $GITHUB_OUTPUT + + - name: Build & Push Multi-Platform ziti-edge-tunnel Container Image + uses: docker/build-push-action@v6 + with: + builder: ${{ steps.buildx.outputs.name }} + context: ${{ github.workspace }}/ + file: ${{ github.workspace }}/docker/ziti-edge-tunnel.Dockerfile + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.tagprep_run.outputs.DOCKER_TAGS }} + build-args: | + ARTIFACTS_DIR=./build + DOCKER_BUILD_DIR=./docker + push: true + + - name: Set up Docker image tags for "run-host" container + env: + IMAGE_REPO: ${{ env.ZITI_HOST_IMAGE }} + id: tagprep_run_host + run: echo DOCKER_TAGS="${IMAGE_REPO}:unstable,${IMAGE_REPO}:${ZITI_VERSION}" | tee -a $GITHUB_OUTPUT + + - name: Build & Push Multi-Platform ziti-host Container Image + uses: docker/build-push-action@v6 + with: + builder: ${{ steps.buildx.outputs.name }} + context: ${{ github.workspace }}/ + file: ${{ github.workspace }}/docker/ziti-host.Dockerfile + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.tagprep_run_host.outputs.DOCKER_TAGS }} + build-args: | + ZITI_EDGE_TUNNEL_IMAGE=${{ env.ZITI_EDGE_TUNNEL_IMAGE }} + ZITI_EDGE_TUNNEL_TAG=${{ env.ZITI_VERSION }} + push: true diff --git a/.github/workflows/publish-containers.yml b/.github/workflows/publish-containers.yml deleted file mode 100644 index 5f994e79..00000000 --- a/.github/workflows/publish-containers.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: Container Images - -on: - workflow_call: - inputs: - ziti-version: - description: 'Ziti Tunneler Release Version' - type: string - required: true - workflow_dispatch: - inputs: - ziti-version: - description: 'Ziti Tunneler Release Version' - type: string - required: true - -jobs: - publish-containers: - runs-on: ubuntu-latest - env: - ZITI_VERSION: ${{ inputs.ziti-version || github.event.inputs.ziti-version }} - steps: - - name: Checkout Workspace - uses: actions/checkout@v3 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - with: - platforms: amd64,arm64 - - - name: Set up Docker BuildKit - id: buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_HUB_API_USER }} - password: ${{ secrets.DOCKER_HUB_API_TOKEN }} - - - name: Set up Docker image tags for "run" container - env: - RELEASE_REPO: openziti/ziti-edge-tunnel - id: tagprep_run - run: | - DOCKER_TAGS="" - DOCKER_TAGS="${RELEASE_REPO}:${ZITI_VERSION},${RELEASE_REPO}:latest" - echo "DEBUG: DOCKER_TAGS=${DOCKER_TAGS}" - echo DOCKER_TAGS="${DOCKER_TAGS}" >> $GITHUB_OUTPUT - - - name: Build & Push Multi-Platform Container Image to Hub - uses: docker/build-push-action@v3 - with: - builder: ${{ steps.buildx.outputs.name }} - context: ${{ github.workspace }}/docker - file: ${{ github.workspace }}/docker/Dockerfile.base - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.tagprep_run.outputs.DOCKER_TAGS }} - build-args: | - ZITI_VERSION=${{ env.ZITI_VERSION }} - GITHUB_REPO=${{ github.repository }} - push: true - - - name: Set up Docker image tags for "run-host" container - env: - RELEASE_REPO: openziti/ziti-host - id: tagprep_run_host - run: | - DOCKER_TAGS="" - DOCKER_TAGS="${RELEASE_REPO}:${ZITI_VERSION},${RELEASE_REPO}:latest" - echo "DEBUG: DOCKER_TAGS=${DOCKER_TAGS}" - echo DOCKER_TAGS="${DOCKER_TAGS}" >> $GITHUB_OUTPUT - - - name: Build & Push Multi-Platform Container Image to Hub - uses: docker/build-push-action@v3 - with: - builder: ${{ steps.buildx.outputs.name }} - context: ${{ github.workspace }}/docker - file: ${{ github.workspace }}/docker/Dockerfile.ziti-host - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.tagprep_run_host.outputs.DOCKER_TAGS }} - build-args: | - ZITI_VERSION=${{ env.ZITI_VERSION }} - GITHUB_REPO=${{ github.repository }} - push: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b5b9c65..456ee859 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,29 +14,30 @@ jobs: outputs: ZITI_VERSION: ${{ steps.get_version.outputs.ZITI_VERSION }} steps: + - name: Debug action + uses: hmarr/debug-action@v3 + - name: download - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: - path: ${{ runner.workspace }}/downloads/ + path: ${{ runner.workspace }}/downloads + merge_multiple: false # some artifacts have the same name and so can not be aggregated in a single directory - name: List Release Artifacts run: ls -horRAS ${{runner.workspace}}/downloads/ + # the purpose of this step is to identify the release that was created for the current tag and upload the + # artifacts that do not need to be renamed - name: Release id: get_release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: # name: defaults to tag name # tag_name: defaults to github.ref # token: defaults to github.token - draft: false - prerelease: false fail_on_unmatched_files: true files: | - ${{ runner.workspace }}/downloads/linux-x64-static-libssl/ziti-edge-tunnel-Linux_x86_64.zip - ${{ runner.workspace }}/downloads/linux-arm-static-libssl/ziti-edge-tunnel-Linux_arm.zip - ${{ runner.workspace }}/downloads/macOS-x64/ziti-edge-tunnel-Darwin_x86_64.zip - ${{ runner.workspace }}/downloads/macOS-arm64/ziti-edge-tunnel-Darwin_arm64.zip + ${{ runner.workspace }}/downloads/**/*.zip # These final two steps are only necessary because we prefer a different # release artifact name than is created by CMake, and so we could change @@ -58,29 +59,28 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.get_release.outputs.upload_url }} - asset_path: ${{ runner.workspace }}/downloads/windows-x64/ziti-edge-tunnel-Windows_AMD64.zip + asset_path: ${{ runner.workspace }}/downloads/windows-x64-mingw/ziti-edge-tunnel-Windows_AMD64.zip asset_name: ziti-edge-tunnel-Windows_x86_64.zip asset_content_type: application/octet-stream - name: Get the Version String from Git Tag id: get_version env: - GITHUB_REF: ${{ github.ref }} + RELEASE_REF: ${{ github.ref_name }} run: | - ZITI_VERSION="${GITHUB_REF#refs/*/v}" - if [[ "${ZITI_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "DEBUG: ZITI_VERSION=${ZITI_VERSION}" - echo ZITI_VERSION="${ZITI_VERSION}" >> $GITHUB_OUTPUT + ZITI_VERSION="${RELEASE_REF#v}" + if [[ "${ZITI_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-.+)?$ ]]; then + echo ZITI_VERSION="${ZITI_VERSION}" | tee -a $GITHUB_OUTPUT else # fail the job because we could not obtain a valid version string from the Git ref - echo "ERROR: ZITI_VERSION=${ZITI_VERSION} is not a semver" + echo "ERROR: ZITI_VERSION=${ZITI_VERSION} is not a release semver" exit 1 fi - call-publish-containers: + call-publish-container-images: name: Publish Container Images needs: [ release ] - uses: ./.github/workflows/publish-containers.yml + uses: ./.github/workflows/publish-container-images.yml secrets: inherit with: ziti-version: ${{ needs.release.outputs.ZITI_VERSION }} diff --git a/.github/workflows/test-deployments.yml b/.github/workflows/test-deployments.yml new file mode 100644 index 00000000..ce30aebf --- /dev/null +++ b/.github/workflows/test-deployments.yml @@ -0,0 +1,32 @@ +name: Test Deployments +on: + workflow_dispatch: + push: + branches: + - main + - release-v* + pull_request: + branches: + - main + - release-v* + +# cancel older, redundant runs of same workflow on same branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }} + cancel-in-progress: true + +jobs: + docker-deployments: + name: Test Docker Deployments + runs-on: ubuntu-latest + steps: + - name: Full Checkout to Allow CMake to Find Version with Git + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Run the Compose Test Script + shell: bash + run: docker/docker.test.bash + env: + I_AM_ROBOT: 1 diff --git a/.gitignore b/.gitignore index 987ea756..42cd14ca 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ compile_commands.json /docker/*.jwt /docker/*.json /docker/*.json.orig + +.run +CMakeFiles/ \ No newline at end of file diff --git a/BUILD.md b/BUILD.md index 4d31abcf..afa8fc5e 100644 --- a/BUILD.md +++ b/BUILD.md @@ -6,9 +6,9 @@ tool chain used. These steps should work properly for you but if your OS has var ## Prerequisites This repository expects the user to have at least a basic understanding of what a Ziti Network -is. To use this library it is also required to have a functioning Ziti Network availalbe to use. +is. To use this library it is also required to have a functioning Ziti Network available to use. To learn more about what Ziti is or how to learn how to setup a Ziti Network head over to [the official documentation -site](https://openziti.github.io/ziti/overview.html). +site](https://openziti.io/). ### Building Requirements @@ -22,14 +22,15 @@ To setup vcpkg you'll need to clone the actual vcpkg repository. The first step It should be set to somewhere durable, such as wherever you check your projects into. The example commands below use $HOME/%USERPROFILE% but you should probably change this to your liking. -Linux/MacOS: +#### Linux or macOS * set/export an environment variable named `VCPKG_ROOT`. for example (use an appropriate location): `export VCPKG_ROOT=${HOME}/vcpkg` * create the directory: `mkdir -p ${VCPKG_ROOT}` * clone the vcpkg project: `git clone git@github.com:microsoft/vcpkg.git ${VCPKG_ROOT}` * run the bootstrap-vcpkg for your platform: `${VCPKG_ROOT}/bootstrap-vcpkg.sh` -Windows: +#### Windows + * set/export an environment variable named `VCPKG_ROOT`. for example (use an appropriate location): `SET VCPKG_ROOT=%USERPROFILE%\vcpkg` * create the directory: `mkdir %VCPKG_ROOT%` * clone the vcpkg project: `git clone git@github.com:microsoft/vcpkg.git %VCPKG_ROOT%` @@ -37,7 +38,7 @@ Windows: ## Building -Make sure you have setup vcpkg (see above). Building the SDK is accomplished with the following commands from the +Make sure you have set up vcpkg (see above). Building the SDK is accomplished with the following commands from the checkout root. Replace the `--preset` value with the one that matches your needs or create your own preset. You can run `cmake` from the checkout root with an `unknown` param passed to `--preset` to see the list of presets: `cmake --preset unknown ${ZITI_TUNNELER_SDK_C_ROOT}/.` @@ -142,25 +143,66 @@ the number of jobs to use, which should ideally be specified to the number of threads your CPU has. You may also want to add that to your preset using the `jobs` property, see the [presets documentation][1] for more details. -[1]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html -[2]: https://cmake.org/download/ +## Cross-compile with Docker + +The default build architecture is x86_64. You can also cross-compile the distribution-specific Linux package or the +generic binary with Docker. Both approaches use an x86 (x86_64, amd64) container image to build the artifacts for arm64 +and arm architectures. -## Docker Crossbuilder Image +### Build the Linux Package with Docker -The CI job which also runs the included `ziti-builder.sh` builds this project inside a Docker container. The script will run the necessary container image if needed. The container image has the tools to cross-compile for target architectures arm, arm64. This script works for Linux, macOS, and WSL2 on Windows. Arm architecture hosts will experience slower build times due to emulation of this x86_64 container image. +The Debian and RedHat packages are built in GitHub and uploaded to DEB and RPM repositories. The Debian package may be +cross-compiled for arm64 or arm with [a few exceptions](.github/cpack-matrix.yml). Cross-compiling the RPM is not yet +supported. -Without any arguments, the `ziti-builder.sh` script will build the `bundle` target with the `ci-linux-x64` (amd64) preset, placing the resulting ZIP archive in `./build/bundle`. +1. build the x64 package builder image +1. run the x64 builder image to build the package for the target architecture + +The `ziti-edge-tunnel` binary is also built for the target architecture and included in the package with appropriate +parameters for the target distribution. + +#### Build the Package Builder Image + +Build the x64 package builder image for Ubuntu Jammy 22.04. There are builder images for several Ubuntu and RedHat +vintages that will work with a wide variety of Debian and RPM family distros. Use an older builder image if your target +distribution is older to ensure LIBC compatibility. ```bash -./ziti-builder.sh +cd ./.github/actions/openziti-tunnel-build-action/ubuntu-22.04/ +docker buildx build --platform linux/amd64 --tag jammy-builder . --load ``` -To build for a specific target architecture, use the `-p` argument to specify the vcpkg preset. +#### Run the Package Builder Container + +Cross-build the Debian package for arm64 in the x64 builder container. The `ci-linux-arm64` in this example is an +architecture-specific CMake [preset][1], and the optional TLS library variable overrides the default library, MBed-TLS. ```bash -./ziti-builder.sh -p ci-linux-arm64 +docker run \ + --rm \ + --platform linux/amd64 \ + --volume "${PWD}:/github/workspace" \ + --workdir "/github/workspace" \ + --env "TLSUV_TLSLIB=openssl" \ + jammy-builder \ + ci-linux-arm64 ``` +### Build the Binary with Docker + +All of the Ziti projects that leverage Ziti's C-SDK are built with a shared builder image: `openziti/ziti-builder`. This +project provides a wrapper script for cross-building the generic `ziti-edge-tunnel` binary using this builder image +optimized for compatibility, i.e., libc 2.27 and static Mbed-TLS library. + +Without any arguments, the `ziti-builder.sh` script will build the `bundle` target with the `ci-linux-x64` (amd64) +preset, placing the resulting ZIP archive in `./build/bundle/`, and the bare executable in +`./build/programs/ziti-edge-tunnel/Release/`. + +Build the generic binary for arm64 with the `ci-linux-arm64` preset. + ```bash -./cmake help +./scripts/ziti-builder.sh -p ci-linux-arm64 ``` + +[1]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html +[2]: https://cmake.org/download/ diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 227fa3d1..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,25 +0,0 @@ -# Release v0.17.23 - -## What's new - -* Tunneler SDK: Recursive DNS support -- DNS queries not matched by Ziti services are forwarded to upstream server (if configured) -* `ziti-edge-tunnel` - * add `-u|--dns-upstream` option for setting DNS upstream server - * `-n/--dns` option is removed - -# Release v0.17.21 - -## What's new -* add script and document building of `ziti-edge-tunnel` with OpenWRT (see [instructions](docs/openwrt/BUILDING.md)) -* config.json and config.json.backup files will be created in the identity path. This file will have the configuration details like identifier name, endpoint name, Active status etc. - -# Release v0.17.20 - -## What's new - -* Publish official `ziti-edge-tunnel` binaries for arm64(aarch64) architecture -* Increased `lwip` limits - -## Fixes - -* Fix: compilation issues when using MS Visual Studio compiler \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index f61e95b0..2d216998 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.21) set(ZITI_SDK_DIR "" CACHE FILEPATH "developer option: use local ziti-sdk-c checkout") -set(ZITI_SDK_VERSION "0.33.4" CACHE STRING "ziti-sdk-c version or branch to use") +set(ZITI_SDK_VERSION "2.0.0-alpha28" CACHE STRING "ziti-sdk-c version or branch to use") # if TUNNEL_SDK_ONLY then don't descend into programs/ziti-edge-tunnel option(TUNNEL_SDK_ONLY "build only ziti-tunnel-sdk (without ziti)" OFF) @@ -12,15 +12,19 @@ message("tunnel only = ${TUNNEL_SDK_ONLY}") option(EXCLUDE_PROGRAMS "exclude building the programs directory" OFF) message("exclude programs = ${EXCLUDE_PROGRAMS}") -option(ZITI_TUNNEL_BUILD_TESTS "Build tests." ON) +if (WIN32) + add_compile_definitions(PATH_SEP='\\\\') +else() + add_compile_definitions(PATH_SEP='/') +endif() find_package(Git) if(NOT GIT_VERSION AND GIT_FOUND) message("Found Git executable \"${GIT_EXECUTABLE}\".") # Generate a git-describe version string from Git repository tags execute_process( - COMMAND ${GIT_EXECUTABLE} describe --tags --dirty=-local --match "v*" - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMAND ${GIT_EXECUTABLE} describe --long --tags + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} OUTPUT_VARIABLE GIT_VERSION RESULT_VARIABLE GIT_ERROR_CODE OUTPUT_STRIP_TRAILING_WHITESPACE @@ -42,7 +46,14 @@ if(NOT GIT_VERSION) set(PROJECT_SEMVER "${DUMMY_SEMVER}") message(WARNING "GIT_VERSION not set. Using dummy PROJECT_SEMVER: \"${PROJECT_SEMVER}\", GIT_VERSION: \"${GIT_VERSION}\".") else() - string(REGEX MATCH "([0-9]+\\.[0-9]+\\.[0-9]+)" PROJECT_SEMVER "${GIT_VERSION}") + # tag-tweak-slug + string(REGEX REPLACE "(.*)-([0-9]+)-(.*)" "\\1" PROJECT_TAG "${GIT_VERSION}") + string(REGEX REPLACE "(.*)-([0-9]+)-(.*)" "\\2" PROJECT_TWEAK "${GIT_VERSION}") + string(REGEX REPLACE "(.*)-([0-9]+)-(.*)" "\\3" PROJECT_SLUG "${GIT_VERSION}") + + # extract semver from pre-release tags like 2.0.0-alpha + string(REGEX MATCH "([0-9]+.[0-9]+.[0-9]+)" PROJECT_SEMVER ${PROJECT_TAG}) + if(NOT PROJECT_SEMVER) set(PROJECT_SEMVER "${DUMMY_SEMVER}") message(WARNING "SEMVER could not be parsed from GIT_VERSION: ${GIT_VERSION}. Setting to PROJECT_SEMVER: ${PROJECT_SEMVER}") @@ -54,8 +65,8 @@ endif() option(DISABLE_SEMVER_VERIFICATION "Toggle SEMVER verification for BUILD_DIST_PACKAGES" OFF) option(BUILD_DIST_PACKAGES "Build packages for distribution package managers" OFF) -# Require explicitly disabling SEMVER verification for building DIST packages. -if(NOT DISABLE_SEMVER_VERIFICATION AND BUILD_DIST_PACKAGES AND PROJECT_SEMVER VERSION_EQUAL "${DUMMY_SEMVER}") +# verify the semver unless DISABLE option is set +if((NOT DISABLE_SEMVER_VERIFICATION) AND PROJECT_SEMVER VERSION_EQUAL "${DUMMY_SEMVER}") unset(GIT_VERSION CACHE) unset(GIT_ERROR_CODE CACHE) unset(PROJECT_SEMVER CACHE) @@ -64,10 +75,22 @@ if(NOT DISABLE_SEMVER_VERIFICATION AND BUILD_DIST_PACKAGES AND PROJECT_SEMVER VE message(FATAL_ERROR "SEMVER Verification failed. A valid SEMVER is required for correct package version composition. To override, set DISABLE_SEMVER_VERIFICATION=ON.") endif() +unset(GIT_VERSION CACHE) +if (PROJECT_TWEAK STREQUAL "0") + set(GIT_VERSION ${PROJECT_TAG}) +else () + set(GIT_VERSION "${PROJECT_TAG}.${PROJECT_TWEAK}") +endif () + project(ziti-tunnel-sdk-c - VERSION "${PROJECT_SEMVER}" + DESCRIPTION "OpenZiti tunneler SDK" + HOMEPAGE_URL "https://github.com/openziti/ziti-tunneler-sdk-c" LANGUAGES C CXX) +option(ZITI_TUNNEL_BUILD_TESTS "Build tests." "${${PROJECT_NAME}_IS_TOP_LEVEL}") + +set(PROJECT_VERSION ${GIT_VERSION}) + if(NOT BUILD_DIST_PACKAGES) include(CPack) set(CPACK_PACKAGE_VENDOR "NetFoundry") @@ -75,21 +98,7 @@ endif() set(CMAKE_C_STANDARD 99) -# capture build date -if (WIN32) - execute_process( - COMMAND powershell get-date -uf '+%a-%m/%d/%Y-%H:%M:%S-%Z' - OUTPUT_VARIABLE BUILD_DATE - OUTPUT_STRIP_TRAILING_WHITESPACE - ) -else() - execute_process( - COMMAND date +%a-%m/%d/%Y-%H:%M:%S-%Z - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - OUTPUT_VARIABLE BUILD_DATE - OUTPUT_STRIP_TRAILING_WHITESPACE - ) -endif() +string(TIMESTAMP BUILD_DATE "%a-%m/%d/%Y-%H:%M:%S-%Z") if (MSVC) add_compile_options(-Zi) @@ -103,12 +112,12 @@ add_custom_target(bundle file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/bundle) macro(BUNDLE_COMP comp) - message("adding target to bundle ${comp} for ${CMAKE_SYSTEM_NAME}:${CMAKE_SYSTEM_PROCESSOR}") add_custom_target(${comp}-bundle BYPRODUCTS ${CMAKE_BINARY_DIR}/bundle/${comp}-${CMAKE_SYSTEM_NAME}_${CMAKE_SYSTEM_PROCESSOR}.zip DEPENDS ${comp} - # copy target file when linker output directory isn't "." (e.g. visual studio) - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + # use generator expression to set working directory to the target's build directory. this works for multi-config generators, e.g. when "--config" is specified at build time. + WORKING_DIRECTORY "$" + COMMENT "adding components to bundle ${comp} for ${CMAKE_SYSTEM_NAME}:${CMAKE_SYSTEM_PROCESSOR}: $ ${${comp}_BUNDLE_COMPS}" COMMAND ${CMAKE_COMMAND} -E tar "cf" "${CMAKE_BINARY_DIR}/bundle/${comp}-${CMAKE_SYSTEM_NAME}_${CMAKE_SYSTEM_PROCESSOR}.zip" --format=zip -- $ ${${comp}_BUNDLE_COMPS}) add_dependencies(bundle ${comp}-bundle) endmacro() @@ -119,19 +128,6 @@ message("cross-compiling: ${CMAKE_CROSSCOMPILING}") link_directories(${CMAKE_BINARY_DIR}/lib) add_subdirectory(deps) - -find_package(libuv CONFIG QUIET) -if (libuv_FOUND) - if (TARGET uv_a) - set(tunnel_libuv_lib uv_a) - else() - set(tunnel_libuv_lib uv) - endif() -else() - find_path(tunnel_libuv_include_dir NAMES uv.h) - find_library(tunnel_libuv_lib uv_a NAMES uv) -endif() - add_subdirectory(lib/ziti-tunnel) if(NOT TUNNEL_SDK_ONLY) diff --git a/CMakePresets.json b/CMakePresets.json index cde3bfab..dc2b5417 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -27,6 +27,13 @@ "VCPKG_TARGET_TRIPLET": "x64-windows-static-md" } }, + { + "name": "vcpkg-win64-mingw-static", + "hidden": true, + "cacheVariables": { + "VCPKG_TARGET_TRIPLET": "x64-mingw-static" + } + }, { "name": "ninja", "hidden": true, @@ -64,7 +71,7 @@ } }, { - "name": "flags-windows", + "name": "flags-windows-vs2022", "description": "Note that all the flags after /W4 are required for MSVC to conform to the language standard", "hidden": true, "cacheVariables": { @@ -72,6 +79,16 @@ "CMAKE_CXX_FLAGS": "/utf-8 /W4 /permissive- /volatile:iso /Zc:preprocessor /Zc:__cplusplus /Zc:externConstexpr /Zc:throwingNew /EHsc" } }, + { + "name": "flags-windows-mingw", + "hidden": true, + "cacheVariables": { + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++", + "CMAKE_EXE_LINKER_FLAGS": "-Wl,-Bstatic -lpthread" + } + }, + { "name": "ci-unix", "hidden": true, @@ -82,9 +99,9 @@ ] }, { - "name": "ci-win64", + "name": "ci-win64-vs2022", "inherits": [ - "flags-windows", + "flags-windows-vs2022", "ci-std", "vs-2022" ], @@ -95,21 +112,44 @@ "hidden": true }, { - "name": "ci-win-arm64", + "name": "ci-win86-vs2022", "inherits": [ - "flags-windows", + "flags-windows-vs2022", + "ci-std", + "vs-2022" + ], + "architecture": "Win32", + "hidden": true + }, + { + "name": "ci-win-arm64-vs2022", + "inherits": [ + "flags-windows-vs2022", "ci-std", "vs-2022" ], "architecture": "ARM64", "hidden": true }, + { + "name": "ci-win64-mingw", + "inherits": [ + "flags-windows-mingw", + "ci-std", + "ninja" + ], + "architecture": { + "value": "x64", + "strategy": "external" + }, + "hidden": true + }, { "name": "ci-build", "binaryDir": "${sourceDir}/build", "hidden": true, "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release", + "CMAKE_BUILD_TYPE": "RelWithDebInfo", "TLSUV_TLSLIB": "openssl" } }, @@ -186,19 +226,44 @@ }, { "name": "ci-windows-x64", + "inherits": "ci-windows-x64-vs2022" + }, + { + "name": "ci-windows-x64-vs2022", "inherits": [ "ci-build", - "ci-win64", + "ci-win64-vs2022", "dev-mode", "vcpkg", "vcpkg-win64-static" ] }, + { + "name": "ci-windows-x86", + "inherits": "ci-windows-x86-vs2022" + }, + { + "name": "ci-windows-x86-vs2022", + "inherits": [ + "ci-build", + "ci-win86-vs2022", + "dev-mode", + "vcpkg" + ], + "cacheVariables": { + "VCPKG_TARGET_TRIPLET": "x86-windows-static-md", + "VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "${sourceDir}/toolchains/Windows-x86.cmake" + } + }, { "name": "ci-windows-arm64", + "inherits": [ "ci-windows-arm64-vs2022" ] + }, + { + "name": "ci-windows-arm64-vs2022", "inherits": [ "ci-build", - "ci-win-arm64", + "ci-win-arm64-vs2022", "dev-mode", "vcpkg" ], @@ -206,6 +271,16 @@ "VCPKG_TARGET_TRIPLET": "arm64-windows-static-md", "VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "${sourceDir}/toolchains/Windows-arm64.cmake" } + }, + { + "name": "ci-windows-x64-mingw", + "inherits": [ + "ci-build", + "ci-win64-mingw", + "dev-mode", + "vcpkg", + "vcpkg-win64-mingw-static" + ] } ] } diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 86c6a7c1..8c4b4228 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,4 @@ # Code of Conduct -All open source projects managed by OpenZiti share a common [code of conduct](https://docs.openziti.io/policies/CODE_OF_CONDUCT.html) +All open source projects managed by OpenZiti share a common [code of conduct](https://openziti.io/policies/CODE_OF_CONDUCT.html) which all contributors are expected to follow. Please be sure you read, understand and adhere to the guidelines expressed therein. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4862e51e..683885bf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing NetFoundry welcomes all and any contributions. All open source projects managed by NetFoundry share a common -[guide for contributions](https://docs.openziti.io/policies/CONTRIBUTING.html). +[guide for contributions](https://openziti.io/policies/CONTRIBUTING.html). If you are eager to contribute to a NetFoundry-managed open source project please read and act accordingly. diff --git a/README.md b/README.md index 6f7d185c..5d75dd2a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ that are useful to Ziti Tunnelers. ## What's a Ziti Tunneler? -[The main article about tunnellers is here](https://docs.openziti.io/docs/reference/tunnelers/linux/). Editors may follow the +[The main article about tunnelers is here](https://openziti.io/docs/reference/tunnelers/linux/). Editors may follow the "Edit this page" link on every page. ## What is the Ziti Tunneler SDK? @@ -76,7 +76,7 @@ int main(int argc, char *argv[]) { ``` Once the Ziti Tunneler SDK is initialized with a network device and ziti-sdk -callbacks, a tunneler application only needs to indiciate which service(s) +callbacks, a tunneler application only needs to indicate which service(s) that should be ## Run with Docker diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 00000000..3a50749c --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,44 @@ + +# Release Process for ziti-edge-tunnel + +## Releaser Steps + +1. [Create a release in GitHub](https://github.com/openziti/ziti-tunnel-sdk-c/releases) with the "Set as a pre-release" box ticked. This finalizes the version of downstream packages and container images and stages them for immediate use by prerelease consumers. +1. At your discretion, allow some time for prerelease consumers to validate the release. +1. Promote the release by [editing it in GitHub](https://github.com/openziti/ziti-tunnel-sdk-c/releases) to un-tick the "Set as a pre-release" box. This triggers the promotion of downstream packages and container images. + +The rest of this document describes these steps in greater detail. + +## Release Artifacts + +A release produces these artifacts. + +* binary executables attached to [the GitHub Release](https://github.com/openziti/ziti-tunnel-sdk-c/releases/latest) +* Linux packages in Artifactory + * DEBs for Debian distros ([doc](https://openziti.io/docs/reference/tunnelers/linux/debian-package)) + * RPMs for RedHat distros ([doc](https://openziti.io/docs/reference/tunnelers/linux/redhat-package)) +* Docker images in Docker Hub + * `openziti/ziti-edge-tunnel` for `run` proxy mode in a container ([K8S doc](https://openziti.io/docs/reference/tunnelers/kubernetes/kubernetes-daemonset)) + * `openziti/ziti-host` for `run-host` reverse-proxy mode in a container ([Docker doc](https://openziti.io/docs/reference/tunnelers/docker/), [K8S doc](https://openziti.io/docs/reference/tunnelers/kubernetes/kubernetes-host)) + +## Create a Release + +Creating a release in GitHub triggers these workflows. + +1. Build release artifacts (CMake): binary executables are uploaded to the GitHub Release. +1. CI package (CPack): Linux packages are uploaded to testing repos in Artifactory. + 1. [the testing repo for RPMs](https://netfoundry.jfrog.io/ui/repos/tree/General/zitipax-openziti-rpm-test?projectKey=zitipax) + 1. [the testing repo for DEBs](https://netfoundry.jfrog.io/ui/repos/tree/General/zitipax-openziti-deb-test?projectKey=zitipax) +1. Docker images are uploaded to Docker Hub. + 1. [ziti-edge-tunnel](https://hub.docker.com/r/openziti/ziti-edge-tunnel/tags) + 1. [ziti-host](https://hub.docker.com/r/openziti/ziti-host/tags) + +## Promote Downstream Releases + +Newly created GitHub Releases default to a full "latest" release, implying `prerelease: false`. GitHub fires the one-time release event `released`, triggering the "Promote Downstream Releases" workflow when a release is created with `prerelease: false` (implied by and mutually exclusive to the default `make_latest: true`) or updated with `prerelease: false`. + +1. Linux packages in Artifactory are copied from the "test" repositories to the "stable" repositories in Artifactory. + 1. [the release repo for RPMs](https://netfoundry.jfrog.io/ui/repos/tree/General/zitipax-openziti-rpm-stable?projectKey=zitipax) + 1. [the release repo for DEBs](https://netfoundry.jfrog.io/ui/repos/tree/General/zitipax-openziti-deb-stable?projectKey=zitipax) +1. Previously-uploaded Docker images in Docker Hub are tagged `:latest`. +1. There are no effects for the executable binaries that were previously uploaded to the GitHub Release. diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index c0b5eeed..a2b1a524 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -18,7 +18,7 @@ endif() FetchContent_Declare(lwip GIT_REPOSITORY https://github.com/lwip-tcpip/lwip.git - GIT_TAG STABLE-2_1_3_RELEASE + GIT_TAG STABLE-2_2_0_RELEASE ) FetchContent_GetProperties(lwip) if(NOT lwip_POPULATED) diff --git a/docker/BUILD.md b/docker/BUILD.md index e18ef489..38778efe 100644 --- a/docker/BUILD.md +++ b/docker/BUILD.md @@ -1,4 +1,7 @@ -The Dockerfile and scripts in this directory build a `ziti-edge-tunnel` (tunneler from C-SDK) Docker image. This procedure is highly similar to that of the `ziti-tunnel` (Go tunneler) Docker image [documented here](https://github.com/openziti/ziti/blob/main/ziti-tunnel/docker/BUILD.md). + +# Building the ziti-edge-tunnel Docker Images + +The Dockerfile and scripts in this directory build a `ziti-edge-tunnel` (tunneler from C-SDK) Docker image. Ziti binaries are downloaded from https://github.com/openziti/ziti-tunnel-sdk-c/ by default. The following build arguments are supported: @@ -24,46 +27,56 @@ the image to a public registry. 1. Enable Docker Experimental Features - See https://docs.docker.com/engine/reference/commandline/cli/#experimental-features + See https://docs.docker.com/engine/reference/commandline/cli/#experimental-features 2. Install & Enable qemu Emulation for Arm (Docker CE / Linux only) - This is taken care of by Docker Desktop if you're building on macOS or Windows, - but you'll need to install qemu emulation support and register Arm binaries to - run on your (presumably) x86_64 build host if you are running Docker CE on Linux: + This is taken care of by Docker Desktop if you're building on macOS or Windows, + but you'll need to install qemu emulation support and register Arm binaries to + run on your (presumably) x86_64 build host if you are running Docker CE on Linux: - $ sudo dnf install -y qemu-system-arm - $ docker run --rm --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d + ```bash + sudo dnf install -y qemu-system-arm + docker run --rm --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d + ``` 3. Verify that the Arm qemu handler is registered. The first line of the file is "enabled". - $ cat /proc/sys/fs/binfmt_misc/qemu-arm - enabled - interpreter /usr/bin/qemu-arm - -Optionally, run an ARM arch container and print system information to test qemu-arm + ```bash + $ cat /proc/sys/fs/binfmt_misc/qemu-arm + enabled + interpreter /usr/bin/qemu-arm + ``` - $ docker run --rm arm64v8/alpine uname -a - Linux 00eea7912eb1 5.11.0-7612-generic #13~1617215757~20.10~97a8d1a-Ubuntu SMP Thu Apr 1 21:09:17 UTC 2 aarch64 Linux + Optionally, run an ARM arch container and print system information to test qemu-arm - $ docker run --rm arm32v7/alpine uname -a - Linux 6fcaad6c8b37 5.11.0-7612-generic #13~1617215757~20.10~97a8d1a-Ubuntu SMP Thu Apr 1 21:09:17 UTC 2 armv7l Linux + ```bash + $ docker run --rm arm64v8/alpine uname -a + Linux 00eea7912eb1 5.11.0-7612-generic #13~1617215757~20.10~97a8d1a-Ubuntu SMP Thu Apr 1 21:09:17 UTC 2 aarch64 Linux + + $ docker run --rm arm32v7/alpine uname -a + Linux 6fcaad6c8b37 5.11.0-7612-generic #13~1617215757~20.10~97a8d1a-Ubuntu SMP Thu Apr 1 21:09:17 UTC 2 armv7l Linux + ``` 4. Create a Builder Instance - $ docker buildx create --use --name=ziti-builder + ```bash + docker buildx create --use --name=ziti-builder + ``` ## Building Run `docker buildx` like this: - $ git fetch --tags && git tag -l | sort -Vr | head -1 - v0.16.1 - $ ZITI_VERSION="0.16.1" - $ docker buildx build \ - --platform linux/amd64,linux/arm/v7,linux/aarch64 \ - --build-arg ZITI_VERSION="${ZITI_VERSION}" \ - -t "netfoundry/ziti-edge-tunnel:${ZITI_VERSION}" . +```bash +$ git fetch --tags && git tag -l | sort -Vr | head -1 +v0.16.1 +$ ZITI_VERSION="0.16.1" +$ docker buildx build \ + --platform linux/amd64,linux/arm/v7,linux/aarch64 \ + --build-arg ZITI_VERSION="${ZITI_VERSION}" \ + -t "netfoundry/ziti-edge-tunnel:${ZITI_VERSION}" . +``` Notes: @@ -93,24 +106,44 @@ This build method produces an image for the CPU that is running the build host (typically amd64), and places the resulting image into your local Docker image cache. - $ ZITI_VERSION="0.16.1" \ - $ docker build \ - --build-arg ZITI_VERSION="${ZITI_VERSION}" \ - -t "netfoundry/ziti-edge-tunnel:${ZITI_VERSION}" . +```bash +ZITI_VERSION=$( + curl -sSf https://api.github.com/repos/openziti/ziti-tunnel-sdk-c/releases/latest \ + | jq -r '.tag_name' \ + | sed -E 's/^v//' +); + +docker buildx build \ + --tag ziti-edge-tunnel:${ZITI_VERSION} \ + --build-arg ZITI_VERSION=${ZITI_VERSION} \ + --file ./docker/Dockerfile.base \ + --load \ + ./docker + +docker buildx build \ + --tag ziti-host:${ZITI_VERSION} \ + --build-arg ZITI_EDGE_TUNNEL_IMAGE=ziti-edge-tunnel \ + --build-arg ZITI_EDGE_TUNNEL_TAG=${ZITI_VERSION} \ + --file ./docker/Dockerfile.ziti-host \ + --load \ + ./docker +``` ## Shell Script for Linux - $ ./buildx.sh -h - Usage: VARIABLES ./buildx.sh [OPTION]... +```bash +$ ./buildx.sh -h +Usage: VARIABLES ./buildx.sh [OPTION]... - Build multi-platform Docker container image on Linux. +Build multi-platform Docker container image on Linux. - VARIABLES - ZITI_VERSION e.g. "0.16.1" corresponding to Git tag "v0.16.1" +VARIABLES + ZITI_VERSION e.g. "0.16.1" corresponding to Git tag "v0.16.1" - OPTIONS - -r REPO container image repository e.g. netfoundry/ziti-edge-tunnel - -c don't check out v${ZITI_VERSION} (use Git working copy) +OPTIONS + -r REPO container image repository e.g. netfoundry/ziti-edge-tunnel + -c don't check out v${ZITI_VERSION} (use Git working copy) - EXAMPLES - ZITI_VERSION=0.16.1 ./buildx.sh -r netfoundry/ziti-edge-tunnel +EXAMPLES + ZITI_VERSION=0.16.1 ./buildx.sh -r netfoundry/ziti-edge-tunnel +``` diff --git a/docker/Dockerfile.base b/docker/Dockerfile.base deleted file mode 100644 index dae58ea6..00000000 --- a/docker/Dockerfile.base +++ /dev/null @@ -1,54 +0,0 @@ -# this builds docker.io/openziti/ziti-edge-tunnel -FROM registry.access.redhat.com/ubi8/ubi-minimal as fetch-ziti-artifacts -# This build stage grabs artifacts that are copied into the final image. -# It uses the same base as the final image to maximize docker cache hits. - -ARG ZITI_VERSION - -ARG GITHUB_BASE_URL -ARG GITHUB_REPO - -WORKDIR /tmp - -### Add necessary Red Hat repos and packages -RUN INSTALL_PKGS="curl unzip" && \ - microdnf -y update --setopt=install_weak_deps=0 --setopt=tsflags=nodocs && \ - microdnf -y install --setopt=install_weak_deps=0 --setopt=tsflags=nodocs ${INSTALL_PKGS} - -COPY fetch-github-releases.sh . -RUN bash ./fetch-github-releases.sh ziti-edge-tunnel - -################ -# -# Main Image -# -################ - -FROM registry.access.redhat.com/ubi8/ubi-minimal - -### Required OpenShift Labels -LABEL name="openziti/ziti-edge-tunnel" \ - maintainer="developers@openziti.org" \ - vendor="NetFoundry" \ - summary="OpenZiti Tunneler" \ - description="Configure a proxy and nameserver for OpenZiti Services" - -USER root - -### add licenses to this directory -RUN mkdir -m0755 /licenses -COPY ./LICENSE-Apache /licenses/apache.txt - -### Add necessary Red Hat repos and packages -RUN INSTALL_PKGS="iproute procps" && \ - microdnf -y update --setopt=install_weak_deps=0 --setopt=tsflags=nodocs && \ - microdnf -y install --setopt=install_weak_deps=0 --setopt=tsflags=nodocs ${INSTALL_PKGS} - -RUN mkdir -p /usr/local/bin -COPY --from=fetch-ziti-artifacts /tmp/ziti-edge-tunnel /usr/local/bin -COPY ./docker-entrypoint.sh / -RUN chmod +x /docker-entrypoint.sh -RUN mkdir -m0777 /ziti-edge-tunnel - -ENTRYPOINT [ "/docker-entrypoint.sh" ] -CMD [ "run" ] diff --git a/docker/Dockerfile.linux-cross-build b/docker/Dockerfile.linux-cross-build deleted file mode 100644 index 6617a3fa..00000000 --- a/docker/Dockerfile.linux-cross-build +++ /dev/null @@ -1,45 +0,0 @@ -ARG CMAKE_VERSION="3.22.3" -FROM debian:buster-slim -# -# usage -# docker run with top-level of tunneler SDK repo mounted as writeable volume on /mnt - -ARG CMAKE_VERSION -ARG uid=1000 -ARG gid=1000 -ENV TZ=Etc/UTC -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update \ - && apt-get -y install \ - gcc-arm-linux-gnueabihf \ - g++-arm-linux-gnueabihf \ - gcc-aarch64-linux-gnu \ - crossbuild-essential-arm64 \ - crossbuild-essential-armhf \ - build-essential \ - curl \ - doxygen \ - git \ - graphviz \ - libsystemd-dev \ - iproute2 \ - pkg-config \ - python3 \ - zlib1g-dev \ - libssl-dev \ - && rm -rf /var/lib/apt/lists/* - -RUN dpkg --add-architecture arm64 && dpkg --add-architecture armhf -RUN apt-get update \ - && apt-get -y install \ - libssl-dev:arm64 \ - libssl-dev:armhf \ - && rm -rf /var/lib/apt/lists/* - -RUN curl -sSfL https://cmake.org/files/v${CMAKE_VERSION%.*}/cmake-${CMAKE_VERSION}-linux-$(uname -m).sh -o cmake.sh \ - && (bash cmake.sh --skip-license --prefix=/usr/local) \ - && rm cmake.sh - -USER ${uid}:${gid} -WORKDIR /mnt/ -ENTRYPOINT ["/mnt/docker/linux-cross-build.sh"] diff --git a/docker/Dockerfile.linux-native-build b/docker/Dockerfile.linux-native-build deleted file mode 100644 index d27db2be..00000000 --- a/docker/Dockerfile.linux-native-build +++ /dev/null @@ -1,39 +0,0 @@ -ARG CMAKE_VERSION="3.22.3" -FROM debian:jessie-slim -# -# (cd ./docker; DOCKER_BUILDKIT=1 docker build --platform arm --file Dockerfile.linux-native-build -t openziti/ziti-edge-tunnel-builder:debian-jessie-arm ./;) -# docker run --rm -it --volume "${PWD}:/mnt" --platform arm openziti/ziti-edge-tunnel-builder:debian-jessie-arm - - -ARG CMAKE_VERSION -ARG uid=1000 -ARG gid=0 -ENV TZ=Etc/UTC -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update \ - && apt-get -y install \ - build-essential \ - curl \ - doxygen \ - git \ - graphviz \ - libsystemd-dev \ - iproute2 \ - pkg-config \ - python3 \ - zlib1g-dev \ - libssl-dev \ - && rm -rf /var/lib/apt/lists/* - -# binary releases are available for x86_64, arm64 -RUN curl -sSfL https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}.tar.gz -o cmake.tgz \ - && tar xf cmake.tgz \ - && (cd ./cmake-${CMAKE_VERSION} && ./bootstrap && make && make install) \ - && rm -r ./cmake-${CMAKE_VERSION} - -# the purpose of the uid:gid is to avoid root-owned build output folder -RUN getent group ${gid} &>/dev/null || groupadd --gid ${gid} ziggy -RUN getent passwd ${uid} &>/dev/null || useradd --system --home-dir /mnt --gid ${gid} --uid ${uid} ziggy -USER ${uid}:${gid} -WORKDIR /mnt -ENTRYPOINT ["/mnt/docker/linux-native-build.sh"] diff --git a/docker/README.md b/docker/README.md index 1953d7ba..db759226 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,253 +1,59 @@ # Run The OpenZiti Tunneler with Docker -## Contents +## Host Services with Docker -- [Conventions](#conventions) -- Use cases: - - [Hosting OpenZiti services](#use-case-hosting-openziti-services) - - [Connecting to OpenZiti services with an intercepting proxy](#use-case-intercepting-proxy-and-nameserver) +The most popular way of using the Linux tunneler in Docker is to "host" an OpenZiti service, meaning as a reverse proxy and exit point from the OpenZiti network toward some target server. You can deploy the container before or after you grant it permission to start hosting the service and it will autonomously obey the OpenZiti controller. -## Conventions +The `openziti/ziti-host` image simply runs `ziti-edge-tunnel run-host` with the following helpful conventions for supplying an enrollment token and persisting the identity. -### Configuring the OpenZiti Identity +### Enroll and Persist Identity in a Volume -It is necessary to supply an identity enrollment token or an enrolled identity configuration JSON to the container as a volume-mounted file or as environment variables. The following variable, volumes, and files are common to both container images described below. - -#### Configuration with Environment Variable - -- `ZITI_IDENTITY_JSON`: This is the identity represented as JSON. This variable overrides other methods of supplying the identity JSON. It is not advisable to mount a volume on the container filesystem when using this method because the identity is written to a temporary file and will cause an error if the file already exists. - -#### Configuration with Files from Mounted Volume - -You may bind a host directory to the container filesystem in `/ziti-edge-tunnel` to supply the token JWT file or configuration JSON file. If you provide a token JWT file, the entrypoint script will enroll the identity during container startup. The entrypoint script will write the identity configuration JSON file in the same directory with a filename like `${ZITI_IDENTITY_BASENAME}.json`. - -- `ZITI_IDENTITY_BASENAME`: the file basename (without the filename suffix) of the enrollment (.jwt) and identity (.json) files the tunneler will use -- `ZITI_ENROLL_TOKEN`: Optionally, you may supply the enrollment token JWT as a string if `${ZITI_IDENTITY_BASENAME}.jwt` is not mounted -- `ZITI_IDENTITY_WAIT`: Optionally, you may configure the container to wait max seconds for the JWT or JSON file to appear in the mounted volume - -## Use Case: Hosting OpenZiti Services - -This use case involves deploying the OpenZiti tunneler as a reverse proxy to publish regular network servers to your OpenZiti Network. You may locate the published servers in a Docker bridge network (use network mode `bridge`) or the Docker host's network (use network mode `host`). See [the Linux tunneler doc](https://docs.openziti.io/docs/reference/tunnelers/linux/) for general info about the OpenZiti tunneler. Use the `openziti/ziti-host` container image for this case. - -### Container Image `openziti/ziti-host` - -This image runs `ziti-edge-tunnel run-host` to invoke the hosting-only mode of the tunneler. The main difference from the parent image (`openziti/ziti-edge-tunnel`) is the command argument and run-as user. This container runs as "nobody" and doesn't require special privileges. - -#### Image Tags for `openziti/ziti-host` - -The `openziti/ziti-host` image is published in Docker Hub and automatically updated with new releases. You may subscribe to `:latest` (default) or pin a version for stability e.g. `:0.19.11`. - -#### Dockerfile for `openziti/ziti-host` - -The Dockerfile for `openziti/ziti-host` is [./Dockerfile.ziti-host](Dockerfile.ziti-host). - -#### Hosting an OpenZiti Service with `openziti/ziti-host` - -Publish servers that are reachable on the Docker host's network, e.g., `tcp:localhost:54321`: - -```bash -# identity file on Docker host is mounted in container: /opt/openziti/etc/identities/my-ziti-identity.json -docker run \ - --name ziti-host \ - --rm \ - --network=host \ - --env ZITI_IDENTITY_BASENAME="my-ziti-identity" \ - --volume /opt/openziti/etc/identities:/ziti-edge-tunnel \ - openziti/ziti-host -``` - -Publish servers inside the same Docker bridge network, e.g., `tcp:my-docker-service:80`: - -```bash -# identity file on Docker host is stuffed in env var: /opt/openziti/etc/identities/my-ziti-identity.json -docker run \ - --name ziti-host \ - --rm \ - --network=my-docker-bridge \ - --env ZITI_IDENTITY_JSON="$(< /opt/openziti/etc/identities/my-ziti-identity.json)" \ - openziti/ziti-host -``` - -This example uses [the included Docker Compose project](docker-compose.yml) to illustrate publishing a server container to your OpenZiti Network. - -1. Create an OpenZiti Config with type `intercept.v1`. - - ```json - { - "addresses": [ - "hello-docker.ziti" - ], - "protocols": [ - "tcp" - ], - "portRanges": [ - { - "low": 80, - "high": 80 - } - ] - } - ``` - -1. Create an OpenZiti Config with type `host.v1` - - ```json - { - "port": 80, - "address": "hello", - "protocol": "tcp" - } - ``` - -1. Create a service associating the two configs with a role attribute like "#HelloServices." -1. Create an identity for your client tunneler named "MyClient" and load the identity. -1. Create an identity named "DockerHost" and download the enrollment token in the same directory as `docker-compose.yml`, i.e., "DockerHost.jwt." -1. Create a Bind service policy assigning "#HelloServices" to be bound by "@DockerHost." -1. Create a Dial service policy granting access to "#HelloServices" to your client tunneler's identity "@MyClient." -1. Run the demo server - - ```bash - docker-compose up --detach hello - ``` - -1. Run the tunneler - - ```bash - ZITI_IDENTITY_JSON="$(< /tmp/my-ziti-id.json)" docker-compose up --detach ziti-host - # debug - ZITI_IDENTITY_JSON="$(< /tmp/my-ziti-id.json)" docker-compose run ziti-host run-host --verbose=4 - ``` - -1. Access the demo server via your OpenZiti Network: [http://hello-docker.ziti](http://hello-docker.ziti) - -#### Docker Compose Examples for `openziti/ziti-host` - -Get a single, enrolled identity configuration from an environment variable. You could define the variable with an `.env` file in the same directory as `docker-compose.yml`. +Set the enrollment token and run the container. This example saves the identity file in the persistent volume: `/ziti-edge-tunnel/ziti_id.json`. ```yaml -version: "3.9" services: ziti-host: - image: openziti/ziti-host + image: docker.io/openziti/ziti-host + volumes: + - ziti-host:/ziti-edge-tunnel environment: - - ZITI_IDENTITY_JSON + - ZITI_ENROLL_TOKEN +volumes: + ziti-host: ``` -Configure a single, enrolled identity from the host filesystem directory in the same directory as `docker-compose.yml`. +### Use an Enrolled Identity from the Environment -In this example, the file `ziti_id.jwt` exists and is used to enroll on the first run, producing `ziti_id.json`, the identity configuration file. Subsequent runs will use only the enrolled identity's JSON configuration file. +You may source an existing identity from the environment. ```yaml -version: "3.9" services: ziti-host: - image: openziti/ziti-host - volumes: - - .:/ziti-edge-tunnel + image: docker.io/openziti/ziti-host environment: - - ZITI_IDENTITY_BASENAME=ziti_id + - ZITI_IDENTITY_JSON ``` -Configure all enrolled identities from a named volume. - -This example loads all files named *.json from the mounted volume. - -```yaml -version: "3.9" -services: - ziti-host: - image: openziti/ziti-host - volumes: - - ziti-identities:/ziti-edge-tunnel -volumes: - ziti-identities: -``` +### Mount an Enrolled Identity File -Enroll a single identity with a token from an environment variable and store in a named volume. +You may mount an existing identity from the host's filesystem. The default path to find the identity during startup is `/ziti-edge-tunnel/ziti_id.json`. Optionally, set `ZITI_IDENTITY_BASENAME` to change the filename prefix from `ziti_id`. ```yaml -version: "3.9" services: ziti-host: - image: openziti/ziti-host + image: docker.io/openziti/ziti-host volumes: - - ziti-identity:/ziti-edge-tunnel - environment: - - ZITI_IDENTITY_BASENAME=ziti_id - - ZITI_ENROLL_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbSI6Im90dCIsImV4cCI6MTY3MDAwEFQ2NywiaXNzIjoiaHR0cHM6Ly83Y2U3ZTQyNC02YTkyLTRmZjItOTQ1OS1lYmJiYTMyMzQ2ZmEucHJvZHVjdGlvbi5uZXRmb3VuZHJ5LmlvOjQ0MyIsImp0aSI6ImQ0YjczZjFlLTRkOWEtNDk0ZC04NGQxLTQ2OWE1MGQyYzhmMCIsInN1YiI6ImdXdkQwaTd5RDkifQ.R5t2hoH0W1vJUn78_O8azoJ05FWLLSh6J3Q1XaDOidaYgDOWcLm7YiV99rymnjSjRC86IjNsAyZK678_D2dqyefR3VBI8LepamZ5jVSAcDFCF3Swk_jszcHDqcYs2YCucr6qrwsv8NTqEdUAJ8NVOiRaZbGhSuBvXTmWilCkCLcL7R4tXpIHakM_2WA4_tmwdbN8i7SGPPAB6pZOK_xDW10nBjg5Fe3Of_-53Gd-3swm9D3Yms1iIPBfMIQUWNzYaOCBa8UvGo8d9JjvJKgTlkMwZHL3hayzAuVEXoR1-LbA1t1Nhd8FgjvuL-YxN0XLaA3koL-FijL7ehWZoyUYPuO3xi63SQpbO-oDtX89jvGLMVercZBscXQsmCkDcj8OAnTb3Czb8HmsHgfydqvT6epUNFxFe_fSGz-CuGIuFBQwygfpBriGBnwVk8dnIJt7Wl75jPR8v-NImIIv1dKCI_ZajlsJ5l8D4OGnj76pBs3Wu7Hq1zxAbJ8HPJmi_ywTHAHVJVghifRTIR6_SyfeZGsHDY9s8YH5ErYvarBvMxwPCmjMMY3SKM_YOPG0u1c-KKByS3m7x7qia6P1ShWwGkbMmY722iFeVvoGN7SD51CkZiqWHClhBtdDv6_1K7y62KEmiX0D4YHXoikNqMCoPwa4yKyDRzoO8DKcAzaVRRg -volumes: - ziti-identity: + - ./ziti_id.json:/ziti-edge-tunnel/ziti_id.json ``` -#### Kubernetes Deployments for `openziti/ziti-host` - -Refer to [the workload tunneling guides for Kubernetes](https://docs.openziti.io/docs/guides/kubernetes/workload-tunneling/). - -## Use Case: Intercepting Proxy and Nameserver - -This use case involves deploying the OpenZiti tunneler as an intercepting proxy with a built-in nameserver. Use the `openziti/ziti-edge-tunnel` container image for this case. - -The "run" mode requires elevated privileges to configure the OS with a DNS resolver and IP routes. - -### Container Image `openziti/ziti-edge-tunnel` - -This image runs `ziti-edge-tunnel run`, the intercepting proxy mode of the tunneler. The Red Hat 8 Universal Base Image (UBI) is the base image of this container. +### Mount a Directory of Enrolled Identity Files -See [the Linux tunneler doc](https://docs.openziti.io/docs/reference/tunnelers/linux/) for general info about the OpenZiti tunneler. - -#### Tags for `openziti/ziti-edge-tunnel` - -The container image `openziti/ziti-edge-tunnel` is published in Docker Hub and automatically updated with new releases. You may subscribe to `:latest` (default) or pin a version for stability, e.g., `:0.19.11`. - -#### Dockerfile for `openziti/ziti-edge-tunnel` - -The Dockerfile for `openziti/ziti-edge-tunnel` is [./Dockerfile.base](Dockerfile.base). - -#### Accessing OpenZiti Services with `openziti/ziti-edge-tunnel` - -Intercepting proxy `run` mode captures DNS names and layer-4 traffic that match authorized destinations. - -```bash -# current directory contains enrollment token file ziti_id.jwt -docker run \ - --name ziti-tun \ - --network host \ - --privileged \ - --volume ${PWD}:/ziti-edge-tunnel/ \ - --volume "/var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket" \ - --device "/dev/net/tun:/dev/net/tun" \ - --env ZITI_IDENTITY_BASENAME=ziti_id \ - openziti/ziti-edge-tunnel -``` - -#### Docker Compose Examples for `openziti/ziti-edge-tunnel` - -This example uses [the Docker Compose project](docker-compose.yml) included in this repo. - -```bash -# enrolled identity file ziti_id.json is in the same directory as docker-compose.yml -ZITI_IDENTITY_BASENAME=ziti_id docker-compose run ziti-tun -``` - -This example uses a single, enrolled identity configuration file `ziti_id.json` in the same directory as `docker-compose.yml`. +You may mount many existing identities from the host's filesystem. The tunneler will load all valid, readable identities at each startup. The tunneler will look for files matching `/ziti-edge-tunnel/*.json`. ```yaml -version: "3.9" services: - ziti-tun: - image: openziti/ziti-edge-tunnel - devices: - - /dev/net/tun:/dev/net/tun + ziti-host: + image: docker.io/openziti/ziti-host volumes: - - .:/ziti-edge-tunnel - - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket - environment: - - ZITI_IDENTITY_BASENAME=ziti_id - - PFXLOG_NO_JSON=true # suppress JSON logging - network_mode: host - privileged: true + - ./identities:/ziti-edge-tunnel ``` - -#### Kubernetes Deployments for `openziti/ziti-edge-tunnel` - -Refer to [the workload tunneling guides for Kubernetes](https://docs.openziti.io/docs/guides/kubernetes/workload-tunneling/). diff --git a/docker/compose.host.yml b/docker/compose.host.yml new file mode 100644 index 00000000..a944634a --- /dev/null +++ b/docker/compose.host.yml @@ -0,0 +1,10 @@ +volumes: + ziti-host: + +services: + ziti-host: + image: ${ZITI_HOST_IMAGE:-openziti/ziti-host}:${ZITI_HOST_TAG:-latest} + volumes: + - ziti-host:/ziti-edge-tunnel + environment: + - ZITI_ENROLL_TOKEN diff --git a/docker/compose.intercept.yml b/docker/compose.intercept.yml new file mode 100644 index 00000000..407ac784 --- /dev/null +++ b/docker/compose.intercept.yml @@ -0,0 +1,15 @@ +volumes: + ziti-edge-tunnel: + +services: + ziti-tun: + image: ${ZITI_EDGE_TUNNEL_IMAGE:-openziti/ziti-edge-tunnel}:${ZITI_EDGE_TUNNEL_TAG:-latest} + devices: + - /dev/net/tun:/dev/net/tun + volumes: + - ziti-edge-tunnel:/ziti-edge-tunnel + - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket + environment: + - ZITI_ENROLL_TOKEN # ZITI_IDENTITY_BASENAME=AcmeIdentity ZITI_ENROLL_TOKEN={JWT} docker-compose up ziti-tun + network_mode: host # use the Docker host's network, not the Docker bridge + privileged: true diff --git a/docker/compose.test.yml b/docker/compose.test.yml new file mode 100644 index 00000000..4a477747 --- /dev/null +++ b/docker/compose.test.yml @@ -0,0 +1,98 @@ +services: + httpbin: + image: mccutchen/go-httpbin + expose: + - 8080 + networks: + - quickstart + + ziti-tun: + pull_policy: never + command: run --verbose=4 --dns-ip-range=100.95.255.128/25 + + ziti-host: + pull_policy: never + networks: + - quickstart + command: run-host --verbose=4 + # entrypoint: ["bash", "-x", "/docker-entrypoint.sh"] + + quickstart: + image: ${ZITI_CLI_IMAGE:-docker.io/openziti/ziti-controller}:${ZITI_CLI_TAG:-latest} + networks: + quickstart: + # this allows other containers to use the same external DNS name to reach the quickstart container from within the + # Docker network that clients outside the Docker network use to reach the quickstart container via port forwarding + aliases: + - ${ZITI_CTRL_ADVERTISED_ADDRESS:-ziti-controller} + - ${ZITI_ROUTER_ADVERTISED_ADDRESS:-ziti-router} + entrypoint: + - bash + - -euc + - | + ZITI_CMD+=" --ctrl-address ${ZITI_CTRL_ADVERTISED_ADDRESS:-quickstart}"\ + " --ctrl-port ${ZITI_CTRL_ADVERTISED_PORT:-1280}"\ + " --router-address ${ZITI_ROUTER_ADVERTISED_ADDRESS:-${ZITI_CTRL_ADVERTISED_ADDRESS:-quickstart}}"\ + " --router-port ${ZITI_ROUTER_PORT:-3022}"\ + " --password ${ZITI_PWD:-admin}" + echo "DEBUG: run command is: ziti $${@} $${ZITI_CMD}" + exec ziti "$${@}" $${ZITI_CMD} + command: -- edge quickstart --home /home/ziggy/quickstart + user: ${ZIGGY_UID:-1000} + working_dir: /home/ziggy + environment: + HOME: /home/ziggy + PFXLOG_NO_JSON: "${PFXLOG_NO_JSON:-true}" + ZITI_ROUTER_NAME: ${ZITI_ROUTER_NAME:-quickstart-router} + volumes: + # store the quickstart state in a named volume "ziti_home" or store the quickstart state on the Docker host in a + # directory, ZITI_HOME + - ${ZITI_HOME:-ziti_home}:/home/ziggy + ports: + - ${ZITI_INTERFACE:-0.0.0.0}:${ZITI_CTRL_ADVERTISED_PORT:-1280}:${ZITI_CTRL_ADVERTISED_PORT:-1280} + - ${ZITI_INTERFACE:-0.0.0.0}:${ZITI_ROUTER_PORT:-3022}:${ZITI_ROUTER_PORT:-3022} + expose: + - ${ZITI_CTRL_ADVERTISED_PORT:-1280} + - ${ZITI_ROUTER_PORT:-3022} + depends_on: + quickstart-init: + condition: service_completed_successfully + healthcheck: + test: + - CMD + - ziti + - agent + - stats + interval: 3s + timeout: 3s + retries: 5 + start_period: 30s + + # this service is used to initialize the ziti_home volume by setting the owner to the UID of the user running the + # quickstart container + quickstart-init: + image: busybox + command: chown -Rc ${ZIGGY_UID:-1000} /home/ziggy + user: root + volumes: + # store the quickstart state in a named volume "ziti_home" or store the quickstart state on the Docker host in a + # directory, ZITI_HOME + - ${ZITI_HOME:-ziti_home}:/home/ziggy + + # add a health check for the quickstart network + quickstart-check: + image: busybox + command: echo "Ziti is cooking" + depends_on: + quickstart: + condition: service_healthy + +# define a custom network so that we can also define a DNS alias for the quickstart container +networks: + quickstart: + driver: bridge + +volumes: + # this will not be used if you switch from named volume to bind mount volume + ziti_home: + driver: local diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index fa8da3ca..00000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,77 +0,0 @@ -version: "3.9" - -x-base-service: &base-service - image: openziti/ziti-edge-tunnel - devices: - - /dev/net/tun:/dev/net/tun - volumes: - - .:/ziti-edge-tunnel - - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket - environment: - - ZITI_IDENTITY_BASENAME # inherit when run like this: ZITI_IDENTITY_BASENAME=AcmeIdentity docker-compose up ziti-tun - - ZITI_ENROLL_TOKEN # ZITI_IDENTITY_BASENAME=AcmeIdentity ZITI_ENROLL_TOKEN={JWT} docker-compose up ziti-tun - - PFXLOG_NO_JSON=true # suppress JSON logging - network_mode: host # use the Docker host's network, not the Docker bridge - privileged: true - -services: - - ziti-tun: # tunneler for one Ziti identity - <<: *base-service - command: - - --verbose=4 - - --dns-ip-range=100.64.64.0/18 - - ziti-tun-dir: # tunneler for all identities in /ziti-edge-tunnel - <<: *base-service - command: - - --verbose=4 - - --dns-ip-range=100.64.64.0/18 - environment: [] # ignore ZITI_IDENTITY_BASENAME and load all identities in same dir - - ziti-test: # docker-compose exec ziti-test bash - <<: *base-service - entrypoint: ["sh", "-c", "while true; do sleep infinity; done"] - - ziti-host: # tunneler for hosting services without providing DNS or IP routes - image: openziti/ziti-host - environment: - - ZITI_IDENTITY_JSON - networks: - - ziti-host - privileged: false # no privileges necessary for run-host mode - - ziti-host-wait: # tunneler for hosting services that waits forever for the identity to become available - image: openziti/ziti-host - environment: - - ZITI_IDENTITY_BASENAME - - ZITI_IDENTITY_WAIT=-1 # optional seconds to wait for identity (or token) to become available, negative value is wait forever - volumes: - - .:/ziti-edge-tunnel - networks: - - ziti-host - privileged: false # no privileges necessary for run-host mode - - ziti-host-dir: # tunneler for hosting services without providing DNS or IP routes - image: openziti/ziti-host - environment: [] # ignore ZITI_IDENTITY_BASENAME and load all identities in dir - volumes: - - .:/ziti-edge-tunnel - networks: - - ziti-host - privileged: false # no privileges necessary for run-host mode - - hello: # http://hello:8080 from bridge network "ziti-host" - image: netfoundry/hello-world-webpage - networks: - - ziti-host - - httpbin: - image: mccutchen/go-httpbin - networks: - - ziti-host - # ports: - # - "127.0.0.1:8080:8080/tcp" - -networks: - ziti-host: diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 756f756b..2c3c3155 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -16,103 +16,117 @@ # limitations under the License. # -set -e -u -o pipefail +set -o errexit -o nounset -o pipefail function alldone() { - # if successfully sent to background then send SIGTERM to trigger a cleanup - # of resolver config, tun devices and associated routes + # if successfully sent to background then send SIGTERM because ZET does not respond to SIGINT [[ "${ZITI_EDGE_TUNNEL_PID:-}" =~ ^[0-9]+$ ]] && { - kill -TERM "$ZITI_EDGE_TUNNEL_PID" - # let entrypoint script exit after ziti-edge-tunnel PID - kill -0 "$ZITI_EDGE_TUNNEL_PID" && wait "$ZITI_EDGE_TUNNEL_PID" + kill -0 "$ZITI_EDGE_TUNNEL_PID" &>/dev/null && { + kill -TERM "$ZITI_EDGE_TUNNEL_PID" + # let entrypoint script exit after ziti-edge-tunnel PID + wait "$ZITI_EDGE_TUNNEL_PID" + } } } trap alldone SIGTERM SIGINT EXIT -IDENTITIES_DIR="/ziti-edge-tunnel" -if ! [[ -d "${IDENTITIES_DIR}" ]]; then - echo "ERROR: need directory ${IDENTITIES_DIR} to find tokens and identities" >&2 - exit 1 -fi +unset \ + ZITI_IDENTITY_DIR \ + IDENTITY_FILE \ + JSON_FILES \ + JWT_CANDIDATE \ + JWT_FILE \ + TUNNEL_OPTS \ + TUNNEL_RUN_MODE -if ! mountpoint "${IDENTITIES_DIR}" &>/dev/null; then - echo "WARN: the identities directory is only available inside this container because ${IDENTITIES_DIR} is not a mounted volume. Be careful to not publish this image with identity inside or lose access to the identity by removing the image prematurely." >&2 -else - if [[ -n "${ZITI_IDENTITY_JSON:-}" ]]; then - echo "WARNING: you supplied the Ziti identity as an env var and you mounted a volume on the identities dir. You may avoid this warning and future errors by not mounting a volume on ${IDENTITIES_DIR} when ZITI_IDENTITY_JSON is defined." >&2 +# adapt deprecated NF_REG_* env vars to undefined ZITI_* env vars +if [[ -z "${ZITI_IDENTITY_BASENAME:-}" ]]; then + if [[ -n "${NF_REG_NAME:-}" ]]; then + echo "WARN: replacing deprecated NF_REG_NAME with ZITI_IDENTITY_BASENAME=${NF_REG_NAME}" + ZITI_IDENTITY_BASENAME="${NF_REG_NAME}" + elif [[ -n "${IOTEDGE_DEVICEID:-}" ]]; then + echo "WARN: replacing deprecated IOTEDGE_DEVICEID with ZITI_IDENTITY_BASENAME=${IOTEDGE_DEVICEID}" + ZITI_IDENTITY_BASENAME="${IOTEDGE_DEVICEID}" fi fi - -# -## Map the preferred, Ziti var names to legacy NF names. This allows us to begin using the preferred vars right away -## while minimizing immediate differences to the main control structure. This eases code review. Later, the legacy -## names can be retired and replaced. -# -if [[ -n "${ZITI_IDENTITY_BASENAME:-}" ]]; then - echo "INFO: setting NF_REG_NAME to \${ZITI_IDENTITY_BASENAME} (${ZITI_IDENTITY_BASENAME})" - NF_REG_NAME="${ZITI_IDENTITY_BASENAME}" -fi -if [[ -n "${ZITI_ENROLL_TOKEN:-}" ]]; then - echo "INFO: setting NF_REG_TOKEN to \${ZITI_ENROLL_TOKEN} (${ZITI_ENROLL_TOKEN})" - NF_REG_TOKEN="${ZITI_ENROLL_TOKEN}" +if [[ -z "${ZITI_ENROLL_TOKEN:-}" && -n "${NF_REG_TOKEN:-}" ]]; then + echo "WARN: replacing deprecated NF_REG_TOKEN with ZITI_ENROLL_TOKEN=${NF_REG_TOKEN}" + ZITI_ENROLL_TOKEN="${NF_REG_TOKEN}" fi -if [[ -n "${ZITI_IDENTITY_WAIT:-}" ]]; then - echo "INFO: setting NF_REG_WAIT to \${ZITI_IDENTITY_WAIT} (${ZITI_IDENTITY_WAIT})" - NF_REG_WAIT="${ZITI_IDENTITY_WAIT}" +if [[ -z "${ZITI_IDENTITY_WAIT:-}" && -n "${NF_REG_WAIT:-}" ]]; then + echo "WARN: replacing deprecated var NF_REG_WAIT with ZITI_IDENTITY_WAIT=${NF_REG_WAIT}" + ZITI_IDENTITY_WAIT="${NF_REG_WAIT}" fi -# treat IOTEDGE_DEVICEID, a standard var assigned by Azure IoT, as an alias for NF_REG_NAME -if [[ -z "${NF_REG_NAME:-}" ]]; then - if [[ -n "${IOTEDGE_DEVICEID:-}" ]]; then - echo "INFO: setting NF_REG_NAME to \${IOTEDGE_DEVICEID} (${IOTEDGE_DEVICEID})" - NF_REG_NAME="${IOTEDGE_DEVICEID}" - fi -fi +# assign default identity dir if not set in parent env; this is a writeable path within the container image +: "${ZITI_IDENTITY_DIR:="/ziti-edge-tunnel"}" -# if identity JSON var is defined then write to a file +# if enrolled identity JSON is provided then write it to a file in the identities dir if [[ -n "${ZITI_IDENTITY_JSON:-}" ]]; then - # if the basename is not defined then use a default basename to write JSON to a file - if [[ -z "${NF_REG_NAME:-}" ]]; then - NF_REG_NAME="ziti_id" + if [[ -z "${ZITI_IDENTITY_BASENAME:-}" ]]; then + ZITI_IDENTITY_BASENAME="ziti_id" + fi + IDENTITY_FILE="${ZITI_IDENTITY_DIR}/${ZITI_IDENTITY_BASENAME}.json" + if [[ -s "${IDENTITY_FILE}" ]]; then + echo "WARN: clobbering non-empty Ziti identity file ${IDENTITY_FILE} with contents of env var ZITI_IDENTITY_JSON" >&2 + fi + echo "${ZITI_IDENTITY_JSON}" > "${IDENTITY_FILE}" +# if an enrollment token is provided then write it to a file in the identities dir so it will be found in the next step +# and used to enroll +elif [[ -n "${ZITI_ENROLL_TOKEN:-}" ]]; then + if [[ -z "${ZITI_IDENTITY_BASENAME:-}" ]]; then + ZITI_IDENTITY_BASENAME="ziti_id" + fi + JWT_FILE="${ZITI_IDENTITY_DIR}/${ZITI_IDENTITY_BASENAME}.jwt" + if [[ -s "${JWT_FILE}" ]]; then + echo "WARN: clobbering non-empty Ziti enrollment token file ${JWT_FILE} with contents of env var ZITI_ENROLL_TOKEN" >&2 fi - if [[ -s "${IDENTITIES_DIR}/${NF_REG_NAME}.json" ]]; then - echo "ERROR: refusing to clobber non-empty Ziti identity file ${NF_REG_NAME}.json with contents of env var ZITI_IDENTITY_JSON!" >&2 + echo "${ZITI_ENROLL_TOKEN}" > "${JWT_FILE}" +# otherwise, assume the identities dir is a mounted volume with identity files or tokens +else + if ! [[ -d "${ZITI_IDENTITY_DIR}" ]]; then + echo "ERROR: need directory ${ZITI_IDENTITY_DIR} to find tokens and identities" >&2 exit 1 - else - echo "${ZITI_IDENTITY_JSON}" > "${IDENTITIES_DIR}/${NF_REG_NAME}.json" fi fi typeset -a TUNNEL_OPTS -# if identity file, else multiple identities dir -if [[ -n "${NF_REG_NAME:-}" ]]; then - IDENTITY_FILE="${IDENTITIES_DIR}/${NF_REG_NAME}.json" +# if identity basename is specified then look for an identity file with that name, else load all identities in the +# identities dir mountpoint +if [[ -n "${ZITI_IDENTITY_BASENAME:-}" ]]; then + IDENTITY_FILE="${ZITI_IDENTITY_DIR}/${ZITI_IDENTITY_BASENAME}.json" TUNNEL_OPTS=("--identity" "${IDENTITY_FILE}") - : ${NF_REG_WAIT:=1} - if [[ "${NF_REG_WAIT}" =~ ^[0-9]+$ ]]; then - echo "DEBUG: waiting ${NF_REG_WAIT}s for ${IDENTITY_FILE} (or token) to appear" - elif (( "${NF_REG_WAIT}" < 0 )); then + + # if wait is specified then wait for the identity file or token to appear + : "${ZITI_IDENTITY_WAIT:=3}" + # if a positive integer then wait that many seconds for the identity file or token to appear + if [[ "${ZITI_IDENTITY_WAIT}" =~ ^[0-9]+$ ]]; then + echo "DEBUG: waiting ${ZITI_IDENTITY_WAIT}s for ${IDENTITY_FILE} (or token) to appear" + # if a negative integer then wait forever for the identity file or token to appear + elif (( ZITI_IDENTITY_WAIT < 0 )); then echo "DEBUG: waiting forever for ${IDENTITY_FILE} (or token) to appear" + # error if not an integer else - echo "ERROR: need integer for NF_REG_WAIT" >&2 + echo "ERROR: ZITI_IDENTITY_WAIT must be an integer (seconds to wait)" >&2 exit 1 fi - while (( $NF_REG_WAIT > 0 || $NF_REG_WAIT < 0)); do + while (( ZITI_IDENTITY_WAIT > 0 || ZITI_IDENTITY_WAIT < 0)); do # if non-empty identity file if [[ -s "${IDENTITY_FILE}" ]]; then echo "INFO: found identity file ${IDENTITY_FILE}" break 1 # look for enrollment token else - echo "INFO: identity file ${IDENTITY_FILE} does not exist" + echo "DEBUG: identity file ${IDENTITY_FILE} not found" for dir in "/var/run/secrets/netfoundry.io/enrollment-token" \ "/enrollment-token" \ - "${IDENTITIES_DIR}"; do - JWT_CANDIDATE="${dir}/${NF_REG_NAME}.jwt" - echo "INFO: looking for ${JWT_CANDIDATE}" + "${ZITI_IDENTITY_DIR}"; do + JWT_CANDIDATE="${dir}/${ZITI_IDENTITY_BASENAME}.jwt" if [[ -s "${JWT_CANDIDATE}" ]]; then JWT_FILE="${JWT_CANDIDATE}" break 1 + else + echo "DEBUG: ${JWT_CANDIDATE} not found" fi done if [[ -n "${JWT_FILE:-}" ]]; then @@ -121,12 +135,14 @@ if [[ -n "${NF_REG_NAME:-}" ]]; then echo "ERROR: failed to enroll with token from ${JWT_FILE} ($(wc -c < "${JWT_FILE}")B)" >&2 exit 1 } - elif [[ -n "${NF_REG_TOKEN:-}" ]]; then - echo "INFO: attempting enrollment with NF_REG_TOKEN" - ziti-edge-tunnel enroll --jwt - --identity "${IDENTITY_FILE}" <<< "${NF_REG_TOKEN}" || { - echo "ERROR: failed to enroll with token from NF_REG_TOKEN ($(wc -c <<<"${NF_REG_TOKEN}")B)" >&2 + elif [[ -n "${ZITI_ENROLL_TOKEN:-}" ]]; then + echo "INFO: attempting enrollment with ZITI_ENROLL_TOKEN" + ziti-edge-tunnel enroll --jwt - --identity "${IDENTITY_FILE}" <<< "${ZITI_ENROLL_TOKEN}" || { + echo "ERROR: failed to enroll with token from ZITI_ENROLL_TOKEN ($(wc -c <<<"${ZITI_ENROLL_TOKEN}")B)" >&2 exit 1 } + # this works but the legacy var name was never deprecated because of doubts about the utility of this + # feature elif [[ -n "${NF_REG_STDIN:-}" ]]; then echo "INFO: trying to get token from stdin" >&2 ziti-edge-tunnel enroll --jwt - --identity "${IDENTITY_FILE}" || { @@ -136,22 +152,24 @@ if [[ -n "${NF_REG_NAME:-}" ]]; then fi fi # decrement the wait seconds until zero or forever if negative - (( NF_REG_WAIT-- )) + (( ZITI_IDENTITY_WAIT-- )) sleep 1 done +# if no identity basename is specified then load all *.json files in the identities dir mountpoint, ignoring enrollment +# tokens else typeset -a JSON_FILES - mapfile -t JSON_FILES < <(ls -1 "${IDENTITIES_DIR}"/*.json) + mapfile -t JSON_FILES < <(ls -1 "${ZITI_IDENTITY_DIR}"/*.json) if [[ ${#JSON_FILES[*]} -gt 0 ]]; then - echo "INFO: NF_REG_NAME not set, loading ${#JSON_FILES[*]} identities from ${IDENTITIES_DIR}" - TUNNEL_OPTS=("--identity-dir" "${IDENTITIES_DIR}") + echo "INFO: loading ${#JSON_FILES[*]} identities from ${ZITI_IDENTITY_DIR}" + TUNNEL_OPTS=("--identity-dir" "${ZITI_IDENTITY_DIR}") else - echo "ERROR: NF_REG_NAME not set and zero identities found in ${IDENTITIES_DIR}" >&2 + echo "ERROR: ZITI_IDENTITY_BASENAME not set and zero identities found in ${ZITI_IDENTITY_DIR}" >&2 exit 1 fi fi -echo "DEBUG: evaluating positionals: $*" +echo "DEBUG: checking for run mode as first positional in: $*" if (( ${#} )) && [[ ${1:0:3} == run ]]; then TUNNEL_RUN_MODE=${1} shift @@ -159,7 +177,8 @@ else TUNNEL_RUN_MODE=run fi -echo "INFO: running ziti-edge-tunnel" +echo "INFO: running: ziti-edge-tunnel ${TUNNEL_RUN_MODE} ${TUNNEL_OPTS[*]} ${*}" ziti-edge-tunnel "${TUNNEL_RUN_MODE}" "${TUNNEL_OPTS[@]}" "${@}" & ZITI_EDGE_TUNNEL_PID=$! +echo "DEBUG: waiting for ziti-edge-tunnel PID: ${ZITI_EDGE_TUNNEL_PID}" wait $ZITI_EDGE_TUNNEL_PID diff --git a/docker/docker.test.bash b/docker/docker.test.bash new file mode 100755 index 00000000..71d1022d --- /dev/null +++ b/docker/docker.test.bash @@ -0,0 +1,169 @@ +#!/usr/bin/env bash + +# exec this script with BASH v4+ on Linux to test the checked-out ziti-tunnel-sdk-c repo's Docker deployments + +set -o errexit +set -o nounset +set -o pipefail +set -o xtrace + +cleanup(){ + if ! (( I_AM_ROBOT )) + then + echo "WARNING: destroying all controller and router state volumes in 30s; set I_AM_ROBOT=1 to suppress this message" >&2 + sleep 30 + fi + docker compose down --volumes --remove-orphans + echo "DEBUG: cleanup complete" +} + +checkCommand() { + if ! command -v "$1" &>/dev/null; then + logError "this script requires command '$1'." + $1 + fi +} + +portcheck(){ + PORT="${1}" + if nc -zv localhost "$PORT" &>/dev/null + then + echo "ERROR: port $PORT is already allocated" >&2 + return 1 + else + echo "DEBUG: port $PORT is available" + return 0 + fi +} + +BASEDIR="$(cd "$(dirname "${0}")" && pwd)" +REPOROOT="$(cd "${BASEDIR}/.." && pwd)" +cd "${REPOROOT}" + +declare -a BINS=(grep docker ./scripts/ziti-builder.sh curl nc jq) +for BIN in "${BINS[@]}"; do + checkCommand "$BIN" +done + +: "${I_AM_ROBOT:=0}" +: "${ZITI_CTRL_ADVERTISED_PORT:=12802}" +: "${ZITI_ROUTER_PORT:=30224}" +# : "${ZIGGY_UID:=$(id -u)}" + +bash -x ./scripts/ziti-builder.sh -p ci-linux-x64-static-libssl +mkdir -p ./build/amd64/linux +cp ./build/programs/ziti-edge-tunnel/Release/ziti-edge-tunnel ./build/amd64/linux/ziti-edge-tunnel + +ZITI_EDGE_TUNNEL_IMAGE="ziti-edge-tunnel" +ZITI_EDGE_TUNNEL_TAG="local" + +docker build \ +--build-arg "DOCKER_BUILD_DIR=./docker" \ +--tag "${ZITI_EDGE_TUNNEL_IMAGE}:${ZITI_EDGE_TUNNEL_TAG}" \ +--file "./docker/ziti-edge-tunnel.Dockerfile" \ +"${PWD}" + +ZITI_HOST_IMAGE="ziti-host" +ZITI_HOST_TAG="local" + +docker build \ +--build-arg "ZITI_EDGE_TUNNEL_IMAGE=${ZITI_EDGE_TUNNEL_IMAGE}" \ +--build-arg "ZITI_EDGE_TUNNEL_TAG=${ZITI_EDGE_TUNNEL_TAG}" \ +--tag "${ZITI_HOST_IMAGE}:${ZITI_HOST_TAG}" \ +--file "./docker/ziti-host.Dockerfile" \ +"${PWD}" + +# also let docker inherit the vars that define the tunneler images +export \ +ZITI_EDGE_TUNNEL_IMAGE \ +ZITI_EDGE_TUNNEL_TAG \ +ZITI_HOST_IMAGE \ +ZITI_HOST_TAG \ + +export COMPOSE_FILE="docker/compose.intercept.yml:docker/compose.host.yml:docker/compose.test.yml" + +cleanup + +# freshen ziti-controller, httpbin, etc. images +docker compose pull + +for PORT in "${ZITI_CTRL_ADVERTISED_PORT}" "${ZITI_ROUTER_PORT}" +do + portcheck "${PORT}" +done + +# configure the quickstart container +export \ +ZITI_CTRL_ADVERTISED_ADDRESS="ziti.127.0.0.1.sslip.io" \ +ZITI_PWD="ziggypw" \ +ZITI_CTRL_ADVERTISED_PORT \ +ZITI_ROUTER_PORT + +# run the check container that waits for a responsive controller agent +docker compose up quickstart-check + +# run the script from heredoc inside the quickstart container after variable interpolation +docker compose exec -T quickstart bash << BASH + +set -o errexit +set -o nounset +set -o pipefail +set -o xtrace + +ziti edge login \ +${ZITI_CTRL_ADVERTISED_ADDRESS}:${ZITI_CTRL_ADVERTISED_PORT} \ +--ca=/home/ziggy/quickstart/pki/root-ca/certs/root-ca.cert \ +--username=admin \ +--password=${ZITI_PWD} \ +--timeout=1 \ +--verbose + +ziti edge create identity "httpbin-client" \ + --jwt-output-file /tmp/httpbin-client.ott.jwt \ + --role-attributes httpbin-clients + +ziti edge create identity "httpbin-host" \ + --jwt-output-file /tmp/httpbin-host.ott.jwt \ + --role-attributes httpbin-hosts + +ziti edge create config "httpbin-intercept-config" intercept.v1 \ + '{"protocols":["tcp"],"addresses":["httpbin.ziti.internal"], "portRanges":[{"low":80, "high":80}]}' + +ziti edge create config "httpbin-host-config" host.v1 \ + '{"protocol":"tcp", "address":"httpbin","port":8080}' + +ziti edge create service "httpbin-service" \ + --configs httpbin-intercept-config,httpbin-host-config \ + --role-attributes test-services + +ziti edge create service-policy "httpbin-bind-policy" Bind \ + --service-roles '#test-services' \ + --identity-roles '#httpbin-hosts' + +ziti edge create service-policy "httpbin-dial-policy" Dial \ + --service-roles '#test-services' \ + --identity-roles '#httpbin-clients' +BASH + +ZITI_ENROLL_TOKEN="$(docker compose exec quickstart cat /tmp/httpbin-host.ott.jwt)" \ +docker compose up ziti-host --detach +docker compose up httpbin --detach + +ZITI_ENROLL_TOKEN="$(docker compose exec quickstart cat /tmp/httpbin-client.ott.jwt)" \ +docker compose up ziti-tun --detach + +ATTEMPTS=5 +DELAY=3 + +curl_cmd="curl --fail --max-time 1 --silent --show-error --request POST --header 'Content-Type: application/json' --data '{\"ziti\": \"works\"}' http://httpbin.ziti.internal/post" +until ! ((ATTEMPTS)) || eval "${curl_cmd}" &> /dev/null +do + (( ATTEMPTS-- )) + echo "Waiting for httpbin service" + sleep ${DELAY} +done +eval "${curl_cmd}" | jq .json + +(( I_AM_ROBOT )) || read -p "Press [Enter] to continue..." + +cleanup diff --git a/docker/fetch-github-releases.sh b/docker/fetch-github-releases.sh deleted file mode 100755 index 4cf9d16c..00000000 --- a/docker/fetch-github-releases.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash - -# -# Copyright 2021 NetFoundry Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://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. -# - -set -euo pipefail - -[[ $# -eq 0 ]] && { - echo "ERROR: need the base name of the executable to fetch e.g. \"ziti-edge-tunnel\"." >&2 - exit 1 -} - -echo "Fetching from GitHub." -# defaults -: "${GITHUB_BASE_URL:=https://github.com}" -: "${GITHUB_REPO:="openziti/ziti-tunnel-sdk-c"}" -: "${ZITI_VERSION:="latest"}" - -if [[ "$ZITI_VERSION" == "latest" ]];then - echo "WARN: ZITI_VERSION unspecified, using 'latest'" >&2 -else - # ensure version string begins with 'v' by stripping if present and re-adding - ZITI_VERSION="v${ZITI_VERSION#v}" -fi - -# map host architecture/os to directories that we use in GitHub. -# (our artifact directories seem to align with Docker's TARGETARCH and TARGETOS -# build arguments, which we could rely on if we fully committed to "docker buildx" - see -# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope) -host_arch=$(uname -m) -case "${host_arch}" in -"x86_64") artifact_arch="x86_64";; -"armv7l") artifact_arch="arm";; -"aarch64") artifact_arch="arm64";; -*) echo "ERROR: Ziti binaries do not exist for architecture ${host_arch}"; exit 1;; -esac - -host_os=$(uname -s) -case "${host_os}" in - "Linux") artifact_os="Linux";; - "Darwin") artifact_os="Darwin";; - #"Windows") artifact_os="windows";; # Windows bins do not exist -*) echo "ERROR: ziti binaries do not exist for os ${host_os}"; exit 1;; -esac - -for exe in "${@}"; do - zip="${exe}-${artifact_os}_${artifact_arch}.zip" - case "${ZITI_VERSION}" in - "latest") url="${GITHUB_BASE_URL}/${GITHUB_REPO}/releases/${ZITI_VERSION}/download/${zip}" ;; - *) url="${GITHUB_BASE_URL}/${GITHUB_REPO}/releases/download/${ZITI_VERSION}/${zip}" ;; - esac - - echo "Fetching ${zip} from ${url}" - rm -f "${zip}" "${exe}" - if { command -v curl > /dev/null; } 2>&1; then - curl -fLsS -O "${url}" - elif { command -v wget > /dev/null; } 2>&1; then - wget "${url}" - else - echo "ERROR: need one of curl or wget to fetch the artifact." >&2 - exit 1 - fi - unzip "${zip}" - if [ -f "${exe}" ]; then - chmod 755 "${exe}" - elif [ -f "${exe}.exe" ]; then - chmod 755 "${exe}.exe" - fi - rm -f "${zip}" -done diff --git a/docker/linux-cross-build.sh b/docker/linux-cross-build.sh deleted file mode 100755 index e96789e9..00000000 --- a/docker/linux-cross-build.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env bash -# -# cross-compile the Linux artifacts for the target architecture on amd64 -# - -set -o pipefail -e -u -set -x - -DIRNAME=$(dirname $0) -REPO_DIR=${DIRNAME}/.. # parent of the top-level dir where this script lives -: ${TARGET:="bundle"} -: ${BUILD_DIST_PACKAGES:="OFF"} -: ${DISABLE_LIBSYSTEMD_FEATURE:="OFF"} - -if (( ${#} )); then - for OPT in ${*}; do - case $OPT in - --package) - TARGET="package" - BUILD_DIST_PACKAGES="ON" - shift - ;; - --no-systemd) - DISABLE_LIBSYSTEMD_FEATURE="ON" - shift - ;; - esac - done -fi - -# if no architectures supplied then default list of three -if (( ${#} )); then - typeset -a JOBS=(${@}) -else - typeset -a JOBS=(amd64 arm64 arm) -fi - -for ARCH in ${JOBS[@]}; do - CMAKE_BUILD_DIR=${REPO_DIR}/build-${ARCH} # adjacent the top-level dir where this script lives - [[ -d ${CMAKE_BUILD_DIR} ]] && rm -rf ${CMAKE_BUILD_DIR} - mkdir ${CMAKE_BUILD_DIR} -# cd ${CMAKE_BUILD_DIR} - case ${ARCH} in - amd64) { cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_TOOLCHAIN_FILE=${REPO_DIR}/toolchains/default.cmake \ - -DBUILD_DIST_PACKAGES=${BUILD_DIST_PACKAGES} \ - -DDISABLE_LIBSYSTEMD_FEATURE=${DISABLE_LIBSYSTEMD_FEATURE} \ - -S ${REPO_DIR} \ - -B ${CMAKE_BUILD_DIR} \ - && cmake \ - --build ${CMAKE_BUILD_DIR} \ - --target ${TARGET} \ - --verbose; - } - ;; - arm64) { cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_TOOLCHAIN_FILE=${REPO_DIR}/toolchains/Linux-arm64.cmake \ - -DBUILD_DIST_PACKAGES=${BUILD_DIST_PACKAGES} \ - -DDISABLE_LIBSYSTEMD_FEATURE=${DISABLE_LIBSYSTEMD_FEATURE} \ - -S ${REPO_DIR} \ - -B ${CMAKE_BUILD_DIR} \ - && cmake \ - --build ${CMAKE_BUILD_DIR} \ - --target ${TARGET} \ - --verbose; - } - ;; - arm) { cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_TOOLCHAIN_FILE=${REPO_DIR}/toolchains/Linux-arm.cmake \ - -DBUILD_DIST_PACKAGES=${BUILD_DIST_PACKAGES} \ - -DDISABLE_LIBSYSTEMD_FEATURE=${DISABLE_LIBSYSTEMD_FEATURE} \ - -S ${REPO_DIR} \ - -B ${CMAKE_BUILD_DIR} \ - && cmake \ - --build ${CMAKE_BUILD_DIR} \ - --target ${TARGET} \ - --verbose; - } - ;; - *) echo "ERROR: invalid architecture '${ARCH}', must be one of amd64, arm, arm64" >&2 - exit 1 - ;; - esac -done diff --git a/docker/linux-native-build.sh b/docker/linux-native-build.sh deleted file mode 100755 index 6b5352f4..00000000 --- a/docker/linux-native-build.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -# -# build the Linux artifacts for the native architecture -# - -set -o pipefail -e -u -set -x - -DIRNAME=$(dirname $0) -REPO_DIR=${DIRNAME}/.. # parent of the top-level dir where this script lives -: ${TARGET:="bundle"} -: ${BUILD_DIST_PACKAGES:="OFF"} -: ${DISABLE_LIBSYSTEMD_FEATURE:="OFF"} - -if (( ${#} )); then - for OPT in ${*}; do - case $OPT in - --package) - TARGET="package" - BUILD_DIST_PACKAGES="ON" - shift - ;; - --no-systemd) - DISABLE_LIBSYSTEMD_FEATURE="ON" - shift - ;; - esac - done -fi - -ARCH=$(dpkg --print-architecture) -CMAKE_BUILD_DIR=${REPO_DIR}/build-${ARCH} # adjacent the top-level dir where this script lives -[[ -d ${CMAKE_BUILD_DIR} ]] && rm -rf ${CMAKE_BUILD_DIR} -mkdir ${CMAKE_BUILD_DIR} -cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_TOOLCHAIN_FILE=${REPO_DIR}/toolchains/default.cmake \ - -DBUILD_DIST_PACKAGES=${BUILD_DIST_PACKAGES} \ - -DDISABLE_LIBSYSTEMD_FEATURE=${DISABLE_LIBSYSTEMD_FEATURE} \ - -S ${REPO_DIR} \ - -B ${CMAKE_BUILD_DIR} \ -&& cmake \ - --build ${CMAKE_BUILD_DIR} \ - --target ${TARGET} \ - --verbose; diff --git a/docker/ziti-edge-tunnel.Dockerfile b/docker/ziti-edge-tunnel.Dockerfile new file mode 100644 index 00000000..eca17ab8 --- /dev/null +++ b/docker/ziti-edge-tunnel.Dockerfile @@ -0,0 +1,38 @@ +FROM registry.access.redhat.com/ubi9/ubi-minimal + +ARG ARTIFACTS_DIR=./build +ARG DOCKER_BUILD_DIR=. +# e.g. linux +ARG TARGETOS +# e.g. arm64 +ARG TARGETARCH + +### Required OpenShift Labels +LABEL name="openziti/ziti-edge-tunnel" \ + maintainer="developers@openziti.org" \ + vendor="NetFoundry" \ + summary="OpenZiti Tunneler" \ + description="Configure a proxy and nameserver for OpenZiti Services" + +USER root + +### add licenses to this directory +RUN mkdir -m0755 /licenses +COPY ${DOCKER_BUILD_DIR}/LICENSE-Apache /licenses/apache.txt + +### Add necessary Red Hat repos and packages +RUN INSTALL_PKGS="iproute procps shadow-utils jq" \ + && microdnf -y update --setopt=install_weak_deps=0 --setopt=tsflags=nodocs \ + && microdnf -y install --setopt=install_weak_deps=0 --setopt=tsflags=nodocs ${INSTALL_PKGS} + +COPY ${ARTIFACTS_DIR}/${TARGETARCH}/${TARGETOS}/ziti-edge-tunnel /usr/local/bin/ +COPY ${DOCKER_BUILD_DIR}/docker-entrypoint.sh / +RUN chmod +x /docker-entrypoint.sh +RUN mkdir -m0777 /ziti-edge-tunnel +RUN groupadd --system --gid 2171 ziti + +RUN UNINSTALL_PKGS="shadow-utils" \ + && microdnf -y remove ${UNINSTALL_PKGS} + +ENTRYPOINT [ "/docker-entrypoint.sh" ] +CMD [ "run" ] diff --git a/docker/ziti-host-deployment.yaml b/docker/ziti-host-deployment.yaml deleted file mode 100644 index 8051c1df..00000000 --- a/docker/ziti-host-deployment.yaml +++ /dev/null @@ -1,33 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: ziti-host - namespace: default -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: ziti-host - template: - metadata: - labels: - app.kubernetes.io/name: ziti-host - spec: - containers: - - env: - - name: ZITI_IDENTITY_BASENAME - value: ziti-host-identity - image: openziti/ziti-host - name: ziti-host - volumeMounts: - - mountPath: /ziti-edge-tunnel - name: persisted-identity - readOnly: true - volumes: - - name: persisted-identity - secret: - defaultMode: 256 - items: - - key: persisted-identity - path: ziti-host-identity.json - secretName: ziti-host-identity diff --git a/docker/Dockerfile.ziti-host b/docker/ziti-host.Dockerfile similarity index 65% rename from docker/Dockerfile.ziti-host rename to docker/ziti-host.Dockerfile index 22641e31..85995e85 100644 --- a/docker/Dockerfile.ziti-host +++ b/docker/ziti-host.Dockerfile @@ -1,6 +1,9 @@ # this builds docker.io/openziti/ziti-host -FROM docker.io/openziti/ziti-edge-tunnel +ARG ZITI_EDGE_TUNNEL_IMAGE="docker.io/openziti/ziti-edge-tunnel" +ARG ZITI_EDGE_TUNNEL_TAG="latest" +# this builds docker.io/openziti/ziti-host +FROM ${ZITI_EDGE_TUNNEL_IMAGE}:${ZITI_EDGE_TUNNEL_TAG} ### Required OpenShift Labels LABEL name="openziti/ziti-host" \ diff --git a/docker/ziti-tun-daemonset.yaml b/docker/ziti-tun-daemonset.yaml deleted file mode 100644 index 06f63a93..00000000 --- a/docker/ziti-tun-daemonset.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: apps/v1 -kind: DaemonSet -metadata: - name: ziti-run-node -spec: - selector: - matchLabels: - app: ziti-edge-tunnel - template: - metadata: - labels: - app: ziti-edge-tunnel - spec: - containers: - - image: openziti/ziti-edge-tunnel - name: ziti-edge-tunnel - env: - - name: ZITI_IDENTITY_BASENAME - value: ziti-identity - volumeMounts: - - name: ziti-enrolled-identity - mountPath: /ziti-edge-tunnel - readOnly: true - - name: system-bus-socket - mountPath: /var/run/dbus/system_bus_socket - securityContext: - privileged: true - args: # [] - hostNetwork: true - dnsPolicy: ClusterFirstWithHostNet - nodeSelector: - node-role.kubernetes.io/node: worker - restartPolicy: Always - volumes: - - name: ziti-enrolled-identity - secret: # kubectl create secret generic ziti-enrolled-identity --from-file=ziti-enrolled-identity=./myZitiIdentityFile.json - secretName: ziti-enrolled-identity - defaultMode: 0400 - items: - - key: ziti-enrolled-identity - path: ziti-identity.json - - name: system-bus-socket - hostPath: - path: /var/run/dbus/system_bus_socket \ No newline at end of file diff --git a/lib/ziti-tunnel-cbs/CMakeLists.txt b/lib/ziti-tunnel-cbs/CMakeLists.txt index 0b593b1e..c750561f 100644 --- a/lib/ziti-tunnel-cbs/CMakeLists.txt +++ b/lib/ziti-tunnel-cbs/CMakeLists.txt @@ -13,7 +13,9 @@ add_library(ziti-tunnel-cbs-c STATIC ziti_dns.c dns_msg.c dns_host.c - dns_host.h) + dns_host.h + ziti_tunnel_model.c +) target_compile_definitions(ziti-tunnel-cbs-c PRIVATE ZITI_LOG_MODULE="tunnel-cbs" @@ -23,8 +25,16 @@ target_include_directories(ziti-tunnel-cbs-c PUBLIC include ) +if(MINGW) + # mingw's linker is single-pass, and we get undefined symbols from libmbedtls for symbols in winsock2 without this + # doing this here this is a bit of a hack, but I couldn't figure out how to influence cmake to get this right when + # we get mbledtls from vcpkg. + SET(socket_lib ws2_32) +endif() + if(CMAKE_SYSTEM_NAME STREQUAL Windows) SET(resolve_lib dnsapi) +elseif(CMAKE_SYSTEM_NAME STREQUAL Android) else() SET(resolve_lib resolv) endif() @@ -32,7 +42,7 @@ endif() target_link_libraries(ziti-tunnel-cbs-c PUBLIC ziti PUBLIC ziti-tunnel-sdk-c - PUBLIC ${resolve_lib} + PUBLIC ${resolve_lib} ${socket_lib} ) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ diff --git a/lib/ziti-tunnel-cbs/dns_host.c b/lib/ziti-tunnel-cbs/dns_host.c index 36b539a3..287efd03 100644 --- a/lib/ziti-tunnel-cbs/dns_host.c +++ b/lib/ziti-tunnel-cbs/dns_host.c @@ -145,14 +145,50 @@ static int fmt_txt(const ns_msg *msg, const ns_rr* rr, dns_answer *ans, size_t m #else -void do_query(const dns_question *q, dns_message *resp, resolver_t *resolver) { - uint8_t resp_msg[PACKETSZ]; - int rc = res_nquery(resolver, q->name, ns_c_in, q->type, resp_msg, PACKETSZ); +static uint8_t *send_and_parse_query(const dns_question *q, int class, ns_msg *ans, resolver_t *resolver) { + int buf_sz = PACKETSZ; + uint8_t *resp_msg = NULL; + int rc; + + for (int attempt = 0; attempt < 2; attempt++) { // retry the query with a larger buffer if first attempt was truncated + resp_msg = calloc(buf_sz, sizeof(uint8_t)); + memset(ans, 0, sizeof(dns_question)); + rc = res_nquery(resolver, q->name, class, (int)q->type, resp_msg, buf_sz); + if (rc < 0) { + ZITI_LOG(DEBUG, "res_query for %s failed", q->name); + break; + } + ns_initparse(resp_msg, rc, ans); + bool trunc = ns_msg_getflag(*ans, ns_f_tc); + if (!trunc) { + break; + } else { + if (attempt == 0) { + ZITI_LOG(DEBUG, "dns response truncated, repeating query with %d byte buffer", rc); + free(resp_msg); + resp_msg = NULL; + buf_sz = rc; // try again with large enough buffer + } else { + rc = -1; + } + } + } + if (rc < 0) { + free(resp_msg); + resp_msg = NULL; + } + + return resp_msg; +} + +void do_query(const dns_question *q, dns_message *resp, resolver_t *resolver) { + ns_msg ans = {0}; + uint8_t *resp_msg = send_and_parse_query(q, ns_c_in, &ans, resolver); + if (resp_msg == NULL) { resp->status = ns_r_servfail; + return; } else { - ns_msg ans = {0}; - ns_initparse(resp_msg, rc, &ans); resp->status = ns_msg_getflag(ans, ns_f_rcode); int rr_count = ns_msg_count(ans, ns_s_an); if (rr_count > 0) { @@ -172,6 +208,7 @@ void do_query(const dns_question *q, dns_message *resp, resolver_t *resolver) { } } } + free(resp_msg); } } @@ -181,13 +218,13 @@ static int fmt_srv(const ns_msg* msg, const ns_rr* rr, dns_answer *ans, size_t m ans->weight = ntohs(*(ptr + 1)); ans->port = ntohs(*(ptr + 2)); - return ns_name_uncompress(ns_msg_base(*msg), ns_msg_end(*msg), (uint8_t *)(ptr + 3), ans->data, max); + return ns_name_uncompress(ns_msg_base(*msg), ns_msg_end(*msg), (uint8_t *)(ptr + 3), (char*)ans->data, max); } static int fmt_mx(const ns_msg * msg, const ns_rr* rr, dns_answer *ans, size_t max) { uint16_t *ptr = (uint16_t *) ns_rr_rdata(*rr); ans->priority = ntohs(*ptr); - return ns_name_uncompress(ns_msg_base(*msg), ns_msg_end(*msg), (uint8_t *)(ptr + 1), ans->data, max); + return ns_name_uncompress(ns_msg_base(*msg), ns_msg_end(*msg), (uint8_t *)(ptr + 1), (char*)ans->data, max); } static int fmt_txt(const ns_msg *msg, const ns_rr* rr, dns_answer *ans, size_t max) { @@ -197,14 +234,14 @@ static int fmt_txt(const ns_msg *msg, const ns_rr* rr, dns_answer *ans, size_t m len = (unsigned int)max; } len--; - memcpy(ans->data, ptr+1, len); - ans->data[len] = 0; + memcpy((char*)ans->data, ptr+1, len); + ((char*)ans->data)[len] = 0; return (int)len; } #endif -static ssize_t on_dns_req(ziti_connection conn, uint8_t *data, ssize_t datalen) { +static ssize_t on_dns_req(ziti_connection conn, const uint8_t *data, ssize_t datalen) { if (datalen < 0) { ziti_close(conn, on_close); return 0; @@ -224,7 +261,7 @@ static ssize_t on_dns_req(ziti_connection conn, uint8_t *data, ssize_t datalen) size_t msg_len = 0; char *json = dns_message_to_json(&msg, 0, &msg_len); - ziti_write(conn, json, msg_len, on_write, json); + ziti_write(conn, (uint8_t *)json, msg_len, on_write, json); free_dns_message(&msg); return datalen; } diff --git a/lib/ziti-tunnel-cbs/dns_host.h b/lib/ziti-tunnel-cbs/dns_host.h index 51814de3..3651af83 100644 --- a/lib/ziti-tunnel-cbs/dns_host.h +++ b/lib/ziti-tunnel-cbs/dns_host.h @@ -54,44 +54,31 @@ typedef struct __res_state resolver_t; #include #define DNS_Q_MODEL(XX, ...) \ -XX(name, string, none, name, __VA_ARGS__) \ -XX(type, int, none, type, __VA_ARGS__) +XX(name, model_string, none, name, __VA_ARGS__) \ +XX(type, model_number, none, type, __VA_ARGS__) #define DNS_A_MODEL(XX, ...) \ DNS_Q_MODEL(XX, __VA_ARGS__) \ -XX(ttl, int, none, ttl, __VA_ARGS__) \ -XX(priority, int, none, priority, __VA_ARGS__) \ -XX(weight, int, none, weight, __VA_ARGS__) \ -XX(port, int, none, port, __VA_ARGS__) \ -XX(data, string, none, data, __VA_ARGS__) +XX(ttl, model_number, none, ttl, __VA_ARGS__) \ +XX(priority, model_number, none, priority, __VA_ARGS__) \ +XX(weight, model_number, none, weight, __VA_ARGS__) \ +XX(port, model_number , none, port, __VA_ARGS__) \ +XX(data, model_string, none, data, __VA_ARGS__) #define DNS_MSG_MODEL(XX,...) \ -XX(status, int, none, status, __VA_ARGS__) \ -XX(id, int, none, id, __VA_ARGS__) \ -XX(recursive, int, none, recursive, __VA_ARGS__) \ +XX(status, model_number, none, status, __VA_ARGS__) \ +XX(id, model_number, none, id, __VA_ARGS__) \ +XX(recursive, model_number, none, recursive, __VA_ARGS__) \ XX(question, dns_question, array, question, __VA_ARGS__) \ XX(answer, dns_answer, array, answer, __VA_ARGS__) \ -XX(comment, string, none, comment, __VA_ARGS__) +XX(comment, model_string, none, comment, __VA_ARGS__) #ifdef __cplusplus extern "C" { #endif -typedef struct dns_flags_s { - union { - uint16_t raw; - struct { - uint8_t is_response: 1; - uint8_t opcode: 4; - uint8_t aa: 1; - uint8_t tc: 1; - uint8_t rd: 1; - uint8_t ra: 1; - uint8_t z: 3; - uint8_t rcode: 4; - }; - }; -} dns_flags_t; +#define DNS_FLAG_QR(f) (((f) & 0x8000U) != 0) +#define DNS_FLAG_RD(f) (((f) & 0x0100U) != 0) DECLARE_MODEL(dns_question, DNS_Q_MODEL) diff --git a/lib/ziti-tunnel-cbs/dns_msg.c b/lib/ziti-tunnel-cbs/dns_msg.c index 4bffe65d..acace9c2 100644 --- a/lib/ziti-tunnel-cbs/dns_msg.c +++ b/lib/ziti-tunnel-cbs/dns_msg.c @@ -19,7 +19,7 @@ static int parse_dns_q(dns_question *q, const unsigned char *buf, size_t buflen) { const uint8_t *p = buf; - size_t namelen = 0; + size_t namelen = 1; // ensure there's room for a nul byte if name is empty while(*p != 0) { namelen += (*p + 1); @@ -34,7 +34,7 @@ static int parse_dns_q(dns_question *q, const unsigned char *buf, size_t buflen) q->type = type; q->name = malloc(namelen); - char *wp = q->name; + char *wp = (char*)q->name; p = buf; while(*p != 0) { if (wp != q->name) *wp++ = '.'; @@ -51,15 +51,14 @@ static int parse_dns_q(dns_question *q, const unsigned char *buf, size_t buflen) int parse_dns_req(dns_message *msg, const unsigned char* buf, size_t buflen) { msg->id = ntohs(*((uint16_t*)buf)); - dns_flags_t flags; - flags.raw = ntohs(*((uint16_t*)buf + 1)); + uint16_t flags = ntohs(*((uint16_t*)buf + 1)); - if (flags.is_response) return -1; + if (DNS_FLAG_QR(flags)) return -1; int qcount = ntohs(*((uint16_t*)buf + 2)); if (qcount != 1) return -1; - msg->recursive = flags.rd; + msg->recursive = DNS_FLAG_RD(flags); msg->question = calloc(2, sizeof(dns_question*)); msg->question[0] = calloc(1, sizeof(dns_question)); parse_dns_q(msg->question[0], buf + 12, buflen - 12); diff --git a/lib/ziti-tunnel-cbs/include/ziti/ziti_dns.h b/lib/ziti-tunnel-cbs/include/ziti/ziti_dns.h index 27dad6d2..92e2a250 100644 --- a/lib/ziti-tunnel-cbs/include/ziti/ziti_dns.h +++ b/lib/ziti-tunnel-cbs/include/ziti/ziti_dns.h @@ -19,7 +19,11 @@ #include +#include "ziti_tunnel_cbs.h" + #define DNS_NO_ERROR 0 +#define DNS_FORMERR 1 +#define DNS_SERVFAIL 2 #define DNS_NXDOMAIN 3 #define DNS_NOT_IMPL 4 #define DNS_REFUSE 5 @@ -31,7 +35,7 @@ extern "C" { int ziti_dns_setup(tunneler_context tnlr, const char *dns_addr, const char *dns_cidr); -int ziti_dns_set_upstream(uv_loop_t *l, const char *host, uint16_t port); +int ziti_dns_set_upstream(uv_loop_t *l, tunnel_upstream_dns_array upstreams); const ip_addr_t *ziti_dns_register_hostname(const ziti_address *addr, void *intercept); diff --git a/lib/ziti-tunnel-cbs/include/ziti/ziti_tunnel_cbs.h b/lib/ziti-tunnel-cbs/include/ziti/ziti_tunnel_cbs.h index d537d9ac..d8e8c429 100644 --- a/lib/ziti-tunnel-cbs/include/ziti/ziti_tunnel_cbs.h +++ b/lib/ziti-tunnel-cbs/include/ziti/ziti_tunnel_cbs.h @@ -35,29 +35,24 @@ extern "C" { XX(data, __VA_ARGS__) \ XX(resolver, __VA_ARGS__) - -enum tunnel_conn_type { - data_conn_type, - resolve_conn_type -}; - #define TUNNELER_APP_DATA_MODEL(XX, ...) \ XX(conn_type, TunnelConnectionType, none, connType, __VA_ARGS__) \ -XX(dst_protocol, string, none, dst_protocol, __VA_ARGS__)\ -XX(dst_hostname, string, none, dst_hostname, __VA_ARGS__)\ -XX(dst_ip, string, none, dst_ip, __VA_ARGS__)\ -XX(dst_port, string, none, dst_port, __VA_ARGS__)\ -XX(src_protocol, string, none, src_protocol, __VA_ARGS__)\ -XX(src_ip, string, none, src_ip, __VA_ARGS__)\ -XX(src_port, string, none, src_port, __VA_ARGS__)\ -XX(source_addr, string, none, source_addr, __VA_ARGS__) +XX(dst_protocol, model_string, none, dst_protocol, __VA_ARGS__)\ +XX(dst_hostname, model_string, none, dst_hostname, __VA_ARGS__)\ +XX(dst_ip, model_string, none, dst_ip, __VA_ARGS__)\ +XX(dst_port, model_string, none, dst_port, __VA_ARGS__)\ +XX(src_protocol, model_string, none, src_protocol, __VA_ARGS__)\ +XX(src_ip, model_string, none, src_ip, __VA_ARGS__)\ +XX(src_port, model_string, none, src_port, __VA_ARGS__)\ +XX(source_addr, model_string, none, source_addr, __VA_ARGS__) DECLARE_ENUM(TunnelConnectionType, TUNNELER_CONN_TYPE_ENUM) DECLARE_MODEL(tunneler_app_data, TUNNELER_APP_DATA_MODEL) -#define TUNNEL_COMMANDS(XX,...) \ +#define TUNNEL_COMMANDS(XX, ...) \ XX(ZitiDump, __VA_ARGS__) \ +XX(IpDump, __VA_ARGS__) \ XX(LoadIdentity, __VA_ARGS__) \ XX(ListIdentities, __VA_ARGS__) \ XX(IdentityOnOff, __VA_ARGS__) \ @@ -72,112 +67,131 @@ XX(SetLogLevel, __VA_ARGS__) \ XX(UpdateTunIpv4, __VA_ARGS__) \ XX(ServiceControl, __VA_ARGS__) \ XX(Status, __VA_ARGS__) \ +XX(RefreshIdentity, __VA_ARGS__) \ XX(RemoveIdentity, __VA_ARGS__) \ XX(StatusChange, __VA_ARGS__) \ -XX(AddIdentity, __VA_ARGS__) +XX(AddIdentity, __VA_ARGS__) \ +XX(Enroll, __VA_ARGS__) \ +XX(ExternalAuth, __VA_ARGS__) \ +XX(SetUpstreamDNS, __VA_ARGS__) DECLARE_ENUM(TunnelCommand, TUNNEL_COMMANDS) #define TUNNEL_CMD(XX, ...) \ XX(command, TunnelCommand, none, Command, __VA_ARGS__) \ -XX(data, json, none, Data, __VA_ARGS__) +XX(data, json, none, Data, __VA_ARGS__) \ +XX(show_result, model_bool, none, , __VA_ARGS__) #define TUNNEL_CMD_RES(XX, ...) \ -XX(success, bool, none, Success, __VA_ARGS__) \ -XX(error, string, none, Error, __VA_ARGS__)\ +XX(success, model_bool, none, Success, __VA_ARGS__) \ +XX(error, model_string, none, Error, __VA_ARGS__)\ XX(data, json, none, Data, __VA_ARGS__) \ -XX(code, int, none, Code, __VA_ARGS__) +XX(code, model_number, none, Code, __VA_ARGS__) #define TNL_LOAD_IDENTITY(XX, ...) \ -XX(identifier, string, none, Identifier, __VA_ARGS__)\ -XX(path, string, none, Path, __VA_ARGS__) \ -XX(apiPageSize, int, none, ApiPageSize, __VA_ARGS__) +XX(identifier, model_string, none, Identifier, __VA_ARGS__)\ +XX(path, model_string, none, Path, __VA_ARGS__) \ +XX(config, ziti_config, ptr, Config, __VA_ARGS__) \ +XX(apiPageSize, model_number, none, ApiPageSize, __VA_ARGS__) #define TNL_ON_OFF_IDENTITY(XX, ...) \ -XX(identifier, string, none, Identifier, __VA_ARGS__) \ -XX(onOff, bool, none, OnOff, __VA_ARGS__) +XX(identifier, model_string, none, Identifier, __VA_ARGS__) \ +XX(onOff, model_bool, none, OnOff, __VA_ARGS__) #define TNL_IDENTITY_INFO(XX, ...) \ -XX(name, string, none, Name, __VA_ARGS__) \ -XX(config, string, none, Config, __VA_ARGS__) \ -XX(network, string, none, Network, __VA_ARGS__) \ -XX(id, string, none, Id, __VA_ARGS__) +XX(name, model_string, none, Name, __VA_ARGS__) \ +XX(config, model_string, none, Config, __VA_ARGS__) \ +XX(network, model_string, none, Network, __VA_ARGS__) \ +XX(id, model_string, none, Id, __VA_ARGS__) #define TNL_IDENTITY_LIST(XX, ...) \ XX(identities, tunnel_identity_info, array, Identities, __VA_ARGS__) #define TNL_ZITI_DUMP(XX, ...) \ -XX(identifier, string, none, Identifier, __VA_ARGS__) \ -XX(dump_path, string, none, DumpPath, __VA_ARGS__) +XX(identifier, model_string, none, Identifier, __VA_ARGS__) \ +XX(dump_path, model_string, none, DumpPath, __VA_ARGS__) + +#define TNL_IP_DUMP(XX, ...) \ +XX(dump_path, model_string, none, DumpPath, __VA_ARGS__) -#define TNL_ENABLE_MFA(XX, ...) \ -XX(identifier, string, none, Identifier, __VA_ARGS__) +#define TNL_IDENTITY_ID(XX, ...) \ +XX(identifier, model_string, none, Identifier, __VA_ARGS__) #define TNL_MFA_ENROL_RES(XX,...) \ -XX(identifier, string, none, Identifier, __VA_ARGS__) \ -XX(is_verified, bool, none, IsVerified, __VA_ARGS__) \ -XX(provisioning_url, string, none, ProvisioningUrl, __VA_ARGS__) \ -XX(recovery_codes, string, array, RecoveryCodes, __VA_ARGS__) +XX(identifier, model_string, none, Identifier, __VA_ARGS__) \ +XX(is_verified, model_bool, none, IsVerified, __VA_ARGS__) \ +XX(provisioning_url, model_string, none, ProvisioningUrl, __VA_ARGS__) \ +XX(recovery_codes, model_string, array, RecoveryCodes, __VA_ARGS__) // MFA auth command #define TNL_SUBMIT_MFA(XX, ...) \ -XX(identifier, string, none, Identifier, __VA_ARGS__) \ -XX(code, string, none, Code, __VA_ARGS__) +XX(identifier, model_string, none, Identifier, __VA_ARGS__) \ +XX(code, model_string, none, Code, __VA_ARGS__) // MFA auth command #define TNL_VERIFY_MFA(XX, ...) \ -XX(identifier, string, none, Identifier, __VA_ARGS__) \ -XX(code, string, none, Code, __VA_ARGS__) +XX(identifier, model_string, none, Identifier, __VA_ARGS__) \ +XX(code, model_string, none, Code, __VA_ARGS__) #define TNL_REMOVE_MFA(XX, ...) \ -XX(identifier, string, none, Identifier, __VA_ARGS__) \ -XX(code, string, none, Code, __VA_ARGS__) +XX(identifier, model_string, none, Identifier, __VA_ARGS__) \ +XX(code, model_string, none, Code, __VA_ARGS__) #define TNL_GENERATE_MFA_CODES(XX, ...) \ -XX(identifier, string, none, Identifier, __VA_ARGS__) \ -XX(code, string, none, Code, __VA_ARGS__) +XX(identifier, model_string, none, Identifier, __VA_ARGS__) \ +XX(code, model_string, none, Code, __VA_ARGS__) #define TNL_MFA_RECOVERY_CODES(XX, ...) \ -XX(identifier, string, none, Identifier, __VA_ARGS__) \ -XX(recovery_codes, string, array, RecoveryCodes, __VA_ARGS__) +XX(identifier, model_string, none, Identifier, __VA_ARGS__) \ +XX(recovery_codes, model_string, array, RecoveryCodes, __VA_ARGS__) #define TNL_GET_MFA_CODES(XX, ...) \ -XX(identifier, string, none, Identifier, __VA_ARGS__) \ -XX(code, string, none, Code, __VA_ARGS__) - -#define TNL_GET_IDENTITY_METRICS(XX, ...) \ -XX(identifier, string, none, Identifier, __VA_ARGS__) +XX(identifier, model_string, none, Identifier, __VA_ARGS__) \ +XX(code, model_string, none, Code, __VA_ARGS__) #define TNL_IDENTITY_METRICS(XX, ...) \ -XX(identifier, string, none, Identifier, __VA_ARGS__) \ -XX(up, string, none, Up, __VA_ARGS__) \ -XX(down, string, none, Down, __VA_ARGS__) +XX(identifier, model_string, none, Identifier, __VA_ARGS__) \ +XX(up, model_string, none, Up, __VA_ARGS__) \ +XX(down, model_string, none, Down, __VA_ARGS__) #define TUNNEL_CMD_INLINE(XX, ...) \ -XX(identifier, string, none, Identifier, __VA_ARGS__) \ +XX(identifier, model_string, none, Identifier, __VA_ARGS__) \ XX(command, TunnelCommand, none, Command, __VA_ARGS__) -#define TNL_DELETE_IDENTITY(XX, ...) \ -XX(identifier, string, none, Identifier, __VA_ARGS__) - #define TUNNEL_SET_LOG_LEVEL(XX, ...) \ -XX(loglevel, string, none, Level, __VA_ARGS__) +XX(loglevel, model_string, none, Level, __VA_ARGS__) #define TUNNEL_TUN_IP_V4(XX, ...) \ -XX(tunIP, string, none, TunIPv4, __VA_ARGS__) \ -XX(prefixLength, int, none, TunPrefixLength, __VA_ARGS__) \ -XX(addDns, bool, none, AddDns, __VA_ARGS__) +XX(tunIP, model_string, none, TunIPv4, __VA_ARGS__) \ +XX(prefixLength, model_number, none, TunPrefixLength, __VA_ARGS__) \ +XX(addDns, model_bool, none, AddDns, __VA_ARGS__) #define TUNNEL_SERVICE_CONTROL(XX, ...) \ -XX(operation, string, none, Operation, __VA_ARGS__) +XX(operation, model_string, none, Operation, __VA_ARGS__) #define TUNNEL_STATUS_CHANGE(XX, ...) \ -XX(woken, bool, none, Woke, __VA_ARGS__) \ -XX(unlocked, bool, none, Unlocked, __VA_ARGS__) +XX(woken, model_bool, none, Woke, __VA_ARGS__) \ +XX(unlocked, model_bool, none, Unlocked, __VA_ARGS__) #define TUNNEL_ADD_IDENTITY(XX, ...) \ -XX(jwtFileName, string, none, JwtFileName, __VA_ARGS__) \ -XX(jwtContent, string, none, JwtContent, __VA_ARGS__) +XX(useKeychain, model_bool, none, UseKeychain, __VA_ARGS__) \ +XX(jwtFileName, model_string, none, JwtFileName, __VA_ARGS__) \ +XX(jwtContent, model_string, none, JwtContent, __VA_ARGS__) + +#define TUNNEL_EXT_AUTH(XX, ...) \ +XX(identifier, model_string, none, identifier, __VA_ARGS__) \ +XX(ext_auth_url, model_string, none, url, __VA_ARGS__) + +#define TUNNEL_UPSTREAM_DNS(XX, ...) \ +XX(host, model_string, none, host, __VA_ARGS__) \ +XX(port, model_number, none, port, __VA_ARGS__) + +#define TNL_ENROLL(XX, ...) \ +XX(name, model_string, none, name, __VA_ARGS__) \ +XX(jwt, model_string, none, jwt, __VA_ARGS__) \ +XX(key, model_string, none, key, __VA_ARGS__) \ +XX(cert, model_string, none, cert, __VA_ARGS__) \ +XX(use_keychain, model_bool, none, useKeychain, __VA_ARGS__) DECLARE_MODEL(tunnel_command, TUNNEL_CMD) DECLARE_MODEL(tunnel_result, TUNNEL_CMD_RES) @@ -185,8 +199,9 @@ DECLARE_MODEL(tunnel_load_identity, TNL_LOAD_IDENTITY) DECLARE_MODEL(tunnel_identity_info, TNL_IDENTITY_INFO) DECLARE_MODEL(tunnel_identity_lst, TNL_IDENTITY_LIST) DECLARE_MODEL(tunnel_ziti_dump, TNL_ZITI_DUMP) +DECLARE_MODEL(tunnel_ip_dump, TNL_IP_DUMP) DECLARE_MODEL(tunnel_on_off_identity, TNL_ON_OFF_IDENTITY) -DECLARE_MODEL(tunnel_enable_mfa, TNL_ENABLE_MFA) +DECLARE_MODEL(tunnel_identity_id, TNL_IDENTITY_ID) DECLARE_MODEL(tunnel_mfa_enrol_res, TNL_MFA_ENROL_RES) DECLARE_MODEL(tunnel_submit_mfa, TNL_SUBMIT_MFA) DECLARE_MODEL(tunnel_verify_mfa, TNL_VERIFY_MFA) @@ -194,22 +209,25 @@ DECLARE_MODEL(tunnel_remove_mfa, TNL_REMOVE_MFA) DECLARE_MODEL(tunnel_generate_mfa_codes, TNL_GENERATE_MFA_CODES) DECLARE_MODEL(tunnel_mfa_recovery_codes, TNL_MFA_RECOVERY_CODES) DECLARE_MODEL(tunnel_get_mfa_codes, TNL_GET_MFA_CODES) -DECLARE_MODEL(tunnel_get_identity_metrics, TNL_GET_IDENTITY_METRICS) DECLARE_MODEL(tunnel_identity_metrics, TNL_IDENTITY_METRICS) DECLARE_MODEL(tunnel_command_inline, TUNNEL_CMD_INLINE) DECLARE_MODEL(tunnel_set_log_level, TUNNEL_SET_LOG_LEVEL) DECLARE_MODEL(tunnel_tun_ip_v4, TUNNEL_TUN_IP_V4) DECLARE_MODEL(tunnel_service_control, TUNNEL_SERVICE_CONTROL) -DECLARE_MODEL(tunnel_delete_identity, TNL_DELETE_IDENTITY) DECLARE_MODEL(tunnel_status_change, TUNNEL_STATUS_CHANGE) DECLARE_MODEL(tunnel_add_identity, TUNNEL_ADD_IDENTITY) +DECLARE_MODEL(tunnel_upstream_dns, TUNNEL_UPSTREAM_DNS) +DECLARE_MODEL(tunnel_enroll, TNL_ENROLL) + +DECLARE_MODEL(tunnel_ext_auth, TUNNEL_EXT_AUTH) #define TUNNEL_EVENTS(XX, ...) \ XX(ContextEvent, __VA_ARGS__) \ XX(ServiceEvent, __VA_ARGS__) \ XX(MFAEvent, __VA_ARGS__) \ XX(MFAStatusEvent, __VA_ARGS__) \ -XX(APIEvent, __VA_ARGS__) +XX(APIEvent, __VA_ARGS__) \ +XX(ExtJWTEvent, __VA_ARGS__) DECLARE_ENUM(TunnelEvent, TUNNEL_EVENTS) @@ -223,36 +241,47 @@ XX(enrollment_challenge, __VA_ARGS__) DECLARE_ENUM(mfa_status, MFA_STATUS) #define BASE_EVENT_MODEL(XX, ...) \ -XX(identifier, string, none, identifier, __VA_ARGS__) \ +XX(identifier, model_string, none, identifier, __VA_ARGS__) \ XX(event_type, TunnelEvent, none, type, __VA_ARGS__) #define ZTX_EVENT_MODEL(XX, ...) \ BASE_EVENT_MODEL(XX, __VA_ARGS__) \ -XX(status, string, none, status, __VA_ARGS__) \ -XX(name, string, none, name, __VA_ARGS__) \ -XX(version, string, none, version, __VA_ARGS__) \ -XX(controller, string, none, controller, __VA_ARGS__) \ -XX(code, int, none, code, __VA_ARGS__) +XX(status, model_string, none, status, __VA_ARGS__) \ +XX(name, model_string, none, name, __VA_ARGS__) \ +XX(version, model_string, none, version, __VA_ARGS__) \ +XX(controller, model_string, none, controller, __VA_ARGS__) \ +XX(code, model_number, none, code, __VA_ARGS__) #define ZTX_SVC_EVENT_MODEL(XX, ...) \ BASE_EVENT_MODEL(XX, __VA_ARGS__) \ -XX(status, string, none, status, __VA_ARGS__) \ +XX(status, model_string, none, status, __VA_ARGS__) \ XX(added_services, ziti_service, array, added_services, __VA_ARGS__) \ XX(removed_services, ziti_service, array, removed_services, __VA_ARGS__) #define MFA_EVENT_MODEL(XX, ...) \ BASE_EVENT_MODEL(XX, __VA_ARGS__) \ -XX(provider, string, none, provider, __VA_ARGS__) \ -XX(status, string, none, status, __VA_ARGS__) \ -XX(operation, string, none, operation, __VA_ARGS__) \ +XX(provider, model_string, none, provider, __VA_ARGS__) \ +XX(status, model_string, none, status, __VA_ARGS__) \ +XX(operation, model_string, none, operation, __VA_ARGS__) \ XX(operation_type, mfa_status, none, operation_type, __VA_ARGS__ ) \ -XX(provisioning_url, string, none, provisioning_url, __VA_ARGS__) \ -XX(recovery_codes, string, array, recovery_codes, __VA_ARGS__) \ -XX(code, int, none, code, __VA_ARGS__) +XX(provisioning_url, model_string, none, provisioning_url, __VA_ARGS__) \ +XX(recovery_codes, model_string, array, recovery_codes, __VA_ARGS__) \ +XX(code, model_number, none, code, __VA_ARGS__) #define ZTX_API_EVENT_MODEL(XX, ...) \ BASE_EVENT_MODEL(XX, __VA_ARGS__) \ -XX(new_ctrl_address, string, none, new_ctrl_address, __VA_ARGS__) +XX(new_ctrl_address, model_string, none, new_ctrl_address, __VA_ARGS__) \ +XX(new_ca_bundle, model_string, none, new_ca_bundle, __VA_ARGS__) + + +#define EXT_JWT_PROVIDER(XX, ...) \ +XX(name, model_string, none, name, __VA_ARGS__) \ +XX(issuer, model_string, none, issuer, __VA_ARGS__) + +#define EXT_SIGNER_EVENT_MODEL(XX, ...) \ +BASE_EVENT_MODEL(XX, __VA_ARGS__) \ +XX(status, model_string, none, status, __VA_ARGS__) \ +XX(providers, jwt_provider, list, providers, __VA_ARGS__) DECLARE_MODEL(base_event, BASE_EVENT_MODEL) DECLARE_MODEL(ziti_ctx_event, ZTX_EVENT_MODEL) @@ -260,6 +289,9 @@ DECLARE_MODEL(mfa_event, MFA_EVENT_MODEL) DECLARE_MODEL(service_event, ZTX_SVC_EVENT_MODEL) DECLARE_MODEL(api_event, ZTX_API_EVENT_MODEL) +DECLARE_MODEL(jwt_provider, EXT_JWT_PROVIDER) +DECLARE_MODEL(ext_signer_event, EXT_SIGNER_EVENT_MODEL) + typedef struct tunneled_service_s tunneled_service_t; #define MAX_PENDING_BYTES (128 * 1024) @@ -277,7 +309,7 @@ typedef void (*event_cb)(const base_event* event); typedef void (*command_cb)(const tunnel_result *, void *ctx); typedef struct { int (*process)(const tunnel_command *cmd, command_cb cb, void *ctx); - int (*load_identity)(const char *identifier, const char *path, int api_page_size, command_cb, void *ctx); + int (*load_identity)(const char *identifier, const char *path, bool disabled, int api_page_size, command_cb cb, void *ctx); // do not use, temporary accessor ziti_context (*get_ziti)(const char *identifier); } ziti_tunnel_ctrl; @@ -309,9 +341,10 @@ void remove_intercepts(ziti_context ziti_ctx, void *tnlr_ctx); const ziti_tunnel_ctrl* ziti_tunnel_init_cmd(uv_loop_t *loop, tunneler_context, event_cb); struct add_identity_request_s { - string identifier; - string identifier_file_name; - string jwt_content; + model_string identifier; + model_string identifier_file_name; + model_string jwt_content; + bool use_keychain; void *add_id_ctx; command_cb cmd_cb; void *cmd_ctx; @@ -322,7 +355,7 @@ struct add_identity_request_s { struct ziti_instance_s { char *identifier; - ziti_options opts; + char *config_path; command_cb load_cb; void *load_ctx; @@ -334,7 +367,10 @@ struct ziti_instance_s { void ziti_set_refresh_interval(unsigned long seconds); -struct ziti_instance_s *new_ziti_instance_ex(const char *identifier); +struct ziti_instance_s *new_ziti_instance(const char *identifier); +int init_ziti_instance(struct ziti_instance_s *inst, const ziti_config *cfg, const ziti_options *opts); +/** set options for tsdk usage on a ziti_instance's ziti_context */ +int set_tnlr_options(struct ziti_instance_s *inst); void set_ziti_instance(const char *identifier, struct ziti_instance_s *inst); void remove_ziti_instance(const char *identifier); diff --git a/lib/ziti-tunnel-cbs/ziti_dns.c b/lib/ziti-tunnel-cbs/ziti_dns.c index 30061d07..bf515daf 100644 --- a/lib/ziti-tunnel-cbs/ziti_dns.c +++ b/lib/ziti-tunnel-cbs/ziti_dns.c @@ -17,13 +17,19 @@ #include #include #include -#include #include "ziti_instance.h" #include "dns_host.h" +#define MAX_UPSTREAMS 5 #define MAX_DNS_NAME 256 #define MAX_IP_LENGTH 16 +#ifndef IN6ADDR_V4MAPPED +#define IN6ADDR_V4MAPPED(v4) \ + {{{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0xff, 0xff, v4[0], v4[1], v4[2], v4[3] }}} +#endif + enum ns_q_type { NS_T_A = 1, NS_T_AAAA = 28, @@ -41,9 +47,9 @@ typedef struct ziti_dns_client_s { struct dns_req { uint16_t id; size_t req_len; - uint8_t req[512]; + uint8_t req[4096]; size_t resp_len; - uint8_t resp[512]; + uint8_t resp[4096]; dns_message msg; @@ -56,9 +62,9 @@ struct dns_req { static void* on_dns_client(const void *app_intercept_ctx, io_ctx_t *io); static int on_dns_close(void *dns_io_ctx); -static ssize_t on_dns_req(void *ziti_io_ctx, void *write_ctx, const void *q_packet, size_t len); +static ssize_t on_dns_req(const void *ziti_io_ctx, void *write_ctx, const void *q_packet, size_t len); static int query_upstream(struct dns_req *req); -static void udp_alloc(uv_handle_t *h, unsigned long reqlen, uv_buf_t *b); +static void dns_upstream_alloc(uv_handle_t *h, size_t reqlen, uv_buf_t *b); static void on_upstream_packet(uv_udp_t *h, ssize_t rc, const uv_buf_t *buf, const struct sockaddr* addr, unsigned int flags); static void complete_dns_req(struct dns_req *req); static void free_dns_req(struct dns_req *req); @@ -72,9 +78,6 @@ typedef struct dns_domain_s { } dns_domain_t; -static void free_domain(dns_domain_t *domain); - - // hostname or domain typedef struct dns_entry_s { char name[MAX_DNS_NAME]; @@ -109,7 +112,9 @@ struct ziti_dns_s { model_map requests; uv_udp_t upstream; - struct sockaddr upstream_addr; + bool is_ipv4; + int num_dns_up; + struct sockaddr_in6 upstream_addr[MAX_UPSTREAMS]; } ziti_dns; static uint32_t next_ipv4() { @@ -202,40 +207,78 @@ ZITI_LOG(ERROR, "failed [" #op "]: %d(%s)", rc, uv_strerror(rc)); \ return rc;} \ }while(0) -int ziti_dns_set_upstream(uv_loop_t *l, const char *host, uint16_t port) { - if (uv_is_active((const uv_handle_t *) &ziti_dns.upstream)) { - uv_udp_recv_stop(&ziti_dns.upstream); - CHECK_UV(uv_udp_connect(&ziti_dns.upstream, NULL)); - } else { +int ziti_dns_set_upstream(uv_loop_t *l, tunnel_upstream_dns_array upstreams) { + if (!uv_is_active((const uv_handle_t *) &ziti_dns.upstream)) { CHECK_UV(uv_udp_init(l, &ziti_dns.upstream)); + int r = uv_udp_bind(&ziti_dns.upstream, + (const struct sockaddr *) &(struct sockaddr_in6){ + .sin6_family = AF_INET6, + .sin6_addr = in6addr_any, + }, 0); + if (r != 0) { + ZITI_LOG(WARN, "failed to bind upstream socket to IPv6 address: %s", uv_strerror(r)); + r = uv_udp_bind(&ziti_dns.upstream, + (const struct sockaddr *) &(struct sockaddr_in){ + .sin_family = AF_INET, + .sin_addr = INADDR_ANY, + }, 0); + if (r != 0) { + ZITI_LOG(WARN, "failed to bind upstream socket to IPv4 address: %s", uv_strerror(r)); + return r; + } + ziti_dns.is_ipv4 = true; + } + CHECK_UV(uv_udp_recv_start(&ziti_dns.upstream, dns_upstream_alloc, on_upstream_packet)); uv_unref((uv_handle_t *) &ziti_dns.upstream); } - if (port == 0) port = 53; + union { + struct in_addr addr; + uint8_t a[4]; + } ipv4; - if (uv_inet_pton(AF_INET6, host, &((struct sockaddr_in6*)&ziti_dns.upstream_addr)->sin6_addr) == 0) { - ziti_dns.upstream_addr.sa_family = AF_INET6; - ((struct sockaddr_in6*)&ziti_dns.upstream_addr)->sin6_port = htons(port); - } else if (uv_inet_pton(AF_INET, host, &((struct sockaddr_in*)&ziti_dns.upstream_addr)->sin_addr) == 0) { - ziti_dns.upstream_addr.sa_family = AF_INET; - ((struct sockaddr_in*)&ziti_dns.upstream_addr)->sin_port = htons(port); - } else { - ZITI_LOG(WARN, "upstream address[%s] is not IP format", host); - char port_str[6]; - snprintf(port_str, sizeof(port_str), "%hu", port); - uv_getaddrinfo_t req = {0}; - CHECK_UV(uv_getaddrinfo(l, &req, NULL, host, port_str, NULL)); - memcpy(&ziti_dns.upstream_addr, req.addrinfo->ai_addr, sizeof(ziti_dns.upstream_addr)); - } - CHECK_UV(uv_udp_recv_start(&ziti_dns.upstream, udp_alloc, on_upstream_packet)); - CHECK_UV(uv_udp_connect(&ziti_dns.upstream, &ziti_dns.upstream_addr)); - ZITI_LOG(INFO, "DNS upstream is set to %s:%hu", host, port); + int idx = 0; + for (int i = 0; upstreams[i] != NULL && idx < MAX_UPSTREAMS; i++) { + const tunnel_upstream_dns *dns = upstreams[i]; + int port = dns->port != 0 ? (int)dns->port : 53; + + if (ziti_dns.is_ipv4) { + if (uv_inet_pton(AF_INET, dns->host, &ipv4) == 0) { + ((struct sockaddr_in *) &ziti_dns.upstream_addr[idx])->sin_family = AF_INET; + ((struct sockaddr_in *) &ziti_dns.upstream_addr[idx])->sin_addr = ipv4.addr; + ((struct sockaddr_in *) &ziti_dns.upstream_addr[idx])->sin_port = htons(port); + idx++; + } else { + ZITI_LOG(WARN, "cannot set non-IPv4 upstream on IPv4 only socket"); + } + } else { + // set IPv6 upstream address, mapping IPv4 target to IPv6 space (if needed) + ziti_dns.upstream_addr[idx].sin6_family = AF_INET6; + ziti_dns.upstream_addr[idx].sin6_port = htons(port); + if (uv_inet_pton(AF_INET6, dns->host, &ziti_dns.upstream_addr[idx].sin6_addr) != 0) { + if (uv_inet_pton(AF_INET, dns->host, &ipv4) == 0) { + ziti_dns.upstream_addr[idx].sin6_addr = (struct in6_addr) IN6ADDR_V4MAPPED(ipv4.a); + } else { + ZITI_LOG(WARN, "upstream address[%s] is not IP format", dns->host); + char port_str[6]; + snprintf(port_str, sizeof(port_str), "%hu", port); + uv_getaddrinfo_t req = {0}; + if(uv_getaddrinfo(l, &req, NULL, dns->host, port_str, NULL) == 0) { + memcpy(&ziti_dns.upstream_addr[idx], req.addrinfo->ai_addr, req.addrinfo->ai_addrlen); + } + } + } + idx++; + } + ZITI_LOG(INFO, "DNS upstream[%d] is set to %s:%hu", idx, dns->host, port); + } + ziti_dns.num_dns_up = idx; return 0; } void* on_dns_client(const void *app_intercept_ctx, io_ctx_t *io) { - ZITI_LOG(DEBUG, "new DNS client"); + ZITI_LOG(TRACE, "new DNS client"); ziti_dns_client_t *clt = calloc(1, sizeof(ziti_dns_client_t)); io->ziti_io = clt; clt->io_ctx = io; @@ -244,10 +287,20 @@ void* on_dns_client(const void *app_intercept_ctx, io_ctx_t *io) { return clt; } +static void remove_dns_req(void *p) { + struct dns_req *req = p; + if (req) { + model_map_remove_key(&ziti_dns.requests, &req->id, sizeof(req->id)); + free_dns_req(req); + } +} + int on_dns_close(void *dns_io_ctx) { ZITI_LOG(TRACE, "DNS client close"); ziti_dns_client_t *clt = dns_io_ctx; - model_map_clear(&clt->active_reqs, NULL); + // we may be here due to udp timeout, and reqs may have been sent to upstream. + // remove reqs from ziti_dns to prevent completion (with invalid io_ctx) if upstream should respond after udp timeout. + model_map_clear(&clt->active_reqs, remove_dns_req); ziti_tunneler_close(clt->io_ctx->tnlr_io); free(clt->io_ctx); free(dns_io_ctx); @@ -340,7 +393,9 @@ static dns_entry_t *ziti_dns_lookup(const char *hostname) { if (domain && model_map_size(&domain->intercepts) > 0) { ZITI_LOG(DEBUG, "matching domain[%s] found for %s", domain->name, hostname); entry = new_ipv4_entry(clean); - entry->domain = domain; + if (entry) { + entry->domain = domain; + } } } @@ -369,22 +424,24 @@ void ziti_dns_deregister_intercept(void *intercept) { dns_entry_t *e = model_map_it_value(it); model_map_remove_key(&e->intercepts, &intercept, sizeof(intercept)); if (model_map_size(&e->intercepts) == 0 && (e->domain == NULL || model_map_size(&e->domain->intercepts) == 0)) { - model_map_remove(&ziti_dns.hostnames, e->name); + it = model_map_it_remove(it); model_map_removel(&ziti_dns.ip_addresses, ip_2_ip4(&e->addr)->addr); ZITI_LOG(DEBUG, "%zu active hostnames mapped to %zu IPs", model_map_size(&ziti_dns.hostnames), model_map_size(&ziti_dns.ip_addresses)); ZITI_LOG(INFO, "DNS mapping %s -> %s is now inactive", e->name, e->ip); + } else { + it = model_map_it_next(it); } - it = model_map_it_next(it); } it = model_map_iterator(&ziti_dns.domains); while (it != NULL) { dns_domain_t *domain = model_map_it_value(it); if (model_map_size(&domain->intercepts) == 0) { - model_map_remove(&ziti_dns.domains, domain->name); + it = model_map_it_remove(it); ZITI_LOG(INFO, "wildcard domain[*%s] is now inactive", domain->name); + } else { + it = model_map_it_next(it); } - it = model_map_it_next(it); } } @@ -426,7 +483,7 @@ const ip_addr_t *ziti_dns_register_hostname(const ziti_address *addr, void *inte } } -static const char DNS_OPT[] = { 0x0, 0x0, 0x29, 0x02, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; +static const char DNS_OPT[] = { 0x0, 0x0, 0x29, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; #define DNS_HEADER_LEN 12 #define DNS_ID(p) ((uint8_t)(p)[0] << 8 | (uint8_t)(p)[1]) @@ -436,6 +493,7 @@ static const char DNS_OPT[] = { 0x0, 0x0, 0x29, 0x02, 0x0, 0x0, 0x0, 0x0, 0x0, 0 #define DNS_RD(p) ((p)[2] & 0x1) #define DNS_SET_RA(p) ((p)[3] = (p)[3] | 0x80) +#define DNS_SET_TC(p) ((p)[2] = (p)[2] | 0x2) #define DNS_SET_CODE(p,c) ((p)[3] = (p)[3] | ((c) & 0xf)) #define DNS_SET_ANS(p) ((p)[2] = (p)[2] | 0x80) #define DNS_SET_ARS(p,n) do{ (p)[6] = (n) >> 8; (p)[7] = (n) & 0xff; } while(0) @@ -487,17 +545,26 @@ static void format_resp(struct dns_req *req) { memcpy(req->resp + DNS_HEADER_LEN, req->req + DNS_HEADER_LEN, query_section_len); uint8_t *rp = req->resp + DNS_HEADER_LEN + query_section_len; + uint8_t *resp_end = req->resp + sizeof(req->resp); + bool truncated = false; if (req->msg.status == DNS_NO_ERROR && req->msg.answer != NULL) { int ans_count = 0; for (int i = 0; req->msg.answer[i] != NULL; i++) { ans_count++; dns_answer *a = req->msg.answer[i]; + + if (resp_end - rp < 10) { // 2 bytes for name ref, 2 for type, 2 for class, and 4 for ttl + truncated = true; + goto done; + } + // name ref *rp++ = 0xc0; *rp++ = 0x0c; - ZITI_LOG(INFO, "found record[%s] for query[%d:%s]", a->data, req->msg.question[0]->type, req->msg.question[0]->name); + ZITI_LOG(INFO, "found record[%s] for query[%d:%s]", a->data, + (int)req->msg.question[0]->type, req->msg.question[0]->name); SET_U16(rp, a->type); SET_U16(rp, 1); // class IN @@ -505,6 +572,10 @@ static void format_resp(struct dns_req *req) { switch (a->type) { case NS_T_A: { + if (resp_end - rp < (2 + sizeof(req->addr.s_addr))) { + truncated = true; + goto done; + } SET_U16(rp, sizeof(req->addr.s_addr)); memcpy(rp, &req->addr.s_addr, sizeof(req->addr.s_addr)); rp += sizeof(req->addr.s_addr); @@ -514,6 +585,10 @@ static void format_resp(struct dns_req *req) { case NS_T_TXT: { uint16_t txtlen = strlen(a->data); uint16_t datalen = 1 + txtlen; + if (resp_end - rp < (3 + txtlen)) { + truncated = true; + goto done; + } SET_U16(rp, datalen); SET_U8(rp, txtlen); memcpy(rp, a->data, txtlen); @@ -523,8 +598,11 @@ static void format_resp(struct dns_req *req) { case NS_T_MX: { uint8_t *hold = rp; rp += 2; -// uint16_t datalen = strlen(a->data) + 1 + 2; -// SET_U16(rp, datalen); + uint16_t datalen_est = strlen(a->data) + 1; + if (resp_end - hold < (4 + datalen_est)) { + truncated = true; + goto done; + } SET_U16(rp, a->priority); rp = format_name(rp, a->data); uint16_t datalen = rp - hold - 2; @@ -534,6 +612,11 @@ static void format_resp(struct dns_req *req) { case NS_T_SRV: { uint8_t *hold = rp; rp += 2; + uint16_t datalen_est = strlen(a->data) + 1; + if (resp_end - hold < (8 + datalen_est)) { + truncated = true; + goto done; + } SET_U16(rp, a->priority); SET_U16(rp, a->weight); SET_U16(rp, a->port); @@ -543,15 +626,22 @@ static void format_resp(struct dns_req *req) { break; } default: - ZITI_LOG(WARN, "unhandled response type[%d]", a->type); + ZITI_LOG(WARN, "unhandled response type[%d]", (int)a->type); } } + done: + if (truncated) { + ZITI_LOG(DEBUG, "dns response truncated"); + DNS_SET_TC(req->resp); + } DNS_SET_ARS(req->resp, ans_count); } DNS_SET_AARS(req->resp, 1); - memcpy(rp, DNS_OPT, sizeof(DNS_OPT)); - rp += sizeof(DNS_OPT); + if (resp_end - rp > 11) { + memcpy(rp, DNS_OPT, sizeof(DNS_OPT)); + rp += sizeof(DNS_OPT); + } req->resp_len = rp - req->resp; } @@ -583,25 +673,31 @@ static void process_host_req(struct dns_req *req) { } } +static void proxy_domain_close_cb(ziti_connection c) { + dns_domain_t *domain = ziti_conn_data(c); + if (domain) { + domain->resolv_proxy = NULL; + } +} static void on_proxy_connect(ziti_connection conn, int status) { dns_domain_t *domain = ziti_conn_data(conn); if (status == ZITI_OK) { ZITI_LOG(INFO, "proxy resolve connection established for domain[%s]", domain->name); + domain->resolv_proxy = conn; } else { ZITI_LOG(ERROR, "failed to establish proxy resolve connection for domain[%s]", domain->name); - domain->resolv_proxy = NULL; - ziti_close(conn, NULL); + ziti_close(conn, proxy_domain_close_cb); } } -static ssize_t on_proxy_data(ziti_connection conn, uint8_t* data, ssize_t status) { +static ssize_t on_proxy_data(ziti_connection conn, const uint8_t* data, ssize_t status) { if (status >= 0) { - ZITI_LOG(INFO, "proxy resolve: %.*s", (int)status, data); + ZITI_LOG(DEBUG, "proxy resolve: %.*s", (int)status, data); dns_message msg = {0}; - int rc = parse_dns_message(&msg, data, status); + int rc = parse_dns_message(&msg, (const char *) data, status); if (rc < 0) { - + // the original DNS client's request won't be completed because we can't get the msg ID. return rc; } uint16_t id = msg.id; @@ -615,36 +711,81 @@ static ssize_t on_proxy_data(ziti_connection conn, uint8_t* data, ssize_t status free_dns_message(&msg); } else { ZITI_LOG(ERROR, "proxy resolve connection failed: %d(%s)", (int)status, ziti_errorstr(status)); - - dns_domain_t *domain = ziti_conn_data(conn); - domain->resolv_proxy = NULL; - ziti_close(conn, NULL); + ziti_close(conn, proxy_domain_close_cb); } return status; } -static void on_proxy_write(ziti_connection conn, ssize_t status, void *ctx) { - ZITI_LOG(INFO, "proxy resolve write: %d", (int)status); - free(ctx); +struct proxy_dns_req_wr_s { + struct dns_req *req; + char *json; +}; + +static void free_proxy_dns_wr(struct proxy_dns_req_wr_s *wr) { + if (wr->json) { + free(wr->json); + wr->json = NULL; + } + free(wr); +} + +static void on_proxy_write(ziti_connection conn, ssize_t len, void *ctx) { + ZITI_LOG(DEBUG, "proxy resolve write: %d", (int)len); + if (ctx) { + struct proxy_dns_req_wr_s *wr = ctx; + if (len < 0) { + ZITI_LOG(WARN, "proxy resolve write failed: %s/%zd", ziti_errorstr(len), len); + wr->req->msg.status = DNS_SERVFAIL; + format_resp(wr->req); + complete_dns_req(wr->req); + ziti_close(conn, proxy_domain_close_cb); + } + free_proxy_dns_wr(wr); + } } static void proxy_domain_req(struct dns_req *req, dns_domain_t *domain) { if (domain->resolv_proxy == NULL) { + // initiate connection to hosting endpoint for this domain model_map_iter it = model_map_iterator(&domain->intercepts); void *intercept = model_map_it_value(it); - domain->resolv_proxy = intercept_resolve_connect(intercept, domain, on_proxy_connect, on_proxy_data); } + dns_question *q = req->msg.question[0]; + if (domain->resolv_proxy == NULL) { + req->msg.status = DNS_SERVFAIL; + } else if (q->type == NS_T_MX || q->type == NS_T_SRV || q->type == NS_T_TXT) { + size_t jsonlen; + struct proxy_dns_req_wr_s *wr = calloc(1, sizeof(struct proxy_dns_req_wr_s)); + wr->req = req; + wr->json = dns_message_to_json(&req->msg, MODEL_JSON_COMPACT, &jsonlen); + if (wr->json) { + ZITI_LOG(DEBUG, "writing proxy resolve req[%04x]: %s", req->id, wr->json); + + // intercept_resolve_connect above can quick-fail if context does not have a valid API session + // in that case resolve_proxy connection will be in Closed state and write will fail. + // ziti_write will queue the message if the connection state is Connecting (as it will be the first time through) + int rc = ziti_write(domain->resolv_proxy, (uint8_t *) wr->json, jsonlen, on_proxy_write, wr); + if (rc == ZITI_OK) { + // completion with client will happen in on_proxy_write if write fails, or on_proxy_data when response arrives + return; + } + ZITI_LOG(WARN, "failed to write proxy resolve request[%04x]: %s", req->id, ziti_errorstr(rc)); + ziti_close(domain->resolv_proxy, proxy_domain_close_cb); + } else { + req->msg.status = DNS_FORMERR; + } + free_proxy_dns_wr(wr); + } else { + req->msg.status = DNS_NOT_IMPL; + } - size_t jsonlen; - char *json = dns_message_to_json(&req->msg, 0, &jsonlen); - ZITI_LOG(INFO, "writing proxy resolve [%s]", json); - ziti_write(domain->resolv_proxy, json, jsonlen, on_proxy_write, json); + format_resp(req); + complete_dns_req(req); } - -ssize_t on_dns_req(void *ziti_io_ctx, void *write_ctx, const void *q_packet, size_t q_len) { - ziti_dns_client_t *clt = ziti_io_ctx; +ssize_t on_dns_req(const void *ziti_io_ctx, void *write_ctx, const void *q_packet, size_t q_len) { + ziti_dns_client_t *clt = (ziti_dns_client_t *)ziti_io_ctx; const uint8_t *dns_packet = q_packet; size_t dns_packet_len = q_len; @@ -658,7 +799,7 @@ ssize_t on_dns_req(void *ziti_io_ctx, void *write_ctx, const void *q_packet, siz } req = calloc(1, sizeof(struct dns_req)); - req->clt = ziti_io_ctx; + req->clt = clt; req->req_len = q_len; memcpy(req->req, q_packet, q_len); @@ -674,7 +815,7 @@ ssize_t on_dns_req(void *ziti_io_ctx, void *write_ctx, const void *q_packet, siz ZITI_LOG(TRACE, "received DNS query q_len=%zd id[%04x] recursive[%s] type[%d] name[%s]", q_len, req->id, req->msg.recursive ? "true" : "false", - req->msg.question[0]->type, + (int)req->msg.question[0]->type, req->msg.question[0]->name); model_map_set_key(&req->clt->active_reqs, &req->id, sizeof(req->id), req); @@ -684,7 +825,7 @@ ssize_t on_dns_req(void *ziti_io_ctx, void *write_ctx, const void *q_packet, siz dns_question *q = req->msg.question[0]; if (q->type == NS_T_A || q->type == NS_T_AAAA) { - process_host_req(req); + process_host_req(req); // will send upstream if no local answer and req is recursive } else { // find domain requires normalized name char reqname[MAX_DNS_NAME]; @@ -706,54 +847,37 @@ ssize_t on_dns_req(void *ziti_io_ctx, void *write_ctx, const void *q_packet, siz return (ssize_t)q_len; } -static void on_upstream_send(uv_udp_send_t *sr, int rc) { - struct dns_req *req = sr->data; - if (rc < 0) { - ZITI_LOG(WARN, "failed to query[%04x] upstream DNS server: %d(%s)", req->id, rc, uv_strerror(rc)); - } - free(sr); -} - int query_upstream(struct dns_req *req) { bool avail = uv_is_active((const uv_handle_t *) &ziti_dns.upstream); - int rc = -1; - uv_udp_send_t *sr = NULL; - - if (avail) { - sr = calloc(1, sizeof(uv_udp_send_t)); - sr->data = req; + bool success = false; + if (avail && req->msg.recursive) { uv_buf_t buf = uv_buf_init((char *) req->req, req->req_len); - if ((rc = uv_udp_send(sr, &ziti_dns.upstream, &buf, 1, NULL, on_upstream_send)) != 0) { - ZITI_LOG(WARN, "failed to query[%04x] upstream DNS server: %d(%s)", req->id, rc, uv_strerror(rc)); - uv_udp_connect(&ziti_dns.upstream, NULL); - rc = uv_udp_connect(&ziti_dns.upstream, &ziti_dns.upstream_addr); - if (rc == 0) { - ZITI_LOG(INFO, "dns upstream re-connected successfully"); - rc = uv_udp_send(sr, &ziti_dns.upstream, &buf, 1, NULL, on_upstream_send); - if (rc != 0) { - ZITI_LOG(WARN, "failed again to query[%04x] upstream DNS server: %d(%s)", req->id, rc, uv_strerror(rc)); - } + + for (int i = 0; i < ziti_dns.num_dns_up; i++) { + int rc = uv_udp_try_send(&ziti_dns.upstream, &buf, 1, + (struct sockaddr *) &ziti_dns.upstream_addr[i]); + if (rc > 0) { + success = true; } else { - ZITI_LOG(WARN, "failed to reconnect upstream: %d/%s", rc, uv_strerror(rc)); + ZITI_LOG(WARN, "failed to query[%04x] upstream DNS server[%d]: %d(%s)", + req->id, i, rc, uv_strerror(rc)); } } } - if (rc != 0 && sr != NULL) free(sr); - return rc == 0 ? DNS_NO_ERROR : DNS_REFUSE; + return success ? DNS_NO_ERROR : DNS_REFUSE; } -static void udp_alloc(uv_handle_t *h, unsigned long reqlen, uv_buf_t *b) { - b->base = malloc(1024); - b->len = 1024; +static void dns_upstream_alloc(uv_handle_t *h, size_t reqlen, uv_buf_t *b) { + static char dns_buf[1024]; + b->base = dns_buf; + b->len = sizeof(dns_buf); } static void on_upstream_packet(uv_udp_t *h, ssize_t rc, const uv_buf_t *buf, const struct sockaddr* addr, unsigned int flags) { if (rc > 0) { uint16_t id = DNS_ID(buf->base); struct dns_req *req = model_map_get_key(&ziti_dns.requests, &id, sizeof(id)); - if (req == NULL) { - ZITI_LOG(WARN, "got response for unknown query[%04x] (rc=%zd)", id, rc); - } else { + if (req != NULL) { ZITI_LOG(TRACE, "upstream sent response to query[%04x] (rc=%zd)", id, rc); if (rc <= sizeof(req->resp)) { req->resp_len = rc; @@ -764,8 +888,8 @@ static void on_upstream_packet(uv_udp_t *h, ssize_t rc, const uv_buf_t *buf, con complete_dns_req(req); } } - free(buf->base); } + static void free_dns_req(struct dns_req *req) { free_dns_message(&req->msg); free(req); @@ -784,10 +908,4 @@ static void complete_dns_req(struct dns_req *req) { ZITI_LOG(WARN, "query[%04x] is stale", req->id); } free_dns_req(req); -} - -static void free_domain(dns_domain_t *domain) { -// model_map_clear(&domain->resolv_cache, NULL); -// ziti_close(domain->resolv_proxy, NULL); - free(domain); } \ No newline at end of file diff --git a/lib/ziti-tunnel-cbs/ziti_hosting.c b/lib/ziti-tunnel-cbs/ziti_hosting.c index 798d3bfa..f0014c34 100644 --- a/lib/ziti-tunnel-cbs/ziti_hosting.c +++ b/lib/ziti-tunnel-cbs/ziti_hosting.c @@ -28,6 +28,7 @@ #include #include #include "ziti_hosting.h" +#include "tlsuv/tlsuv.h" #if _WIN32 #ifndef strcasecmp @@ -35,26 +36,41 @@ #endif #endif +#define KEEPALIVE_DELAY 60 + /********** hosting **********/ static void on_bridge_close(uv_handle_t *handle); struct hosted_io_ctx_s { struct hosted_service_ctx_s *service; ziti_connection client; - char server_dial_str[64]; + tunneler_app_data *app_data; + char client_identity[80]; + const char *computed_dst_protocol; + const char *computed_dst_ip_or_hn; + const char *computed_dst_port; + char resolved_dst[80]; union { uv_tcp_t tcp; uv_udp_t udp; } server; }; +static void hosted_io_context_free(hosted_io_context io) { + if (io) { + if (io->app_data) { + free_tunneler_app_data_ptr(io->app_data); + } + free(io); + } +} static void ziti_conn_close_cb(ziti_connection zc) { struct hosted_io_ctx_s *io_ctx = ziti_conn_data(zc); if (io_ctx) { - ZITI_LOG(TRACE, "hosted_service[%s] client[%s] ziti_conn[%p] closed", - io_ctx->service->service_name, ziti_conn_source_identity(zc), zc); - free(io_ctx); + ZITI_LOG(TRACE, "hosted_service[%s] client[%s] ziti_conn[%p] io[%p] closed", + io_ctx->service->service_name, io_ctx->client_identity, zc, io_ctx); + hosted_io_context_free(io_ctx); ziti_conn_set_data(zc, NULL); } else { ZITI_LOG(TRACE, "ziti_conn[%p] is closed", zc); @@ -115,7 +131,7 @@ static void hosted_server_close_cb(uv_handle_t *handle) { if (io_ctx->client) { ziti_close(io_ctx->client, ziti_conn_close_cb); ZITI_LOG(TRACE, "hosted_service[%s] client[%s] server_conn[%p] closed", - io_ctx->service->service_name, ziti_conn_source_identity(io_ctx->client), handle); + io_ctx->service->service_name, io_ctx->client_identity, handle); } else { ZITI_LOG(TRACE, "server_conn[%p] closed", handle); handle->data = NULL; @@ -158,29 +174,66 @@ void *local_addr(uv_handle_t *h, struct sockaddr *name, int *len) { /** called by ziti sdk when a client connection is established (or fails) */ static void on_hosted_client_connect_complete(ziti_connection clt, int err) { struct hosted_io_ctx_s *io_ctx = ziti_conn_data(clt); + + if (io_ctx == NULL) { + ZITI_LOG(WARN, "missing io_ctx"); + ziti_close(clt, ziti_conn_close_cb); + return; + } + if (err == ZITI_OK) { - uv_handle_t *server = (uv_handle_t *) &io_ctx->server; + int rc; + uv_handle_t *server = (uv_handle_t *) &io_ctx->server.tcp; + uv_os_fd_t fd; + if ((rc = uv_fileno(server, &fd)) != 0) { + ZITI_LOG(ERROR, "failed to bridge client[%s] with hosted_service[%s] fd[%d]: %s", + io_ctx->client_identity, io_ctx->service->service_name, + fd, uv_strerror(rc)); + hosted_server_close(io_ctx); + return; + } + struct sockaddr_storage name_storage; struct sockaddr *name = (struct sockaddr *) &name_storage; int len = sizeof(name_storage); local_addr(server, name, &len); uv_getnameinfo_t req = {0}; uv_getnameinfo(io_ctx->service->loop, &req, NULL, name, NI_NUMERICHOST|NI_NUMERICSERV); - uv_os_fd_t fd; - int e = uv_fileno((uv_handle_t *) &io_ctx->server, &fd); ZITI_LOG(DEBUG, "hosted_service[%s] client[%s] local_addr[%s:%s] fd[%d] server[%s] connected %d", io_ctx->service->service_name, - ziti_conn_source_identity(clt), req.host, req.service, fd, io_ctx->server_dial_str, len); - int rc = ziti_conn_bridge(clt, (uv_handle_t *) &io_ctx->server, on_bridge_close); + io_ctx->client_identity, req.host, req.service, fd, io_ctx->resolved_dst, len); + rc = ziti_conn_bridge(clt, server, on_bridge_close); if (rc != 0) { - ZITI_LOG(ERROR, "failed to bridge client[%s] with hosted_service[%s]", ziti_conn_source_identity(clt), io_ctx->service->service_name); + ZITI_LOG(ERROR, "failed to bridge client[%s] with hosted_service[%s] laddr[%s:%s] fd[%d]: %s", + io_ctx->client_identity, io_ctx->service->service_name, + req.host, req.service, fd, uv_strerror(rc)); hosted_server_close(io_ctx); } } else { ZITI_LOG(ERROR, "hosted_service[%s] client[%s] failed to connect: %s", io_ctx->service->service_name, - ziti_conn_source_identity(clt), ziti_errorstr(err)); + io_ctx->client_identity, ziti_errorstr(err)); + hosted_server_close(io_ctx); } } + +static void complete_hosted_tcp_connection(hosted_io_context io_ctx) { + ZITI_LOG(DEBUG, "hosted_service[%s], client[%s]: connected to server %s", io_ctx->service->service_name, + io_ctx->client_identity, io_ctx->resolved_dst); + + uv_tcp_t *tcp = &io_ctx->server.tcp; + + if (uv_tcp_keepalive(tcp, 1, KEEPALIVE_DELAY) != 0) { + ZITI_LOG(WARN, "hosted_service[%s], client[%s]: failed to set TCP keepalive", + io_ctx->service->service_name, io_ctx->client_identity); + } + if (uv_tcp_nodelay(tcp, 1) != 0) { + ZITI_LOG(WARN, "hosted_service[%s], client[%s]: failed to set TCP nodelay", + io_ctx->service->service_name, io_ctx->client_identity); + } + + ziti_accept(io_ctx->client, on_hosted_client_connect_complete, NULL); +} + /** * called by libuv when a connection is established (or failed) with a TCP server * @@ -190,6 +243,8 @@ static void on_hosted_client_connect_complete(ziti_connection clt, int err) { static void on_hosted_tcp_server_connect_complete(uv_connect_t *c, int status) { if (c == NULL || c->handle == NULL || c->handle->data == NULL) { ZITI_LOG(ERROR, "null handle or io_ctx"); + if (c) free(c); + return; } struct hosted_io_ctx_s *io_ctx = c->handle->data; if (io_ctx->client == NULL) { @@ -201,24 +256,37 @@ static void on_hosted_tcp_server_connect_complete(uv_connect_t *c, int status) { if (status < 0) { ZITI_LOG(ERROR, "hosted_service[%s], client[%s]: connect to %s failed: %s", io_ctx->service->service_name, - ziti_conn_source_identity(io_ctx->client), io_ctx->server_dial_str, uv_strerror(status)); + io_ctx->client_identity, io_ctx->resolved_dst, uv_strerror(status)); hosted_server_close(io_ctx); free(c); return; } - ZITI_LOG(DEBUG, "hosted_service[%s], client[%s]: connected to server %s", io_ctx->service->service_name, - ziti_conn_source_identity(io_ctx->client), io_ctx->server_dial_str); - ziti_accept(io_ctx->client, on_hosted_client_connect_complete, NULL); + complete_hosted_tcp_connection(io_ctx); free(c); } -struct addrinfo_params_s { - const char * address; - const char * port; - char _port_str[12]; // buffer used when config type uses int for port - struct addrinfo hints; - char err[128]; -}; +/** + * called by tlsuv when a proxy connection to a hosted tcp server is established (or failed) + */ +static void on_proxy_connect(uv_os_sock_t sock, int status, void *ctx) { + hosted_io_context io = ctx; + + if (status != 0) { + ZITI_LOG(ERROR, "proxy connect failed: %s (e=%d)", uv_strerror(status), status); + hosted_server_close(io); + return; + } + + int uv_err = uv_tcp_open(&io->server.tcp, sock); + if (uv_err != 0) { + ZITI_LOG(ERROR, "uv_tcp_open failed: %s (e=%d)", uv_strerror(uv_err), uv_err); + hosted_server_close(io); + return; + } + + complete_hosted_tcp_connection(io); +} + static int get_protocol_id(const char *protocol) { if (strcasecmp(protocol, "tcp") == 0) { @@ -244,6 +312,7 @@ static bool allowed_hostname_match(const char *hostname, const allowed_hostnames struct allowed_hostname_s *entry; LIST_FOREACH(entry, hostnames, _next) { if (entry->domain_name[0] == '*') { + if (entry->domain_name[1] == '\0') return true; for (char *dot = strchr(hostname, '.'); dot != NULL; dot = strchr(dot + 1, '.')) { if (strcmp(dot, entry->domain_name + 1) == 0) return true; } @@ -254,322 +323,420 @@ static bool allowed_hostname_match(const char *hostname, const allowed_hostnames return false; } -static bool addrinfo_from_host_ctx(struct addrinfo_params_s *dial_params, const host_ctx_t *host_ctx, tunneler_app_data *app_data) { - const char *dial_protocol_str = NULL; - - if (host_ctx->forward_protocol) { - dial_protocol_str = app_data->dst_protocol; - if (dial_protocol_str == NULL | dial_protocol_str[0] == '\0') { - snprintf(dial_params->err, sizeof(dial_params->err), - "hosted_service[%s] config specifies 'forwardProtocol', but client didn't send %s", - host_ctx->service_name, DST_PROTO_KEY); - return false; +static const char *compute_dst_protocol(const host_ctx_t *service, const tunneler_app_data *app_data, + int *protocol_number, char *err, size_t err_sz) { + const char *dst_proto; + if (service->forward_protocol) { + if (app_data == NULL || app_data->dst_protocol == NULL || app_data->dst_protocol[0] == '\0') { + snprintf(err, err_sz, "config specifies 'forwardProtocol', but client didn't send %s in app_data", + DST_PROTO_KEY); + return NULL; } - if (!protocol_match(app_data->dst_protocol, &host_ctx->proto_u.allowed_protocols)) { - snprintf(dial_params->err, sizeof(dial_params->err), - "hosted_service[%s] client requested protocol '%s' is not allowed", host_ctx->service_name, - app_data->dst_protocol); - return false; + if (!protocol_match(app_data->dst_protocol, &service->proto_u.allowed_protocols)) { + snprintf(err, err_sz, "requested protocol '%s' is not in 'allowedProtocols", app_data->dst_protocol); + return NULL; } - dial_protocol_str = app_data->dst_protocol; + dst_proto = app_data->dst_protocol; } else { - dial_protocol_str = host_ctx->proto_u.protocol; + dst_proto = service->proto_u.protocol; } - dial_params->hints.ai_protocol = get_protocol_id(dial_protocol_str); - if (dial_params->hints.ai_protocol < 0) { - snprintf(dial_params->err, sizeof(dial_params->err), "unsupported %s '%s'", DST_PROTO_KEY, dial_protocol_str); - return false; + if ((*protocol_number = get_protocol_id(dst_proto)) < 0) { + snprintf(err, err_sz, "requested protocol '%s' is not supported", dst_proto); + return NULL; } - if (host_ctx->forward_address) { - if (app_data->dst_hostname != NULL && app_data->dst_hostname[0] != 0) { - if (!allowed_hostname_match(app_data->dst_hostname, &host_ctx->addr_u.allowed_hostnames)) { - snprintf(dial_params->err, sizeof(dial_params->err), - "hosted_service[%s] client requested address '%s' is not allowed", host_ctx->service_name, - app_data->dst_hostname); - return false; - } + return dst_proto; +} - dial_params->address = app_data->dst_hostname; - dial_params->hints.ai_flags = AI_ADDRCONFIG; - } else if (app_data->dst_ip != NULL && app_data->dst_ip[0] != 0) { - uv_getaddrinfo_t gai_req = {0}; - int s; - if((s = uv_getaddrinfo(host_ctx->loop, &gai_req, NULL, app_data->dst_ip, - app_data->dst_port, &dial_params->hints)) != 0) { - ZITI_LOG(ERROR, "hosted_service[%s],getaddrinfo(%s,%s) failed: %s", - host_ctx->service_name, app_data->dst_ip, app_data->dst_port, gai_strerror(s)); - if (gai_req.addrinfo != NULL) { - uv_freeaddrinfo(gai_req.addrinfo); - } - return false; - } - ziti_address dst; - ziti_address_from_sockaddr(&dst, gai_req.addrinfo->ai_addr); - if (!address_match(&dst, &host_ctx->addr_u.allowed_addresses)) { - ZITI_LOG(ERROR, "hosted_service[%s] client requested address '%s' is not allowed", - host_ctx->service_name,app_data->dst_ip); - if (gai_req.addrinfo != NULL) { - uv_freeaddrinfo(gai_req.addrinfo); - } - return false; - } - if (gai_req.addrinfo != NULL) { - uv_freeaddrinfo(gai_req.addrinfo); +static const char *compute_dst_ip_or_hn(const host_ctx_t *service, const tunneler_app_data *app_data, + bool *is_ip, char *err, size_t err_sz) { + const char *ip_or_hn; + bool ip_expected = false; + bool hn_expected = false; + if (service->forward_address) { + if (app_data != NULL) { + if (app_data->dst_hostname != NULL) { + ZITI_LOG(VERBOSE, "using address from dst_hostname"); + ip_or_hn = app_data->dst_hostname; + hn_expected = true; + } else if (app_data->dst_ip != NULL) { + ZITI_LOG(VERBOSE, "using address from dst_ip"); + ip_or_hn = app_data->dst_ip; + ip_expected = true; + } else { + snprintf(err, err_sz, "config specifies 'forwardAddress', but client didn't send %s or %s in app_data", + DST_IP_KEY, DST_HOST_KEY); + return NULL; } - dial_params->address = app_data->dst_ip; - dial_params->hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV; } else { - snprintf(dial_params->err, sizeof(dial_params->err), - "hosted_service[%s] config specifies 'forwardAddress' but client didn't send %s or %s", - host_ctx->service_name, DST_HOST_KEY, DST_IP_KEY); - return false; + snprintf(err, err_sz, "config specifies 'forwardAddress', but client didn't send app_data"); + return NULL; } } else { - dial_params->address = host_ctx->addr_u.address; + ZITI_LOG(VERBOSE, "using address from config"); + ip_or_hn = service->addr_u.address; } - if (host_ctx->forward_port) { - if (app_data->dst_port == NULL) { - snprintf(dial_params->err, sizeof(dial_params->err), - "hosted_service[%s] config specifies 'forwardPort' but client didn't send %s", - host_ctx->service_name, DST_PORT_KEY); - return false; + ziti_address dst; + if (!ziti_address_from_string(&dst, ip_or_hn)) { + snprintf(err, err_sz, "failed to parse %s", ip_or_hn); + return NULL; + } + *is_ip = (dst.type == ziti_address_cidr); + + if (ip_expected && *is_ip == false) { + ZITI_LOG(DEBUG, "client forwarded non-IP %s in dst_ip", ip_or_hn); + } + if (hn_expected && *is_ip == true) { + ZITI_LOG(DEBUG, "client forwarded IP %s in dst_hostname", ip_or_hn); + } + + // authorize address if forwarding + if (service->forward_address) { + if (dst.type == ziti_address_hostname) { + if (!allowed_hostname_match(ip_or_hn, &service->addr_u.allowed_hostnames)) { + snprintf(err, err_sz, "requested address '%s' is not in allowedAddresses", + app_data->dst_hostname); + return NULL; + } + } else if (dst.type == ziti_address_cidr) { + if (!address_match(&dst, &service->addr_u.allowed_addresses)) { + snprintf(err, err_sz, "requested address '%s' is not in allowedAddresses", app_data->dst_ip); + return NULL; + } } + } + return ip_or_hn; +} + +static const char *compute_dst_port(const host_ctx_t *service, const tunneler_app_data *app_data, char *err, size_t err_sz) { + if (service->forward_port) { + if (app_data == NULL || app_data->dst_port == NULL || app_data->dst_port[0] == '\0') { + snprintf(err, err_sz, "config specifies 'forwardPort' but client didn't send %s in app_data", DST_PORT_KEY); + return NULL; + } errno = 0; int port = (int) strtol(app_data->dst_port, NULL, 10); if (errno != 0) { - snprintf(dial_params->err, sizeof(dial_params->err), - "hosted_service[%s] client sent invalid %s '%s'", host_ctx->service_name, - DST_PORT_KEY, app_data->dst_port); - return false; + snprintf(err, err_sz, "invalid %s '%s' in app_data", DST_PORT_KEY, app_data->dst_port); + return NULL; } - - if (!port_match(port, &host_ctx->port_u.allowed_port_ranges)) { - snprintf(dial_params->err, sizeof(dial_params->err), - "hosted_service[%s] client requested port '%s' is not allowed", host_ctx->service_name, - app_data->dst_port); - return false; + if (!port_match(port, &service->port_u.allowed_port_ranges)) { + snprintf(err, err_sz, "requested port '%s' is not in allowedPortRanges", app_data->dst_port); + return NULL; } - dial_params->port = app_data->dst_port; + return app_data->dst_port; + } + + static char port_from_config[12]; + snprintf(port_from_config, sizeof(port_from_config), "%d", service->port_u.port); + return port_from_config; +} + +static int do_bind(hosted_io_context io, const char *addr, int socktype) { + // split out the ip and port if port was specified + char *src_ip = strdup(addr); + char *port = strchr(src_ip, ':'); + if (port != NULL) { + *port = '\0'; + port++; + } + + uv_getaddrinfo_t ai_req = {0}; + struct addrinfo hints = {0}; + hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; + hints.ai_protocol = get_protocol_id(io->computed_dst_protocol); + hints.ai_socktype = socktype; + + int uv_err = uv_getaddrinfo(io->service->loop, &ai_req, NULL, src_ip, port, &hints); + free(src_ip); + + if (uv_err != 0) { + ZITI_LOG(ERROR, "hosted_service[%s], client[%s]: getaddrinfo(%s) failed: %s", + io->service->service_name, io->client_identity, addr, uv_strerror(uv_err)); + return -1; + } + + if (ai_req.addrinfo->ai_next != NULL) { + ZITI_LOG(DEBUG, "hosted_service[%s], client[%s]: getaddrinfo(%s) returned multiple results; using first", + io->service->service_name, io->client_identity, addr); + } + + ziti_address src_za; + ziti_address_from_sockaddr(&src_za, ai_req.addrinfo->ai_addr); // convert for easy validation + if (!address_match(&src_za, &io->service->allowed_source_addresses)) { + ZITI_LOG(ERROR, "hosted_service[%s], client[%s] client requested source IP %s is not allowed", + io->service->service_name, io->client_identity, addr); + return -1; + } + + switch (hints.ai_protocol) { + case IPPROTO_TCP: + uv_err = uv_tcp_bind(&io->server.tcp, ai_req.addrinfo->ai_addr, 0); + break; + case IPPROTO_UDP: + uv_err = uv_udp_bind(&io->server.udp, ai_req.addrinfo->ai_addr, 0); + break; + default: + ZITI_LOG(ERROR, "hosted_service[%s] client[%s] unsupported protocol %d when binding source address", + io->service->service_name, io->client_identity, hints.ai_protocol); + uv_err = UV_EINVAL; + } + + uv_freeaddrinfo(ai_req.addrinfo); + + if (uv_err != 0) { + ZITI_LOG(ERROR, "hosted_service[%s] client[%s]: bind failed: %s", io->service->service_name, + io->client_identity, uv_strerror(uv_err)); + return -1; + } + + return 0; +} + +static hosted_io_context hosted_io_context_new(struct hosted_service_ctx_s *service_ctx, ziti_connection client, + tunneler_app_data *app_data, const char *dst_protocol, const char *dst_ip_or_hn, const char *dst_port) { + hosted_io_context io = calloc(1, sizeof(struct hosted_io_ctx_s)); + io->service = service_ctx; + + // include underlay details in client identity if available + if (app_data && app_data->src_protocol && app_data->src_ip && app_data->src_port) { + snprintf(io->client_identity, sizeof(io->client_identity), "%s] client_src_addr[%s:%s:%s", ziti_conn_source_identity(client), + app_data->src_protocol, app_data->src_ip, app_data->src_port); } else { - snprintf(dial_params->_port_str, sizeof(dial_params->_port_str), "%d", host_ctx->port_u.port); - dial_params->port = dial_params->_port_str; + strncpy(io->client_identity, ziti_conn_source_identity(client), sizeof(io->client_identity)); + } + io->computed_dst_protocol = dst_protocol; + io->computed_dst_ip_or_hn = dst_ip_or_hn; + io->computed_dst_port = dst_port; + + int socktype, uv_err = -1; + int protocol_number = get_protocol_id(dst_protocol); + switch (protocol_number) { + case IPPROTO_TCP: + uv_err = uv_tcp_init(service_ctx->loop, &io->server.tcp); + socktype = SOCK_STREAM; + io->server.tcp.data = io; + break; + case IPPROTO_UDP: + uv_err = uv_udp_init(service_ctx->loop, &io->server.udp); + socktype = SOCK_DGRAM; + io->server.udp.data = io; + break; + default: + ZITI_LOG(ERROR, "hosted_service[%s] client[%s] unsupported protocol '%s''", service_ctx->service_name, + io->client_identity, dst_protocol); + free(io); + return NULL; + } + if (uv_err != 0) { + ZITI_LOG(ERROR, "hosted_service[%s] client[%s] dst[%s:%s:%s] failed to initialize underlay handle: %s", + service_ctx->service_name, io->client_identity, dst_protocol, dst_ip_or_hn, dst_port, uv_strerror(uv_err)); + free(io); + return NULL; + } + // uv handle has been initialized and must be closed before freeing `io` now. + + // if app_data includes source ip[:port], verify that it is allowed before attempting to bind + if (app_data && app_data->source_addr && app_data->source_addr[0] != '\0') { + if (do_bind(io, app_data->source_addr, socktype) != 0) { + hosted_server_close(io); + return NULL; + } } - return true; + // success. now set references to ziti connection and app_data so cleanup happens in ziti_conn_close_cb + io->client = client; + io->app_data = app_data; + + return io; } -/** called by ziti sdk when a ziti endpoint (client) initiates connection to a hosted service */ -static void on_hosted_client_connect(ziti_connection serv, ziti_connection clt, int status, ziti_client_ctx *clt_ctx) { +static void on_hosted_client_connect_resolved(uv_getaddrinfo_t* req, int status, struct addrinfo* res); + +/** called by ziti sdk when a ziti endpoint (client) initiates connection to a hosted service + * - compute dial address (from appdata if forwarding, or from dial address in config) + * - if forwarding, validate address is allowed + * - validate src address if specified + * - initiate async dns resolution of dial address (if computed address is hostname?) + */ +static void on_hosted_client_connect(ziti_connection serv, ziti_connection clt, int status, const ziti_client_ctx *clt_ctx) { struct hosted_service_ctx_s *service_ctx = ziti_conn_data(serv); if (service_ctx == NULL) { ZITI_LOG(ERROR, "null service_ctx"); - ziti_close(clt, ziti_conn_close_cb); + ziti_close(clt, NULL); return; } if (status != ZITI_OK) { - ZITI_LOG(ERROR, "incoming connection to service[%s] failed: %s", service_ctx->service_name, ziti_errorstr(status)); - ziti_close(clt, ziti_conn_close_cb); + ZITI_LOG(ERROR, "hosted_service[%s] incoming connection failed: %s", service_ctx->service_name, ziti_errorstr(status)); + ziti_close(clt, NULL); return; } - const char *client_identity = clt_ctx->caller_id; - if (client_identity == NULL) client_identity = ""; - - uv_getaddrinfo_t dial_ai_req = {0}; - uv_getaddrinfo_t source_ai_req = {0}; - struct hosted_io_ctx_s *io_ctx = NULL; - bool err = false; - - tunneler_app_data app_data; - memset(&app_data, 0, sizeof(app_data)); + tunneler_app_data *app_data = NULL; if (clt_ctx->app_data != NULL) { - ZITI_LOG(DEBUG, "hosted_service[%s], client[%s]: received app_data_json='%.*s'", service_ctx->service_name, - client_identity, (int)clt_ctx->app_data_sz, clt_ctx->app_data); - if (parse_tunneler_app_data(&app_data, (char *)clt_ctx->app_data, clt_ctx->app_data_sz) < 0) { - ZITI_LOG(ERROR, "hosted_service[%s], client[%s]: failed to parse app_data_json '%.*s'", - service_ctx->service_name, - client_identity, (int)clt_ctx->app_data_sz, clt_ctx->app_data); - err = true; - goto done; + ZITI_LOG(DEBUG, "hosted_service[%s] client[%s]: received app_data_json='%.*s'", service_ctx->service_name, + clt_ctx->caller_id, (int) clt_ctx->app_data_sz, clt_ctx->app_data); + if (parse_tunneler_app_data_ptr(&app_data, (char *) clt_ctx->app_data, clt_ctx->app_data_sz) < 0) { + ZITI_LOG(ERROR, "hosted_service[%s] client[%s]: failed to parse app_data_json '%.*s'", + service_ctx->service_name, clt_ctx->caller_id, (int) clt_ctx->app_data_sz, clt_ctx->app_data); + ziti_close(clt, NULL); + return; } } - if (app_data.conn_type == TunnelConnectionTypes.resolver) { + if (app_data != NULL && app_data->conn_type == TunnelConnectionTypes.resolver) { accept_resolver_conn(clt, &service_ctx->addr_u.allowed_hostnames); - free_tunneler_app_data(&app_data); + free_tunneler_app_data_ptr(app_data); return; } - struct addrinfo_params_s dial_ai_params; - memset(&dial_ai_params, 0, sizeof(dial_ai_params)); - int s = addrinfo_from_host_ctx(&dial_ai_params, service_ctx, &app_data); - if (!s) { - ZITI_LOG(ERROR, "hosted_service[%s], client[%s]: failed to create dial addrinfo params: %s", - service_ctx->service_name, client_identity, dial_ai_params.err); - err = true; - goto done; + char err[80]; + int protocol_number; + const char *protocol = compute_dst_protocol(service_ctx, app_data, &protocol_number, err, sizeof(err)); + if (protocol == NULL) { + ZITI_LOG(ERROR, "hosted_service[%s] client[%s] failed to compute destination protocol: %s", + service_ctx->service_name, clt_ctx->caller_id, err); + free_tunneler_app_data_ptr(app_data); + ziti_close(clt, NULL); + return; } - switch (dial_ai_params.hints.ai_protocol) { - case IPPROTO_TCP: - dial_ai_params.hints.ai_socktype = SOCK_STREAM; - break; - case IPPROTO_UDP: - dial_ai_params.hints.ai_socktype = SOCK_DGRAM; - break; + bool is_ip; + const char *ip_or_hn = compute_dst_ip_or_hn(service_ctx, app_data, &is_ip, err, sizeof(err)); + if (ip_or_hn == NULL) { + ZITI_LOG(ERROR, "hosted_service[%s] client[%s] failed to compute destination address: %s", + service_ctx->service_name, clt_ctx->caller_id, err); + free_tunneler_app_data_ptr(app_data); + ziti_close(clt, NULL); + return; } - if ((s = uv_getaddrinfo(service_ctx->loop, &dial_ai_req, NULL, dial_ai_params.address, dial_ai_params.port, &dial_ai_params.hints)) != 0) { - ZITI_LOG(ERROR, "hosted_service[%s], client[%s]: getaddrinfo(%s,%s) failed: %s", - service_ctx->service_name, client_identity, dial_ai_params.address, dial_ai_params.port, gai_strerror(s)); - err = true; - goto done; + const char *port = compute_dst_port(service_ctx, app_data, err, sizeof(err)); + if (port == NULL) { + ZITI_LOG(ERROR, "hosted_service[%s] client[%s] failed to compute destination port: %s", + service_ctx->service_name, clt_ctx->caller_id, err); + free_tunneler_app_data_ptr(app_data); + ziti_close(clt, NULL); + return; } - if (dial_ai_req.addrinfo->ai_next != NULL) { - ZITI_LOG(DEBUG, "hosted_service[%s], client[%s]: getaddrinfo(%s,%s) returned multiple results; using first", - service_ctx->service_name, client_identity, dial_ai_params.address, dial_ai_params.port); + + hosted_io_context io = hosted_io_context_new(service_ctx, clt, app_data, protocol, ip_or_hn, port); + if (io == NULL) { + ZITI_LOG(ERROR, "hosted_service[%s] client[%s] failed to create io context", service_ctx->service_name, + clt_ctx->caller_id); + free_tunneler_app_data_ptr(app_data); + ziti_close(clt, NULL); + return; } - const char *dst_proto = app_data.dst_protocol; - const char *dst_ip = app_data.dst_hostname ? app_data.dst_hostname : app_data.dst_ip; - const char *dst_port = app_data.dst_port; - if (dst_proto != NULL && dst_ip != NULL && dst_port != NULL) { - ZITI_LOG(INFO, "hosted_service[%s], client[%s] dst_addr[%s:%s:%s]: incoming connection", - service_ctx->service_name, client_identity, dst_proto, dst_ip, dst_port); - } else { - ZITI_LOG(INFO, "hosted_service[%s], client[%s] incoming connection", - service_ctx->service_name, client_identity); - } - - const char *source_addr = app_data.source_addr; - if (source_addr != NULL && *source_addr != 0) { - struct addrinfo source_hints = {0}; - const char *port_sep = strchr(source_addr, ':'); - const char *source_port = NULL; - char source_ip_cp[64]; - if (port_sep != NULL) { - source_port = port_sep + 1; - strncpy(source_ip_cp, source_addr, port_sep - source_addr); - source_ip_cp[port_sep - source_addr] = '\0'; - source_addr = source_ip_cp; - } - source_hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICHOST | AI_NUMERICSERV; - source_hints.ai_protocol = dial_ai_params.hints.ai_protocol; - source_hints.ai_socktype = dial_ai_params.hints.ai_socktype; - if ((s = uv_getaddrinfo(service_ctx->loop, &source_ai_req, NULL, source_addr, source_port, &source_hints)) != 0) { - ZITI_LOG(ERROR, "hosted_service[%s], client[%s]: getaddrinfo(%s,%s) failed: %s", - service_ctx->service_name, client_identity, source_addr, source_port, gai_strerror(s)); - err = true; - goto done; - } - if (source_ai_req.addrinfo->ai_next != NULL) { - ZITI_LOG(DEBUG, "hosted_service[%s], client[%s]: getaddrinfo(%s,%s) returned multiple results; using first", - service_ctx->service_name, client_identity, source_addr, source_port); - } - ziti_address src_za; - ziti_address_from_sockaddr(&src_za, source_ai_req.addrinfo->ai_addr); - if (!address_match(&src_za, &service_ctx->allowed_source_addresses)) { - ZITI_LOG(ERROR, "hosted_service[%s], client[%s] client requested source IP %s is not allowed", - service_ctx->service_name, client_identity, source_addr); - err = true; - goto done; + ZITI_LOG(INFO, "hosted_service[%s] client[%s] dst_addr[%s:%s:%s]: incoming connection", + service_ctx->service_name, io->client_identity, protocol, ip_or_hn, port); + + struct addrinfo hints = {0}; + hints.ai_protocol = protocol_number; + hints.ai_socktype = protocol_number == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV; + if (is_ip) hints.ai_flags |= AI_NUMERICHOST; + ziti_conn_set_data(clt, io); + + if (service_ctx->proxy_connector) { + if (protocol_number == IPPROTO_TCP) { + ZITI_LOG(DEBUG, "hosted_service[%s] client[%s] dst_addr[%s:%s:%s] connecting through proxy %s", + service_ctx->service_name, io->client_identity, protocol, ip_or_hn, port, service_ctx->proxy_addr); + service_ctx->proxy_connector->connect(service_ctx->loop, service_ctx->proxy_connector, ip_or_hn, port, + on_proxy_connect, io); + } else { + ZITI_LOG(WARN, "hosted_service[%s] client[%s] cannot use proxy for udp. dropping connection", + service_ctx->service_name, io->client_identity); + hosted_server_close(io); } + return; } - io_ctx = calloc(1, sizeof(struct hosted_io_ctx_s)); - io_ctx->service = service_ctx; - io_ctx->client = clt; - ziti_conn_set_data(clt, io_ctx); + uv_getaddrinfo_t *ai_req = calloc(1, sizeof(uv_getaddrinfo_t)); + ai_req->data = io; + int s = uv_getaddrinfo(service_ctx->loop, ai_req, on_hosted_client_connect_resolved, ip_or_hn, port, &hints); + if (s != 0) { + ZITI_LOG(ERROR, "hosted_service[%s] client[%s]: getaddrinfo(%s:%s:%s) failed: %s", + service_ctx->service_name, io->client_identity, protocol, ip_or_hn, port, uv_strerror(s)); + free(ai_req); + hosted_server_close(io); + return; + } +} + +static void on_hosted_client_connect_resolved(uv_getaddrinfo_t* ai_req, int status, struct addrinfo* res) { + hosted_io_context io = ai_req->data; + if (io == NULL) { + ZITI_LOG(ERROR, "null io"); + if (status >= 0) uv_freeaddrinfo(res); + free(ai_req); + return; + } + + if (status < 0) { + ZITI_LOG(ERROR, "hosted_service[%s] client[%s] getaddrinfo(%s:%s:%s) failed: %s", io->service->service_name, + io->client_identity, io->computed_dst_protocol, io->computed_dst_ip_or_hn, io->computed_dst_port, + uv_strerror(status)); + free(ai_req); + ZITI_LOG(DEBUG, "closing c[%p] io[%p]", io->client, ziti_conn_data(io->client)); + hosted_server_close(io); + return; + } + + if (res->ai_next != NULL) { + ZITI_LOG(DEBUG, "hosted_service[%s], client[%s]: getaddrinfo(%s:%s:%s) returned multiple results; using first", + io->service->service_name, io->client_identity, io->computed_dst_protocol, + io->computed_dst_ip_or_hn, io->computed_dst_port); + } - char host[48]; - char port[12]; - s = getnameinfo(dial_ai_req.addrinfo->ai_addr, dial_ai_req.addrinfo->ai_addrlen, host, sizeof(host), port, sizeof(port), - NI_NUMERICHOST | NI_NUMERICSERV); - if (s == 0) { - snprintf(io_ctx->server_dial_str, sizeof(io_ctx->server_dial_str), "%s:%s:%s", - get_protocol_str(dial_ai_req.addrinfo->ai_protocol), host, port); + uv_getnameinfo_t ni_req = {0}; + int uv_err = uv_getnameinfo(io->service->loop, &ni_req, NULL, res->ai_addr, NI_NUMERICHOST | NI_NUMERICSERV); + if (uv_err == 0) { + snprintf(io->resolved_dst, sizeof(io->resolved_dst), "%s:%s:%s", + get_protocol_str(res->ai_protocol), ni_req.host, ni_req.service); } else { - ZITI_LOG(WARN, "hosted_service[%s] client[%s] getnameinfo failed: %s", io_ctx->service->service_name, - ziti_conn_source_identity(io_ctx->client), gai_strerror(s)); - strncpy(io_ctx->server_dial_str, "", sizeof(io_ctx->server_dial_str)); + ZITI_LOG(WARN, "hosted_service[%s] client[%s] getnameinfo failed: %s", io->service->service_name, + io->client_identity, uv_strerror(uv_err)); + strncpy(io->resolved_dst, "", sizeof(io->resolved_dst)); } - int uv_err; - switch (dial_ai_req.addrinfo->ai_protocol) { + ZITI_LOG(DEBUG, "hosted_service[%s] client[%s] initiating connection to %s", + io->service->service_name, io->client_identity, io->resolved_dst); + + switch (res->ai_protocol) { case IPPROTO_TCP: - uv_tcp_init(service_ctx->loop, &io_ctx->server.tcp); - io_ctx->server.tcp.data = io_ctx; - if (source_ai_req.addrinfo != NULL) { - uv_err = uv_tcp_bind(&io_ctx->server.tcp, source_ai_req.addrinfo->ai_addr, 0); - if (uv_err != 0) { - ZITI_LOG(ERROR, "hosted_service[%s], client[%s]: uv_tcp_bind failed: %s", - service_ctx->service_name, client_identity, uv_err_name(uv_err)); - err = true; - goto done; - } - } { uv_connect_t *c = malloc(sizeof(uv_connect_t)); - uv_err = uv_tcp_connect(c, &io_ctx->server.tcp, dial_ai_req.addrinfo->ai_addr, on_hosted_tcp_server_connect_complete); + uv_err = uv_tcp_connect(c, &io->server.tcp, res->ai_addr, on_hosted_tcp_server_connect_complete); if (uv_err != 0) { ZITI_LOG(ERROR, "hosted_service[%s], client[%s]: uv_tcp_connect failed: %s", - service_ctx->service_name, client_identity, uv_err_name(uv_err)); - err = true; - goto done; + io->service->service_name, io->client_identity, uv_strerror(uv_err)); + hosted_server_close(io); + free(c); } } break; case IPPROTO_UDP: - uv_udp_init(service_ctx->loop, &io_ctx->server.udp); - io_ctx->server.udp.data = io_ctx; - if (source_ai_req.addrinfo != NULL) { - uv_err = uv_udp_bind(&io_ctx->server.udp, source_ai_req.addrinfo->ai_addr, 0); - if (uv_err != 0) { - ZITI_LOG(ERROR, "hosted_service[%s] client[%s]: uv_udp_bind failed: %s", - service_ctx->service_name, client_identity, uv_err_name(uv_err)); - err = true; - goto done; - } - } - uv_err = uv_udp_connect(&io_ctx->server.udp, dial_ai_req.addrinfo->ai_addr); + uv_err = uv_udp_connect(&io->server.udp, res->ai_addr); if (uv_err != 0) { ZITI_LOG(ERROR, "hosted_service[%s], client[%s]: uv_udp_connect failed: %s", - service_ctx->service_name, client_identity, uv_err_name(uv_err)); - err = true; - goto done; - } - if (ziti_accept(clt, on_hosted_client_connect_complete, NULL) != ZITI_OK) { + io->service->service_name, io->client_identity, uv_strerror(uv_err)); + hosted_server_close(io); + } else if (ziti_accept(io->client, on_hosted_client_connect_complete, NULL) != ZITI_OK) { ZITI_LOG(ERROR, "ziti_accept failed"); - err = true; - goto done; + hosted_server_close(io); } break; } - done: - if (err) { - if (io_ctx == NULL) { - // if we get an error before creating io_ctx, just close incoming connection - ziti_close(clt, ziti_conn_close_cb); - } else { - hosted_server_close(io_ctx); - } - } - if (clt_ctx->app_data != NULL) { - free_tunneler_app_data(&app_data); - } - if (dial_ai_req.addrinfo != NULL) { - uv_freeaddrinfo(dial_ai_req.addrinfo); - } - if (source_ai_req.addrinfo != NULL) { - uv_freeaddrinfo(source_ai_req.addrinfo); - } + uv_freeaddrinfo(res); + free(ai_req); } /** called by ziti SDK when a hosted service listener is ready */ @@ -583,7 +750,7 @@ static void hosted_listen_cb(ziti_connection serv, int status) { if (status != ZITI_OK) { ZITI_LOG(ERROR, "unable to host service %s: %s", host_ctx->service_name, ziti_errorstr(status)); ziti_conn_set_data(serv, NULL); - ziti_close(serv, ziti_conn_close_cb); + ziti_close(serv, NULL); free_hosted_service_ctx(host_ctx); } } @@ -601,8 +768,8 @@ static void listen_opts_from_host_cfg_v1(ziti_listen_opts *opts, const ziti_host if (config && config->listen_options) { opts->bind_using_edge_identity = config->listen_options->bind_with_identity; - opts->identity = config->listen_options->identity; - opts->connect_timeout_seconds = config->listen_options->connect_timeout_seconds; + opts->identity = (char*)config->listen_options->identity; + opts->connect_timeout_seconds = (int)config->listen_options->connect_timeout_seconds; opts->terminator_cost = config->listen_options->cost; const char *prec = config->listen_options->precendence; @@ -634,7 +801,8 @@ host_ctx_t *ziti_sdk_c_host(void *ziti_ctx, uv_loop_t *loop, const char *service host_ctx->cfg_type = cfg_type; host_ctx->cfg = cfg; - char *display_proto = "?", *display_addr = "?", display_port[12] = { '?', '\0' }; + const char *display_proto = "?", *display_addr = "?"; + char display_port[12] = { '?', '\0' }; ziti_listen_opts listen_opts; ziti_listen_opts *listen_opts_p = NULL; switch (cfg_type) { @@ -647,7 +815,7 @@ host_ctx_t *ziti_sdk_c_host(void *ziti_ctx, uv_loop_t *loop, const char *service host_ctx->forward_protocol = host_v1_cfg->forward_protocol; if (host_v1_cfg->forward_protocol) { STAILQ_INIT(&host_ctx->proto_u.allowed_protocols); - string_array allowed_protos = host_v1_cfg->allowed_protocols; + model_string_array allowed_protos = host_v1_cfg->allowed_protocols; for (i = 0; allowed_protos != NULL && allowed_protos[i] != NULL; i++) { protocol_t *p = calloc(1, sizeof(protocol_t)); p->protocol = strdup(allowed_protos[i]); @@ -713,7 +881,7 @@ host_ctx_t *ziti_sdk_c_host(void *ziti_ctx, uv_loop_t *loop, const char *service } } else { host_ctx->port_u.port = host_v1_cfg->port; - snprintf(display_port, sizeof(display_port), "%d", host_v1_cfg->port); + snprintf(display_port, sizeof(display_port), "%d", (int)host_v1_cfg->port); } STAILQ_INIT(&host_ctx->allowed_source_addresses); @@ -734,6 +902,25 @@ host_ctx_t *ziti_sdk_c_host(void *ziti_ctx, uv_loop_t *loop, const char *service memcpy(&a->za, allowed_src_addrs[i], sizeof(a->za)); STAILQ_INSERT_TAIL(&host_ctx->allowed_source_addresses, a, entries); } + + if (host_v1_cfg->proxy.type == ziti_proxy_server_type_http) { + const char *addr = host_v1_cfg->proxy.address; + if (addr != NULL && addr[0] != '\0') { + struct tlsuv_url_s url = {0}; + if (tlsuv_parse_url(&url, addr) == 0) { + host_ctx->proxy_addr = host_v1_cfg->proxy.address; + char host[128], port[6]; + snprintf(host, sizeof(host), "%.*s", (int) url.hostname_len, url.hostname); + snprintf(port, sizeof(port), "%d", url.port); + host_ctx->proxy_connector = tlsuv_new_proxy_connector(tlsuv_PROXY_HTTP, host, port); + } else { + ZITI_LOG(ERROR, "hosted_service[%s] could not parse host.v1 proxy address '%s' as ':'", + host_ctx->service_name, host_v1_cfg->proxy.address); + free_hosted_service_ctx(host_ctx); + return NULL; + } + } + } } break; case SERVER_CFG_V1: { @@ -746,7 +933,7 @@ host_ctx_t *ziti_sdk_c_host(void *ziti_ctx, uv_loop_t *loop, const char *service host_ctx->forward_address = false; host_ctx->addr_u.address = server_v1_cfg->hostname; - snprintf(display_port, sizeof(display_port), "%d", server_v1_cfg->port); + snprintf(display_port, sizeof(display_port), "%d", (int)server_v1_cfg->port); host_ctx->forward_port = false; host_ctx->port_u.port = server_v1_cfg->port; } @@ -778,7 +965,7 @@ host_ctx_t *ziti_sdk_c_host(void *ziti_ctx, uv_loop_t *loop, const char *service static void on_uv_close(uv_handle_t *handle) { struct hosted_io_ctx_s *io_ctx = handle->data; - free(io_ctx); + hosted_io_context_free(io_ctx); } static void on_bridge_close(uv_handle_t *handle) { diff --git a/lib/ziti-tunnel-cbs/ziti_hosting.h b/lib/ziti-tunnel-cbs/ziti_hosting.h index c9deb10d..6cf446c1 100644 --- a/lib/ziti-tunnel-cbs/ziti_hosting.h +++ b/lib/ziti-tunnel-cbs/ziti_hosting.h @@ -21,6 +21,7 @@ #ifndef ZITI_TUNNEL_SDK_C_ZITI_HOSTING_H #define ZITI_TUNNEL_SDK_C_ZITI_HOSTING_H #include +#include "tlsuv/http.h" // allowed address is one of: // - ip subnet address // - DNS name or wildcard @@ -42,7 +43,7 @@ struct hosted_service_ctx_s { bool forward_protocol; union { protocol_list_t allowed_protocols; - char *protocol; + const char *protocol; } proto_u; bool forward_address; union { @@ -50,7 +51,7 @@ struct hosted_service_ctx_s { address_list_t allowed_addresses; allowed_hostnames_t allowed_hostnames; }; - char *address; + const char *address; } addr_u; bool forward_port; union { @@ -58,6 +59,8 @@ struct hosted_service_ctx_s { uint16_t port; } port_u; address_list_t allowed_source_addresses; + const char *proxy_addr; + tlsuv_connector_t *proxy_connector; }; struct tunneled_service_s { diff --git a/lib/ziti-tunnel-cbs/ziti_tunnel_cbs.c b/lib/ziti-tunnel-cbs/ziti_tunnel_cbs.c index e1670c01..b6357797 100644 --- a/lib/ziti-tunnel-cbs/ziti_tunnel_cbs.c +++ b/lib/ziti-tunnel-cbs/ziti_tunnel_cbs.c @@ -219,14 +219,14 @@ static void parse_socket_address(const char *address, char **proto, char **ip, c } } -/** render app_data as string (json) */ -static ssize_t get_app_data_json(char *buf, size_t bufsz, tunneler_io_context io, ziti_context ziti_ctx, const char *source_ip, tunneler_app_data *app_data) { +/** initialize app_data and render json for a dial request. */ +static ssize_t get_app_data(char *buf, size_t bufsz, tunneler_io_context io, ziti_context ziti_ctx, const char *source_ip, tunneler_app_data *app_data) { const char *intercepted = get_intercepted_address(io); const char *client = get_client_address(io); - char source_addr[64]; if (intercepted != NULL) { - parse_socket_address(intercepted, &app_data->dst_protocol, &app_data->dst_ip, &app_data->dst_port); + parse_socket_address(intercepted, (char**)&app_data->dst_protocol, + (char**)&app_data->dst_ip, (char**)&app_data->dst_port); if (app_data->dst_ip) { const char *dst_hostname = ziti_dns_reverse_lookup(app_data->dst_ip); if (dst_hostname) { @@ -235,22 +235,23 @@ static ssize_t get_app_data_json(char *buf, size_t bufsz, tunneler_io_context io } } if (client != NULL) { - parse_socket_address(client, &app_data->src_protocol, &app_data->src_ip, &app_data->src_port); + parse_socket_address(client, (char**)&app_data->src_protocol, + (char**)&app_data->src_ip, (char**)&app_data->src_port); } if (source_ip != NULL && *source_ip != 0) { const ziti_identity *zid = ziti_get_identity(ziti_ctx); - strncpy(source_addr, source_ip, sizeof(source_addr)); - string_replace(source_addr, sizeof(source_addr), "$tunneler_id.name", zid->name); - string_replace(source_addr, sizeof(source_addr), "$dst_ip", app_data->dst_ip); - string_replace(source_addr, sizeof(source_addr), "$dst_port", app_data->dst_port); - string_replace(source_addr, sizeof(source_addr), "$src_ip", app_data->src_ip); - string_replace(source_addr, sizeof(source_addr), "$src_port", app_data->src_port); - app_data->source_addr = source_addr; + size_t source_addr_maxlen = 64; + size_t source_addr_sz = source_addr_maxlen * sizeof(char); + char *srcAddr = calloc(source_addr_maxlen, sizeof(char)); + strncpy(srcAddr, source_ip, source_addr_sz); + string_replace(srcAddr, source_addr_sz, "$tunneler_id.name", zid->name); + string_replace(srcAddr, source_addr_sz, "$dst_ip", app_data->dst_ip); + string_replace(srcAddr, source_addr_sz, "$dst_port", app_data->dst_port); + string_replace(srcAddr, source_addr_sz, "$src_ip", app_data->src_ip); + string_replace(srcAddr, source_addr_sz, "$src_port", app_data->src_port); + app_data->source_addr = srcAddr; } ssize_t json_len = tunneler_app_data_to_json_r(app_data, MODEL_JSON_COMPACT, buf, bufsz); - - // value points to stack buffer - app_data->source_addr = NULL; return json_len; } @@ -260,7 +261,7 @@ static void dial_opts_from_intercept_cfg_v1(ziti_dial_opts *opts, const ziti_int tag *t = (tag *) model_map_get(&(config->dial_options), "identity"); if (t != NULL) { if (t->type == tag_string) { - opts->identity = t->string_value; + opts->identity = (char*)t->string_value; } else { ZITI_LOG(WARN, "dial_options.identity has non-string type %d", t->type); } @@ -269,7 +270,7 @@ static void dial_opts_from_intercept_cfg_v1(ziti_dial_opts *opts, const ziti_int t = (tag *)model_map_get(&(config->dial_options), "connect_timeout_seconds"); if (t != NULL) { if (t->type == tag_number) { - opts->connect_timeout_seconds = t->num_value; + opts->connect_timeout_seconds = (int)t->num_value; } else { ZITI_LOG(WARN, "dial_options.connect_timeout_seconds has non-numeric type %d", t->type); } @@ -302,8 +303,7 @@ void * ziti_sdk_c_dial(const void *intercept_ctx, struct io_ctx_s *io) { return NULL; } - ziti_dial_opts dial_opts; - memset(&dial_opts, 0, sizeof(dial_opts)); + ziti_dial_opts dial_opts = {0}; char app_data_json[256]; const char *source_ip = NULL; @@ -320,32 +320,29 @@ void * ziti_sdk_c_dial(const void *intercept_ctx, struct io_ctx_s *io) { } tunneler_app_data app_data = {0}; - ssize_t json_len = get_app_data_json(app_data_json, sizeof(app_data_json), io->tnlr_io, ziti_ctx, source_ip, &app_data); + ssize_t json_len = get_app_data(app_data_json, sizeof(app_data_json), io->tnlr_io, ziti_ctx, source_ip, &app_data); if (json_len < 0) { ZITI_LOG(ERROR, "service[%s] failed to encode app_data", zi_ctx->service_name); free(ziti_io_ctx); return NULL; } + dial_opts.stream = strcmp(app_data.dst_protocol, "tcp") == 0; + char resolved_dial_identity[128]; if (dial_opts.identity != NULL && dial_opts.identity[0] != '\0') { - const char *dst_addr = get_intercepted_address(io->tnlr_io); - if (dst_addr != NULL) { - strncpy(resolved_dial_identity, dial_opts.identity, sizeof(resolved_dial_identity)); - if (parse_tunneler_app_data(&app_data, (char *)app_data_json, json_len) >= 0){ - if (app_data.dst_protocol != NULL) { - string_replace(resolved_dial_identity, sizeof(resolved_dial_identity), "$dst_protocol", app_data.dst_protocol); - } - if (app_data.dst_ip != NULL) { - string_replace(resolved_dial_identity, sizeof(resolved_dial_identity), "$dst_ip", app_data.dst_ip); - } - if (app_data.dst_port != NULL) { - string_replace(resolved_dial_identity, sizeof(resolved_dial_identity), "$dst_port", app_data.dst_port); - } - if (app_data.dst_hostname != NULL){ - string_replace(resolved_dial_identity, sizeof(resolved_dial_identity), "$dst_hostname", app_data.dst_hostname); - } - } + strncpy(resolved_dial_identity, dial_opts.identity, sizeof(resolved_dial_identity)); + if (app_data.dst_protocol != NULL) { + string_replace(resolved_dial_identity, sizeof(resolved_dial_identity), "$dst_protocol", app_data.dst_protocol); + } + if (app_data.dst_ip != NULL) { + string_replace(resolved_dial_identity, sizeof(resolved_dial_identity), "$dst_ip", app_data.dst_ip); + } + if (app_data.dst_port != NULL) { + string_replace(resolved_dial_identity, sizeof(resolved_dial_identity), "$dst_port", app_data.dst_port); + } + if (app_data.dst_hostname != NULL) { + string_replace(resolved_dial_identity, sizeof(resolved_dial_identity), "$dst_hostname", app_data.dst_hostname); } dial_opts.identity = resolved_dial_identity; } @@ -395,7 +392,7 @@ ssize_t ziti_sdk_c_write(const void *ziti_io_ctx, void *write_ctx, const void *d return zs; } - ZITI_LOG(VERBOSE, "applying backpressure %lu pending bytes", _ziti_io_ctx->pending_wbytes); + ZITI_LOG(VERBOSE, "applying backpressure %" PRIu64 " pending bytes", _ziti_io_ctx->pending_wbytes); return ERR_WOULDBLOCK; } @@ -471,7 +468,6 @@ intercept_ctx_t *new_intercept_ctx(tunneler_context tnlr_ctx, ziti_intercept_t * intercept_ctx_t *i_ctx = intercept_ctx_new(tnlr_ctx, zi_ctx->service_name, zi_ctx); intercept_ctx_set_match_addr(i_ctx, intercept_match_addr); - int i; const ziti_address *intercept_addr; switch (zi_ctx->cfg_desc->cfgtype) { case CLIENT_CFG_V1: @@ -607,6 +603,7 @@ static void ziti_conn_close_cb(ziti_connection zc) { } if (io->ziti_io) { free(io->ziti_io); + io->ziti_io = NULL; } ziti_tunneler_close(io->tnlr_io); free(io); @@ -623,6 +620,12 @@ ziti_connection intercept_resolve_connect(ziti_intercept_t *intercept, void *ctx .app_data_sz = strlen(RESOLVE_APP_DATA) }; - ziti_dial_with_options(conn, intercept->service_name, &opts, conn_cb, data_cb); + int rc = ziti_dial_with_options(conn, intercept->service_name, &opts, conn_cb, data_cb); + if (rc != ZITI_OK) { + ZITI_LOG(WARN, "failed to establish proxy resolver connection: %s", ziti_errorstr(rc)); + ziti_close(conn, NULL); + return NULL; + } + return conn; } \ No newline at end of file diff --git a/lib/ziti-tunnel-cbs/ziti_tunnel_ctrl.c b/lib/ziti-tunnel-cbs/ziti_tunnel_ctrl.c index 5c44f3f3..25b688df 100644 --- a/lib/ziti-tunnel-cbs/ziti_tunnel_ctrl.c +++ b/lib/ziti-tunnel-cbs/ziti_tunnel_ctrl.c @@ -14,16 +14,21 @@ limitations under the License. */ -#include + +#include #include #include +#include #include "ziti_hosting.h" #include "ziti_instance.h" -#include "stdarg.h" -#include + #include +#include +#include +#include + #ifndef MAXBUFFERLEN #define MAXBUFFERLEN 8192 #endif @@ -47,10 +52,11 @@ static const char * cfg_types[] = { "ziti-tunneler-client.v1", "intercept.v1", " static unsigned long refresh_interval = 10; static int process_cmd(const tunnel_command *cmd, void (*cb)(const tunnel_result *, void *ctx), void *ctx); -static int load_identity(const char *identifier, const char *path, int api_page_size, command_cb cb, void *ctx); +static int load_identity_cfg(const char *identifier, const ziti_config *cfg, bool disabled, + int api_page_size, command_cb cb, void *ctx); +static int load_identity(const char *identifier, const char *path, bool disabled, int api_page_size, command_cb cb, void *ctx); static void get_transfer_rates(const char *identifier, command_cb cb, void *ctx); -static struct ziti_instance_s *new_ziti_instance(const char *identifier, const char *path); -static void load_ziti_async(uv_loop_t *loop, void *arg); +static void load_ziti_async(uv_loop_t *loop, struct ziti_instance_s *inst); static void on_sigdump(uv_signal_t *sig, int signum); static void enable_mfa(ziti_context ztx, void *ctx); static void verify_mfa(ziti_context ztx, char *code, void *ctx); @@ -63,6 +69,9 @@ static void tunnel_status_event(TunnelEvent event, int status, void *event_data, static ziti_context get_ziti(const char *identifier); static void update_config(uv_work_t *wr); static void update_config_done(uv_work_t *wr, int err); +static void on_cmd_enroll(const ziti_config *cfg, int status, const char *err_message, void *ctx); + +static void on_ext_auth(ziti_context ztx, const char *url, void *ctx); struct tunnel_cb_s { void *ctx; @@ -74,6 +83,7 @@ typedef struct api_update_req_s { uv_work_t wr; ziti_context ztx; char *new_url; + char *new_ca; int err; const char *errmsg; } api_update_req; @@ -113,33 +123,20 @@ static ziti_context get_ziti(const char *identifier) { return inst ? inst->ztx : NULL; } -static int ziti_dump_to_log_op(void* stringsBuilder, const char *fmt, ...) { - static char line[4096]; - - va_list vargs; - va_start(vargs, fmt); - vsnprintf(line, sizeof(line), fmt, vargs); - va_end(vargs); - - if (strlen(stringsBuilder) + strlen(line) > MAXBUFFERLEN) { - return -1; - } - // write/append to the buffer - strncat(stringsBuilder, line, sizeof(line)); - return 0; -} - -static void ziti_dump_to_log(void *ctx) { - char* buffer; - buffer = malloc(MAXBUFFERLEN*sizeof(char)); - buffer[0] = 0; - //actually invoke ziti_dump here - ziti_dump(ctx, ziti_dump_to_log_op, buffer); - ZITI_LOG(INFO, "ziti dump to log %s", buffer); - free(buffer); +/** typedef for e.g. `string_buf_fmt`, `dump_file_op` */ +typedef int (*dump_writer)(void *writer_ctx, const char *, ...); +/** typedef for dump fn, e.g. `ziti_dump`, `ip_dump` */ +typedef void (*dumper)(void *dumper_ctx, dump_writer writer, void *writer_ctx); + +static char * dump_to_string(dumper dump, void *dump_ctx) { + string_buf_t *out = new_string_buf(); + dump(dump_ctx, (int (*)(void *, const char *, ...)) string_buf_fmt, out); + char *val = string_buf_to_string(out, NULL); + free(out); + return val; } -static int ziti_dump_to_file_op(void* fp, const char *fmt, ...) { +static int dump_to_file_op(void* fp, const char *fmt, ...) { static char line[4096]; va_list vargs; @@ -151,7 +148,7 @@ static int ziti_dump_to_file_op(void* fp, const char *fmt, ...) { return 0; } -static void ziti_dump_to_file(void *ctx, char* outputFile) { +static void dump_to_file(dumper dump, void *dump_ctx, char* outputFile) { FILE *fp; fp = fopen(outputFile, "w+"); if(fp == NULL) @@ -163,25 +160,49 @@ static void ziti_dump_to_file(void *ctx, char* outputFile) { uv_gettimeofday(&dump_time); char time_str[32]; - struct tm* start_tm = gmtime(&dump_time.tv_sec); + time_t sec = (time_t)dump_time.tv_sec; + struct tm* start_tm = gmtime(&sec); strftime(time_str, sizeof(time_str), "%a %b %d %Y, %X %p", start_tm); - fprintf(fp, "Ziti Dump starting: %s\n",time_str); + fprintf(fp, "Dump starting: %s\n",time_str); - //actually invoke ziti_dump here - ziti_dump(ctx, ziti_dump_to_file_op, fp); + dump(dump_ctx, dump_to_file_op, fp); fflush(fp); fclose(fp); } +static void ip_dump(const tunnel_ip_stats *stats, dump_writer writer, void *writer_ctx) { + int i; + + writer(writer_ctx, "\n=================\nMemory Pools:\n"); + writer(writer_ctx, "%-16s%-12s%-12s%-12s\n", "Pool Name", "In Use", "Max Used", "Limit"); + tunnel_ip_mem_pool_array pools = stats->pools; + for (i = 0; pools[i] != NULL; i++) { + writer(writer_ctx, "%-16s%-12d%-12d%-12d\n", pools[i]->name, pools[i]->used, pools[i]->max, pools[i]->avail); + } + + writer(writer_ctx, "\n=================\nIP Connections:\n"); + writer(writer_ctx, "%-12s%-40s%-40s%-16s%-24s\n", + "Protocol", "Local Address", "Remote Address", "State", "Ziti Service"); + tunnel_ip_conn_array conns = stats->connections; + char local_addr[64]; + char remote_addr[64]; + for (i = 0; conns[i] != NULL; i++) { + snprintf(local_addr, sizeof(local_addr), "%s:%lld", conns[i]->local_ip, conns[i]->local_port); + snprintf(remote_addr, sizeof(remote_addr), "%s:%lld", conns[i]->remote_ip, conns[i]->remote_port); + writer(writer_ctx, "%-12s%-40s%-40s%-16s%-24s\n", + conns[i]->protocol, local_addr, remote_addr, conns[i]->state, conns[i]->service); + } + +} + static void disconnect_identity(ziti_context ziti_ctx, void *tnlr_ctx) { ZITI_LOG(INFO, "Disconnecting Identity %s", ziti_get_identity(ziti_ctx)->name); remove_intercepts(ziti_ctx, tnlr_ctx); - // https://github.com/openziti/ziti-tunnel-sdk-c/issues/275 - not able to close tun gracefully, probably because of the crash from this statement - // ziti_shutdown(ziti_ctx); // causes the crash + ziti_set_enabled(ziti_ctx, false); } -bool is_null(const void * field, char* message, tunnel_result* result) { +bool is_null(const void * field, const char* message, tunnel_result* result) { if (field == NULL) { result->error = message; result->success = false; @@ -202,15 +223,34 @@ static int process_cmd(const tunnel_command *cmd, command_cb cb, void *ctx) { ZITI_LOG(TRACE, "processing command[%s] with data[%s]", TunnelCommands.name(cmd->command), cmd->data); switch (cmd->command) { case TunnelCommand_LoadIdentity: { - tunnel_load_identity load; + tunnel_load_identity load = {0}; if (cmd->data == NULL || parse_tunnel_load_identity(&load, cmd->data, strlen(cmd->data)) < 0) { result.success = false; result.error = "invalid command"; break; } - const char *id = load.identifier ? load.identifier : load.path; - load_identity(id, load.path, load.apiPageSize, cb, ctx); - return 0; + + int rc = ZITI_INVALID_CONFIG; + if (load.config != NULL) { + if (load.identifier == NULL) { + result.success = false; + result.code = rc; + result.error = "identifier is required when loading with config"; + break; + } + rc = load_identity_cfg(load.identifier, load.config, false, (int)load.apiPageSize, cb, ctx); + } else if (load.path != NULL) { + const char *id = load.identifier ? load.identifier : load.path; + rc = load_identity(id, load.path, false, (int)load.apiPageSize, cb, ctx); + } + + if (rc == ZITI_OK) { + return 0; + } + result.success = false; + result.error = (char*)ziti_errorstr(rc); + result.code = rc; + break; } case TunnelCommand_ListIdentities: { @@ -285,6 +325,9 @@ static int process_cmd(const tunnel_command *cmd, command_cb cb, void *ctx) { } const char *key; struct ziti_instance_s *inst; + struct json_object *json = dump.dump_path == NULL ? + json_object_new_object() : NULL; + bool success = true; MODEL_MAP_FOREACH(key, inst, &instances) { if (inst->ztx == NULL) { continue; @@ -296,30 +339,36 @@ static int process_cmd(const tunnel_command *cmd, command_cb cb, void *ctx) { if (dump.identifier != NULL && strcmp(dump.identifier, inst->identifier) != 0) { continue; } - bool success = true; if (dump.dump_path == NULL) { - ziti_dump_to_log(inst->ztx); + char *dumpstr = dump_to_string((dumper) ziti_dump, inst->ztx); + struct json_object *s = json_object_new_string(dumpstr); + json_object_object_add(json, inst->identifier, s); + free(dumpstr); } else { char dump_file[MAXPATHLEN]; char* dump_path = realpath(dump.dump_path, NULL); if (dump_path != NULL) { snprintf(dump_file, sizeof(dump_file), "%s/%s.ziti", dump_path, identity->name); - ziti_dump_to_file(inst->ztx, dump_file); + dump_to_file((dumper) ziti_dump, inst->ztx, dump_file); } else { ZITI_LOG(WARN, "Could not generate the ziti dump file, because the path is not found"); success = false; } } - if (success) { - result.success = true; - result.code = IPC_SUCCESS; - } else { - result.success = false; - result.code = IPC_ERROR; + } + if (success) { + result.success = true; + result.code = IPC_SUCCESS; + if (json) { + result.data = strdup(json_object_to_json_string(json)); + json_object_put(json); } - + } else { + result.success = false; + result.code = IPC_ERROR; } + if (!result.success) { char errorMsg[1024]; snprintf(errorMsg, sizeof(errorMsg),"No matching identifier found for %s", dump.identifier); @@ -331,22 +380,69 @@ static int process_cmd(const tunnel_command *cmd, command_cb cb, void *ctx) { break; } + case TunnelCommand_IpDump: { + ZITI_LOG(INFO, "ip dump started"); + tunnel_ip_dump dump = {0}; + if (cmd->data != NULL && parse_tunnel_ip_dump(&dump, cmd->data, strlen(cmd->data)) < 0) { + result.success = false; + result.error = "invalid command"; + free_tunnel_ip_dump(&dump); + break; + } + tunnel_ip_stats stats = {0}; + ziti_tunnel_get_ip_stats(&stats); + result.data = tunnel_ip_stats_to_json(&stats, MODEL_JSON_COMPACT, NULL); + bool success = true; + if (dump.dump_path != NULL) { + char dump_file[MAXPATHLEN]; + char* dump_path = realpath(dump.dump_path, NULL); + if (dump_path != NULL) { + uv_timeval64_t now; + uv_gettimeofday(&now); + struct tm *tm = gmtime(&now.tv_sec); + snprintf(dump_file, sizeof(dump_file), "%s/%04d-%02d-%02dT%02d:%02d:%02d.%03dZ.ip_dump", dump_path, + 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec, now.tv_usec / 1000); + dump_to_file((dumper) ip_dump, &stats, dump_file); + free(dump_path); + } else { + ZITI_LOG(WARN, "Could not generate the ip dump file, because the path is not found"); + success = false; + } + } + if (success) { + result.success = true; + result.code = IPC_SUCCESS; + } else { + result.success = false; + result.code = IPC_ERROR; + } + if (!result.success) { + result.error = "ip dump failed"; + ZITI_LOG(WARN, "%s", result.error); + } else { + ZITI_LOG(INFO, "ip dump finished"); + } + free_tunnel_ip_stats(&stats); + break; + } + case TunnelCommand_EnableMFA: { - tunnel_enable_mfa enable_mfa_cmd = {0}; - if (cmd->data != NULL && parse_tunnel_enable_mfa(&enable_mfa_cmd, cmd->data, strlen(cmd->data)) < 0) { + tunnel_identity_id id = {0}; + if (cmd->data != NULL && parse_tunnel_identity_id(&id, cmd->data, strlen(cmd->data)) < 0) { result.success = false; result.error = "invalid command"; - free_tunnel_enable_mfa(&enable_mfa_cmd); + free_tunnel_identity_id(&id); break; } - if (is_null(enable_mfa_cmd.identifier, "Identifier info is not found in the request", &result)) { - free_tunnel_enable_mfa(&enable_mfa_cmd); + if (is_null(id.identifier, "Identifier info is not found in the request", &result)) { + free_tunnel_identity_id(&id); break; } - struct ziti_instance_s *inst = model_map_get(&instances, enable_mfa_cmd.identifier); + struct ziti_instance_s *inst = model_map_get(&instances, id.identifier); if (is_null(inst, "ziti context not found", &result) || is_null(inst->ztx, "ziti context is not loaded", &result)) { - free_tunnel_enable_mfa(&enable_mfa_cmd); + free_tunnel_identity_id(&id); break; } if (inst->ztx == NULL) { @@ -356,13 +452,14 @@ static int process_cmd(const tunnel_command *cmd, command_cb cb, void *ctx) { } struct tunnel_cb_s *req = malloc(sizeof(struct tunnel_cb_s)); - req->ctx = strdup(enable_mfa_cmd.identifier); + req->ctx = (void*)id.identifier; + id.identifier = NULL; req->cmd_cb = cb; req->cmd_ctx = ctx; enable_mfa(inst->ztx, req); - free_tunnel_enable_mfa(&enable_mfa_cmd); + free_tunnel_identity_id(&id); return 0; } @@ -426,7 +523,6 @@ static int process_cmd(const tunnel_command *cmd, command_cb cb, void *ctx) { return 0; } - default: result.error = "command not implemented"; case TunnelCommand_SubmitMFA: { tunnel_submit_mfa auth = {0}; if (cmd->data == NULL || parse_tunnel_submit_mfa(&auth, cmd->data, strlen(cmd->data)) < 0) { @@ -520,47 +616,68 @@ static int process_cmd(const tunnel_command *cmd, command_cb cb, void *ctx) { } case TunnelCommand_GetMetrics: { - tunnel_get_identity_metrics get_identity_metrics_cmd = {0}; - if (cmd->data == NULL || parse_tunnel_get_identity_metrics(&get_identity_metrics_cmd, cmd->data, strlen(cmd->data)) < 0) { + tunnel_identity_id get_identity_metrics_cmd = {0}; + if (cmd->data == NULL || parse_tunnel_identity_id(&get_identity_metrics_cmd, cmd->data, strlen(cmd->data)) < 0) { result.error = "invalid command"; result.success = false; - free_tunnel_get_identity_metrics(&get_identity_metrics_cmd); + free_tunnel_identity_id(&get_identity_metrics_cmd); break; } if (is_null(get_identity_metrics_cmd.identifier, "Identifier info is not found in the request", &result)) { - free_tunnel_get_identity_metrics(&get_identity_metrics_cmd); + free_tunnel_identity_id(&get_identity_metrics_cmd); break; } struct ziti_instance_s *inst = model_map_get(&instances, get_identity_metrics_cmd.identifier); if (is_null(inst, "ziti context not found", &result) || is_null(inst->ztx, "ziti context is not loaded", &result)) { - free_tunnel_get_identity_metrics(&get_identity_metrics_cmd); + free_tunnel_identity_id(&get_identity_metrics_cmd); break; } get_transfer_rates(get_identity_metrics_cmd.identifier, (command_cb) cb, ctx); - free_tunnel_get_identity_metrics(&get_identity_metrics_cmd); + free_tunnel_identity_id(&get_identity_metrics_cmd); return 0; } + case TunnelCommand_RefreshIdentity: { + tunnel_identity_id id = {0}; + if (cmd->data == NULL || parse_tunnel_identity_id(&id, cmd->data, strlen(cmd->data)) < 0) { + result.success = false; + result.error = "invalid command"; + } else if (!is_null(id.identifier, + "Identifier info is not found in the remove identity request", + &result)) { + struct ziti_instance_s *inst = model_map_get(&instances, id.identifier); + if (inst && inst->ztx) { + result.code = ziti_refresh(inst->ztx); + result.success = result.code == ZITI_OK; + result.error = result.code == ZITI_OK ? NULL : ziti_errorstr((int) result.code); + } else { + result.error = "Identity not found"; + } + } + free_tunnel_identity_id(&id); + break; + } + case TunnelCommand_RemoveIdentity: { - tunnel_delete_identity delete_id = {0}; - if (cmd->data == NULL || parse_tunnel_delete_identity(&delete_id, cmd->data, strlen(cmd->data)) < 0) { + tunnel_identity_id delete_id = {0}; + if (cmd->data == NULL || parse_tunnel_identity_id(&delete_id, cmd->data, strlen(cmd->data)) < 0) { result.success = false; result.error = "invalid command"; - free_tunnel_delete_identity(&delete_id); + free_tunnel_identity_id(&delete_id); break; } result.data = tunnel_command_to_json(cmd, MODEL_JSON_COMPACT, NULL); if (is_null(delete_id.identifier, "Identifier info is not found in the remove identity request", &result)) { - free_tunnel_delete_identity(&delete_id); + free_tunnel_identity_id(&delete_id); break; } struct ziti_instance_s *inst = model_map_get(&instances, delete_id.identifier); if (is_null(inst, "ziti context not found", &result) || is_null(inst->ztx, "ziti context is not loaded", &result)) { - free_tunnel_delete_identity(&delete_id); + free_tunnel_identity_id(&delete_id); break; } @@ -571,7 +688,7 @@ static int process_cmd(const tunnel_command *cmd, command_cb cb, void *ctx) { result.success = true; result.code = IPC_SUCCESS; - free_tunnel_delete_identity(&delete_id); + free_tunnel_identity_id(&delete_id); break; } @@ -599,8 +716,79 @@ static int process_cmd(const tunnel_command *cmd, command_cb cb, void *ctx) { break; } - case TunnelCommand_Unknown: { - ZITI_LOG(VERBOSE, "Unknown tunnel command received"); + case TunnelCommand_Enroll: { + tunnel_enroll enroll = {0}; + int rc; + if (cmd->data == NULL || parse_tunnel_enroll(&enroll, cmd->data, strlen(cmd->data)) < 0) { + result.error = "invalid command"; + result.success = false; + free_tunnel_enroll(&enroll); + break; + } else { + struct tunnel_cb_s *req = malloc(sizeof(struct tunnel_cb_s)); + req->cmd_cb = cb; + req->cmd_ctx = ctx; + + ziti_enroll_opts opts = { + .enroll_name = enroll.name, + .jwt_content = enroll.jwt, + .enroll_key = enroll.key, + .enroll_cert = enroll.cert, + .use_keychain = enroll.use_keychain, + }; + ziti_enroll(&opts, CMD_CTX.loop, on_cmd_enroll, req); + free_tunnel_enroll(&enroll); + return 0; + } + } + + case TunnelCommand_SetUpstreamDNS: { + tunnel_upstream_dns_array upstreams = NULL; + int rc; + if (parse_tunnel_upstream_dns_array(&upstreams, cmd->data, strlen(cmd->data)) < 0) { + result.error = "invalid command"; + result.success = false; + } else if ((rc = ziti_dns_set_upstream(CMD_CTX.loop,upstreams)) != 0) { + result.error = (char*)uv_strerror(rc); + result.success = false; + } else { + result.success = true; + result.code = IPC_SUCCESS; + } + free_tunnel_upstream_dns_array(&upstreams); + break; + } + + case TunnelCommand_ExternalAuth: { + tunnel_identity_id id = {}; + if (cmd->data == NULL || + parse_tunnel_identity_id(&id, cmd->data, strlen(cmd->data)) < 0) { + result.error = "invalid command"; + result.success = false; + free_tunnel_identity_id(&id); + break; + } + + struct ziti_instance_s *inst = model_map_get(&instances, id.identifier); + if (is_null(inst, "ziti context not found", &result) || + is_null(inst->ztx, "ziti context is not loaded", &result)) { + free_tunnel_identity_id(&id); + break; + } + + struct tunnel_cb_s *req = calloc(1, sizeof(*req)); + req->ctx = (void*)id.identifier; + req->cmd_cb = cb; + req->cmd_ctx = ctx; + ziti_ext_auth(inst->ztx, on_ext_auth, req); + return 0; + } + default: { + static char err[512]; + snprintf(err, sizeof(err), "unsupported command %d[%s]", + cmd->command, TunnelCommands.name(cmd->command)); + ZITI_LOG(VERBOSE, "%s", err); + result.error = err; break; } } @@ -610,18 +798,37 @@ static int process_cmd(const tunnel_command *cmd, command_cb cb, void *ctx) { return 0; } -static int load_identity(const char *identifier, const char *path, int api_page_size, command_cb cb, void *ctx) { +static int load_identity(const char *identifier, const char *path, bool disabled, int api_page_size, command_cb cb, void *ctx) { + ziti_config cfg = {0}; + int rc = ziti_load_config(&cfg, path); + if (rc == ZITI_OK) { + if (identifier == NULL) { + identifier = path; + } + rc = load_identity_cfg(identifier, &cfg, disabled, api_page_size, cb, ctx); + } + + free_ziti_config(&cfg); + return rc; +} - struct ziti_instance_s *inst = new_ziti_instance(identifier, path); +static int load_identity_cfg(const char *identifier, const ziti_config *cfg, bool disabled, int api_page_size, command_cb cb, void *ctx) { + struct ziti_instance_s *inst = new_ziti_instance(identifier ? identifier : cfg->cfg_source); + ziti_options opts = { + .api_page_size = api_page_size > 0 ? api_page_size : 0, + .disabled = disabled, + }; + int rc = init_ziti_instance(inst, cfg, &opts); + if (rc != ZITI_OK) { + goto on_error; + } inst->load_cb = cb; inst->load_ctx = ctx; - inst->opts.config = strdup(path); - if (api_page_size > 0) { - inst->opts.api_page_size = api_page_size; - } load_ziti_async(CMD_CTX.loop, inst); - return 0; + + on_error: + return rc; } static void get_transfer_rates(const char *identifier, command_cb cb, void *ctx) { @@ -636,12 +843,14 @@ static void get_transfer_rates(const char *identifier, command_cb cb, void *ctx) }; int metrics_len = 6; if (up > 0) { - id_metrics.up = calloc((metrics_len + 1), sizeof(char)); - snprintf(id_metrics.up, metrics_len, "%.2lf", up); + char *ups = calloc((metrics_len + 1), sizeof(char)); + id_metrics.up = ups; + snprintf(ups, metrics_len + 1, "%.2lf", up); } if (down > 0) { - id_metrics.down = calloc((metrics_len + 1), sizeof(char)); - snprintf(id_metrics.down, metrics_len, "%.2lf", down); + char *downs = calloc((metrics_len + 1), sizeof(char)); + id_metrics.down = downs; + snprintf(downs, metrics_len+1, "%.2lf", down); } tunnel_result result = {0}; @@ -654,24 +863,50 @@ static void get_transfer_rates(const char *identifier, command_cb cb, void *ctx) free(result.data); } -static struct ziti_instance_s *new_ziti_instance(const char *identifier, const char *path) { +struct ziti_instance_s *new_ziti_instance(const char *identifier) { struct ziti_instance_s *inst = calloc(1, sizeof(struct ziti_instance_s)); + inst->identifier = strdup(identifier); + return inst; +} - inst->identifier = strdup(identifier ? identifier : path); - if (path) { - inst->opts.config = realpath(path, NULL); +int init_ziti_instance(struct ziti_instance_s *inst, const ziti_config *cfg, const ziti_options *opts) { + FREE(inst->ztx); + int rc = ziti_context_init(&inst->ztx, cfg); + if (rc != ZITI_OK) { + ZITI_LOG(ERROR, "ziti_context_init failed: %s", ziti_errorstr(rc)); + return rc; } - inst->opts.config_types = cfg_types; - inst->opts.events = -1; - inst->opts.event_cb = on_ziti_event; - inst->opts.refresh_interval = refresh_interval; /* default refresh */ - inst->opts.app_ctx = inst; - return inst; + rc = ziti_context_set_options(inst->ztx, opts); + if (rc != ZITI_OK) { + ZITI_LOG(ERROR, "ziti_context_set_options failed: %s", ziti_errorstr(rc)); + FREE(inst->ztx); + return rc; + } + + rc = set_tnlr_options(inst); + if (rc != ZITI_OK) { + FREE(inst->ztx); + } + + return rc; } -struct ziti_instance_s *new_ziti_instance_ex(const char *identifier) { - return new_ziti_instance(identifier, NULL); +int set_tnlr_options(struct ziti_instance_s *inst) { + ziti_options tunneler_ziti_options = { + .config_types = cfg_types, + .event_cb = on_ziti_event, // ensure ziti events are propagated (as tunnel events) via the command interface + .events = -1, + .refresh_interval = (long)refresh_interval, + .app_ctx = inst + }; + int rc = ziti_context_set_options(inst->ztx, &tunneler_ziti_options); + if (rc != ZITI_OK) { + ZITI_LOG(ERROR, "ziti_context_set_options failed: %s", ziti_errorstr(rc)); + FREE(inst->ztx); + } + + return rc; } void set_ziti_instance(const char *identifier, struct ziti_instance_s *inst) { @@ -717,17 +952,20 @@ static void on_ziti_event(ziti_context ztx, const ziti_event_t *event) { ZITI_LOG(ERROR, "something bad had happened: incorrect context"); } + const ziti_identity *identity = ziti_get_identity(ztx); + const char *ctx_name = identity ? identity->name : instance->identifier; + switch (event->type) { case ZitiContextEvent: { ziti_ctx_event ev = {0}; ev.event_type = TunnelEvents.ContextEvent; ev.identifier = instance->identifier; - ev.code = event->event.ctx.ctrl_status; - if (event->event.ctx.ctrl_status == ZITI_OK) { - ev.name = ziti_get_identity(ztx)->name; + ev.code = event->ctx.ctrl_status; + if (event->ctx.ctrl_status == ZITI_OK) { + ev.name = (char*)ctx_name; ev.version = ziti_get_controller_version(ztx)->version; - ev.controller = instance->opts.controller; - ZITI_LOG(INFO, "ziti_ctx[%s] connected to controller", ziti_get_identity(ztx)->name); + ev.controller = (char *) ziti_get_controller(ztx); + ZITI_LOG(INFO, "ziti_ctx[%s] connected to controller", ctx_name); ev.status = "OK"; const char *ctrl = ziti_get_controller(ztx); @@ -741,8 +979,8 @@ static void on_ziti_event(ziti_context ztx, const ziti_event_t *event) { } } else { - ZITI_LOG(WARN, "ziti_ctx controller connections failed: %s", ziti_errorstr(event->event.ctx.ctrl_status)); - ev.status = (char*)ziti_errorstr(event->event.ctx.ctrl_status); + ZITI_LOG(WARN, "ziti_ctx controller connections failed: %s", ziti_errorstr(event->ctx.ctrl_status)); + ev.status = (char*)ziti_errorstr(event->ctx.ctrl_status); } CMD_CTX.on_event((const base_event *) &ev); break; @@ -756,17 +994,17 @@ static void on_ziti_event(ziti_context ztx, const ziti_event_t *event) { }; bool send_event = false; - if (event->event.service.removed != NULL) { - ev.removed_services = event->event.service.removed; - for (zs = event->event.service.removed; *zs != NULL; zs++) { + if (event->service.removed != NULL) { + ev.removed_services = event->service.removed; + for (zs = event->service.removed; *zs != NULL; zs++) { send_event = true; on_service(ztx, *zs, ZITI_SERVICE_UNAVAILABLE, CMD_CTX.tunnel_ctx); } } - if (event->event.service.added != NULL) { - ev.added_services = event->event.service.added; - for (zs = event->event.service.added; *zs != NULL; zs++) { + if (event->service.added != NULL) { + ev.added_services = event->service.added; + for (zs = event->service.added; *zs != NULL; zs++) { send_event = true; on_service(ztx, *zs, ZITI_OK, CMD_CTX.tunnel_ctx); } @@ -777,11 +1015,11 @@ static void on_ziti_event(ziti_context ztx, const ziti_event_t *event) { CMD_CTX.on_event((const base_event *) &ev); } - if (event->event.service.changed != NULL) { - ev.added_services = event->event.service.changed; - ev.removed_services = event->event.service.changed; + if (event->service.changed != NULL) { + ev.added_services = event->service.changed; + ev.removed_services = event->service.changed; send_event = false; - for (zs = event->event.service.changed; *zs != NULL; zs++) { + for (zs = event->service.changed; *zs != NULL; zs++) { send_event = true; on_service(ztx, *zs, ZITI_OK, CMD_CTX.tunnel_ctx); } @@ -795,8 +1033,7 @@ static void on_ziti_event(ziti_context ztx, const ziti_event_t *event) { } case ZitiRouterEvent: { - const struct ziti_router_event *rt_event = &event->event.router; - const char *ctx_name = ziti_get_identity(ztx)->name; + const struct ziti_router_event *rt_event = &event->router; switch (rt_event->status) { case EdgeRouterAdded: ZITI_LOG(INFO, "ztx[%s] added edge router %s@%s", ctx_name, rt_event->name, rt_event->address); @@ -818,31 +1055,49 @@ static void on_ziti_event(ziti_context ztx, const ziti_event_t *event) { break; } - case ZitiMfaAuthEvent : { - const char *ctx_name = ziti_get_identity(ztx)->name; - ZITI_LOG(INFO, "ztx[%s] Mfa event received", ctx_name); - mfa_event ev = {0}; - ev.event_type = TunnelEvents.MFAEvent; - ev.identifier = instance->identifier; - ev.operation = mfa_status_name(mfa_status_auth_challenge); - CMD_CTX.on_event((const base_event *) &ev); + case ZitiAuthEvent : + if (event->auth.action == ziti_auth_prompt_totp) { + ZITI_LOG(INFO, "ztx[%s/%s] Mfa event received", instance->identifier, ctx_name); + mfa_event ev = {0}; + ev.event_type = TunnelEvents.MFAEvent; + ev.identifier = instance->identifier; + ev.operation = mfa_status_name(mfa_status_auth_challenge); + CMD_CTX.on_event((const base_event *) &ev); + } else if (event->auth.action == ziti_auth_login_external) { + ZITI_LOG(INFO, "ztx[%s/%s] ext auth event received", instance->identifier, ctx_name); + ext_signer_event ev = {0}; + ev.event_type = TunnelEvents.ExtJWTEvent; + ev.identifier = instance->identifier; + ev.status = "login_with_ext_signer"; + + ziti_jwt_signer *signer; + MODEL_LIST_FOREACH(signer, event->auth.providers) { + jwt_provider *provider = calloc(1, sizeof(*provider)); + provider->name = signer->name; + provider->issuer = signer->provider_url; + model_list_append(&ev.providers, provider); + } + CMD_CTX.on_event((const base_event *) &ev); + model_list_clear(&ev.providers, free); + } break; - } case ZitiAPIEvent: { - if (event->event.api.new_ctrl_address) { - if (instance->opts.config) { + if (event->api.new_ctrl_address || event->api.new_ca_bundle) { + if (instance->config_path) { api_update_req *req = calloc(1, sizeof(api_update_req)); req->wr.data = req; req->ztx = ztx; - req->new_url = strdup(event->event.api.new_ctrl_address); + req->new_url = event->api.new_ctrl_address ? strdup(event->api.new_ctrl_address) : NULL; + req->new_ca = event->api.new_ca_bundle ? strdup(event->api.new_ca_bundle) : NULL; uv_queue_work(CMD_CTX.loop, &req->wr, update_config, update_config_done); } api_event ev = {0}; ev.event_type = TunnelEvents.APIEvent; ev.identifier = instance->identifier; - ev.new_ctrl_address = event->event.api.new_ctrl_address; + ev.new_ctrl_address = event->api.new_ctrl_address; + ev.new_ca_bundle = event->api.new_ca_bundle; CMD_CTX.on_event((const base_event *) &ev); } else { ZITI_LOG(WARN, "unexpected API event: new_ctrl_address is missing"); @@ -856,26 +1111,22 @@ static void on_ziti_event(ziti_context ztx, const ziti_event_t *event) { } } -static void load_ziti_async(uv_loop_t *loop, void *arg) { - struct ziti_instance_s *inst = arg; - +static void load_ziti_async(uv_loop_t *loop, struct ziti_instance_s *inst) { tunnel_result result = { .success = true, .error = NULL, .code = IPC_SUCCESS, }; - char *config_path = realpath(inst->opts.config, NULL); - ZITI_LOG(INFO, "attempting to load ziti instance from file[%s]", inst->opts.config); + ZITI_LOG(INFO, "attempting to load ziti instance[%s]", inst->identifier); if (model_map_get(&instances, inst->identifier) != NULL) { - ZITI_LOG(WARN, "ziti context already loaded for %s", inst->opts.config); + ZITI_LOG(WARN, "ziti context already loaded for %s", inst->identifier); result.success = false; result.error = "context already loaded"; result.code = IPC_ERROR; } else { - ZITI_LOG(INFO, "loading ziti instance from %s", config_path); - inst->opts.app_ctx = inst; - if (ziti_init_opts(&inst->opts, loop) == ZITI_OK) { + ZITI_LOG(INFO, "loading ziti instance[%s]", inst->identifier); + if (ziti_context_run(inst->ztx, loop) == ZITI_OK) { model_map_set(&instances, inst->identifier, inst); } else { result.success = false; @@ -891,31 +1142,7 @@ static void load_ziti_async(uv_loop_t *loop, void *arg) { if (!result.success) { free(inst); } - - free(config_path); -} - -/* -static void on_mfa_query(ziti_context ztx, void* mfa_ctx, ziti_auth_query_mfa *aq_mfa, ziti_ar_mfa_cb response_cb) { - struct ziti_instance_s *inst = ziti_app_ctx(ztx); - - struct mfa_request_s *mfar = calloc(1, sizeof(struct mfa_request_s)); - mfar->ztx = ztx; - mfar->submit_f = response_cb; - mfar->submit_ctx = mfa_ctx; - - inst->mfa_req = mfar; - - mfa_event ev = {0}; - ev.event_type = TunnelEvents.MFAEvent; - ev.provider = strdup(aq_mfa->provider); - ev.identifier = strdup(inst->identifier); - - CMD_CTX.on_event((const base_event *) &ev); - - free_mfa_event(&ev); } - */ static void on_submit_mfa(ziti_context ztx, int status, void *ctx) { struct tunnel_cb_s *req = ctx; @@ -985,16 +1212,18 @@ static void on_enable_mfa(ziti_context ztx, int status, ziti_mfa_enrollment *enr if (status == ZITI_OK) { ev->operation_type = mfa_status_enrollment_challenge; ev->provisioning_url = strdup(enrollment->provisioning_url); - char **rc = enrollment->recovery_codes; + const char **rc = enrollment->recovery_codes; int size = 0; - while (*rc != NULL) { + while (rc != NULL && *rc != NULL) { rc++; size++; } ev->recovery_codes = calloc((size + 1), sizeof(char *)); - int idx; - for (idx=0; enrollment->recovery_codes[idx] !=0; idx++) { - ev->recovery_codes[idx] = strdup(enrollment->recovery_codes[idx]); + if(enrollment->recovery_codes != NULL) { + int idx; + for (idx = 0; enrollment->recovery_codes[idx] != 0; idx++) { + ev->recovery_codes[idx] = strdup(enrollment->recovery_codes[idx]); + } } } @@ -1075,7 +1304,7 @@ static void remove_mfa(ziti_context ztx, char *code, void *ctx) { ziti_mfa_remove(ztx, code, on_remove_mfa, ctx); } -static void on_mfa_recovery_codes(ziti_context ztx, int status, char **recovery_codes, void *ctx) { +static void on_mfa_recovery_codes(ziti_context ztx, int status, const char **recovery_codes, void *ctx) { struct tunnel_cb_s *req = ctx; tunnel_result result = {0}; if (status != ZITI_OK) { @@ -1088,7 +1317,7 @@ static void on_mfa_recovery_codes(ziti_context ztx, int status, char **recovery_ tunnel_mfa_recovery_codes mfa_recovery_codes = {0}; mfa_recovery_codes.identifier = strdup(req->ctx); - mfa_recovery_codes.recovery_codes = recovery_codes; + mfa_recovery_codes.recovery_codes = (model_string_array) recovery_codes; size_t json_len; result.data = tunnel_mfa_recovery_codes_to_json(&mfa_recovery_codes, MODEL_JSON_COMPACT, &json_len); @@ -1113,6 +1342,41 @@ static void get_mfa_codes(ziti_context ztx, char *code, void *ctx) { ziti_mfa_get_recovery_codes(ztx, code, on_mfa_recovery_codes, ctx); } +static void on_cmd_enroll(const ziti_config *cfg, int status, const char *err_message, void *ctx) { + struct tunnel_cb_s *req = ctx; + char *cfg_json = cfg ? ziti_config_to_json(cfg, MODEL_JSON_COMPACT, NULL) : NULL; + + tunnel_result result = { + .success = (status == ZITI_OK), + .error = (char*)err_message, + .code = status, + .data = cfg_json, + }; + + req->cmd_cb(&result, req->cmd_ctx); + free(cfg_json); +} + +static void on_ext_auth(ziti_context ztx, const char *url, void *ctx) { + struct tunnel_cb_s *req = ctx; + tunnel_result result = { + .success = true, + .code = IPC_SUCCESS, + }; + if (req->cmd_cb) { + tunnel_ext_auth auth = { + .identifier = (char*)req->ctx, + .ext_auth_url = (char*)url, + }; + result.data = tunnel_ext_auth_to_json(&auth, MODEL_JSON_COMPACT, NULL); + + req->cmd_cb(&result, req->cmd_ctx); + } + FREE(result.data); + FREE(req->ctx); + free(req); +} + #define CHECK(lbl, op) do{ \ int rc = (op); \ if (rc < 0) { \ @@ -1162,22 +1426,59 @@ static void on_sigdump(uv_signal_t *sig, int signum) { uv_gettimeofday(&dump_time); char time_str[32]; - struct tm* start_tm = gmtime(&dump_time.tv_sec); + time_t sec = (time_t)dump_time.tv_sec; + struct tm* start_tm = gmtime(&sec); strftime(time_str, sizeof(time_str), "%Y-%m-%dT%H:%M:%S", start_tm); - CHECK(cleanup, fprintf(dumpfile, "ZIti Dump starting: %s\n",time_str)); + CHECK(cleanup, fprintf(dumpfile, "Ziti Dump starting: %s\n", time_str)); const char *k; struct ziti_instance_s *inst; MODEL_MAP_FOREACH(k, inst, &instances) { CHECK(cleanup, fprintf(dumpfile, "instance: %s\n", k)); - ziti_dump(inst->ztx, (int (*)(void *, const char *, ...)) fprintf, dumpfile); + ziti_dump(inst->ztx, (dump_writer) fprintf, dumpfile); } + CHECK(cleanup, fprintf(dumpfile, "IP Dump starting: %s\n", time_str)); + tunnel_ip_stats stats = {0}; + ziti_tunnel_get_ip_stats(&stats); + ip_dump(&stats, (dump_writer) fprintf, dumpfile); + free_tunnel_ip_stats(&stats); + CHECK(cleanup, fflush(dumpfile)); cleanup: fclose(dumpfile); } + +static int update_file(const char *path, char *content, size_t content_len) { +#define CHECK_UV(desc, op) do{ \ + uv_fs_req_cleanup(&fs_req); \ + rc = op; \ + if (rc < 0) { \ + ZITI_LOG(ERROR, "op[" desc "] failed: %d(%s)", rc, uv_strerror(rc)); \ + goto DONE; \ + }} while(0) + + int rc = 0; + uv_fs_t fs_req = {0}; + CHECK_UV("check exiting config", uv_fs_stat(NULL, &fs_req, path, NULL)); + uint64_t mode = fs_req.statbuf.st_mode; + + char backup[FILENAME_MAX]; + snprintf(backup, sizeof(backup), "%s.bak", path); + CHECK_UV("create backup", uv_fs_rename(NULL, &fs_req, path, backup, NULL)); + + uv_os_fd_t f; + CHECK_UV("open new config", f = uv_fs_open(NULL, &fs_req, path, UV_FS_O_WRONLY | UV_FS_O_CREAT, (int) mode, NULL)); + uv_buf_t buf = uv_buf_init(content, content_len); + CHECK_UV("write new config", uv_fs_write(NULL, &fs_req, f, &buf, 1, 0, NULL)); + CHECK_UV("close new config", uv_fs_close(NULL, &fs_req, f, NULL)); + + DONE: + return rc; +#undef CHECK_UV +} + #define CHECK_UV(desc, op) do{ \ int rc = op; \ if (rc < 0) { \ @@ -1190,7 +1491,7 @@ goto DONE; \ static void update_config(uv_work_t *wr) { api_update_req *req = wr->data; struct ziti_instance_s *inst = ziti_app_ctx(req->ztx); - const char *config_file = inst->opts.config; + const char *config_file = inst->config_path; size_t cfg_len; char *cfg_buf = NULL; uv_file f; @@ -1200,7 +1501,6 @@ static void update_config(uv_work_t *wr) { cfg_len = fs_req.statbuf.st_size; cfg_buf = malloc(cfg_len); - uint64_t cfg_mode = fs_req.statbuf.st_mode; CHECK_UV("open existing config", f = uv_fs_open(wr->loop, &fs_req, config_file, UV_FS_O_RDONLY, 0, NULL)); uv_buf_t buf = uv_buf_init(cfg_buf, cfg_len); CHECK_UV("read existing config", uv_fs_read(wr->loop, &fs_req, f, &buf, 1, 0, NULL)); @@ -1215,21 +1515,35 @@ static void update_config(uv_work_t *wr) { } FREE(cfg_buf); - free(cfg.controller_url); - cfg.controller_url = req->new_url; - req->new_url = NULL; - - char backup[FILENAME_MAX]; + // attempt to update CA bundle external to config file + if (req->new_ca && strncmp(cfg.id.ca, "file://", strlen("file://")) == 0) { + struct tlsuv_url_s path_uri; + char path[FILENAME_MAX]; + CHECK_UV("parse CA bundle path", tlsuv_parse_url(&path_uri, cfg.id.ca)); + strncpy(path, path_uri.path, path_uri.path_len); + CHECK_UV("update CA bundle file", update_file(path, req->new_ca, strlen(req->new_ca))); + FREE(req->new_ca); + } - snprintf(backup, sizeof(backup), "%s.bak", config_file); - CHECK_UV("create backup", uv_fs_rename(wr->loop, &fs_req, config_file, backup, NULL)); + bool write_new_cfg = false; + if (req->new_url) { + free((char*)cfg.controller_url); + cfg.controller_url = req->new_url; + req->new_url = NULL; + write_new_cfg = true; + } - CHECK_UV("open new config", f = uv_fs_open(wr->loop, &fs_req, config_file, UV_FS_O_WRONLY | UV_FS_O_CREAT, (int)cfg_mode, NULL)); - cfg_buf = ziti_config_to_json(&cfg, 0, &cfg_len); - buf = uv_buf_init(cfg_buf, cfg_len); - CHECK_UV("write new config", uv_fs_write(wr->loop, &fs_req, f, &buf, 1, 0, NULL)); - CHECK_UV("close new config", uv_fs_close(wr->loop, &fs_req, f, NULL)); + if (req->new_ca) { + free((char*)cfg.id.ca); + cfg.id.ca = req->new_ca; + req->new_ca = NULL; + write_new_cfg = true; + } + if (write_new_cfg) { + cfg_buf = ziti_config_to_json(&cfg, 0, &cfg_len); + CHECK_UV("update config", update_file(config_file, cfg_buf, cfg_len)); + } DONE: free_ziti_config(&cfg); FREE(cfg_buf); @@ -1255,7 +1569,8 @@ IMPL_MODEL(tunnel_identity_info, TNL_IDENTITY_INFO) IMPL_MODEL(tunnel_identity_lst, TNL_IDENTITY_LIST) IMPL_MODEL(tunnel_on_off_identity, TNL_ON_OFF_IDENTITY) IMPL_MODEL(tunnel_ziti_dump, TNL_ZITI_DUMP) -IMPL_MODEL(tunnel_enable_mfa, TNL_ENABLE_MFA) +IMPL_MODEL(tunnel_ip_dump, TNL_IP_DUMP) +IMPL_MODEL(tunnel_identity_id, TNL_IDENTITY_ID) IMPL_MODEL(tunnel_mfa_enrol_res, TNL_MFA_ENROL_RES) IMPL_MODEL(tunnel_submit_mfa, TNL_SUBMIT_MFA) IMPL_MODEL(tunnel_verify_mfa, TNL_VERIFY_MFA) @@ -1263,9 +1578,7 @@ IMPL_MODEL(tunnel_remove_mfa, TNL_REMOVE_MFA) IMPL_MODEL(tunnel_generate_mfa_codes, TNL_GENERATE_MFA_CODES) IMPL_MODEL(tunnel_mfa_recovery_codes, TNL_MFA_RECOVERY_CODES) IMPL_MODEL(tunnel_get_mfa_codes, TNL_GET_MFA_CODES) -IMPL_MODEL(tunnel_get_identity_metrics, TNL_GET_IDENTITY_METRICS) IMPL_MODEL(tunnel_identity_metrics, TNL_IDENTITY_METRICS) -IMPL_MODEL(tunnel_delete_identity, TNL_DELETE_IDENTITY) IMPL_MODEL(tunnel_status_change, TUNNEL_STATUS_CHANGE) // ************** TUNNEL Events @@ -1278,3 +1591,6 @@ IMPL_MODEL(service_event, ZTX_SVC_EVENT_MODEL) IMPL_MODEL(api_event, ZTX_API_EVENT_MODEL) IMPL_MODEL(tunnel_command_inline, TUNNEL_CMD_INLINE) +IMPL_MODEL(jwt_provider, EXT_JWT_PROVIDER) +IMPL_MODEL(ext_signer_event, EXT_SIGNER_EVENT_MODEL) + diff --git a/lib/ziti-tunnel-cbs/ziti_tunnel_model.c b/lib/ziti-tunnel-cbs/ziti_tunnel_model.c new file mode 100644 index 00000000..8ba29c63 --- /dev/null +++ b/lib/ziti-tunnel-cbs/ziti_tunnel_model.c @@ -0,0 +1,26 @@ +/* + Copyright 2024 NetFoundry Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://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. + */ + +#include + +// ******* TUNNEL MODEL +IMPL_MODEL(tunnel_service_control, TUNNEL_SERVICE_CONTROL) +IMPL_MODEL(tunnel_set_log_level, TUNNEL_SET_LOG_LEVEL) +IMPL_MODEL(tunnel_tun_ip_v4, TUNNEL_TUN_IP_V4) +IMPL_MODEL(tunnel_add_identity, TUNNEL_ADD_IDENTITY) +IMPL_MODEL(tunnel_ext_auth, TUNNEL_EXT_AUTH) +IMPL_MODEL(tunnel_upstream_dns, TUNNEL_UPSTREAM_DNS) +IMPL_MODEL(tunnel_enroll, TNL_ENROLL) \ No newline at end of file diff --git a/lib/ziti-tunnel/CMakeLists.txt b/lib/ziti-tunnel/CMakeLists.txt index c2a99bbc..c6d79e67 100644 --- a/lib/ziti-tunnel/CMakeLists.txt +++ b/lib/ziti-tunnel/CMakeLists.txt @@ -26,7 +26,11 @@ else() set(lwip_sys_srcs ${LWIP_CONTRIB_DIR}/ports/unix/port/sys_arch.c) endif() +if (ANDROID) + set(platform_lwip_include ${CMAKE_CURRENT_SOURCE_DIR}/lwip/ports/android) +endif () set (LWIP_INCLUDE_DIRS + "${platform_lwip_include}" "${LWIP_DIR}/src/include" "${LWIP_CONTRIB_INCLUDE}" "${CMAKE_CURRENT_SOURCE_DIR}/lwip" @@ -46,7 +50,6 @@ target_include_directories(ziti-tunnel-sdk-c target_link_libraries(ziti-tunnel-sdk-c PUBLIC ziti PUBLIC lwipcore - PUBLIC ${tunnel_libuv_lib} ) #copy relevant .h files to the include folder diff --git a/lib/ziti-tunnel/include/ziti/ziti_tunnel.h b/lib/ziti-tunnel/include/ziti/ziti_tunnel.h index a4eb2864..3de80dcb 100644 --- a/lib/ziti-tunnel/include/ziti/ziti_tunnel.h +++ b/lib/ziti-tunnel/include/ziti/ziti_tunnel.h @@ -201,6 +201,32 @@ extern void ziti_tunnel_set_log_level(int lvl); typedef void (*ziti_tunnel_async_fn)(uv_loop_t *loop, void *ctx); extern void ziti_tunnel_async_send(tunneler_context tctx, ziti_tunnel_async_fn f, void *arg); + +#define TNL_IP_MEM_POOL(XX, ...) \ +XX(name, model_string, none, Name, __VA_ARGS__) \ +XX(max, model_number, none, Max, __VA_ARGS__) \ +XX(used, model_number, none, Used, __VA_ARGS__) \ +XX(avail, model_number, none, Avail, __VA_ARGS__) + +#define TNL_IP_CONN(XX, ...) \ +XX(protocol, model_string, none, Protocol, __VA_ARGS__) \ +XX(local_ip, model_string, none, LocalIP, __VA_ARGS__) \ +XX(local_port, model_number, none, LocalPort, __VA_ARGS__) \ +XX(remote_ip, model_string, none, RemoteIP, __VA_ARGS__) \ +XX(remote_port, model_number, none, RemotePort, __VA_ARGS__) \ +XX(state, model_string, none, State, __VA_ARGS__) \ +XX(service, model_string, none, Service, __VA_ARGS__) + +#define TNL_IP_STATS(XX, ...) \ +XX(pools, tunnel_ip_mem_pool, array, Pools, __VA_ARGS__) \ +XX(connections, tunnel_ip_conn, array, Connections, __VA_ARGS__) + +DECLARE_MODEL(tunnel_ip_mem_pool, TNL_IP_MEM_POOL) +DECLARE_MODEL(tunnel_ip_conn, TNL_IP_CONN) +DECLARE_MODEL(tunnel_ip_stats, TNL_IP_STATS) + +extern void ziti_tunnel_get_ip_stats(tunnel_ip_stats *stats); + #ifdef __cplusplus } #endif diff --git a/lib/ziti-tunnel/intercept.c b/lib/ziti-tunnel/intercept.c index c681253e..e409bfe7 100644 --- a/lib/ziti-tunnel/intercept.c +++ b/lib/ziti-tunnel/intercept.c @@ -53,7 +53,7 @@ void ziti_address_from_in6_addr(ziti_address *za, const struct in6_addr *a) { za->type = ziti_address_cidr; za->addr.cidr.af = AF_INET6; za->addr.cidr.bits = 128; - memcpy(&za->addr.cidr.ip, &a, sizeof(struct in6_addr)); + memcpy(&za->addr.cidr.ip, &a->s6_addr, sizeof(struct in6_addr)); } void ziti_address_from_sockaddr_in(ziti_address *za, const struct sockaddr_in *sin) { diff --git a/lib/ziti-tunnel/lwip/lwipopts.h b/lib/ziti-tunnel/lwip/lwipopts.h index 48d54d8b..b8e16f4a 100644 --- a/lib/ziti-tunnel/lwip/lwipopts.h +++ b/lib/ziti-tunnel/lwip/lwipopts.h @@ -34,7 +34,7 @@ #ifdef TCP_MSS #undef TCP_MSS /* cleanup warnings */ #endif -#define TCP_MSS 32768 /* TCP Maximum segment size (536) */ +#define TCP_MSS 16382 /* TCP Maximum segment size (536). 16382 avoids u16_t underflow in TCP_SNDLOWAT calculation */ #define TCP_SND_BUF (2*TCP_MSS) /* TCP sender buffer space in bytes (2 * TCP_MSS) */ #define TCP_SND_QUEUELEN 64 /* TCP sender buffer space in pbufs ((4 * (TCP_SND_BUF) + (TCP_MSS - 1))/(TCP_MSS)) */ // TCP_SNDQUEUELEN_OVERFLOW = 0xffffu - 3 @@ -51,10 +51,12 @@ // APIs #define LWIP_RAW 1 +#define LWIP_ARP 0 #define LWIP_NETCONN 0 #define LWIP_SOCKET 0 // protocols +#define LWIP_IPV6_MLD 0 #define LWIP_IPV6 1 /* enable ipv6 */ #define IPV6_FRAG_COPYHEADER 1 /* avoid assert in lwip code when ipv6 is enabled */ diff --git a/lib/ziti-tunnel/lwip/netif_shim.c b/lib/ziti-tunnel/lwip/netif_shim.c index 96124af7..28a89415 100644 --- a/lib/ziti-tunnel/lwip/netif_shim.c +++ b/lib/ziti-tunnel/lwip/netif_shim.c @@ -36,6 +36,13 @@ static err_t netif_shim_output(struct netif *netif, struct pbuf *p, const ip4_ad return ERR_OK; } +/** + * This function is called by the TCP/IP stack when an IP6 packet should be sent. + */ +static err_t netif_shim_output_ip6(struct netif *netif, struct pbuf *p, const ip6_addr_t *ipaddr) { + return netif_shim_output(netif, p, NULL); +} + /** * This function should be called when a packet is ready to be read * from the interface. It uses the function low_level_input() that @@ -93,6 +100,7 @@ err_t netif_shim_init(struct netif *netif) { netif->name[0] = IFNAME0; netif->name[1] = IFNAME1; netif->output = netif_shim_output; + netif->output_ip6 = netif_shim_output_ip6; return ERR_OK; } \ No newline at end of file diff --git a/lib/ziti-tunnel/lwip/ports/android/arch/cc.h b/lib/ziti-tunnel/lwip/ports/android/arch/cc.h new file mode 100644 index 00000000..11183225 --- /dev/null +++ b/lib/ziti-tunnel/lwip/ports/android/arch/cc.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2001-2003 Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Adam Dunkels + * + */ + +// openziti: tweaked for Android build + +#ifndef LWIP_ARCH_CC_H +#define LWIP_ARCH_CC_H + +/* see https://sourceforge.net/p/predef/wiki/OperatingSystems/ */ +#if defined __ANDROID__ +#define LWIP_UNIX_ANDROID +#elif defined __linux__ +#define LWIP_UNIX_LINUX +#elif defined __APPLE__ +#define LWIP_UNIX_MACH +#elif defined __OpenBSD__ +#define LWIP_UNIX_OPENBSD +#elif defined __CYGWIN__ +#define LWIP_UNIX_CYGWIN +#elif defined __GNU__ +#define LWIP_UNIX_HURD +#endif + +#define LWIP_TIMEVAL_PRIVATE 0 +#include + +#define LWIP_ERRNO_INCLUDE + +#if defined(LWIP_UNIX_LINUX) || defined(LWIP_UNIX_HURD) +#define LWIP_ERRNO_STDINCLUDE 1 +#endif + +#define LWIP_RAND() ((u32_t)rand()) + +/* different handling for unit test, normally not needed */ +#ifdef LWIP_NOASSERT_ON_ERROR +#define LWIP_ERROR(message, expression, handler) do { if (!(expression)) { \ + handler;}} while(0) +#endif + +#if defined(LWIP_UNIX_ANDROID) && !defined(FD_SET) +typedef __kernel_fd_set fd_set; +#endif + +#if defined(LWIP_UNIX_MACH) +/* sys/types.h and signal.h bring in Darwin byte order macros. pull the + header here and disable LwIP's version so that apps still can get + the macros via LwIP headers and use system headers */ +#include +#define LWIP_DONT_PROVIDE_BYTEORDER_FUNCTIONS +#endif + +struct sio_status_s; +typedef struct sio_status_s sio_status_t; +#define sio_fd_t sio_status_t* +#define __sio_fd_t_defined + +typedef unsigned int sys_prot_t; + +struct __res_state{}; + +#endif /* LWIP_ARCH_CC_H */ diff --git a/lib/ziti-tunnel/tests/address_test.cpp b/lib/ziti-tunnel/tests/address_test.cpp index 1e972e0b..965fc48c 100644 --- a/lib/ziti-tunnel/tests/address_test.cpp +++ b/lib/ziti-tunnel/tests/address_test.cpp @@ -88,4 +88,20 @@ TEST_CASE("address_match", "[address]") { REQUIRE(model_map_get(&tctx.intercepts_cache, "tcp:192.168.0.10:81") == intercept_s3); // todo hostname and wildcard dns matching +} + +TEST_CASE("address_conversion", "[address]") { + const char *ip6_str = "2768:8631:c02:ffc9::1308"; + ip_addr_t ip6; + ipaddr_aton(ip6_str, &ip6); + ziti_address za_from_ip6; + ziti_address_from_ip_addr(&za_from_ip6, &ip6); + + ziti_address za_from_str; + ziti_address_from_string(&za_from_str, ip6_str); + + char za_str[128]; + ziti_address_print(za_str, sizeof(za_str), &za_from_ip6); + fprintf(stderr, "%s converted to %s\n", ip6_str, za_str); + REQUIRE(ziti_address_match(&za_from_ip6, &za_from_str) == 0); } \ No newline at end of file diff --git a/lib/ziti-tunnel/tunnel_tcp.c b/lib/ziti-tunnel/tunnel_tcp.c index 716b7ed9..5497d0c6 100644 --- a/lib/ziti-tunnel/tunnel_tcp.c +++ b/lib/ziti-tunnel/tunnel_tcp.c @@ -25,8 +25,15 @@ #define MIN(a,b) ((a)<(b) ? (a) : (b)) #endif -#define LOG_STATE(level, op, pcb, ...) \ -TNL_LOG(level, op " %p, state=%d(%s) flags=%#0x", ##__VA_ARGS__, pcb, pcb->state, tcp_state_str(pcb->state), pcb->flags) +#define LOG_STATE(level, op, pcb, ...) do { \ + io_ctx_t *io = ((io_ctx_t *)(pcb)->callback_arg); \ + tunneler_io_context tnlr_io = io ? io->tnlr_io : NULL; \ + const char *service_name = tnlr_io ? tnlr_io->service_name : ""; \ + TNL_LOG(level, op " src[%s] dst[%s] state[%d/%s] flags[%#0x] service[%s]", ##__VA_ARGS__, \ + tnlr_io ? tnlr_io->client : "", \ + tnlr_io ? tnlr_io->intercepted : "", \ + pcb->state, tcp_state_str(pcb->state), pcb->flags, service_name); \ +} while (0) #define tcp_states(XX)\ XX(CLOSED)\ @@ -177,10 +184,10 @@ static void on_tcp_client_err(void *io_ctx, err_t err) { const char *client = ""; if (io->tnlr_io != NULL) { client = io->tnlr_io->client; + // null our pcb so tunneler_tcp_close doesn't try to close it. + io->tnlr_io->tcp = NULL; } TNL_LOG(ERR, "client=%s err=%d, terminating connection", client, err); - // null our pcb so tunneler_tcp_close doesn't try to close it. - io->tnlr_io->tcp = NULL; io->close_fn(io->ziti_io); } } @@ -197,7 +204,7 @@ ssize_t tunneler_tcp_write(struct tcp_pcb *pcb, const void *data, size_t len) { } // avoid ERR_MEM. size_t sendlen = MIN(len, tcp_sndbuf(pcb)); - TNL_LOG(TRACE, "pcb[%p] sendlen=%zd", pcb, sendlen); + LOG_STATE(TRACE, "sendlen=%zd", pcb, sendlen); if (sendlen > 0) { err_t w_err = tcp_write(pcb, data, (u16_t) sendlen, TCP_WRITE_FLAG_COPY); // TODO hold data until client acks... via on_client_ack maybe? then we wouldn't need to copy here. @@ -242,11 +249,12 @@ int tunneler_tcp_close(struct tcp_pcb *pcb) { return 0; } LOG_STATE(DEBUG, "closing", pcb); + tcp_arg(pcb, NULL); + tcp_recv(pcb, NULL); + tcp_err(pcb, NULL); if (pcb->state == CLOSED) { return 0; } - tcp_arg(pcb, NULL); - tcp_recv(pcb, NULL); if (pcb->state < ESTABLISHED) { TNL_LOG(DEBUG, "closing connection before handshake complete. sending RST to client"); tcp_abandon(pcb, 1); @@ -268,14 +276,18 @@ void tunneler_tcp_dial_completed(struct io_ctx_s *io, bool ok) { } struct tcp_pcb *pcb = io->tnlr_io->tcp; - tcp_arg(pcb, io); + if (pcb == NULL) { + TNL_LOG(ERR, "tcp connection with %s is no longer viable", io->tnlr_io->client); + // no need to close the ziti side here, since it was done in the tcp error callback. + return; + } + if (!ok) { TNL_LOG(VERBOSE, "ziti dial failed. not sending SYN to client."); return; } ip_set_option(pcb, SOF_KEEPALIVE); tcp_recv(pcb, on_tcp_client_data); - tcp_err(pcb, on_tcp_client_err); /* Send a SYN|ACK together with the MSS option. */ err_t rc = tcp_enqueue_flags(pcb, TCP_SYN | TCP_ACK); @@ -287,7 +299,7 @@ void tunneler_tcp_dial_completed(struct io_ctx_s *io, bool ok) { tcp_output(io->tnlr_io->tcp); } -static tunneler_io_context new_tunneler_io_context(tunneler_context tnlr_ctx, const char *service_name, struct tcp_pcb *pcb) { +static tunneler_io_context new_tunneler_io_context(tunneler_context tnlr_ctx, const char *service_name, const char *src, const char *dst, struct tcp_pcb *pcb) { struct tunneler_io_ctx_s *ctx = calloc(1, sizeof(struct tunneler_io_ctx_s)); if (ctx == NULL) { TNL_LOG(ERR, "failed to allocate tunneler_io_ctx"); @@ -295,8 +307,8 @@ static tunneler_io_context new_tunneler_io_context(tunneler_context tnlr_ctx, co } ctx->tnlr_ctx = tnlr_ctx; ctx->service_name = strdup(service_name); - snprintf(ctx->client, sizeof(ctx->client), "tcp:%s:%d", ipaddr_ntoa(&pcb->remote_ip), pcb->remote_port); - snprintf(ctx->intercepted, sizeof(ctx->intercepted), "tcp:%s:%d", ipaddr_ntoa(&pcb->local_ip), pcb->local_port); + snprintf(ctx->client, sizeof(ctx->client), "tcp:%s:%d", src, pcb->remote_port); + snprintf(ctx->intercepted, sizeof(ctx->intercepted), "tcp:%s:%d", dst, pcb->local_port); ctx->proto = tun_tcp; ctx->tcp = pcb; return ctx; @@ -339,12 +351,26 @@ u8_t recv_tcp(void *tnlr_ctx_arg, struct raw_pcb *pcb, struct pbuf *p, const ip_ struct tcp_hdr *tcphdr = (struct tcp_hdr *)((char*)p->payload + iphdr_hlen); u16_t src_p = lwip_ntohs(tcphdr->src); u16_t dst_p = lwip_ntohs(tcphdr->dest); + char src_str[IPADDR_STRLEN_MAX]; char dst_str[IPADDR_STRLEN_MAX]; + ipaddr_ntoa_r(&src, src_str, sizeof(src_str)); ipaddr_ntoa_r(&dst, dst_str, sizeof(dst_str)); - TNL_LOG(TRACE, "received segment %s:%d->%s:%d", - ipaddr_ntoa(&src), src_p, dst_str, dst_p); - u8_t flags = TCPH_FLAGS(tcphdr); + + if (tunnel_log_level >= TRACE) { + char flags_str[40] = {0}; + if (flags & TCP_FIN) strcat(flags_str, "FIN,"); + if (flags & TCP_SYN) strcat(flags_str, "SYN,"); + if (flags & TCP_RST) strcat(flags_str, "RST,"); + if (flags & TCP_PSH) strcat(flags_str, "PSH,"); + if (flags & TCP_ACK) strcat(flags_str, "ACK,"); + if (flags & TCP_URG) strcat(flags_str, "URG,"); + if (flags & TCP_ECE) strcat(flags_str, "ECE,"); + if (flags & TCP_CWR) strcat(flags_str, "CWR,"); + if (strlen(flags_str) > 0) flags_str[strlen(flags_str) - 1] = '\0'; // remove trailing comma + TNL_LOG(TRACE, "received segment src[tcp:%s:%d] dst[tcp:%s:%d] flags[%s]", src_str, src_p, dst_str, dst_p, flags_str); + } + if (!(flags & TCP_SYN)) { /* this isn't a SYN segment, so let lwip process it */ return 0; @@ -353,7 +379,7 @@ u8_t recv_tcp(void *tnlr_ctx_arg, struct raw_pcb *pcb, struct pbuf *p, const ip_ intercept_ctx_t *intercept_ctx = lookup_intercept_by_address(tnlr_ctx, "tcp", &dst, dst_p); if (intercept_ctx == NULL) { /* dst address is not being intercepted. don't consume */ - TNL_LOG(TRACE, "no intercepted addresses match tcp:%s:%d", ipaddr_ntoa(&dst), dst_p); + TNL_LOG(TRACE, "no intercepted addresses match tcp:%s:%d", dst_str, dst_p); return 0; } @@ -363,7 +389,7 @@ u8_t recv_tcp(void *tnlr_ctx_arg, struct raw_pcb *pcb, struct pbuf *p, const ip_ tpcb->local_port == dst_p && ip_addr_cmp(&tpcb->remote_ip, &src) && ip_addr_cmp(&tpcb->local_ip, &dst)) { - TNL_LOG(VERBOSE, "received SYN on active connection: client=tcp:%s:%d, service=%s", ipaddr_ntoa(&src), src_p, intercept_ctx->service_name); + TNL_LOG(VERBOSE, "received SYN on active connection: client=tcp:%s:%d, service=%s", src_str, src_p, intercept_ctx->service_name); /* Move this PCB to the front of the list so that subsequent lookups will be faster (we exploit locality in TCP segment arrivals). */ @@ -395,7 +421,7 @@ u8_t recv_tcp(void *tnlr_ctx_arg, struct raw_pcb *pcb, struct pbuf *p, const ip_ TNL_LOG(ERR, "failed to allocate io_context"); goto done; } - io->tnlr_io = new_tunneler_io_context(tnlr_ctx, intercept_ctx->service_name, npcb); + io->tnlr_io = new_tunneler_io_context(tnlr_ctx, intercept_ctx->service_name, src_str, dst_str, npcb); if (io->tnlr_io == NULL) { TNL_LOG(ERR, "failed to allocate tunneler io context"); goto done; @@ -405,7 +431,9 @@ u8_t recv_tcp(void *tnlr_ctx_arg, struct raw_pcb *pcb, struct pbuf *p, const ip_ io->close_write_fn = intercept_ctx->close_write_fn ? intercept_ctx->close_write_fn : tnlr_ctx->opts.ziti_close_write; io->close_fn = intercept_ctx->close_fn ? intercept_ctx->close_fn : tnlr_ctx->opts.ziti_close; - snprintf(io->tnlr_io->intercepted, sizeof(io->tnlr_io->intercepted), "tcp:%s:%d", ipaddr_ntoa(&dst), dst_p); + tcp_err(npcb, on_tcp_client_err); + tcp_arg(npcb, io); + TNL_LOG(DEBUG, "intercepted address[%s] client[%s] service[%s]", io->tnlr_io->intercepted, io->tnlr_io->client, intercept_ctx->service_name); void *ziti_io_ctx = zdial(intercept_ctx->app_intercept_ctx, io); @@ -417,6 +445,7 @@ u8_t recv_tcp(void *tnlr_ctx_arg, struct raw_pcb *pcb, struct pbuf *p, const ip_ /* now we wait for the tunneler app to call ziti_tunneler_dial_complete() */ done: + check_tnlr_timer(tnlr_ctx); pbuf_free(p); return 1; } @@ -440,4 +469,20 @@ struct io_ctx_list_s *tunneler_tcp_active(const void *zi_ctx) { } return l; -} \ No newline at end of file +} + +void tunneler_tcp_get_conn(tunnel_ip_conn *conn, struct tcp_pcb *pcb) { + if (!conn || !pcb) return; + conn->protocol = strdup("tcp"); + conn->local_ip = strdup(ipaddr_ntoa(&pcb->local_ip)); + conn->local_port = pcb->local_port; + conn->remote_ip = strdup(ipaddr_ntoa(&pcb->remote_ip)); + conn->remote_port = pcb->remote_port; + conn->state = strdup(tcp_state_str(pcb->state)); + const char *service = "unknown"; + struct io_ctx_s *io = pcb->callback_arg; + if (io && io->tnlr_io && io->tnlr_io->service_name) { + service = io->tnlr_io->service_name; + } + conn->service = strdup(service); +} diff --git a/lib/ziti-tunnel/tunnel_tcp.h b/lib/ziti-tunnel/tunnel_tcp.h index aa67d8bd..1d9f9f26 100644 --- a/lib/ziti-tunnel/tunnel_tcp.h +++ b/lib/ziti-tunnel/tunnel_tcp.h @@ -38,4 +38,6 @@ extern int tunneler_tcp_close_write(struct tcp_pcb *pcb); /** return list of io contexts for active connections to the given service. caller must free the returned pointer */ extern struct io_ctx_list_s *tunneler_tcp_active(const void *zi_ctx); +extern void tunneler_tcp_get_conn(tunnel_ip_conn *conn, struct tcp_pcb *pcb); + #endif //ZITI_TUNNELER_SDK_TUNNELER_TCP_H diff --git a/lib/ziti-tunnel/tunnel_udp.c b/lib/ziti-tunnel/tunnel_udp.c index 9ac32f86..fa79afb3 100644 --- a/lib/ziti-tunnel/tunnel_udp.c +++ b/lib/ziti-tunnel/tunnel_udp.c @@ -24,6 +24,11 @@ // initiate orderly shutdown static void udp_timeout_cb(uv_timer_t *t) { struct io_ctx_s *io = t->data; + tunneler_io_context tnlr_io = io->tnlr_io; + if (tnlr_io) { + TNL_LOG(TRACE, "initiating close idle_timeout[%d] src[%s] dst[%s] service[%s]", tnlr_io->idle_timeout, + tnlr_io->client, tnlr_io->intercepted, tnlr_io->service_name); + } io->close_fn(io->ziti_io); } @@ -36,17 +41,7 @@ static void to_ziti(struct io_ctx_s *io, struct pbuf *p) { return; } - struct pbuf *recv_data = NULL; - if (io->tnlr_io->udp.queued != NULL) { - if (p != NULL) { - pbuf_cat(io->tnlr_io->udp.queued, p); - } - recv_data = io->tnlr_io->udp.queued; - io->tnlr_io->udp.queued = NULL; - } else { - recv_data = p; - } - + struct pbuf *recv_data = p; if (recv_data == NULL) { TNL_LOG(TRACE, "no data to write"); return; @@ -55,10 +50,11 @@ static void to_ziti(struct io_ctx_s *io, struct pbuf *p) { uv_timer_start(io->tnlr_io->conn_timer, udp_timeout_cb, UDP_TIMEOUT, 0); do { - TNL_LOG(TRACE, "writing %d bytes to ziti", recv_data->len); + TNL_LOG(TRACE, "writing %d bytes to ziti src[%s] dst[%s] service[%s]", recv_data->len, + io->tnlr_io->client, io->tnlr_io->intercepted, io->tnlr_io->service_name); struct write_ctx_s *wr_ctx = calloc(1, sizeof(struct write_ctx_s)); wr_ctx->pbuf = recv_data; - wr_ctx->udp = io->tnlr_io->udp.pcb; + wr_ctx->udp = io->tnlr_io->udp; wr_ctx->ack = tunneler_udp_ack; recv_data = recv_data->next; @@ -67,7 +63,8 @@ static void to_ziti(struct io_ctx_s *io, struct pbuf *p) { if (s == ERR_WOULDBLOCK) { tunneler_udp_ack(wr_ctx); free(wr_ctx); - TNL_LOG(DEBUG, "ziti_write stalled: dropping UDP packet service=%s, client=%s, ret=%ld", io->tnlr_io->service_name, io->tnlr_io->client, s); + TNL_LOG(WARN, "ziti_write stalled: dropping UDP packet service=%s, client=%s, ret=%ld", io->tnlr_io->service_name, io->tnlr_io->client, s); + break; } else if (s < 0) { tunneler_udp_ack(wr_ctx); free(wr_ctx); @@ -78,26 +75,6 @@ static void to_ziti(struct io_ctx_s *io, struct pbuf *p) { } while (recv_data != NULL); } -/** called by lwip when a packet arrives from a connected client and the ziti service is not yet connected */ -void on_udp_client_data_enqueue(void *io_context, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port) { - if (io_context == NULL) { - TNL_LOG(DEBUG, "null io_context"); - return; - } - struct io_ctx_s *io_ctx = io_context; - tunneler_io_context tnlr_io_ctx = io_ctx->tnlr_io; - if (tnlr_io_ctx == NULL) { - TNL_LOG(INFO, "null tnlr_io_context"); - return; - } - if (tnlr_io_ctx->udp.queued == NULL) { - tnlr_io_ctx->udp.queued = p; - } else { - pbuf_chain(tnlr_io_ctx->udp.queued, p); - } - TNL_LOG(VERBOSE, "queued %d bytes", tnlr_io_ctx->udp.queued->len); -} - /** called by lwip when a packet arrives from a connected client and the ziti service is connected */ void on_udp_client_data(void *io_context, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port) { if (io_context == NULL) { @@ -106,6 +83,13 @@ void on_udp_client_data(void *io_context, struct udp_pcb *pcb, struct pbuf *p, c } TNL_LOG(VERBOSE, "%d bytes from %s:%d", p->len, ipaddr_ntoa(addr), port); + struct io_ctx_s *io = io_context; + if (!io->tnlr_io->conn_timer) { + io->tnlr_io->conn_timer = calloc(1, sizeof(uv_timer_t)); + io->tnlr_io->conn_timer->data = io; + uv_timer_init(io->tnlr_io->tnlr_ctx->loop, io->tnlr_io->conn_timer); + } + to_ziti(io_context, p); } @@ -116,28 +100,14 @@ void tunneler_udp_ack(struct write_ctx_s *write_ctx) { int tunneler_udp_close(struct udp_pcb *pcb) { struct io_ctx_s *io_ctx = pcb->recv_arg; tunneler_io_context tnlr_io_ctx = io_ctx->tnlr_io; - TNL_LOG(DEBUG, "closing %s session", tnlr_io_ctx->service_name); + TNL_LOG(DEBUG, "closing src[%s] dst[%s] service[%s]", + tnlr_io_ctx->client, tnlr_io_ctx->intercepted, tnlr_io_ctx->service_name); udp_remove(pcb); - if (tnlr_io_ctx->udp.queued != NULL) { - pbuf_free(tnlr_io_ctx->udp.queued); - tnlr_io_ctx->udp.queued = NULL; - } return 0; } void tunneler_udp_dial_completed(struct io_ctx_s *io, bool ok) { - struct udp_pcb *pcb = io->tnlr_io->udp.pcb; - /* change recv callback to send packets that arrive instead of queuing */ - udp_recv(pcb, on_udp_client_data, io); - - /* send any data that was queued while waiting for the dial to complete */ - if (ok) { - io->tnlr_io->conn_timer = calloc(1, sizeof(uv_timer_t)); - io->tnlr_io->conn_timer->data = io; - uv_timer_init(io->tnlr_io->tnlr_ctx->loop, io->tnlr_io->conn_timer); - - to_ziti(io, NULL); - } else { + if (!ok) { ziti_tunneler_close(io->tnlr_io); } } @@ -179,9 +149,11 @@ u8_t recv_udp(void *tnlr_ctx_arg, struct raw_pcb *pcb, struct pbuf *p, const ip_ struct udp_hdr *udphdr = (struct udp_hdr *)((char*)p->payload + iphdr_hlen); u16_t src_p = lwip_ntohs(udphdr->src); u16_t dst_p = lwip_ntohs(udphdr->dest); - - TNL_LOG(TRACE, "received datagram %s:%d->%s:%d", - ipaddr_ntoa(&src), src_p, ipaddr_ntoa(&dst), dst_p); + char src_str[IPADDR_STRLEN_MAX]; + char dst_str[IPADDR_STRLEN_MAX]; + ipaddr_ntoa_r(&src, src_str, sizeof(src_str)); + ipaddr_ntoa_r(&dst, dst_str, sizeof(dst_str)); + TNL_LOG(TRACE, "received datagram src[%s:%d] dst[%s:%d]", src_str, src_p, dst_str, dst_p); /* first see if this datagram belongs to an active connection */ for (struct udp_pcb *con_pcb = udp_pcbs, *prev = NULL; con_pcb != NULL; con_pcb = con_pcb->next) { @@ -205,7 +177,7 @@ u8_t recv_udp(void *tnlr_ctx_arg, struct raw_pcb *pcb, struct pbuf *p, const ip_ /* is the dest address being intercepted? */ intercept_ctx_t * intercept_ctx = lookup_intercept_by_address(tnlr_ctx, "udp", &dst, dst_p); if (intercept_ctx == NULL) { - TNL_LOG(TRACE, "no intercepted addresses match udp:%s:%d", ipaddr_ntoa(&dst), dst_p); + TNL_LOG(TRACE, "no intercepted addresses match udp:%s:%d", dst_str, dst_p); return 0; } @@ -222,7 +194,7 @@ u8_t recv_udp(void *tnlr_ctx_arg, struct raw_pcb *pcb, struct pbuf *p, const ip_ npcb->local_port = dst_p; err_t err = udp_connect(npcb, &src, src_p); if (err != ERR_OK) { - TNL_LOG(ERR, "failed to udp_connect %s:%d: err: %d", ipaddr_ntoa(&src), src_p, err); + TNL_LOG(ERR, "failed to udp_connect %s:%d: err: %d", src_str, src_p, err); udp_remove(npcb); pbuf_free(p); return 1; @@ -247,10 +219,9 @@ u8_t recv_udp(void *tnlr_ctx_arg, struct raw_pcb *pcb, struct pbuf *p, const ip_ io->tnlr_io->tnlr_ctx = tnlr_ctx; io->tnlr_io->proto = tun_udp; io->tnlr_io->service_name = strdup(intercept_ctx->service_name); - snprintf(io->tnlr_io->client, sizeof(io->tnlr_io->client), "udp:%s:%d", ipaddr_ntoa(&src), src_p); - snprintf(io->tnlr_io->intercepted, sizeof(io->tnlr_io->intercepted), "udp:%s:%d", ipaddr_ntoa(&dst), dst_p); - io->tnlr_io->udp.pcb = npcb; - io->tnlr_io->udp.queued = NULL; + snprintf(io->tnlr_io->client, sizeof(io->tnlr_io->client), "udp:%s:%d", src_str, src_p); + snprintf(io->tnlr_io->intercepted, sizeof(io->tnlr_io->intercepted), "udp:%s:%d", dst_str, dst_p); + io->tnlr_io->udp = npcb; io->ziti_ctx = intercept_ctx->app_intercept_ctx; io->write_fn = intercept_ctx->write_fn ? intercept_ctx->write_fn : tnlr_ctx->opts.ziti_write; io->close_fn = intercept_ctx->close_fn ? intercept_ctx->close_fn : tnlr_ctx->opts.ziti_close; @@ -259,7 +230,7 @@ u8_t recv_udp(void *tnlr_ctx_arg, struct raw_pcb *pcb, struct pbuf *p, const ip_ TNL_LOG(DEBUG, "intercepted address[%s] client[%s] service[%s]", io->tnlr_io->intercepted, io->tnlr_io->client, intercept_ctx->service_name); - udp_recv(npcb, on_udp_client_data_enqueue, io); + udp_recv(npcb, on_udp_client_data, io); void *ziti_io_ctx = zdial(intercept_ctx->app_intercept_ctx, io); if (ziti_io_ctx == NULL) { @@ -313,4 +284,20 @@ struct io_ctx_list_s *tunneler_udp_active(const void *zi_ctx) { } return l; -} \ No newline at end of file +} + +void tunneler_udp_get_conn(tunnel_ip_conn *conn, struct udp_pcb *pcb) { + if (!conn || !pcb) return; + conn->protocol = strdup("udp"); + conn->local_ip = strdup(ipaddr_ntoa(&pcb->local_ip)); + conn->local_port = pcb->local_port; + conn->remote_ip = strdup(ipaddr_ntoa(&pcb->remote_ip)); + conn->remote_port = pcb->remote_port; + conn->state = strdup(""); + const char *service = "unknown"; + struct io_ctx_s *io = pcb->recv_arg; + if (io && io->tnlr_io && io->tnlr_io->service_name) { + service = io->tnlr_io->service_name; + } + conn->service = strdup(service); +} diff --git a/lib/ziti-tunnel/tunnel_udp.h b/lib/ziti-tunnel/tunnel_udp.h index 5658b842..0e36405f 100644 --- a/lib/ziti-tunnel/tunnel_udp.h +++ b/lib/ziti-tunnel/tunnel_udp.h @@ -29,4 +29,6 @@ extern int tunneler_udp_close(struct udp_pcb *pcb); /** return list of io contexts for active connections to the given service. caller must free the returned pointer */ extern struct io_ctx_list_s *tunneler_udp_active(const void *zi_ctx); +extern void tunneler_udp_get_conn(tunnel_ip_conn *conn, struct udp_pcb *pcb); + #endif //ZITI_TUNNELER_SDK_TUNNELER_UDP_H diff --git a/lib/ziti-tunnel/ziti_tunnel.c b/lib/ziti-tunnel/ziti_tunnel.c index d0963165..f6f31c0b 100644 --- a/lib/ziti-tunnel/ziti_tunnel.c +++ b/lib/ziti-tunnel/ziti_tunnel.c @@ -196,7 +196,7 @@ void free_tunneler_io_context(tunneler_io_context *tnlr_io_ctx_p) { if (*tnlr_io_ctx_p != NULL) { tunneler_io_context io = *tnlr_io_ctx_p; - if (io->service_name != NULL) free(io->service_name); + if (io->service_name != NULL) free((char*)io->service_name); free(io); *tnlr_io_ctx_p = NULL; } @@ -418,7 +418,7 @@ ssize_t ziti_tunneler_write(tunneler_io_context tnlr_io_ctx, const void *data, s r = tunneler_tcp_write(tnlr_io_ctx->tcp, data, len); break; case tun_udp: - r = tunneler_udp_write(tnlr_io_ctx->udp.pcb, data, len); + r = tunneler_udp_write(tnlr_io_ctx->udp, data, len); break; } @@ -439,8 +439,8 @@ int ziti_tunneler_close(tunneler_io_context tnlr_io_ctx) { tnlr_io_ctx->tcp = NULL; break; case tun_udp: - tunneler_udp_close(tnlr_io_ctx->udp.pcb); - tnlr_io_ctx->udp.pcb = NULL; + tunneler_udp_close(tnlr_io_ctx->udp); + tnlr_io_ctx->udp = NULL; break; default: TNL_LOG(ERR, "unknown proto %d", tnlr_io_ctx->proto); @@ -486,8 +486,30 @@ static void on_tun_data(uv_poll_t * req, int status, int events) { } } -static void check_lwip_timeouts(uv_timer_t * handle) { +static void check_lwip_timeouts(uv_timer_t * timer) { + // if timer is not active it may have been a while since + // we run timers, let LWIP adjust timeouts + if (!uv_is_active((const uv_handle_t *) timer)) { + sys_restart_timeouts(); + } + + // run timers before potentially pausing sys_check_timeouts(); + + if (tcp_active_pcbs == NULL && tcp_tw_pcbs == NULL) { + uv_timer_stop(timer); + return; + } + + u32_t sleep = sys_timeouts_sleeptime(); + TNL_LOG(TRACE, "next wake in %d millis", sleep); + // second sleep arg is only need to make timer `active` next time + // we hit this function to avoid calling sys_restart_timeouts() + uv_timer_start(timer, check_lwip_timeouts, sleep, sleep); +} + +void check_tnlr_timer(tunneler_context tnlr_ctx) { + check_lwip_timeouts(&tnlr_ctx->lwip_timer_req); } /** @@ -556,9 +578,9 @@ static void run_packet_loop(uv_loop_t *loop, tunneler_context tnlr_ctx) { exit(1); } + // don't run LWIP timers until we have active TCP connections uv_timer_init(loop, &tnlr_ctx->lwip_timer_req); - uv_unref(&tnlr_ctx->lwip_timer_req); - uv_timer_start(&tnlr_ctx->lwip_timer_req, check_lwip_timeouts, 0, 10); + uv_unref((uv_handle_t *) &tnlr_ctx->lwip_timer_req); } typedef struct ziti_tunnel_async_call_s { @@ -619,6 +641,52 @@ void ziti_tunnel_async_send(tunneler_context tctx, ziti_tunnel_async_fn f, void #define _str(x) #x #define str(x) _str(x) + +IMPL_MODEL(tunnel_ip_mem_pool, TNL_IP_MEM_POOL) +IMPL_MODEL(tunnel_ip_conn, TNL_IP_CONN) +IMPL_MODEL(tunnel_ip_stats, TNL_IP_STATS) + +static void ziti_tunnel_get_ip_mem_pool(tunnel_ip_mem_pool *pool, int pool_id, const char *pool_name) { + if (!pool) return; + TNL_LOG(VERBOSE, "getting IP mem pool %s", pool_name); + pool->name = strdup(pool_name); + pool->used = memp_pools[pool_id]->stats->used; + pool->max = memp_pools[pool_id]->stats->max; + pool->avail = memp_pools[pool_id]->stats->avail; +} + +void ziti_tunnel_get_ip_stats(tunnel_ip_stats *stats) { + if (!stats) return; + TNL_LOG(DEBUG, "collecting ip statistics"); + if (stats->pools) free(stats->pools); + stats->pools = calloc(4, sizeof(tunnel_ip_mem_pool *)); + stats->pools[0] = calloc(1, sizeof(tunnel_ip_mem_pool)); + ziti_tunnel_get_ip_mem_pool(stats->pools[0], MEMP_PBUF_POOL, _str(MEMP_PBUF_POOL)); + stats->pools[1] = calloc(1, sizeof(tunnel_ip_mem_pool)); + ziti_tunnel_get_ip_mem_pool(stats->pools[1], MEMP_TCP_PCB, _str(MEMP_TCP_PCB)); + stats->pools[2] = calloc(1, sizeof(tunnel_ip_mem_pool)); + ziti_tunnel_get_ip_mem_pool(stats->pools[2], MEMP_UDP_PCB, _str(MEMP_UDP_PCB)); + + int max_conns = MEMP_NUM_TCP_PCB + MEMP_NUM_UDP_PCB + 1; + stats->connections = calloc(max_conns, sizeof(tunnel_ip_conn *)); + + int i= 0; + for (struct tcp_pcb *tpcb = tcp_tw_pcbs; tpcb != NULL; tpcb = tpcb->next) { + stats->connections[i] = calloc(1, sizeof(tunnel_ip_conn)); + tunneler_tcp_get_conn(stats->connections[i++], tpcb); + } + + for (struct tcp_pcb *tpcb = tcp_active_pcbs; tpcb != NULL; tpcb = tpcb->next) { + stats->connections[i] = calloc(1, sizeof(tunnel_ip_conn)); + tunneler_tcp_get_conn(stats->connections[i++], tpcb); + } + for (struct udp_pcb *upcb = udp_pcbs; upcb != NULL; upcb = upcb->next) { + stats->connections[i] = calloc(1, sizeof(tunnel_ip_conn)); + tunneler_udp_get_conn(stats->connections[i++], upcb); + } +} + + const char* ziti_tunneler_version() { return str(GIT_VERSION); } diff --git a/lib/ziti-tunnel/ziti_tunnel_priv.h b/lib/ziti-tunnel/ziti_tunnel_priv.h index c39160d3..644e3a9f 100644 --- a/lib/ziti-tunnel/ziti_tunnel_priv.h +++ b/lib/ziti-tunnel/ziti_tunnel_priv.h @@ -142,21 +142,19 @@ typedef enum { struct tunneler_io_ctx_s { tunneler_context tnlr_ctx; - const char *service_name; + char *service_name; char client[64]; char intercepted[64]; tunneler_proto_type proto; union { struct tcp_pcb *tcp; - struct { - struct udp_pcb *pcb; - struct pbuf *queued; - } udp; + struct udp_pcb *udp; }; uv_timer_t *conn_timer; uint32_t idle_timeout; }; +extern void check_tnlr_timer(tunneler_context tnlr_ctx); extern void free_tunneler_io_context(tunneler_io_context *tnlr_io_ctx_p); extern void free_intercept(intercept_ctx_t *intercept); diff --git a/package-repos.gpg b/package-repos.gpg index 5cee3489..4476fd12 100644 --- a/package-repos.gpg +++ b/package-repos.gpg @@ -9,33 +9,33 @@ OpMJNxvaVNFScXSlkqYvlFcLe3B/UhYPds9UTndeI3THnHb3v1r57pkadfp2r45Z 8nQdwoS+XLdU8S/S22XjKNv06GmEQja3G/QVcSnFsbo9Y9DzYyB9hIjRXIZjZ2bt Mn8YXcgYqyW1NIZy0/1oyZoNmXbTiQsgTK74gRpXLSIT/rQEFUy3+s/n+ZggQcYt phs02DQI0wXoJN0AEQEAAbQtT3BlblppdGkgRGV2ZWxvcGVycyA8ZGV2ZWxvcGVy -c0BvcGVueml0aS5vcmc+iQHUBBMBCgA+FiEENMvPGEJ9iBS1vbsN3jYj7wjJluUF -AmLMgtICGwMFCQPCZwAFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQ3jYj7wjJ -luVbDgwAoSffOAZT35se4fxUNzlQkDWAN1rvopxgXPRubT5SFMRCzEfaF4YteQL4 -zI4ibNwNyVJ9PLDVEpxG8Voxlnc4gXQLxaKMVXCC2A/D+HvMyrbTwYj+dcWSZtSH -Qjs7pzefRji1B7do/szyPP3DaItOXsF709oAMyZuxq226BmRHa2BHNgUooj8Rrk+ -m4kW6PIyMCJ1gClFEjY32JGIAKvpP/riUMjFIEzOV0WlEYTV6xf97OE558V4AMaw -SW4MSnER7kBSWKWBVv9s6+BqHLRwFcnR5ZJvkuEmZT0tnJiW52hXJiP//A7KcCDd -e9ioU/tBx/uhdE2diZhpHv+PHzW62U+cT+6T/5EWadNPliIdIQt9y93gEIYVfyHB -Ly6fEMJZBf1mhBFvDAc/6tmZamEgD7N4vix6Z0o+5U5fRY9jWORxka5fDWX5HN+t -pmEf2F0qXN2oNobJE1RPhBBR99JojQaVoRpI6EthbhMVc7XsNNSMG6XglSE3p1K1 -tqf51dmMuQGNBGLMgtIBDADiUbMpm9jyARnA9gHbziEOfC89skWJvjT5nuMFoQSg -Gpu/Tpgrc42JHrJtFv6J+5gmkFunX1HOcymqdMLS99OLFx+/1D/1JiWYoffsNiWu -qxAOGC8MhvvLRsv4ahP8F/hHnl+zgUaPKglO1yUSdOE3EalV1H+nCE6iLHCF2iZc -9+voNdul9xIJfJAlykdpfgY5+3RN3l6tFrX6BxEzlZsH6eM/V/iFsQubOkM6JXge -4s5lySimblvr1HSfDr84+dvyF+QnJ8QOJDD7B9oXpoG4lG9a8GFK16wLUq3n+3w7 -x4zs515s5CjKPVZBsNLgIqmXitGSOFHSWYqZA+8oHl6XzpbVBik9wQZuzJwLnTiN -uvbx5ndXgvYUAVNwKUZpO5phEM6F166qb8MQmVTyTWUB1lC6/xbSlP83g4l2mwOb -YqsKCRyEqAqK0GdG2dO397JHPCibi/PmyjSaQRzeHh2LWnC8YM/hqwChdMooP73v -5gnxZ+3ltUeSR0sK+OyxR98AEQEAAYkBvAQYAQoAJhYhBDTLzxhCfYgUtb27Dd42 -I+8IyZblBQJizILSAhsMBQkDwmcAAAoJEN42I+8IyZblHBAL/0VrLlKDFoy91rPA -F44+RFdGoQ7wb+t7uR41821Q2C3wcz2R9+e1/0/SUsnu1KG9twUsiFbgxMyI8tj4 -1BlAKdmIHvjLVVdasx/tP/CjahUID0ecbd+g0HtfRVNN18Z1amPgQe8ztaHQhH0n -EmC7wLR4Z4VJjhJmSgyG19GEOmPrsHJxmSYtISyZ84eqkMauQpV+m2Ipmw5gCxvz -Hz+3yK53LRh7Yr9utRnyyssiCGyepYSeXwZ4teJg7CbZEqT7GIjj8A+Jpumqc1UJ -roX0wFHN9l5mjEIck8QCLBFNeysDfI+HAlqnCpsoMDBufdBTgYZtKDCv640FQS4l -VMyZVdLhJ2AYeq2dlcSpCi/qeKX+Ik23k9hq69wVyuwWhenGAeAj8Po8jhzq43Bn -FGJ0QVYloHa3CWUQVycWjkt7N3GYhVa9RPS3HHj/XwHYUWI0PpE2rUrCtHC62Ecq -JOr5kDFAFInkdZouGw09jHGc0rZV8qVeHUkiVxgvkZjalyw+qw== -=zftM +c0BvcGVueml0aS5vcmc+iQHOBBMBCgA4AhsDBQsJCAcDBRUKCQgLBRYCAwEAAh4B +AheAFiEENMvPGEJ9iBS1vbsN3jYj7wjJluUFAmT3gMYACgkQ3jYj7wjJluXxbwv9 +Ge1iagTLnM54a9oJZC5+4tODj0yKpxLvM6FOYR7lZlvKhxLLJdcObvlNDXutrqko +ujGDg3qPlAKVmCgLfceVhNPtjFKO0IH3AMxZmpjnowezqm4AlPCBXY6gDbxB58WO +trQKVHEL7C52ae0Q+pBvzOE19KU1ggQBJS3oPWjiM5S+vSy+PYrasCLM1NSD9ONj +tIekJ7LhUD2vdumr26felMZ7p147vpxiO1jBGxV5m/HYV39QOibTLMcxM2wzKNCC +vHnmx3t5LmVlNqiH1NaQH4qeZIQDjqHc21tRSR30OUKjClqiBtxOGIttRa2yaZWQ +Xj8/5+1WSBMyE6zL3106QYwu5yf+gjHwm0IO+qciM4BwhHq+wulLocPlyQOlV+Wn +LkZizml4UP+BlJ26E90pgvtN1sIF88PyCdpq0KlPUa38F7wK7ucu4y31p9SGQoWJ +XiA4gJ0z80NTHNm87BbicvuM/Gn1XARGkHLenlblJw0qNOaZ0JRoNlb83ds7ZWj/ +uQGNBGLMgtIBDADiUbMpm9jyARnA9gHbziEOfC89skWJvjT5nuMFoQSgGpu/Tpgr +c42JHrJtFv6J+5gmkFunX1HOcymqdMLS99OLFx+/1D/1JiWYoffsNiWuqxAOGC8M +hvvLRsv4ahP8F/hHnl+zgUaPKglO1yUSdOE3EalV1H+nCE6iLHCF2iZc9+voNdul +9xIJfJAlykdpfgY5+3RN3l6tFrX6BxEzlZsH6eM/V/iFsQubOkM6JXge4s5lySim +blvr1HSfDr84+dvyF+QnJ8QOJDD7B9oXpoG4lG9a8GFK16wLUq3n+3w7x4zs515s +5CjKPVZBsNLgIqmXitGSOFHSWYqZA+8oHl6XzpbVBik9wQZuzJwLnTiNuvbx5ndX +gvYUAVNwKUZpO5phEM6F166qb8MQmVTyTWUB1lC6/xbSlP83g4l2mwObYqsKCRyE +qAqK0GdG2dO397JHPCibi/PmyjSaQRzeHh2LWnC8YM/hqwChdMooP73v5gnxZ+3l +tUeSR0sK+OyxR98AEQEAAYkBtgQYAQoAIAIbDBYhBDTLzxhCfYgUtb27Dd42I+8I +yZblBQJk94EDAAoJEN42I+8IyZblQzoL/imuppfaVcMQ4Qe0c+JEV0NQKwLIgnUy +bky26yyQ8DCOZUYGNQJORLXrWthSWWz6TBldfwoYOlof2qgOYpV/EEOo8l8uuqqP +CPQQI47mKF0As49RsWssEaRTqRpzIUgU/W2vpcub3b+NSzO4N2hBFvTx+SfSCLTC +53x1hP7QPR5BMiWGJ0ti9YIhJAWn65DmA33MUh0DoNeQW2+k5g35sHLREWsG/qB4 +nai7HrkKPIrI/J4eAwYSS2pxyT1IJ00YnTkjziICeZqxVBeox6d3zrYOrR3RB/rq +29beypZMZLqV3N5O/lOk6Mf6aaVdC484TYiqD7RwGNNGRJgT/BOElYG+Pj3i3yDD +cAIW8uxIQRVvasqJNZfPfIiVYVFQotivFWEyElXCPFdq2RTBDZ87Lyb1m1lkxLQf +vGibIWaL0W7L7CmeWQLpw8eAUiPX9RZMYD8dTwt9LGcXy7jS3FgEKxvlsrRpB8hn +n805lmL41AqzItzehi/DGxwjZpC6lYxQBA== +=vK0d -----END PGP PUBLIC KEY BLOCK----- diff --git a/programs/CMakeLists.txt b/programs/CMakeLists.txt index cb62d5e2..3855e606 100644 --- a/programs/CMakeLists.txt +++ b/programs/CMakeLists.txt @@ -1,2 +1,15 @@ + +if (NOT TARGET subcommand) + FetchContent_Declare(subcommand + GIT_REPOSITORY https://github.com/openziti/subcommands.c.git + GIT_TAG main + ) + FetchContent_GetProperties(subcommand) + if (NOT subcommand_POPULATED) + FetchContent_Populate(subcommand) + endif () + add_library(subcommand INTERFACE) + target_include_directories(subcommand INTERFACE ${subcommand_SOURCE_DIR}) +endif () + add_subdirectory(ziti-edge-tunnel) -add_subdirectory(tests) \ No newline at end of file diff --git a/programs/tests/CMakeLists.txt b/programs/tests/CMakeLists.txt deleted file mode 100644 index 77ec37c0..00000000 --- a/programs/tests/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -project(tests) - -add_executable(ziti-edge-tunnel-test ziti-edge-tunnel-test.c) -set_property(TARGET ziti-edge-tunnel-test PROPERTY C_STANDARD 11) - -target_link_libraries(ziti-edge-tunnel-test - PUBLIC ${tunnel_libuv_lib} - ) - -install(TARGETS ziti-edge-tunnel-test - DESTINATION ${CMAKE_INSTALL_BINDIR} - ) \ No newline at end of file diff --git a/programs/tests/ziti-edge-tunnel-test.c b/programs/tests/ziti-edge-tunnel-test.c deleted file mode 100644 index cf27c3c8..00000000 --- a/programs/tests/ziti-edge-tunnel-test.c +++ /dev/null @@ -1,88 +0,0 @@ -/* - Copyright 2019-2021 NetFoundry Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://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. - */ - -#include -#include -#include -#include "uv.h" - -#if _WIN32 -static char eventsockfile[] = "\\\\.\\pipe\\ziti-edge-tunnel-event.sock"; -#elif __unix__ || unix || ( __APPLE__ && __MACH__ ) -static char eventsockfile[] = "/tmp/ziti-edge-tunnel-event.sock"; -#endif - - -static void cmd_alloc(uv_handle_t *s, size_t sugg, uv_buf_t *b) { - b->base = malloc(sugg); - b->len = sugg; -} - -static void on_response(uv_stream_t *s, ssize_t len, const uv_buf_t *b) { - if (len > 0) { - printf("received response <%.*s>\n", (int) len, b->base); - } else { - fprintf(stderr,"Read Response error %s\n", uv_err_name(len)); - } -} - -void on_connect(uv_connect_t* connect, int status){ - if (status < 0) { - puts("failed to connect!"); - } else { - puts("connected!"); - int res = uv_read_start((uv_stream_t *) connect->handle, cmd_alloc, on_response); - if (res != 0) { - printf("UV read error %s\n", uv_err_name(res)); - } - } -} - -static uv_loop_t* connect_and_read_message(char sockfile[],uv_connect_t* connect, uv_pipe_t* client_handle) { - uv_loop_t* loop = uv_default_loop(); - - int res = uv_pipe_init(loop, client_handle, 0); - if (res != 0) { - printf("UV client handle init failed %s\n", uv_err_name(res)); - return NULL; - } - - uv_pipe_connect(connect, client_handle, sockfile, on_connect); - - return loop; -} - -int main(int argc, char *argv[]) { - uv_pipe_t client_handle; - uv_connect_t* connect = (uv_connect_t*)malloc(sizeof(uv_connect_t)); - - uv_loop_t* loop = connect_and_read_message(eventsockfile, connect, &client_handle); - - if (loop == NULL) { - printf("Cannot run UV loop, loop is null"); - return 1; - } - - int res = uv_run(loop, UV_RUN_DEFAULT); - if (res != 0) { - printf("UV run error %s\n", uv_err_name(res)); - return 1; - } - uv_close((uv_handle_t *)&client_handle, NULL); - return 0; -} - - diff --git a/programs/ziti-edge-tunnel/CMakeLists.txt b/programs/ziti-edge-tunnel/CMakeLists.txt index d5939d5f..dcf9d299 100644 --- a/programs/ziti-edge-tunnel/CMakeLists.txt +++ b/programs/ziti-edge-tunnel/CMakeLists.txt @@ -10,29 +10,49 @@ endif() if(CMAKE_SYSTEM_NAME STREQUAL Windows) include(wintun.cmake) set(tun_lib wintun) - configure_file(${wintun_SOURCE_DIR}/bin/${CMAKE_SYSTEM_PROCESSOR}/wintun.dll ${CMAKE_CURRENT_BINARY_DIR}/wintun.dll COPYONLY) - set(ziti-edge-tunnel_BUNDLE_COMPS ${CMAKE_CURRENT_BINARY_DIR}/wintun.dll) + set(wintun_dll "${wintun_SOURCE_DIR}/bin/${CMAKE_SYSTEM_PROCESSOR}/wintun.dll") set(NETIF_DRIVER_SOURCE netif_driver/windows/tun.c netif_driver/windows/tun.h) endif() if (MSVC) - message("using visual studio") - FetchContent_Declare(win-c - GIT_REPOSITORY https://github.com/netfoundry/win-c.git - GIT_TAG master - ) - FetchContent_MakeAvailable(win-c) - set(getopt libwinc) -endif() + find_package(unofficial-getopt-win32 REQUIRED) + set(getopt unofficial::getopt-win32::getopt) +endif () -set(ZITI_INSTANCE_COMMON include/model/events.h include/model/dtos.h instance.c include/identity-utils.h config-utils.c include/config-utils.h instance-config.c include/instance-config.h) +set(ZITI_INSTANCE_COMMON + include/model/events.h + include/model/dtos.h + instance.c + include/identity-utils.h + config-utils.c + include/config-utils.h + instance-config.c + include/instance-config.h +) if (WIN32) - set(ZITI_INSTANCE_OS windows-service.c include/windows/windows-service.h include/windows/windows-scripts.h windows-scripts.c windows/log_utils.c include/service-utils.h) + set(ZITI_INSTANCE_OS + windows-service.c + include/windows/windows-service.h + include/windows/windows-scripts.h + windows-scripts.c + windows/log_utils.c + include/service-utils.h) endif () add_executable(ziti-edge-tunnel ziti-edge-tunnel.c ${NETIF_DRIVER_SOURCE} ${ZITI_INSTANCE_COMMON} ${ZITI_INSTANCE_OS}) set_property(TARGET ziti-edge-tunnel PROPERTY C_STANDARD 11) +if(CMAKE_SYSTEM_NAME STREQUAL Windows) + # copy wintun.dll to the directory that contains ziti-edge-tunnel.exe + add_custom_command( + TARGET ziti-edge-tunnel POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${wintun_dll}" "$" + COMMENT "Copying ${wintun_dll} to $" + ) + # bundle components are relative to the target's build directory. no directory is needed here since we copied the dll to the build directory. + set(ziti-edge-tunnel_BUNDLE_COMPS wintun.dll) +endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") option(DISABLE_LIBSYSTEMD_FEATURE "libsystemd library integration toggle" OFF) message("DISABLE_LIBSYSTEMD_FEATURE: ${DISABLE_LIBSYSTEMD_FEATURE}") diff --git a/programs/ziti-edge-tunnel/config-utils.c b/programs/ziti-edge-tunnel/config-utils.c index 347aec54..97abf355 100644 --- a/programs/ziti-edge-tunnel/config-utils.c +++ b/programs/ziti-edge-tunnel/config-utils.c @@ -28,11 +28,11 @@ static char* identifier_path = NULL; char* get_system_config_path() { char* config_path = malloc(FILENAME_MAX * sizeof(char)); #if _WIN32 - sprintf(config_path, "%s/NetFoundry", getenv(app_data)); + snprintf(config_path, FILENAME_MAX, "%s\\NetFoundry", getenv(app_data)); #elif __linux__ - sprintf(config_path, "/var/lib/ziti"); + snprintf(config_path, FILENAME_MAX, "/var/lib/ziti"); #else - sprintf(config_path, "/tmp"); + snprintf(config_path, FILENAME_MAX, "/tmp"); #endif return config_path; } diff --git a/programs/ziti-edge-tunnel/include/identity-utils.h b/programs/ziti-edge-tunnel/include/identity-utils.h index 7a73006d..8e6ad390 100644 --- a/programs/ziti-edge-tunnel/include/identity-utils.h +++ b/programs/ziti-edge-tunnel/include/identity-utils.h @@ -34,19 +34,19 @@ extern "C" { extern tunnel_identity *find_tunnel_identity(const char* identifier); -extern tunnel_identity *create_or_get_tunnel_identity(const char* identifier, char* fingerprint) ; +extern tunnel_identity *create_or_get_tunnel_identity(const char* identifier, const char* fingerprint) ; -extern void set_mfa_status(char* identifier, bool mfa_enabled, bool mfa_needed); +extern void set_mfa_status(const char* identifier, bool mfa_enabled, bool mfa_needed); -extern void update_mfa_time(char* identifier); +extern void update_mfa_time(const char* identifier); extern tunnel_service *get_tunnel_service(tunnel_identity* identifier, ziti_service* zs); -extern tunnel_service *find_tunnel_service(tunnel_identity* id, char* svc_id); +extern tunnel_service *find_tunnel_service(tunnel_identity* id, const char* svc_id); extern void add_or_remove_services_from_tunnel(tunnel_identity *id, tunnel_service_array added_services, tunnel_service_array removed_services); -extern bool load_tunnel_status(char* config_data); +extern bool load_tunnel_status(const char* config_data); extern tunnel_status *get_tunnel_status(); @@ -54,7 +54,7 @@ extern tunnel_identity_array get_tunnel_identities(); extern int get_remaining_timeout(int timeout, int timeout_rem, tunnel_identity *tnl_id); -void delete_identity_from_instance(char* identifier); +void delete_identity_from_instance(const char* identifier); void set_ip_info(uint32_t dns_ip, uint32_t tun_ip, int bits); @@ -66,13 +66,13 @@ const char* get_log_level_label(); int get_log_level(const char* log_level); -void set_ziti_status(bool enabled, char* identifier); +void set_ziti_status(bool enabled, const char* identifier); -void set_tun_ipv4_into_instance(char* tun_ip, int prefixLength, bool addDns); +void set_tun_ipv4_into_instance(const char* tun_ip, int prefixLength, bool addDns); char* get_ip_range_from_config(); -char* get_dns_ip(); +const char* get_dns_ip(); bool get_add_dns_flag(); diff --git a/programs/ziti-edge-tunnel/include/model/dtos.h b/programs/ziti-edge-tunnel/include/model/dtos.h index 6471829e..4762636d 100644 --- a/programs/ziti-edge-tunnel/include/model/dtos.h +++ b/programs/ziti-edge-tunnel/include/model/dtos.h @@ -24,96 +24,97 @@ extern "C" { #endif #define TUNNEL_CONFIG(XX, ...) \ -XX(ZtAPI, string, none, ztAPI, __VA_ARGS__) \ -XX(ConfigTypes, string, array, ConfigTypes, __VA_ARGS__) +XX(ZtAPI, model_string, none, ztAPI, __VA_ARGS__) \ +XX(ConfigTypes, model_string, array, ConfigTypes, __VA_ARGS__) #define TUNNEL_METRICS(XX, ...) \ -XX(Up, int, none, Up, __VA_ARGS__) \ -XX(Down, int, none, Down, __VA_ARGS__) +XX(Up, model_number, none, Up, __VA_ARGS__) \ +XX(Down, model_number, none, Down, __VA_ARGS__) #define TUNNEL_IDENTITY(XX, ...) \ -XX(Name, string, none, Name, __VA_ARGS__) \ -XX(Identifier, string, none, Identifier, __VA_ARGS__) \ -XX(FingerPrint, string, none, FingerPrint, __VA_ARGS__) \ -XX(Active, bool, none, Active, __VA_ARGS__) \ -XX(Loaded, bool, none, Loaded, __VA_ARGS__) \ +XX(Name, model_string, none, Name, __VA_ARGS__) \ +XX(Identifier, model_string, none, Identifier, __VA_ARGS__) \ +XX(FingerPrint, model_string, none, FingerPrint, __VA_ARGS__) \ +XX(Active, model_bool, none, Active, __VA_ARGS__) \ +XX(Loaded, model_bool, none, Loaded, __VA_ARGS__) \ XX(Config, tunnel_config, ptr, Config, __VA_ARGS__) \ -XX(ControllerVersion, string, none, ControllerVersion, __VA_ARGS__) \ -XX(IdFileStatus, bool, none, IdFileStatus, __VA_ARGS__) \ -XX(MfaEnabled, bool, none, MfaEnabled, __VA_ARGS__) \ -XX(MfaNeeded, bool, none, MfaNeeded, __VA_ARGS__) \ +XX(ControllerVersion, model_string, none, ControllerVersion, __VA_ARGS__) \ +XX(IdFileStatus, model_bool, none, IdFileStatus, __VA_ARGS__) \ +XX(NeedsExtAuth, model_bool, none, NeedsExtAuth, __VA_ARGS__) \ +XX(MfaEnabled, model_bool, none, MfaEnabled, __VA_ARGS__) \ +XX(MfaNeeded, model_bool, none, MfaNeeded, __VA_ARGS__) \ XX(Services, tunnel_service, array, Services, __VA_ARGS__) \ XX(Metrics, tunnel_metrics, none, Metrics, __VA_ARGS__) \ -XX(Tags, string, array, Tags, __VA_ARGS__) \ -XX(MfaMinTimeout, int, none, MfaMinTimeout, __VA_ARGS__) \ -XX(MfaMaxTimeout, int, none, MfaMaxTimeout, __VA_ARGS__) \ -XX(MfaMinTimeoutRem, int, none, MfaMinTimeoutRem, __VA_ARGS__) \ -XX(MfaMaxTimeoutRem, int, none, MfaMaxTimeoutRem, __VA_ARGS__) \ -XX(MinTimeoutRemInSvcEvent, int, none, MinTimeoutRemInSvcEvent, __VA_ARGS__) \ -XX(MaxTimeoutRemInSvcEvent, int, none, MaxTimeoutRemInSvcEvent, __VA_ARGS__) \ +XX(Tags, model_string, array, Tags, __VA_ARGS__) \ +XX(MfaMinTimeout, model_number, none, MfaMinTimeout, __VA_ARGS__) \ +XX(MfaMaxTimeout, model_number, none, MfaMaxTimeout, __VA_ARGS__) \ +XX(MfaMinTimeoutRem, model_number, none, MfaMinTimeoutRem, __VA_ARGS__) \ +XX(MfaMaxTimeoutRem, model_number, none, MfaMaxTimeoutRem, __VA_ARGS__) \ +XX(MinTimeoutRemInSvcEvent, model_number, none, MinTimeoutRemInSvcEvent, __VA_ARGS__) \ +XX(MaxTimeoutRemInSvcEvent, model_number, none, MaxTimeoutRemInSvcEvent, __VA_ARGS__) \ XX(MfaLastUpdatedTime, timestamp, ptr, MfaLastUpdatedTime, __VA_ARGS__) \ XX(ServiceUpdatedTime, timestamp, ptr, ServiceUpdatedTime, __VA_ARGS__) \ -XX(Deleted, bool, none, Deleted, __VA_ARGS__) \ -XX(Notified, bool, none, Notified, __VA_ARGS__) +XX(Deleted, model_bool, none, Deleted, __VA_ARGS__) \ +XX(Notified, model_bool, none, Notified, __VA_ARGS__) #define TUNNEL_ADDRESS(XX, ...) \ -XX(IsHost, bool, none, IsHost, __VA_ARGS__) \ -XX(HostName, string, none, HostName, __VA_ARGS__) \ -XX(IP, string, none, IP, __VA_ARGS__) \ -XX(Prefix, int, none, Prefix, __VA_ARGS__) +XX(IsHost, model_bool, none, IsHost, __VA_ARGS__) \ +XX(HostName, model_string, none, HostName, __VA_ARGS__) \ +XX(IP, model_string, none, IP, __VA_ARGS__) \ +XX(Prefix, model_number, none, Prefix, __VA_ARGS__) #define TUNNEL_PORT_RANGE(XX, ...) \ -XX(High, int, none, High, __VA_ARGS__) \ -XX(Low, int, none, Low, __VA_ARGS__) +XX(High, model_number, none, High, __VA_ARGS__) \ +XX(Low, model_number, none, Low, __VA_ARGS__) #define TUNNEL_POSTURE_CHECK(XX, ...) \ -XX(IsPassing, bool, none, IsPassing, __VA_ARGS__) \ -XX(QueryType, string, none, QueryType, __VA_ARGS__) \ -XX(Id, string, none, Id, __VA_ARGS__) \ -XX(Timeout, int, none, Timeout, __VA_ARGS__) \ -XX(TimeoutRemaining, int, none, TimeoutRemaining, __VA_ARGS__) +XX(IsPassing, model_bool, none, IsPassing, __VA_ARGS__) \ +XX(QueryType, model_string, none, QueryType, __VA_ARGS__) \ +XX(Id, model_string, none, Id, __VA_ARGS__) \ +XX(Timeout, model_number, none, Timeout, __VA_ARGS__) \ +XX(TimeoutRemaining, model_number, none, TimeoutRemaining, __VA_ARGS__) #define TUNNEL_SERVICE_PERMISSIONS(XX,...) \ -XX(Bind, bool, none, Bind, __VA_ARGS__) \ -XX(Dial, bool, none, Dial, __VA_ARGS__) +XX(Bind, model_bool, none, Bind, __VA_ARGS__) \ +XX(Dial, model_bool, none, Dial, __VA_ARGS__) #define TUNNEL_SERVICE(XX, ...) \ -XX(Id, string, none, Id, __VA_ARGS__) \ -XX(Name, string, none, Name, __VA_ARGS__) \ -XX(Protocols, string, array, Protocols, __VA_ARGS__) \ +XX(Id, model_string, none, Id, __VA_ARGS__) \ +XX(Name, model_string, none, Name, __VA_ARGS__) \ +XX(Protocols, model_string, array, Protocols, __VA_ARGS__) \ XX(Addresses, tunnel_address, array, Addresses, __VA_ARGS__) \ XX(AllowedSourceAddresses, tunnel_address, array, AllowedSourceAddresses, __VA_ARGS__) \ XX(Ports, tunnel_port_range, array, Ports, __VA_ARGS__) \ -XX(OwnsIntercept, bool, none, OwnsIntercept, __VA_ARGS__) \ +XX(OwnsIntercept, model_bool, none, OwnsIntercept, __VA_ARGS__) \ XX(PostureChecks, tunnel_posture_check, array, PostureChecks, __VA_ARGS__) \ -XX(IsAccessible, bool, none, IsAccessible, __VA_ARGS__) \ -XX(Timeout, int, none, Timeout, __VA_ARGS__) \ -XX(TimeoutRemaining, int, none, TimeoutRemaining, __VA_ARGS__) \ +XX(IsAccessible, model_bool, none, IsAccessible, __VA_ARGS__) \ +XX(Timeout, model_number, none, Timeout, __VA_ARGS__) \ +XX(TimeoutRemaining, model_number, none, TimeoutRemaining, __VA_ARGS__) \ XX(Permissions, tunnel_service_permissions , none, Permissions, __VA_ARGS__) #define TUNNEL_STATUS(XX, ...) \ -XX(Active, bool, none, Active, __VA_ARGS__) \ -XX(Duration, int, none, Duration, __VA_ARGS__) \ +XX(Active, model_bool, none, Active, __VA_ARGS__) \ +XX(Duration, model_number, none, Duration, __VA_ARGS__) \ XX(StartTime, timestamp, none, StartTime, __VA_ARGS__) \ XX(Identities, tunnel_identity, array, Identities, __VA_ARGS__) \ XX(IpInfo, ip_info, ptr, IpInfo, __VA_ARGS__) \ -XX(LogLevel, string, none, LogLevel, __VA_ARGS__) \ +XX(LogLevel, model_string, none, LogLevel, __VA_ARGS__) \ XX(ServiceVersion, service_version, ptr, ServiceVersion, __VA_ARGS__) \ -XX(TunIpv4, string, none, TunIpv4, __VA_ARGS__) \ -XX(TunPrefixLength, int, none, TunIpv4Mask, __VA_ARGS__) \ -XX(AddDns, bool, none, AddDns, __VA_ARGS__) \ -XX(ApiPageSize, int, none, ApiPageSize, __VA_ARGS__) +XX(TunIpv4, model_string, none, TunIpv4, __VA_ARGS__) \ +XX(TunPrefixLength, model_number, none, TunIpv4Mask, __VA_ARGS__) \ +XX(AddDns, model_bool, none, AddDns, __VA_ARGS__) \ +XX(ApiPageSize, model_number, none, ApiPageSize, __VA_ARGS__) #define IP_INFO(XX, ...) \ -XX(Ip, string, none, Ip, __VA_ARGS__) \ -XX(Subnet, string, none, Subnet, __VA_ARGS__) \ -XX(MTU, int, none, MTU, __VA_ARGS__) \ -XX(DNS, string, none, DNS, __VA_ARGS__) +XX(Ip, model_string, none, Ip, __VA_ARGS__) \ +XX(Subnet, model_string, none, Subnet, __VA_ARGS__) \ +XX(MTU, model_number, none, MTU, __VA_ARGS__) \ +XX(DNS, model_string, none, DNS, __VA_ARGS__) #define SERVICE_VERSION(XX, ...) \ -XX(Version, string, none, Version, __VA_ARGS__) \ -XX(Revision, string, none, Revision, __VA_ARGS__) \ -XX(BuildDate, string, none, BuildDate, __VA_ARGS__) +XX(Version, model_string, none, Version, __VA_ARGS__) \ +XX(Revision, model_string, none, Revision, __VA_ARGS__) \ +XX(BuildDate, model_string, none, BuildDate, __VA_ARGS__) DECLARE_MODEL(tunnel_config, TUNNEL_CONFIG) DECLARE_MODEL(tunnel_metrics, TUNNEL_METRICS) diff --git a/programs/ziti-edge-tunnel/include/model/events.h b/programs/ziti-edge-tunnel/include/model/events.h index fc1ebbf7..79ef9a0e 100644 --- a/programs/ziti-edge-tunnel/include/model/events.h +++ b/programs/ziti-edge-tunnel/include/model/events.h @@ -25,13 +25,13 @@ extern "C" { #endif #define STATUS_EVENT(XX, ...) \ -XX(Op, string, none, Op, __VA_ARGS__) +XX(Op, model_string, none, Op, __VA_ARGS__) #define ACTION_EVENT(XX, ...) \ STATUS_EVENT(XX, __VA_ARGS__) \ -XX(Action, string, none, Action, __VA_ARGS__) \ -XX(Identifier, string, none, Identifier, __VA_ARGS__) \ -XX(Fingerprint, string, none, Fingerprint, __VA_ARGS__) +XX(Action, model_string, none, Action, __VA_ARGS__) \ +XX(Identifier, model_string, none, Identifier, __VA_ARGS__) \ +XX(Fingerprint, model_string, none, Fingerprint, __VA_ARGS__) #define TUNNEL_STATUS_EVENT(XX, ...) \ STATUS_EVENT(XX, __VA_ARGS__) \ @@ -48,23 +48,23 @@ XX(RemovedServices, tunnel_service, array, RemovedServices, __VA_ARGS__) #define MFA_STATUS_EVENT(XX, ...) \ ACTION_EVENT(XX, __VA_ARGS__) \ -XX(Successful, bool, none, Successful, __VA_ARGS__) \ -XX(Error, string, none, Error, __VA_ARGS__) \ -XX(ProvisioningUrl, string, none, ProvisioningUrl, __VA_ARGS__) \ -XX(RecoveryCodes, string, array, RecoveryCodes, __VA_ARGS__) +XX(Successful, model_bool, none, Successful, __VA_ARGS__) \ +XX(Error, model_string, none, Error, __VA_ARGS__) \ +XX(ProvisioningUrl, model_string, none, ProvisioningUrl, __VA_ARGS__) \ +XX(RecoveryCodes, model_string, array, RecoveryCodes, __VA_ARGS__) #define TUNNEL_METRICS_EVENT(XX, ...) \ STATUS_EVENT(XX, __VA_ARGS__) \ XX(Identities, tunnel_identity, array, Identities, __VA_ARGS__) #define TUNNEL_NOTIFICATION_MESSAGE(XX, ...) \ -XX(IdentityName, string, none, IdentityName, __VA_ARGS__) \ -XX(Identifier, string, none, Identifier, __VA_ARGS__) \ -XX(Fingerprint, string, none, Identifier, __VA_ARGS__) \ -XX(Message, string, none, Message, __VA_ARGS__) \ -XX(MfaMinimumTimeout, int, none, MfaMinimumTimeout, __VA_ARGS__) \ -XX(MfaMaximumTimeout, int, none, MfaMaximumTimeout, __VA_ARGS__) \ -XX(MfaTimeDuration, int, none, MfaTimeDuration, __VA_ARGS__) \ +XX(IdentityName, model_string, none, IdentityName, __VA_ARGS__) \ +XX(Identifier, model_string, none, Identifier, __VA_ARGS__) \ +XX(Fingerprint, model_string, none, Identifier, __VA_ARGS__) \ +XX(Message, model_string, none, Message, __VA_ARGS__) \ +XX(MfaMinimumTimeout, model_number, none, MfaMinimumTimeout, __VA_ARGS__) \ +XX(MfaMaximumTimeout, model_number, none, MfaMaximumTimeout, __VA_ARGS__) \ +XX(MfaTimeDuration, model_number, none, MfaTimeDuration, __VA_ARGS__) \ XX(Severity, event_severity, none, Severity, __VA_ARGS__) #define TUNNEL_NOTIFICATION_EVENT(XX, ...) \ @@ -84,7 +84,8 @@ XX(bulk, __VA_ARGS__) \ XX(error, __VA_ARGS__) \ XX(changed, __VA_ARGS__) \ XX(normal, __VA_ARGS__) \ -XX(connected, __VA_ARGS__) \ +XX(connected, __VA_ARGS__) \ +XX(needs_ext_login, __VA_ARGS__) \ XX(disconnected, __VA_ARGS__) DECLARE_ENUM(event_severity, EVENT_SEVERITY) diff --git a/programs/ziti-edge-tunnel/include/windows/windows-scripts.h b/programs/ziti-edge-tunnel/include/windows/windows-scripts.h index 93cb2d33..5454e4ca 100644 --- a/programs/ziti-edge-tunnel/include/windows/windows-scripts.h +++ b/programs/ziti-edge-tunnel/include/windows/windows-scripts.h @@ -31,9 +31,9 @@ void add_nrpt_rules(uv_loop_t *nrpt_loop, model_map *hostnames, const char* dns_ip); void remove_nrpt_rules(uv_loop_t *nrpt_loop, model_map *hostnames); void remove_all_nrpt_rules(); -bool is_nrpt_policies_effective(char* tns_ip); +bool is_nrpt_policies_effective(const char* tns_ip); void remove_and_add_nrpt_rules(uv_loop_t *nrpt_loop, model_map *hostnames, const char* dns_ip); -void update_interface_metric(uv_loop_t *ziti_loop, char* tun_name, int metric); +void update_interface_metric(uv_loop_t *ziti_loop, wchar_t* tun_name, int metric); void update_symlink(uv_loop_t *symlink_loop, char* symlink, char* filename); #endif //ZITI_TUNNEL_SDK_C_WINDOWS_SCRIPTS_H diff --git a/programs/ziti-edge-tunnel/include/windows/windows-service.h b/programs/ziti-edge-tunnel/include/windows/windows-service.h index 6a3d4996..0b6bdcfc 100644 --- a/programs/ziti-edge-tunnel/include/windows/windows-service.h +++ b/programs/ziti-edge-tunnel/include/windows/windows-service.h @@ -23,7 +23,7 @@ VOID SvcInit( DWORD, LPTSTR * ); VOID SvcReportEvent( LPTSTR, DWORD ); VOID SvcDelete(void); DWORD WINAPI ServiceWorkerThread (LPVOID lpParam); -DWORD LphandlerFunctionEx( +DWORD WINAPI LphandlerFunctionEx( DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, diff --git a/programs/ziti-edge-tunnel/instance-config.c b/programs/ziti-edge-tunnel/instance-config.c index c4d738de..be22a0dc 100644 --- a/programs/ziti-edge-tunnel/instance-config.c +++ b/programs/ziti-edge-tunnel/instance-config.c @@ -48,7 +48,6 @@ bool load_config_from_file(char* config_file_name) { if (strlen(config_buffer) > 0) { loaded = load_tunnel_status(config_buffer); - config_buffer[0] = '\0'; if (!loaded) { ZITI_LOG(WARN, "Config file %s cannot be read, will be overwritten", config_file_name); } @@ -162,7 +161,7 @@ bool save_tunnel_status_to_file() { free(config_file_name); free(bkp_config_file_name); free(config_path); - } + } free(tunnel_status); return saved; } diff --git a/programs/ziti-edge-tunnel/instance.c b/programs/ziti-edge-tunnel/instance.c index 58d1a063..1ea7dbeb 100644 --- a/programs/ziti-edge-tunnel/instance.c +++ b/programs/ziti-edge-tunnel/instance.c @@ -15,7 +15,6 @@ */ #include "model/dtos.h" -#include #include #include #include @@ -46,13 +45,10 @@ tunnel_identity *find_tunnel_identity(const char* identifier) { * file name will be passed to this function, if this called by the load identities function. * file name will be null, if this is called any other time */ -tunnel_identity *create_or_get_tunnel_identity(const char* identifier, char* filename) { +tunnel_identity *create_or_get_tunnel_identity(const char* identifier, const char* filename) { tunnel_identity *id = find_tunnel_identity(identifier); if (id != NULL) { - if (filename != NULL) { - id->IdFileStatus = true; - } return id; } else { tunnel_identity *tnl_id = calloc(1, sizeof(struct tunnel_identity_s)); @@ -66,17 +62,15 @@ tunnel_identity *create_or_get_tunnel_identity(const char* identifier, char* fil } else { length = strlen(filename); } - tnl_id->FingerPrint = calloc(length + 1, sizeof(char)); - char fingerprint[FILENAME_MAX]; - memcpy(&fingerprint, filename, length); - fingerprint[length] = '\0'; - snprintf(tnl_id->FingerPrint, length+1, "%s", fingerprint); - - tnl_id->Name = calloc(length + 1, sizeof(char)); - snprintf(tnl_id->Name, length+1, "%s", fingerprint); + char *fingerprint = calloc(length + 1, sizeof(char)); + snprintf(fingerprint, length+1, "%s", filename); + tnl_id->FingerPrint = fingerprint; - tnl_id->IdFileStatus = true; + char *name = calloc(length + 1, sizeof(char)); + snprintf(name, length+1, "%s", fingerprint); + tnl_id->Name = name; + tnl_id->Active = true; } model_map_set(&tnl_identity_map, identifier, tnl_id); return tnl_id; @@ -96,20 +90,20 @@ void set_mfa_timeout(tunnel_identity *tnl_id) { if (tnl_svc->Timeout > -1) { if (mfa_min_timeout == -1 || mfa_min_timeout > tnl_svc->Timeout) { - mfa_min_timeout = tnl_svc->Timeout; + mfa_min_timeout = (int)tnl_svc->Timeout; } if (mfa_max_timeout == -1 || mfa_max_timeout < tnl_svc->Timeout) { - mfa_max_timeout = tnl_svc->Timeout; + mfa_max_timeout = (int)tnl_svc->Timeout; } } else { no_timeout_svc = true; } if (tnl_svc->TimeoutRemaining > -1) { if (mfa_min_timeout_rem == -1 || mfa_min_timeout_rem > tnl_svc->TimeoutRemaining) { - mfa_min_timeout_rem = tnl_svc->TimeoutRemaining; + mfa_min_timeout_rem = (int)tnl_svc->TimeoutRemaining; } if (mfa_max_timeout_rem == -1 || mfa_max_timeout_rem < tnl_svc->TimeoutRemaining) { - mfa_max_timeout_rem = tnl_svc->TimeoutRemaining; + mfa_max_timeout_rem = (int)tnl_svc->TimeoutRemaining; } } else { no_timeout_svc_rem = true; @@ -229,12 +223,12 @@ static void setTunnelPostureDataTimeout(tunnel_service *tnl_svc, ziti_service *s model_map_set(&postureCheckMap, pq->id, pq); } - int timeoutRemaining = *pqs->posture_queries[posture_query_idx]->timeoutRemaining; + int timeoutRemaining = (int)*pqs->posture_queries[posture_query_idx]->timeoutRemaining; if ((minTimeoutRemaining == -1) || (timeoutRemaining < minTimeoutRemaining)) { minTimeoutRemaining = timeoutRemaining; } - int timeout = pqs->posture_queries[posture_query_idx]->timeout; + int timeout = (int)pqs->posture_queries[posture_query_idx]->timeout; if ((minTimeout == -1) || (timeout < minTimeout)) { minTimeout = timeout; } @@ -266,8 +260,9 @@ static tunnel_address *to_address(const ziti_address *za) { if (za->type == ziti_address_cidr) { tnl_address->IsHost = false; tnl_address->HostName = NULL; - tnl_address->IP = calloc(INET_ADDRSTRLEN+1, sizeof(char)); - uv_inet_ntop(za->addr.cidr.af, &za->addr.cidr.ip, tnl_address->IP, INET_ADDRSTRLEN); + char *ip = calloc(INET_ADDRSTRLEN + 1, sizeof(char)); + tnl_address->IP = ip; + uv_inet_ntop(za->addr.cidr.af, &za->addr.cidr.ip, ip, INET_ADDRSTRLEN); tnl_address->Prefix = (int) za->addr.cidr.bits; ZITI_LOG(TRACE, "IP address: %s", tnl_address->IP); } else { @@ -328,7 +323,7 @@ static void setTunnelAllowedSourceAddress(tunnel_service *tnl_svc, ziti_service static void setTunnelServiceAddress(tunnel_service *tnl_svc, ziti_service *service) { const char* cfg_json = ziti_service_get_raw_config(service, CFG_INTERCEPT_V1); tunnel_address_array tnl_addr_arr = NULL; - string_array protocols = NULL; + model_string_array protocols = NULL; tunnel_port_range_array tnl_port_range_arr; if (cfg_json != NULL && strlen(cfg_json) > 0) { ZITI_LOG(TRACE, "intercept.v1: %s", cfg_json); @@ -395,7 +390,7 @@ static void setTunnelServiceAddress(tunnel_service *tnl_svc, ziti_service *servi tnl_svc->Protocols = protocols; } -tunnel_service *find_tunnel_service(tunnel_identity* id, char* svc_id) { +tunnel_service *find_tunnel_service(tunnel_identity* id, const char* svc_id) { int idx = 0; tunnel_service *svc = NULL; if (id->Services != NULL) { @@ -432,9 +427,7 @@ tunnel_identity_array get_tunnel_identities() { int idx = 0; MODEL_MAP_FOREACH(id, tnl_id, &tnl_identity_map) { - if (tnl_id->IdFileStatus) { - tnl_id_arr[idx++] = tnl_id; - } + tnl_id_arr[idx++] = tnl_id; } return tnl_id_arr; @@ -458,7 +451,6 @@ tunnel_identity_array get_tunnel_identities_for_metrics() { id_new->Active = id->Active; id_new->Loaded = id->Loaded; id_new->Metrics = id->Metrics; - id_new->IdFileStatus = id->IdFileStatus; tnl_id_arr[i] = id_new; } free(arr); @@ -534,6 +526,44 @@ void set_mfa_timeout_rem(tunnel_identity *tnl_id) { } +void remove_duplicate_path_separators(char *str, char target) { + if (str == NULL || *str == '\0') return; + + char *write = str; // where to write the next char to + char *next = str; // the next char in the string to check + + while (*next) { + *write = *next; //copy the next char to the write position + if (*next == target) { + while (*(next + 1) == target) { // keep reading until no longer matching + next++; + } + } + write++; + next++; + } + *write = '\0'; +} + +void normalize_identifier(char *str) { + char* init_pos = str; +#if _WIN32 + // this is only fine because windows doesn't allow slashes in file/directory names so any `/` should be converted to `\` + char find = '/'; + char replace = PATH_SEP; + if (str == NULL) return; + + for (; *str != '\0'; str++) { + if (*str == find) { + *str = replace; + } + } +#else + return; // nothing to normalize at this time +#endif + remove_duplicate_path_separators(init_pos, PATH_SEP); +} + /* * while loading data from the config file that is generated by WDE, Identifier will be empty and Fingerprint will be present */ @@ -550,33 +580,26 @@ void set_identifier_from_identities() { } if (tnl_id->Identifier != NULL) { // set this field to false during initialization - tnl_id->IdFileStatus = false; + normalize_identifier((char*)tnl_id->Identifier); model_map_set(&tnl_identity_map, tnl_id->Identifier, tnl_id); } //on startup - set mfa needed to false to correctly reflect tunnel status. After the identity is loaded these //are set to true __if necessary__ - tnl_id->MfaEnabled = false; tnl_id->MfaNeeded = false; } } void initialize_tunnel_status() { - tnl_status.Active = true; tnl_status.Duration = 0; uv_timeval64_t now; uv_gettimeofday(&now); - tnl_status.StartTime.tv_sec = now.tv_sec; + tnl_status.StartTime.tv_sec = (long)now.tv_sec; tnl_status.StartTime.tv_usec = now.tv_usec; - if (tnl_status.ApiPageSize < MIN_API_PAGESIZE) { - tnl_status.ApiPageSize = DEFAULT_API_PAGESIZE; - } - if (tnl_status.LogLevel == NULL) { - tnl_status.LogLevel = "info"; - } + tnl_status.ApiPageSize = DEFAULT_API_PAGESIZE; } -bool load_tunnel_status(char* config_data) { +bool load_tunnel_status(const char* config_data) { if (parse_tunnel_status(&tnl_status, config_data, strlen(config_data)) < 0) { ZITI_LOG(ERROR, "Could not read tunnel status from config data"); initialize_tunnel_status(); @@ -593,7 +616,7 @@ bool load_tunnel_status(char* config_data) { tnl_status.IpInfo = NULL; } if (tnl_status.TunIpv4) { - free(tnl_status.TunIpv4); + free((char*)tnl_status.TunIpv4); tnl_status.TunIpv4 = NULL; } if (tnl_status.ServiceVersion) { @@ -634,7 +657,6 @@ tunnel_status *get_tunnel_status() { char *get_tunnel_config(size_t *json_len) { tunnel_status tnl_config = {0}; tunnel_status *tnl_sts = get_tunnel_status(); - tnl_config.Active = tnl_sts->Active; tnl_config.Duration = tnl_sts->Duration; tnl_config.StartTime = tnl_sts->StartTime; @@ -688,7 +710,7 @@ char *get_tunnel_config(size_t *json_len) { return tunnel_config_json; } -void set_mfa_status(char* identifier, bool mfa_enabled, bool mfa_needed) { +void set_mfa_status(const char* identifier, bool mfa_enabled, bool mfa_needed) { tunnel_identity *tnl_id = find_tunnel_identity(identifier); if (tnl_id != NULL) { tnl_id->MfaEnabled = mfa_enabled; @@ -698,7 +720,7 @@ void set_mfa_status(char* identifier, bool mfa_enabled, bool mfa_needed) { } } -void update_mfa_time(char* identifier) { +void update_mfa_time(const char* identifier) { tunnel_identity *tnl_id = find_tunnel_identity(identifier); if (tnl_id != NULL) { uv_timeval64_t now; @@ -715,7 +737,7 @@ void update_mfa_time(char* identifier) { void set_ip_info(uint32_t dns_ip, uint32_t tun_ip, int bits) { tnl_status.TunPrefixLength = bits; - if (tnl_status.TunIpv4) free(tnl_status.TunIpv4); + if (tnl_status.TunIpv4) free((char*)tnl_status.TunIpv4); ip_addr_t tun_ip4 = IPADDR4_INIT(tun_ip); tnl_status.TunIpv4 = strdup(ipaddr_ntoa(&tun_ip4)); @@ -740,12 +762,12 @@ void set_log_level(const char* log_level) { return; } if (tnl_status.LogLevel) { - free(tnl_status.LogLevel); + free((char*)tnl_status.LogLevel); tnl_status.LogLevel = NULL; } tnl_status.LogLevel = strdup(log_level); for (int i = 0; tnl_status.LogLevel[i] != '\0'; i++) { - tnl_status.LogLevel[i] = (char)tolower(tnl_status.LogLevel[i]); + ((char*)tnl_status.LogLevel)[i] = (char)tolower(tnl_status.LogLevel[i]); } } @@ -772,6 +794,9 @@ int get_log_level(const char* log_level) { return (int) strtol(loglvl, NULL, 10); } } + if (isdigit(log_level[0])) { + return (int) strtol(log_level, NULL, 10); + } int lvl = 0; int num_levels = sizeof(level_labels) / sizeof(const char *); for (int i = 0;i < num_levels; i++) { @@ -795,7 +820,7 @@ void set_service_version() { tnl_status.ServiceVersion->Version = strdup(version); char *revision_idx = strstr(version, "-"); if (revision_idx != NULL) { - tnl_status.ServiceVersion->Version[revision_idx - version] = '\0'; + ((char*)tnl_status.ServiceVersion->Version)[revision_idx - version] = '\0'; tnl_status.ServiceVersion->Revision = strdup(revision_idx + 1); } } @@ -803,19 +828,24 @@ void set_service_version() { tnl_status.ServiceVersion->BuildDate = strdup(ziti_tunneler_build_date()); } -void delete_identity_from_instance(char* identifier) { +void delete_identity_from_instance(const char* identifier) { tunnel_identity *id = model_map_get(&tnl_identity_map, identifier); if (id == NULL) { return; } model_map_remove(&tnl_identity_map, identifier); ZITI_LOG(DEBUG, "ztx[%s] is removed from the tunnel identity list", identifier); + + // delete identity file + remove(identifier); + ZITI_LOG(INFO, "Identity file %s is deleted",identifier); + free_tunnel_identity(id); free(id); } -void set_tun_ipv4_into_instance(char* tun_ip, int prefixLength, bool addDns) { - if (tnl_status.TunIpv4 != NULL) free(tnl_status.TunIpv4); +void set_tun_ipv4_into_instance(const char* tun_ip, int prefixLength, bool addDns) { + if (tnl_status.TunIpv4 != NULL) free((char*)tnl_status.TunIpv4); tnl_status.TunIpv4 = strdup(tun_ip); tnl_status.TunPrefixLength = prefixLength; @@ -827,12 +857,12 @@ char* get_ip_range_from_config() { char* ip_range = NULL; if (tnl_status.TunIpv4 != NULL && tnl_status.TunPrefixLength > 0) { ip_range = calloc(30, sizeof(char)); - snprintf(ip_range, 30 * sizeof(char), "%s/%d",tnl_status.TunIpv4, tnl_status.TunPrefixLength); + snprintf(ip_range, 30 * sizeof(char), "%s/%d",tnl_status.TunIpv4, (int)tnl_status.TunPrefixLength); } return ip_range; } -char* get_dns_ip() { +const char* get_dns_ip() { return tnl_status.IpInfo->DNS; } @@ -840,7 +870,7 @@ bool get_add_dns_flag() { return tnl_status.AddDns; } -void set_ziti_status(bool enabled, char* identifier) { +void set_ziti_status(bool enabled, const char* identifier) { tunnel_identity *id = model_map_get(&tnl_identity_map, identifier); if (id == NULL) { return; diff --git a/programs/ziti-edge-tunnel/netif_driver/darwin/utun.c b/programs/ziti-edge-tunnel/netif_driver/darwin/utun.c index 2fe13c04..5b9ecff7 100644 --- a/programs/ziti-edge-tunnel/netif_driver/darwin/utun.c +++ b/programs/ziti-edge-tunnel/netif_driver/darwin/utun.c @@ -14,6 +14,7 @@ limitations under the License. */ +#include #include #include #include @@ -30,6 +31,7 @@ #include #include "utun.h" +#include "ziti/model_collections.h" int utun_close(struct netif_handle_s *tun) { int r = 0; @@ -100,19 +102,41 @@ int utun_uv_poll_init(netif_handle tun, uv_loop_t *loop, uv_poll_t *tun_poll_req */ int utun_add_route(netif_handle tun, const char *dest) { char cmd[1024]; - snprintf(cmd, sizeof(cmd), "route add %s -interface %s", dest, tun->name); + snprintf(cmd, sizeof(cmd), "route -n add %s -interface %s", dest, tun->name); int s = system(cmd); return s; } int utun_delete_route(netif_handle tun, const char *dest) { char cmd[1024]; - snprintf(cmd, sizeof(cmd), "route delete %s -interface %s", dest, tun->name); + snprintf(cmd, sizeof(cmd), "route -n delete %s -interface %s", dest, tun->name); int s = system(cmd); return s; } +static model_map excluded; +static uv_once_t delete_once; + +static void delete_excluded() { + char cmd[1024]; + const char *rt; + void *dummy; + MODEL_MAP_FOREACH(rt, dummy, &excluded) { + snprintf(cmd, sizeof(cmd), "route -q -n delete %s", rt); + system(cmd); + } + model_map_clear(&excluded, free); +} +static void delete_init() { + int rc = atexit(delete_excluded); + if (rc) { + ZITI_LOG(WARN, "failed to register route cleanup: %s", strerror(errno)); + } +} + static int utun_exclude_rt(netif_handle dev, uv_loop_t *l, const char *addr) { + uv_once(&delete_once, delete_init); + char gw[128] = {0}; const char *get_gw_cmd = "route -n get default | awk '/gateway: / { printf \"%s\", $2 }'"; ZITI_LOG(DEBUG, "executing '%s'", get_gw_cmd); @@ -133,8 +157,10 @@ static int utun_exclude_rt(netif_handle dev, uv_loop_t *l, const char *addr) { } ZITI_LOG(DEBUG, "default route gw is '%s'", gw); + model_map_set(&excluded, addr, NULL); + char cmd[1024]; - snprintf(cmd, sizeof(cmd), "route add %s %s", addr, gw); + snprintf(cmd, sizeof(cmd), "route -n add %s %s", addr, gw); ZITI_LOG(DEBUG, "executing '%s'", cmd); s = system(cmd); return s; @@ -252,7 +278,7 @@ netif_driver utun_open(char *error, size_t error_len, const char *cidr) { // add a route for the subnet if one was specified if (prefix_sep != NULL) { - snprintf(cmd, sizeof(cmd), "route add -net %s -interface %s", cidr, tun->name); + snprintf(cmd, sizeof(cmd), "route -n add -net %s -interface %s", cidr, tun->name); system(cmd); } } diff --git a/programs/ziti-edge-tunnel/netif_driver/linux/resolvers.c b/programs/ziti-edge-tunnel/netif_driver/linux/resolvers.c index 93af84b2..28512dcf 100644 --- a/programs/ziti-edge-tunnel/netif_driver/linux/resolvers.c +++ b/programs/ziti-edge-tunnel/netif_driver/linux/resolvers.c @@ -137,7 +137,53 @@ static bool set_systemd_resolved_link_setting(sd_bus *bus, const char *tun, cons return true; } -bool try_libsystemd_resolver(void) { +// wait for systemd to recognize the tun device before configuring, lest the configuration get overwritten +static bool wait_for_tun(const char *name, sd_bus *bus, unsigned int timeout_ms) { + const unsigned int delay_ms = 250; + char systemd_path[128]; + unsigned int iterations = timeout_ms / delay_ms; + bool active = false; + snprintf(systemd_path, sizeof(systemd_path), "/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2d%s_2edevice", name); + + ZITI_LOG(DEBUG, "waiting %d ms for systemd path '%s' to become active", timeout_ms, systemd_path); + + for (int count = 0; count < iterations && active == false; count++, uv_sleep(delay_ms)) { + sd_bus_message *message = NULL; + + int r = sd_bus_get_property_f( + bus, + "org.freedesktop.systemd1", + systemd_path, + "org.freedesktop.systemd1.Unit", + "ActiveState", + NULL, + &message, + "s" + ); + if (r < 0) { + ZITI_LOG(VERBOSE, "failed to get ActiveState property: %s", strerror(-r)); + continue; + } + + const char *state = NULL; + r = sd_bus_message_read_f(message, "s", &state); + if (r < 0) { + ZITI_LOG(VERBOSE, "failed to read property: %s", strerror(-r)); + } else { + if (state) { + ZITI_LOG(DEBUG, "device state (c=%d): %s", count, state); + if (strcmp(state, "active") == 0) { + active = true; + } + } + } + sd_bus_message_unref_f(message); + } + + return false; +} + +bool try_libsystemd_resolver(const char *tun_name) { if (!libsystemd_dl_success) { return false; } @@ -155,15 +201,14 @@ bool try_libsystemd_resolver(void) { r = sd_bus_open_system_f(&bus); if ((r >= 0) && (sd_bus_is_bus_client_f(bus) > 0)) { ZITI_LOG(DEBUG, "Connected to system DBus"); + wait_for_tun(tun_name, bus, 3000); r = sd_bus_is_acquired_name(bus, RESOLVED_DBUS_NAME); if (r != 0) { ZITI_LOG(WARN, "libsystemd resolver unsuccessful. Falling back to legacy resolvers"); return false; } - if (r == 0) { - ZITI_LOG(INFO, "systemd-resolved selected as DNS resolver manager"); - return true; - } + ZITI_LOG(INFO, "systemd-resolved selected as DNS resolver manager"); + return true; } else { ZITI_LOG(DEBUG, "Could not create system DBus client"); } @@ -228,25 +273,25 @@ void dns_update_resolvconf(const char *tun, unsigned int ifindex, const char *ad static bool make_copy(const char *src, const char *dst) { - uv_fs_t *req = (uv_fs_t *)malloc(sizeof(uv_fs_t)); + uv_fs_t req = {0}; ZITI_LOG(INFO, "attempting copy of: %s", src); - int ret = uv_fs_copyfile(uv_default_loop(), req, src, dst, UV_FS_COPYFILE_EXCL, NULL); + int ret = uv_fs_copyfile(uv_default_loop(), &req, src, dst, UV_FS_COPYFILE_EXCL, NULL); - if (req->result < 0) { - if (req->result == UV_EEXIST) { - ZITI_LOG(DEBUG, "%s has already been copied", req->path); + if (req.result < 0) { + if (req.result == UV_EEXIST) { + ZITI_LOG(DEBUG, "%s has already been copied", req.path); } else { - ZITI_LOG(WARN, "could not create copy[%s]: %s", req->new_path, uv_strerror(req->result)); - uv_fs_req_cleanup(req); + ZITI_LOG(WARN, "could not create copy[%s]: %s", req.new_path, uv_strerror(req.result)); + uv_fs_req_cleanup(&req); return false; } } - ZITI_LOG(INFO, "copy successful: %s", req->new_path); + ZITI_LOG(INFO, "copy successful: %s", req.new_path); - uv_fs_req_cleanup(req); + uv_fs_req_cleanup(&req); return true; } @@ -288,7 +333,7 @@ void dns_update_etc_resolv(const char *tun, unsigned int ifindex, const char *ad return; } - char *buffer = NULL; + _cleanup_(cleanup_bufferp) char *buffer = NULL; size_t buffer_size; ssize_t line_size; off_t match_start_offset = -1; @@ -296,7 +341,7 @@ void dns_update_etc_resolv(const char *tun, unsigned int ifindex, const char *ad while((line_size = getline(&buffer, &buffer_size, file)) != -1) { if(strstr(buffer, match) != NULL) { if(strstr(buffer, replace) != NULL) { - ZITI_LOG(TRACE, "ziti nameserver is already in %s", RESOLV_CONF_FILE); + ZITI_LOG(DEBUG, "ziti nameserver is already in %s", RESOLV_CONF_FILE); return; } match_start_offset = ftell(file) - line_size; @@ -345,11 +390,8 @@ void dns_update_etc_resolv(const char *tun, unsigned int ifindex, const char *ad CLEANUP_ETC_RESOLV(); } - // Handle case in which realloc moves the memory block - // and calls free() - if (rptr != replace) { - replace = NULL; - } + // prevent double free() when cleanup_bufferp() is called + replace = NULL; strcat(rptr, remaining_content); @@ -368,16 +410,17 @@ void dns_update_etc_resolv(const char *tun, unsigned int ifindex, const char *ad CLEANUP_ETC_RESOLV(); } } - ZITI_LOG(DEBUG, "Added ziti DNS resolver to %s", RESOLV_CONF_FILE); - return; + } else { + // If no nameserver directives to prepend, just append to the file. + if (fputs(replace, file) == EOF) { + ZITI_LOG(ERROR, "EOF received while appending to: %s", RESOLV_CONF_FILE); + CLEANUP_ETC_RESOLV(); + } } - // If no nameserver directives to prepend, just append to the file. - if (fputs(replace, file) == EOF) { - ZITI_LOG(ERROR, "EOF received while appending to: %s", RESOLV_CONF_FILE); - CLEANUP_ETC_RESOLV(); - } + ZITI_LOG(DEBUG, "Added ziti DNS resolver to %s", RESOLV_CONF_FILE); + return; } diff --git a/programs/ziti-edge-tunnel/netif_driver/linux/resolvers.h b/programs/ziti-edge-tunnel/netif_driver/linux/resolvers.h index b4c5dec7..b5a35d78 100644 --- a/programs/ziti-edge-tunnel/netif_driver/linux/resolvers.h +++ b/programs/ziti-edge-tunnel/netif_driver/linux/resolvers.h @@ -46,7 +46,7 @@ #endif #ifndef EXCLUDE_LIBSYSTEMD_RESOLVER -bool try_libsystemd_resolver(void); +bool try_libsystemd_resolver(const char *tun_name); #endif bool is_systemd_resolved_primary_resolver(void); bool is_resolvconf_systemd_resolved(void); diff --git a/programs/ziti-edge-tunnel/netif_driver/linux/tun.c b/programs/ziti-edge-tunnel/netif_driver/linux/tun.c index c7ce5e14..b0f290e7 100644 --- a/programs/ziti-edge-tunnel/netif_driver/linux/tun.c +++ b/programs/ziti-edge-tunnel/netif_driver/linux/tun.c @@ -245,7 +245,7 @@ static void dns_update_systemd_resolve(const char* tun, unsigned int ifindex, co static void find_dns_updater() { #ifndef EXCLUDE_LIBSYSTEMD_RESOLVER - if(try_libsystemd_resolver()) { + if(try_libsystemd_resolver(dns_maintainer.tun_name)) { dns_updater = dns_update_systemd_resolved; return; } diff --git a/programs/ziti-edge-tunnel/netif_driver/windows/tun.c b/programs/ziti-edge-tunnel/netif_driver/windows/tun.c index 2fa3ed6a..99f7bea9 100644 --- a/programs/ziti-edge-tunnel/netif_driver/windows/tun.c +++ b/programs/ziti-edge-tunnel/netif_driver/windows/tun.c @@ -27,19 +27,21 @@ #define _Ret_bytecount_(n) #endif +#ifndef _Post_maybenull_ +#define _Post_maybenull_ +#endif + #include #include #include #include -#include #include #include #include #include "tun.h" -#define ZITI_TUN_GUID L"2cbfd72d-370c-43b0-b0cd-c8f092a7e134" -#define ZITI_TUN L"ziti-tun0" +#define ZITI_TUN_NAME_BASE L"ziti-tun" #define ROUTE_LIFETIME (10 * 60) /* in seconds */ #define ROUTE_REFRESH ((ROUTE_LIFETIME - (ROUTE_LIFETIME/10))*1000) @@ -68,69 +70,84 @@ static int tun_add_route(netif_handle tun, const char *dest); static int tun_del_route(netif_handle tun, const char *dest); int set_dns(netif_handle tun, uint32_t dns_ip); static int tun_exclude_rt(netif_handle dev, uv_loop_t *loop, const char *dest); -static void if_change_cb(PVOID CallerContext, PMIB_IPINTERFACE_ROW Row, MIB_NOTIFICATION_TYPE NotificationType); +static void WINAPI if_change_cb(PVOID CallerContext, PMIB_IPINTERFACE_ROW Row, MIB_NOTIFICATION_TYPE NotificationType); static void refresh_routes(uv_timer_t *timer); static void cleanup_adapters(wchar_t *tun_name); static HANDLE if_change_handle; -static WINTUN_CREATE_ADAPTER_FUNC WintunCreateAdapter; -static WINTUN_DELETE_ADAPTER_FUNC WintunDeleteAdapter; -static WINTUN_DELETE_POOL_DRIVER_FUNC WintunDeletePoolDriver; -static WINTUN_ENUM_ADAPTERS_FUNC WintunEnumAdapters; -static WINTUN_FREE_ADAPTER_FUNC WintunFreeAdapter; -static WINTUN_OPEN_ADAPTER_FUNC WintunOpenAdapter; -static WINTUN_GET_ADAPTER_LUID_FUNC WintunGetAdapterLUID; -static WINTUN_GET_ADAPTER_NAME_FUNC WintunGetAdapterName; -static WINTUN_SET_ADAPTER_NAME_FUNC WintunSetAdapterName; -static WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC WintunGetRunningDriverVersion; -static WINTUN_SET_LOGGER_FUNC WintunSetLogger; -static WINTUN_START_SESSION_FUNC WintunStartSession; -static WINTUN_END_SESSION_FUNC WintunEndSession; -static WINTUN_GET_READ_WAIT_EVENT_FUNC WintunGetReadWaitEvent; -static WINTUN_RECEIVE_PACKET_FUNC WintunReceivePacket; -static WINTUN_RELEASE_RECEIVE_PACKET_FUNC WintunReleaseReceivePacket; -static WINTUN_ALLOCATE_SEND_PACKET_FUNC WintunAllocateSendPacket; -static WINTUN_SEND_PACKET_FUNC WintunSendPacket; +static WINTUN_CREATE_ADAPTER_FUNC *WintunCreateAdapter; +static WINTUN_CLOSE_ADAPTER_FUNC *WintunCloseAdapter; +static WINTUN_OPEN_ADAPTER_FUNC *WintunOpenAdapter; +static WINTUN_GET_ADAPTER_LUID_FUNC *WintunGetAdapterLUID; +static WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC *WintunGetRunningDriverVersion; +static WINTUN_DELETE_DRIVER_FUNC *WintunDeleteDriver; +static WINTUN_SET_LOGGER_FUNC *WintunSetLogger; +static WINTUN_START_SESSION_FUNC *WintunStartSession; +static WINTUN_END_SESSION_FUNC *WintunEndSession; +static WINTUN_GET_READ_WAIT_EVENT_FUNC *WintunGetReadWaitEvent; +static WINTUN_RECEIVE_PACKET_FUNC *WintunReceivePacket; +static WINTUN_RELEASE_RECEIVE_PACKET_FUNC *WintunReleaseReceivePacket; +static WINTUN_ALLOCATE_SEND_PACKET_FUNC *WintunAllocateSendPacket; +static WINTUN_SEND_PACKET_FUNC *WintunSendPacket; static uv_once_t wintunInit; static HMODULE WINTUN; static MIB_IPFORWARD_ROW2 default_rt; -static void InitializeWintun(void) -{ +static void CALLBACK WintunLogger(_In_ WINTUN_LOGGER_LEVEL Level, _In_ DWORD64 Timestamp, _In_z_ const WCHAR *LogLine) { + switch (Level) { + case WINTUN_LOG_INFO: + ZITI_LOG(INFO, "%ls", LogLine); + break; + case WINTUN_LOG_WARN: + ZITI_LOG(WARN, "%ls", LogLine); + break; + case WINTUN_LOG_ERR: + ZITI_LOG(ERROR, "%ls", LogLine); + break; + default: + return; + } +} + +static void InitializeWintun(void) { HMODULE Wintun = LoadLibraryExW(L"wintun.dll", NULL, LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32); - if (!Wintun) + if (!Wintun) { + DWORD error = GetLastError(); + fprintf(stderr, "Failed to load wintun.dll. Error code: %lu\n", error); return; -#define X(Name, Type) ((Name = (Type)GetProcAddress(Wintun, #Name)) == NULL) - if (X(WintunCreateAdapter, WINTUN_CREATE_ADAPTER_FUNC) || - X(WintunDeleteAdapter, WINTUN_DELETE_ADAPTER_FUNC) || - X(WintunDeletePoolDriver, WINTUN_DELETE_POOL_DRIVER_FUNC) || - X(WintunEnumAdapters, WINTUN_ENUM_ADAPTERS_FUNC) || - X(WintunFreeAdapter, WINTUN_FREE_ADAPTER_FUNC) || - X(WintunOpenAdapter, WINTUN_OPEN_ADAPTER_FUNC) || - X(WintunGetAdapterLUID, WINTUN_GET_ADAPTER_LUID_FUNC) || - X(WintunGetAdapterName, WINTUN_GET_ADAPTER_NAME_FUNC) || - X(WintunSetAdapterName, WINTUN_SET_ADAPTER_NAME_FUNC) || - X(WintunGetRunningDriverVersion, WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC) || - X(WintunSetLogger, WINTUN_SET_LOGGER_FUNC) || - X(WintunStartSession, WINTUN_START_SESSION_FUNC) || - X(WintunEndSession, WINTUN_END_SESSION_FUNC) || - X(WintunGetReadWaitEvent, WINTUN_GET_READ_WAIT_EVENT_FUNC) || - X(WintunReceivePacket, WINTUN_RECEIVE_PACKET_FUNC) || - X(WintunReleaseReceivePacket, WINTUN_RELEASE_RECEIVE_PACKET_FUNC) || - X(WintunAllocateSendPacket, WINTUN_ALLOCATE_SEND_PACKET_FUNC) || - X(WintunSendPacket, WINTUN_SEND_PACKET_FUNC)) -#undef X - { - DWORD LastError = GetLastError(); - FreeLibrary(Wintun); - SetLastError(LastError); - Wintun = NULL; } - WINTUN = Wintun; +#define X(Name) \ + if ((*(FARPROC *)&Name = GetProcAddress(Wintun, #Name)) == NULL) \ + { \ + DWORD error = GetLastError(); \ + fprintf(stderr, "Failed to get address of %s. Error code: %lu\n", #Name, error); \ + FreeLibrary(Wintun); \ + return; \ + } + + X(WintunCreateAdapter) + X(WintunCloseAdapter) + X(WintunOpenAdapter) + X(WintunGetAdapterLUID) + X(WintunGetRunningDriverVersion) + X(WintunDeleteDriver) + X(WintunSetLogger) + X(WintunStartSession) + X(WintunEndSession) + X(WintunGetReadWaitEvent) + X(WintunReceivePacket) + X(WintunReleaseReceivePacket) + X(WintunAllocateSendPacket) + X(WintunSendPacket) + +#undef X + + WINTUN = Wintun; + WintunSetLogger(WintunLogger); } static bool flush_dns() { @@ -157,6 +174,13 @@ static bool flush_dns() { return result; } +static WINTUN_ADAPTER_HANDLE adapter = NULL; + +void cleanup_resources() { + if (adapter) WintunCloseAdapter(adapter); + if (WINTUN) FreeLibrary(WINTUN); +} + netif_driver tun_open(struct uv_loop_s *loop, uint32_t tun_ip, const char *cidr, char *error, size_t error_len) { if (error != NULL) { memset(error, 0, error_len * sizeof(char)); @@ -177,26 +201,41 @@ netif_driver tun_open(struct uv_loop_s *loop, uint32_t tun_ip, const char *cidr, } return NULL; } - cleanup_adapters(ZITI_TUN); flush_dns(); - BOOL rr; - GUID adapterGuid; - IIDFromString(ZITI_TUN_GUID, &adapterGuid); - WINTUN_ADAPTER_HANDLE adapter = WintunOpenAdapter(L"Ziti", ZITI_TUN); - if (adapter) { - WintunDeleteAdapter(adapter, true, &rr); + int tun_num = 0; + swprintf(tun->name, MAX_ADAPTER_NAME, L"%ls%d", ZITI_TUN_NAME_BASE, tun_num); + WINTUN_ADAPTER_HANDLE found = WintunOpenAdapter(tun->name); + while (found) { + tun_num++; + WintunCloseAdapter(found); // already exists. increment and try again + if (tun_num > 15) { + char* msg = "TOO MANY TUN DEVICES?"; + size_t msg_len = strlen(msg); + snprintf(error, msg_len, "%s", msg); + return NULL; + } + swprintf(tun->name, MAX_ADAPTER_NAME, L"%ls%d", ZITI_TUN_NAME_BASE, tun_num); + found = WintunOpenAdapter(tun->name); } - tun->adapter = WintunCreateAdapter(L"Ziti", ZITI_TUN, &adapterGuid, NULL); + + tun->adapter = WintunCreateAdapter(tun->name, L"OpenZiti", NULL); // Wintun adds "Tunnel" so this will be "OpenZiti Tunnel" if (!tun->adapter) { DWORD err = GetLastError(); - snprintf(error, error_len, "Failed to create adapter: %d", err); + snprintf(error, error_len, "Failed to create adapter: %ld", err); return NULL; } + adapter = tun->adapter; WintunGetAdapterLUID(tun->adapter, &tun->luid); - WintunGetAdapterName(tun->adapter, tun->name); + + if (atexit(cleanup_resources) != 0) { + char* msg = "Cannot set exit function"; + size_t msg_len = strlen(msg); + snprintf(error, msg_len, "%s", msg); + return NULL; + } NotifyIpInterfaceChange(AF_INET, if_change_cb, tun, TRUE, &if_change_handle); @@ -268,8 +307,7 @@ static int tun_close(struct netif_handle_s *tun) { } if (tun->adapter) { - WintunDeleteAdapter(tun->adapter, true, NULL); - WintunFreeAdapter(tun->adapter); + WintunCloseAdapter(tun->adapter); tun->adapter = NULL; } free(tun); @@ -393,7 +431,7 @@ int tun_del_route(netif_handle tun, const char *dest) { return 0; } -static void if_change_cb(PVOID CallerContext, PMIB_IPINTERFACE_ROW Row, MIB_NOTIFICATION_TYPE NotificationType) { +static void WINAPI if_change_cb(PVOID CallerContext, PMIB_IPINTERFACE_ROW Row, MIB_NOTIFICATION_TYPE NotificationType) { struct netif_handle_s *tun = CallerContext; MIB_IPFORWARD_ROW2 rt = {0}; @@ -486,39 +524,29 @@ int set_dns(netif_handle tun, uint32_t dns_ip) { char cmd[1024]; char ip[4]; memcpy(ip, &dns_ip, 4); + wchar_t* tun_name = get_tun_name(tun); snprintf(cmd, sizeof(cmd), "powershell -Command Set-DnsClientServerAddress " "-InterfaceAlias %ls " "-ServerAddress %d.%d.%d.%d", - tun->name, ip[0], ip[1], ip[2], ip[3]); + tun_name, ip[0], ip[1], ip[2], ip[3]); + //free(tun_name); ZITI_LOG(INFO, "executing '%s'", cmd); int rc = system(cmd); if (rc != 0) { - ZITI_LOG(WARN, "set DNS: %d(err=%d)", rc, GetLastError()); + ZITI_LOG(WARN, "set DNS: %d(err=%ld)", rc, GetLastError()); } return rc; } -char* get_tun_name(netif_handle tun) { +wchar_t* get_tun_name(netif_handle tun) { return tun->name; } static BOOL CALLBACK tun_delete_cb(_In_ WINTUN_ADAPTER_HANDLE adapter, _In_ LPARAM param) { - wchar_t name[32]; - WintunGetAdapterName(adapter, name); - wchar_t *tun_name = param; - if (wcsncmp(name, tun_name, wcslen(tun_name)) == 0) { - WintunDeleteAdapter(adapter, true, NULL); - ZITI_LOG(INFO, "Deleted wintun adapter %ls", name); - } else { - ZITI_LOG(INFO, "Not deleting wintun adapter %ls, name didn't match %ls", name, tun_name); - } + ZITI_LOG(INFO, "Deleting wintun adapter"); + WintunCloseAdapter(adapter); // the call back should always return value greater than zero, so the cleanup function will continue return 1; -} - -static void cleanup_adapters(wchar_t *tun_name) { - ZITI_LOG(INFO, "Cleaning up orphan wintun adapters"); - WintunEnumAdapters(L"Ziti", tun_delete_cb, tun_name); -} +} \ No newline at end of file diff --git a/programs/ziti-edge-tunnel/netif_driver/windows/tun.h b/programs/ziti-edge-tunnel/netif_driver/windows/tun.h index b2dfec59..fa0be68e 100644 --- a/programs/ziti-edge-tunnel/netif_driver/windows/tun.h +++ b/programs/ziti-edge-tunnel/netif_driver/windows/tun.h @@ -25,6 +25,6 @@ extern netif_driver tun_open(struct uv_loop_s *loop, uint32_t tun_ip, const char extern int set_dns(netif_handle tun, uint32_t dns_ip); -extern char* get_tun_name(netif_handle tun); +extern wchar_t* get_tun_name(netif_handle tun); #endif //ZITI_TUNNEL_SDK_C_TUN_H diff --git a/programs/ziti-edge-tunnel/package/CPackGenConfig.cmake b/programs/ziti-edge-tunnel/package/CPackGenConfig.cmake index 8087ede2..77497067 100644 --- a/programs/ziti-edge-tunnel/package/CPackGenConfig.cmake +++ b/programs/ziti-edge-tunnel/package/CPackGenConfig.cmake @@ -14,7 +14,7 @@ if(CPACK_GENERATOR MATCHES "RPM") set(CPACK_RPM_PACKAGE_LICENSE "Apache-2.0") set(CPACK_RPM_PACKAGE_RELEASE_DIST "OFF") set(CPACK_RPM_PACKAGE_SUMMARY "OpenZiti Edge Tunneling Software Client") - set(CPACK_RPM_PACKAGE_URL "https://openziti.github.io/") + set(CPACK_RPM_PACKAGE_URL "https://openziti.io/") set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "${CPACK_RPM_PRE_INSTALL}") set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CPACK_RPM_POST_INSTALL}") diff --git a/programs/ziti-edge-tunnel/package/CPackPackage.cmake b/programs/ziti-edge-tunnel/package/CPackPackage.cmake index f9a748dd..7a3274e1 100644 --- a/programs/ziti-edge-tunnel/package/CPackPackage.cmake +++ b/programs/ziti-edge-tunnel/package/CPackPackage.cmake @@ -103,7 +103,6 @@ install(FILES "${INSTALL_OUT_DIR}/${SYSTEMD_UNIT_FILE_NAME}" DESTINATION "${CPACK_SHARE_DIR}" COMPONENT "${COMPONENT_NAME}") - install(FILES "${INSTALL_OUT_DIR}/${SYSTEMD_EXECSTARTPRE}" DESTINATION "${CPACK_BIN_DIR}" PERMISSIONS @@ -124,47 +123,52 @@ install(FILES "${INSTALL_OUT_DIR}/${ZITI_POLKIT_RULES_FILE}.sample" COMPONENT "${COMPONENT_NAME}" RENAME "${ZITI_POLKIT_RULES_FILE}") +install(FILES "${CMAKE_SOURCE_DIR}/scripts/debug.bash" + COMPONENT "${COMPONENT_NAME}" + DESTINATION "${CPACK_BIN_DIR}" + PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ + GROUP_EXECUTE GROUP_READ) + if("RPM" IN_LIST CPACK_GENERATOR) - set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "/usr/share/polkit-1/rules.d") - - set(RPM_IN_DIR "${PACKAGING_BASE}/rpm") - set(RPM_PRE_INSTALL_IN "${RPM_IN_DIR}/pre.sh.in") - set(RPM_POST_INSTALL_IN "${RPM_IN_DIR}/post.sh.in") - set(RPM_PRE_UNINSTALL_IN "${RPM_IN_DIR}/preun.sh.in") - set(RPM_POST_UNINSTALL_IN "${RPM_IN_DIR}/postun.sh.in") - - set(CPACK_RPM_PRE_INSTALL "${INSTALL_OUT_DIR}/pre.sh") - set(CPACK_RPM_POST_INSTALL "${INSTALL_OUT_DIR}/post.sh") - set(CPACK_RPM_PRE_UNINSTALL "${INSTALL_OUT_DIR}/preun.sh") - set(CPACK_RPM_POST_UNINSTALL "${INSTALL_OUT_DIR}/postun.sh") - - configure_file("${RPM_PRE_INSTALL_IN}" "${CPACK_RPM_PRE_INSTALL}" @ONLY) - configure_file("${RPM_POST_INSTALL_IN}" "${CPACK_RPM_POST_INSTALL}" @ONLY) - configure_file("${RPM_PRE_UNINSTALL_IN}" "${CPACK_RPM_PRE_UNINSTALL}" @ONLY) - configure_file("${RPM_POST_UNINSTALL_IN}" "${CPACK_RPM_POST_UNINSTALL}" @ONLY) + set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "/usr/share/polkit-1/rules.d") + + set(RPM_IN_DIR "${PACKAGING_BASE}/rpm") + set(RPM_PRE_INSTALL_IN "${RPM_IN_DIR}/pre.sh.in") + set(RPM_POST_INSTALL_IN "${RPM_IN_DIR}/post.sh.in") + set(RPM_PRE_UNINSTALL_IN "${RPM_IN_DIR}/preun.sh.in") + set(RPM_POST_UNINSTALL_IN "${RPM_IN_DIR}/postun.sh.in") + + set(CPACK_RPM_PRE_INSTALL "${INSTALL_OUT_DIR}/pre.sh") + set(CPACK_RPM_POST_INSTALL "${INSTALL_OUT_DIR}/post.sh") + set(CPACK_RPM_PRE_UNINSTALL "${INSTALL_OUT_DIR}/preun.sh") + set(CPACK_RPM_POST_UNINSTALL "${INSTALL_OUT_DIR}/postun.sh") + + configure_file("${RPM_PRE_INSTALL_IN}" "${CPACK_RPM_PRE_INSTALL}" @ONLY) + configure_file("${RPM_POST_INSTALL_IN}" "${CPACK_RPM_POST_INSTALL}" @ONLY) + configure_file("${RPM_PRE_UNINSTALL_IN}" "${CPACK_RPM_PRE_UNINSTALL}" @ONLY) + configure_file("${RPM_POST_UNINSTALL_IN}" "${CPACK_RPM_POST_UNINSTALL}" @ONLY) endif() if("DEB" IN_LIST CPACK_GENERATOR) - - set(DEB_IN_DIR "${PACKAGING_BASE}/deb") - set(DEB_CONFFILES_IN "${DEB_IN_DIR}/conffiles.in") - set(DEB_PRE_INSTALL_IN "${DEB_IN_DIR}/preinst.in") - set(DEB_POST_INSTALL_IN "${DEB_IN_DIR}/postinst.in") - set(DEB_PRE_UNINSTALL_IN "${DEB_IN_DIR}/prerm.in") - set(DEB_POST_UNINSTALL_IN "${DEB_IN_DIR}/postrm.in") - set(DEB_TEMPLATES_IN "${DEB_IN_DIR}/templates.in") - - set(CPACK_DEB_CONFFILES "${INSTALL_OUT_DIR}/conffiles") - set(CPACK_DEB_PRE_INSTALL "${INSTALL_OUT_DIR}/preinst") - set(CPACK_DEB_POST_INSTALL "${INSTALL_OUT_DIR}/postinst") - set(CPACK_DEB_PRE_UNINSTALL "${INSTALL_OUT_DIR}/prerm") - set(CPACK_DEB_POST_UNINSTALL "${INSTALL_OUT_DIR}/postrm") - set(CPACK_DEB_TEMPLATES "${INSTALL_OUT_DIR}/templates") - - configure_file("${DEB_CONFFILES_IN}" "${CPACK_DEB_CONFFILES}" @ONLY) - configure_file("${DEB_PRE_INSTALL_IN}" "${CPACK_DEB_PRE_INSTALL}" @ONLY) - configure_file("${DEB_POST_INSTALL_IN}" "${CPACK_DEB_POST_INSTALL}" @ONLY) - configure_file("${DEB_PRE_UNINSTALL_IN}" "${CPACK_DEB_PRE_UNINSTALL}" @ONLY) - configure_file("${DEB_POST_UNINSTALL_IN}" "${CPACK_DEB_POST_UNINSTALL}" @ONLY) - configure_file("${DEB_TEMPLATES_IN}" "${CPACK_DEB_TEMPLATES}" @ONLY) + set(DEB_IN_DIR "${PACKAGING_BASE}/deb") + set(DEB_CONFFILES_IN "${DEB_IN_DIR}/conffiles.in") + set(DEB_PRE_INSTALL_IN "${DEB_IN_DIR}/preinst.in") + set(DEB_POST_INSTALL_IN "${DEB_IN_DIR}/postinst.in") + set(DEB_PRE_UNINSTALL_IN "${DEB_IN_DIR}/prerm.in") + set(DEB_POST_UNINSTALL_IN "${DEB_IN_DIR}/postrm.in") + set(DEB_TEMPLATES_IN "${DEB_IN_DIR}/templates.in") + + set(CPACK_DEB_CONFFILES "${INSTALL_OUT_DIR}/conffiles") + set(CPACK_DEB_PRE_INSTALL "${INSTALL_OUT_DIR}/preinst") + set(CPACK_DEB_POST_INSTALL "${INSTALL_OUT_DIR}/postinst") + set(CPACK_DEB_PRE_UNINSTALL "${INSTALL_OUT_DIR}/prerm") + set(CPACK_DEB_POST_UNINSTALL "${INSTALL_OUT_DIR}/postrm") + set(CPACK_DEB_TEMPLATES "${INSTALL_OUT_DIR}/templates") + + configure_file("${DEB_CONFFILES_IN}" "${CPACK_DEB_CONFFILES}" @ONLY) + configure_file("${DEB_PRE_INSTALL_IN}" "${CPACK_DEB_PRE_INSTALL}" @ONLY) + configure_file("${DEB_POST_INSTALL_IN}" "${CPACK_DEB_POST_INSTALL}" @ONLY) + configure_file("${DEB_PRE_UNINSTALL_IN}" "${CPACK_DEB_PRE_UNINSTALL}" @ONLY) + configure_file("${DEB_POST_UNINSTALL_IN}" "${CPACK_DEB_POST_UNINSTALL}" @ONLY) + configure_file("${DEB_TEMPLATES_IN}" "${CPACK_DEB_TEMPLATES}" @ONLY) endif() diff --git a/programs/ziti-edge-tunnel/package/deb/postinst.in b/programs/ziti-edge-tunnel/package/deb/postinst.in index ce7e4822..311c4b6f 100644 --- a/programs/ziti-edge-tunnel/package/deb/postinst.in +++ b/programs/ziti-edge-tunnel/package/deb/postinst.in @@ -1,10 +1,27 @@ -ln -sf @CPACK_BIN_DIR@/@CPACK_PACKAGE_NAME@ /usr/bin/@CPACK_PACKAGE_NAME@ -ln -sf @CPACK_SHARE_DIR@/@SYSTEMD_UNIT_FILE_NAME@ @SYSTEMD_UNIT_DIR@/@SYSTEMD_UNIT_FILE_NAME@ +# always place a symlink to the installed executable in /usr/bin +ln -sfn @CPACK_BIN_DIR@/@CPACK_PACKAGE_NAME@ /usr/bin/@CPACK_PACKAGE_NAME@ + +# copy the unit file to the systemd unit directory as a regular file; buffer stderr together with stdout to avoid +# confusing output line order where stderr appears after subsequent command's stdout; this also seems to influence order +# of operations in an important way because, when stdout and stderr are buffered independently, the install command +# always reports an error as if the subsequent ln command had already succeeded: "install: +# '/opt/openziti/share/ziti-edge-tunnel.service' and '/lib/systemd/system/ziti-edge-tunnel.service' are the same file" +install -m 644 @CPACK_SHARE_DIR@/@SYSTEMD_UNIT_FILE_NAME@ @SYSTEMD_UNIT_DIR@/@SYSTEMD_UNIT_FILE_NAME@ 2>&1 + +# delete the old copy of the unit file to reduce confusion; this has the downside of an error during dpkg remove +# operation because this file was specified to be installed with this package and no longer exists; modifying the file +# with an explanation or warning or link is an alternative to removing it, but it would only work for this deb, not the +# rpm, artifact of this build because rpm creates unwanted *.rpmsave backups of files that were modified after the +# specified version was installed +if [ -e @CPACK_SHARE_DIR@/@SYSTEMD_UNIT_FILE_NAME@ ]; then + unlink @CPACK_SHARE_DIR@/@SYSTEMD_UNIT_FILE_NAME@ +fi + # Source debconf library. . /usr/share/debconf/confmodule -# Add user `ziti' +# ensure user `ziti' exists if install or upgrade if [ "$1" = "configure" ]; then # create user / group # systemd-sysusers isn't on xenial, possibly others? test and fall back to useradd as a last ditch @@ -28,15 +45,24 @@ if [ "$1" = "configure" ]; then chown root:ziti "@ZITI_IDENTITY_DIR@" chmod 0770 "@ZITI_IDENTITY_DIR@" - find "@ZITI_IDENTITY_DIR@" -maxdepth 1 -name "*.json" -type f -exec chown ziti:ziti "{}" + -exec chmod 0400 "{}" + + find "@ZITI_IDENTITY_DIR@" -maxdepth 1 -name "*.json" -type f -exec chown ziti:ziti "{}" + -exec chmod 0440 "{}" + - policykit_version=$(dpkg-query -Wf '${Version;5}' policykit-1 | cut -d . -f 2) - systemd_version=$(dpkg-query -Wf '${Version;3}' systemd) + # sort ascending the installed and max policykit versions, saving the highest version, so we + # can ensure the installed version is less than the max version + policykit_version="$(dpkg-query -Wf '${Version}' policykit-1)" + max_policykit_version="0.106" + highest_policykit_version="$(printf '%s\n' ${policykit_version} ${max_policykit_version} | sort -V | tail -n1)" - # install PolicyKit policy if using policykit < 0.106 (https://askubuntu.com/questions/1287924/whats-going-on-with-policykit) - if [ ${policykit_version} -lt 106 ]; then - # ... the set-llmnr action was exposed with v243 (https://github.com/systemd/systemd/commit/52aaef0f5dc81b9a08d720f551eac53ac88aa596) - if [ ${systemd_version} -ge 243 ]; then + # sort ascending the installed and min systemd versions, saving the lowest version, so we can ensure the installed + # version is greater than or equal to the min version + systemd_version=$(dpkg-query -Wf '${Version}' systemd) + min_systemd_version="243" + lowest_systemd_version="$(printf '%s\n' ${systemd_version} ${min_systemd_version} | sort -V | head -n1)" + + # install PolicyKit policy if < v0.106 (https://askubuntu.com/questions/1287924/whats-going-on-with-policykit) + if [ ${policykit_version} != ${max_policykit_version} ] && [ ${max_policykit_version} = ${highest_policykit_version} ]; then + # run as root unless systemd >= v243 (required set-llmnr introduced v243 https://github.com/systemd/systemd/commit/52aaef0f5dc81b9a08d720f551eac53ac88aa596) + if [ ${systemd_version} = ${min_systemd_version} ] || [ ${min_systemd_version} = ${lowest_systemd_version} ]; then cp "@CPACK_SHARE_DIR@/@ZITI_POLKIT_PKLA_FILE@.sample" "/var/lib/polkit-1/localauthority/10-vendor.d/@ZITI_POLKIT_PKLA_FILE@" db_set ziti_edge_tunnel/install_pkla true else @@ -82,7 +108,12 @@ fi # End copied section if [ "$1" = "configure" ]; then - ssize=$(tput cols) + # if interactive and stdin is a tty + if [ "$DEBIAN_FRONTEND" != "noninteractive" -a -t 0 ]; then + ssize=$(tput cols) + else + ssize=80 + fi printf '\n' printf %"$ssize"s | tr " " "-" echo "@SYSTEMD_SERVICE_NAME@ was installed..." diff --git a/programs/ziti-edge-tunnel/package/deb/postrm.in b/programs/ziti-edge-tunnel/package/deb/postrm.in index 7de6cd2c..2e1eaba1 100644 --- a/programs/ziti-edge-tunnel/package/deb/postrm.in +++ b/programs/ziti-edge-tunnel/package/deb/postrm.in @@ -11,9 +11,15 @@ if [ "$1" = "purge" ]; then deb-systemd-helper unmask @SYSTEMD_UNIT_FILE_NAME@ >/dev/null || true fi fi -# End copied seciton +# End copied section + +# delete the symlink to the executable that was created by the postinst scriptlet +if [ -L /usr/bin/@CPACK_PACKAGE_NAME@ ]; then + unlink /usr/bin/@CPACK_PACKAGE_NAME@ +fi -if [ -L @SYSTEMD_UNIT_DIR@/@SYSTEMD_UNIT_FILE_NAME@ ]; then +# delete the regular file of the service unit that was copied by the postinst scriptlet +if [ -e @SYSTEMD_UNIT_DIR@/@SYSTEMD_UNIT_FILE_NAME@ ]; then unlink @SYSTEMD_UNIT_DIR@/@SYSTEMD_UNIT_FILE_NAME@ fi @@ -23,10 +29,6 @@ if [ -d /run/systemd/system ]; then fi # End copied section -if [ -L /usr/bin/@CPACK_PACKAGE_NAME@ ]; then - unlink /usr/bin/@CPACK_PACKAGE_NAME@ -fi - if [ -e /usr/share/debconf/confmodule ]; then # Source debconf library. . /usr/share/debconf/confmodule @@ -46,7 +48,7 @@ fi ### ### Unlikely all files and directories were removed. ### Some of the unremoved files and directories are likely owned by `ziti' user or group. -### Conseequently, don't remove 'ziti` user and group as it will strand the files +### Consequently, don't remove 'ziti` user and group as it will strand the files ### #if [ "$1" = "purge" ]; then # if command -v deluser >/dev/null; then diff --git a/programs/ziti-edge-tunnel/package/rpm/post.sh.in b/programs/ziti-edge-tunnel/package/rpm/post.sh.in index 6f7819a5..e147017f 100644 --- a/programs/ziti-edge-tunnel/package/rpm/post.sh.in +++ b/programs/ziti-edge-tunnel/package/rpm/post.sh.in @@ -1,8 +1,22 @@ SYSTEMD_SERVICE_NAME=@SYSTEMD_SERVICE_NAME@ SYSTEMD_UNIT_FILE_NAME=@SYSTEMD_UNIT_FILE_NAME@ -if [ $1 -eq 1 ]; then + +# if not 0 (uninstall) then 1 or 2 (initial or upgrade) +if [ $1 -ne 0 ]; then [ -d @CPACK_ETC_DIR@ ] || %{__mkdir} @CPACK_ETC_DIR@ - %{__install} -m 644 @CPACK_SHARE_DIR@/$SYSTEMD_UNIT_FILE_NAME %{_unitdir}/$SYSTEMD_UNIT_FILE_NAME + + # place a symlink in /usr/bin targeting the installed binary + ln -sfn @CPACK_BIN_DIR@/@CPACK_PACKAGE_NAME@ /usr/bin/@CPACK_PACKAGE_NAME@ + + # copy the unit file to the systemd unit directory as a regular file + %{__install} -m 644 @CPACK_SHARE_DIR@/$SYSTEMD_UNIT_FILE_NAME %{_unitdir}/$SYSTEMD_UNIT_FILE_NAME 2>&1 + + # delete the old copy of the unit file to reduce confusion; modifying the file with an explanation or warning or + # link is an alternative to removing it, but that causes unwanted *.rpmsave backups since it is then presumed to be + # a user modification + if [ -e @CPACK_SHARE_DIR@/$SYSTEMD_UNIT_FILE_NAME ]; then + unlink @CPACK_SHARE_DIR@/$SYSTEMD_UNIT_FILE_NAME + fi fi %systemd_post $SYSTEMD_SERVICE_NAME @@ -13,13 +27,19 @@ chmod -R u=rwX,g=rwX,o= "@ZITI_STATE_DIR@" || : chown root:ziti "@ZITI_IDENTITY_DIR@" || : chmod 0770 "@ZITI_IDENTITY_DIR@" || : -find "@ZITI_IDENTITY_DIR@" -maxdepth 1 -name "*.json" -type f -exec chown ziti:ziti "{}" + -exec chmod 0400 "{}" + || : +find "@ZITI_IDENTITY_DIR@" -maxdepth 1 -name "*.json" -type f -exec chown ziti:ziti "{}" + -exec chmod 0440 "{}" + || : # remove socket files that were created by older ziti-edge-tunnel versions rm -f /tmp/ziti-edge-tunnel.sock /tmp/ziti-edge-tunnel-event.sock -if [ $1 -eq 1 ]; then - ssize=$(tput cols) +# if not 0 (uninstall) then 1 or 2 (initial or upgrade) +if [ $1 -ne 0 ]; then + # if stdin is a tty + if [ -t 0 ]; then + ssize=$(tput cols) + else + ssize=80 + fi printf '\n' printf %"$ssize"s | tr " " "-" echo "$SYSTEMD_SERVICE_NAME was installed..." diff --git a/programs/ziti-edge-tunnel/package/rpm/postun.sh.in b/programs/ziti-edge-tunnel/package/rpm/postun.sh.in index 6f7f90be..81a9b103 100644 --- a/programs/ziti-edge-tunnel/package/rpm/postun.sh.in +++ b/programs/ziti-edge-tunnel/package/rpm/postun.sh.in @@ -5,5 +5,9 @@ systemctl daemon-reload >/dev/null 2>&1 || : %systemd_postun_with_restart ${SYSTEMD_SERVICE_NAME} if [ $1 -eq 0 ]; then - rm %{_unitdir}/${SYSTEMD_UNIT_FILE_NAME} + # delete the symlink to the executable that was created by the post scriptlet + unlink /usr/bin/$SYSTEMD_SERVICE_NAME + + # delete the regular file of the service unit that was copied by the post scriptlet + unlink %{_unitdir}/${SYSTEMD_UNIT_FILE_NAME} fi diff --git a/programs/ziti-edge-tunnel/package/systemd/ziti-edge-tunnel.env.in b/programs/ziti-edge-tunnel/package/systemd/ziti-edge-tunnel.env.in index 5ff6e32d..b4abce72 100644 --- a/programs/ziti-edge-tunnel/package/systemd/ziti-edge-tunnel.env.in +++ b/programs/ziti-edge-tunnel/package/systemd/ziti-edge-tunnel.env.in @@ -1,3 +1,9 @@ +# all enrollment tokens named *.jwt are consumed and replaced with identity JSON files to be loaded at startup ZITI_IDENTITY_DIR='@ZITI_IDENTITY_DIR@' + +# reserved dynamic IP range for proxied services ZITI_DNS_IP_RANGE='100.64.0.1/10' + +# the log level specified in @ZITI_STATE_DIR@/config.json has higher precedence than this env var; delete or modify that +# file or set via IPC "ziti-edge-tunnel set_log_level --loglevel DEBUG" ZITI_VERBOSE=2 diff --git a/programs/ziti-edge-tunnel/windows-scripts.c b/programs/ziti-edge-tunnel/windows-scripts.c index 04992fca..e4741bb6 100644 --- a/programs/ziti-edge-tunnel/windows-scripts.c +++ b/programs/ziti-edge-tunnel/windows-scripts.c @@ -436,7 +436,7 @@ void remove_single_nrpt_rule(char* nrpt_rule) { } } -bool is_nrpt_policies_effective(char* tns_ip) { +bool is_nrpt_policies_effective(const char* tns_ip) { char add_cmd[MAX_POWERSHELL_COMMAND_LEN]; size_t buf_len = sprintf(add_cmd, "powershell -Command \"Add-DnsClientNrptRule -Namespace '.ziti.test' -NameServers '%s' -Comment 'Added by ziti-edge-tunnel' -DisplayName 'ziti-edge-tunnel:.ziti.test'\"",tns_ip); ZITI_LOG(TRACE, "add test nrpt rule. total script size: %zd", buf_len); @@ -477,9 +477,9 @@ bool is_nrpt_policies_effective(char* tns_ip) { } } -void update_interface_metric(uv_loop_t *ziti_loop, char* tun_name, int metric) { +void update_interface_metric(uv_loop_t *ziti_loop, wchar_t* tun_name, int metric) { char script[MAX_POWERSHELL_SCRIPT_LEN] = { 0 }; - size_t buf_len = sprintf(script, "$i=Get-NetIPInterface | Where -FilterScript {$_.InterfaceAlias -Eq \"%ls\"}\n", tun_name); + size_t buf_len = sprintf(script, "$i=Get-NetIPInterface | Where -FilterScript {$_.InterfaceAlias -Eq \"%ls\"}; ", tun_name); size_t copied = buf_len; buf_len = sprintf(script + copied, "Set-NetIPInterface -InterfaceIndex $i.ifIndex -InterfaceMetric %d", metric); copied += buf_len; diff --git a/programs/ziti-edge-tunnel/windows-service.c b/programs/ziti-edge-tunnel/windows-service.c index 0b702fa0..6546e980 100644 --- a/programs/ziti-edge-tunnel/windows-service.c +++ b/programs/ziti-edge-tunnel/windows-service.c @@ -437,7 +437,7 @@ DWORD get_process_path(LPTSTR lpBuffer, DWORD nBufferLength) { // Return value: // None // -DWORD LphandlerFunctionEx( +DWORD WINAPI LphandlerFunctionEx( DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, @@ -489,7 +489,7 @@ BOOL SetPrivilege(HANDLE hToken, LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) // // Enable the privilege or disable all privileges. // - if (AdjustTokenPrivileges(hToken, FALSE, &tp, NULL, (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) + if (AdjustTokenPrivileges(hToken, FALSE, &tp, 0, (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) { // // Check to see if you have proper access. diff --git a/programs/ziti-edge-tunnel/windows/log_utils.c b/programs/ziti-edge-tunnel/windows/log_utils.c index 977fbaa6..83796130 100644 --- a/programs/ziti-edge-tunnel/windows/log_utils.c +++ b/programs/ziti-edge-tunnel/windows/log_utils.c @@ -25,16 +25,21 @@ #include "windows/windows-scripts.h" #include +#if _WIN32 +#include +#define mkdir(path, mode) CreateDirectory(path, NULL) +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif +#define realpath(rel, abs) _fullpath(abs, rel, PATH_MAX) +#endif + static bool open_log(char* log_filename); static bool rotate_log(); static char* log_filename; static void set_is_interactive(); static BOOL is_interactive = TRUE; - - - - char* get_log_file_name(){ return log_filename; } @@ -47,6 +52,25 @@ static struct tm *start_time; static const char* log_filename_base = "ziti-tunneler.log"; static int rotation_count = 7; +static uint8_t mkdir_p(const char *path) { + char actualpath[PATH_MAX]; + char *cur_path = realpath(path, actualpath); + char *p = cur_path; + struct stat info; + while(*p != '\0') { + if(*p == PATH_SEP) { + *p = 0; + if (stat(cur_path, &info) != 0){ + mkdir(cur_path, NULL); + } + *p = PATH_SEP; + } + p++; + } + + return mkdir(cur_path, NULL); +} + static char* get_log_path() { char process_dir[FILENAME_MAX]; //create string buffer to hold path char process_full_path[FILENAME_MAX]; @@ -56,24 +80,34 @@ static char* get_log_path() { _splitpath_s(process_full_path, drive, sizeof(drive), dir, sizeof(dir), NULL, 0, NULL, 0); _makepath_s(process_dir, sizeof(process_dir), drive, dir, NULL, NULL); + size_t process_dir_len = strlen(process_dir); + if(process_dir_len> 200) { + printf("Process directory is too long for logging. Please shorten the path where the binary is installed.\n"); + exit(0); + } + char* log_path = calloc(FILENAME_MAX, sizeof(char)); - snprintf(log_path, FILENAME_MAX, "%s/logs", process_dir); - int check; - mkdir(log_path); - strcat(log_path, "/service"); - check = mkdir(log_path); - if (check == 0) { - printf("\nlog path is created at %s", log_path); + if(process_dir[strlen(process_dir)-1] != PATH_SEP) { + snprintf(log_path, FILENAME_MAX, "%s%clogs%cservice", process_dir, PATH_SEP, PATH_SEP); } else { - printf("\nlog path is found at %s", log_path); + snprintf(log_path, FILENAME_MAX, "%slogs%cservice", process_dir, PATH_SEP); } + mkdir_p(log_path); + + struct stat info; + if (stat(log_path, &info) != 0) { + fprintf(stderr,"\nlogging cannot proceed. the path could not be created: %s!\n", log_path); + } else { + fprintf(stderr,"\nlogs enabled at: %s\n", log_path); + } + return log_path; } char* get_base_filename() { char* log_path = get_log_path(); char* temp_log_filename = calloc(FILENAME_MAX, sizeof(char)); - snprintf(temp_log_filename, FILENAME_MAX, "%s/%s", log_path, log_filename_base); + snprintf(temp_log_filename, FILENAME_MAX, "%s%c%s", log_path, PATH_SEP, log_filename_base); free(log_path); return temp_log_filename; } @@ -300,7 +334,7 @@ static void delete_older_logs(uv_async_t *ar) { } if (old_log != NULL) { char logfile_to_delete[MAX_PATH]; - snprintf(logfile_to_delete, MAX_PATH, "%s/%s", log_path, old_log); + snprintf(logfile_to_delete, MAX_PATH, "%s%c%s", log_path, PATH_SEP, old_log); ZITI_LOG(INFO, "Deleting old log file %s", logfile_to_delete); remove(logfile_to_delete); rotation_index--; diff --git a/programs/ziti-edge-tunnel/wintun.cmake b/programs/ziti-edge-tunnel/wintun.cmake index 48651951..2d1cd0cf 100644 --- a/programs/ziti-edge-tunnel/wintun.cmake +++ b/programs/ziti-edge-tunnel/wintun.cmake @@ -1,8 +1,8 @@ FetchContent_Declare(wintun - URL https://www.wintun.net/builds/wintun-0.10.3.zip - URL_HASH SHA256=97de836805006c39c3c6ddf57bac0707d096cc88a9ca0b552cb95f1de08da060 + URL https://www.wintun.net/builds/wintun-0.14.1.zip + DOWNLOAD_EXTRACT_TIMESTAMP TRUE ) FetchContent_GetProperties(wintun) @@ -11,4 +11,4 @@ if(NOT wintun_POPULATED) endif() add_library(wintun INTERFACE) -target_include_directories(subcommand INTERFACE ${wintun_SOURCE_DIR}/include) +target_include_directories(wintun INTERFACE ${wintun_SOURCE_DIR}/include) diff --git a/programs/ziti-edge-tunnel/ziti-edge-tunnel.c b/programs/ziti-edge-tunnel/ziti-edge-tunnel.c index d3c439af..0a548b87 100644 --- a/programs/ziti-edge-tunnel/ziti-edge-tunnel.c +++ b/programs/ziti-edge-tunnel/ziti-edge-tunnel.c @@ -39,6 +39,7 @@ #endif #elif _WIN32 #include +#include #include "netif_driver/windows/tun.h" #include "windows/windows-service.h" #include "windows/windows-scripts.h" @@ -109,8 +110,6 @@ struct enroll_cb_params { uv_buf_t config; /* out */ }; -static char* ipc_cmd_buffered(ipc_cmd_ctx_t *ipc_cmd_ctx_new); - struct cfg_instance_s { char *cfg; LIST_ENTRY(cfg_instance_s) _next; @@ -127,7 +126,7 @@ struct event_conn_s { static LIST_HEAD(events_list, event_conn_s) event_clients_list = LIST_HEAD_INITIALIZER(event_clients_list); struct ipc_conn_s { - uv_pipe_t *ipc_client_conn; + uv_pipe_t ipc; LIST_ENTRY(ipc_conn_s) _next_ipc_cmd; }; // list to store the ipc connections @@ -135,8 +134,9 @@ static LIST_HEAD(ipc_list, ipc_conn_s) ipc_clients_list = LIST_HEAD_INITIALIZER( static long refresh_metrics = 5000; static long metrics_latency = 5000; -static char* configured_cidr; - +static char *configured_cidr = NULL; +static char *configured_log_level = NULL; +static char *configured_proxy = NULL; static char *config_dir = NULL; static uv_pipe_t cmd_server; @@ -151,7 +151,7 @@ static const ziti_tunnel_ctrl *CMD_CTRL; static bool started_by_scm = false; static bool tunnel_interrupted = false; -uv_loop_t *main_ziti_loop; +uv_loop_t *global_loop_ref = NULL; tunneler_context tunneler; static uv_mutex_t stop_mutex; static uv_cond_t stop_cond; @@ -162,6 +162,7 @@ static char cmdsockfile[] = "\\\\.\\pipe\\ziti-edge-tunnel.sock"; static char eventsockfile[] = "\\\\.\\pipe\\ziti-edge-tunnel-event.sock"; #elif __unix__ || unix || ( __APPLE__ && __MACH__ ) #include +#include #define SOCKET_PATH "/tmp/.ziti" static char cmdsockfile[] = SOCKET_PATH "/ziti-edge-tunnel.sock"; static char eventsockfile[] = SOCKET_PATH "/ziti-edge-tunnel-event.sock"; @@ -218,6 +219,15 @@ static void cmd_alloc(uv_handle_t *s, size_t sugg, uv_buf_t *b) { b->len = sugg; } +static void send_tunnel_status(char* status) { + tunnel_status_event tnl_sts_evt = {0}; + tnl_sts_evt.Op = strdup(status); + tnl_sts_evt.Status = get_tunnel_status(); + send_events_message(&tnl_sts_evt, (to_json_fn) tunnel_status_event_to_json, true); + tnl_sts_evt.Status = NULL; //don't free + free_tunnel_status_event(&tnl_sts_evt); +} + static void on_cmd_write(uv_write_t *wr, int len) { if (wr->data) { free(wr->data); @@ -228,7 +238,7 @@ static void on_cmd_write(uv_write_t *wr, int len) { static void on_command_resp(const tunnel_result* result, void *ctx) { size_t json_len; char *json = tunnel_result_to_json(result, MODEL_JSON_COMPACT, &json_len); - ZITI_LOG(INFO, "resp[%d,len=%zd] = %.*s", + ZITI_LOG(TRACE, "resp[%d,len=%zd] = %.*s", result->success, json_len, (int)json_len, json); if (result->data != NULL) { @@ -236,39 +246,40 @@ static void on_command_resp(const tunnel_result* result, void *ctx) { if (parse_tunnel_command(&tnl_res_cmd, result->data, strlen(result->data)) >= 0) { switch (tnl_res_cmd.command) { case TunnelCommand_RemoveIdentity: { - tunnel_delete_identity tnl_delete_id; - if (tnl_res_cmd.data != NULL && parse_tunnel_delete_identity(&tnl_delete_id, tnl_res_cmd.data, strlen(tnl_res_cmd.data)) >= 0) { + tunnel_identity_id tnl_delete_id; + if (tnl_res_cmd.data != NULL && parse_tunnel_identity_id(&tnl_delete_id, tnl_res_cmd.data, strlen(tnl_res_cmd.data)) >= 0) { if (tnl_delete_id.identifier == NULL) { ZITI_LOG(ERROR, "Identity filename is not found in the remove identity request, not deleting the identity file"); break; } - // delete identity file - remove(tnl_delete_id.identifier); - ZITI_LOG(INFO, "Identity file %s is deleted",tnl_delete_id.identifier); #if _WIN32 - tunnel_identity *id = create_or_get_tunnel_identity(tnl_delete_id.identifier, NULL); - if (id->Services) { - model_map hostnamesToRemove = {0}; - for (int index=0 ; id->Services[index]; index++ ) { - tunnel_service *tnl_svc = id->Services[index]; - if (tnl_svc->Addresses != NULL) { - for (int i = 0; tnl_svc->Addresses[i]; i++) { - tunnel_address *addr = tnl_svc->Addresses[i]; - if (addr->IsHost && model_map_get(&hostnamesToRemove, addr->HostName) == NULL) { - model_map_set(&hostnamesToRemove, addr->HostName, "TRUE"); + tunnel_identity *id = find_tunnel_identity(tnl_delete_id.identifier); + if(id != NULL) { + if (id->Services) { + model_map hostnamesToRemove = {0}; + for (int index = 0; id->Services[index]; index++) { + tunnel_service *tnl_svc = id->Services[index]; + if (tnl_svc->Addresses != NULL) { + for (int i = 0; tnl_svc->Addresses[i]; i++) { + tunnel_address *addr = tnl_svc->Addresses[i]; + if (addr->IsHost && + model_map_get(&hostnamesToRemove, addr->HostName) == NULL) { + model_map_set(&hostnamesToRemove, addr->HostName, "TRUE"); + } } } } - } - if (model_map_size(&hostnamesToRemove) > 0) { - remove_nrpt_rules(main_ziti_loop, &hostnamesToRemove); + if (model_map_size(&hostnamesToRemove) > 0) { + remove_nrpt_rules(global_loop_ref, &hostnamesToRemove); + } } + } else { + ZITI_LOG(WARN, "asked to remove identity, but identity was not found: %s", tnl_delete_id.identifier); } - #endif delete_identity_from_instance(tnl_delete_id.identifier); - free_tunnel_delete_identity(&tnl_delete_id); + free_tunnel_identity_id(&tnl_delete_id); // should be the last line in this function as it calls the mutex/lock save_tunnel_status_to_file(); } @@ -362,6 +373,7 @@ void tunnel_enroll_cb(const ziti_config *cfg, int status, const char *err, void send_tunnel_command(&tnl_cmd, add_id_req->cmd_ctx); free_tunnel_command(&tnl_cmd); free(add_id_req); + save_tunnel_status_to_file(); } static void enroll_ziti_async(uv_loop_t *loop, void *arg) { @@ -370,6 +382,7 @@ static void enroll_ziti_async(uv_loop_t *loop, void *arg) { ziti_enroll_opts enroll_opts = {0}; enroll_opts.enroll_name = add_id_req->identifier; enroll_opts.jwt_content = add_id_req->jwt_content; + enroll_opts.use_keychain = add_id_req->use_keychain; ziti_enroll(&enroll_opts, loop, tunnel_enroll_cb, add_id_req); } @@ -387,7 +400,9 @@ static bool process_tunnel_commands(const tunnel_command *tnl_cmd, command_cb cb cmd_accepted = true; tunnel_set_log_level tunnel_set_log_level_cmd = {0}; - if (tnl_cmd->data == NULL || parse_tunnel_set_log_level(&tunnel_set_log_level_cmd, tnl_cmd->data, strlen(tnl_cmd->data)) < 0) { + if (tnl_cmd->data == NULL || + parse_tunnel_set_log_level(&tunnel_set_log_level_cmd, tnl_cmd->data, strlen(tnl_cmd->data)) < 0 || + tunnel_set_log_level_cmd.loglevel == NULL) { result.error = "invalid command"; result.success = false; break; @@ -396,8 +411,9 @@ static bool process_tunnel_commands(const tunnel_command *tnl_cmd, command_cb cb if (strcasecmp(ziti_log_level_label(), tunnel_set_log_level_cmd.loglevel) != 0) { ziti_log_set_level_by_label(tunnel_set_log_level_cmd.loglevel); ziti_tunnel_set_log_level(get_log_level(tunnel_set_log_level_cmd.loglevel)); - set_log_level(ziti_log_level_label()); - ZITI_LOG(INFO, "Log level is set to %s", tunnel_set_log_level_cmd.loglevel); + const char *level = ziti_log_level_label(); + set_log_level(level); + ZITI_LOG(INFO, "Log level is set to %s", level); } else { ZITI_LOG(INFO, "Log level is already set to %s", tunnel_set_log_level_cmd.loglevel); } @@ -422,7 +438,7 @@ static bool process_tunnel_commands(const tunnel_command *tnl_cmd, command_cb cb break; } char* tun_ip_str = strdup(tunnel_tun_ip_v4_cmd.tunIP); - // make a copy so we can free it later - validating ip address input + // make a copy, so we can free it later - validating ip address input char* tun_ip_cpy = tun_ip_str; char* ip_ptr = strtok(tun_ip_str, "."); //cut the string using dot delimiter if (ip_ptr == NULL) { @@ -469,7 +485,9 @@ static bool process_tunnel_commands(const tunnel_command *tnl_cmd, command_cb cb free_tunnel_tun_ip_v4(&tunnel_tun_ip_v4_cmd); break; } - set_tun_ipv4_into_instance(tunnel_tun_ip_v4_cmd.tunIP, tunnel_tun_ip_v4_cmd.prefixLength, tunnel_tun_ip_v4_cmd.addDns); + set_tun_ipv4_into_instance(tunnel_tun_ip_v4_cmd.tunIP, + (int)tunnel_tun_ip_v4_cmd.prefixLength, + tunnel_tun_ip_v4_cmd.addDns); result.success = true; result.code = IPC_SUCCESS; break; @@ -496,6 +514,24 @@ static bool process_tunnel_commands(const tunnel_command *tnl_cmd, command_cb cb break; } + if (tunnel_add_identity_cmd.jwtFileName == NULL) { + result.error = "identity filename not provided"; + result.success = false; + break; + } + + if (tunnel_add_identity_cmd.jwtContent == NULL) { + result.error = "jwt content not provided"; + result.success = false; + break; + } + + if (config_dir == NULL) { + result.error = "config directory not set"; + result.success = false; + break; + } + char* extension = strstr(tunnel_add_identity_cmd.jwtFileName, ".jwt"); size_t length; if (extension != NULL) { @@ -506,14 +542,14 @@ static bool process_tunnel_commands(const tunnel_command *tnl_cmd, command_cb cb char new_identifier[FILENAME_MAX] = {0}; char new_identifier_name[FILENAME_MAX] = {0}; if ((strlen(config_dir) + length + 6) > FILENAME_MAX - 1 ) { - ZITI_LOG(ERROR, "failed to create file %s/%s.json, The length of the file name is longer than %d", config_dir, tunnel_add_identity_cmd.jwtFileName, FILENAME_MAX); + ZITI_LOG(ERROR, "failed to create file %s%c%s.json, The length of the file name is longer than %d", config_dir, PATH_SEP, tunnel_add_identity_cmd.jwtFileName, FILENAME_MAX); result.error = "invalid file name"; result.success = false; free_tunnel_add_identity(&tunnel_add_identity_cmd); break; } strncpy(new_identifier_name, tunnel_add_identity_cmd.jwtFileName, length); - sprintf(new_identifier, "%s/%s.json", config_dir, new_identifier_name); + snprintf(new_identifier, FILENAME_MAX, "%s%c%s.json", config_dir, PATH_SEP, new_identifier_name); FILE *outfile; if ((outfile = fopen(new_identifier, "wb")) == NULL) { ZITI_LOG(ERROR, "failed to open file %s: %s(%d)", new_identifier, strerror(errno), errno); @@ -530,8 +566,9 @@ static bool process_tunnel_commands(const tunnel_command *tnl_cmd, command_cb cb add_id_req->identifier = strdup(new_identifier); add_id_req->identifier_file_name = strdup(new_identifier_name); add_id_req->jwt_content = strdup(tunnel_add_identity_cmd.jwtContent); + add_id_req->use_keychain = tunnel_add_identity_cmd.useKeychain; - enroll_ziti_async(main_ziti_loop, add_id_req); + enroll_ziti_async(global_loop_ref, add_id_req); free_tunnel_add_identity(&tunnel_add_identity_cmd); return true; } @@ -553,7 +590,7 @@ static bool process_tunnel_commands(const tunnel_command *tnl_cmd, command_cb cb if (!stop_windows_service()) { ZITI_LOG(INFO, "Could not send stop signal to scm, Tunnel must not be started as service"); stop_tunnel_and_cleanup(); - uv_stop(main_ziti_loop); + uv_stop(global_loop_ref); } } free_tunnel_service_control(&tunnel_service_control_opts); @@ -593,18 +630,9 @@ static bool process_tunnel_commands(const tunnel_command *tnl_cmd, command_cb cb } } -static void queue_ipc_command(ipc_cmd_ctx_t *ipc_command_ctx, ssize_t len, char* base) { - struct ipc_cmd_s *cmd_new = calloc(1, sizeof(struct ipc_cmd_s)); - cmd_new->cmd_data = strdup(base); - cmd_new->len = len; - uv_mutex_trylock(&ipc_command_ctx->cmd_lock); - STAILQ_INSERT_TAIL(&ipc_command_ctx->ipc_cmd_queue, cmd_new, _next); - uv_mutex_unlock(&ipc_command_ctx->cmd_lock); -} - -static void process_ipc_command(uv_stream_t *s, ssize_t len, char* base) { +static void process_ipc_command(uv_stream_t *s, json_object *json) { tunnel_command tnl_cmd = {0}; - if (parse_tunnel_command(&tnl_cmd, base, len) >= 0) { + if (tunnel_command_from_json(&tnl_cmd, json) >= 0) { // process_tunnel_commands is used to update the log level and the tun ip information in the config file through IPC command. // So when the user restarts the tunnel, the new values will be taken. // The config file can be modified only from ziti-edge-tunnel.c file. @@ -627,35 +655,42 @@ static void process_ipc_command(uv_stream_t *s, ssize_t len, char* base) { static void on_cmd(uv_stream_t *s, ssize_t len, const uv_buf_t *b) { + struct ipc_conn_s *ipc_client = (struct ipc_conn_s*)s; if (len < 0) { - ZITI_LOG(WARN, "received from client - %s. Closing connection.", uv_err_name(len)); - struct ipc_conn_s *del_ipc_client = NULL; - LIST_FOREACH(del_ipc_client, &ipc_clients_list, _next_ipc_cmd) { - if((uv_stream_t *)del_ipc_client->ipc_client_conn == s) { - break; - } - } - if (del_ipc_client) { - LIST_REMOVE(del_ipc_client, _next_ipc_cmd); - free(del_ipc_client); + if (len != UV_EOF) { + ZITI_LOG(WARN, "received from client - %s. Closing connection.", uv_err_name(len)); } - uv_close((uv_handle_t *) s, (uv_close_cb) free); - ZITI_LOG(WARN,"IPC client connection closed, count: %d", sizeof_ipc_clients_list()); + + LIST_REMOVE(ipc_client, _next_ipc_cmd); + + json_tokener *tokener = s->data; + if (tokener) json_tokener_free(tokener); + uv_close((uv_handle_t *) &ipc_client->ipc, (uv_close_cb) free); + ZITI_LOG(DEBUG, "IPC client connection closed, count: %d", sizeof_ipc_clients_list()); } else { - ZITI_LOG(INFO, "received cmd <%.*s>", (int) len, b->base); + ZITI_LOG(DEBUG, "received cmd <%.*s>", (int) len, b->base); -#if LAST_CHAR_IPC_CMD == '\0' - process_ipc_command(s, len, b->base); -#else - queue_ipc_command(ipc_cmd_ctx, len, b->base); - if (b->base[len-1] == LAST_CHAR_IPC_CMD) { - char* new_buff = ipc_cmd_buffered(ipc_cmd_ctx); - ZITI_LOG(TRACE, "buffered cmd <%.*s>", (int) (strlen(new_buff) + 1), new_buff); - process_ipc_command(s, strlen(new_buff) + 1, new_buff); - free(new_buff); + json_tokener *parser = s->data; + + size_t processed = 0; + while (processed < len) { + json_object *json = json_tokener_parse_ex(parser, b->base + processed, (int) (len - processed)); + size_t end = json_tokener_get_parse_end(parser); + processed += end; + if (json) { + process_ipc_command(s, json); + json_object_put(json); + } else if (json_tokener_get_error(parser) != json_tokener_continue) { + ZITI_LOG(ERROR, "failed to parse json command: %s, received[%.*s]", + json_tokener_error_desc(json_tokener_get_error(parser)), + (int) len, b->base); + json_tokener_free(parser); + LIST_REMOVE(ipc_client, _next_ipc_cmd); + uv_close((uv_handle_t *) &ipc_client->ipc, (uv_close_cb) free); + break; + } } -#endif } free(b->base); @@ -663,13 +698,12 @@ static void on_cmd(uv_stream_t *s, ssize_t len, const uv_buf_t *b) { static void on_cmd_client(uv_stream_t *s, int status) { int current_ipc_channels = sizeof_ipc_clients_list(); - uv_pipe_t *cmd_conn = malloc(sizeof(uv_pipe_t)); - uv_pipe_init(s->loop, cmd_conn, 0); - uv_accept(s, (uv_stream_t *) cmd_conn); - uv_read_start((uv_stream_t *) cmd_conn, cmd_alloc, on_cmd); - struct ipc_conn_s *ipc_conn = calloc(1, sizeof(struct ipc_conn_s)); - ipc_conn->ipc_client_conn = cmd_conn; - LIST_INSERT_HEAD(&ipc_clients_list, ipc_conn, _next_ipc_cmd); + struct ipc_conn_s *cmd_conn = calloc(1, sizeof(struct ipc_conn_s)); + cmd_conn->ipc.data = json_tokener_new(); + uv_pipe_init(s->loop, &cmd_conn->ipc, 0); + uv_accept(s, (uv_stream_t *) &cmd_conn->ipc); + uv_read_start((uv_stream_t *) &cmd_conn->ipc, cmd_alloc, on_cmd); + LIST_INSERT_HEAD(&ipc_clients_list, cmd_conn, _next_ipc_cmd); ZITI_LOG(DEBUG,"Received IPC client connection request, count: %d", ++current_ipc_channels); } @@ -717,7 +751,6 @@ static int start_cmd_socket(uv_loop_t *l, int sockfd) { return -1; } - static void on_events_client(uv_stream_t *s, int status) { int current_events_channels = sizeof_event_clients_list(); uv_pipe_t* event_conn = malloc(sizeof(uv_pipe_t)); @@ -729,13 +762,7 @@ static void on_events_client(uv_stream_t *s, int status) { ZITI_LOG(DEBUG,"Received events client connection request, count: %d", ++current_events_channels); // send status message immediately - tunnel_status_event tnl_sts_evt = {0}; - tnl_sts_evt.Op = strdup("status"); - tnl_sts_evt.Status = get_tunnel_status(); - send_events_message(&tnl_sts_evt, (to_json_fn) tunnel_status_event_to_json, true); - tnl_sts_evt.Status = NULL; - free_tunnel_status_event(&tnl_sts_evt); - + send_tunnel_status("status"); } @@ -745,7 +772,7 @@ void on_write_event(uv_write_t* req, int status) { if (status == UV_EPIPE) { struct event_conn_s *event_client; LIST_FOREACH(event_client, &event_clients_list, _next_event) { - if (event_client->event_client_conn == req->handle) { + if (event_client->event_client_conn == (uv_pipe_t*) req->handle) { break; } } @@ -907,7 +934,7 @@ static char* addUnit(int count, char* unit) { return result; } -static string convert_seconds_to_readable_format(int input) { +static char* convert_seconds_to_readable_format(int input) { int seconds = input % (60 * 60 * 24); int hours = (int)((double) seconds / 60 / 60); seconds = input % (60 * 60); @@ -950,10 +977,10 @@ static bool check_send_notification(tunnel_identity *tnl_id) { return false; } if (tnl_id->MfaMinTimeoutRem > 0) { - tnl_id->MfaMinTimeoutRem = get_remaining_timeout(tnl_id->MfaMinTimeout, tnl_id->MinTimeoutRemInSvcEvent, tnl_id); + tnl_id->MfaMinTimeoutRem = get_remaining_timeout((int)tnl_id->MfaMinTimeout, (int)tnl_id->MinTimeoutRemInSvcEvent, tnl_id); } if (tnl_id->MfaMaxTimeoutRem > 0) { - tnl_id->MfaMaxTimeoutRem = get_remaining_timeout(tnl_id->MfaMaxTimeout, tnl_id->MaxTimeoutRemInSvcEvent, tnl_id); + tnl_id->MfaMaxTimeoutRem = get_remaining_timeout((int)tnl_id->MfaMaxTimeout, (int)tnl_id->MaxTimeoutRemInSvcEvent, tnl_id); } if (tnl_id->Notified) { @@ -971,21 +998,22 @@ static bool check_send_notification(tunnel_identity *tnl_id) { static notification_message *create_notification_message(tunnel_identity *tnl_id) { notification_message *notification = calloc(1, sizeof(struct notification_message_s)); - notification->Message = calloc(MAXMESSAGELEN, sizeof(char)); + char *Message = calloc(MAXMESSAGELEN, sizeof(char)); if (tnl_id->MfaMaxTimeoutRem == 0) { - snprintf(notification->Message, MAXMESSAGELEN, "All of the services of identity %s have timed out", tnl_id->Name); + snprintf(Message, MAXMESSAGELEN, "All of the services of identity %s have timed out", tnl_id->Name); notification->Severity = event_severity_critical; } else if (tnl_id->MfaMinTimeoutRem == 0) { - snprintf(notification->Message, MAXMESSAGELEN, "Some of the services of identity %s have timed out", tnl_id->Name); + snprintf(Message, MAXMESSAGELEN, "Some of the services of identity %s have timed out", tnl_id->Name); notification->Severity = event_severity_major; } else if (tnl_id->MfaMinTimeoutRem <= 20*60) { - char* message = convert_seconds_to_readable_format(tnl_id->MfaMinTimeoutRem); - snprintf(notification->Message, MAXMESSAGELEN, "Some of the services of identity %s are timing out in %s", tnl_id->Name, message); + char* message = convert_seconds_to_readable_format((int)tnl_id->MfaMinTimeoutRem); + snprintf(Message, MAXMESSAGELEN, "Some of the services of identity %s are timing out in %s", tnl_id->Name, message); free(message); notification->Severity = event_severity_minor; } else { // do nothing } + notification->Message = Message; notification->IdentityName = strdup(tnl_id->Name); notification->Identifier = strdup(tnl_id->Identifier); @@ -1011,16 +1039,16 @@ static void broadcast_metrics(uv_timer_t *timer) { model_map notification_map = {0}; for(idx = 0; metrics_event.Identities[idx]; idx++) { tnl_id = metrics_event.Identities[idx]; - if (tnl_id->Active && tnl_id->Loaded && tnl_id->IdFileStatus) { + if (tnl_id->Active && tnl_id->Loaded) { active_identities = true; - tunnel_get_identity_metrics get_metrics = { + tunnel_identity_id get_metrics = { .identifier = tnl_id->Identifier, }; size_t json_len; tunnel_command tnl_cmd = { .command = TunnelCommand_GetMetrics, - .data = tunnel_get_identity_metrics_to_json(&get_metrics, MODEL_JSON_COMPACT, &json_len), + .data = tunnel_identity_id_to_json(&get_metrics, MODEL_JSON_COMPACT, &json_len), }; tunnel_command_inline *tnl_cmd_inline = alloc_tunnel_command_inline(); @@ -1113,7 +1141,7 @@ static void load_identities(uv_work_t *wr) { continue; } - char* ext = get_filename_ext(file.name); + const char* ext = get_filename_ext(file.name); // ignore back up files if (strcasecmp(ext, ".bak") == 0 || strcasecmp(ext, ".original") == 0 || strcasecmp(ext, "json") != 0) { @@ -1125,7 +1153,7 @@ static void load_identities(uv_work_t *wr) { if (file.type == UV_DIRENT_FILE) { struct cfg_instance_s *inst = calloc(1, sizeof(struct cfg_instance_s)); inst->cfg = malloc(MAXPATHLEN); - snprintf(inst->cfg, MAXPATHLEN, "%s/%s", config_dir, file.name); + snprintf(inst->cfg, MAXPATHLEN, "%s%c%s", config_dir, PATH_SEP, file.name); create_or_get_tunnel_identity(inst->cfg, file.name); LIST_INSERT_HEAD(&load_list, inst, _next); } @@ -1135,6 +1163,7 @@ static void load_identities(uv_work_t *wr) { static void load_id_cb(const tunnel_result *res, void *ctx) { struct cfg_instance_s *inst = ctx; + if (res->success) { ZITI_LOG(INFO, "identity[%s] loaded", inst->cfg); } else { @@ -1148,11 +1177,18 @@ static void load_identities_complete(uv_work_t * wr, int status) { struct cfg_instance_s *inst = LIST_FIRST(&load_list); LIST_REMOVE(inst, _next); - CMD_CTRL->load_identity(NULL, inst->cfg, get_api_page_size(), load_id_cb, inst); - identity_loaded = true; if (config_dir == NULL) { create_or_get_tunnel_identity(inst->cfg, inst->cfg); } + + tunnel_identity *id = find_tunnel_identity(inst->cfg); + if(id != NULL) { + CMD_CTRL->load_identity(NULL, inst->cfg, !id->Active, get_api_page_size(), load_id_cb, inst); + } else { + ZITI_LOG(WARN, "identity not found? %s", inst->cfg); + } + + identity_loaded = true; } if (identity_loaded) { start_metrics_timer(wr->loop); @@ -1163,11 +1199,11 @@ static void load_identities_complete(uv_work_t * wr, int status) { } static void on_event(const base_event *ev) { + tunnel_identity *id = find_tunnel_identity(ev->identifier); switch (ev->event_type) { case TunnelEvent_ContextEvent: { const ziti_ctx_event *zev = (ziti_ctx_event *) ev; ZITI_LOG(INFO, "ztx[%s] context event : status is %s", ev->identifier, zev->status); - tunnel_identity *id = find_tunnel_identity(ev->identifier); if (id == NULL) { break; } @@ -1180,6 +1216,7 @@ static void on_event(const base_event *ev) { id_event.Fingerprint = strdup(id_event.Id->FingerPrint); } id_event.Id->Loaded = true; + id_event.Id->NeedsExtAuth = false; action_event controller_event = {0}; controller_event.Op = strdup("controller"); @@ -1189,10 +1226,9 @@ static void on_event(const base_event *ev) { } if (zev->code == ZITI_OK) { - id_event.Id->Active = true; // determine it from controller if (zev->name) { if (id_event.Id->Name != NULL && strcmp(id_event.Id->Name, zev->name) != 0) { - free(id_event.Id->Name); + free((char*)id_event.Id->Name); id_event.Id->Name = strdup(zev->name); } else if (id_event.Id->Name == NULL) { id_event.Id->Name = strdup(zev->name); @@ -1200,7 +1236,7 @@ static void on_event(const base_event *ev) { } if (zev->version) { if (id_event.Id->ControllerVersion != NULL && strcmp(id_event.Id->ControllerVersion, zev->version) != 0) { - free(id_event.Id->ControllerVersion); + free((char*)id_event.Id->ControllerVersion); id_event.Id->ControllerVersion = strdup(zev->version); } else if (id_event.Id->ControllerVersion == NULL) { id_event.Id->ControllerVersion = strdup(zev->version); @@ -1208,7 +1244,7 @@ static void on_event(const base_event *ev) { } if (zev->controller) { if (id_event.Id->Config != NULL && id_event.Id->Config->ZtAPI != NULL && strcmp(id_event.Id->Config->ZtAPI, zev->controller) != 0) { - free(id_event.Id->Config->ZtAPI); + free((char*)id_event.Id->Config->ZtAPI); id_event.Id->Config->ZtAPI = strdup(zev->controller); } else if (id_event.Id->Config == NULL) { id_event.Id->Config = calloc(1, sizeof(tunnel_config)); @@ -1236,7 +1272,6 @@ static void on_event(const base_event *ev) { case TunnelEvent_ServiceEvent: { const service_event *svc_ev = (service_event *) ev; ZITI_LOG(VERBOSE, "=============== ztx[%s] service event ===============", ev->identifier); - tunnel_identity *id = find_tunnel_identity(ev->identifier); if (id == NULL) { break; } @@ -1296,7 +1331,8 @@ static void on_event(const base_event *ev) { if (svc->Addresses != NULL) { for (int i = 0; svc->Addresses[i]; i++) { tunnel_address *addr = svc->Addresses[i]; - if (addr->IsHost && model_map_get(&hostnamesToAdd, addr->HostName) == NULL) { + bool has_dial = ziti_service_has_permission(svc_ev->added_services[svc_idx], ziti_session_type_Dial); + if (addr->IsHost && model_map_get(&hostnamesToAdd, addr->HostName) == NULL && has_dial) { if (model_map_get(&hostnamesToRemove, addr->HostName) != NULL) { model_map_set(&hostnamesToEdit, addr->HostName, "TRUE"); } else { @@ -1323,14 +1359,15 @@ static void on_event(const base_event *ev) { } } } - if (model_map_size(&hostnamesToEdit) > 0 && !is_host_only()) { - remove_and_add_nrpt_rules(main_ziti_loop, &hostnamesToEdit, get_dns_ip()); + + if (id->Active && model_map_size(&hostnamesToEdit) > 0 && !is_host_only()) { + remove_and_add_nrpt_rules(global_loop_ref, &hostnamesToEdit, get_dns_ip()); } - if (model_map_size(&hostnamesToAdd) > 0 && !is_host_only()) { - add_nrpt_rules(main_ziti_loop, &hostnamesToAdd, get_dns_ip()); + if (id->Active && model_map_size(&hostnamesToAdd) > 0 && !is_host_only()) { + add_nrpt_rules(global_loop_ref, &hostnamesToAdd, get_dns_ip()); } if (model_map_size(&hostnamesToRemove) > 0 && !is_host_only()) { - remove_nrpt_rules(main_ziti_loop, &hostnamesToRemove); + remove_nrpt_rules(global_loop_ref, &hostnamesToRemove); } #endif @@ -1364,12 +1401,12 @@ static void on_event(const base_event *ev) { case TunnelEvent_MFAEvent: { const mfa_event *mfa_ev = (mfa_event *) ev; - ZITI_LOG(INFO, "ztx[%s] is requesting MFA code", ev->identifier); - tunnel_identity *id = find_tunnel_identity(ev->identifier); + ZITI_LOG(INFO, "ztx[%s] is requesting MFA code. Identity needs MFA", ev->identifier); if (id == NULL) { break; } - set_mfa_status(ev->identifier, true, true); + set_mfa_status(ev->identifier, id->MfaEnabled, true); + send_tunnel_status("status"); mfa_status_event mfa_sts_event = { .Op = strdup("mfa"), .Action = strdup(mfa_ev->operation), @@ -1388,7 +1425,7 @@ static void on_event(const base_event *ev) { case TunnelEvent_MFAStatusEvent:{ const mfa_event *mfa_ev = (mfa_event *) ev; - ZITI_LOG(INFO, "ztx[%s] MFA Status code : %d", ev->identifier, mfa_ev->code); + ZITI_LOG(INFO, "ztx[%s] MFA Status code : %d", ev->identifier, (int)mfa_ev->code); mfa_status_event mfa_sts_event = { .Op = strdup("mfa"), @@ -1414,9 +1451,11 @@ static void on_event(const base_event *ev) { send_events_message(&id_event, (to_json_fn) identity_event_to_json, true); id_event.Id = NULL; free_identity_event(&id_event); + save_tunnel_status_to_file(); // persist the mfa change break; case mfa_status_enrollment_remove: set_mfa_status(ev->identifier, false, false); + save_tunnel_status_to_file(); // persist the mfa change break; case mfa_status_enrollment_challenge: mfa_sts_event.RecoveryCodes = mfa_ev->recovery_codes; @@ -1432,7 +1471,9 @@ static void on_event(const base_event *ev) { mfa_sts_event.Error = strdup(mfa_ev->status); } - tunnel_identity *id = create_or_get_tunnel_identity(ev->identifier, NULL); + if (id == NULL) { + id = create_or_get_tunnel_identity(ev->identifier, NULL); + } if (id->FingerPrint) { mfa_sts_event.Fingerprint = strdup(id->FingerPrint); } @@ -1442,14 +1483,12 @@ static void on_event(const base_event *ev) { mfa_sts_event.RecoveryCodes = NULL; free_mfa_status_event(&mfa_sts_event); free_mfa_event((mfa_event *) mfa_ev); - free(mfa_ev); break; } case TunnelEvent_APIEvent: { const api_event *api_ev = (api_event *) ev; ZITI_LOG(INFO, "ztx[%s] API Event with controller address : %s", api_ev->identifier, api_ev->new_ctrl_address); - tunnel_identity *id = find_tunnel_identity(ev->identifier); if (id == NULL) { break; } @@ -1469,7 +1508,7 @@ static void on_event(const base_event *ev) { id_event.Id->Config->ZtAPI = strdup(api_ev->new_ctrl_address); updated = true; } else if (id_event.Id->Config->ZtAPI != NULL && strcmp(id_event.Id->Config->ZtAPI, api_ev->new_ctrl_address) != 0) { - free(id_event.Id->Config->ZtAPI); + free((char*)id_event.Id->Config->ZtAPI); id_event.Id->Config->ZtAPI = strdup(api_ev->new_ctrl_address); updated = true; } @@ -1481,7 +1520,21 @@ static void on_event(const base_event *ev) { free_identity_event(&id_event); break; } - + case TunnelEvent_ExtJWTEvent: + if (id != NULL){ + const ext_signer_event *ese = (const ext_signer_event *) ev; + id->NeedsExtAuth = true; + ZITI_LOG(INFO, "ztx[%s] ext auth: %s", id->Identifier, ese->status); + identity_event id_event = {0}; + id_event.Op = "identity"; + id_event.Action = (char*)event_name(event_needs_ext_login); + id_event.Id = id; + if (id_event.Id->FingerPrint) { + id_event.Fingerprint = id_event.Id->FingerPrint; + } + send_events_message(&id_event, (to_json_fn) identity_event_to_json, true); + } + break; case TunnelEvent_Unknown: default: ZITI_LOG(WARN, "unhandled event received: %d", ev->event_type); @@ -1497,50 +1550,11 @@ static char* normalize_host(char* hostname) { // remove the . from the end of the hostname snprintf(hostname_new, len * sizeof(char), ".%s", hostname); } else { - sprintf(hostname_new,".%s", hostname); + snprintf(hostname_new, len + 2, ".%s", hostname); } return hostname_new; } -static char* ipc_cmd_buffered(ipc_cmd_ctx_t *ipc_cmd_ctx_new) { - - ipc_cmd_q cmd_q; - STAILQ_INIT(&cmd_q); - - uv_mutex_trylock(&ipc_cmd_ctx_new->cmd_lock); - cmd_q = ipc_cmd_ctx_new->ipc_cmd_queue; - uv_mutex_unlock(&ipc_cmd_ctx_new->cmd_lock); - - STAILQ_INIT(&ipc_cmd_ctx_new->ipc_cmd_queue); - struct ipc_cmd_s *ipc_cmd; - - char buff[MAXIPCCOMMANDLEN] = {0}; - ssize_t buff_len = 0; - while (!STAILQ_EMPTY(&cmd_q)) { - ipc_cmd = STAILQ_FIRST(&cmd_q); - STAILQ_REMOVE_HEAD(&cmd_q, _next); - - if (ipc_cmd->cmd_data != NULL) { - strncat(buff, ipc_cmd->cmd_data, ipc_cmd->len); - buff_len += ipc_cmd->len; - } - - free(ipc_cmd->cmd_data); - free(ipc_cmd); - - char lastChar = buff[buff_len - 1]; - if (lastChar == LAST_CHAR_IPC_CMD) { - // end of the ipc command - break; - } - } - - char* buf_new = calloc(buff_len + 1, sizeof(char)); - snprintf(buf_new, buff_len, buff); - return buf_new; - -} - static int run_tunnel(uv_loop_t *ziti_loop, uint32_t tun_ip, uint32_t dns_ip, const char *ip_range, const char *dns_upstream) { netif_driver tun; char tun_error[64]; @@ -1591,18 +1605,11 @@ static int run_tunnel(uv_loop_t *ziti_loop, uint32_t tun_ip, uint32_t dns_ip, co ip_addr_t dns_ip4 = IPADDR4_INIT(dns_ip); ziti_dns_setup(tunneler, ipaddr_ntoa(&dns_ip4), ip_range); if (dns_upstream) { - char *col = strchr(dns_upstream, ':'); - if (col) { - char host[HOST_NAME_MAX]; - snprintf(host, sizeof(host), "%.*s", (int)(col - dns_upstream), dns_upstream); - int port = atoi(col + 1); - if (port < 0 || port > UINT16_MAX) { - ZITI_LOG(ERROR, "invalid upstream DNS server port: %d", port); - } - ziti_dns_set_upstream(ziti_loop, host, port); - } else { - ziti_dns_set_upstream(ziti_loop, dns_upstream, 0); - } + tunnel_upstream_dns upstream = { + .host = dns_upstream + }; + tunnel_upstream_dns *a[] = { &upstream, NULL}; + ziti_dns_set_upstream(ziti_loop, a); } run_tunneler_loop(ziti_loop); if (tun->close) { @@ -1723,6 +1730,13 @@ static void enforce_sockpath(int sockfd, const char* path) { } #endif +#if __linux__ || __APPLE__ +static void on_exit_signal(uv_signal_t *s, int sig) { + ZITI_LOG(WARN, "received signal: %s", strsignal(sig)); + exit(1); +} +#endif + static void run_tunneler_loop(uv_loop_t* ziti_loop) { int cmdsockfile_fd = ZITI_SOCKFD_UNSET; int eventsockfile_fd = ZITI_SOCKFD_UNSET; @@ -1755,6 +1769,23 @@ static void run_tunneler_loop(uv_loop_t* ziti_loop) { ZITI_LOG(DEBUG, "Using socket activation for command and event sockets."); } } +#endif + +#if __linux__ || __APPLE__ +#define handle_sig(n, f) \ + uv_signal_t sig_##n; \ + uv_signal_init(ziti_loop, &sig_##n); \ + uv_signal_start(&sig_##n, f, n); \ + uv_unref((uv_handle_t *) &sig_##n) + + handle_sig(SIGINT, on_exit_signal); + handle_sig(SIGTERM, on_exit_signal); + handle_sig(SIGABRT, on_exit_signal); + handle_sig(SIGSEGV, on_exit_signal); + handle_sig(SIGQUIT, on_exit_signal); + +#undef handle_sig + #endif CMD_CTRL = ziti_tunnel_init_cmd(ziti_loop, tunneler, on_event); @@ -1854,6 +1885,7 @@ static struct option run_options[] = { { "refresh", required_argument, NULL, 'r'}, { "dns-ip-range", required_argument, NULL, 'd'}, { "dns-upstream", required_argument, NULL, 'u'}, + { "proxy", required_argument, NULL, 'x' }, }; static struct option run_host_options[] = { @@ -1861,6 +1893,7 @@ static struct option run_host_options[] = { { "identity-dir", required_argument, NULL, 'I'}, { "verbose", required_argument, NULL, 'v'}, { "refresh", required_argument, NULL, 'r'}, + { "proxy", required_argument, NULL, 'x' }, }; #ifndef DEFAULT_DNS_CIDR @@ -1869,12 +1902,57 @@ static struct option run_host_options[] = { static const char* dns_upstream = NULL; static bool host_only = false; +#include "tlsuv/http.h" + +static int init_proxy_connector(const char *url) { + if (url == NULL) url = getenv("HTTP_PROXY"); + if (url == NULL) url = getenv("http_proxy"); + if (url == NULL) { + ZITI_LOG(DEBUG, "proxy_url not set"); + return 0; + } + + struct tlsuv_url_s proxy_url; + int r = tlsuv_parse_url(&proxy_url, url); + if (r != 0) { + ZITI_LOG(ERROR, "failed to parse '%s' as 'type://[username[:password]@]hostname:port'", url); + return -1; + } + + // assume http if no protocol was specified + if (proxy_url.scheme == NULL) { + proxy_url.scheme = "http"; + proxy_url.scheme_len = strlen(proxy_url.scheme); + } + + if (strncmp(proxy_url.scheme, "http", proxy_url.scheme_len) != 0) { + ZITI_LOG(ERROR, "proxy type '%.*s' is not supported. 'http' is currently the only supported type", + (int)proxy_url.scheme_len, proxy_url.scheme); + return -1; + } + + char host[128], port[6]; + snprintf(host, sizeof(host), "%.*s", (int)proxy_url.hostname_len, proxy_url.hostname); + snprintf(port, sizeof(port), "%d", proxy_url.port); + tlsuv_connector_t *proxy = tlsuv_new_proxy_connector(tlsuv_PROXY_HTTP, host, port); + if (proxy_url.username) { + char user[128], passwd[128]; + snprintf(user, sizeof(user), "%.*s", (int)proxy_url.username_len, proxy_url.username); + snprintf(passwd, sizeof(passwd), "%.*s", (int)proxy_url.password_len, proxy_url.password); + proxy->set_auth(proxy, tlsuv_PROXY_BASIC, user, passwd); + } + ZITI_LOG(INFO, "connecting to OpenZiti controller and edge routers through proxy '%s:%s'", host, port); + tlsuv_set_global_connector(proxy); + + return 0; +} + static int run_opts(int argc, char *argv[]) { int c, option_index, errors = 0; optind = 0; bool identity_provided = false; - while ((c = getopt_long(argc, argv, "i:I:v:r:d:u:", + while ((c = getopt_long(argc, argv, "i:I:v:r:d:u:x:", run_options, &option_index)) != -1) { switch (c) { case 'i': { @@ -1889,7 +1967,7 @@ static int run_opts(int argc, char *argv[]) { identity_provided = true; break; case 'v': - setenv("ZITI_LOG", optarg, true); + configured_log_level = optarg; break; case 'r': { unsigned long interval = strtoul(optarg, NULL, 10); @@ -1902,8 +1980,11 @@ static int run_opts(int argc, char *argv[]) { case 'u': dns_upstream = optarg; break; + case 'x': + configured_proxy = optarg; + break; default: { - ZITI_LOG(ERROR, "Unknown option '%c'", c); + fprintf(stderr, "Unknown option '%c'\n", c); errors++; break; } @@ -1912,7 +1993,7 @@ static int run_opts(int argc, char *argv[]) { CHECK_COMMAND_ERRORS(errors); - printf("About to run tunnel service... %s", main_cmd.name); + fprintf(stderr, "About to run tunnel service... %s\n", main_cmd.name); ziti_set_app_info(main_cmd.name, ziti_tunneler_version()); return optind; @@ -1923,7 +2004,7 @@ static int run_host_opts(int argc, char *argv[]) { optind = 0; bool identity_provided = false; - while ((c = getopt_long(argc, argv, "i:I:v:r:", + while ((c = getopt_long(argc, argv, "i:I:v:r:x:", run_host_options, &option_index)) != -1) { switch (c) { case 'i': { @@ -1938,15 +2019,18 @@ static int run_host_opts(int argc, char *argv[]) { identity_provided = true; break; case 'v': - setenv("ZITI_LOG", optarg, true); + configured_log_level = optarg; break; case 'r': { unsigned long interval = strtoul(optarg, NULL, 10); ziti_set_refresh_interval(interval); break; } + case 'x': + configured_proxy = optarg; + break; default: { - ZITI_LOG(ERROR, "Unknown option '%c'", c); + fprintf(stderr, "Unknown option '%c'\n", c); errors++; break; } @@ -1959,7 +2043,7 @@ static int run_host_opts(int argc, char *argv[]) { CHECK_COMMAND_ERRORS(errors); - printf("About to run tunnel service that hosts services... %s", main_cmd.name); + fprintf(stderr, "About to run tunnel service that hosts services... %s\n", main_cmd.name); ziti_set_app_info(main_cmd.name, ziti_tunneler_version()); host_only = true; @@ -1983,31 +2067,30 @@ static void interrupt_handler(int sig) { #endif static void run(int argc, char *argv[]) { - uv_loop_t *ziti_loop = uv_default_loop(); - main_ziti_loop = ziti_loop; uv_cond_init(&stop_cond); uv_mutex_init(&stop_mutex); initialize_instance_config(); //set log level in precedence: command line flag (-v/--verbose) -> env var (ZITI_LOG) -> config file - int log_level = get_log_level(NULL); + int log_level = get_log_level(configured_log_level); + log_writer log_fn = NULL; - //initialize logger to INFO here. logger will be set further down #if _WIN32 - log_init(ziti_loop); - ziti_log_init(ziti_loop, INFO, ziti_log_writer); + // initialize log function here. level will be set further down + log_init(global_loop_ref); + log_fn = ziti_log_writer; remove_all_nrpt_rules(); signal(SIGINT, interrupt_handler); -#else - ziti_log_init(ziti_loop, log_level, NULL); #endif + ziti_log_init(global_loop_ref, log_level, log_fn); + // generate tunnel status instance and save active state and start time if (config_dir != NULL) { set_identifier_path(config_dir); - load_tunnel_status_from_file(ziti_loop); + load_tunnel_status_from_file(global_loop_ref); } uint32_t tun_ip; @@ -2069,7 +2152,7 @@ static void run(int argc, char *argv[]) { ZITI_LOG(INFO," - initialized at : %s (local time), %s (UTC)", time_val, time_str); ZITI_LOG(INFO," - log file location: %s", get_log_file_name()); ZITI_LOG(INFO,"============================================================================"); - move_config_from_previous_windows_backup(ziti_loop); + move_config_from_previous_windows_backup(global_loop_ref); ZITI_LOG(DEBUG, "granting se_debug privilege to current process to allow access to privileged processes during posture checks"); //ensure this process has the necessary access token to get the full path of privileged processes @@ -2078,25 +2161,26 @@ static void run(int argc, char *argv[]) { } #endif - // set log level from instance/config, if NULL is returned, the default log level will be used - const char* log_lvl = get_log_level_label(); - if (log_lvl != NULL) { - ziti_log_set_level_by_label(log_lvl); + if (configured_log_level == NULL) { + // set log level from instance/config, if NULL is returned, the default log level will be used + const char *log_lvl = get_log_level_label(); + if (log_lvl != NULL) { + ziti_log_set_level_by_label(log_lvl); + } } - ziti_tunnel_set_log_level(get_log_level(log_lvl)); + ziti_tunnel_set_log_level(ziti_log_level(NULL, NULL)); set_log_level(ziti_log_level_label()); ziti_tunnel_set_logger(ziti_logger); - if (ziti_loop == NULL) { - ZITI_LOG(ERROR, "failed to initialize default uv loop"); - exit(EXIT_FAILURE); + if (init_proxy_connector(configured_proxy) != 0) { + exit(1); } int rc; if (is_host_only()) { - rc = run_tunnel_host_mode(ziti_loop); + rc = run_tunnel_host_mode(global_loop_ref); } else { - rc = run_tunnel(ziti_loop, tun_ip, dns_ip, configured_cidr, dns_upstream); + rc = run_tunnel(global_loop_ref, tun_ip, dns_ip, configured_cidr, dns_upstream); } exit(rc); } @@ -2130,7 +2214,12 @@ static int version_opts(int argc, char *argv[]) { static void version() { if (verbose_version) { - printf("ziti-tunneler:\t%s\nziti-sdk:\t%s\n", ziti_tunneler_version(), ziti_get_version()->version); + tls_context *tls = default_tls_context("", 0); + printf("ziti-tunneler: %s\n" + "ziti-sdk: %s\n" + "tlsuv: %s[%s]\n", + ziti_tunneler_version(), ziti_get_version()->version, tlsuv_version(), tls->version()); + tls->free_ctx(tls); } else { printf("%s\n", ziti_tunneler_version()); } @@ -2148,28 +2237,46 @@ static char* config_file; static int parse_enroll_opts(int argc, char *argv[]) { static struct option opts[] = { - {"jwt", required_argument, NULL, 'j'}, - {"identity", required_argument, NULL, 'i'}, - {"key", required_argument, NULL, 'k'}, - {"cert", required_argument, NULL, 'c'}, - { "name", required_argument, NULL, 'n'} + { "jwt", required_argument, NULL, 'j'}, + { "identity", required_argument, NULL, 'i'}, + { "use-keychain", no_argument, NULL, 'K' }, + { "key", required_argument, NULL, 'k'}, + { "cert", required_argument, NULL, 'c'}, + { "name", required_argument, NULL, 'n'}, + { "proxy", required_argument, NULL, 'x' }, }; int c, option_index, errors = 0; + const char *proxy_arg = NULL; optind = 0; - while ((c = getopt_long(argc, argv, "j:i:k:c:n:", + while ((c = getopt_long(argc, argv, "j:i:Kk:c:n:x:", opts, &option_index)) != -1) { switch (c) { case 'j': enroll_opts.jwt = optarg; break; - case 'k': - enroll_opts.enroll_key = realpath(optarg, NULL); - if (enroll_opts.enroll_key == NULL) { - // may be pkcs11 key ref + case 'K': + enroll_opts.use_keychain = true; + break; + case 'k': { + uv_fs_t req = {}; + if (uv_fs_stat(NULL, &req, optarg, NULL) == 0 +#if defined(S_ISREG) + && (S_ISREG(req.statbuf.st_mode) +#if defined(S_ISLNK) + || S_ISLNK(req.statbuf.st_mode) +#endif + ) +#endif + ) { + enroll_opts.enroll_key = realpath(optarg, NULL); + } else { + // may be key ref (keychain/pkcs11) enroll_opts.enroll_key = optarg; } + uv_fs_req_cleanup(&req); break; + } case 'c': enroll_opts.enroll_cert = realpath(optarg, NULL); break; @@ -2179,6 +2286,9 @@ static int parse_enroll_opts(int argc, char *argv[]) { case 'i': config_file = optarg; break; + case 'x': + proxy_arg = optarg; + break; default: { fprintf(stderr, "Unknown option '%c'\n", c); errors++; @@ -2187,6 +2297,10 @@ static int parse_enroll_opts(int argc, char *argv[]) { } } + if (init_proxy_connector(proxy_arg) != 0) { + errors++; + } + if (enroll_opts.jwt == NULL || config_file == NULL) { errors++; } @@ -2241,8 +2355,11 @@ static int write_close(FILE *fp, const uv_buf_t *data) static void enroll(int argc, char *argv[]) { uv_loop_t *l = uv_loop_new(); - int log_level = get_log_level(NULL); - ziti_log_init(l, log_level, NULL); + int log_level = get_log_level(configured_log_level); + ziti_log_init(global_loop_ref, log_level, NULL); + if (init_proxy_connector(configured_proxy) != 0) { + exit(EXIT_FAILURE); + } if (config_file == 0) { ZITI_LOG(ERROR, "output file option(-i|--identity) is required"); @@ -2293,7 +2410,9 @@ static void enroll(int argc, char *argv[]) { } } -static tunnel_command *cmd; +static tunnel_command cmd = { + .show_result = true, // consistent with old behaviour +}; static int dump_opts(int argc, char *argv[]) { static struct option opts[] = { @@ -2304,8 +2423,7 @@ static int dump_opts(int argc, char *argv[]) { optind = 0; tunnel_ziti_dump *dump_options = calloc(1, sizeof(tunnel_ziti_dump)); - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_ZitiDump; + cmd.command = TunnelCommand_ZitiDump; while ((c = getopt_long(argc, argv, "i:p:", opts, &option_index)) != -1) { @@ -2327,7 +2445,7 @@ static int dump_opts(int argc, char *argv[]) { CHECK_COMMAND_ERRORS(errors); size_t json_len; - cmd->data = tunnel_ziti_dump_to_json(dump_options, MODEL_JSON_COMPACT, &json_len); + cmd.data = tunnel_ziti_dump_to_json(dump_options, MODEL_JSON_COMPACT, &json_len); if (dump_options != NULL) { free_tunnel_ziti_dump(dump_options); free(dump_options); @@ -2336,92 +2454,173 @@ static int dump_opts(int argc, char *argv[]) { return optind; } -static void on_response(uv_stream_t *s, ssize_t len, const uv_buf_t *b) { - if (len > 0) { - printf("received response <%.*s>\n", (int) len, b->base); - } else { - fprintf(stderr,"Read Response error %s\n", uv_err_name(len)); +static int ip_dump_opts(int argc, char *argv[]) { + static struct option opts[] = { + {"dump_path", required_argument, NULL, 'p'}, + }; + int c, option_index, errors = 0; + optind = 0; + + tunnel_ip_dump *dump_options = calloc(1, sizeof(tunnel_ip_dump)); + cmd.command = TunnelCommand_IpDump; + + while ((c = getopt_long(argc, argv, "p:", opts, &option_index)) != -1) { + switch (c) { + case 'p': + dump_options->dump_path = realpath(optarg, NULL); + break; + default: { + fprintf(stderr, "Unknown option '%c'\n", c); + errors++; + break; + } + } } - uv_read_stop(s); - free(b->base); - uv_close((uv_handle_t *)s, NULL); -} -void on_write(uv_write_t* req, int status) { - if (status < 0) { - fprintf(stderr,"Could not sent message to the tunnel. Write error %s\n", uv_err_name(status)); + CHECK_COMMAND_ERRORS(errors); + + size_t json_len; + cmd.data = tunnel_ip_dump_to_json(dump_options, MODEL_JSON_COMPACT, &json_len); + if (dump_options != NULL) { + free_tunnel_ip_dump(dump_options); + free(dump_options); } - free(req); -} -void send_message_to_pipe(uv_stream_t *pipe, char *msg) { - ZITI_LOG(VERBOSE, "Message...%s\n", msg); - uv_write_t *req = (uv_write_t*) malloc(sizeof(uv_write_t)); - req->data = msg; + return optind; +} - uv_buf_t bufs[2]; - bufs[0] = uv_buf_init(msg, strlen(msg)); - bufs[1] = uv_buf_init("\n", 1); +static int send_message_to_tunnel(char* message, bool show_result) { +#if _WIN32 + HANDLE cmd_soc = CreateFileA(cmdsockfile, + GENERIC_READ | GENERIC_WRITE, + 0, NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL); + if (cmd_soc == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); + fprintf(stderr, "failed to connect to pipe: %lu", err); + exit(1); + } +#else + uv_os_sock_t cmd_soc = socket(AF_UNIX, SOCK_STREAM, 0); + struct sockaddr_un addr = { + .sun_family = AF_UNIX, +#if __APPLE__ + .sun_len = sizeof(addr), +#endif + }; + strncpy(addr.sun_path, cmdsockfile, sizeof(addr.sun_path)); - int rc = uv_write(req, pipe, bufs, 2, on_write); - if (rc != 0) { - on_write(req, rc); + if (connect(cmd_soc, (const struct sockaddr *) &addr, sizeof(addr))) { + perror("cmd socket connect"); } -} -void on_connect(uv_connect_t* connect, int status){ - if (status < 0) { - fprintf(stderr, "failed to connect: %d/%s", status, uv_strerror(status)); - free(connect->data); - } else { - int res = uv_read_start((uv_stream_t *) connect->handle, cmd_alloc, on_response); - if (res != 0) { - fprintf(stderr, "UV read error %d/%s\n", res, uv_strerror(res)); +#endif + size_t msg_size = strlen(message); + size_t count = 0; + while (count < strlen(message)) { +#if _WIN32 + DWORD c; + if (!WriteFile(cmd_soc, message + count, msg_size - count, &c, NULL)) { + fprintf(stderr, "failed to write to pipe: %lu", GetLastError()); + exit(1); } - send_message_to_pipe(connect->handle, connect->data); +#else + ssize_t c; + c = write(cmd_soc, message + count, msg_size - count); +#endif + if (c < 0) { + perror("write command"); + exit(1); + } + count += c; } - free(connect); -} - -static uv_loop_t* connect_and_send_cmd(char pipesockfile[],uv_connect_t* connect, uv_pipe_t* client_handle) { - uv_loop_t* loop = uv_default_loop(); - int res = uv_pipe_init(loop, client_handle, 0); - if (res != 0) { - fprintf(stderr, "UV client handle init failed %d/%s\n", res, uv_strerror(res)); - return NULL; + struct json_tokener *parser = json_tokener_new(); + char buf[8*1024]; + struct json_object *json = NULL; + while(json == NULL) { +#if _WIN32 + DWORD c; + if (!ReadFile(cmd_soc, buf, sizeof(buf), &c, NULL)) { + fprintf(stderr, "failed to read from pipe: %lu", GetLastError()); + exit(1); + } +#else + ssize_t c = read(cmd_soc, buf, sizeof(buf)); +#endif + if (c < 0) { + perror("read resp"); + exit(1); + } + json = json_tokener_parse_ex(parser, buf, (int) c); + if (json == NULL) { + enum json_tokener_error e = json_tokener_get_error(parser); + if (e != json_tokener_continue) { + fprintf(stderr, "JSON parsing error: %s\n in payload: %.*s", + json_tokener_error_desc(e), (int)c, buf); + exit(1); + } + } } - uv_pipe_connect(connect, client_handle, pipesockfile, on_connect); + if (show_result) { + printf("%s\n", json_object_to_json_string_ext(json, JSON_C_TO_STRING_PRETTY)); + } + int code = json_object_get_boolean(json_object_object_get(json, "Success")) ? + 0 : json_object_get_int(json_object_object_get(json, "Code")); + json_object_put(json); + json_tokener_free(parser); - return loop; + return code; } -static void send_message_to_tunnel(char* message) { - uv_pipe_t client_handle; - uv_connect_t* connect = (uv_connect_t*)malloc(sizeof(uv_connect_t)); - connect->data = strdup(message); - - uv_loop_t* loop = connect_and_send_cmd(cmdsockfile, connect, &client_handle); +static void send_message_to_tunnel_fn(int argc, char *argv[]) { + char* json = tunnel_command_to_json(&cmd, MODEL_JSON_COMPACT, NULL); + int result = send_message_to_tunnel(json, cmd.show_result); + free_tunnel_command(&cmd); + free(json); + exit(result); +} - if (loop == NULL) { - fprintf(stderr, "Cannot run UV loop, loop is null"); - return; +// reusable parsing of a single required `-i` option +static char* get_identity_opt(int argc, char *argv[]) { + static struct option opts[] = { + {"identity", required_argument, NULL, 'i'}, + }; + int c, option_index, errors = 0; + optind = 0; + char *id = NULL; + while ((c = getopt_long(argc, argv, "i:", + opts, &option_index)) != -1) { + switch (c) { + case 'i': + id = optarg; + break; + default: { + fprintf(stderr, "Unknown option '%c'\n", c); + errors++; + break; + } + } } - int res = uv_run(loop, UV_RUN_DEFAULT); - if (res != 0) { - fprintf(stderr, "UV run error %s\n", uv_err_name(res)); + if (id == NULL) { + fprintf(stderr, "-i option is required"); + errors++; } + CHECK_COMMAND_ERRORS(errors); + return id; } -static void send_message_to_tunnel_fn(int argc, char *argv[]) { - char* json = tunnel_command_to_json(cmd, MODEL_JSON_COMPACT, NULL); - send_message_to_tunnel(json); - free_tunnel_command(cmd); - free(cmd); - cmd = NULL; - free(json); +static int ext_auth_opts(int argc, char *argv[]) { + tunnel_identity_id id = { + .identifier = (char*)get_identity_opt(argc, argv), + }; + + cmd.command = TunnelCommands.ExternalAuth; + cmd.data = tunnel_identity_id_to_json(&id, MODEL_JSON_COMPACT, NULL); + return optind; } static int on_off_identity_opts(int argc, char *argv[]) { @@ -2433,8 +2632,7 @@ static int on_off_identity_opts(int argc, char *argv[]) { optind = 0; tunnel_on_off_identity on_off_identity_options = {0}; - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_IdentityOnOff; + cmd.command = TunnelCommand_IdentityOnOff; while ((c = getopt_long(argc, argv, "i:o:", opts, &option_index)) != -1) { @@ -2461,7 +2659,7 @@ static int on_off_identity_opts(int argc, char *argv[]) { CHECK_COMMAND_ERRORS(errors); size_t json_len; - cmd->data = tunnel_on_off_identity_to_json(&on_off_identity_options, MODEL_JSON_COMPACT, &json_len); + cmd.data = tunnel_on_off_identity_to_json(&on_off_identity_options, MODEL_JSON_COMPACT, &json_len); on_off_identity_options.identifier = NULL; // don't try to free static memory (`optarg`) free_tunnel_on_off_identity(&on_off_identity_options); @@ -2469,69 +2667,27 @@ static int on_off_identity_opts(int argc, char *argv[]) { } static int enable_identity_opts(int argc, char *argv[]) { - static struct option opts[] = { - {"identity", required_argument, NULL, 'i'}, - }; - int c, option_index, errors = 0; - optind = 0; - - tunnel_load_identity load_identity_options = {0}; - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_LoadIdentity; - - while ((c = getopt_long(argc, argv, "i:", - opts, &option_index)) != -1) { - switch (c) { - case 'i': - load_identity_options.path = realpath(optarg, NULL); - break; - default: { - fprintf(stderr, "Unknown option '%c'\n", c); - errors++; - break; - } - } - } - CHECK_COMMAND_ERRORS(errors); + tunnel_load_identity load_identity_options = { + .path = realpath(get_identity_opt(argc, argv), NULL), + }; + cmd.command = TunnelCommand_LoadIdentity; size_t json_len; - cmd->data = tunnel_load_identity_to_json(&load_identity_options, MODEL_JSON_COMPACT, &json_len); + cmd.data = tunnel_load_identity_to_json(&load_identity_options, MODEL_JSON_COMPACT, &json_len); free_tunnel_load_identity(&load_identity_options); return optind; } static int enable_mfa_opts(int argc, char *argv[]) { - static struct option opts[] = { - {"identity", required_argument, NULL, 'i'}, + tunnel_identity_id id = { + .identifier = get_identity_opt(argc, argv), }; - int c, option_index, errors = 0; - optind = 0; - - tunnel_enable_mfa *enable_mfa_options = calloc(1, sizeof(tunnel_enable_mfa)); - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_EnableMFA; - - while ((c = getopt_long(argc, argv, "i:", - opts, &option_index)) != -1) { - switch (c) { - case 'i': - enable_mfa_options->identifier = optarg; - break; - default: { - fprintf(stderr, "Unknown option '%c'\n", c); - errors++; - break; - } - } - } - - CHECK_COMMAND_ERRORS(errors); + cmd.command = TunnelCommand_EnableMFA; size_t json_len; - cmd->data = tunnel_enable_mfa_to_json(enable_mfa_options, MODEL_JSON_COMPACT, &json_len); - free(enable_mfa_options); + cmd.data = tunnel_identity_id_to_json(&id, MODEL_JSON_COMPACT, &json_len); return optind; } @@ -2545,8 +2701,7 @@ static int verify_mfa_opts(int argc, char *argv[]) { optind = 0; tunnel_verify_mfa *verify_mfa_options = calloc(1, sizeof(tunnel_verify_mfa)); - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_VerifyMFA; + cmd.command = TunnelCommand_VerifyMFA; while ((c = getopt_long(argc, argv, "i:c:", opts, &option_index)) != -1) { @@ -2568,7 +2723,7 @@ static int verify_mfa_opts(int argc, char *argv[]) { CHECK_COMMAND_ERRORS(errors); size_t json_len; - cmd->data = tunnel_verify_mfa_to_json(verify_mfa_options, MODEL_JSON_COMPACT, &json_len); + cmd.data = tunnel_verify_mfa_to_json(verify_mfa_options, MODEL_JSON_COMPACT, &json_len); free(verify_mfa_options); return optind; @@ -2583,8 +2738,7 @@ static int remove_mfa_opts(int argc, char *argv[]) { optind = 0; tunnel_remove_mfa *remove_mfa_options = calloc(1, sizeof(tunnel_remove_mfa)); - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_RemoveMFA; + cmd.command = TunnelCommand_RemoveMFA; while ((c = getopt_long(argc, argv, "i:c:", opts, &option_index)) != -1) { @@ -2606,7 +2760,7 @@ static int remove_mfa_opts(int argc, char *argv[]) { CHECK_COMMAND_ERRORS(errors); size_t json_len; - cmd->data = tunnel_remove_mfa_to_json(remove_mfa_options, MODEL_JSON_COMPACT, &json_len); + cmd.data = tunnel_remove_mfa_to_json(remove_mfa_options, MODEL_JSON_COMPACT, &json_len); free(remove_mfa_options); return optind; @@ -2621,8 +2775,7 @@ static int submit_mfa_opts(int argc, char *argv[]) { optind = 0; tunnel_submit_mfa *submit_mfa_options = calloc(1, sizeof(tunnel_submit_mfa)); - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_SubmitMFA; + cmd.command = TunnelCommand_SubmitMFA; while ((c = getopt_long(argc, argv, "i:c:", opts, &option_index)) != -1) { @@ -2644,7 +2797,7 @@ static int submit_mfa_opts(int argc, char *argv[]) { CHECK_COMMAND_ERRORS(errors); size_t json_len; - cmd->data = tunnel_submit_mfa_to_json(submit_mfa_options, MODEL_JSON_COMPACT, &json_len); + cmd.data = tunnel_submit_mfa_to_json(submit_mfa_options, MODEL_JSON_COMPACT, &json_len); free(submit_mfa_options); return optind; @@ -2659,8 +2812,7 @@ static int generate_mfa_codes_opts(int argc, char *argv[]) { optind = 0; tunnel_generate_mfa_codes *mfa_codes_options = calloc(1, sizeof(tunnel_generate_mfa_codes)); - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_GenerateMFACodes; + cmd.command = TunnelCommand_GenerateMFACodes; while ((c = getopt_long(argc, argv, "i:c:", opts, &option_index)) != -1) { @@ -2682,7 +2834,7 @@ static int generate_mfa_codes_opts(int argc, char *argv[]) { CHECK_COMMAND_ERRORS(errors); size_t json_len; - cmd->data = tunnel_generate_mfa_codes_to_json(mfa_codes_options, MODEL_JSON_COMPACT, &json_len); + cmd.data = tunnel_generate_mfa_codes_to_json(mfa_codes_options, MODEL_JSON_COMPACT, &json_len); free(mfa_codes_options); return optind; @@ -2697,8 +2849,7 @@ static int get_mfa_codes_opts(int argc, char *argv[]) { optind = 0; tunnel_get_mfa_codes *get_mfa_codes_options = calloc(1, sizeof(tunnel_get_mfa_codes)); - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_GetMFACodes; + cmd.command = TunnelCommand_GetMFACodes; while ((c = getopt_long(argc, argv, "i:c:", opts, &option_index)) != -1) { @@ -2720,7 +2871,7 @@ static int get_mfa_codes_opts(int argc, char *argv[]) { CHECK_COMMAND_ERRORS(errors); size_t json_len; - cmd->data = tunnel_get_mfa_codes_to_json(get_mfa_codes_options, MODEL_JSON_COMPACT, &json_len); + cmd.data = tunnel_get_mfa_codes_to_json(get_mfa_codes_options, MODEL_JSON_COMPACT, &json_len); free(get_mfa_codes_options); return optind; @@ -2733,15 +2884,12 @@ static int set_log_level_opts(int argc, char *argv[]) { int c, option_index, errors = 0; optind = 0; - tunnel_set_log_level *log_level_options = calloc(1, sizeof(tunnel_set_log_level)); - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_SetLogLevel; - + tunnel_set_log_level log_level_options = {0}; while ((c = getopt_long(argc, argv, "l:", opts, &option_index)) != -1) { switch (c) { case 'l': - log_level_options->loglevel = optarg; + log_level_options.loglevel = optarg; break; default: { fprintf(stderr, "Unknown option '%c'\n", c); @@ -2751,11 +2899,17 @@ static int set_log_level_opts(int argc, char *argv[]) { } } + if (log_level_options.loglevel == NULL) { + fprintf(stderr, "symbolic level option(-l|--loglevel) is not specified, e.g., INFO, DEBUG\n"); + errors++; + } + CHECK_COMMAND_ERRORS(errors); + cmd.command = TunnelCommand_SetLogLevel; + size_t json_len; - cmd->data = tunnel_set_log_level_to_json(log_level_options, MODEL_JSON_COMPACT, &json_len); - free(log_level_options); + cmd.data = tunnel_set_log_level_to_json(&log_level_options, MODEL_JSON_COMPACT, &json_len); return optind; } @@ -2770,8 +2924,7 @@ static int update_tun_ip_opts(int argc, char *argv[]) { optind = 0; tunnel_tun_ip_v4 *tun_ip_v4_options = calloc(1, sizeof(tunnel_tun_ip_v4)); - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_UpdateTunIpv4; + cmd.command = TunnelCommand_UpdateTunIpv4; while ((c = getopt_long(argc, argv, "t:p:d:", opts, &option_index)) != -1) { @@ -2800,7 +2953,7 @@ static int update_tun_ip_opts(int argc, char *argv[]) { CHECK_COMMAND_ERRORS(errors); size_t json_len; - cmd->data = tunnel_tun_ip_v4_to_json(tun_ip_v4_options, MODEL_JSON_COMPACT, &json_len); + cmd.data = tunnel_tun_ip_v4_to_json(tun_ip_v4_options, MODEL_JSON_COMPACT, &json_len); free(tun_ip_v4_options); return optind; @@ -2815,8 +2968,7 @@ static int endpoint_status_change_opts(int argc, char *argv[]) { optind = 0; tunnel_status_change *tunnel_status_change_opts = calloc(1, sizeof(tunnel_status_change)); - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_StatusChange; + cmd.command = TunnelCommand_StatusChange; while ((c = getopt_long(argc, argv, "w:u:", opts, &option_index)) != -1) { @@ -2846,7 +2998,7 @@ static int endpoint_status_change_opts(int argc, char *argv[]) { CHECK_COMMAND_ERRORS(errors); size_t json_len; - cmd->data = tunnel_status_change_to_json(tunnel_status_change_opts, MODEL_JSON_COMPACT, &json_len); + cmd.data = tunnel_status_change_to_json(tunnel_status_change_opts, MODEL_JSON_COMPACT, &json_len); free(tunnel_status_change_opts); return optind; @@ -2856,7 +3008,7 @@ static int endpoint_status_change_opts(int argc, char *argv[]) { static void service_control(int argc, char *argv[]) { tunnel_service_control *tunnel_service_control_opt = calloc(1, sizeof(tunnel_service_control)); - if (parse_tunnel_service_control(tunnel_service_control_opt, cmd->data, strlen(cmd->data)) < 0) { + if (parse_tunnel_service_control(tunnel_service_control_opt, cmd.data, strlen(cmd.data)) < 0) { fprintf(stderr, "Could not fetch service control data"); return; } @@ -2878,8 +3030,7 @@ static int svc_opts(int argc, char *argv[]) { }; tunnel_service_control *tunnel_service_control_options = calloc(1, sizeof(tunnel_service_control)); - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_ServiceControl; + cmd.command = TunnelCommand_ServiceControl; int c, option_index, errors = 0; optind = 0; @@ -2902,7 +3053,7 @@ static int svc_opts(int argc, char *argv[]) { CHECK_COMMAND_ERRORS(errors); size_t json_len; - cmd->data = tunnel_service_control_to_json(tunnel_service_control_options, MODEL_JSON_COMPACT, &json_len); + cmd.data = tunnel_service_control_to_json(tunnel_service_control_options, MODEL_JSON_COMPACT, &json_len); return optind; } @@ -2911,42 +3062,19 @@ static int svc_opts(int argc, char *argv[]) { static int get_status_opts(int argc, char *argv[]) { optind = 0; - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_Status; + cmd.command = TunnelCommand_Status; return optind; } static int delete_identity_opts(int argc, char *argv[]) { - static struct option opts[] = { - {"identity", required_argument, NULL, 'i'}, + tunnel_identity_id id = { + .identifier = get_identity_opt(argc, argv), }; - int c, option_index, errors = 0; - optind = 0; - - tunnel_delete_identity *delete_identity_options = calloc(1, sizeof(tunnel_delete_identity)); - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_RemoveIdentity; - - while ((c = getopt_long(argc, argv, "i:", - opts, &option_index)) != -1) { - switch (c) { - case 'i': - delete_identity_options->identifier = optarg; - break; - default: { - fprintf(stderr, "Unknown option '%c'\n", c); - errors++; - break; - } - } - } - - CHECK_COMMAND_ERRORS(errors); + cmd.command = TunnelCommand_RemoveIdentity; size_t json_len; - cmd->data = tunnel_delete_identity_to_json(delete_identity_options, MODEL_JSON_COMPACT, &json_len); - free(delete_identity_options); + cmd.data = tunnel_identity_id_to_json(&id, MODEL_JSON_COMPACT, &json_len); return optind; } @@ -2954,6 +3082,7 @@ static int delete_identity_opts(int argc, char *argv[]) { static int add_identity_opts(int argc, char *argv[]) { static struct option opts[] = { + {"use-keychain", no_argument, NULL, 'K' }, {"identity", required_argument, NULL, 'i'}, {"jwt", required_argument, NULL, 'j'}, }; @@ -2961,12 +3090,14 @@ static int add_identity_opts(int argc, char *argv[]) { optind = 0; tunnel_add_identity *tunnel_add_identity_opt = calloc(1, sizeof(tunnel_add_identity)); - cmd = calloc(1, sizeof(tunnel_command)); - cmd->command = TunnelCommand_AddIdentity; + cmd.command = TunnelCommand_AddIdentity; - while ((c = getopt_long(argc, argv, "i:j:", + while ((c = getopt_long(argc, argv, "Ki:j:", opts, &option_index)) != -1) { switch (c) { + case 'K': + tunnel_add_identity_opt->useKeychain = true; + break; case 'i': tunnel_add_identity_opt->jwtFileName = optarg; break; @@ -2984,17 +3115,18 @@ static int add_identity_opts(int argc, char *argv[]) { CHECK_COMMAND_ERRORS(errors); size_t json_len; - cmd->data = tunnel_add_identity_to_json(tunnel_add_identity_opt, MODEL_JSON_COMPACT, &json_len); + cmd.data = tunnel_add_identity_to_json(tunnel_add_identity_opt, MODEL_JSON_COMPACT, &json_len); free(tunnel_add_identity_opt); return optind; } - static CommandLine enroll_cmd = make_command("enroll", "enroll Ziti identity", "-j|--jwt -i|--identity [-k|--key [-c|--cert ]] [-n|--name ]", "\t-j|--jwt\tenrollment token file\n" + "\t-x|--proxy type://[username[:password]@]hostname_or_ip:port\tproxy to use when connecting to OpenZiti controller. 'http' is currently the only supported type.\n" "\t-i|--identity\toutput identity file\n" + "\t-K|--use-keychain\tuse keychain to generate/store private key\n" "\t-k|--key\tprivate key for enrollment\n" "\t-c|--cert\tcertificate for enrollment\n" "\t-n|--name\tidentity name\n", @@ -3003,6 +3135,8 @@ static CommandLine run_cmd = make_command("run", "run Ziti tunnel (required supe "-i [-r N] [-v N] [-d|--dns-ip-range N.N.N.N/n]", "\t-i|--identity \trun with provided identity file (required)\n" "\t-I|--identity-dir \tload identities from provided directory\n" + "\t-x|--proxy type://[username[:password]@]hostname_or_ip:port\tproxy to use when" + " connecting to OpenZiti controller and edge routers. 'http' is currently the only supported type." "\t-v|--verbose N\tset log level, higher level -- more verbose (default 3)\n" "\t-r|--refresh N\tset service polling interval in seconds (default 10)\n" "\t-d|--dns-ip-range \tspecify CIDR block in which service DNS names" @@ -3012,12 +3146,16 @@ static CommandLine run_host_cmd = make_command("run-host", "run Ziti tunnel to h "-i [-r N] [-v N]", "\t-i|--identity \trun with provided identity file (required)\n" "\t-I|--identity-dir \tload identities from provided directory\n" + "\t-x|--proxy type://[username[:password]@]hostname_or_ip:port\tproxy to use when" + " connecting to OpenZiti controller and edge routers" "\t-v|--verbose N\tset log level, higher level -- more verbose (default 3)\n" "\t-r|--refresh N\tset service polling interval in seconds (default 10)\n", run_host_opts, run); static CommandLine dump_cmd = make_command("dump", "dump the identities information", "[-i ] [-p ]", "\t-i|--identity\tdump identity info\n" "\t-p|--dump_path\tdump into path\n", dump_opts, send_message_to_tunnel_fn); +static CommandLine ip_dump_cmd = make_command("ip_dump", "dump ip stack information", "[-p ]", + "\t-p|--dump_path\tdump into path\n", ip_dump_opts, send_message_to_tunnel_fn); static CommandLine on_off_id_cmd = make_command("on_off_identity", "enable/disable the identities information", "-i -o t|f", "\t-i|--identity\tidentity info that needs to be enabled/disabled\n" "\t-o|--onoff\t't' or 'f' to enable or disable the identity\n", on_off_identity_opts, send_message_to_tunnel_fn); @@ -3043,9 +3181,11 @@ static CommandLine get_mfa_codes_cmd = make_command("get_mfa_codes", "Get MFA co static CommandLine get_status_cmd = make_command("tunnel_status", "Get Tunnel Status", "", "", get_status_opts, send_message_to_tunnel_fn); static CommandLine delete_id_cmd = make_command("delete", "delete the identities information", "[-i ]", "\t-i|--identity\tidentity info that needs to be deleted\n", delete_identity_opts, send_message_to_tunnel_fn); -static CommandLine add_id_cmd = make_command("add", "enroll and load the identities information", "[-i ]", - "\t-i|--identity\tfile name for the identity file that will be generated\n" - "\t-j|--jwt\tjwt content that needs to be enrolled\n", add_identity_opts, send_message_to_tunnel_fn); +static CommandLine add_id_cmd = make_command("add", "enroll and load the identity", "-j -i ", + "\t-K|--use-keychain\tuse keychain to generate/store private key\n" + "\t-j|--jwt\tenrollment token content\n" + "\t-i|--identity\toutput identity .json file (relative to \"-I\" config directory)\n", + add_identity_opts, send_message_to_tunnel_fn); static CommandLine set_log_level_cmd = make_command("set_log_level", "Set log level of the tunneler", "-l ", "\t-l|--loglevel\tlog level of the tunneler\n", set_log_level_opts, send_message_to_tunnel_fn); static CommandLine update_tun_ip_cmd = make_command("update_tun_ip", "Update tun ip of the tunneler", "[-t ] [-p ] [-d ]", @@ -3055,6 +3195,12 @@ static CommandLine update_tun_ip_cmd = make_command("update_tun_ip", "Update tun static CommandLine ep_status_change_cmd = make_command("endpoint_sts_change", "send endpoint status change message to the tunneler", "[-w ] [-u ]", "\t-w|--wake\twake the tunneler\n" "\t-u|--unlock\tunlock the tunneler\n", endpoint_status_change_opts, send_message_to_tunnel_fn); +static CommandLine ext_auth_login = make_command( + "ext-jwt-login", + "login with ext JWT signer", "-i ", + "\t-i|--identity\tidentity to authenticate\n", + ext_auth_opts, send_message_to_tunnel_fn); + #if _WIN32 static CommandLine service_control_cmd = make_command("service_control", "execute service control functions for Ziti tunnel (required superuser access)", "-o|--operation